How to Authenticate with the OpenSEO MCP Server for Self-Hosted Instances

OpenSEO's self-hosted MCP server supports two authentication modes: Cloudflare Access (JWT-based) for production deployments and local_noauth for private or development environments.

When you self-host the every-app/open-seo repository, every request to the Model Context Protocol (MCP) endpoint must carry a valid authentication context. The server resolves this context differently based on the AUTH_MODE environment variable, either verifying a Cloudflare Access JWT or generating a synthetic identity for local use.

Supported Authentication Modes

The AUTH_MODE variable in src/lib/auth-mode.ts accepts three values, though only two apply to self-hosted deployments:

  • cloudflare_access — Production-grade JWT verification via Cloudflare Access
  • local_noauth — Bypass authentication for internal tooling or private networks
  • hosted — Reserved for the managed OpenSEO cloud service
// src/lib/auth-mode.ts
export const authModeSchema = z.enum(["cloudflare_access", "local_noauth", "hosted"]);

(source: auth-mode.ts:1-7)

Mode Comparison

Mode Security Level Use Case Required Configuration
Cloudflare Access High Production deployments with team-based access control AUTH_MODE=cloudflare_access, TEAM_DOMAIN, POLICY_AUD
Local No-Auth None Development, testing, air-gapped private networks AUTH_MODE=local_noauth

Cloudflare Access Authentication (Production)

For production self-hosted instances, OpenSEO integrates with Cloudflare Access to enforce identity-aware policies. Clients must obtain a valid Cloudflare Access JWT and include it in the cf-access-jwt-assertion header.

Required Environment Variables

AUTH_MODE=cloudflare_access
TEAM_DOMAIN=https://your-team.cloudflareaccess.com
POLICY_AUD=your-cloudflare-access-audience-tag

JWT Verification Flow

The verification pipeline resides in src/middleware/ensure-user/cloudflareAccess.ts:

  1. Extract the JWT from the cf-access-jwt-assertion header
  2. Fetch the JWK set from https://${TEAM_DOMAIN}/cdn-cgi/access/certs
  3. Verify the token using the jose library's jwtVerify function
  4. Validate the audience matches POLICY_AUD
  5. Extract identity claims: sub (user ID), email, and organization context
// src/middleware/ensure-user/cloudflareAccess.ts
export async function resolveCloudflareAccessContext(
  headers: Headers,
): Promise<CloudflareAccessContext> {
  const jwt = headers.get("cf-access-jwt-assertion");
  if (!jwt) throw new Error("Missing Cloudflare Access JWT");

  const { payload } = await jwtVerify(jwt, jwks, {
    issuer: TEAM_DOMAIN,
    audience: POLICY_AUD,
  });

  return {
    userId: payload.sub as string,
    email: payload.email as string,
    organizationId: await resolveOrganization(payload),
  };
}

(source: cloudflareAccess.ts:39-90)

Making Authenticated MCP Requests

Once your Cloudflare Access policy is configured, obtain a JWT through your identity provider and include it in requests:


# Obtain JWT via Cloudflare Access login flow, then:

curl -X POST "https://your-worker.example.com/mcp" \
     -H "Content-Type: application/json" \
     -H "cf-access-jwt-assertion: $CF_ACCESS_TOKEN" \
     -d '{
       "jsonrpc":"2.0",
       "method":"tools/list",
       "id":1
     }'

Using the MCP Client Library

For TypeScript applications, pass the JWT through custom headers:

import { createMcpClient } from "agents/mcp/client";

const client = createMcpClient({
  baseUrl: "https://your-worker.example.com/mcp",
  headers: {
    "cf-access-jwt-assertion": process.env.CF_ACCESS_JWT!,
  },
});

// Now call any OpenSEO MCP tool
const backlinks = await client.call("tools/call", {
  name: "get_backlinks_profile",
  arguments: { projectId: "proj_123" },
});

Local No-Auth Mode (Development)

For private networks or local development, set AUTH_MODE=local_noauth to disable JWT verification entirely. The server generates a synthetic user context via resolveLocalNoAuthContext() in the delegated auth middleware.


# Minimal configuration for development

AUTH_MODE=local_noauth

In this mode, the MCP client requires no authentication headers:

const client = createMcpClient({
  baseUrl: "http://localhost:8787/mcp",
  // No headers needed
});

(source: delegated auth implementation in src/middleware/ensure-user/delegated.ts)

MCP Transport Implementation

The handleSelfHostedOpenSeoMcpRequest function in src/server/mcp/transport.ts orchestrates the authentication flow for both modes:

// src/server/mcp/transport.ts
export async function handleSelfHostedOpenSeoMcpRequest(
  request: Request,
  authMode: "cloudflare_access" | "local_noauth",
  env: unknown,
  ctx: ExecutionContext,
) {
  // 1. Resolve identity based on authMode
  const userContext = authMode === "cloudflare_access"
    ? await resolveCloudflareAccessContext(request.headers)
    : await resolveLocalNoAuthContext();

  // 2. Build MCP props with resolved identity
  const mcpProps = createWorkersOAuthMcpProps(userContext, getPublicOrigin(request));

  // 3. Create and return request handler
  return createRequestHandler(mcpProps, { corsHeaders: getCorsHeaders() });
}

(source: transport.ts:64-88)

The resolved ToolAuthContext is validated against Zod schemas in src/server/mcp/context.ts before being passed to the underlying Agents SDK handler.

Summary

  • Cloudflare Access mode (AUTH_MODE=cloudflare_access) provides production-ready authentication using JWT tokens from Cloudflare's identity platform
  • Local no-auth mode (AUTH_MODE=local_noauth) eliminates authentication for trusted private deployments
  • The entry point for all MCP requests is handleSelfHostedOpenSeoMcpRequest in src/server/mcp/transport.ts
  • JWT verification occurs in src/middleware/ensure-user/cloudflareAccess.ts using the jose library
  • Identity resolution branches based on authMode, then builds a ToolAuthContext for MCP tool execution

Frequently Asked Questions

How do I obtain a Cloudflare Access JWT for testing?

Log in through your Cloudflare Access-protected application in a browser, then extract the CF_Authorization cookie or use the Cloudflare Access CLI (cloudflared access token). The JWT can also be programmatically obtained via OAuth flows configured in your Cloudflare Access policy.

Can I switch between authentication modes without redeploying?

No. The AUTH_MODE environment variable is read at worker initialization. Changing modes requires restarting or redeploying the Cloudflare Worker after updating your environment configuration.

What happens if the Cloudflare Access JWT expires?

The jwtVerify call in cloudflareAccess.ts will throw a verification error, and the MCP endpoint returns a 401 response with a clear error message indicating token expiration. Clients must refresh their JWT through the Cloudflare Access login flow.

Does local_noauth mode support multiple synthetic users?

The current implementation in resolveLocalNoAuthContext() returns a fixed synthetic identity. For multi-user testing scenarios, you would need to modify the delegated auth middleware or use Cloudflare Access with test policies.

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 →