How to Integrate Your Own Services with the OpenSEO MCP Server

You integrate custom services by creating a Zod-validated tool module in src/server/mcp/tools/; the createOpenSeoMcpServer factory auto-discovers these files and exposes them to AI agents via the Model Context Protocol (MCP) without requiring changes to core server logic.

The OpenSEO MCP server is a production-ready implementation built on the Agents SDK that exposes SEO data—keywords, backlinks, SERP results, and rank tracking—to AI agents through a JSON-RPC-style API. According to the every-app/open-seo source code, the server uses a strict plugin architecture where each integration lives as an isolated tool, making it straightforward to add your own services while inheriting built-in authentication, CORS handling, and legacy JSON support.

OpenSEO MCP Server Architecture Overview

Before adding your service, understand the three-layer architecture defined in the source:

  • Transport Layer (src/server/mcp/transport.ts): Handles CORS headers, legacy JSON request validation, and routes incoming calls to handleAuthenticatedOpenSeoMcpRequest or handleSelfHostedOpenSeoMcpRequest. This layer ensures your tool inherits automatic authentication checks.

  • Context Provider (src/server/mcp/context.ts): Constructs the McpProps object containing the authenticated user identity, organization ID, and base URL. Your tool receives this context automatically, allowing you to enforce row-level security or tenant isolation.

  • Tool Registry (src/server/mcp/tools/): A directory of thin wrappers around service-side functions. Each tool validates input using Zod and returns a structuredContent payload. The server serializes this for JSON-RPC callers automatically.

Step-by-Step Integration Guide

Step 1: Create a New Tool Module in src/server/mcp/tools/

Create a TypeScript file in the tools directory. The file must export a tool object conforming to the McpTool interface (defined in src/server/mcp/table.ts). The server uses a static import map to auto-discover any file placed here, so no manual registration is required.

// src/server/mcp/tools/echo.ts
import { z } from "zod";
import { createMcpTool } from "agents/mcp/server";
import { outputSchema } from "@/server/mcp/output-schemas";

const inputSchema = z.object({
  message: z.string(),
});

const resultSchema = outputSchema;

async function handler(args: { message: string }) {
  // Replace with your actual service call
  const echoed = `You said: ${args.message}`;
  return { result: echoed };
}

export const echo = createMcpTool({
  name: "echo",
  inputSchema,
  resultSchema,
  handler,
});

Step 2: Define Input and Output Schemas with Zod

Every tool must declare a strict input schema using Zod to validate JSON-RPC parameters before your handler executes. The output must conform to the centralized outputSchema exported from src/server/mcp/output-schemas.ts, ensuring consistent serialization across the MCP boundary.

  • Input validation: Use z.object() to define required fields, types, and constraints.
  • Output wrapping: Return an object with a result key containing your serializable payload.

Step 3: Implement the Handler Function

The handler receives validated arguments and the McpProps context. Implement your business logic here—whether querying a database, calling an internal microservice, or fetching from a third-party REST API.

Key implementation rules:

  • Keep handlers async and stateless.
  • Access environment variables for secrets (e.g., process.env.WEATHER_API_KEY).
  • Throw standard errors; the transport layer in transport.ts catches these and returns proper MCP error codes.

Step 4: Expose the Tool to Clients

Once the file is saved in src/server/mcp/tools/, the createOpenSeoMcpServer factory automatically includes it in the tool registry. Clients can immediately invoke it via the MCP SDK:

// Client-side invocation
const response = await mcp.call("echo", { message: "Hello World" });
console.log(response.result); // "You said: Hello World"

Authentication is handled transparently by the existing OAuth flow managed in src/lib/oauth-resource.ts, so your tool executes under the same user context as built-in tools.

Real-World Example: Integrating an External REST API

Below is a complete implementation for integrating a weather API, demonstrating environment variable usage and external HTTP calls:

// src/server/mcp/tools/weather.ts
import { z } from "zod";
import { createMcpTool } from "agents/mcp/server";
import { outputSchema } from "@/server/mcp/output-schemas";

const inputSchema = z.object({
  city: z.string().min(1),
});

const resultSchema = outputSchema;

async function handler({ city }: { city: string }) {
  const apiKey = process.env.WEATHER_API_KEY;
  
  const resp = await fetch(
    `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&appid=${apiKey}`
  );
  
  if (!resp.ok) {
    throw new Error(`Weather API returned ${resp.status}`);
  }
  
  const data = await resp.json();
  return { result: data };
}

export const weather = createMcpTool({
  name: "weather",
  inputSchema,
  resultSchema,
  handler,
});

Client usage remains consistent:

const forecast = await mcp.call("weather", { city: "Berlin" });
console.log(forecast.result.main.temp);

Handling Advanced Authentication and Scopes

If your service requires dedicated OAuth scopes beyond the default MCP_SCOPE, modify src/lib/oauth-resource.ts:

  1. Add your scope constant to MCP_OAUTH_SCOPES.
  2. Include the scope in the client authorization request.
  3. Access the authenticated context in your handler via the McpProps object created in src/server/mcp/context.ts.

The transport layer (transport.ts) validates the token against these scopes before invoking your tool, ensuring zero-touch security for custom integrations.

Summary

  • Create: Add a TypeScript file to src/server/mcp/tools/ exporting a createMcpTool instance.
  • Validate: Use Zod for input schemas and outputSchema from src/server/mcp/output-schemas.ts for responses.
  • Discover: The createOpenSeoMcpServer factory auto-registers tools via static imports—no core file edits needed.
  • Secure: Authentication flows through handleAuthenticatedOpenSeoMcpRequest in transport.ts and context building in context.ts.
  • Extend: Add custom OAuth scopes in src/lib/oauth-resource.ts if required.

Frequently Asked Questions

Do I need to restart the server to add a new tool?

Yes. Because the tool registry relies on static imports discovered at build time, you must deploy the new file and restart the Node.js process. The architecture does not support hot-reloading of tool modules in production.

Can I use TypeScript interfaces instead of Zod for validation?

No. The createMcpTool factory requires a Zod schema for runtime validation. The MCP server uses this schema to validate JSON-RPC requests before they reach your handler, ensuring type safety across the wire.

How do I access the authenticated user inside my tool handler?

The handler receives the McpProps context object automatically, which includes the user ID, organization, and base URL constructed in src/server/mcp/context.ts. You can destructure this from the second argument if your tool definition includes context injection, or use the global request context managed by the transport layer.

Is there a performance limit on how many tools I can add?

There is no explicit limit in the OpenSEO source code. However, because all tools are imported statically at startup in transport.ts, startup time increases linearly with the number of tool files. Keep tool implementations lightweight and defer heavy initialization to the handler execution phase.

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 →