Skip to main content
MCP export needs to know who the person at the other end is, and only you can answer that. The login bridge is where you answer it: a single page on your host that makes sure the user is signed in, then redirects back to us with a signed assertion naming them. Everything else in the OAuth flow is ours — discovery, client registration, consent, tokens, grants, revocation. You run no authorization server, store no clients, and issue no tokens. Set its URL as MCP login URL on the connection the agent uses. Until it is set, that agent is not exportable.

The exchange

1

We send the user to you

During authorization we resolve the agent to its connection and redirect the browser to your login URL with four query parameters:We send no return_to. The callback is a fixed URL, so there is no redirect target for you to validate.
2

You authenticate the user

Their existing session if they have one, your normal login if they do not. We never see credentials.
3

You redirect back with an assertion

4

We verify, ask for consent, and issue the token

We match state to the request we stashed, verify the assertion against your key, show our consent screen, record the grant, and hand the client a code.
The whole handshake expires in 10 minutes, and state is claimed exactly once.

The assertion

A JWT signed with your private key — the same key you already sign session tokens and mint responses with.
string
required
The subject. Opaque to us: we carry it into the grant, the run, and the token we mint against your mint_url, and never parse it. It must be the same subject your own API authorizes — see Subjects.
string
required
Exactly https://api.usepatchwork.co/oauth/authorize/callback. Binds the assertion to this endpoint so it cannot be replayed elsewhere.
string
required
The state value we sent you. Binds this assertion to this one authorization request.
integer
required
Expiry. Keep it short — a couple of minutes is plenty for a redirect. We allow 30 seconds of clock skew.
string
required
JWT header, not a claim. Names the signing key so we can match it in your JWKS.
Signing must be asymmetric: RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384 or PS512. HS* is refused — we hold only your public key, and accepting a symmetric algorithm against a public key is a forgery.

Which key we check it against

The connection’s own jwks_uri if it has one, otherwise your workspace’s. Most integrations have one key and should leave the connection’s blank; set it only when a connection has a genuinely separate signing key for a separate user pool. Either way it is the key you already published, matched by kid.

Minimal implementation

No client store, no token endpoint, no consent UI, no grant table. Those are ours.

It is a page, not a redirect

The bridge is yours, so anything that decides who the subject is belongs here — and this is the only place it can live, because we do not model your tenants.
  • Tenant or workspace choice. A user who belongs to several picks one, and you bake the choice into sub — user_9f2:acme. The MCP client then acts in that tenant for the life of the grant.
  • Step-up MFA, seat and plan checks, blocking a user, an interstitial explaining what is about to be connected: your policy, before you mint the subject.
Whatever you decide, decide it before signing. The subject in the assertion is what the grant records and what every later call runs as.

Why it is safe

Checklist

  • The URL is set on the connection the agent uses.
  • Signed in, the bridge redirects back without the user doing anything.
  • Signed out, it logs them in and then redirects back.
  • aud is the callback URL exactly, including the scheme and host.
  • nonce is the state you were given, unmodified.
  • The signature is asymmetric and the kid is in your published JWKS.
  • sub is the same subject string your own API authorizes, and your mint endpoint will accept it from the relay branch — that is how the agent gets a token for your backend on every MCP call.