How the OpenSEO OAuth Provider Handles MCP Client Registrations and Tokens
The OpenSEO OAuth provider delegates core OAuth 2.0 logic to the @cloudflare/workers-oauth-provider package while implementing a thin compatibility layer in src/server/mcp/oauth-provider.ts that normalizes dynamic client registration requests, enforces mandatory mcp scopes, and manages token lifecycles with 24-hour access tokens and 30-day refresh tokens backed by Cloudflare KV.
The OpenSEO application bundles a custom OAuth 2.0 provider specifically engineered for Managed Cloud Platform (MCP) integrations. According to the source code in the every-app/open-seo repository, this implementation exposes standard OAuth endpoints under /api/auth/oauth2/, handles dynamic client registration (DCR) with specialized normalization for public versus confidential clients, and persists MCP authentication context in KV storage to maintain user organization associations across token exchanges.
OAuth Endpoint Architecture
The provider exposes three standard OAuth 2.0 endpoints defined as constants in src/server/mcp/oauth-provider.ts at lines 32‑35:
/api/auth/oauth2/authorize– Initiates the authorization flow/api/auth/oauth2/token– Handles token exchange and refresh/api/auth/oauth2/register– Processes dynamic client registration
These paths are wired into the OAuthProvider instance created by the createProvider factory function (lines 99‑135). The configuration limits available scopes to the MCP-specific set (MCP_OAUTH_SCOPES) and mandates the mcp scope for all operations (lines 26‑38).
Dynamic Client Registration Normalization
When handling DCR requests, the provider applies a compatibility shim to handle variations in client authentication methods. The normalizeClientRegistrationRequest function in src/server/mcp/oauth-registration.ts (lines 14‑68) performs critical transformations:
- Validates request size and parses the JSON payload
- Detects public clients – Requests omitting
token_endpoint_auth_methodare automatically assignedtoken_endpoint_auth_method: "none" - Handles confidential clients – Special cases like Perplexity that require secrets receive
client_secret_postauthentication
This normalization ensures compatibility with diverse MCP clients before delegating to the underlying @cloudflare/workers-oauth-provider package.
MCP-Specific Token Lifecycle Management
Token issuance and validation are governed by the tokenExchangeCallback defined in src/server/mcp/oauth-provider.ts (lines 16‑22). This callback enforces strict MCP requirements:
tokenExchangeCallback: ({ props, requestedScope }) => {
if (!requestedScope.includes(MCP_SCOPE)) {
throw new OAuthError("invalid_scope", {
description: "The mcp scope is required"
});
}
const authContext = workersOAuthMcpPropsSchema.parse(props)[MCP_AUTH_CONTEXT_PROP];
return {
accessTokenProps: createWorkersOAuthMcpProps({
...authContext,
scopes: requestedScope,
}),
};
}
Token lifetimes are configured in the provider options (lines 48‑50):
- Access tokens: 24 hours
- Refresh tokens: 30 days
- Client registrations: 1 year TTL (line 55)
The callback validates that every token request includes the mandatory mcp scope, then reconstructs the accessTokenProps using the stored MCP context from KV storage, ensuring tokens remain bound to the original user ID, email, and organization.
Authorization Flow and Context Persistence
After user consent, the handleOAuthConsentResponse function (lines 36‑44) creates MCP-specific properties using createWorkersOAuthMcpProps. These properties embed the user's authentication context and granted scopes into the KV payload. The provider then calls oauth.completeAuthorization (lines 45‑55) to finalize the flow, storing the context for subsequent tokenExchangeCallback invocations.
Garbage Collection of Expired Registrations
The exported provider object includes a purgeExpiredData method (lines 64‑71) designed for cron-driven cleanup. This routine calls the underlying provider's garbage collection logic with a batch size of 200 keys, removing stale KV entries from expired client registrations and tokens without manual intervention.
Implementing MCP OAuth Flows
Registering a New Client
To dynamically register an MCP client, POST to the registration endpoint:
const registrationBody = {
redirect_uris: ["https://myapp.example/callback"],
// Omit token_endpoint_auth_method for public clients
};
const resp = await fetch(
"https://openseo.example.com/api/auth/oauth2/register",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(registrationBody),
}
);
const clientInfo = await resp.json();
// Returns client_id, and client_secret for confidential clients
Initiating Authorization
Construct the authorization URL with the mandatory mcp scope:
const authUrl = new URL("https://openseo.example.com/api/auth/oauth2/authorize");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", clientInfo.client_id);
authUrl.searchParams.set("redirect_uri", "https://myapp.example/callback");
authUrl.searchParams.set("scope", "mcp read write");
authUrl.searchParams.set("state", "xyz"); // CSRF protection
// Redirect browser to authUrl
Exchanging Codes for Tokens
Exchange the authorization code for access and refresh tokens:
const tokenResp = await fetch(
"https://openseo.example.com/api/auth/oauth2/token",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code: authCode,
redirect_uri: "https://myapp.example/callback",
client_id: clientInfo.client_id,
// client_secret: only for confidential clients
}),
}
);
const { access_token, refresh_token, expires_in } = await tokenResp.json();
Refreshing Access Tokens
Use the refresh token to obtain new access tokens without user interaction:
const refreshResp = await fetch(
"https://openseo.example.com/api/auth/oauth2/token",
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: clientInfo.client_id,
// client_secret: required for confidential clients
}),
}
);
const { access_token, expires_in } = await refreshResp.json();
Summary
- Endpoint Structure: The provider exposes
/api/auth/oauth2/authorize,/token, and/registerthrough constants defined insrc/server/mcp/oauth-provider.ts. - Registration Normalization: The
normalizeClientRegistrationRequestfunction insrc/server/mcp/oauth-registration.tsdistinguishes public clients (none) from confidential clients like Perplexity (client_secret_post). - Scope Enforcement: The
tokenExchangeCallbackinsrc/server/mcp/oauth-provider.tsthrowsinvalid_scopeerrors for any request missing the mandatorymcpscope. - Token Lifetimes: Access tokens expire in 24 hours, refresh tokens in 30 days, and client registrations persist for one year.
- Context Persistence: User identity and organization data are stored in KV via
createWorkersOAuthMcpPropsand restored during token exchanges. - Maintenance: The
purgeExpiredDatamethod performs automated garbage collection of expired KV entries in batches of 200 keys.
Frequently Asked Questions
What is the difference between public and confidential MCP clients in OpenSEO?
Public clients omit the token_endpoint_auth_method in registration requests and are automatically assigned the none authentication method, making them suitable for browser-based applications. Confidential clients, such as those used by Perplexity, explicitly require secrets and receive the client_secret_post method to authenticate at the token endpoint, as determined by the normalization logic in src/server/mcp/oauth-registration.ts.
Why does every token request require the mcp scope?
The tokenExchangeCallback function in src/server/mcp/oauth-provider.ts explicitly validates that requestedScope.includes(MCP_SCOPE) returns true before issuing tokens. This enforcement ensures that all access tokens issued through the OpenSEO provider maintain compatibility with the Managed Cloud Platform API requirements, preventing unauthorized scope elevation.
How long do MCP access tokens and client registrations remain valid?
According to the provider configuration in src/server/mcp/oauth-provider.ts, access tokens expire after 24 hours, refresh tokens remain valid for 30 days, and dynamic client registrations persist for one year (365 days) before automatic expiration and garbage collection.
Which package provides the underlying OAuth 2.0 implementation?
The OpenSEO application depends on @cloudflare/workers-oauth-provider, an open-source package that handles the core OAuth 2.0 logic. The every-app/open-seo repository adds a thin compatibility layer—including the normalizeClientRegistrationRequest shim and MCP-specific context handling—to adapt this package for Managed Cloud Platform integrations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →