Добавь HTTP-клиент, каталог моделей и шесть read-only инструментов MCP

Клиент — замороженный объект с семью методами GET, без универсального
запроса и тела; денежные поля проверяются на строковый тип (fail-closed).
Каталог джойнит /api/models и /v1/models: модальность и рубли из первого,
USD и доступность ключу из второго. Инструменты: list_models, pick_model,
estimate_cost, get_balance, get_usage, list_api_keys. Точка входа stdio
уводит console.log/info в stderr первым импортом.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
vasyanskandClaude Opus 5.5 committed 2026-10-24 10:59:06 +07:00
1 parent ade6eb82df
commit d7d02267e2
18 files changed
+1935

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
// Инструмент list_api_keys — только чтение списка ключей аккаунта.
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import type { ApiKeyEntry, Client } from "../client.js";
import { cleanText } from "../sanitize.js";
import { READ_ONLY_ANNOTATIONS, guarded, okResult } from "./common.js";
export const KEYS_TOOL_DESCRIPTION =
"Список API-ключей аккаунта openrusrouter.ru: префикс, лимит расхода и его окно, израсходовано, разрешённые модели. " +
"Полный ключ не выводится никогда, только префикс; is_current_key отмечает ключ, которым работает этот MCP. " +
"allowed_models: null вместе с all_models_allowed: true означает, что ключу доступны все модели. " +
"Создать, отозвать или изменить ключ через MCP нельзя — такие действия требуют браузерной сессии на openrusrouter.ru. " +
"Это сознательное ограничение безопасности: ключ, которым пользуется MCP, не должен уметь управлять другими ключами или собой";
export function presentKeys(
keys: readonly ApiKeyEntry[],
currentApiKey: string,
includeRevoked: boolean,
): Record<string, unknown>[] {
return keys
.filter((k) => includeRevoked || k.revoked_at === null)
.map((k) => {
const allowed = Array.isArray(k.allowed_models) && k.allowed_models.length > 0 ? k.allowed_models : null;
return {
id: k.id,
name: cleanText(typeof k.name === "string" ? k.name : "", 200),
key_prefix: k.prefix,
created_at: k.created_at,
last_used_at: k.last_used_at ?? null,
revoked_at: k.revoked_at ?? null,
spend_window: k.spend_window === "" ? null : k.spend_window,
spend_limit_rub: k.spend_limit_rub,
spend_used_rub: k.spend_used,
spend_resets_at: k.spend_resets_at ?? null,
allowed_models: allowed === null ? null : allowed.map((m) => cleanText(String(m), 200)),
all_models_allowed: allowed === null,
// Сравнение локальное: сам ключ в результат не попадает.
is_current_key: k.prefix !== "" && currentApiKey.startsWith(k.prefix),
};
});
}
export function registerKeyTools(server: McpServer, client: Client, currentApiKey: string): void {
server.registerTool(
"list_api_keys",
{
title: "API-ключи аккаунта",
description: KEYS_TOOL_DESCRIPTION,
inputSchema: {
include_revoked: z.boolean().default(false),
},
annotations: READ_ONLY_ANNOTATIONS,
},
(args) =>
guarded(async () => {
const { keys } = await client.getKeys();
return okResult({ keys: presentKeys(keys, currentApiKey, args.include_revoked) });
}),
);
}