From 88496071ae08d0e0e4edab3fbb7cc537c79db88a Mon Sep 17 00:00:00 2001 From: Ilyaas Kapadia <86218345+IlyaasK@users.noreply.github.com> Date: Fri, 29 May 2026 15:37:08 -0400 Subject: [PATCH] Add MCP API key management tool --- src/lib/mcp/register.ts | 2 + src/lib/mcp/tools/api-keys.ts | 166 ++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 src/lib/mcp/tools/api-keys.ts diff --git a/src/lib/mcp/register.ts b/src/lib/mcp/register.ts index 644150c..161148c 100644 --- a/src/lib/mcp/register.ts +++ b/src/lib/mcp/register.ts @@ -1,5 +1,6 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { registerKernelPrompts } from "@/lib/mcp/prompts"; +import { registerAPIKeyCapabilities } from "@/lib/mcp/tools/api-keys"; import { registerAppCapabilities } from "@/lib/mcp/tools/apps"; import { registerBrowserPoolCapabilities } from "@/lib/mcp/tools/browser-pools"; import { registerBrowserCapabilities } from "@/lib/mcp/tools/browsers"; @@ -18,6 +19,7 @@ export function registerMcpCapabilities(server: McpServer) { registerDocsTools(server); registerBrowserCapabilities(server); registerProjectCapabilities(server); + registerAPIKeyCapabilities(server); registerBrowserPoolCapabilities(server); registerProxyTools(server); registerExtensionTools(server); diff --git a/src/lib/mcp/tools/api-keys.ts b/src/lib/mcp/tools/api-keys.ts new file mode 100644 index 0000000..ec7bd26 --- /dev/null +++ b/src/lib/mcp/tools/api-keys.ts @@ -0,0 +1,166 @@ +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { createKernelClient } from "@/lib/mcp/kernel-client"; + +export function registerAPIKeyCapabilities(server: McpServer) { + // manage_api_keys -- Create, list, get, update, and delete Kernel API keys + server.tool( + "manage_api_keys", + 'Manage Kernel API keys. Use "create" to create an org-wide or project-scoped key, "list" to discover masked keys, "get" to retrieve one masked key, "update" to rename a key, or "delete" to revoke a key. Created keys include the plaintext key once.', + { + action: z + .enum(["create", "list", "get", "update", "delete"]) + .describe("Operation to perform."), + api_key_id: z + .string() + .describe("API key ID. Required for get, update, and delete.") + .optional(), + name: z.string().describe("(create, update) API key name.").optional(), + project_id: z + .string() + .nullable() + .describe( + "(create) Project ID for project-scoped keys. Omit or use null for org-wide keys.", + ) + .optional(), + days_to_expire: z + .number() + .int() + .min(1) + .max(3650) + .nullable() + .describe( + "(create) Days until expiry, up to 3650. Use null for no expiry.", + ) + .optional(), + limit: z.number().describe("(list) Max results per page.").optional(), + offset: z.number().describe("(list) Pagination offset.").optional(), + }, + async (params, extra) => { + if (!extra.authInfo) throw new Error("Authentication required"); + const client = createKernelClient(extra.authInfo.token); + + try { + switch (params.action) { + case "create": { + if (!params.name) { + return { + content: [ + { type: "text", text: "Error: name is required for create." }, + ], + }; + } + const createParams: Parameters[0] = { + name: params.name, + }; + if (params.project_id !== undefined) { + createParams.project_id = params.project_id; + } + if (params.days_to_expire !== undefined) { + createParams.days_to_expire = params.days_to_expire; + } + const apiKey = await client.apiKeys.create(createParams); + return { + content: [ + { type: "text", text: JSON.stringify(apiKey, null, 2) }, + ], + }; + } + case "list": { + const page = await client.apiKeys.list({ + ...(params.limit !== undefined && { limit: params.limit }), + ...(params.offset !== undefined && { offset: params.offset }), + }); + const items = page.getPaginatedItems(); + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + items, + has_more: page.has_more, + next_offset: page.next_offset, + }, + null, + 2, + ), + }, + ], + }; + } + case "get": { + if (!params.api_key_id) { + return { + content: [ + { + type: "text", + text: "Error: api_key_id is required for get.", + }, + ], + }; + } + const apiKey = await client.apiKeys.retrieve(params.api_key_id); + return { + content: [ + { type: "text", text: JSON.stringify(apiKey, null, 2) }, + ], + }; + } + case "update": { + if (!params.api_key_id) { + return { + content: [ + { + type: "text", + text: "Error: api_key_id is required for update.", + }, + ], + }; + } + if (!params.name) { + return { + content: [ + { type: "text", text: "Error: name is required for update." }, + ], + }; + } + const apiKey = await client.apiKeys.update(params.api_key_id, { + name: params.name, + }); + return { + content: [ + { type: "text", text: JSON.stringify(apiKey, null, 2) }, + ], + }; + } + case "delete": { + if (!params.api_key_id) { + return { + content: [ + { + type: "text", + text: "Error: api_key_id is required for delete.", + }, + ], + }; + } + await client.apiKeys.delete(params.api_key_id); + return { + content: [{ type: "text", text: "API key deleted successfully" }], + }; + } + } + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error in manage_api_keys (${params.action}): ${error instanceof Error ? error.message : String(error)}`, + }, + ], + }; + } + }, + ); +}