Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/lib/mcp/register.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -18,6 +19,7 @@ export function registerMcpCapabilities(server: McpServer) {
registerDocsTools(server);
registerBrowserCapabilities(server);
registerProjectCapabilities(server);
registerAPIKeyCapabilities(server);
registerBrowserPoolCapabilities(server);
registerProxyTools(server);
registerExtensionTools(server);
Expand Down
166 changes: 166 additions & 0 deletions src/lib/mcp/tools/api-keys.ts
Original file line number Diff line number Diff line change
@@ -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<typeof client.apiKeys.create>[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)}`,
},
],
};
}
},
);
}
Loading