@nicolasmirson/plan-mode-claude
Version:
Structured planning workflow for AI assistants with MCP integration
233 lines • 10.6 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
import { PlanManager } from './PlanManager.js';
export class PlanModeMCPServer {
constructor() {
this.server = new Server({
name: 'plan-mode-server',
version: '1.0.0',
capabilities: {
tools: {},
},
});
this.planManager = new PlanManager();
this.setupTools();
this.setupApprovalCallback();
}
setupTools() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'enter_plan_mode',
description: 'Enter plan mode to start planning a task',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'create_plan',
description: 'Create a new plan with title and description',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'The title of the plan',
},
description: {
type: 'string',
description: 'The description of what the plan will accomplish',
},
},
required: ['title', 'description'],
},
},
{
name: 'add_plan_step',
description: 'Add a step to the current plan',
inputSchema: {
type: 'object',
properties: {
content: {
type: 'string',
description: 'The description of the step',
},
priority: {
type: 'string',
enum: ['high', 'medium', 'low'],
description: 'The priority of the step',
default: 'medium',
},
dependencies: {
type: 'array',
items: {
type: 'string',
},
description: 'Array of step IDs that this step depends on',
},
},
required: ['content'],
},
},
{
name: 'exit_plan_mode',
description: 'Exit plan mode and present the plan for approval',
inputSchema: {
type: 'object',
properties: {
plan: {
type: 'string',
description: 'The plan in markdown format to present to the user',
},
},
required: ['plan'],
},
},
{
name: 'update_step_status',
description: 'Update the status of a plan step',
inputSchema: {
type: 'object',
properties: {
stepId: {
type: 'string',
description: 'The ID of the step to update',
},
status: {
type: 'string',
enum: ['pending', 'in_progress', 'completed'],
description: 'The new status of the step',
},
},
required: ['stepId', 'status'],
},
},
{
name: 'get_plan_status',
description: 'Get the current plan and its status',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
],
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'enter_plan_mode':
this.planManager.enterPlanMode();
return {
content: [
{
type: 'text',
text: 'Entered plan mode. You can now create a plan and add steps.',
},
],
};
case 'create_plan':
const plan = this.planManager.createPlan(args?.title || '', args?.description || '');
return {
content: [
{
type: 'text',
text: `Created plan: ${plan.title} (ID: ${plan.id})`,
},
],
};
case 'add_plan_step':
const step = this.planManager.addStep(args?.content || '', args?.priority || 'medium', args?.dependencies);
return {
content: [
{
type: 'text',
text: `Added step: ${step.content} (ID: ${step.id})`,
},
],
};
case 'exit_plan_mode':
const response = await this.planManager.exitPlanMode();
return {
content: [
{
type: 'text',
text: response.approved
? 'Plan approved! Ready to execute.'
: `Plan needs modifications: ${response.feedback}`,
},
],
};
case 'update_step_status':
this.planManager.updateStepStatus(args?.stepId || '', args?.status || 'pending');
return {
content: [
{
type: 'text',
text: `Updated step ${args?.stepId} status to ${args?.status}`,
},
],
};
case 'get_plan_status':
const currentPlan = this.planManager.getCurrentPlan();
const state = this.planManager.getState();
if (!currentPlan) {
return {
content: [
{
type: 'text',
text: `Plan mode: ${state.isInPlanMode ? 'active' : 'inactive'}. No current plan.`,
},
],
};
}
const markdown = this.planManager.planToMarkdown(currentPlan);
return {
content: [
{
type: 'text',
text: `Plan mode: ${state.isInPlanMode ? 'active' : 'inactive'}\n\n${markdown}`,
},
],
};
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
throw new McpError(ErrorCode.InternalError, errorMessage);
}
});
}
setupApprovalCallback() {
// This would typically integrate with the client's approval mechanism
// For this example, we'll simulate approval
this.planManager.setApprovalCallback(async (request) => {
// In a real implementation, this would show the plan to the user
// and wait for their response through the MCP client
console.log('Plan ready for approval:');
console.log(request.markdown);
// For now, auto-approve (in real usage, this would be handled by the client)
return {
approved: true,
};
});
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}
// Start the server if this file is run directly
if (import.meta.url === `file://${process.argv[1]}`) {
const server = new PlanModeMCPServer();
server.start().catch(console.error);
}
//# sourceMappingURL=mcpServer.js.map