8083b0b8a8
CI / Build & Test (push) Successful in 17s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
98 lines
3.3 KiB
TypeScript
98 lines
3.3 KiB
TypeScript
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { z } from "zod";
|
|
import type { BetterNewsClient } from "../client.js";
|
|
import { handleTool } from "../utils.js";
|
|
|
|
const SCOPES = [
|
|
"news:read",
|
|
"news:write",
|
|
"news:moderate",
|
|
"news:comment",
|
|
"sources:read",
|
|
"sources:write",
|
|
"bookmarks:write",
|
|
"tickets:read",
|
|
"tickets:write",
|
|
"tickets:moderate",
|
|
"mod:queue",
|
|
"mod:stats",
|
|
"mod:users",
|
|
"blog:write",
|
|
"user:profile",
|
|
"user:apikeys",
|
|
"privacy:read",
|
|
"files:write",
|
|
] as const;
|
|
|
|
export function registerApiKeyTools(server: McpServer, client: BetterNewsClient) {
|
|
server.tool(
|
|
"list_api_keys",
|
|
"List API keys for the authenticated user. Requires user:apikeys scope.",
|
|
{},
|
|
handleTool(async () => {
|
|
const data = await client.get<unknown>("/api/user/api-keys");
|
|
return JSON.stringify(data, null, 2);
|
|
}),
|
|
);
|
|
|
|
server.tool(
|
|
"create_api_key",
|
|
"Create a new API key. The raw key value is returned only once. Requires cookie-based session (not another API key) and user:apikeys scope.",
|
|
{
|
|
name: z.string().min(1).max(50).describe("Human-readable name for this key"),
|
|
scopes: z
|
|
.array(z.enum(SCOPES))
|
|
.optional()
|
|
.describe("Permission scopes. Empty array = full access (inherits your role)."),
|
|
expiresAt: z
|
|
.string()
|
|
.datetime()
|
|
.nullable()
|
|
.optional()
|
|
.describe("Expiry date (ISO 8601). Null = never expires."),
|
|
},
|
|
handleTool(async (body) => {
|
|
const data = await client.post<unknown>("/api/user/api-keys", body);
|
|
return JSON.stringify(data, null, 2);
|
|
}),
|
|
);
|
|
|
|
server.tool(
|
|
"update_api_key",
|
|
"Update an API key's name, scopes, or expiry. Requires user:apikeys scope.",
|
|
{
|
|
keyId: z.string().uuid().describe("API key UUID"),
|
|
name: z.string().min(1).max(50).optional().describe("New name"),
|
|
scopes: z.array(z.enum(SCOPES)).optional().describe("New scopes list"),
|
|
expiresAt: z
|
|
.string()
|
|
.datetime()
|
|
.nullable()
|
|
.optional()
|
|
.describe("New expiry date (ISO 8601). Null = never expires."),
|
|
},
|
|
handleTool(async ({ keyId, ...body }) => {
|
|
const data = await client.patch<unknown>(`/api/user/api-keys/${keyId}`, body);
|
|
return JSON.stringify(data, null, 2);
|
|
}),
|
|
);
|
|
|
|
server.tool(
|
|
"revoke_api_key",
|
|
"Revoke or permanently delete an API key. Pass hardDelete=true to permanently remove it; otherwise it is soft-revoked and stays visible in the list. Requires user:apikeys scope.",
|
|
{
|
|
keyId: z.string().uuid().describe("API key UUID"),
|
|
hardDelete: z
|
|
.boolean()
|
|
.optional()
|
|
.describe("True to permanently delete; false (default) to soft-revoke"),
|
|
},
|
|
handleTool(async ({ keyId, hardDelete }) => {
|
|
const data = await client.delete<unknown>(
|
|
`/api/user/api-keys/${keyId}${hardDelete ? "?hardDelete=true" : ""}`,
|
|
);
|
|
return JSON.stringify(data, null, 2);
|
|
}),
|
|
);
|
|
}
|