/** * MCP Server for Obsidian CLI Bundle * Constitutional Principle I: MCP Protocol Compliance * Constitutional Principle VI: Stdio Transport Standard */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { logger } from './utils/logger.js'; import { createErrorResponse } from './utils/error-handler.js'; import { ToolOutput } from './utils/types.js'; /** * MCP Server instance */ export class ObsidianMCPServer { private server: Server; private tools: Map = new Map(); constructor() { this.server = new Server( { name: 'obsidian-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.setupHandlers(); } /** * Register a tool handler */ registerTool( name: string, description: string, inputSchema: Record, handler: ToolHandler ): void { // Ensure handler has description and inputSchema handler.description = description; handler.inputSchema = inputSchema; this.tools.set(name, handler); logger.debug('Registered tool', { name }); } /** * Setup MCP protocol handlers */ private setupHandlers(): void { // Handle tools/list request this.server.setRequestHandler(ListToolsRequestSchema, async () => { logger.debug('Handling tools/list request'); const toolsList = Array.from(this.tools.entries()).map(([name, handler]) => ({ name, description: handler.description || `Tool: ${name}`, inputSchema: handler.inputSchema || { type: 'object', properties: {}, }, })); logger.info('Returning tools list', { count: toolsList.length }); return { tools: toolsList, }; }); // Handle tools/call request this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; logger.info('Handling tool call', { tool: name }); try { const handler = this.tools.get(name); if (!handler) { throw new Error(`Tool not found: ${name}`); } // Execute tool handler const result = await handler.execute(args || {}); logger.debug('Tool call succeeded', { tool: name }); // MCP expects a specific response format return { content: result.content, isError: result.isError || false, }; } catch (error) { logger.error('Tool call failed', { tool: name, error: error instanceof Error ? error.message : String(error), }); const errorResponse = createErrorResponse(error); return { content: errorResponse.content, isError: true, }; } }); logger.info('MCP server handlers configured'); } /** * Connect to stdio transport */ async connect(): Promise { const transport = new StdioServerTransport(); await this.server.connect(transport); logger.info('MCP server connected to stdio transport'); } /** * Close server connection */ async close(): Promise { await this.server.close(); logger.info('MCP server closed'); } } /** * Tool handler interface */ export interface ToolHandler { description: string; inputSchema: Record; execute(args: Record): Promise; } /** * Create a tool handler */ export function createToolHandler( description: string, inputSchema: Record, execute: (args: Record) => Promise ): ToolHandler { return { description, inputSchema, execute, }; }