Add MCP API key management tool#109
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Monitoring Plan: API key management via MCP toolWhat this PR does: Lets AI agents create, list, rename, and delete Kernel API keys directly through the MCP protocol, without leaving their context. Intended effect:
Risks:
Status updates will be posted automatically on this PR as monitoring progresses. |
rgarcia
left a comment
There was a problem hiding this comment.
Approving. Verified the tool against @onkernel/sdk@0.58.0:
- All five calls match the SDK surface (
create/list/retrieve/update/delete, param shapes, and the offset-pagination accessorsgetPaginatedItems/has_more/next_offset). - Masking holds at the type level: read paths return
APIKey(masked_keyonly); onlycreatereturnsCreatedAPIKeywith the plaintextkey. - Consistent with the established
manage_*action-multiplexed pattern (mirrorsmanage_projectsclosely).
Non-blocking notes (most inherited from manage_projects, so not regressions — worth addressing across the family rather than blocking here):
- Error results (both validation guards and the
catch) return plain text content withoutisError: true, so the protocol-level result reads as success. Pre-existing across these tools. limit/offsetusez.number()rather than.int()(days_to_expirecorrectly uses.int().min(1).max(3650)).- Security surface is already noted in the description: any valid MCP token can mint org-wide keys and delete keys (API-authorization concern, not fixable in the tool layer), and the plaintext key lands in model context/transcript — the "treat as secret" guidance is prompt-level, not enforced.
If schema-level guarantees are ever wanted over prose-level ((create, update) …) descriptions, a discriminated union via registerTool({ inputSchema }) is the upgrade path, but I wouldn't churn the whole repo for it.
Mark API-key tool validation and caught SDK failures as MCP error results so callers do not see failed operations as successful text responses. Require integer pagination inputs at the MCP schema boundary so agents get immediate validation feedback before invoking the SDK.
Move text, JSON, paginated, and error tool responses into one MCP response helper so projects and API keys do not drift in protocol semantics. Use the shared helper in manage_projects and manage_api_keys, including isError results for validation and caught SDK failures.
Use one shared MCP pagination schema for projects and API keys so both tools match the API's forgiving pagination contract. API keys now rely on the backend clamp behavior instead of rejecting values that projects pass through.
masnwilliams
left a comment
There was a problem hiding this comment.
reviewed — code is clean and consistent with the manage_* pattern, typechecks, SDK calls verified. zod validation on days_to_expire (.int().min(1).max(3650)) is a nice touch. import + registration correct.
the substantive discussion here is agent-fit, not code quality.
agent-fit (worth a product decision)
create(api-keys.ts:62) mints a long-lived credential and returns the plaintextkeyonce — which the agent then echoes into the MCP response / chat transcript. a key that outlives the conversation landing in logs/history is a real secret-hygiene concern.delete(api-keys.ts:148) revokes a key — an agent revoking a key in active use can break a running integration.
read side is well-guarded (list/get only return masked_key). for reference, how established servers handle this:
- Stripe exposes destructive writes (refunds) but relies on server-side scope enforcement — a read-only restricted key physically can't mutate.
- GitHub keeps secrets write-only (values never readable) and routes the token via env var, not a tool arg; also supports
--read-onlyand--toolsetsgating.
suggestions: (1) add MCP annotations (destructiveHint on delete, readOnlyHint on list/get); (2) confirm a read-only Kernel API key rejects apiKeys.create/delete server-side so the guarantee doesn't depend on the model; (3) consider toolset gating so a deployment can opt out of key management entirely.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f838f6e. Configure here.
masnwilliams
left a comment
There was a problem hiding this comment.
follow-up review — the annotations + gating additions address the agent-fit feedback well. nice work, and the shared paginationParams / paginatedJsonResponse / toolErrorResponse cleanup is a bonus. typechecks clean. a few notes, none blocking on this PR's own code:
coordinate before merge
- collides with #112. both PRs independently add
src/lib/mcp/responses.ts(not on main) and refactorregister.ts. whichever lands second will hard-conflict. worth deciding merge order now and rebasing the other — ideally one of them ownsresponses.tsand the toolset-registration refactor, and the other builds on it.
suggestions
openWorldHint: trueonmanage_api_keysis debatable — that hint signals interaction with an external open world (web search, third-party sites). key management hits Kernel's own API, sofalsereads more accurately. the hint genuinely fits something likebrowser_curl/manage_auth_connections.- gating is a denylist (
KERNEL_MCP_DISABLED_TOOLSETS), so new/sensitive tools are exposed by default. GitHub's--toolsetsis an allowlist (default-deny), which is safer for credential-type tools. denylist is a legit choice for backwards-compat — just flagging it as a conscious call, especially since #104 (managed-auth) will ride on this same mechanism. destructiveHint: trueon a multiplexedmanage_*tool means clients will prompt for confirmation even onlist/get. acceptable conservative tradeoff given one tool spans read + write; only fixable by splitting reads into their own tool, which probably isn't worth it.
the gating mechanism itself is clean — type-safe registration table, alias resolution, all/none, hard error on unknown values. good to go on my end once the #112 overlap is sorted.
masnwilliams
left a comment
There was a problem hiding this comment.
approving — annotations + the env-driven toolset gating fully address the agent-fit feedback, and the openWorldHint correction lands it accurately. clean and typechecks. one non-code item to settle outside this PR: coordinate merge order with #112 since both add responses.ts and refactor register.ts (whichever merges second will need a rebase).
|
No issues detected at 1h post-deploy across Production. Signals (all healthy):
Risks watched (all clean): Auth forwarding, key exposure in errors, 500s on create/delete, MCP server stability No |
|
Intended effect confirmed at ~24h post-deploy in Production. Signals:
Risks watched (all clean): Auth forwarding, key exposure in error logs, 500s on create/delete, MCP server stability. The |
|
Verdict: no_issue after 72h (3.003 days) across Production. Final evidence:
The |

Summary
manage_api_keysMCP tool for create/list/get/update/deleteproject_idand optionaldays_to_expiresrc/lib/mcp/responses.tssrc/lib/mcp/schemas.ts, matching the API's backend-clamped pagination behaviorisError: trueAgent Experience / Flow
This PR lets agents provision credentials for automation without leaving MCP. The important agent experience detail is that API-key creation returns the plaintext key once, while every later read path returns masked metadata only.
Typical project-scoped flow:
manage_projects listormanage_projects createand capturesproject_id.manage_api_keys create name=<purposeful-name> project_id=<project_id> days_to_expire=<n>when a downstream workflow needs a project-scoped key.manage_api_keys getorlist; these return masked values suitable for audit and UI display.manage_api_keys updatefor name changes anddeletefor revocation when the workflow is complete.Typical org-wide flow:
manage_api_keys create name=<purposeful-name>withoutproject_idonly when org-wide access is explicitly intended.days_to_expirefor temporary automation keys so cleanup does not depend only on manual deletion.Agent safety notes:
manage_projectsandmanage_api_keysnow share the same response helpers, pagination envelope, pagination input schema, and failure semantics across the project-to-key workflow.Authorization Boundary
MCP forwards the caller's bearer token to the Kernel SDK/API and does not implement a separate API-key authorization policy. The API must remain the source of truth for who may create org-wide keys, create project-scoped keys, read key metadata, update names, or revoke keys.
API-side authorization audit/enforcement is tracked separately in kernel/kernel: https://github.com/kernel/kernel/issues/2297
Validation
bunx prettier --check src/lib/mcp/schemas.ts src/lib/mcp/responses.ts src/lib/mcp/tools/api-keys.ts src/lib/mcp/tools/projects.tsgit diff --checkKERNEL_CLI_PROD_CLIENT_ID=dummy KERNEL_CLI_STAGING_CLIENT_ID=dummy KERNEL_CLI_DEV_CLIENT_ID=dummy NEXT_PUBLIC_CLERK_DOMAIN=example.clerk.accounts.dev NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXhhbXBsZS5jbGVyay5hY2NvdW50cy5kZXYk CLERK_SECRET_KEY=sk_test_dummy REDIS_URL=redis://localhost:6379 bun run buildhttp://127.0.0.1:3002/mcpwithAPI_BASE_URL=http://127.0.0.1:3001: initialized MCP, verifiedmanage_api_keyswas listed, created a scratch project, created a project-scoped API key, confirmed plaintext key is returned on create only, got/updated/listed/deleted the key, then deleted the scratch projectNotes
bun run format:checkstill fails on pre-existingAGENTS.mdformatting outside this PR scope.bun run buildattempt failed only becausenext/fontcould not fetch Google Fonts; the rerun with network access passed.Note
High Risk
Introduces credential lifecycle (create with one-time plaintext, revoke) over MCP; misconfiguration or weak API-side auth would expose high-impact operations despite forwarding the caller's token.
Overview
Adds
manage_api_keysso agents can create, list, get, update, and delete Kernel API keys via MCP, including optional project scoping and expiry on create. Create is the only path that returns the plaintext key; later reads stay masked.MCP registration is refactored into a toolset registry driven by
KERNEL_MCP_DISABLED_TOOLSETS, so self-hosted deployments can skip registering sensitive tools (e.g.api_keys/manage_api_keys). Invalid env values fail at startup with a clear error.manage_projectsand the new tool shareresponses.ts(JSON, paginated lists,isError: trueon validation/SDK failures) andschemas.tsfor list pagination hints aligned with API clamping. README and.env.exampledocument the new tool count and gating option.Reviewed by Cursor Bugbot for commit 8074fb9. Bugbot is set up for automated code reviews on this repo. Configure here.