Jake Shore f3c4cd817b Add all MCP servers + factory infra to MCPEngine — 2026-02-06
=== NEW SERVERS ADDED (7) ===
- servers/closebot — 119 tools, 14 modules, 4,656 lines TS (Stage 7)
- servers/google-console — Google Search Console MCP (Stage 7)
- servers/meta-ads — Meta/Facebook Ads MCP (Stage 8)
- servers/twilio — Twilio communications MCP (Stage 8)
- servers/competitor-research — Competitive intel MCP (Stage 6)
- servers/n8n-apps — n8n workflow MCP apps (Stage 6)
- servers/reonomy — Commercial real estate MCP (Stage 1)

=== FACTORY INFRASTRUCTURE ADDED ===
- infra/factory-tools — mcp-jest, mcp-validator, mcp-add, MCP Inspector
  - 60 test configs, 702 auto-generated test cases
  - All 30 servers score 100/100 protocol compliance
- infra/command-center — Pipeline state, operator playbook, dashboard config
- infra/factory-reviews — Automated eval reports

=== DOCS ADDED ===
- docs/MCP-FACTORY.md — Factory overview
- docs/reports/ — 5 pipeline evaluation reports
- docs/research/ — Browser MCP research

=== RULES ESTABLISHED ===
- CONTRIBUTING.md — All MCP work MUST go in this repo
- README.md — Full inventory of 37 servers + infra docs
- .gitignore — Updated for Python venvs

TOTAL: 37 MCP servers + full factory pipeline in one repo.
This is now the single source of truth for all MCP work.
2026-02-06 06:32:29 -05:00

318 lines
7.5 KiB
TypeScript

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import type { MetaApiClient } from './client/meta-client.js';
import type { ToolCategory, LoadTier, ToolAnnotations } from './types/meta-api.js';
/**
* Tool definition with metadata
*/
export interface ToolDefinition {
name: string;
description: string;
inputSchema: z.ZodType<unknown>;
annotations?: ToolAnnotations;
_meta?: {
labels: {
category: string;
access: "read" | "write" | "delete";
complexity: "simple" | "complex" | "batch";
};
};
handler: (args: Record<string, unknown>) => Promise<{ content: Array<{ type: string; text: string }>; isError?: boolean }>;
}
/**
* Module registry for lazy loading
*/
interface ToolModule {
category: ToolCategory;
tier: LoadTier;
loaded: boolean;
register: (registry: ToolRegistry) => Promise<void> | void;
}
export class ToolRegistry {
private tools = new Map<string, ToolDefinition>();
private client: MetaApiClient;
constructor(client: MetaApiClient) {
this.client = client;
}
/**
* Register a tool
*/
registerTool(tool: ToolDefinition): void {
this.tools.set(tool.name, tool);
console.error(`[MCP] Registered tool: ${tool.name}`);
}
/**
* Get all registered tools
*/
getTools(): ToolDefinition[] {
return Array.from(this.tools.values());
}
/**
* Get a specific tool
*/
getTool(name: string): ToolDefinition | undefined {
return this.tools.get(name);
}
/**
* Get the API client
*/
getClient(): MetaApiClient {
return this.client;
}
}
export class ModuleRegistry {
private modules = new Map<ToolCategory, ToolModule>();
private toolRegistry: ToolRegistry;
constructor(toolRegistry: ToolRegistry) {
this.toolRegistry = toolRegistry;
}
/**
* Register a tool module
*/
registerModule(module: ToolModule): void {
this.modules.set(module.category, module);
}
/**
* Load a module if not already loaded
*/
async loadModule(category: ToolCategory): Promise<void> {
const module = this.modules.get(category);
if (!module) {
throw new Error(`Module ${category} not registered`);
}
if (module.loaded) {
return;
}
console.error(`[MCP] Loading module: ${category}`);
await module.register(this.toolRegistry);
module.loaded = true;
console.error(`[MCP] Module loaded: ${category}`);
}
/**
* Get module status
*/
getModuleStatus(): Record<string, { tier: LoadTier; loaded: boolean }> {
const status: Record<string, { tier: LoadTier; loaded: boolean }> = {};
for (const [category, module] of this.modules.entries()) {
status[category] = {
tier: module.tier,
loaded: module.loaded,
};
}
return status;
}
/**
* Check if a module is loaded
*/
isModuleLoaded(category: ToolCategory): boolean {
return this.modules.get(category)?.loaded || false;
}
/**
* Get tool registry
*/
getToolRegistry(): ToolRegistry {
return this.toolRegistry;
}
}
/**
* Create gateway tool for lazy-loaded advanced modules
*/
export function createGatewayTool(
category: ToolCategory,
registry: ModuleRegistry,
description: string
): ToolDefinition {
return {
name: `${category}_tools`,
description,
inputSchema: z.object({}),
annotations: {
title: `Load ${category} module`,
readOnlyHint: true,
openWorldHint: false,
},
handler: async () => {
// Load the module if not loaded
if (!registry.isModuleLoaded(category)) {
await registry.loadModule(category);
return {
content: [{
type: 'text',
text: `Module '${category}' has been loaded. Available tools are now registered. Please list tools again to see them.`,
}],
};
}
return {
content: [{
type: 'text',
text: `Module '${category}' is already loaded.`,
}],
};
},
};
}
/**
* Create and configure the MCP server
*/
export function createMcpServer(toolRegistry: ToolRegistry): Server {
const server = new Server(
{
name: '@clawdbot/meta-ads-mcp',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Handle tools/list
server.setRequestHandler('tools/list' as never, async () => {
const tools = toolRegistry.getTools();
return {
tools: tools.map(tool => {
// Convert Zod schema to JSON Schema
const inputSchema = zodToJsonSchema(tool.inputSchema);
return {
name: tool.name,
description: tool.description,
inputSchema,
_meta: tool._meta,
annotations: tool.annotations,
};
}),
};
});
// Handle tools/call
server.setRequestHandler('tools/call' as never, async (request: { params: { name: string; arguments: Record<string, unknown> } }) => {
const { name, arguments: args } = request.params;
const tool = toolRegistry.getTool(name);
if (!tool) {
return {
content: [{
type: 'text',
text: `Unknown tool: ${name}`,
}],
isError: true,
};
}
try {
// Validate input
const validatedArgs = tool.inputSchema.parse(args);
// Execute handler
return await tool.handler(validatedArgs as Record<string, unknown>);
} catch (error) {
return {
content: [{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
}],
isError: true,
};
}
});
return server;
}
/**
* Convert Zod schema to JSON Schema (simplified)
*/
function zodToJsonSchema(schema: z.ZodType<unknown>): Record<string, unknown> {
// This is a simplified conversion. For production, use @anatine/zod-to-openapi or similar
if (schema instanceof z.ZodObject) {
const shape = (schema as z.ZodObject<z.ZodRawShape>).shape;
const properties: Record<string, unknown> = {};
const required: string[] = [];
for (const [key, value] of Object.entries(shape)) {
if (value instanceof z.ZodString) {
properties[key] = {
type: 'string',
description: value.description,
};
} else if (value instanceof z.ZodNumber) {
properties[key] = {
type: 'number',
description: value.description,
};
} else if (value instanceof z.ZodBoolean) {
properties[key] = {
type: 'boolean',
description: value.description,
};
} else if (value instanceof z.ZodArray) {
properties[key] = {
type: 'array',
description: value.description,
items: {},
};
} else if (value instanceof z.ZodEnum) {
properties[key] = {
type: 'string',
enum: value.options,
description: value.description,
};
} else {
properties[key] = {
type: 'object',
description: value.description,
};
}
if (!value.isOptional()) {
required.push(key);
}
}
return {
type: 'object',
properties,
required,
};
}
return { type: 'object' };
}
/**
* Connect server to stdio transport
*/
export async function connectServer(server: Server): Promise<void> {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('[MCP] Meta Ads MCP server running on stdio');
}