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 authenticationhandleSelfHostedOpenSeoMcpRequest— 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:
- Client sends JSON-RPC — An AI agent POSTs to
/mcpwith acall_toolmethod - Transport validates — CORS, authentication, and protocol detection occur
- Context builds — User, org, role, and billing become a
ToolContext - Server routes — The
McpServerlooks up the tool by name - Schema validation — Input args are checked against the tool's Zod
inputSchema - Handler executes — Business logic runs with full
ToolContextaccess - Response formats —
mcpResponsebuildstext,meta, andstructuredContent - 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →