How to Configure OAuth for MCP in Self-Hosted Mode: A Complete Guide

To configure OAuth for MCP in self-hosted mode, set the BETTER_AUTH_SECRET and OAuth client credentials in your environment variables, deploy the Cloudflare Workers script, and complete the standard OAuth 2.0 flow via /api/auth/oauth2/authorize to obtain access tokens for the /mcp endpoint.

The OpenSEO repository provides a self-hosted Multi-Channel Protocol (MCP) server that exposes SEO tools—such as keyword research, SERP analysis, and backlink tracking—to external AI agents. When deploying this system in self-hosted mode, configuring OAuth 2.0 is essential to secure access to these tools without relying on external identity providers. The entire OAuth implementation runs inside your Cloudflare Workers environment and is fully customizable through the source code in src/server/mcp/oauth-provider.ts.

Prerequisites and Environment Variables

Before deploying the MCP server, you must configure several critical environment variables. The system performs strict pre-flight checks to ensure security prerequisites are met.

Set the following variables in your Workers environment:


# Required: Encryption key for OAuth tokens (must be ≥ 32 characters)

BETTER_AUTH_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Required: Your OAuth client credentials

OAUTH_CLIENT_ID=your-mcp-client-id
OAUTH_CLIENT_SECRET=your-mcp-client-secret

# Optional: KV namespace for storing OAuth state

OAUTH_KV=oauth_kv_namespace

The BETTER_AUTH_SECRET validation occurs in src/lib/selfhost-preflight.ts (lines 174-182). If this secret is missing or shorter than 32 characters, the self-hosted instance will emit a clear error and disable Google Search Console OAuth features.

Understanding the OAuth Provider Architecture

The OAuth implementation is modular and consists of several interconnected components:

  • createOpenSeoOAuthProvider in src/server/mcp/oauth-provider.ts (lines 18-33): Initializes the provider with MCP-specific routes, token time-to-live (TTL) settings, and supported scopes.
  • createWorkersOAuthMcpProps in src/server/mcp/context.ts (lines 19-28): Constructs the authentication context containing user ID, email, and organization ID that tools access during execution.
  • Route handlers: The provider automatically registers /api/auth/oauth2/authorize, /api/auth/oauth2/token, /api/auth/oauth2/register, and the MCP API endpoint at /mcp.

Token lifetimes are defined as constants: access tokens expire after 24 hours (MCP_ACCESS_TOKEN_TTL_SECONDS at line 46) and refresh tokens last 30 days (MCP_REFRESH_TOKEN_TTL_SECONDS at line 47).

Step-by-Step Configuration

Deploy the Workers OAuth Provider

The main entry point at src/server.ts instantiates the OAuth provider without requiring additional boilerplate:

// src/server.ts (excerpt)
import { createOpenSeoOAuthProvider } from "@/server/mcp/oauth-provider";

const openSeoOAuthProvider = createOpenSeoOAuthProvider(appFetch);

return openSeoOAuthProvider.fetch(request, env as OpenSeoOAuthEnv, ctx);

This single integration registers all OAuth endpoints and the MCP tool API. The provider handles request routing, consent management, and token issuance automatically.

Define MCP OAuth Scopes

Scopes control access levels to MCP tools. The supported scopes are defined in src/lib/oauth-resource.ts:

// src/lib/oauth-resource.ts (excerpt)
export const MCP_OAUTH_SCOPES = ["mcp", "gsc", "search-console"];
export const MCP_SCOPE = "mcp";

The base "mcp" scope is mandatory for accessing core tools. Additional scopes like "gsc" enable specific integrations such as Google Search Console data access.

Execute the Authorization Flow

Step 1: Direct users to the authorization endpoint

Construct the authorization URL with the required parameters:

GET https://<your-host>/api/auth/oauth2/authorize \
    ?response_type=code \
    &client_id=$OAUTH_CLIENT_ID \
    &redirect_uri=https://<your-host>/oauth/callback \
    &scope=mcp \
    &state=xyz123

The server redirects to /oauth-consent, where the user accepts or denies the request. The buildConsentUrl function (lines 85-95 in oauth-provider.ts) constructs this consent screen.

Step 2: Exchange the authorization code for tokens

After the user accepts, exchange the code at the token endpoint:

POST https://<your-host>/api/auth/oauth2/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=authorization_code&code=CODE_FROM_STEP_1&redirect_uri=https://<your-host>/oauth/callback&client_id=$OAUTH_CLIENT_ID&client_secret=$OAUTH_CLIENT_SECRET"

The JSON response includes:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...",
  "expires_in": 86400,
  "token_type": "Bearer",
  "scope": "mcp"
}

Call MCP Tools with Access Tokens

With a valid access token, invoke MCP tools via JSON-RPC requests to the /mcp endpoint:

POST https://<your-host>/mcp \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "jsonrpc": "2.0",
      "method": "get_backlinks_profile",
      "params": { "domain": "example.com" },
      "id": 1
    }'

The handleAuthenticatedOpenSeoMcpRequest function in src/server/mcp/transport.ts validates the token, extracts the auth context via createWorkersOAuthMcpProps, and dispatches the tool call.

Refresh Expired Tokens

Since access tokens expire after 24 hours, implement token refresh in your client:

POST https://<your-host>/api/auth/oauth2/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=refresh_token&refresh_token=$REFRESH_TOKEN&client_id=$OAUTH_CLIENT_ID&client_secret=$OAUTH_CLIENT_SECRET"

The tokenExchangeCallback in the provider options (lines 33-37) handles scope validation during refresh using withWorkersOAuthMcpScopes.

Troubleshooting Common Configuration Issues

Issue Source Location Resolution
Missing BETTER_AUTH_SECRET src/lib/selfhost-preflight.ts (lines 174-182) Ensure the secret is set and ≥ 32 characters; check Workers logs for the specific error message.
Invalid request origin csrfProtected in oauth-provider.ts (lines 48-50) Returns HTTP 403 with "Invalid request origin"; verify your redirect_uri matches the registered client configuration.
Missing required MCP scope getGrantedMcpScopes (lines 39-48) Returns HTTP 400 with "The mcp scope is required"; ensure your authorization request includes scope=mcp.
Unauthenticated consent attempts resolveContextForConsent (lines 73-81) Returns null context, triggering a redirect to sign-in; ensure users are authenticated before initiating OAuth.

Summary

  • Set BETTER_AUTH_SECRET (minimum 32 characters) and OAuth client credentials in your environment to enable encryption and token security.
  • Deploy the provider via src/server.ts, which automatically registers /api/auth/oauth2/authorize, /api/auth/oauth2/token, and /mcp endpoints.
  • Request the "mcp" scope during authorization to access core SEO tools; additional scopes like "gsc" enable specific integrations.
  • Exchange codes for tokens using standard OAuth 2.0 flows, then call MCP tools via JSON-RPC to /mcp with Bearer token authentication.
  • Implement refresh logic to handle the 24-hour access token expiration using the 30-day refresh tokens.

Frequently Asked Questions

What is the minimum length for BETTER_AUTH_SECRET?

The BETTER_AUTH_SECRET must be at least 32 characters long. This requirement is enforced in src/lib/selfhost-preflight.ts (lines 174-182). If the secret is missing or too short, the self-hosted deployment will disable Google Search Console OAuth features and log a descriptive error.

How long do MCP access tokens remain valid?

Access tokens expire after 24 hours (86,400 seconds), defined by MCP_ACCESS_TOKEN_TTL_SECONDS in src/server/mcp/oauth-provider.ts (line 46). Refresh tokens remain valid for 30 days (MCP_REFRESH_TOKEN_TTL_SECONDS at line 47), allowing clients to maintain long-term sessions without re-authorization.

Can I restrict which MCP tools a client can access?

Yes, scope-based access control is implemented via the MCP_OAUTH_SCOPES array in src/lib/oauth-resource.ts. While the base "mcp" scope grants access to core tools, you can define additional scopes (such as "gsc" for Google Search Console) and modify the tokenExchangeCallback in oauth-provider.ts to validate specific scopes before issuing tokens.

Why does my authorization request return "Invalid request origin"?

This error originates from the csrfProtected middleware in src/server/mcp/oauth-provider.ts (lines 48-50) and indicates a CSRF protection failure. Verify that the redirect_uri parameter in your authorization request exactly matches the origin registered for your OAuth client, and ensure the request includes proper origin headers.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →