Skip to main content

Build an OAuth-Protected MCP Server for Willow

When an MCP server is set to Proxy OAuth, Willow does not hold a shared credential. Instead it runs a standard OAuth authorization-code flow against your server for each user, then forwards the access token your server issues as Authorization: Bearer <token> on every tool call. Your server is both the MCP server and its own OAuth authorization server.

This page is the endpoint reference. For a step-by-step build with runnable code, see Tutorial: Build an OAuth MCP Server with Express.

How Willow drives the flow

When an admin selects Discover OAuth Settings, and when a user connects, Willow:

  1. Fetches your authorization server metadata to learn your endpoints.
  2. Registers a client dynamically (RFC 7591), sending its callback URL as a redirect URI.
  3. Redirects the user to your authorization endpoint, receives the code at its callback, and exchanges it at your token endpoint.
  4. Forwards the resulting access token to your MCP endpoint on each tools/call.

You implement four HTTP endpoints for steps 1 through 3, plus token validation on your MCP endpoint. This page covers the OAuth endpoints; for the MCP endpoint itself (initialize, tools/list, tools/call), see Build an MCP Server Behind Willow.

1. Authorization server metadata

Serve OAuth metadata at the well-known path so Willow can discover your endpoints:

GET /.well-known/oauth-authorization-server
{
"issuer": "https://mcp.example.com",
"authorization_endpoint": "https://mcp.example.com/authorize",
"token_endpoint": "https://mcp.example.com/token",
"registration_endpoint": "https://mcp.example.com/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_post"]
}

Include a registration_endpoint so Willow can register itself. Without dynamic registration, an admin has to enter a Client ID manually under Proxy OAuth Advanced settings.

2. Dynamic client registration

Willow registers a client by POSTing its redirect URI to your registration endpoint:

POST /register
{
"redirect_uris": ["https://{your-org}.mcp-s.com/{your-org}/api/auth/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code"],
"response_types": ["code"]
}

Your response must echo redirect_uris back as an array, along with a client_id:

{
"client_id": "generated-client-id",
"redirect_uris": ["https://{your-org}.mcp-s.com/{your-org}/api/auth/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code"],
"response_types": ["code"]
}
Echo redirect_uris as an array

Willow validates the registration response and expects redirect_uris to be an array. If you omit it or return it in another shape, discovery fails with redirect_uris expected array, received undefined, and connecting the server fails with OAuth Discovery Failed — HTTP 400. Return the same redirect URIs Willow sent.

If you set token_endpoint_auth_method to a confidential method such as client_secret_post, also return a client_secret.

3. Authorization endpoint

Willow redirects the user's browser to your authorization endpoint:

GET /authorize?response_type=code&client_id=...&redirect_uri=...&state=...&code_challenge=...&code_challenge_method=S256

Authenticate the user however you like, then redirect back to the redirect_uri with a short-lived authorization code and the original state:

302 Location: https://{your-org}.mcp-s.com/{your-org}/api/auth/callback?code=<auth-code>&state=<state>

Preserve state exactly. If you advertised PKCE in your metadata, store the code_challenge to verify against the code_verifier at the token step.

4. Token endpoint

Willow exchanges the code for a token:

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=<auth-code>&redirect_uri=...&code_verifier=...

Return an access token:

{
"access_token": "<token>",
"token_type": "Bearer",
"expires_in": 3600
}

Willow stores this token per user and sends it as Authorization: Bearer <token> on every subsequent tool call for that user.

Validate the forwarded token

On your MCP endpoint, treat the token as you would any access token you issued:

  1. Read the Authorization: Bearer <token> header on initialize and tools/call.
  2. Look up or verify the token against your own store or signing keys.
  3. Reject calls whose token is missing, expired, or revoked.

Because your server issued the token, you validate it against your own authorization server, not against Willow.