AppTweak MCP Server

  • src
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { storage } from './storage.js'; import { SEQUENTIAL_THINKING_PROMPT, formatPlanAsTodos } from './prompts.js'; import { Goal, Todo } from './types.js'; class SoftwarePlanningServer { private server: Server; private currentGoal: Goal | null = null; constructor() { this.server = new Server( { name: 'software-planning-tool', version: '0.1.0', }, { capabilities: { resources: {}, tools: {}, }, } ); this.setupResourceHandlers(); this.setupToolHandlers(); this.server.onerror = (error) => console.error('[MCP Error]', error); } private setupResourceHandlers() { this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [ { uri: 'planning://current-goal', name: 'Current Goal', description: 'The current software development goal being planned', mimeType: 'application/json', }, { uri: 'planning://implementation-plan', name: 'Implementation Plan', description: 'The current implementation plan with todos', mimeType: 'application/json', }, ], })); this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { switch (request.params.uri) { case 'planning://current-goal': { if (!this.currentGoal) { throw new McpError( ErrorCode.InvalidParams, 'No active goal. Start a new planning session first.' ); } return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(this.currentGoal, null, 2), }, ], }; } case 'planning://implementation-plan': { if (!this.currentGoal) { throw new McpError( ErrorCode.InvalidParams, 'No active goal. Start a new planning session first.' ); } const plan = await storage.getPlan(this.currentGoal.id); if (!plan) { throw new McpError( ErrorCode.InvalidParams, 'No implementation plan found for current goal.' ); } return { contents: [ { uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(plan, null, 2), }, ], }; } default: throw new McpError( ErrorCode.InvalidRequest, `Unknown resource URI: ${request.params.uri}` ); } }); } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'start_planning', description: 'Start a new planning session with a goal', inputSchema: { type: 'object', properties: { goal: { type: 'string', description: 'The software development goal to plan', }, }, required: ['goal'], }, }, { name: 'save_plan', description: 'Save the current implementation plan', inputSchema: { type: 'object', properties: { plan: { type: 'string', description: 'The implementation plan text to save', }, }, required: ['plan'], }, }, { name: 'add_todo', description: 'Add a new todo item to the current plan', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Title of the todo item', }, description: { type: 'string', description: 'Detailed description of the todo item', }, complexity: { type: 'number', description: 'Complexity score (0-10)', minimum: 0, maximum: 10, }, codeExample: { type: 'string', description: 'Optional code example', }, }, required: ['title', 'description', 'complexity'], }, }, { name: 'remove_todo', description: 'Remove a todo item from the current plan', inputSchema: { type: 'object', properties: { todoId: { type: 'string', description: 'ID of the todo item to remove', }, }, required: ['todoId'], }, }, { name: 'get_todos', description: 'Get all todos in the current plan', inputSchema: { type: 'object', properties: {}, }, }, { name: 'update_todo_status', description: 'Update the completion status of a todo item', inputSchema: { type: 'object', properties: { todoId: { type: 'string', description: 'ID of the todo item', }, isComplete: { type: 'boolean', description: 'New completion status', }, }, required: ['todoId', 'isComplete'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case 'start_planning': { const { goal } = request.params.arguments as { goal: string }; this.currentGoal = await storage.createGoal(goal); await storage.createPlan(this.currentGoal.id); return { content: [ { type: 'text', text: SEQUENTIAL_THINKING_PROMPT, }, ], }; } case 'save_plan': { if (!this.currentGoal) { throw new McpError( ErrorCode.InvalidRequest, 'No active goal. Start a new planning session first.' ); } const { plan } = request.params.arguments as { plan: string }; const todos = formatPlanAsTodos(plan); for (const todo of todos) { await storage.addTodo(this.currentGoal.id, todo); } return { content: [ { type: 'text', text: `Successfully saved ${todos.length} todo items to the implementation plan.`, }, ], }; } case 'add_todo': { if (!this.currentGoal) { throw new McpError( ErrorCode.InvalidRequest, 'No active goal. Start a new planning session first.' ); } const todo = request.params.arguments as Omit< Todo, 'id' | 'isComplete' | 'createdAt' | 'updatedAt' >; const newTodo = await storage.addTodo(this.currentGoal.id, todo); return { content: [ { type: 'text', text: JSON.stringify(newTodo, null, 2), }, ], }; } case 'remove_todo': { if (!this.currentGoal) { throw new McpError( ErrorCode.InvalidRequest, 'No active goal. Start a new planning session first.' ); } const { todoId } = request.params.arguments as { todoId: string }; await storage.removeTodo(this.currentGoal.id, todoId); return { content: [ { type: 'text', text: `Successfully removed todo ${todoId}`, }, ], }; } case 'get_todos': { if (!this.currentGoal) { throw new McpError( ErrorCode.InvalidRequest, 'No active goal. Start a new planning session first.' ); } const todos = await storage.getTodos(this.currentGoal.id); return { content: [ { type: 'text', text: JSON.stringify(todos, null, 2), }, ], }; } case 'update_todo_status': { if (!this.currentGoal) { throw new McpError( ErrorCode.InvalidRequest, 'No active goal. Start a new planning session first.' ); } const { todoId, isComplete } = request.params.arguments as { todoId: string; isComplete: boolean; }; const updatedTodo = await storage.updateTodoStatus( this.currentGoal.id, todoId, isComplete ); return { content: [ { type: 'text', text: JSON.stringify(updatedTodo, null, 2), }, ], }; } default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } }); } async run() { await storage.initialize(); const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Software Planning MCP server running on stdio'); } } const server = new SoftwarePlanningServer(); server.run().catch(console.error);