# How to Troubleshoot MCP Server Connection Failures in OpenSEO

> Troubleshoot OpenSEO MCP server connection failures. Learn to diagnose HTTP, authentication, construction, and wiring issues using error codes to quickly resolve problems.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-08-14

---

**OpenSEO MCP server connection failures typically stem from four layers: HTTP request handling, authentication context resolution, MCP server construction, or handler wiring—each producing distinct error codes that pinpoint the root cause.**

The Model-Context-Protocol (MCP) server in [every-app/open-seo](https://github.com/every-app/open-seo) serves as the bridge between client-side agents and the backend OpenSEO implementation. When this connection breaks, you need a systematic approach to diagnose whether the issue lies in transport routing, authentication, server initialization, or request validation. This guide walks through the exact code paths in `src/server/mcp/` to resolve failures fast.

## Understanding the Four Failure Layers

OpenSEO's MCP architecture splits connection handling into distinct layers. Knowing which layer produces which symptom accelerates debugging.

### Layer 1: HTTP Request Handling

The entry points `handleSelfHostedOpenSeoMcpRequest` and `handleAuthenticatedOpenSeoMcpRequest` in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) parse incoming requests and orchestrate the response flow.

- **401/403 errors** → Authentication context failed to resolve
- **400 errors** → Malformed JSON-RPC payload
- **405 errors** → Wrong HTTP method (not POST or OPTIONS)
- **500 errors** → Internal error before handler creation

These handlers validate request method, headers, CORS pre-flight status, and route to the appropriate auth resolver (`cloudflare_access` or `local_noauth`).

### Layer 2: Auth Context Resolution

The helper functions `resolveCloudflareAccessContext` and `resolveLocalNoAuthContext` populate `MCP_AUTH_CONTEXT_PROP` with user identity, organization ID, and base URL.

Failures here produce **incomplete auth props**:

- Missing `userId` → server rejects all tool calls
- Missing `organizationId` → multi-tenant isolation breaks
- Missing `baseUrl` → generated links point to wrong origin

Check [`src/server/mcp/context.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/context.ts) for the expected shape and `src/middleware/ensure-user/` for resolver implementations.

### Layer 3: MCP Server Construction

`createOpenSeoMcpServer` in [`src/server/mcp/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/server.ts) registers approximately 30 tools (whoami, search-console, rank-tracker, etc.) and returns a configured `McpServer` instance.

Failure symptoms at this layer:

- **404 errors** → Route not found in handler mapping
- **502 errors** → Server failed to instantiate
- **"Method not found" errors** → Specific tool failed to register

The tool registration block starts at line 44—verify no import errors prevent loading.

### Layer 4: Handler Wiring

`createMcpHandler` (from `agents/mcp/server`) wraps the server with CORS handling and legacy request processing.

Configuration in [`src/server/mcp/transport.test.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.test.ts) shows:

- `allowedOriginHostnames: undefined` → accepts any origin (self-hosted default)
- `legacy: "reject"` → forces modern JSON-RPC format

**403 errors** here indicate origin rejection; **400 errors** indicate legacy request format rejection.

## Step-by-Step Troubleshooting Workflow

### 1. Verify Request Shape

The MCP endpoint requires strict formatting:

```typescript
// Valid MCP request structure
const payload = {
  jsonrpc: "2.0",
  id: 1,
  method: "tools/list",
};

const response = await fetch("https://your-domain.com/mcp", {
  method: "POST",
  headers: {
    Accept: "application/json, text/event-stream",
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});

```

Reference the test helper `createMcpRequest` in [`src/server/mcp/transport.test.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.test.ts) (lines 69-82) for the canonical implementation.

### 2. Test CORS Pre-Flight Handling

Browsers automatically send `OPTIONS` requests before POST. The handler must short-circuit auth resolution and return 200:

```typescript
// Simulate pre-flight for debugging
const preflight = await fetch("https://your-domain.com/mcp", {
  method: "OPTIONS",
  headers: {
    Origin: "https://client.example.com",
    "Access-Control-Request-Method": "POST",
    "Access-Control-Request-Headers": "content-type,accept",
  },
});

console.assert(preflight.status === 200, "CORS pre-flight failed");

```

If you see 401/403 on `OPTIONS`, the transport layer isn't recognizing pre-flight. Check `handleSelfHostedOpenSeoMcpRequest` lines 85-99 for the early-return logic.

### 3. Validate Auth Mode Configuration

Self-hosted deployments pass an auth mode string as the second argument to `handleSelfHostedOpenSeoMcpRequest`:

| Mode | Behavior | Required Headers |
|------|----------|----------------|
| `"local_noauth"` | Creates dummy admin context | None |
| `"cloudflare_access"` | Extracts user from Cloudflare headers | `Cf-Access-Client-Id`, `Cf-Access-Jwt-Assertion` |

Test assertions at lines 71-74 in [`transport.test.ts`](https://github.com/every-app/open-seo/blob/main/transport.test.ts) verify `resolveCloudflareAccessContext` receives `Headers`. Missing headers produce incomplete auth context.

### 4. Inspect Constructed Auth Props

The symbol `MCP_AUTH_CONTEXT_PROP` carries auth data. Required fields per lines 45-52 in [`transport.test.ts`](https://github.com/every-app/open-seo/blob/main/transport.test.ts):

- `userId`
- `userEmail`
- `organizationId`
- `baseUrl`

Any `undefined` value causes runtime rejections. Debug by logging the props before server construction:

```typescript
import { createWorkersOAuthMcpProps } from "@/server/mcp/context";

const authProps = createWorkersOAuthMcpProps({
  userId: "admin",
  userEmail: "admin@localhost",
  organizationId: "delegated-local-admin",
  baseUrl: "https://open-seo.test",
});

// Verify before passing to handler
console.log("Auth props:", authProps);

```

### 5. Confirm Tool Registry Completion

`createOpenSeoMcpServer` registers tools in a block starting at line 44 of [`server.ts`](https://github.com/every-app/open-seo/blob/main/server.ts). If a tool fails to load due to import errors, you'll get "method not found" for that specific capability.

Common tool registration pattern:

```typescript
register(whoamiTool);
register(searchConsoleTools);
register(rankTrackerTools);
register(getAuditPagesTool);
// ... ~30 total tools

```

### 6. Review Handler Options

`createMcpHandler` options control request acceptance:

```typescript
createMcpHandler(server, {
  allowedOriginHostnames: undefined, // self-hosted: accept any origin
  legacy: "reject", // force modern JSON-RPC format
});

```

**Critical**: `legacy: "reject"` causes 400 errors for old request envelopes. The test at lines 155-159 in [`transport.test.ts`](https://github.com/every-app/open-seo/blob/main/transport.test.ts) documents this behavior.

### 7. Check Instrumentation Logs

[`src/server/mcp/instrumentation.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/instrumentation.ts) wraps tool execution with tracing. For **500 errors**, the stack trace points to the exact failing tool handler. Enable detailed logging to capture:

- Tool name being invoked
- Input parameters
- Execution timing
- Error stacks

### 8. Run the Test Suite

Execute the comprehensive tests to validate your deployment:

```bash
pnpm test  # or npm test

```

The [`transport.test.ts`](https://github.com/every-app/open-seo/blob/main/transport.test.ts) suite validates:
- Request parsing
- Auth resolution paths
- CORS handling
- Tool availability
- Error responses

A failing test pinpoints the broken layer without manual debugging.

## Quick Diagnostic Checklist

Use this checklist to systematically eliminate causes:

- [ ] Request method is **POST** (or **OPTIONS** for pre-flight)
- [ ] Headers include `Accept: application/json, text/event-stream` and `Content-Type: application/json`
- [ ] JSON-RPC payload contains `jsonrpc: "2.0"`, `id`, and `method` fields
- [ ] Auth mode string matches `"local_noauth"` or `"cloudflare_access"`
- [ ] Auth context resolves with all four required fields populated
- [ ] `createOpenSeoMcpServer` receives complete auth props under `MCP_AUTH_CONTEXT_PROP`
- [ ] `createMcpHandler` uses `legacy: "reject"` with appropriate origin settings
- [ ] All required tools appear in the server registration list
- [ ] [`src/server/mcp/instrumentation.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/instrumentation.ts) shows no unhandled exceptions

## Key Source Files Reference

| File | Purpose |
|------|---------|
| [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) | Request routing, auth resolution, handler orchestration |
| [`src/server/mcp/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/server.ts) | Tool registration and `McpServer` construction |
| [`src/server/mcp/transport.test.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.test.ts) | Canonical test cases documenting expected behavior |
| [`src/server/mcp/context.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/context.ts) | Auth context shape and factory functions |
| [`src/server/mcp/instrumentation.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/instrumentation.ts) | Production tracing and error capture |
| [`src/server/mcp/public-origin.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/public-origin.ts) | Public URL determination for CORS and redirects |

## Summary

- **MCP connection failures** in OpenSEO fall into four diagnosable layers: HTTP transport, auth resolution, server construction, and handler wiring
- **Error codes map directly** to layers: 401/403 → auth, 400 → payload/format, 405 → method, 500 → server/internal
- **The test suite in [`transport.test.ts`](https://github.com/every-app/open-seo/blob/main/transport.test.ts)** serves as the authoritative specification for correct behavior
- **Auth context completeness** is the most common root cause—verify all four required fields
- **CORS pre-flight must short-circuit** before auth resolution or browsers will fail silently

## Frequently Asked Questions

### What causes "method not found" errors in OpenSEO MCP?

The MCP server returns "method not found" when a tool wasn't registered in `createOpenSeoMcpServer`. Check [`src/server/mcp/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/server.ts) around line 44 for the registration block. Import failures or missing tool files prevent registration—verify all ~30 tools load without errors. The `tools/list` method should return all available tools; compare its output against your expected set.

### Why do I get 403 errors on self-hosted OpenSEO deployments?

403 errors indicate origin rejection or authentication failure. For self-hosted deployments, `allowedOriginHostnames` defaults to `undefined` (accept any origin), so check that your auth mode (`"local_noauth"` or `"cloudflare_access"`) matches your deployment. With `"cloudflare_access"`, missing `Cf-Access-*` headers cause immediate 403. With `"local_noauth"`, ensure the dummy context generator runs successfully.

### How do I debug CORS failures with OpenSEO MCP?

CORS issues manifest as failed pre-flight `OPTIONS` requests. The handler must return 200 with appropriate headers before processing `POST`. Test with explicit `OPTIONS` requests and verify `handleSelfHostedOpenSeoMcpRequest` returns early (lines 85-99 in [`transport.ts`](https://github.com/every-app/open-seo/blob/main/transport.ts)). If auth resolution runs on `OPTIONS`, the pre-flight fails—check that the method check precedes all auth logic.

### What's the difference between `handleSelfHostedOpenSeoMcpRequest` and `handleAuthenticatedOpenSeoMcpRequest`?

`handleSelfHostedOpenSeoMcpRequest` accepts an explicit auth mode parameter for deployments you control, supporting `"local_noauth"` for development or `"cloudflare_access"` for Cloudflare-protected origins. `handleAuthenticatedOpenSeoMcpRequest` infers auth from the request environment for managed deployments. Use the self-hosted variant when running your own infrastructure; the authenticated variant for platform-managed instances.