Skip to main content
Glama

TypeScript MCP Server Template

by dhinojosac
server.ts10.8 kB
import Fastify, { FastifyRequest, FastifyReply, FastifyError } from 'fastify'; import cors from '@fastify/cors'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import helloPlugin from './plugins/helloPlugin.js'; import weatherPlugin, { registerWeatherTools, } from './plugins/weatherPlugin.js'; import { SayHelloSchema, CalculateSchema, validateToolArgs, type SayHelloArgs, type CalculateArgs, } from './schemas/toolSchemas.js'; /** * Creates and configures the MCP Server with tools and resources * Using current SDK version compatible methods with official best practices * @returns Configured MCP Server instance */ function createMCPServer(): McpServer { const server = new McpServer({ name: 'ts-template-mcp-server', version: '1.0.0', }); // Register the sayHello tool with Zod validation server.registerTool( 'sayHello', { title: 'Say Hello', description: 'Says hello to a person by name', }, async (args: { [x: string]: any }) => { const { name }: SayHelloArgs = validateToolArgs(SayHelloSchema, args); return { content: [ { type: 'text', text: `Hello, ${name}!`, }, ], }; } ); // Register the calculate tool with Zod validation server.registerTool( 'calculate', { title: 'Calculate', description: 'Performs basic arithmetic calculations (add, subtract, multiply, divide)', }, async (args: { [x: string]: any }) => { const { operation, a, b }: CalculateArgs = validateToolArgs( CalculateSchema, args ); let result: number; switch (operation) { case 'add': result = a + b; break; case 'subtract': result = a - b; break; case 'multiply': result = a * b; break; case 'divide': result = a / b; break; default: // This should never happen due to Zod validation, but keeping for safety throw new Error( `Unknown operation: ${operation}. Supported: add, subtract, multiply, divide` ); } return { content: [ { type: 'text', text: `${a} ${operation} ${b} = ${result}`, }, ], }; } ); // Register weather tools (demonstrating plugin pattern) registerWeatherTools(server); // Register server info resource using current SDK approach server.registerResource( 'server-info', 'mcp://server-info', { title: 'Server Information', description: 'Information about this MCP server', mimeType: 'application/json', }, async uri => { return { contents: [ { uri: uri.href, mimeType: 'application/json', text: JSON.stringify( { name: 'ts-template-mcp-server', version: '1.0.0', description: 'A TypeScript MCP server template with Zod validation and plugin architecture', capabilities: ['tools', 'resources'], author: 'Your Name', created: new Date().toISOString(), transport: 'StreamableHTTP + STDIO', validation: 'Zod schemas', tools: [ { name: 'sayHello', description: 'Says hello to a person by name', parameters: '{ name: string }', }, { name: 'calculate', description: 'Performs basic arithmetic calculations', parameters: "{ operation: 'add'|'subtract'|'multiply'|'divide', a: number, b: number }", }, { name: 'getWeatherForecast', description: 'Get weather forecast for coordinates', parameters: '{ latitude: number, longitude: number }', }, { name: 'getWeatherAlerts', description: 'Get weather alerts for US state', parameters: '{ state: string }', }, ], }, null, 2 ), }, ], }; } ); // Register hello message resource using current SDK approach server.registerResource( 'hello-message', 'mcp://hello-message', { title: 'Hello Message', description: 'A simple hello message resource', mimeType: 'text/plain', }, async uri => { return { contents: [ { uri: uri.href, mimeType: 'text/plain', text: 'Hello from the MCP TypeScript Server!\n\nThis is a resource that can be read by MCP clients.\n\nThis server follows best practices from the official MCP TypeScript SDK.\n\nAvailable tools:\n- sayHello: { name: string }\n- calculate: { operation: string, a: number, b: number }', }, ], }; } ); return server; } /** * Sets up and starts the Fastify server with MCP integration * Following official SDK patterns for session management where possible */ async function startServer() { const fastify = Fastify({ logger: { level: 'info', transport: { target: 'pino-pretty', options: { colorize: true, }, }, }, }); // Enhanced CORS for MCP await fastify.register(cors, { origin: true, // More flexible than '*' allowedHeaders: [ 'Content-Type', 'Authorization', 'Mcp-Session-Id', // Required MCP header 'Cache-Control', 'Accept', ], methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'], }); // Register the hello plugin await fastify.register(helloPlugin); // Register the weather plugin await fastify.register(weatherPlugin); // Create MCP server instance const mcpServer = createMCPServer(); // Session storage for better transport management (following official patterns) const sessions = new Map<string, StreamableHTTPServerTransport>(); // MCP endpoint with proper session management fastify.all('/mcp', async (request: FastifyRequest, reply: FastifyReply) => { try { // Get or create session ID (following official patterns) const sessionId = (request.headers['mcp-session-id'] as string) || `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; let transport = sessions.get(sessionId); if (!transport) { // Create new transport for this session transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => sessionId, }); // Connect MCP server to transport await mcpServer.connect(transport); sessions.set(sessionId, transport); fastify.log.info(`Created new MCP session: ${sessionId}`); } // Handle the request await transport.handleRequest(request.raw, reply.raw, request.body); } catch (error) { fastify.log.error('MCP request error:', error); if (!reply.sent) { reply.status(500).send({ error: 'Internal MCP server error', message: error instanceof Error ? error.message : 'Unknown error', }); } } }); // Enhanced health check fastify.get('/health', async () => { return { status: 'ok', timestamp: new Date().toISOString(), server: 'ts-template-mcp-server', version: '1.0.0', uptime: process.uptime(), sessions: sessions.size, capabilities: ['tools', 'resources'], }; }); // Server info endpoint fastify.get('/info', async () => { return { name: 'ts-template-mcp-server', version: '1.0.0', description: 'A TypeScript MCP server template following official SDK best practices', endpoints: { mcp: '/mcp', health: '/health', hello: '/hello/:name', }, capabilities: ['tools', 'resources'], transport: 'StreamableHTTP', sdkVersion: '@modelcontextprotocol/sdk ^1.0.4', }; }); // Enhanced error handler fastify.setErrorHandler( async ( error: FastifyError, request: FastifyRequest, reply: FastifyReply ) => { fastify.log.error( { error: error.message, stack: error.stack, url: request.url, method: request.method, }, 'Request error' ); const statusCode = error.statusCode || 500; return reply.status(statusCode).send({ error: error.message || 'Internal Server Error', statusCode, timestamp: new Date().toISOString(), }); } ); // Graceful shutdown handler fastify.addHook('onClose', async () => { fastify.log.info('Cleaning up MCP sessions...'); sessions.clear(); }); try { const address = await fastify.listen({ port: 3000, host: '0.0.0.0', }); fastify.log.info(`MCP Server ready at ${address}`); fastify.log.info('MCP endpoint: /mcp'); fastify.log.info('Hello endpoint: /hello/:name'); fastify.log.info('Health check: /health'); fastify.log.info('Server info: /info'); } catch (err) { fastify.log.error('Error starting server:', err); process.exit(1); } } // Enhanced graceful shutdown const gracefulShutdown = (signal: string) => { console.log(`\nReceived ${signal}, shutting down gracefully...`); process.exit(0); }; process.on('SIGINT', () => gracefulShutdown('SIGINT')); process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); // Check if running in STDIO mode (for CLI clients like Claude Desktop) const isStdioMode = process.argv.includes('--stdio') || process.env['MCP_STDIO'] === 'true'; if (isStdioMode) { // STDIO mode for CLI clients (following Node.js quickstart pattern) const mcpServer = createMCPServer(); const transport = new StdioServerTransport(); mcpServer.connect(transport).catch(err => { console.error('Failed to start STDIO server:', err); process.exit(1); }); console.error('MCP Server running in STDIO mode'); } else { // HTTP mode for web clients startServer().catch(err => { console.error('Failed to start HTTP server:'); console.error(err); if (err.stack) { console.error('Stack trace:'); console.error(err.stack); } process.exit(1); }); }

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/dhinojosac/calendar-mcp'

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