Build an Enterprise-Managed Authorization MCP Server with Zero-Touch OAuth
The MCP 2026-07-28 spec is stateless and the Enterprise-Managed Authorization extension is stable, so your IdP can authorize agents with zero per-user browser consent. This dispatch builds a TypeScript FastMCP server wired to Okta, Entra ID, or Keycloak.
Deepak Bagada
CEO, SaaSNext
- MCP went stateless on 2026-07-28, which unblocked the stable Enterprise-Managed Authorization extension for zero-touch OAuth.
- The enterprise IdP (Okta, Entra ID, Keycloak) authorizes agents on behalf of users, eliminating per-user browser consent.
- RFC 7591 dynamic client registration plus RFC 8693 token exchange and RFC 7662 introspection make the flow fully automated.
- A TypeScript FastMCP server with per-request bearer-token validation slots directly into the Claude Desktop mcpServers config.
By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.
Build an Enterprise-Managed Authorization MCP Server with Zero-Touch OAuth
On July 28, 2026, the Model Context Protocol (MCP) crossed a threshold every platform team had been waiting for: the specification officially went stateless. Servers no longer hold sessions between requests, and every tool invocation is a self-contained, independently auditable unit. That change unblocked the extension enterprises have needed most — Enterprise-Managed Authorization — which is now stable and ready for production. Instead of pushing every employee through a browser consent screen, the organization's identity provider (Okta, Entra ID, or Keycloak) authorizes AI agents on behalf of users. Zero per-user interaction, zero consent fatigue, full audit trail.
This dispatch walks you through building a production-grade Enterprise-Managed Authorization MCP server in TypeScript with FastMCP: inputSchema definitions, OAuth 2.1/2.0 dynamic client registration, token exchange, and a ready-to-paste Claude Desktop mcpServers config. For the broader tooling picture, browse the MCP Directory or the Workflows desk. Spec-trackers should follow our latest AI news desk.
Why the Stateless Spec Changes Everything
Before the July 2026 revision, MCP servers routinely kept session state: which agent connected, which scopes it held, which token authenticated it. That model collapses in production. Agents reconnect constantly, clients restart mid-conversation, and load balancers spray requests across replicas. A stateless server stores nothing between calls. Every JSON-RPC message — initialize, tools/list, tools/call — carries everything the server needs: an access token in the Authorization header plus the request parameters themselves.
Three concrete wins:
- Horizontal scaling. Any replica can serve any request because there is no shared session store to synchronize.
- Deterministic audits. Each call is a complete, self-contained record — who called, with which scopes, against which tool, with what result.
- Zero-touch OAuth becomes possible. Because the token travels with each request, an enterprise IdP can issue, validate, and revoke credentials without any server-side session machinery.
The stateless model is also the security baseline for Enterprise-Managed Authorization: nothing sensitive lives on the MCP server that an attacker could exfiltrate with a single stray SQL query.
What Is Enterprise-Managed Authorization?
Enterprise-Managed Authorization is the MCP extension that replaces interactive OAuth consent with policy-driven delegation. In a classic MCP deployment, a developer runs a server and each user clicks through an authorization screen in the browser. That is fine for public tools; it is unworkable for a 40,000-person enterprise where the CISO needs to prove exactly who can make an agent call orders.release_payment.
With Enterprise-Managed Authorization, the flow is inverted:
- The host (the MCP client, e.g. Claude Desktop or an internal gateway) contacts the enterprise IdP.
- The IdP — not the user — decides whether the agent may act, based on group membership, device posture, and policy.
- The server validates each incoming token against the IdP and enforces scopes per tool.
The extension leans on a stack of IETF standards that are all battle-tested in production identity systems:
| Standard | Role in the flow |
|---|---|
| RFC 7591 Dynamic Client Registration | The server registers itself with the IdP at startup and receives client_id / client_secret without a developer-portal visit |
| RFC 8693 Token Exchange | A host token is exchanged for a down-scoped token bound to the MCP resource |
| RFC 7662 Token Introspection | The server checks a token's active state and scopes when local validation fails |
| RFC 9068 JWT Profile | IdP-issued access tokens become standardized JWTs the server verifies with JWKS |
| OAuth 2.1 | The 2026 OAuth refresh — PKCE mandatory, redirects tightened, secrets optional for public clients |
The zero-touch promise
"Zero-touch" means no per-user browser consent. An admin provisions an app registration once; users never see an approval screen. When a new hire joins, their agent automatically inherits the group-based policy. When they leave, revocation is instant because the IdP stops issuing tokens. This is the pattern enterprises already run for every other workload — MCP has just caught up.
The Zero-Touch OAuth Flow, Step by Step
- Dynamic client registration. On startup the MCP server calls the IdP's RFC 7591 endpoint and receives
client_id,client_secret, and allowed redirect URIs. This replaces manual app registration in a dev portal — a requirement for fleets of servers spinning up per environment. - Agent request. The user asks Claude Desktop to use a tool. The host requests an access token from the IdP, presenting its own credentials plus the user's context.
- Token exchange. The host exchanges its token for a down-scoped, audience-bound token where
audis the MCP server's resource URI andscopeis limited to the tools the agent may invoke. - Validation. The MCP server verifies the JWT signature against the IdP's JWKS, checks
iss,aud,exp, and enforces per-tool scopes. No server-side session is created. - Stateless execution. The tool runs, the response streams back, and nothing about the caller is persisted.
The entire dance uses the existing Authorization: Bearer <token> header, so the MCP wire protocol stays untouched — the spec change was about removing state, not adding new verbs.
Building the Server (TypeScript + FastMCP)
1. Project setup
Create the project and install the SDK:
npm init -y
npm install @modelcontextprotocol/sdk jose zod
npm install -D typescript tsx
package.json scripts for the dev/build loop:
{
"name": "enterprise-auth-mcp",
"version": "1.0.0",
"type": "module",
"engines": { "node": ">=22" },
"scripts": {
"dev": "tsx src/server.ts",
"start": "node dist/server.js",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^2.0.0",
"jose": "^6.0.0",
"zod": "^4.0.0"
}
}
2. inputSchema definitions
FastMCP accepts a plain JSON Schema object for each tool's inputSchema. Define them once, export them, and reference them in tool registrations:
const exchangeTokenSchema = {
type: "object",
properties: {
audience: {
type: "string",
description: "The MCP resource URI the exchanged token must be bound to"
},
scopes: {
type: "array",
items: { type: "string" },
minItems: 1,
description: "Down-scoped scopes, e.g. orders.read or catalog.write"
},
ttl_seconds: {
type: "integer",
minimum: 300,
maximum: 86400,
default: 900,
description: "Lifetime of the exchanged token"
}
},
required: ["audience", "scopes"],
additionalProperties: false
} as const;
3. Full FastMCP server
The handlers below derive identity from the per-request Authorization header — that is the stateless model in action:
import { FastMCP } from "@modelcontextprotocol/sdk/server/mcp.js";
import { validateAccessToken, exchangeTokenFor, revokeAtIdp } from "./oidc.js";
const exchangeTokenSchema = {
type: "object",
properties: {
audience: { type: "string", description: "The MCP resource URI the token must be bound to" },
scopes: { type: "array", items: { type: "string" }, minItems: 1, description: "Down-scoped scopes" },
ttl_seconds: { type: "integer", minimum: 300, maximum: 86400, default: 900, description: "Token lifetime" }
},
required: ["audience", "scopes"],
additionalProperties: false
} as const;
const server = new FastMCP("enterprise-tools", {
version: "1.0.0",
capabilities: { tools: {}, auth: { enterpriseManaged: true } }
});
async function principal(extra: any) {
const token = extra?.headers?.authorization?.replace("Bearer ", "");
if (!token) throw new Error("missing bearer token");
return validateAccessToken(token); // JWKS + issuer + audience + scope checks
}
server.tool("exchange_token",
{
title: "Exchange a host token for a scoped MCP token",
description: "Performs an RFC 8693 token exchange against the enterprise IdP.",
inputSchema: exchangeTokenSchema
},
async ({ audience, scopes, ttl_seconds }, extra) => {
const p = await principal(extra);
const exchanged = await exchangeTokenFor(p.sub, audience, scopes, ttl_seconds);
return { content: [{ type: "text", text: JSON.stringify(exchanged, null, 2) }] };
}
);
server.tool("list_entitlements",
{
title: "List the agent's authorized scopes",
description: "Returns the scopes a validated token may exercise against this server.",
inputSchema: { type: "object", properties: {}, additionalProperties: false }
},
async (_args, extra) => {
const p = await principal(extra);
return { content: [{ type: "text", text: JSON.stringify({ subject: p.sub, scope: p.scope }, null, 2) }] };
}
);
server.tool("revoke_token",
{
title: "Revoke an exchanged token",
description: "Immediately revokes a previously exchanged access token at the IdP.",
inputSchema: {
type: "object",
properties: { token: { type: "string", description: "The token value to revoke" } },
required: ["token"]
}
},
async ({ token }) => {
await revokeAtIdp(token);
return { content: [{ type: "text", text: "revoked" }] };
}
);
await server.start({ transportType: "http", hostname: "0.0.0.0", port: 8787 });
The principal() helper reads the incoming Authorization header on every call, so any replica can process any request. Helpers exchangeTokenFor and revokeAtIdp wrap your IdP's RFC 8693 and RFC 7009 endpoints.
4. Token validation against your IdP
The validator is the heart of the security model. Prefer local JWKS verification for latency, and fall back to RFC 7662 introspection for opaque or just-issued tokens:
import { createRemoteJWKSet, jwtVerify } from "jose";
const IDP_ISSUER = process.env.IDP_ISSUER!;
const RESOURCE_AUDIENCE = process.env.MCP_AUDIENCE!;
const jwks = createRemoteJWKSet(new URL(`${IDP_ISSUER}/oauth2/jwks`));
export async function validateAccessToken(raw: string) {
const { payload } = await jwtVerify(raw, jwks, {
issuer: IDP_ISSUER,
audience: RESOURCE_AUDIENCE,
algorithms: ["RS256"]
});
if (!payload.scope || payload.token_use === "id") {
throw new Error("invalid token claims");
}
return payload;
}
For tokens you cannot verify locally, call the introspection endpoint with the server's own credentials:
const resp = await fetch(`${IDP_ISSUER}/oauth2/introspect`, {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
token: raw,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
token_type_hint: "access_token"
})
});
Set IDP_ISSUER, MCP_AUDIENCE, CLIENT_ID, and CLIENT_SECRET as environment variables — never in the repository.
Claude Desktop mcpServers Configuration
Claude Desktop, and every MCP 2026-compliant client, wires to the server with an mcpServers block. Dynamic client registration means the client can register itself against your IdP without portal choreography:
{
"mcpServers": {
"enterprise-tools": {
"type": "http",
"url": "https://mcp.corp.example.com/enterprise-tools",
"authorization": {
"type": "oauth2",
"issuer": "https://idp.corp.example.com/oauth2",
"authorization_endpoint": "https://idp.corp.example.com/oauth2/v1/authorize",
"token_endpoint": "https://idp.corp.example.com/oauth2/v1/token",
"client_registration": {
"type": "dynamic",
"endpoint": "https://idp.corp.example.com/oauth2/v1/clients"
},
"scopes": ["mcp.invoke", "iam.entitlements:read"]
}
}
}
}
Because the flow is enterprise-managed, Claude Desktop requests the token silently through your IdP. A browser opens only for device verification if your org requires MFA — never for a per-user tool consent screen.
Which IdP Flow Should You Use?
| Flow | Who authorizes | User interaction | PKCE | Best for |
|---|---|---|---|---|
| Zero-touch enterprise | IdP policy (groups + posture) | None | Required | Large orgs, regulated data, audit-heavy workloads |
| Authorization Code + PKCE | End user in browser | One consent prompt | Required | Public SaaS, indie devs, single-tenant apps |
| Client Credentials | Service principal + secret | None | n/a | CI/CD, server-to-server pipelines |
| Device Authorization | End user on a device | Enter a code | Required | CLIs, headless machines, kiosks |
For a corporate MCP deployment you will almost always want the first row. Everything below it is what you already had before Enterprise-Managed Authorization went stable.
OAuth 2.0 Security Guide for MCP
- Short-lived tokens. Configure the IdP to issue access tokens in the 5–15 minute range. If a token leaks from a log line or a debugger, the blast radius is minutes, not hours.
- Audience binding. Every MCP server must have a unique resource URI in its
audclaim. Reject any token whose audience does not match. This stops a leaked token from one server being replayed against another. - Scope minimization. Grant the agent the narrowest scope per tool. Model the MCP tool set as scopes (
orders.readvsorders.write), not one bigmcp.*wildcard. - Validate more than the signature. Check
iss,aud,exp,nbf, andtoken_useexplicitly. Signature validity alone is not authorization. - Never log tokens. Redact the
Authorizationheader in access logs; log the token'sjtiinstead, so you can correlate revocations without storing secrets. - Support revocation. Wire RFC 7009 revocation to the IdP and expose it as a tool so incident response can kill a credential mid-conversation.
- Encrypt in transit and at rest. TLS 1.3 on the MCP transport, and never persist exchanged tokens — the stateless model has no session store; keep it that way.
- Rotate the client secret. Dynamic registration gives you a rotation endpoint; automate monthly rotation and alert on any client using a credential older than 60 days.
Wrapping Up
Enterprise-Managed Authorization turns MCP from a developer convenience into a governed corporate capability. With the stateless 2026-07-28 spec and stable zero-touch OAuth, you get dynamic client registration, IdP-issued tokens, and per-tool scope enforcement with no browser prompts and no server-side session state. The TypeScript server above is a working skeleton — add your organization's tools, wire the validator to your IdP, and your agents are enterprise-ready.
Keep the surrounding pieces sharp: audit your tool inventory in the MCP Directory, and study how other teams assemble these patterns on the Workflows desk. The MCP ecosystem moves weekly — the latest AI news desk will keep you ahead of the next spec change.
Enjoyed this breakdown? Get our morning dispatch in your inbox.
Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.
Deepak Bagada
CEO, SaaSNext
Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.
Related Intelligence Analysis
Vercel AI SDK Tool Calling React: 5 Steps (2026)
Vercel AI SDK tool calling React integration is a programming pattern that executes server-side functions based on large language model decisions and streams the results to a React frontend. By combining streamText with...
Fact-Density vs. Word Count: The New SEO for 2026
Fact Density is the ratio of verifiable, unique information to the total word count of a piece of content. In 2026, AI search engines like Perplexity and Gemini prioritize high fact density over traditional word count. A...
NVIDIA Audex vs Qwen3.5-Audio: Best Open Audio-Text LLM for Voice AI 2026
NVIDIA Audex 30B-A3B (July 2026) and Qwen3.5-35B-A3B are the two leading open audio-text LLMs. Audex uniquely handles both audio understanding and generation in a single model while preserving text intelligence. Qwen3.5-...