UNPKG

markmv

Version:

TypeScript CLI for markdown file operations with intelligent link refactoring

187 lines 8.28 kB
/** * MCP Server implementation for markmv * * Provides Model Context Protocol server that exposes markmv functionality as tools for AI agents. * Uses auto-generated tool definitions from JSON Schema-first approach. Allows seamless integration * with Claude and other MCP clients. */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { createMarkMv } from './index.js'; import { autoGeneratedMcpTools, getMcpToolNames } from './generated/mcp-tools.js'; import { validateInput } from './generated/ajv-validators.js'; const markmv = createMarkMv(); /** Convert snake_case to camelCase for method name mapping */ function snakeToCamel(str) { return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); } /** Type guard to check if an object is a valid OperationResult */ function isOperationResult(obj) { if (!obj || typeof obj !== 'object' || Array.isArray(obj)) { return false; } // Since we've checked it's an object above, this is safe // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const record = obj; return (typeof record.success === 'boolean' && Array.isArray(record.modifiedFiles) && Array.isArray(record.createdFiles) && Array.isArray(record.deletedFiles) && Array.isArray(record.errors) && Array.isArray(record.warnings) && Array.isArray(record.changes)); } /** Create and configure the MCP server for markmv */ export function createMcpServer() { const server = new Server({ name: 'markmv-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, }); // List available tools (auto-generated) server.setRequestHandler(ListToolsRequestSchema, async (_request) => { return { tools: autoGeneratedMcpTools, }; }); // Handle tool calls (auto-generated with validation) server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { // Validate tool name const availableTools = getMcpToolNames(); if (!availableTools.includes(name)) { throw new Error(`Unknown tool: ${name}. Available tools: ${availableTools.join(', ')}`); } // Convert snake_case tool name back to camelCase method name const methodName = snakeToCamel(name); // Validate input using auto-generated validators const validation = validateInput(methodName, args); if (!validation.valid) { throw new Error(`Validation failed: ${validation.errors?.join(', ')}`); } // Route to appropriate method with proper type checking let result; if (methodName === 'moveFile') { if (typeof args === 'object' && args !== null && !Array.isArray(args)) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const argsObj = args; const sourcePath = argsObj.sourcePath; const destinationPath = argsObj.destinationPath; const options = argsObj.options || {}; if (typeof sourcePath === 'string' && typeof destinationPath === 'string' && typeof options === 'object' && options !== null && !Array.isArray(options)) { result = await markmv.moveFile(sourcePath, destinationPath, // eslint-disable-next-line @typescript-eslint/consistent-type-assertions options); } else { throw new Error('Invalid parameters for moveFile: sourcePath and destinationPath must be strings'); } } else { throw new Error('Invalid arguments object for moveFile'); } } else if (methodName === 'moveFiles') { if (typeof args === 'object' && args !== null && !Array.isArray(args)) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const argsObj = args; const moves = argsObj.moves; const options = argsObj.options || {}; if (Array.isArray(moves) && typeof options === 'object' && options !== null && !Array.isArray(options)) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions result = await markmv.moveFiles(moves, options); } else { throw new Error('Invalid parameters for moveFiles: moves must be an array'); } } else { throw new Error('Invalid arguments object for moveFiles'); } } else if (methodName === 'validateOperation') { if (typeof args === 'object' && args !== null && !Array.isArray(args)) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const argsObj = args; const operationResult = argsObj.result; if (isOperationResult(operationResult)) { result = await markmv.validateOperation(operationResult); } else { throw new Error('Invalid OperationResult structure: missing required properties'); } } else { throw new Error('Invalid arguments object for validateOperation'); } } else if (methodName === 'testAutoExposure') { if (typeof args === 'object' && args !== null && !Array.isArray(args)) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const argsObj = args; const input = argsObj.input; if (typeof input === 'string') { const { testAutoExposure } = await import('./index.js'); result = await testAutoExposure(input); } else { throw new Error('Invalid parameters for testAutoExposure: input must be a string'); } } else { throw new Error('Invalid arguments object for testAutoExposure'); } } else { throw new Error(`Method ${methodName} not implemented`); } return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); return server; } /** Start the MCP server */ export async function startMcpServer() { const server = createMcpServer(); const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js'); const transport = new StdioServerTransport(); await server.connect(transport); console.error('markmv MCP server started'); } // For direct execution if (process.argv[1] && process.argv[1].endsWith('mcp-server.js')) { startMcpServer().catch((error) => { console.error('Failed to start MCP server:', error); process.exit(1); }); } //# sourceMappingURL=mcp-server.js.map