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 tohandleAuthenticatedOpenSeoMcpRequestorhandleSelfHostedOpenSeoMcpRequest. This layer ensures your tool inherits automatic authentication checks. -
Context Provider (
src/server/mcp/context.ts): Constructs theMcpPropsobject 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 astructuredContentpayload. 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
resultkey 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.tscatches 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:
- Add your scope constant to
MCP_OAUTH_SCOPES. - Include the scope in the client authorization request.
- Access the authenticated context in your handler via the
McpPropsobject created insrc/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 acreateMcpToolinstance. - Validate: Use Zod for input schemas and
outputSchemafromsrc/server/mcp/output-schemas.tsfor responses. - Discover: The
createOpenSeoMcpServerfactory auto-registers tools via static imports—no core file edits needed. - Secure: Authentication flows through
handleAuthenticatedOpenSeoMcpRequestintransport.tsand context building incontext.ts. - Extend: Add custom OAuth scopes in
src/lib/oauth-resource.tsif 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →