How the OpenSEO MCP Server Works for AI Agents: Architecture & Tool Registration Explained

The OpenSEO MCP server exposes SEO research tools to AI agents via the Model Context Protocol (MCP), using a layered architecture of transport, context, server, and tool registry layers to handle authentication, request routing, and JSON-RPC responses.

OpenSEO provides a production-ready Model Context Protocol (MCP) server that lets AI agents perform keyword research, SERP analysis, domain overviews, and backlink checks through a standardized JSON-RPC interface. This article breaks down how the server works based on the actual implementation in the every-app/open-seo repository.

What Is MCP and Why OpenSEO Uses It

The Model Context Protocol (MCP) is a JSON-RPC based standard for exposing tools to AI systems. Instead of building custom integrations for each AI client (Claude Code, Cursor, Codex CLI), OpenSEO implements a single MCP server that any compatible agent can discover and call.

This approach eliminates fragmentation: one tool registration in src/server/mcp/tools/ automatically becomes available to all MCP-speaking clients.

The Five-Layer Architecture

OpenSEO's MCP implementation follows a strict layered design. Each layer has a single responsibility and clear interfaces to adjacent layers.

Transport Layer: HTTP Entry and Authentication

The transport layer in src/server/mcp/transport.ts handles all HTTP concerns: CORS validation, request parsing, authentication resolution, and protocol detection (legacy JSON vs. modern streaming).

Two primary entry points exist:

  • handleAuthenticatedOpenSeoMcpRequest — for hosted deployments using OAuth-based authentication
  • handleSelfHostedOpenSeoMcpRequest — for self-hosted environments using Cloudflare Access

Both functions validate the request origin, resolve the authenticated user context, and build an McpProps object that flows downstream.

// src/server/mcp/transport.ts
export async function handleAuthenticatedOpenSeoMcpRequest(
  request: Request,
  props: unknown,
  env: unknown,
  ctx: ExecutionContext,
) {
  const result = hostedWorkersOAuthMcpPropsSchema.safeParse(props);
  if (!result.success) return new Response("MCP auth context required", { status: 403 });

  const authContext = result.data[MCP_AUTH_CONTEXT_PROP];
  const membership = await AuthRepository.getMembership(authContext.userId, authContext.organizationId);
  if (!membership) return new Response("Organization access revoked", { status: 401 });

  const requestProps = createWorkersOAuthMcpProps({ ...authContext, role: membership.role });
  return createRequestHandler(requestProps, [new URL(getHostedBaseUrl()).hostname])(request, env, ctx);
}

The transport wraps the McpServer in a WebStandardStreamableHTTPServerTransport for modern streaming support or falls back to legacy handling as needed.

Context Layer: Normalized ToolContext

The context layer in src/server/mcp/context.ts transforms raw authentication data into a structured ToolContext that every tool receives. This includes:

  • User identity, organization, and role
  • Scoped permissions for the request
  • Billing context for credit consumption
  • Project metadata helpers
  • Base URL construction for link generation

Normalizing these concerns in one place prevents every tool from reimplementing authentication parsing.

Server Layer: McpServer Instantiation

The server layer in src/server/mcp/server.ts creates the central McpServer instance, configures server-wide capabilities, and orchestrates tool registration.

// src/server/mcp/server.ts
export function createOpenSeoMcpServer(authProps: McpProps) {
  const server = new McpServer(
    {
      name: "OpenSEO MCP",
      title: "OpenSEO",
      version: "0.0.12",
      description:
        "SEO research tools for AI agents: keyword research …",
      websiteUrl: "https://openseo.so",
      icons: [{ src: "https://openseo.so/android-chrome-512x512.png", mimeType: "image/png", sizes: ["512x512"] }],
    },
    {
      capabilities: { tools: { listChanged: false } },
      instructions:
        "OpenSEO research tools use credits …",
    },
  );

  const register = <Input extends ToolSchema>(tool: OpenSeoToolDefinition<Input>) =>
    registerOpenSeoTool(server, tool, authProps);

  // Register every tool…
  register(whoamiTool);
  register(getDomainOverviewTool);
  // …
  return server;
}

Key configuration includes:

  • Server metadata — name, version, description, icons for client discovery
  • Capabilities — currently tools.listChanged: false (static tool set)
  • Instructions — credit usage guidance for AI agents

Tool Registry Layer: Individual SEO Tools

Each MCP tool lives in src/server/mcp/tools/ and exports a definition containing:

  • name — the JSON-RPC method identifier (e.g., get_domain_overview)
  • config — Zod schemas for input validation and output typing
  • handler — the business logic wrapped with authentication and telemetry

Registration instruments the handler for telemetry and binds it to the server with schema validation.

Service Layer: Reusing Existing Domain Logic

Tools do not duplicate business logic. Instead, they delegate to existing services like DomainService.getOverview, KeywordService.research, or BacklinkService.analyze. This guarantees that:

  • MCP writes appear in the OpenSEO dashboard immediately
  • Billing, project memory (SAM), and audit logging work identically to UI actions
  • Bug fixes and feature updates apply to all interfaces simultaneously

How an MCP Request Flows Through the System

Understanding the end-to-end flow clarifies where customization and debugging can occur:

  1. Client sends JSON-RPC — An AI agent POSTs to /mcp with a call_tool method
  2. Transport validates — CORS, authentication, and protocol detection occur
  3. Context builds — User, org, role, and billing become a ToolContext
  4. Server routes — The McpServer looks up the tool by name
  5. Schema validation — Input args are checked against the tool's Zod inputSchema
  6. Handler executes — Business logic runs with full ToolContext access
  7. Response formatsmcpResponse builds text, meta, and structuredContent
  8. JSON-RPC returns — The transport wraps the result and adds CORS headers

Example: The Get Domain Overview Tool

The get_domain_overview tool demonstrates the complete pattern:

// src/server/mcp/tools/get-domain-overview.ts
export const getDomainOverviewTool = {
  name: "get_domain_overview",
  config: { /* Zod schemas … */ },
  handler: withMcpProjectAuth(async (args, context) => {
    const { locationCode, languageCode } = resolveLabsMarket(args, context.project);
    const result = await DomainService.getOverview(
      { projectId: args.projectId, domain: args.domain, scope: args.scope, locationCode, languageCode },
      context.billing,
    );
    const text = [
      `Target: ${result.displayTarget} (scope: ${result.scope})`,
      `Organic traffic: ${result.organicTraffic ?? "?"}`,
      // …
    ].join("\n");
    return mcpResponse({ text, meta: buildProjectMeta(context, args.projectId, `/p/${args.projectId}/domain`, { domain: args.domain }), structuredContent: result });
  }),
};

The withMcpProjectAuth wrapper ensures the tool only runs when the user has valid project access. The mcpResponse helper in src/server/mcp/formatters.ts constructs a three-part response:

Field Purpose
text Human-readable table for text-only MCP clients
meta Link back to the OpenSEO UI for full visualization
structuredContent Raw service result for rich-client validation

What an AI Agent Sends and Receives

A typical client request looks like:

POST https://app.openseo.so/mcp
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": "123",
  "method": "call_tool",
  "params": {
    "tool_name": "get_domain_overview",
    "args": {
      "projectId": "proj_abc123",
      "domain": "example.com"
    }
  }
}

The response provides multiple consumption options:

{
  "jsonrpc": "2.0",
  "id": "123",
  "result": {
    "text": "Target: example.com (scope: domain)\nOrganic traffic: 12 300\nOrganic keywords: 4 567\nBacklinks: 8 910\nReferring domains: 321",
    "meta": { "projectId": "proj_abc123", "url": "https://app.openseo.so/p/proj_abc123/domain?domain=example.com" },
    "structuredContent": { "organicTraffic": 12300, "organicKeywords": 4567, "backlinks": 8910, "referringDomains": 321 }
  }
}

AI clients can display the text block for immediate user feedback, use structuredContent for programmatic processing, or present the meta.url link for deep-dive analysis.

Key Implementation Files

File Responsibility
src/server/mcp/server.ts McpServer instantiation and tool registration
src/server/mcp/transport.ts HTTP handling, auth resolution, CORS, protocol routing
src/server/mcp/context.ts ToolContext normalization from OAuth/Cloudflare Access
src/server/mcp/tools/*.ts Individual tool implementations
src/server/mcp/formatters.ts mcpResponse construction for consistent output
web/content/docs/mcp.md User-facing connection documentation

Summary

  • OpenSEO's MCP server exposes SEO tools through a standardized JSON-RPC interface that any MCP-compatible AI agent can consume
  • The five-layer architecture (transport, context, server, tool registry, service) separates concerns and enables both hosted and self-hosted deployments
  • Tool handlers reuse existing domain services, ensuring MCP actions integrate seamlessly with the OpenSEO dashboard and billing system
  • Response formatting provides human-readable text, UI deep-links, and structured data to accommodate diverse client capabilities

Frequently Asked Questions

What AI clients can connect to the OpenSEO MCP server?

Any client implementing the Model Context Protocol can connect. Confirmed compatible clients include Claude Code, Cursor, Codex CLI, and OpenClaw. The server speaks standard MCP JSON-RPC over HTTP with CORS support for browser-based tools.

How does authentication work for MCP requests?

The transport layer supports two flows. Hosted deployments validate OAuth tokens through handleAuthenticatedOpenSeoMcpRequest, checking organization membership via AuthRepository.getMembership. Self-hosted deployments use handleSelfHostedOpenSeoMcpRequest with Cloudflare Access token validation. Both paths produce an McpProps object that downstream layers consume.

Can I add custom SEO tools to the OpenSEO MCP server?

Yes. Create a new file in src/server/mcp/tools/ following the OpenSeoToolDefinition pattern: export a name, Zod config with inputSchema and outputSchema, and a handler wrapped with withMcpProjectAuth. Import and register() your tool in src/server/mcp/server.ts. The server will expose it immediately to all MCP clients.

Does using MCP consume the same credits as the web UI?

Yes. Tool handlers receive a context.billing object and pass it to underlying services. Credit consumption, project memory writes, and audit logging occur identically regardless of whether the action originates from the web UI, MCP, or SAM server.

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 →