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 Accesslocal_noauth— Bypass authentication for internal tooling or private networkshosted— 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:
- Extract the JWT from the
cf-access-jwt-assertionheader - Fetch the JWK set from
https://${TEAM_DOMAIN}/cdn-cgi/access/certs - Verify the token using the
joselibrary'sjwtVerifyfunction - Validate the audience matches
POLICY_AUD - 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
handleSelfHostedOpenSeoMcpRequestinsrc/server/mcp/transport.ts - JWT verification occurs in
src/middleware/ensure-user/cloudflareAccess.tsusing thejoselibrary - Identity resolution branches based on
authMode, then builds aToolAuthContextfor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →