# How OpenSEO MCP Server Handles OAuth and Direct API Authentication

> OpenSEO MCP server streamlines OAuth and direct API key authentication using a unified transport layer. Learn how it enforces mandatory mcp scope for secure access.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: internals
- Published: 2026-09-02

---

**OpenSEO supports both OAuth 2.0 Authorization Code flows and direct API key authentication for its MCP server, converging both paths on a unified transport layer that strictly enforces the mandatory `mcp` scope.**

The OpenSEO MCP (Machine Control Protocol) server implements a dual-path authentication architecture designed to accommodate interactive user sessions and programmatic API access. According to the every-app/open-seo source code, the system maintains distinct entry points for OAuth-based and API key-based authentication while routing all validated requests through a common transport layer that guarantees authorization consistency.

## OAuth 2.0 Authorization Code Flow

The primary authentication method for interactive clients follows the standard OAuth 2.0 Authorization Code flow, implemented in [`src/server/mcp/oauth-provider.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-provider.ts) through the `createOpenSeoOAuthProvider()` factory function. This provider integrates with Cloudflare Workers OAuth Provider (`@cloudflare/workers-oauth-provider`) and Better Auth for persistent storage.

### Authorization Endpoint and User Consent

The flow initiates when a client sends a request to `/api/auth/oauth2/authorize`. The provider parses the request and checks authentication status via `resolveHostedContext`:

```typescript
// src/server/mcp/oauth-provider.ts
if (url.pathname === OAUTH_AUTHORIZE_PATH) {
  return handleOAuthAuthorizeRequest(request, env);
}

```

Unauthenticated users are redirected to the sign-in page, while authenticated users proceed to the consent UI at `/oauth-consent`. The consent interface displays requested scopes, always including the mandatory `mcp` scope defined in [`src/lib/oauth-resource.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/oauth-resource.ts). If the user denies consent, the authorization fails immediately.

### Token Issuance and Scope Validation

Upon consent approval, the provider creates an OAuth grant containing the user's ID, email, organization, and granted scopes. The system validates scope presence using `getGrantedMcpScopes()`:

```typescript
// src/server/mcp/oauth-provider.ts
let scopes = getGrantedMcpScopes(authRequest.scope); // throws if `mcp` missing

```

The grant is stored in the Better Auth KV store, and an authorization code is returned to the client. The client exchanges this code at `/api/auth/oauth2/token`, where the `tokenExchangeCallback` validates the grant and issues an access token (TTL: 24 hours) and refresh token (TTL: 30 days).

## Direct API Key Authentication

For programmatic access, OpenSEO supports direct authentication via API keys implemented in [`src/server/mcp/api-key-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/api-key-auth.ts). This path bypasses the OAuth redirect flow while maintaining equivalent security controls and rate limiting.

### Key Validation and Rate Limiting

The system inspects incoming requests for an `x-api-key` header or Bearer token. Valid MCP API keys must carry the `oseo_` prefix defined in [`src/lib/auth-api-key.ts`](https://github.com/every-app/open-seo/blob/main/src/lib/auth-api-key.ts). The key is validated against Better Auth using `auth.api.verifyApiKey`:

```typescript
// src/server/mcp/api-key-auth.ts
const apiKey = getApiKey(request); // looks for header 'x-api-key' or Bearer token
if (!apiKey) return null; // fall back to OAuth provider

```

Upon successful validation, the system applies a per-user rate limit of 5000 requests per minute via the Cloudflare Rate-Limit binding `MCP_RATE_LIMIT`.

### Organization Resolution and Scope Assignment

After validation, `resolveExistingActiveHostedOrganization()` determines the user's active organization. The system constructs an MCP auth-props object using `createWorkersOAuthMcpProps()`, assigning the special client ID `"api_key"` and granting the full set of MCP OAuth scopes (`MCP_OAUTH_SCOPES`).

## Unified Transport Layer Enforcement

Both authentication paths converge at [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts), where `handleAuthenticatedOpenSeoMcpRequest()` performs final authorization. The transport extracts the `MCP_AUTH_CONTEXT_PROP` from either the OAuth token or API key props, then enforces that `MCP_SCOPE` is present:

```typescript
// src/server/mcp/transport.ts
if (!result.data[MCP_AUTH_CONTEXT_PROP].scopes.includes(MCP_SCOPE)) {
  return new Response("MCP scope required", { status: 403 });
}

```

If the scope check fails, the server returns a 403 Forbidden response. The transport layer also injects CORS headers allowing requests from any origin, supporting self-hosted MCP clients.

## Summary

- **Dual authentication paths**: OpenSEO supports both OAuth 2.0 Authorization Code flows and direct API keys (`oseo_*`) for MCP access.
- **Mandatory scope enforcement**: All authentication methods must explicitly include the `mcp` scope, validated in [`src/server/mcp/oauth-provider.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-provider.ts) via `getGrantedMcpScopes()` and enforced in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts).
- **Configurable token lifetimes**: OAuth access tokens expire after 24 hours, while refresh tokens remain valid for 30 days as configured in `createOpenSeoOAuthProvider()`.
- **Rate limiting**: API key authentication applies a 5000 request per minute limit per user via Cloudflare Rate-Limit bindings.
- **Unified transport**: Both authentication methods converge on `handleAuthenticatedOpenSeoMcpRequest()` in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) for consistent authorization checks.

## Frequently Asked Questions

### What is the required API key format for OpenSEO MCP authentication?

API keys must start with the `oseo_` prefix and are validated via `auth.api.verifyApiKey` from Better Auth. The system checks the `x-api-key` header or Bearer token format in [`src/server/mcp/api-key-auth.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/api-key-auth.ts). Without this prefix, the request falls back to standard OAuth processing or fails authentication entirely.

### How does OpenSEO enforce the mandatory MCP scope?

The `mcp` scope enforcement occurs at multiple points. During OAuth flows, `getGrantedMcpScopes()` in [`src/server/mcp/oauth-provider.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-provider.ts) throws an error if the scope is missing. For API keys, `createWorkersOAuthMcpProps()` automatically assigns all MCP scopes. Finally, [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) validates scope presence before executing any MCP method, returning 403 Forbidden if absent.

### What are the token expiration policies for OpenSEO MCP OAuth?

According to [`src/server/mcp/oauth-provider.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-provider.ts), access tokens carry a 24-hour TTL, while refresh tokens remain valid for 30 days. These lifetimes are configured in the `OAuthProvider` initialization within `createOpenSeoOAuthProvider()`.

### Can MCP clients authenticate from self-hosted origins?

Yes. The transport layer in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) explicitly adds CORS headers allowing requests from any origin. This enables self-hosted MCP clients to authenticate using either OAuth or API keys against the OpenSEO server, with all security policies enforced uniformly.