{"slug": "auth-for-your-mcp-server-without-running-an-authorization-server", "title": "Auth for your MCP server, without running an authorization server", "summary": "Grantor, a new authentication protocol, enables remote MCP servers to verify client credentials without running an authorization server. The system uses self-certifying 'deeds' verified via local crypto and a public registry contract on Base, with optional zero-knowledge fleet membership proofs. Developers can integrate it with three routes, and it includes automatic discovery documents and RFC 9728 metadata for standard OAuth clients.", "body_md": "You built a remote MCP server. It’s useful, so now strangers’ agents want to call it, and you need to answer the question every remote server hits: who’s allowed in?\n\nThe MCP spec’s answer is OAuth 2.1: put an authorization server in front, register clients, issue tokens. Which is correct, and heavy. An authorization server is a stateful, security-critical service with a user database — you run it or you rent it, and either way it’s now load-bearing infrastructure for what might be a weekend project. It also assumes your callers can do an OAuth dance, and that you want accounts.\n\nHere’s the shape with no authorization server anywhere: the credential — called a deed — certifies itself. An agent mints one locally by signing a challenge you issued (or by proving fleet membership in zero knowledge — more below). Your server verifies it with a library: local crypto plus one eth_call to a public registry contract on Base, served by any RPC provider. No token service, no client registration, no user table. Your whole auth stack is three routes:\n\n``` js\nimport { DeedVerifier, sessionJwt } from \"@grantor/verify\";\nimport { grantorExpress } from \"@grantor/verify/express\";\nimport { Registry } from \"@grantor/verify/registry\";\n\nconst verifier = new DeedVerifier(\n  RPC_URL, Registry.canonical(), CHAIN_ID, TENANT_ID,\n  AUDIENCE, ORIGIN, MAX_TTL_SECS, CACHE_TTL_SECS,\n  false, Math.floor(Date.now() / 1000),\n);\n\nconst g = grantorExpress({\n  verifier, app,\n  challengeEndpoint: \"/auth/challenge\",\n  chainId: CHAIN_ID,\n  modes: [\"user-sig\", \"agent-zk\"],\n  vouchSignature: VOUCH_SIGNATURE, vouchEpoch: VOUCH_EPOCH, vouchExp: VOUCH_EXP,\n});\napp.get(\"/auth/challenge\", g.challenge);\n```\n\nThat one call also auto-publishes GET /.well-known/grantor-deed — a discovery document naming your tenant, chain, modes, and challenge endpoint — and self-checks it at startup, so a misconfiguration fails your boot, not your first user’s login.\n\nThe second route exchanges a deed for a session, the way a token endpoint would — an MCP client authenticates once, not per request:\n\n``` js\napp.post(\"/auth/token\", async (req, res) => {\n  const { deed, challenge } = req.body;\n  const claims = await g.guard.verify(JSON.stringify(deed), challenge);\n  // claims.sub is a verified, pseudonymous subject — recomputed by the\n  // verifier, not read from the deed. Mint YOUR session from it:\n  res.json({ session_jwt: sessionJwt(claims.sub, AUDIENCE, BigInt(TENANT_ID),\n    SIGNING_KEY_PEM, BigInt(now), BigInt(TTL), { iss: ORIGIN }) });\n});\n```\n\nsessionJwt mints a plain ES256 JWT with your key — any JOSE library verifies it without ever importing this SDK. The third route is your existing MCP transport, gated on that session. That’s the entire surface.\n\nThree properties you don’t usually get from a weekend auth setup:\n\nRejections teach the caller. Every 401 carries WWW-Authenticate:\n\nGrantor-Deed … plus discovery/learn fields pointing at your discovery document and a machine-readable onboarding manifest. The guard also serves RFC 9728 protected-resource metadata, so an MCP-spec OAuth client discovers what your server needs the standard way. A capable agent that gets rejected can read its way to authenticated — no human writes an integration ticket. (All of it opt-out with one flag if you want silent 401s.)\n\nYou can authorize a whole fleet without an allowlist. With agent-zk in modes, any agent enrolled in your tenant’s on-chain registry proves membership in zero knowledge. Membership is the authorization — no per-agent config on your server, and you learn a stable pseudonym per agent, not a wallet address.\n\nBilling is enforced where verification happens. The verifier checks the tenant’s on-chain status during the same read, and fails closed if the chain is unreachable. Nobody can verify deeds against a lapsed tenant.\n\nWhat it costs, honestly: you need a tenant on the registry (createTenant plus USDC funding on Base — a few contract calls, no signup form, because there is no server to sign up with) and a signed origin vouch for wherever your server runs, which is what lets a well-behaved agent refuse to authenticate to a hostile origin impersonating you. The whole thing is an unaudited developer preview. The guard ships in TypeScript, Python, Go, and Rust, so this isn’t an Express-only story.\n\nFull guide, transcribed from a runnable reference server: [https://chaingrantor.com/docs/guide/mcp-server](https://chaingrantor.com/docs/guide/mcp-server) — and #1 in this series covers the other direction, gating what your own sub-agents may do (including wrapping any third-party MCP server so enforcement is structural, not voluntary): [https://dev.to/grantor/give-your-ai-sub-agent-a-budget-not-your-keys-2e7h](https://dev.to/grantor/give-your-ai-sub-agent-a-budget-not-your-keys-2e7h)\n\nOAuth told us auth needs an authorization server. For agents, it needs an authorization — the server part turns out to be optional.", "url": "https://wpnews.pro/news/auth-for-your-mcp-server-without-running-an-authorization-server", "canonical_source": "https://dev.to/grantor/auth-for-your-mcp-server-without-running-an-authorization-server-1o6h", "published_at": "2026-09-01 00:05:19+00:00", "updated_at": "2026-09-01 00:22:35.078741+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["Grantor", "Base", "MCP"], "alternates": {"html": "https://wpnews.pro/news/auth-for-your-mcp-server-without-running-an-authorization-server", "markdown": "https://wpnews.pro/news/auth-for-your-mcp-server-without-running-an-authorization-server.md", "text": "https://wpnews.pro/news/auth-for-your-mcp-server-without-running-an-authorization-server.txt", "jsonld": "https://wpnews.pro/news/auth-for-your-mcp-server-without-running-an-authorization-server.jsonld"}}