feat: implement Obsidian MCP Bundle MVP (Phase 1-3)
- Complete project setup with TypeScript, Jest, MCPB manifest - Implement foundational infrastructure (CLI executor, logger, error handler) - Add 9 file operation tools for User Story 1 - Full MCP protocol compliance with stdio transport - Input validation and sanitization for security - Comprehensive error handling with actionable messages - Constitutional compliance: all 6 principles satisfied MVP includes: - obsidian_create_note, read, append, prepend, delete, move, rename, open, file_info - Zod validation schemas for all parameters - 30s timeout configuration with per-command overrides - Stderr-only logging with sanitized output - Graceful shutdown handling Build: ✅ 0 errors, 0 vulnerabilities Tasks: 48/167 complete (MVP milestone) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
157
src/server.ts
Normal file
157
src/server.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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<string, ToolHandler> = 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<string, unknown>,
|
||||
handler: ToolHandler
|
||||
): void {
|
||||
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<void> {
|
||||
const transport = new StdioServerTransport();
|
||||
await this.server.connect(transport);
|
||||
logger.info('MCP server connected to stdio transport');
|
||||
}
|
||||
|
||||
/**
|
||||
* Close server connection
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
await this.server.close();
|
||||
logger.info('MCP server closed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool handler interface
|
||||
*/
|
||||
export interface ToolHandler {
|
||||
description: string;
|
||||
inputSchema: Record<string, unknown>;
|
||||
execute(args: Record<string, unknown>): Promise<ToolOutput>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tool handler
|
||||
*/
|
||||
export function createToolHandler(
|
||||
description: string,
|
||||
inputSchema: Record<string, unknown>,
|
||||
execute: (args: Record<string, unknown>) => Promise<ToolOutput>
|
||||
): ToolHandler {
|
||||
return {
|
||||
description,
|
||||
inputSchema,
|
||||
execute,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user