Skip to content

feat(mcp): add an MCP server for AI agents, gated by idp:mcp - #72

Merged
lorenzocorallo merged 2 commits into
mainfrom
feat/mcp-server
Oct 24, 2026
Merged

lorenzocorallo merged 2 commits into
mainfrom
feat/mcp-server

Conversation

@lorenzocorallo

Copy link
Copy Markdown
Member

What this adds

  • /api/mcp, an MCP server. Claude Code, Codex, Gemini CLI, VS Code and hosted connectors can connect to it and read or change this identity provider for you: debug an app that can't sign people in, register or edit apps, and set up roles, permissions and who holds them.
  • idp:mcp, a new built-in permission (migration 0022). Only people who hold it can use the server. Nobody has it yet except Master Admin; give it to a role.
  • The agent acts as you. Every tool calls the same store function as the matching page, so the same permission check, bounded delegation, RBAC write lock and audit log apply. Tools you have no permission for are not even listed. Taking idp:mcp away, or disconnecting the agent, ends access on the next request.
  • Account page: an MCP panel with the server URL, one-line setup commands for Claude Code, Codex, Gemini CLI and VS Code, and your connected agents with a disconnect button.
  • Consent page: the agent scope is explained, and people without idp:mcp are stopped there with a "Back to the agent" button.

Setup is one command, for example:

claude mcp add --scope user --transport http polinetwork-auth https://auth.polinetwork.org/api/mcp
codex mcp add polinetwork-auth --url https://auth.polinetwork.org/api/mcp

Tools

Permission Tools
idp:mcp whoami, get_server_info, inspect_token
idp:applications:read list_applications, get_application, diagnose_application
idp:applications:write create_application, rotate_application_secret; with read also update_application, delete_application
idp:people:read search_people
idp:users:read list_users, get_user; with idp:roles:read also explain_access
idp:roles:read / idp:permissions:read list_roles, get_role / list_permissions
idp:roles:write / idp:roles:assign / idp:permissions:write create, update and delete roles / give and take roles / create, update and delete permissions
Master Admin list_audit_events

diagnose_application is the main debugging tool: it lists bad settings, and given the authorization URL an app sends, it says what /oauth2/authorize would answer and why (wrong redirect URI with the near miss, scopes, PKCE, resource links and their scope policy).

How agents sign in

Standard MCP authorization: protected resource metadata at /.well-known/oauth-protected-resource/api/mcp, a WWW-Authenticate challenge, and the authorization server metadata also served at the RFC 8414 paths agents try first.

Agents register themselves through Better Auth's dynamic client registration. It is now on, but only for agents:

  • Redirects only to http://localhost, 127.0.0.1 or [::1] (any port), or exactly to a hosted agent's callback page. Someone who tricks you into approving their client cannot collect the code.
  • Authorization code + refresh only, public or with a secret, no keys. A registration with a browser session is refused, so the shared app pool is untouched.
  • Scopes are only idp:mcp and offline_access. No openid, so no ID token and no UserInfo. Agents that ask for more (Codex asks for every listed scope) get only these. A sign-in without resource gets a token for the MCP server, so these tokens work nowhere else.
  • The registrant's logo, links and logout URLs are dropped, so nothing looks vouched for by PoliNetwork.
  • Tokens are EdDSA JWTs addressed to /api/mcp, valid 5 minutes. Refresh tokens last 30 days and survive signing out of the website.
  • Registrations never approved within a day, and agents unused for 60 days, are deleted by the daily retention job.

Other change: creating an application

createOidcClientFn used the provider's adminCreateOAuthClient, which needs a browser session (an MCP call has none) and writes outside the RBAC lock, followed by a recheck. Both the page and the MCP tool now use createOidcClient, which inserts the row under the lock, in the provider's format, the same way update, rotate and delete already work. I replaced the integration test about the old recheck window with one showing nothing is inserted until the lock is held. The test for a key taken in the meantime still passes.

Decisions for you

  1. Hosted callbacks (claude.ai, claude.com, chatgpt.com, vscode.dev) are allowed so Claude Desktop, claude.ai and ChatGPT connectors work. The code goes to their servers, so we rely on them tying the sign-in to the right user. Remove them from HOSTED_MCP_CALLBACKS if you want loopback only.
  2. Cursor does not work: it registers a cursor://anysphere… callback, which Better Auth refuses as an invalid app scheme.
  3. Browser-only clients (MCP Inspector in direct mode, web IDEs) do not work: they would need CORS on the token endpoint, which every app shares. Not added.
  4. Client secrets from create_application and rotate_application_secret go back to the agent once, like on the page.
  5. Deleting people is not exposed. The audit log tool is Master Admin only, since there is no audit permission yet.

Testing

  • vp check, vp test (331 passed), vp run build with the bundle check, and a smoke test of the production bundle.
  • Integration suites against a throwaway database and a running server: identity HTTP suite 26/26. The database suites pass, except rate-limit "bounds server token traffic", which fails the same way on unmodified main when the suites share a database and passes on its own.
  • Codex CLI 0.163: codex mcp login (it asked for every scope and was narrowed), then codex exec called whoami, list_applications, get_server_info and list_roles.
  • Claude Code 2.1.310: claude mcp login → ✔ Connected; claude -p created an app and diagnosed a broken authorize URL correctly.
  • Official MCP TypeScript SDK v1 and v2 clients ran discovery, registration, PKCE, token and tool calls on their own.
  • Revocation, disconnect, write-only users, pruning, and the reviewer's reproduction (below) checked live.
  • Not tested live: Claude Desktop / claude.ai, ChatGPT, VS Code and Gemini CLI. They follow the same spec, but I couldn't run them here.

Review

GPT-6.1-sol (high) reviewed the first commit. It found no critical or high issues, and five medium ones:

  1. An agent registration could still be used as a general sign-in (openid + claims, no resource) and read name and email from UserInfo. Fixed: no openid for agents, and resource defaults to the MCP server, for GET and POST.
  2. create_application could create the app but lose its one-time secret for someone who can write but not read applications. Fixed.
  3. Pruning would delete active agents that don't use refresh tokens. Fixed: pruning now goes by recorded use.
  4. No CORS for browser-only clients. Not changed, see decision 3.
  5. diagnose_application ignored a resource's scope policy and could call a failing request fine. Fixed, with a test.

Agents such as Claude Code and Codex connect to /api/mcp, sign in through
OAuth with dynamic client registration limited to agents, and act with the
person's own permissions. Creating applications now inserts the client under
the RBAC write lock instead of through the provider's session-only endpoint.
- Agent clients no longer get openid, and a sign-in without a resource gets a
  token for the MCP server, so an agent registration cannot read profiles or
  sign people in elsewhere. GET and POST authorization requests are rewritten.
- create_application returns the new client's credentials even when the
  person cannot read applications; update and delete need read access.
- Agents are pruned after 60 days without use, recorded on their consent,
  instead of when they hold no refresh token.
- diagnose_application applies each resource's scope policy.
@coderabbitai

coderabbitai Bot commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 079db1f9-baf4-4e39-842f-4e2d83ff77a1
Commits

Reviewing files that changed from the base of the PR and between 0f19ef9 and 3897080.

Files ignored due to path filters: 1
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Files selected for processing: 31
  • README.md
  • drizzle/0022_idp_mcp.sql
  • drizzle/meta/0022_snapshot.json
  • drizzle/meta/_journal.json
  • package.json
  • src/auth/account.functions.ts
  • src/auth/data-retention.ts
  • src/auth/identity.integration.test.ts
  • src/auth/index.ts
  • src/auth/mcp-clients.test.ts
  • src/auth/mcp-clients.ts
  • src/auth/mcp-connections.ts
  • src/auth/mcp-diagnostics.test.ts
  • src/auth/mcp-diagnostics.ts
  • src/auth/mcp-resource.ts
  • src/auth/mcp-server.ts
  • src/auth/mcp-token.ts
  • src/auth/oidc-clients.ts
  • src/auth/oidc-registry.ts
  • src/auth/oidc.functions.ts
  • src/auth/rbac.test.ts
  • src/auth/rbac.ts
  • src/components/account/mcp-panel.tsx
  • src/components/account/use-account-actions.ts
  • src/routeTree.gen.ts
  • src/routes/[.]well-known/oauth-authorization-server.$.ts
  • src/routes/[.]well-known/oauth-protected-resource.$.ts
  • src/routes/[.]well-known/openid-configuration.$.ts
  • src/routes/api/mcp.ts
  • src/routes/consent.tsx
  • src/routes/index.tsx
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lorenzocorallo
lorenzocorallo merged commit 720e8bc into main Oct 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant