Your Users Can Now Connect Claude to Your Software Themselves
The Model Context Protocol turned your server into an OAuth resource server, and that one reclassification moved the setup work off the user. They paste a URL, sign in the way they already do, click Authorize. Here is the handshake, what it costs to build, and the five things that silently break it.
Sep 15, 2026
A year ago, connecting an AI assistant to an internal system was an IT ticket. Someone in engineering generated an API key, sent it over a channel they hoped was private, and the user pasted it into a JSON config file on their own machine. Every one of those steps has a failure mode, and the last one meant the feature was only ever going to reach people comfortable editing JSON.
That is no longer how it works. The Model Context Protocol's authorization spec turned the MCP server into a standard OAuth 2.1 resource server, and the practical consequence is small to describe and large in effect: the AI client can now discover, on its own, where your application's front desk is, and walk the user through signing in there.
The user pastes one URL. They see their own company's login. They click Authorize. That is the entire setup.
Two short companion pages answer the narrower questions on their own: what MCP OAuth is, and how you add a custom connector in Claude. This page is the implementation account behind both.
This article is about the handshake that makes that possible, what it takes to implement on your side, and the five things that break it silently. Five of our services now expose an OAuth-protected MCP endpoint, three of them running the whole authorization server themselves, and the same class of mistake cost us the most time in every one.
Contents
- What the user actually does
- The API key it replaces, and why that mattered
- The handshake, step by step
- Which MCP specification version introduced this
- Does this work with ChatGPT as well as Claude?
- What you have to implement
- Five things that break it silently
- Is it safe to let users connect an AI assistant to a production system?
- Who should do this now
What the user actually does
Here is the connection guide we shipped last week with a time-tracking and profitability platform we run for an accounting firm. It is four A4 pages, it is bilingual, and the connection section is three steps:
The whole setup, as written for the end user
- 1
Step 1 — In Claude: Settings, then Connectors, then Add custom connector. Paste the URL, which is your dashboard address with /mcp on the end.
- 2
Step 2 — Click Connect. A window from your own platform opens and asks for the Google account you already sign in with.
- 3
Step 3 — Check the scope shown, then Authorize. The connector keeps the connection and renews it by itself.
The prerequisite line in that guide is the one that tells you what changed: "the Google account you already open the dashboard with. If the dashboard will not let you in, neither will the connector."
That sentence is only writable because the application's own identity provider is doing the work. Nobody issued this user anything. Their access to the AI connector is, by construction, exactly their access to the app, and it ends when their staff account ends. The firm's administrator did not have to think about AI at all.
Two months earlier, the same capability required us to generate a per-user API key in a database and send it to them.
The API key it replaces, and why that mattered
An API key is a shared secret. Its problems are not subtle, they are just familiar:
- Someone has to issue it. That is a human in a loop, usually your most expensive one.
- It has to travel. Over Slack, over email, into a password manager if you are lucky.
- It usually carries the issuer's permissions, not the user's, because scoping keys per person is work that teams skip.
- It does not expire, so revocation is a manual act someone has to remember on the day a person leaves.
- It requires the user to store it. Which means a config file, which means a technical user.
MCP OAuth replaces all of that with the authorization-code flow that every website's "Sign in with Google" button has used for a decade. The difference from a normal OAuth integration, and the thing that is genuinely new, is that the client has never heard of your application before. Claude does not ship with a Porcelanosa integration or a driver for your ERP. It has one string: the URL a user pasted.
So the spec had to answer a question ordinary OAuth never asks: given only a resource URL, how does a client find out where to authenticate? Everything below is the answer to that question.
The handshake, step by step
Five round trips, none of which the user sees except the sign-in and the consent screen.
From a pasted URL to a working connector
- 1
1. The client calls your MCP endpoint with no token — You answer 401 Unauthorized with a WWW-Authenticate header carrying a resource_metadata parameter, as defined in RFC 9728 section 5.1. This is the only pointer the client gets, and it is mandatory.
- 2
2. The client fetches your protected resource metadata — A public JSON document at /.well-known/oauth-protected-resource naming the canonical resource URI and, in authorization_servers, at least one authorization server. MCP servers MUST serve this.
- 3
3. The client fetches the authorization server metadata — RFC 8414 at /.well-known/oauth-authorization-server, or OpenID Connect Discovery. This is where it learns your authorize, token and registration endpoints, and that you support PKCE with S256.
- 4
4. The client obtains a client ID — By publishing a Client ID Metadata Document at a URL it controls (the current recommendation), by dynamic registration against your /register endpoint (deprecated but still widely used), or by pre-registration.
- 5
5. The user signs in and consents — Standard authorization code flow with PKCE, carrying a resource parameter (RFC 8707) naming your server. The code is exchanged for a short-lived access token bound to your server as its audience.
Every subsequent MCP request carries Authorization: Bearer <token>. The spec is explicit that the token must be in the header and never in a query string, where it would land in your access logs.
Two details in that list do real security work and are easy to skip.
The resource parameter. Required by RFC 8707, it names the specific server the token is for, and your server must then validate that tokens presented to it were issued for it. Without that, a malicious MCP server can collect a token a user meant for you and replay it against you. The spec calls this out directly: clients "MUST send this parameter regardless of whether authorization servers support it".
PKCE, advertised. Your metadata document's code_challenge_methods_supported field has to be present. Its absence is how an authorization server signals it does not support PKCE at all, and a correct client will refuse to start. In our CRM's metadata struct that field is the one with a deliberate comment saying it must never be omitted, because a well-meaning omitempty there breaks every client rather than degrading anything.
Which MCP specification version introduced this
The auth story took four revisions to arrive, and knowing which one you are reading matters, because a lot of tutorials online describe a version that is two generations old.
| Revision | What it changed for authorization |
|---|---|
| 2025-03-26 | First auth spec. The MCP server was expected to be its own authorization server, which conflated two roles and made reusing a corporate IdP awkward. |
| 2025-06-18 | The turning point. MCP servers reclassified as OAuth resource servers, protected resource metadata (RFC 9728) required for discovery, and Resource Indicators (RFC 8707) required of clients to stop token replay. |
| 2025-11-25 | Client ID Metadata Documents introduced, OpenID Connect Discovery accepted alongside RFC 8414, incremental scope consent via WWW-Authenticate, and an enterprise-managed authorization extension built on identity assertion grants. |
| 2026-07-28 | Current. CIMD is the recommended registration path and Dynamic Client Registration is deprecated, retained only for authorization servers that do not support CIMD. Clients MUST validate the iss parameter (RFC 9207) against a recorded issuer before sending the code anywhere. |
Two of these are worth understanding rather than just noting.
RFC 9728 is the load-bearing piece, and it is young: OAuth 2.0 Protected Resource Metadata was only published in April 2025, after years in draft. It is the document that lets a client start from an address and find a front desk. Nothing in the user experience described at the top of this article is possible without it.
Dynamic Client Registration is on the way out, and if you are building now, that should change what you write. DCR asks an open, public endpoint to create a database record for any caller who asks, which is an abuse surface and a source of unbounded table growth. Client ID Metadata Documents replace it with a decentralised model borrowed from IndieAuth: the client uses an HTTPS URL it controls as its client_id, your authorization server fetches that URL to read the client's name, logo and redirect URIs, and nothing is stored. Support DCR today for the clients that still need it, but do not build your product around it.
Does this work with ChatGPT as well as Claude?
Both are MCP clients, and both run the flow for the user. The paths differ.
Claude. Custom connectors using remote MCP are available across plans. An individual goes to Customize, then Connectors, then Add custom connector, and pastes the URL. On Team and Enterprise, only an Owner can add the connector to the organisation, after which each member connects individually with their own account. That split is exactly right for a business system: one administrative decision, then per-person identity. Anthropic's own wording for what the user gets is worth quoting, because it is the line that sells the change internally: it lets Claude act on your behalf "without Claude ever seeing your actual password".
ChatGPT. Full MCP client support, including write actions, sits behind Developer mode: Settings, then Apps, then Advanced settings. You then add a custom connector with your server URL and pick OAuth. OpenAI's Apps SDK is the separate, heavier path for publishing an integration into the app directory, with review and distribution attached.
One constraint applies to both, and it catches people: the server must be a public HTTPS endpoint. Neither product can reach an MCP server on a laptop or inside a private network. That is a deployment decision to make before you write any code, not after.
The other honest note: the same server does not always behave identically in both clients. Expect to test in both, and expect the discovery-document quirks in the next section to be where they diverge.
What you have to implement
Less than teams expect, if your application already has a login. That condition is the whole economics of this.
Our CRM shipped the flow across four days in August, in this order, and it is a reasonable order for anyone:
What we built, in the order we built it
- 1
Discovery metadata — The two public JSON documents. No behaviour, no state, no auth. Build these first because every client failure downstream is easier to debug when discovery is already known good.
- 2
Bearer token acceptance — Accept OAuth bearer tokens on the MCP endpoint alongside whatever API-key path already exists. Keep both for a while: the key path is how you test tools while the flow is half-built.
- 3
Authorize endpoint and consent screen — The only user-visible page in the whole feature, and the only human checkpoint in the flow.
- 4
Token, refresh and revoke — Short-lived access tokens, a refresh path, and a revocation route. Ours issue a 30 minute access token against a 30 day sliding refresh window.
The consent screen deserves its own paragraph, because it is the part where engineering instincts are wrong. It is not a page. It is a decision. Ours is deliberately chrome-free, with no navigation and nothing clickable but Authorize and Cancel, and it renders three facts: which client is asking, which account you are signed in as, and what scope you are about to grant. Two rules behind it are worth stealing:
- Every value on the page comes from the request parked server-side, never from the query string of the request rendering the page. Otherwise what the user reads is not necessarily what they grant.
- The client name is attacker-supplied whenever registration is open, so it is escaped and rendered as text, never into an attribute or a URL.
The reason this is a week of work rather than a quarter is that the authorize endpoint can sit in the same binary as the app and reuse the session the user already has. In the accounting platform, that means no second Google flow, no second redirect URI, and the hosted-domain restriction already on the login is what keeps the connector to the firm's own staff, for free.
Five things that break it silently
Every one of these cost us at least half a day, and all of them present the same way: the connector dialog fails with no useful message.
1. The issuer in your metadata does not match the iss your tokens carry. This is the single most common reason an "Add custom connector" dialog fails, and it fails without telling anyone why. The fix is structural rather than careful: read both from one config value, in one process, so they cannot drift.
2. You serve only one spelling of the authorization server metadata. Clients differ on whether they probe /.well-known/oauth-authorization-server or /.well-known/openid-configuration, and some try only one. A 404 on the one a given client picked ends the flow with nothing to fall back on. Serve the identical body at both. It costs one route.
3. You serve the protected resource document only at the bare path. RFC 9728 lets a client insert the resource's own path into the well-known URL, so a client whose resource is https://example.com/mcp may legitimately probe /.well-known/oauth-protected-resource/mcp. Mount the same handler at the bare path and at a wildcard beneath it.
4. Your discovery documents are not CORS-open. They are fetched cross-origin by browser-based clients before any credential exists. These documents are public by definition, since they are how an unauthenticated caller learns how to authenticate, so Access-Control-Allow-Origin: * on them grants nothing that fetching the URL directly would not.
5. You cache discovery for hours. These documents encode your issuer and endpoint URLs. A hostname change, or a fix to a wrong issuer, then has to wait out the TTL in every client that cached it. We cache for five minutes. They are cheap to serve and rarely fetched.
A sixth, less a bug than a decision: do not omit jwks_uri incorrectly, and do not emit it blank. If you sign access tokens symmetrically there is no key set to publish and the field must be absent, not "". Reusing a client-side metadata struct to emit server-side metadata is how a blank one ships.
Why we stopped relying on remembering any of this
All five are invisible in code review, because the code that causes them looks correct in isolation. So after the third implementation we pulled the endpoint into a shared internal library and wrote a conformance package beside it that asserts the behaviour rather than the source.
That distinction is the whole point. Its predecessor was a CI script that grepped for strings, and it failed the way source inspection always fails: it accumulated three exemptions, an opt-out, and still missed the one service whose endpoint path is /internal/mcp and therefore never matched a grep for /mcp. The replacement drives the service's real handler with real requests and proves that a client which gets a 401 can follow the challenge to a document that is actually served, describing a path that actually exists.
If you are going to do this more than once, write that test before the second one.
Is it safe to let users connect an AI assistant to a production system?
It is safer than the API key it replaces. It is not automatically safe, and the difference is authorization, which the token does not give you.
What you get for free: per-user identity instead of a shared secret, an explicit consent step, tokens that expire on their own, central revocation, and, when you reuse your existing IdP, the offboarding process you already run.
What you still own:
- Scope every tool to the caller's own permissions, on the server. The token says who is calling. It says nothing about what they may do. In our CRM every query is scoped to the caller's organisation automatically, so the model cannot cross a tenant boundary even if it tries.
- Validate the audience. Your server MUST reject tokens that were not issued for it. This is the requirement that stops a token harvested by another MCP server from working against yours.
- Never forward the inbound token downstream. If your MCP server calls an upstream API, it acts as a client to that API with a separate token. Passing the user's token through is the confused deputy problem, and the spec forbids it outright.
- Instrument it. We record three events, because they answer three different questions: the consent screen's outcome, an MCP session that completed, and every tool call with its outcome. No arguments are ever recorded. A firm that never connected and a firm that connected and never used it look identical otherwise, and they need very different responses from you.
- Write the consent screen for the person reading it. It is the only moment a human sees what is being granted. If the scope is described in your internal vocabulary, the checkpoint is decorative.
And one thing this article deliberately does not cover, because it is a different problem with a different answer: what happens when the model writes. Authorization decides whether a call is allowed. It says nothing about whether the person can tell what the model just did, or undo it.
We have built both shapes of that and they are genuinely different. For discrete business records, a write tool takes a dry_run flag, returns the object it would create, and the human approves before it lands. For a document somebody is iterating on, that is unusable: a confirm prompt on every edit of a ninety-item wine list is worse than no assistant. There the agent edits live, and the safety net is an undo and a revision panel, plus a tool that renders the result back to the model as a screenshot so it can look at what it did. We reversed a propose-preview-apply design to get there.
The dry-run half is written up in what separates an MCP demo from production. The live-edit half is not written up anywhere yet.
If a security review is going to ask about this, and it will, the questions land in the same place as everything else in a vendor security questionnaire: who can reach it, with whose identity, and how do you cut access.
Who should do this now
The calculation is not about AI strategy. It is about who your users are.
Do it if your application already has an SSO login and a permission model. You are mostly wiring existing parts together, and the flow inherits your offboarding for nothing. This is the case where a week of work changes who in the customer's organisation can use the thing.
Do it if you are shipping AI access to non-technical staff. An API key caps your adoption at the subset of users who will edit a config file, and in most businesses that subset is engineering. We measured this the expensive way: the capability existed for two months behind a key and went nowhere; the three-step guide is what moved it.
Wait if your MCP server is internal-only and used by five engineers. A static credential in a secrets manager is a defensible answer for a tool with five users who all have a laptop and a terminal. The flow above earns its cost when the population is wide, or outside your company, or changing.
Reconsider the whole thing if you do not have a login at all. The reason this is a week of work for an existing platform is that the identity already exists. Building an identity provider in order to build an MCP connector is a different project, and it should be planned as one.
If you land on one of the first two and would rather not build it yourself, that is the shape of work we take on: we design, build and run the MCP server and the authorization flow in front of it, whether it sits on your own platform or on an existing system like Salesforce.
The pattern underneath all of this is the same one that made MCP worth adopting in the first place, which we covered in what MCP is and when it beats building your own integrations: a standard exists so that N clients and M servers do not need N times M integrations. The authorization spec extends that from the tools to the front door. Your software does not need a Claude integration or a ChatGPT integration. It needs to be a correct OAuth resource server, once.
If you want to see what that looks like at scale, we run 91 tools through a single MCP server across CRM, invoicing and inventory, and we wrote up what separates an MCP demo from one that survives production.
And if you are weighing whether your own platform should expose one, that is a conversation we have most weeks: our MCP work, or tell us what you are running.