// Инструмент 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[] { 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) }); }), ); }