Skip to main content
Glama
mcp-server-base.tsโ€ข6.79 kB
/** * Base patterns and utilities for MCP server development * This provides common functionality that all MCP servers can use */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { type CallToolRequest, CallToolRequestSchema, type ListToolsRequest, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; export interface MCPTool<TInput = unknown, TOutput = unknown> { name: string; description: string; inputSchema: object; execute: (input: TInput) => Promise<TOutput>; } export interface MCPServerConfig { name: string; version: string; description?: string; tools: MCPTool<unknown, unknown>[]; enableCLI?: boolean; cliCommands?: Record<string, (args: string[]) => Promise<void>>; } /** * Base class for MCP servers with common functionality */ export class MCPServerBase { protected server: Server; protected config: MCPServerConfig; protected tools: Map<string, MCPTool<unknown, unknown>> = new Map(); constructor(config: MCPServerConfig) { this.config = config; this.server = new Server( { name: config.name, version: config.version, }, { capabilities: { tools: {}, }, }, ); // Register tools for (const tool of config.tools) { this.tools.set(tool.name, tool); } this.setupHandlers(); } /** * Set up MCP request handlers */ private setupHandlers(): void { // List tools handler this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: Array.from(this.tools.values()).map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema, })), })); // Call tool handler this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => { const { name, arguments: args } = request.params; const tool = this.tools.get(name); if (!tool) { throw new Error(`Unknown tool: ${name}`); } try { const result = await tool.execute(args); return { content: [ { type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2), }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Error executing ${name}: ${errorMessage}`, }, ], isError: true, }; } }); } /** * Add a tool to the server */ addTool<TInput = unknown, TOutput = unknown>(tool: MCPTool<TInput, TOutput>): void { this.tools.set(tool.name, tool as MCPTool<unknown, unknown>); } /** * Remove a tool from the server */ removeTool(name: string): void { this.tools.delete(name); } /** * Start the MCP server */ async start(): Promise<void> { // Check if running as CLI if (this.config.enableCLI && this.isRunningAsCLI()) { await this.runCLI(); return; } // Start as MCP server const transport = new StdioServerTransport(); await this.server.connect(transport); } /** * Check if running as CLI (not as MCP server) */ private isRunningAsCLI(): boolean { // Check for CLI-specific arguments or environment return ( process.argv.includes('--cli') || process.argv.includes('cli') || process.env.MCP_CLI_MODE === 'true' ); } /** * Run in CLI mode */ private async runCLI(): Promise<void> { if (!this.config.cliCommands) { console.error('CLI mode enabled but no CLI commands defined'); process.exit(1); } const command = process.argv[2]; const cliHandler = this.config.cliCommands[command]; if (!cliHandler) { console.error(`Unknown command: ${command}`); console.error('Available commands:', Object.keys(this.config.cliCommands).join(', ')); process.exit(1); } try { await cliHandler(process.argv.slice(3)); } catch (error) { console.error('CLI Error:', error instanceof Error ? error.message : String(error)); process.exit(1); } } /** * Create a tool with validation */ static createTool<TInput = unknown, TOutput = unknown>(config: { name: string; description: string; inputSchema: object; execute: (input: TInput) => Promise<TOutput>; validate?: (input: TInput) => boolean | string; }): MCPTool<TInput, TOutput> { return { name: config.name, description: config.description, inputSchema: config.inputSchema, execute: async (input: TInput) => { // Run validation if provided if (config.validate) { const validation = config.validate(input); if (validation !== true) { throw new Error( typeof validation === 'string' ? validation : 'Invalid input', ); } } return config.execute(input); }, }; } /** * Helper to create error responses */ protected createErrorResponse(message: string): { content: Array<{ type: 'text'; text: string }>; isError: boolean; } { return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true, }; } /** * Helper to create success responses */ protected createSuccessResponse<T = unknown>( data: T, ): { content: Array<{ type: 'text'; text: string }> } { return { content: [ { type: 'text', text: typeof data === 'string' ? data : JSON.stringify(data, null, 2), }, ], }; } } /** * Utility function to create and start an MCP server */ export async function createMCPServer(config: MCPServerConfig): Promise<void> { const server = new MCPServerBase(config); await server.start(); } /** * Decorator for tool methods (for class-based tool definitions) */ export function tool(name: string, description: string, inputSchema: object) { return <T extends Record<string, unknown>>( target: T, propertyKey: string, descriptor: PropertyDescriptor, ) => { // Store tool metadata const ctor = target.constructor as Constructor; if (!ctor._tools) { ctor._tools = []; } ctor._tools.push({ name, description, inputSchema, execute: descriptor.value, }); }; } /** * Helper to extract tools from a class decorated with @tool */ interface ToolMetadata<TInput = unknown, TOutput = unknown> { name: string; description: string; inputSchema: object; execute: (this: unknown, input: TInput) => Promise<TOutput>; } interface ClassWithTools { constructor: { _tools?: ToolMetadata[]; }; } type Constructor = { new (...args: unknown[]): unknown; _tools?: ToolMetadata[]; }; export function extractToolsFromClass<T extends ClassWithTools>(instance: T): MCPTool[] { const tools = instance.constructor._tools || []; return tools.map((tool: ToolMetadata) => ({ ...tool, execute: tool.execute.bind(instance), })); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mearman/mcp-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server