/**
* MCP Server implementation
*
* Core server using low-level MCP SDK Server API for fine-grained control.
* Supports both stdio and HTTP transports via environment configuration.
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
ListToolsRequestSchema,
CallToolRequestSchema,
type CallToolResult,
} from '@modelcontextprotocol/sdk/types.js';
import { createLogger } from './shared/logger.js';
import { getConfig } from './config/index.js';
import { getToolRegistry } from './tools/registry.js';
import { withSpan } from './shared/tracing.js';
import { sanitizeObject } from './shared/pii-sanitizer.js';
const logger = createLogger('server');
const config = getConfig();
/**
* Create and configure the MCP server
*/
export function createServer(): Server {
const server = new Server(
{
name: config.serverName,
version: config.serverVersion,
},
{
capabilities: {
tools: {},
// Enable resources and prompts when implemented
// resources: {},
// prompts: {},
},
}
);
// Handle list tools request
server.setRequestHandler(ListToolsRequestSchema, async () => {
const registry = getToolRegistry();
const tools = registry.getDefinitions();
logger.debug('Listed tools', { count: tools.length });
return { tools };
});
// Handle tool execution request
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
return withSpan(
`tool.${name}`,
async (span) => {
span.setAttribute('mcp.tool.name', name);
const registry = getToolRegistry();
const result = await registry.execute(name, args);
if (result.success) {
span.setAttribute('mcp.tool.success', true);
// Format successful response
const response: CallToolResult = {
content: [
{
type: 'text',
text: JSON.stringify(sanitizeObject(result.data as Record<string, unknown>), null, 2),
},
],
isError: false,
};
return response;
} else {
span.setAttribute('mcp.tool.success', false);
span.setAttribute('mcp.tool.error_code', result.error?.code ?? 'UNKNOWN');
// Format error response
const response: CallToolResult = {
content: [
{
type: 'text',
text: JSON.stringify(result.error, null, 2),
},
],
isError: true,
};
return response;
}
},
{
attributes: {
'mcp.tool.name': name,
},
}
);
});
logger.info('MCP server created', {
name: config.serverName,
version: config.serverVersion,
});
return server;
}
/**
* Connect server to stdio transport and run
*/
export async function runStdioServer(server: Server): Promise<void> {
const transport = new StdioServerTransport();
logger.info('Starting stdio transport');
await server.connect(transport);
logger.info('MCP server running on stdio');
}
/**
* Graceful shutdown handler
*/
export function setupGracefulShutdown(
server: Server,
cleanup: () => Promise<void>
): void {
const shutdown = async (signal: string): Promise<void> => {
logger.info('Received shutdown signal', { signal });
try {
await server.close();
await cleanup();
logger.info('Graceful shutdown complete');
process.exit(0);
} catch (error) {
logger.error('Error during shutdown', { error: String(error) });
process.exit(1);
}
};
process.on('SIGINT', () => void shutdown('SIGINT'));
process.on('SIGTERM', () => void shutdown('SIGTERM'));
// Handle uncaught errors
process.on('uncaughtException', (error) => {
logger.critical('Uncaught exception', { error: error.message, stack: error.stack });
void shutdown('uncaughtException');
});
process.on('unhandledRejection', (reason) => {
logger.critical('Unhandled rejection', { reason: String(reason) });
void shutdown('unhandledRejection');
});
}