Files
openrusrouter-mcp/src/tools/keys.ts
T
vasyanskandClaude Opus 5.5 d7d02267e2 Добавь 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>
2026-10-24 10:59:06 +07:00

62 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Инструмент 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) });
}),
);
}