# Directory Structure
```
├── .gitignore
├── Dockerfile
├── LICENSE
├── package.json
├── pnpm-lock.yaml
├── README.md
├── smithery.yaml
├── software-planning-tool-logo.png
├── src
│ ├── index.ts
│ ├── prompts.ts
│ ├── storage.ts
│ └── types.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
node_modules/
build/
*.log
.env*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Software Planning Tool 🚀
[](https://smithery.ai/server/@NightTrek/Software-planning-mcp)
A Model Context Protocol (MCP) server designed to facilitate software development planning through an interactive, structured approach. This tool helps break down complex software projects into manageable tasks, track implementation progress, and maintain detailed development plans.
<a href="https://glama.ai/mcp/servers/a35c7qc7ie">
<img width="380" height="200" src="https://glama.ai/mcp/servers/a35c7qc7ie/badge" alt="Software Planning Tool MCP server" />
</a>
## Features ✨
- **Interactive Planning Sessions**: Start and manage development planning sessions
- **Todo Management**: Create, update, and track development tasks
- **Complexity Scoring**: Assign complexity scores to tasks for better estimation
- **Code Examples**: Include relevant code snippets in task descriptions
- **Implementation Plans**: Save and manage detailed implementation plans
## Installation 🛠️
### Installing via Smithery
To install Software Planning Tool for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@NightTrek/Software-planning-mcp):
```bash
npx -y @smithery/cli install @NightTrek/Software-planning-mcp --client claude
```
### Manual Installation
1. Clone the repository
2. Install dependencies:
```bash
pnpm install
```
3. Build the project:
```bash
pnpm run build
```
4. Add to your MCP settings configuration (typically located at `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`):
```json
{
"mcpServers": {
"software-planning-tool": {
"command": "node",
"args": [
"/path/to/software-planning-tool/build/index.js"
],
"disabled": false,
"autoApprove": []
}
}
}
```
## Available Tools 🔧
### start_planning
Start a new planning session with a specific goal.
```typescript
{
goal: string // The software development goal to plan
}
```
### add_todo
Add a new todo item to the current plan.
```typescript
{
title: string, // Title of the todo item
description: string, // Detailed description
complexity: number, // Complexity score (0-10)
codeExample?: string // Optional code example
}
```
### get_todos
Retrieve all todos in the current plan.
```typescript
// No parameters required
```
### update_todo_status
Update the completion status of a todo item.
```typescript
{
todoId: string, // ID of the todo item
isComplete: boolean // New completion status
}
```
### save_plan
Save the current implementation plan.
```typescript
{
plan: string // The implementation plan text
}
```
### remove_todo
Remove a todo item from the current plan.
```typescript
{
todoId: string // ID of the todo item to remove
}
```
## Example Usage 📝
Here's a complete example of using the software planning tool:
1. Start a planning session:
```typescript
await client.callTool("software-planning-tool", "start_planning", {
goal: "Create a React-based dashboard application"
});
```
2. Add a todo item:
```typescript
const todo = await client.callTool("software-planning-tool", "add_todo", {
title: "Set up project structure",
description: "Initialize React project with necessary dependencies",
complexity: 3,
codeExample: `
npx create-react-app dashboard
cd dashboard
npm install @material-ui/core @material-ui/icons
`
});
```
3. Update todo status:
```typescript
await client.callTool("software-planning-tool", "update_todo_status", {
todoId: todo.id,
isComplete: true
});
```
4. Save the implementation plan:
```typescript
await client.callTool("software-planning-tool", "save_plan", {
plan: `
# Dashboard Implementation Plan
## Phase 1: Setup (Complexity: 3)
- Initialize React project
- Install dependencies
- Set up routing
## Phase 2: Core Features (Complexity: 5)
- Implement authentication
- Create dashboard layout
- Add data visualization components
`
});
```
## Development 🔨
### Project Structure
```
software-planning-tool/
├── src/
│ ├── index.ts # Main server implementation
│ ├── prompts.ts # Planning prompts and templates
│ ├── storage.ts # Data persistence
│ └── types.ts # TypeScript type definitions
├── build/ # Compiled JavaScript
├── package.json
└── tsconfig.json
```
### Building
```bash
pnpm run build
```
### Testing
Test all features using the MCP inspector:
```bash
pnpm run inspector
```
## License 📄
MIT
---
Made with ❤️ using the Model Context Protocol
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "build"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
startCommand:
type: stdio
configSchema:
# JSON Schema defining the configuration options for the MCP.
type: object
properties: {}
commandFunction:
# A function that produces the CLI command to start the MCP on stdio.
|-
() => ({ command: 'node', args: ['build/index.js'] })
```
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
```typescript
export interface Todo {
id: string;
title: string;
description: string;
complexity: number;
codeExample?: string;
isComplete: boolean;
createdAt: string;
updatedAt: string;
}
export interface Goal {
id: string;
description: string;
createdAt: string;
}
export interface ImplementationPlan {
goalId: string;
todos: Todo[];
updatedAt: string;
}
export interface StorageData {
goals: Record<string, Goal>;
plans: Record<string, ImplementationPlan>;
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "software-planning-tool",
"version": "0.1.0",
"description": "A Model Context Protocol server",
"private": true,
"type": "module",
"bin": {
"software-planning-tool": "./build/index.js"
},
"files": [
"build"
],
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"prepare": "npm run build",
"watch": "tsc --watch",
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "0.6.0"
},
"devDependencies": {
"@types/node": "^20.11.24",
"typescript": "^5.3.3"
}
}
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Use the official Node.js image.
FROM node:18-alpine
# Set the working directory inside the container.
WORKDIR /app
# Copy over only package.json and pnpm-lock.yaml to install dependencies.
COPY package.json pnpm-lock.yaml ./
# Install pnpm globally and then install dependencies.
RUN npm install -g pnpm && pnpm install --frozen-lockfile
# Copy the rest of the application code.
COPY . .
# Build the application.
RUN pnpm run build
# Change the permissions for the build script to make it executable.
RUN chmod +x build/index.js
# Expose the port the app runs on
EXPOSE 3000
# Command to run the application
ENTRYPOINT ["node", "build/index.js"]
```
--------------------------------------------------------------------------------
/src/prompts.ts:
--------------------------------------------------------------------------------
```typescript
export const SEQUENTIAL_THINKING_PROMPT = `You are a senior software architect guiding the development of a software feature through a question-based sequential thinking process. Your role is to:
1. UNDERSTAND THE GOAL
- Start by thoroughly understanding the provided goal
- Break down complex requirements into manageable components
- Identify potential challenges and constraints
2. ASK STRATEGIC QUESTIONS
Ask focused questions about:
- System architecture and design patterns
- Technical requirements and constraints
- Integration points with existing systems
- Security considerations
- Performance requirements
- Scalability needs
- Data management and storage
- User experience requirements
- Testing strategy
- Deployment considerations
3. ANALYZE RESPONSES
- Process user responses to refine understanding
- Identify gaps in information
- Surface potential risks or challenges
- Consider alternative approaches
- Validate assumptions
4. DEVELOP THE PLAN
As understanding develops:
- Create detailed, actionable implementation steps
- Include complexity scores (0-10) for each task
- Provide code examples where helpful
- Consider dependencies between tasks
- Break down large tasks into smaller subtasks
- Include testing and validation steps
- Document architectural decisions
5. ITERATE AND REFINE
- Continue asking questions until all aspects are clear
- Refine the plan based on new information
- Adjust task breakdown and complexity scores
- Add implementation details as they emerge
6. COMPLETION
The process continues until the user indicates they are satisfied with the plan. The final plan should be:
- Comprehensive and actionable
- Well-structured and prioritized
- Clear in its technical requirements
- Specific in its implementation details
- Realistic in its complexity assessments
GUIDELINES:
- Ask one focused question at a time
- Maintain context from previous responses
- Be specific and technical in questions
- Consider both immediate and long-term implications
- Document key decisions and their rationale
- Include relevant code examples in task descriptions
- Consider security, performance, and maintainability
- Focus on practical, implementable solutions
Begin by analyzing the provided goal and asking your first strategic question.`;
export const formatPlanAsTodos = (plan: string): Array<{
title: string;
description: string;
complexity: number;
codeExample?: string;
}> => {
// This is a placeholder implementation
// In a real system, this would use more sophisticated parsing
// to extract todos from the plan text
const todos = plan.split('\n\n')
.filter(section => section.trim().length > 0)
.map(section => {
const lines = section.split('\n');
const title = lines[0].replace(/^[0-9]+\.\s*/, '').trim();
const complexity = parseInt(section.match(/Complexity:\s*([0-9]+)/)?.[1] || '5');
const codeExample = section.match(/\`\`\`[^\`]*\`\`\`/)?.[0];
const description = section
.replace(/^[0-9]+\.\s*[^\n]*\n/, '')
.replace(/Complexity:\s*[0-9]+/, '')
.replace(/\`\`\`[^\`]*\`\`\`/, '')
.trim();
return {
title,
description,
complexity,
codeExample: codeExample?.replace(/^\`\`\`|\`\`\`$/g, ''),
};
});
return todos;
};
```
--------------------------------------------------------------------------------
/src/storage.ts:
--------------------------------------------------------------------------------
```typescript
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import { StorageData, Goal, ImplementationPlan, Todo } from './types.js';
export class Storage {
private storagePath: string;
private data: StorageData;
constructor() {
// Store data in user's home directory under .software-planning-tool
const dataDir = path.join(os.homedir(), '.software-planning-tool');
this.storagePath = path.join(dataDir, 'data.json');
this.data = {
goals: {},
plans: {},
};
}
async initialize(): Promise<void> {
try {
// Create data directory if it doesn't exist
const dataDir = path.dirname(this.storagePath);
await fs.mkdir(dataDir, { recursive: true });
// Try to read existing data
const data = await fs.readFile(this.storagePath, 'utf-8');
this.data = JSON.parse(data);
} catch (error) {
// If file doesn't exist or can't be read, use default empty data
await this.save();
}
}
private async save(): Promise<void> {
await fs.writeFile(this.storagePath, JSON.stringify(this.data, null, 2));
}
async createGoal(description: string): Promise<Goal> {
const goal: Goal = {
id: Date.now().toString(),
description,
createdAt: new Date().toISOString(),
};
this.data.goals[goal.id] = goal;
await this.save();
return goal;
}
async getGoal(id: string): Promise<Goal | null> {
return this.data.goals[id] || null;
}
async createPlan(goalId: string): Promise<ImplementationPlan> {
const plan: ImplementationPlan = {
goalId,
todos: [],
updatedAt: new Date().toISOString(),
};
this.data.plans[goalId] = plan;
await this.save();
return plan;
}
async getPlan(goalId: string): Promise<ImplementationPlan | null> {
return this.data.plans[goalId] || null;
}
async addTodo(
goalId: string,
{ title, description, complexity, codeExample }: Omit<Todo, 'id' | 'isComplete' | 'createdAt' | 'updatedAt'>
): Promise<Todo> {
const plan = await this.getPlan(goalId);
if (!plan) {
throw new Error(`No plan found for goal ${goalId}`);
}
const todo: Todo = {
id: Date.now().toString(),
title,
description,
complexity,
codeExample,
isComplete: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
plan.todos.push(todo);
plan.updatedAt = new Date().toISOString();
await this.save();
return todo;
}
async removeTodo(goalId: string, todoId: string): Promise<void> {
const plan = await this.getPlan(goalId);
if (!plan) {
throw new Error(`No plan found for goal ${goalId}`);
}
plan.todos = plan.todos.filter((todo: Todo) => todo.id !== todoId);
plan.updatedAt = new Date().toISOString();
await this.save();
}
async updateTodoStatus(goalId: string, todoId: string, isComplete: boolean): Promise<Todo> {
const plan = await this.getPlan(goalId);
if (!plan) {
throw new Error(`No plan found for goal ${goalId}`);
}
const todo = plan.todos.find((t: Todo) => t.id === todoId);
if (!todo) {
throw new Error(`No todo found with id ${todoId}`);
}
todo.isComplete = isComplete;
todo.updatedAt = new Date().toISOString();
plan.updatedAt = new Date().toISOString();
await this.save();
return todo;
}
async getTodos(goalId: string): Promise<Todo[]> {
const plan = await this.getPlan(goalId);
return plan?.todos || [];
}
}
export const storage = new Storage();
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/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);
```