{"slug": "enterprise-managed-authorization-and-client-credentials-for-mcp", "title": "Enterprise-managed authorization and client credentials for MCP", "summary": "The Model Context Protocol (MCP) now has two official authorization extensions in its ext-auth repository: Enterprise-Managed Authorization (identifier io.modelcontextprotocol/enterprise-managed-authorization, stable as of October 2026) and OAuth Client Credentials (io.modelcontextprotocol/oauth-client-credentials, draft), covering centralized corporate identity-provider control and machine-to-machine access with no user present. In the enterprise flow the MCP client trades a corporate sign-in for an Identity Assertion JWT Authorization Grant (ID-JAG) at the company's identity provider — such as Okta, Microsoft Entra ID or a corporate SSO system — then exchanges the ID-JAG for an access token, so the user is never redirected to the MCP server's authorization server login page. The author reports that as of October 2026 the mainstream chat and IDE hosts list neither extension, and recommends a signed JWT assertion over a client secret for client credentials so the private key never leaves the service.", "body_md": "# Enterprise-managed authorization and client credentials for MCP\n\nCore Model Context Protocol (MCP) authorization pictures one person in a browser, approving one client for one server. That fits less well when a company’s information technology (IT) team wants to decide centrally who may use which server, or when the caller is a scheduled job with nobody there to click anything. Two official extensions cover those cases.\n\nThis is part 28 of my series on what an MCP server does under the 2026-07-28 specification, after [part 27 on Skills over MCP](https://pournasserian.com/writing/mcp-2026-skills). It builds on [part 18](https://pournasserian.com/writing/mcp-2026-oauth-resource-server) for core authorization and [part 23](https://pournasserian.com/writing/mcp-2026-extensions) for how extensions work. The facts are as I read them in October 2026.\n\n## In brief\n\n1. **Two official extensions cover what core authorization handles poorly.** Enterprise-Managed Authorization (stable) puts MCP access under the company’s identity provider (IdP); OAuth Client Credentials (draft) serves callers with no user present.\n2. **In the enterprise flow, the user never sees your authorization server’s login page.** The client trades a corporate sign-in for an Identity Assertion JSON Web Token (JWT) Authorization Grant (ID-JAG) at the IdP, then the ID-JAG for an access token.\n3. **For client credentials, prefer a signed JWT assertion to a client secret.** The private key never leaves the service.\n4. **Support is early.** As of October 2026, the mainstream chat and integrated development environment (IDE) hosts list neither extension.\n\n## Which authorization model fits\n\nBoth extensions live in the [`ext-auth` repository](https://github.com/modelcontextprotocol/ext-auth):\n\n| Extension | Identifier | Status, October 2026 | Solves | \n|---|---|---|---|\n| [Enterprise-Managed Authorization](https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization) | `io.modelcontextprotocol/enterprise-managed-authorization` | Stable | IT controls MCP access centrally through the corporate IdP, with no per-user, per-server consent | \n| [OAuth Client Credentials](https://modelcontextprotocol.io/extensions/auth/oauth-client-credentials) | `io.modelcontextprotocol/oauth-client-credentials` | Draft | Machine-to-machine access with no user present | \n\nBoth are optional and additive: they don’t change core behaviour, they compose with each other, and they’re versioned independently.\n\nWhich one you need depends on who is calling. Core authorization uses a Proof Key for Code Exchange (PKCE), and a local server on standard input and output (stdio), as in [part 7](https://pournasserian.com/writing/mcp-2026-transports), takes its credentials from the environment:\n\n## Enterprise-managed authorization\n\nLetting each user authorize each server is right for consumers. In a company it causes friction and gaps: employees shouldn’t have to understand every server’s authorization details, security teams can’t enforce consistent policy, and onboarding and offboarding mean authorizing, then revoking, dozens of services by hand.\n\nThe extension makes the organization’s IdP (Okta, Microsoft Entra ID or a corporate single sign-on (SSO) system) the authoritative decision-maker. Users sign in to the MCP client with corporate SSO. The client gets an ID-JAG from the IdP and exchanges it at your MCP server’s authorization server, which validates it against the IdP’s JSON Web Key Set (JWKS) and issues an access token. The user is never redirected to that authorization server’s login page.\n\nWhat the organization gets:\n\n- **Central policy.** The IdP holds the approved MCP servers and each one’s access rules.\n- **One sign-in.** No per-server consent prompts.\n- **Enforcement before issuance.** Users without access never receive a token.\n- **Revocation in one place.** Removing access at the IdP covers every client.\n- **An audit trail** for compliance.\n\n### What each party does\n\n| Party | Duties | \n|---|---|\n| MCP client | Declare the extension on each request. Support SSO with the enterprise IdP and keep the identity assertion, an OpenID Connect (OIDC) ID token or a Security Assertion Markup Language (SAML) assertion. Exchange it for an ID-JAG, then redeem that at the MCP authorization server. Support IdP configuration at the organization level. Handle scope errors gracefully | \n| MCP server | Declare the extension in its authorization metadata to require the enterprise flow. Optionally publish a resource descriptor, so admins can configure policy in the IdP console | \n| MCP authorization server | Validate ID-JAGs: signature against the IdP’s JWKS, issuer, audience, expiry. Map claims to permissions. Link accounts using `sub` as the stable key, falling back to email for accounts created before enterprise management | \n\n### In Python\n\nIn the official Python software development kit (SDK), `mcp` 2.3.0, `IdentityAssertionOAuthProvider` (in `mcp.client.auth.extensions.identity_assertion`) covers the last exchange, presenting the ID-JAG with the JWT-bearer grant of Request for Comments (RFC) 7523. Getting the ID-JAG is left to you, through an `assertion_provider` callback. It serves confidential clients only, so it needs a client secret. It also takes your authorization server’s `issuer` as configuration, so the MCP server can’t redirect the ID-JAG.\n\n## OAuth client credentials\n\nBackground services, scheduled jobs, continuous integration and delivery (CI/CD) pipelines, server-to-server integrations and daemons have no human present and can’t open a browser. This extension adds the OAuth 2.0 client credentials grant, so they authenticate with the application’s own credentials instead of a user’s. If a human should explicitly authorize access, use the standard flow.\n\nIt allows two credential formats:\n\n|  | JWT bearer assertion, RFC 7523 | Client secret | \n|---|---|---|\n| Secret on the wire | No, the private key stays local | Yes | \n| Lifetime | Short, minutes | Long-lived until rotated | \n| Risk if leaked | A limited window | Silent impersonation until rotation | \n| Guidance | Preferred | Store in a secrets manager, rotate regularly, scope minimally | \n\n- **Clients** declare the extension on each request, obtain a token before connecting, send it as a bearer token, and refresh it before expiry. These tokens often live shorter than user tokens.\n- **Servers** validate the JWT’s signature and claims against the authorization server’s JWKS, check scopes per operation and, as a recommendation, advertise the extension in[`server/discover`](https://pournasserian.com/writing/mcp-2026-server-discover) .\n\n### In Python\n\n`mcp` 2.3.0 has a provider for each format, in `mcp.client.auth.extensions.client_credentials`: `ClientCredentialsOAuthProvider` for a client ID and secret, and `PrivateKeyJWTOAuthProvider` for a signed assertion (the TypeScript SDK’s are `ClientCredentialsProvider` and `PrivateKeyJwtProvider`). Each plugs into `httpx2`, the SDK’s Hypertext Transfer Protocol (HTTP) client. The assertion comes from your `assertion_provider` callback: in production, the SDK says, usually from a workload identity provider. For testing and simple setups, `SignedJWTParameters` signs it with your private key, as here:\n\n``` python\nimport httpx2\n\nfrom mcp.client import Client, advertise\nfrom mcp.client.auth.extensions.client_credentials import (\n    PrivateKeyJWTOAuthProvider,\n    SignedJWTParameters,\n)\nfrom mcp.client.streamable_http import streamable_http_client\nfrom mcp.shared.auth import OAuthClientInformationFull, OAuthToken\n\nMCP_URL = \"https://mcp.example.com/mcp\"\nCLIENT_ID = \"report-service\"  # registered with the authorization server, with its public key\n\nclass MemoryTokenStorage:\n    \"\"\"The SDK's TokenStorage protocol, kept in memory.\"\"\"\n\n    def __init__(self) -> None:\n        self.tokens: OAuthToken | None = None\n        self.client_info: OAuthClientInformationFull | None = None\n\n    async def get_tokens(self) -> OAuthToken | None:\n        return self.tokens\n\n    async def set_tokens(self, tokens: OAuthToken) -> None:\n        self.tokens = tokens\n\n    async def get_client_info(self) -> OAuthClientInformationFull | None:\n        return self.client_info\n\n    async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:\n        self.client_info = client_info\n\ndef make_provider(private_key_pem: str) -> PrivateKeyJWTOAuthProvider:\n    \"\"\"The private key signs assertions here and never leaves this service.\"\"\"\n    assertions = SignedJWTParameters(issuer=CLIENT_ID, subject=CLIENT_ID, signing_key=private_key_pem)\n    return PrivateKeyJWTOAuthProvider(\n        server_url=MCP_URL,\n        storage=MemoryTokenStorage(),\n        client_id=CLIENT_ID,\n        assertion_provider=assertions.create_assertion_provider(),\n        issuer=\"https://auth.example.com\",  # the only server an assertion is sent to\n    )\n\nasync def nightly_report(http: httpx2.AsyncClient) -> str:\n    \"\"\"Runs with no user present; the provider gets the token.\"\"\"\n    declared = advertise(\"io.modelcontextprotocol/oauth-client-credentials\")\n    async with Client(streamable_http_client(MCP_URL, http_client=http), extensions=[declared]) as client:\n        result = await client.call_tool(\"list_files\", {})\n        return result.content[0].text\n```\n\nI ran it with `httpx2.AsyncClient(auth=make_provider(key))`, in one process, against the SDK’s own MCP server, a stand-in authorization server and a throwaway key pair. The requests:\n\n``` php\nPOST mcp.example.com/mcp -> 401\nGET mcp.example.com/.well-known/oauth-protected-resource/mcp -> 200\nGET auth.example.com/.well-known/oauth-authorization-server -> 200\nPOST auth.example.com/token -> 200\nPOST mcp.example.com/mcp -> 200\nPOST mcp.example.com/mcp -> 200\nPOST mcp.example.com/mcp -> 200\n```\n\nWhat the token endpoint received:\n\n```\ngrant_type: client_credentials\nclient_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer\nresource: https://mcp.example.com/mcp\nscope: files:read\nclient_secret sent: False\nassertion claims: {'iss': 'report-service', 'sub': 'report-service', 'aud': 'https://auth.example.com'} lifetime: 300 s\n```\n\nWhat the run showed:\n\n- **No secret on the wire.** The JWT travels as a client assertion beside`grant_type=client_credentials` and lives 300 seconds, the`SignedJWTParameters` default. The scope came from the server’s Protected Resource Metadata.\n- **One line declares the extension.**`advertise(...)` put it in the client capabilities of every request’s`_meta` .\n- **`issuer=` pins where credentials go.** Without it, 2.3.0 warns that 3.0 will require it.\n- **Tokens come after a 401.** The first token came only after a 401 and a discovery of both metadata documents. When the two-second demo token expired, the next request went the same way. That isn’t yet what the extension asks of clients: a token before connecting, and a refresh before expiry.\n\n## Support, and my advice for Entra ID\n\nThe [official client matrix](https://modelcontextprotocol.io/extensions/client-matrix) lists Enterprise-Managed Authorization in Archestra.AI and in Apify’s mcpc command-line client, and Client Credentials in mcpc. As of October 2026, the mainstream chat and IDE hosts don’t list either. Enterprise-Managed Authorization also needs support on the IdP’s side, so check with your IdP vendor.\n\nWhat follows is my advice, not part of either extension. If your MCP servers sit behind Entra ID in a large organization, keep Enterprise-Managed Authorization on your roadmap. It removes the per-user consent friction that Entra’s lack of dynamic registration otherwise forces you to work around ([part 19](https://pournasserian.com/writing/mcp-2026-oauth-step-up-and-entra) covers what to expect from Entra today). Until hosts support it, pre-registered clients with core authorization, and Client Credentials for automation, are the practical options.\n\n## Method and caveats\n\n- Built from my guide’s chapter on authorization extensions, against the 2026-07-28 specification; the status and support facts are as of October 2026.\n- The Python sample ran against `mcp` 2.3.0 on 9 October 2026, in one process with no network; the output is what it printed.\n- Where my notes were less precise about the Python SDK (which classes it ships, and how its providers renew a token), this article follows the installed package.", "url": "https://wpnews.pro/news/enterprise-managed-authorization-and-client-credentials-for-mcp", "canonical_source": "https://pournasserian.com/writing/mcp-2026-enterprise-auth", "published_at": "2026-10-24 00:00:00+00:00", "updated_at": "2026-10-24 12:43:25.706434+00:00", "lang": "en", "topics": ["agent-protocols", "ai-agents", "ai-policy", "developer-tools"], "entities": ["Model Context Protocol", "Enterprise-Managed Authorization", "OAuth Client Credentials", "Okta", "Microsoft Entra ID", "ext-auth"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/enterprise-managed-authorization-and-client-credentials-for-mcp", "markdown": "https://wpnews.pro/news/enterprise-managed-authorization-and-client-credentials-for-mcp.md", "text": "https://wpnews.pro/news/enterprise-managed-authorization-and-client-credentials-for-mcp.txt", "jsonld": "https://wpnews.pro/news/enterprise-managed-authorization-and-client-credentials-for-mcp.jsonld"}}