#
tokens: 19874/50000 7/65 files (page 2/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 2. Use http://codebase.md/deus-h/claudeus-plane-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .cursorignore
├── .env.example
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── CONTRIBUTING.md
├── Dockerfile
├── docs
│   ├── smithery-docs.md
│   └── transform-to-proper-standards.md
├── eslint.config.js
├── jest.config.js
├── LICENSE
├── notes.txt
├── package.json
├── plane-instances-example.json
├── plane-instances-test-example.json
├── pnpm-lock.yaml
├── readme.md
├── SECURITY.md
├── smithery.yaml
├── src
│   ├── api
│   │   ├── base-client.ts
│   │   ├── client.ts
│   │   ├── issues
│   │   │   ├── client.ts
│   │   │   └── types.ts
│   │   ├── projects.ts
│   │   └── types
│   │       ├── config.ts
│   │       └── project.ts
│   ├── config
│   │   └── plane-config.ts
│   ├── dummy-data
│   │   ├── json.d.ts
│   │   ├── projects.d.ts
│   │   └── projects.json
│   ├── index.ts
│   ├── inspector-wrapper.ts
│   ├── mcp
│   │   ├── server.ts
│   │   └── tools.ts
│   ├── prompts
│   │   └── projects
│   │       ├── definitions.ts
│   │       ├── handlers.ts
│   │       └── index.ts
│   ├── security
│   │   └── SecurityManager.ts
│   ├── test
│   │   ├── integration
│   │   │   └── projects.test.ts
│   │   ├── mcp-test-harness.ts
│   │   ├── setup.ts
│   │   └── unit
│   │       └── tools
│   │           └── projects
│   │               └── list.test.ts
│   ├── tools
│   │   ├── index.ts
│   │   ├── issues
│   │   │   ├── create.ts
│   │   │   ├── get.ts
│   │   │   ├── list.ts
│   │   │   └── update.ts
│   │   └── projects
│   │       ├── __tests__
│   │       │   ├── create.test.ts
│   │       │   ├── delete.test.ts
│   │       │   ├── handlers.test.ts
│   │       │   └── update.test.ts
│   │       ├── create.ts
│   │       ├── delete.ts
│   │       ├── handlers.ts
│   │       ├── index.ts
│   │       ├── list.ts
│   │       └── update.ts
│   └── types
│       ├── api.ts
│       ├── index.ts
│       ├── issue.ts
│       ├── mcp.d.ts
│       ├── mcp.ts
│       ├── project.ts
│       ├── prompt.ts
│       └── security.ts
├── tsconfig.json
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/src/tools/projects/update.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod';
  2 | import { Tool, ToolResponse } from '../../types/mcp.js';
  3 | import { PlaneApiClient } from '../../api/client.js';
  4 | 
  5 | const inputSchema = {
  6 |     type: 'object',
  7 |     properties: {
  8 |         workspace_slug: {
  9 |             type: 'string',
 10 |             description: 'The slug of the workspace containing the project. If not provided, uses the default workspace.'
 11 |         },
 12 |         project_id: {
 13 |             type: 'string',
 14 |             description: 'The ID of the project to update (required)'
 15 |         },
 16 |         // Optional fields - any field can be updated
 17 |         name: {
 18 |             type: 'string',
 19 |             description: 'New name for the project'
 20 |         },
 21 |         identifier: {
 22 |             type: 'string',
 23 |             description: 'New unique identifier for the project in the workspace. Example: "PROJ1"'
 24 |         },
 25 |         description: {
 26 |             type: 'string',
 27 |             description: 'New description for the project'
 28 |         },
 29 |         network: {
 30 |             type: 'integer',
 31 |             description: 'Project visibility: 0 for Secret (private), 2 for Public',
 32 |             enum: [0, 2]
 33 |         },
 34 |         emoji: {
 35 |             type: 'string',
 36 |             description: 'HTML emoji DEX code without the "&#". Example: "1f680" for rocket'
 37 |         },
 38 |         icon_prop: {
 39 |             type: 'object',
 40 |             description: 'Custom icon properties for the project'
 41 |         },
 42 |         module_view: {
 43 |             type: 'boolean',
 44 |             description: 'Enable/disable module view for the project'
 45 |         },
 46 |         cycle_view: {
 47 |             type: 'boolean',
 48 |             description: 'Enable/disable cycle view for the project'
 49 |         },
 50 |         issue_views_view: {
 51 |             type: 'boolean',
 52 |             description: 'Enable/disable project views for the project'
 53 |         },
 54 |         page_view: {
 55 |             type: 'boolean',
 56 |             description: 'Enable/disable pages for the project'
 57 |         },
 58 |         inbox_view: {
 59 |             type: 'boolean',
 60 |             description: 'Enable/disable intake for the project'
 61 |         },
 62 |         cover_image: {
 63 |             type: 'string',
 64 |             description: 'URL for the project cover image'
 65 |         },
 66 |         archive_in: {
 67 |             type: 'integer',
 68 |             description: 'Months in which issues should be automatically archived (0-12)',
 69 |             minimum: 0,
 70 |             maximum: 12
 71 |         },
 72 |         close_in: {
 73 |             type: 'integer',
 74 |             description: 'Months in which issues should be auto-closed (0-12)',
 75 |             minimum: 0,
 76 |             maximum: 12
 77 |         },
 78 |         default_assignee: {
 79 |             type: 'string',
 80 |             description: 'UUID of the user who will be the default assignee for issues'
 81 |         },
 82 |         project_lead: {
 83 |             type: 'string',
 84 |             description: 'UUID of the user who will lead the project'
 85 |         },
 86 |         estimate: {
 87 |             type: 'string',
 88 |             description: 'UUID of the estimate to use for the project'
 89 |         },
 90 |         default_state: {
 91 |             type: 'string',
 92 |             description: 'Default state to use when issues are auto-closed'
 93 |         }
 94 |     },
 95 |     required: ['project_id']
 96 | };
 97 | 
 98 | const zodInputSchema = z.object({
 99 |     workspace_slug: z.string().optional(),
100 |     project_id: z.string(),
101 |     // All fields are optional for updates
102 |     name: z.string().optional(),
103 |     identifier: z.string().optional(),
104 |     description: z.string().optional(),
105 |     network: z.number().min(0).max(2).optional(),
106 |     emoji: z.string().optional(),
107 |     icon_prop: z.record(z.unknown()).optional(),
108 |     module_view: z.boolean().optional(),
109 |     cycle_view: z.boolean().optional(),
110 |     issue_views_view: z.boolean().optional(),
111 |     page_view: z.boolean().optional(),
112 |     inbox_view: z.boolean().optional(),
113 |     cover_image: z.string().nullable().optional(),
114 |     archive_in: z.number().min(0).max(12).optional(),
115 |     close_in: z.number().min(0).max(12).optional(),
116 |     default_assignee: z.string().nullable().optional(),
117 |     project_lead: z.string().nullable().optional(),
118 |     estimate: z.string().nullable().optional(),
119 |     default_state: z.string().nullable().optional()
120 | });
121 | 
122 | export class UpdateProjectTool implements Tool {
123 |     name = 'claudeus_plane_projects__update';
124 |     description = 'Updates an existing project in a workspace. If no workspace is specified, uses the default workspace. Allows updating any project properties including name, visibility, views, and automation settings.';
125 |     status: 'enabled' | 'disabled' = 'enabled';
126 |     inputSchema = inputSchema;
127 | 
128 |     constructor(private client: PlaneApiClient) {}
129 | 
130 |     async execute(args: Record<string, unknown>): Promise<ToolResponse> {
131 |         const input = zodInputSchema.parse(args);
132 |         const { workspace_slug, project_id, ...updateData } = input;
133 | 
134 |         try {
135 |             // Use the workspace from config if not provided
136 |             const workspace = workspace_slug || this.client.instance.defaultWorkspace;
137 |             if (!workspace) {
138 |                 throw new Error('No workspace provided or configured');
139 |             }
140 | 
141 |             this.client.notify({
142 |                 type: 'info',
143 |                 message: `Updating project ${project_id} in workspace: ${workspace}`,
144 |                 source: this.name,
145 |                 data: { workspace, project_id, ...updateData }
146 |             });
147 | 
148 |             const project = await this.client.updateProject(workspace, project_id, updateData);
149 |             
150 |             this.client.notify({
151 |                 type: 'info',
152 |                 message: `Successfully updated project ${project_id}`,
153 |                 data: { 
154 |                     projectId: project.id,
155 |                     workspace
156 |                 },
157 |                 source: this.name
158 |             });
159 | 
160 |             return {
161 |                 content: [{
162 |                     type: 'text',
163 |                     text: `Successfully updated project (ID: ${project.id}) in workspace "${workspace}"\n\nUpdated project details:\n${JSON.stringify(project, null, 2)}`
164 |                 }]
165 |             };
166 |         } catch (error) {
167 |             if (error instanceof Error) {
168 |                 this.client.notify({
169 |                     type: 'error',
170 |                     message: `Failed to update project: ${error.message}`,
171 |                     data: { 
172 |                         error: error.message,
173 |                         workspace: workspace_slug,
174 |                         project_id
175 |                     },
176 |                     source: this.name
177 |                 });
178 | 
179 |                 return {
180 |                     isError: true,
181 |                     content: [{
182 |                         type: 'text',
183 |                         text: `Failed to update project: ${error.message}`
184 |                     }]
185 |                 };
186 |             }
187 |             throw error;
188 |         }
189 |     }
190 | } 
191 | 
```

--------------------------------------------------------------------------------
/src/mcp/tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  3 | import { 
  4 |     ListToolsRequestSchema, 
  5 |     CallToolRequestSchema,
  6 |     ListResourcesRequestSchema,
  7 |     ListResourceTemplatesRequestSchema,
  8 |     ReadResourceRequestSchema,
  9 |     ServerResult
 10 | } from '@modelcontextprotocol/sdk/types.js';
 11 | import { PlaneApiClient } from '../api/client.js';
 12 | import { allTools } from '../tools/index.js';
 13 | import { DEFAULT_INSTANCE } from '../config/plane-config.js';
 14 | import { z } from 'zod';
 15 | import { Tool, ToolResponse } from '../types/mcp.js';
 16 | import { McpServer } from './server.js';
 17 | 
 18 | interface MCPMessage {
 19 |     jsonrpc: '2.0';
 20 |     id?: number;
 21 |     method?: string;
 22 |     params?: Record<string, unknown>;
 23 |     result?: {
 24 |         content?: Array<{ type: string; text: string }>;
 25 |         _meta?: Record<string, unknown>;
 26 |     };
 27 | }
 28 | 
 29 | function constructResourceUri(name: string, url: string): string {
 30 |     return `plane://${name}@${new URL(url).hostname}`;
 31 | }
 32 | 
 33 | export function registerTools(server: Server, clients: Map<string, PlaneApiClient>) {
 34 |     // Register resource handlers
 35 |     server.setRequestHandler(ListResourcesRequestSchema, async () => {
 36 |         const resources = Array.from(clients.entries()).map(([name, client]) => ({
 37 |             id: name,
 38 |             name: `Instance: ${name}`,
 39 |             type: "plane_instance",
 40 |             uri: constructResourceUri(name, client.baseUrl),
 41 |             metadata: {
 42 |                 url: client.baseUrl,
 43 |                 authType: "api_key"
 44 |             }
 45 |         }));
 46 |         return { resources };
 47 |     });
 48 | 
 49 |     server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
 50 |         if (!request.params?.uri || typeof request.params.uri !== 'string') {
 51 |             throw { code: -32602, message: 'Resource URI must be a non-empty string' };
 52 |         }
 53 | 
 54 |         const match = request.params.uri.match(/^plane:\/\/([^@]+)@/);
 55 |         if (!match) {
 56 |             throw { code: -32602, message: `Invalid Plane resource URI format: ${request.params.uri}` };
 57 |         }
 58 | 
 59 |         const name = match[1];
 60 |         const client = clients.get(name);
 61 |         if (!client) {
 62 |             throw { code: -32602, message: `Unknown instance: ${name}` };
 63 |         }
 64 | 
 65 |         return {
 66 |             resource: {
 67 |                 id: name,
 68 |                 name: `Instance: ${name}`,
 69 |                 type: "plane_instance",
 70 |                 uri: constructResourceUri(name, client.baseUrl),
 71 |                 metadata: {
 72 |                     url: client.baseUrl,
 73 |                     authType: "api_key",
 74 |                     capabilities: {
 75 |                         projects: true,
 76 |                         issues: true,
 77 |                         cycles: true,
 78 |                         modules: true
 79 |                     }
 80 |                 }
 81 |             },
 82 |             contents: [{
 83 |                 type: 'text',
 84 |                 uri: constructResourceUri(name, client.baseUrl),
 85 |                 text: JSON.stringify({
 86 |                     url: client.baseUrl,
 87 |                     authType: "api_key",
 88 |                     capabilities: {
 89 |                         projects: true,
 90 |                         issues: true,
 91 |                         cycles: true,
 92 |                         modules: true
 93 |                     }
 94 |                 }, null, 2)
 95 |             }]
 96 |         };
 97 |     });
 98 | 
 99 |     server.setRequestHandler(ListResourceTemplatesRequestSchema, async (request) => {
100 |         const resourceId = request.params?.id;
101 |         if (!resourceId || typeof resourceId !== 'string') {
102 |             return { resourceTemplates: [] };
103 |         }
104 | 
105 |         const client = clients.get(resourceId);
106 |         if (!client) {
107 |             return { resourceTemplates: [] };
108 |         }
109 | 
110 |         return {
111 |             resourceTemplates: [{
112 |                 id: "claudeus_plane_discover_endpoints_template",
113 |                 name: "Discover Endpoints",
114 |                 description: "Discover available REST API endpoints on this Plane instance",
115 |                 tool: "claudeus_plane_discover_endpoints",
116 |                 arguments: {
117 |                     instance: resourceId
118 |                 }
119 |             }]
120 |         };
121 |     });
122 | 
123 |     // Register tool handlers
124 |     server.setRequestHandler(ListToolsRequestSchema, async (request) => {
125 |         const instance = (request.params?.instance as string) || DEFAULT_INSTANCE;
126 |         const client = clients.get(instance);
127 |         
128 |         if (!client) {
129 |             throw new Error(`Unknown instance: ${instance}`);
130 |         }
131 | 
132 |         return {
133 |             tools: allTools.map(tool => ({
134 |                 name: tool.name,
135 |                 description: tool.description,
136 |                 status: tool.status || 'enabled',
137 |                 inputSchema: tool.inputSchema || { type: 'object', properties: {} }
138 |             }))
139 |         };
140 |     });
141 | 
142 |     server.setRequestHandler(CallToolRequestSchema, async (request): Promise<ServerResult> => {
143 |         const { name, arguments: args } = request.params;
144 |         const toolDef = allTools.find(t => t.name === name);
145 | 
146 |         if (!toolDef) {
147 |             throw new Error(`Tool not found: ${name}`);
148 |         }
149 | 
150 |         const instance = (args?.instance as string) || DEFAULT_INSTANCE;
151 |         const client = clients.get(instance);
152 |         if (!client) {
153 |             throw new Error(`Unknown instance: ${instance}`);
154 |         }
155 | 
156 |         const toolInstance = new toolDef.class(client);
157 |         const result = await toolInstance.execute(args || {});
158 | 
159 |         return {
160 |             content: result.content,
161 |             _meta: request.params._meta
162 |         };
163 |     });
164 | }
165 | 
166 | interface ExecutableTool extends Tool {
167 |     execute(args: Record<string, unknown>): Promise<ToolResponse>;
168 | }
169 | 
170 | type ToolClass = new (client: PlaneApiClient) => ExecutableTool;
171 | 
172 | export function setupToolHandlers(server: Server, client: PlaneApiClient): void {
173 |     // Register tool list handler
174 |     server.setRequestHandler(z.object({
175 |         method: z.literal('tools/list')
176 |     }), async () => {
177 |         return {
178 |             tools: allTools.map(tool => ({
179 |                 name: tool.name,
180 |                 description: tool.description,
181 |                 inputSchema: tool.inputSchema
182 |             }))
183 |         };
184 |     });
185 | 
186 |     // Register tool call handler
187 |     server.setRequestHandler(z.object({
188 |         method: z.literal('tools/call'),
189 |         params: z.object({
190 |             name: z.string(),
191 |             arguments: z.record(z.unknown()).optional(),
192 |             _meta: z.object({
193 |                 progressToken: z.union([z.string(), z.number()]).optional()
194 |             }).optional()
195 |         })
196 |     }), async (request) => {
197 |         const { name, arguments: args } = request.params;
198 |         const toolDef = allTools.find(t => t.name === name);
199 | 
200 |         if (!toolDef) {
201 |             throw new Error(`Tool not found: ${name}`);
202 |         }
203 | 
204 |         const ToolClass = toolDef.class as ToolClass;
205 |         const toolInstance = new ToolClass(client);
206 |         const result = await toolInstance.execute(args || {});
207 | 
208 |         return {
209 |             content: result.content,
210 |             _meta: request.params._meta
211 |         };
212 |     });
213 | }
```

--------------------------------------------------------------------------------
/src/mcp/server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  3 | import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
  4 | import express, { Express, Response } from 'express';
  5 | import cors from 'cors';
  6 | import { z } from 'zod';
  7 | import { PromptDefinition, PromptContext } from '../types/prompt.js';
  8 | import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
  9 | import { 
 10 |   ListResourcesRequestSchema, 
 11 |   ReadResourceRequestSchema, 
 12 |   ListResourceTemplatesRequestSchema,
 13 |   ListPromptsRequestSchema,
 14 |   GetPromptRequestSchema
 15 | } from '@modelcontextprotocol/sdk/types.js';
 16 | 
 17 | type ServerCapabilities = {
 18 |   [key: string]: unknown;
 19 |   prompts?: { list?: boolean, execute?: boolean };
 20 |   tools?: { list?: boolean, call?: boolean };
 21 |   resources?: { list?: boolean, read?: boolean };
 22 | };
 23 | 
 24 | interface Connection {
 25 |   id: string;
 26 |   transport: any;
 27 |   initialized: boolean;
 28 | }
 29 | 
 30 | interface ServerRequest<T = unknown> {
 31 |   method: string;
 32 |   params: T;
 33 | }
 34 | 
 35 | export class McpServer {
 36 |   private server: Server;
 37 |   private app: Express;
 38 |   private connections: Map<string, Connection> = new Map();
 39 |   private nextConnectionId = 1;
 40 |   private capabilities = {
 41 |     prompts: { listChanged: true },
 42 |     tools: { listChanged: true },
 43 |     resources: { listChanged: true }
 44 |   };
 45 |   private registeredPrompts: PromptDefinition[] = [];
 46 | 
 47 |   constructor(name: string = 'claudeus-plane-mcp', version: string = '1.0.0') {
 48 |     // Create server with proper initialization
 49 |     this.server = new Server(
 50 |       { name, version },
 51 |       { capabilities: this.capabilities }
 52 |     );
 53 | 
 54 |     this.app = express();
 55 |     this.app.use(cors());
 56 |     this.app.use(express.json());
 57 | 
 58 |     // Register resource handlers first
 59 |     this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
 60 |       return { resources: [] }; // Placeholder - will be overridden by tools.ts
 61 |     });
 62 | 
 63 |     this.server.setRequestHandler(ReadResourceRequestSchema, async () => {
 64 |       return { resource: null, contents: [] }; // Placeholder - will be overridden by tools.ts
 65 |     });
 66 | 
 67 |     this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
 68 |       return { resourceTemplates: [] }; // Placeholder - will be overridden by tools.ts
 69 |     });
 70 | 
 71 |     // Register prompt handlers using SDK schemas
 72 |     this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
 73 |       return { 
 74 |         prompts: this.registeredPrompts.map(p => ({
 75 |           name: p.name,
 76 |           description: p.description,
 77 |           schema: p.schema
 78 |         }))
 79 |       };
 80 |     });
 81 | 
 82 |     this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
 83 |       const promptName = request.params?.name;
 84 |       if (!promptName || typeof promptName !== 'string') {
 85 |         throw new Error('Prompt name is required');
 86 |       }
 87 | 
 88 |       const prompt = this.registeredPrompts.find(p => p.name === promptName);
 89 |       if (!prompt) {
 90 |         throw new Error(`Unknown prompt: ${promptName}`);
 91 |       }
 92 | 
 93 |       return {
 94 |         name: prompt.name,
 95 |         description: prompt.description,
 96 |         schema: prompt.schema
 97 |       };
 98 |     });
 99 | 
100 |     // Then register initialization and shutdown handlers
101 |     const initializeSchema = z.object({
102 |       method: z.literal('initialize'),
103 |       params: z.object({
104 |         capabilities: z.record(z.unknown())
105 |       })
106 |     });
107 | 
108 |     const shutdownSchema = z.object({
109 |       method: z.literal('shutdown')
110 |     });
111 | 
112 |     this.server.setRequestHandler(initializeSchema, async (request) => {
113 |       if (!this.isValidCapabilities(request.params.capabilities)) {
114 |         throw {
115 |           code: -32602,
116 |           message: 'Invalid params: capabilities must be an object'
117 |         };
118 |       }
119 |       return {
120 |         protocolVersion: '2024-11-05',
121 |         serverInfo: {
122 |           name,
123 |           version
124 |         },
125 |         capabilities: this.capabilities
126 |       };
127 |     });
128 | 
129 |     this.server.setRequestHandler(shutdownSchema, async () => {
130 |       return { success: true };
131 |     });
132 |   }
133 | 
134 |   private isValidCapabilities(capabilities: unknown): boolean {
135 |     return typeof capabilities === 'object' && capabilities !== null && !Array.isArray(capabilities);
136 |   }
137 | 
138 |   private trackConnection(transport: any): void {
139 |     const id = `conn_${this.nextConnectionId++}`;
140 |     this.connections.set(id, { id, transport, initialized: true });
141 |     console.error(`🔌 New connection established: ${id}`);
142 |   }
143 | 
144 |   private untrackConnection(transport: any): void {
145 |     for (const [id, conn] of this.connections.entries()) {
146 |       if (conn.transport === transport) {
147 |         this.connections.delete(id);
148 |         console.error(`🔌 Connection closed: ${id}`);
149 |         break;
150 |       }
151 |     }
152 |   }
153 | 
154 |   getServer(): Server {
155 |     return this.server;
156 |   }
157 | 
158 |   getApp(): Express {
159 |     return this.app;
160 |   }
161 | 
162 |   getActiveConnections(): number {
163 |     return this.connections.size;
164 |   }
165 | 
166 |   async connectStdio(): Promise<void> {
167 |     const transport = new StdioServerTransport();
168 |     this.trackConnection(transport);
169 |     try {
170 |       await this.server.connect(transport);
171 |     } catch (error) {
172 |       this.untrackConnection(transport);
173 |       throw error;
174 |     }
175 |   }
176 | 
177 |   async connectSSE(port = 3000, path = '/sse'): Promise<void> {
178 |     this.app.get(path, (req, res: Response) => {
179 |       const transport = new SSEServerTransport(path, res);
180 |       
181 |       res.writeHead(200, {
182 |         'Content-Type': 'text/event-stream',
183 |         'Cache-Control': 'no-cache',
184 |         'Connection': 'keep-alive'
185 |       });
186 | 
187 |       this.trackConnection(transport);
188 | 
189 |       this.server.connect(transport).catch(error => {
190 |         console.error('Failed to connect transport:', error);
191 |         this.untrackConnection(transport);
192 |         res.end();
193 |       });
194 | 
195 |       res.on('close', () => {
196 |         this.untrackConnection(transport);
197 |       });
198 |     });
199 | 
200 |     await new Promise<void>((resolve) => {
201 |       this.app.listen(port, () => {
202 |         console.error(`Server listening on port ${port}`);
203 |         resolve();
204 |       });
205 |     });
206 |   }
207 | 
208 |   registerPrompt(prompt: PromptDefinition): void {
209 |     // Register the execute handler for this specific prompt
210 |     const executeSchema = z.object({
211 |       method: z.literal(`prompts/${prompt.name}/execute`),
212 |       params: z.object({
213 |         arguments: z.record(z.unknown())
214 |       })
215 |     });
216 | 
217 |     this.server.setRequestHandler(executeSchema, async (request, extra) => {
218 |       try {
219 |         const context: PromptContext = {
220 |           workspace: process.env.WORKSPACE_PATH || '',
221 |           connectionId: 'default'
222 |         };
223 | 
224 |         // Execute the prompt handler with the arguments
225 |         const result = await prompt.handler(request.params.arguments, context);
226 |         console.error(`Executed prompt: ${prompt.name}`);
227 |         
228 |         // Ensure we have a properly structured response
229 |         if (!result?.messages || !Array.isArray(result.messages)) {
230 |           throw new Error('Prompt handler must return a messages array');
231 |         }
232 |         
233 |         return {
234 |           messages: result.messages,
235 |           metadata: result.metadata || {},
236 |           tools: []
237 |         };
238 |       } catch (error) {
239 |         console.error(`Failed to execute prompt ${prompt.name}:`, error);
240 |         return {
241 |           messages: [{
242 |             role: 'assistant',
243 |             content: {
244 |               type: 'text',
245 |               text: `Error executing prompt ${prompt.name}: ${error instanceof Error ? error.message : String(error)}`
246 |             }
247 |           }],
248 |           metadata: { error: error instanceof Error ? error.message : String(error) },
249 |           tools: []
250 |         };
251 |       }
252 |     });
253 | 
254 |     // Track the registered prompt
255 |     this.registeredPrompts.push(prompt);
256 |     console.error(`Registered prompt: ${prompt.name}`);
257 |   }
258 | 
259 |   async initialize(): Promise<void> {
260 |     try {
261 |       await this.connectStdio();
262 |       console.error('Server initialized successfully');
263 |     } catch (error) {
264 |       console.error('Failed to initialize server:', error);
265 |       throw error;
266 |     }
267 |   }
268 | 
269 |   async start(): Promise<void> {
270 |     try {
271 |       console.error('Server started successfully');
272 |     } catch (error) {
273 |       console.error('Failed to start server:', error);
274 |       throw error;
275 |     }
276 |   }
277 | } 
```

--------------------------------------------------------------------------------
/src/tools/projects/__tests__/handlers.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ProjectsAPI } from '@/api/projects.js';
  2 | import { 
  3 |     listProjects, 
  4 |     createProject, 
  5 |     updateProject, 
  6 |     deleteProject 
  7 | } from '@/tools/projects/handlers.js';
  8 | import { PlaneInstance } from '@/config/plane-config.js';
  9 | import { describe, it, expect, beforeEach, vi } from 'vitest';
 10 | 
 11 | // Mock ProjectsAPI
 12 | vi.mock('@/api/projects.js', () => ({
 13 |     ProjectsAPI: vi.fn().mockImplementation((instance) => ({
 14 |         instance,
 15 |         listProjects: vi.fn(),
 16 |         createProject: vi.fn(),
 17 |         updateProject: vi.fn(),
 18 |         deleteProject: vi.fn()
 19 |     }))
 20 | }));
 21 | 
 22 | describe('Project Tool Handlers', () => {
 23 |     let api: ReturnType<typeof vi.mocked<ProjectsAPI>>;
 24 |     const mockInstance: PlaneInstance = {
 25 |         name: 'test',
 26 |         baseUrl: 'https://test.plane.so',
 27 |         defaultWorkspace: 'default-workspace',
 28 |         apiKey: 'test-key'
 29 |     };
 30 | 
 31 |     beforeEach(() => {
 32 |         api = new ProjectsAPI(mockInstance) as ReturnType<typeof vi.mocked<ProjectsAPI>>;
 33 |     });
 34 | 
 35 |     describe('listProjects', () => {
 36 |         it('should list projects from default workspace', async () => {
 37 |             const mockProjects = [{
 38 |                 id: '123e4567-e89b-12d3-a456-426614174000',
 39 |                 name: 'Test Project',
 40 |                 identifier: 'TEST',
 41 |                 description: null,
 42 |                 network: 1,
 43 |                 workspace: '123e4567-e89b-12d3-a456-426614174001',
 44 |                 project_lead: null,
 45 |                 default_assignee: null,
 46 |                 is_member: true,
 47 |                 member_role: 1,
 48 |                 total_members: 1,
 49 |                 total_cycles: 0,
 50 |                 total_modules: 0,
 51 |                 module_view: true,
 52 |                 cycle_view: true,
 53 |                 issue_views_view: true,
 54 |                 page_view: true,
 55 |                 inbox_view: true,
 56 |                 created_at: '2024-01-25T00:00:00Z',
 57 |                 updated_at: '2024-01-25T00:00:00Z',
 58 |                 created_by: '123e4567-e89b-12d3-a456-426614174002',
 59 |                 updated_by: '123e4567-e89b-12d3-a456-426614174002'
 60 |             }];
 61 |             vi.mocked(api.listProjects).mockResolvedValue(mockProjects);
 62 | 
 63 |             const result = await listProjects(api, {});
 64 |             
 65 |             expect(api.listProjects).toHaveBeenCalledWith('default-workspace', { include_archived: undefined });
 66 |             expect(result.content[0].text).toBe(JSON.stringify(mockProjects, null, 2));
 67 |         });
 68 | 
 69 |         it('should list projects from specified workspace', async () => {
 70 |             const mockProjects = [{
 71 |                 id: '123e4567-e89b-12d3-a456-426614174000',
 72 |                 name: 'Test Project',
 73 |                 identifier: 'TEST',
 74 |                 description: null,
 75 |                 network: 1,
 76 |                 workspace: '123e4567-e89b-12d3-a456-426614174001',
 77 |                 project_lead: null,
 78 |                 default_assignee: null,
 79 |                 is_member: true,
 80 |                 member_role: 1,
 81 |                 total_members: 1,
 82 |                 total_cycles: 0,
 83 |                 total_modules: 0,
 84 |                 module_view: true,
 85 |                 cycle_view: true,
 86 |                 issue_views_view: true,
 87 |                 page_view: true,
 88 |                 inbox_view: true,
 89 |                 created_at: '2024-01-25T00:00:00Z',
 90 |                 updated_at: '2024-01-25T00:00:00Z',
 91 |                 created_by: '123e4567-e89b-12d3-a456-426614174002',
 92 |                 updated_by: '123e4567-e89b-12d3-a456-426614174002'
 93 |             }];
 94 |             vi.mocked(api.listProjects).mockResolvedValue(mockProjects);
 95 | 
 96 |             const result = await listProjects(api, { workspace_slug: 'custom-workspace' });
 97 |             
 98 |             expect(api.listProjects).toHaveBeenCalledWith('custom-workspace', { include_archived: undefined });
 99 |             expect(result.content[0].text).toBe(JSON.stringify(mockProjects, null, 2));
100 |         });
101 | 
102 |         it('should handle errors gracefully', async () => {
103 |             vi.mocked(api.listProjects).mockRejectedValue(new Error('API Error'));
104 |             
105 |             await expect(listProjects(api, {}))
106 |                 .rejects
107 |                 .toThrow('Failed to list projects: API Error');
108 |         });
109 |     });
110 | 
111 |     describe('createProject', () => {
112 |         it('should create a project in default workspace', async () => {
113 |             const mockProject = {
114 |                 id: '123e4567-e89b-12d3-a456-426614174000',
115 |                 name: 'New Project',
116 |                 identifier: 'NEW',
117 |                 description: null,
118 |                 network: 1,
119 |                 workspace: '123e4567-e89b-12d3-a456-426614174001',
120 |                 project_lead: null,
121 |                 default_assignee: null,
122 |                 is_member: true,
123 |                 member_role: 1,
124 |                 total_members: 1,
125 |                 total_cycles: 0,
126 |                 total_modules: 0,
127 |                 module_view: true,
128 |                 cycle_view: true,
129 |                 issue_views_view: true,
130 |                 page_view: true,
131 |                 inbox_view: true,
132 |                 created_at: '2024-01-25T00:00:00Z',
133 |                 updated_at: '2024-01-25T00:00:00Z',
134 |                 created_by: '123e4567-e89b-12d3-a456-426614174002',
135 |                 updated_by: '123e4567-e89b-12d3-a456-426614174002'
136 |             };
137 |             vi.mocked(api.createProject).mockResolvedValue(mockProject);
138 | 
139 |             const result = await createProject(api, {
140 |                 name: 'New Project',
141 |                 identifier: 'NEW'
142 |             });
143 |             
144 |             expect(api.createProject).toHaveBeenCalledWith('default-workspace', {
145 |                 name: 'New Project',
146 |                 identifier: 'NEW'
147 |             });
148 |             expect(result.content[0].text).toBe(JSON.stringify(mockProject, null, 2));
149 |         });
150 | 
151 |         it('should handle validation errors', async () => {
152 |             await expect(createProject(api, {}))
153 |                 .rejects
154 |                 .toThrow();
155 |         });
156 |     });
157 | 
158 |     describe('updateProject', () => {
159 |         it('should update a project', async () => {
160 |             const mockProject = {
161 |                 id: '123e4567-e89b-12d3-a456-426614174000',
162 |                 name: 'Updated Project',
163 |                 identifier: 'UPD',
164 |                 description: null,
165 |                 network: 1,
166 |                 workspace: '123e4567-e89b-12d3-a456-426614174001',
167 |                 project_lead: null,
168 |                 default_assignee: null,
169 |                 is_member: true,
170 |                 member_role: 1,
171 |                 total_members: 1,
172 |                 total_cycles: 0,
173 |                 total_modules: 0,
174 |                 module_view: true,
175 |                 cycle_view: true,
176 |                 issue_views_view: true,
177 |                 page_view: true,
178 |                 inbox_view: true,
179 |                 created_at: '2024-01-25T00:00:00Z',
180 |                 updated_at: '2024-01-25T00:00:00Z',
181 |                 created_by: '123e4567-e89b-12d3-a456-426614174002',
182 |                 updated_by: '123e4567-e89b-12d3-a456-426614174002'
183 |             };
184 |             vi.mocked(api.updateProject).mockResolvedValue(mockProject);
185 | 
186 |             const result = await updateProject(api, {
187 |                 project_id: '1',
188 |                 name: 'Updated Project'
189 |             });
190 |             
191 |             expect(api.updateProject).toHaveBeenCalledWith('default-workspace', '1', {
192 |                 name: 'Updated Project'
193 |             });
194 |             expect(result.content[0].text).toBe(JSON.stringify(mockProject, null, 2));
195 |         });
196 | 
197 |         it('should handle missing project_id', async () => {
198 |             await expect(updateProject(api, { name: 'Test' }))
199 |                 .rejects
200 |                 .toThrow();
201 |         });
202 |     });
203 | 
204 |     describe('deleteProject', () => {
205 |         it('should delete a project', async () => {
206 |             vi.mocked(api.deleteProject).mockResolvedValue(undefined);
207 | 
208 |             const result = await deleteProject(api, {
209 |                 project_id: '1'
210 |             });
211 |             
212 |             expect(api.deleteProject).toHaveBeenCalledWith('default-workspace', '1');
213 |             expect(JSON.parse(result.content[0].text)).toEqual({
214 |                 success: true,
215 |                 message: 'Project deleted successfully'
216 |             });
217 |         });
218 | 
219 |         it('should handle deletion errors', async () => {
220 |             vi.mocked(api.deleteProject).mockRejectedValue(new Error('Not found'));
221 |             
222 |             await expect(deleteProject(api, { project_id: '1' }))
223 |                 .rejects
224 |                 .toThrow('Failed to delete project: Not found');
225 |         });
226 |     });
227 | }); 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "compilerOptions": {
  3 |     /* Visit https://aka.ms/tsconfig to read more about this file */
  4 | 
  5 |     /* Projects */
  6 |     // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
  7 |     // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
  8 |     // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
  9 |     // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
 10 |     // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
 11 |     // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
 12 | 
 13 |     /* Language and Environment */
 14 |     "target": "ES2022",
 15 |     "lib": ["ES2022"],
 16 |     // "jsx": "preserve",                                /* Specify what JSX code is generated. */
 17 |     // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
 18 |     // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
 19 |     // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
 20 |     // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
 21 |     // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
 22 |     // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
 23 |     // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
 24 |     // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
 25 |     // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
 26 | 
 27 |     /* Modules */
 28 |     "module": "NodeNext",
 29 |     "rootDir": "./src",
 30 |     "moduleResolution": "NodeNext",
 31 |     "baseUrl": ".",
 32 |     "paths": {
 33 |       "@/*": ["src/*"],
 34 |       "*": ["src/types/*"]
 35 |     },
 36 |     "typeRoots": [
 37 |       "./node_modules/@types",
 38 |       "./src/types"
 39 |     ],
 40 |     // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
 41 |     // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
 42 |     // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
 43 |     // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
 44 |     // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
 45 |     // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
 46 |     // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
 47 |     // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
 48 |     // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
 49 |     // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
 50 |     "resolveJsonModule": true,
 51 |     // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
 52 |     // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
 53 | 
 54 |     /* JavaScript Support */
 55 |     // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
 56 |     // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
 57 |     // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
 58 | 
 59 |     /* Emit */
 60 |     "declaration": true,
 61 |     // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
 62 |     // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
 63 |     "sourceMap": true,
 64 |     // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
 65 |     // "noEmit": true,                                   /* Disable emitting files from a compilation. */
 66 |     // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
 67 |     "outDir": "./dist",
 68 |     // "removeComments": true,                           /* Disable emitting comments. */
 69 |     // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
 70 |     // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
 71 |     // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
 72 |     // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
 73 |     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
 74 |     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
 75 |     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
 76 |     // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
 77 |     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
 78 |     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
 79 |     // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
 80 |     // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
 81 | 
 82 |     /* Interop Constraints */
 83 |     // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
 84 |     // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
 85 |     // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
 86 |     // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
 87 |     "esModuleInterop": true,
 88 |     // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
 89 |     "forceConsistentCasingInFileNames": true,
 90 | 
 91 |     /* Type Checking */
 92 |     "strict": true,
 93 |     // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
 94 |     // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
 95 |     // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
 96 |     // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
 97 |     // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
 98 |     // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
 99 |     // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
100 |     // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
101 |     // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
102 |     // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
103 |     // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
104 |     // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
105 |     // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
106 |     // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
107 |     // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
108 |     // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
109 |     // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
110 |     // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
111 |     // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
112 | 
113 |     /* Completeness */
114 |     // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
115 |     "skipLibCheck": true
116 |   },
117 |   "include": ["src/**/*"],
118 |   "exclude": ["node_modules", "dist"]
119 | }
120 | 
```

--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolWithClass } from '../types/mcp.js';
  2 | import { ListProjectsTool } from './projects/list.js';
  3 | import { CreateProjectTool } from './projects/create.js';
  4 | import { UpdateProjectTool } from './projects/update.js';
  5 | import { DeleteProjectTool } from './projects/delete.js';
  6 | import { ListIssuesTools } from './issues/list.js';
  7 | import { CreateIssueTool } from './issues/create.js';
  8 | import { GetIssueTool } from './issues/get.js';
  9 | import { UpdateIssueTool } from './issues/update.js';
 10 | 
 11 | // Export all tools with their classes
 12 | export const allTools: ToolWithClass[] = [
 13 |   {
 14 |     name: 'claudeus_plane_projects__list',
 15 |     description: 'Lists all projects in a Plane workspace. If no workspace is specified, lists projects from the default workspace.',
 16 |     status: 'enabled',
 17 |     inputSchema: {
 18 |       type: 'object',
 19 |       properties: {
 20 |         workspace_slug: {
 21 |           type: 'string',
 22 |           description: 'The slug of the workspace to list projects from. If not provided, uses the default workspace.'
 23 |         },
 24 |         include_archived: {
 25 |           type: 'boolean',
 26 |           description: 'Whether to include archived projects',
 27 |           default: false
 28 |         }
 29 |       }
 30 |     },
 31 |     class: ListProjectsTool
 32 |   },
 33 |   {
 34 |     name: 'claudeus_plane_projects__create',
 35 |     description: 'Creates a new project in a workspace. If no workspace is specified, uses the default workspace.',
 36 |     status: 'enabled',
 37 |     inputSchema: {
 38 |       type: 'object',
 39 |       properties: {
 40 |         workspace_slug: {
 41 |           type: 'string',
 42 |           description: 'The slug of the workspace to create the project in. If not provided, uses the default workspace.'
 43 |         },
 44 |         name: {
 45 |           type: 'string',
 46 |           description: 'The name of the project'
 47 |         },
 48 |         identifier: {
 49 |           type: 'string',
 50 |           description: 'The unique identifier for the project'
 51 |         },
 52 |         description: {
 53 |           type: 'string',
 54 |           description: 'A description of the project'
 55 |         }
 56 |       },
 57 |       required: ['name', 'identifier']
 58 |     },
 59 |     class: CreateProjectTool
 60 |   },
 61 |   {
 62 |     name: 'claudeus_plane_projects__update',
 63 |     description: 'Updates an existing project in a workspace. If no workspace is specified, uses the default workspace.',
 64 |     status: 'enabled',
 65 |     inputSchema: {
 66 |       type: 'object',
 67 |       properties: {
 68 |         workspace_slug: {
 69 |           type: 'string',
 70 |           description: 'The slug of the workspace to update the project in. If not provided, uses the default workspace.'
 71 |         },
 72 |         project_id: {
 73 |           type: 'string',
 74 |           description: 'The ID of the project to update.'
 75 |         },
 76 |         name: {
 77 |           type: 'string',
 78 |           description: 'The new name of the project.'
 79 |         },
 80 |         description: {
 81 |           type: 'string',
 82 |           description: 'The new description of the project.'
 83 |         },
 84 |         start_date: {
 85 |           type: 'string',
 86 |           format: 'date',
 87 |           description: 'The new start date of the project.'
 88 |         },
 89 |         end_date: {
 90 |           type: 'string',
 91 |           format: 'date',
 92 |           description: 'The new end date of the project.'
 93 |         },
 94 |         status: {
 95 |           type: 'string',
 96 |           description: 'The new status of the project.'
 97 |         }
 98 |       },
 99 |       required: ['project_id']
100 |     },
101 |     class: UpdateProjectTool
102 |   },
103 |   {
104 |     name: 'claudeus_plane_projects__delete',
105 |     description: 'Deletes an existing project in a workspace. If no workspace is specified, uses the default workspace.',
106 |     status: 'enabled',
107 |     inputSchema: {
108 |       type: 'object',
109 |       properties: {
110 |         workspace_slug: {
111 |           type: 'string',
112 |           description: 'The slug of the workspace to delete the project from. If not provided, uses the default workspace.'
113 |         },
114 |         project_id: {
115 |           type: 'string',
116 |           description: 'The ID of the project to delete.'
117 |         }
118 |       },
119 |       required: ['project_id']
120 |     },
121 |     class: DeleteProjectTool
122 |   },
123 |   {
124 |     name: 'claudeus_plane_issues__list',
125 |     description: 'Lists issues in a Plane project',
126 |     status: 'enabled',
127 |     inputSchema: {
128 |       type: 'object',
129 |       properties: {
130 |         workspace_slug: {
131 |           type: 'string',
132 |           description: 'The slug of the workspace to list issues from. If not provided, uses the default workspace.'
133 |         },
134 |         project_id: {
135 |           type: 'string',
136 |           description: 'The ID of the project to list issues from'
137 |         },
138 |         state: {
139 |           type: 'string',
140 |           description: 'Filter issues by state ID'
141 |         },
142 |         priority: {
143 |           type: 'string',
144 |           enum: ['urgent', 'high', 'medium', 'low', 'none'],
145 |           description: 'Filter issues by priority'
146 |         },
147 |         assignee: {
148 |           type: 'string',
149 |           description: 'Filter issues by assignee ID'
150 |         },
151 |         label: {
152 |           type: 'string',
153 |           description: 'Filter issues by label ID'
154 |         },
155 |         created_by: {
156 |           type: 'string',
157 |           description: 'Filter issues by creator ID'
158 |         },
159 |         start_date: {
160 |           type: 'string',
161 |           format: 'date',
162 |           description: 'Filter issues by start date (YYYY-MM-DD)'
163 |         },
164 |         target_date: {
165 |           type: 'string',
166 |           format: 'date',
167 |           description: 'Filter issues by target date (YYYY-MM-DD)'
168 |         },
169 |         subscriber: {
170 |           type: 'string',
171 |           description: 'Filter issues by subscriber ID'
172 |         },
173 |         is_draft: {
174 |           type: 'boolean',
175 |           description: 'Filter draft issues',
176 |           default: false
177 |         },
178 |         archived: {
179 |           type: 'boolean',
180 |           description: 'Filter archived issues',
181 |           default: false
182 |         },
183 |         page: {
184 |           type: 'number',
185 |           description: 'Page number (1-based)',
186 |           default: 1
187 |         },
188 |         page_size: {
189 |           type: 'number',
190 |           description: 'Number of items per page',
191 |           default: 100
192 |         }
193 |       },
194 |       required: ['project_id']
195 |     },
196 |     class: ListIssuesTools
197 |   },
198 |   {
199 |     name: 'claudeus_plane_issues__create',
200 |     description: 'Creates a new issue in a Plane project',
201 |     status: 'enabled',
202 |     inputSchema: {
203 |       type: 'object',
204 |       properties: {
205 |         workspace_slug: {
206 |           type: 'string',
207 |           description: 'The slug of the workspace to create the issue in. If not provided, uses the default workspace.'
208 |         },
209 |         project_id: {
210 |           type: 'string',
211 |           description: 'The ID of the project to create the issue in'
212 |         },
213 |         name: {
214 |           type: 'string',
215 |           description: 'The name/title of the issue'
216 |         },
217 |         description_html: {
218 |           type: 'string',
219 |           description: 'The HTML description of the issue'
220 |         },
221 |         priority: {
222 |           type: 'string',
223 |           enum: ['urgent', 'high', 'medium', 'low', 'none'],
224 |           description: 'The priority of the issue',
225 |           default: 'none'
226 |         },
227 |         start_date: {
228 |           type: 'string',
229 |           format: 'date',
230 |           description: 'The start date of the issue (YYYY-MM-DD)'
231 |         },
232 |         target_date: {
233 |           type: 'string',
234 |           format: 'date',
235 |           description: 'The target date of the issue (YYYY-MM-DD)'
236 |         },
237 |         estimate_point: {
238 |           type: 'number',
239 |           description: 'Story points or time estimate for the issue'
240 |         },
241 |         state: {
242 |           type: 'string',
243 |           description: 'The state ID for the issue'
244 |         },
245 |         assignees: {
246 |           type: 'array',
247 |           items: {
248 |             type: 'string'
249 |           },
250 |           description: 'Array of user IDs to assign to the issue'
251 |         },
252 |         labels: {
253 |           type: 'array',
254 |           items: {
255 |             type: 'string'
256 |           },
257 |           description: 'Array of label IDs to apply to the issue'
258 |         },
259 |         parent: {
260 |           type: 'string',
261 |           description: 'ID of the parent issue (for sub-issues)'
262 |         },
263 |         is_draft: {
264 |           type: 'boolean',
265 |           description: 'Whether this is a draft issue',
266 |           default: false
267 |         }
268 |       },
269 |       required: ['project_id', 'name']
270 |     },
271 |     class: CreateIssueTool
272 |   },
273 |   {
274 |     name: 'claudeus_plane_issues__get',
275 |     description: 'Gets a single issue by ID from a Plane project',
276 |     status: 'enabled',
277 |     inputSchema: {
278 |       type: 'object',
279 |       properties: {
280 |         workspace_slug: {
281 |           type: 'string',
282 |           description: 'The slug of the workspace containing the issue. If not provided, uses the default workspace.'
283 |         },
284 |         project_id: {
285 |           type: 'string',
286 |           description: 'The ID of the project containing the issue'
287 |         },
288 |         issue_id: {
289 |           type: 'string',
290 |           description: 'The ID of the issue to retrieve'
291 |         }
292 |       },
293 |       required: ['project_id', 'issue_id']
294 |     },
295 |     class: GetIssueTool
296 |   },
297 |   {
298 |     name: 'claudeus_plane_issues__update',
299 |     description: 'Updates an existing issue in a Plane project',
300 |     status: 'enabled',
301 |     inputSchema: {
302 |       type: 'object',
303 |       properties: {
304 |         workspace_slug: {
305 |           type: 'string',
306 |           description: 'The slug of the workspace containing the issue. If not provided, uses the default workspace.'
307 |         },
308 |         project_id: {
309 |           type: 'string',
310 |           description: 'The ID of the project containing the issue'
311 |         },
312 |         issue_id: {
313 |           type: 'string',
314 |           description: 'The ID of the issue to update'
315 |         },
316 |         name: {
317 |           type: 'string',
318 |           description: 'The new name/title of the issue'
319 |         },
320 |         description_html: {
321 |           type: 'string',
322 |           description: 'The new HTML description of the issue'
323 |         },
324 |         priority: {
325 |           type: 'string',
326 |           enum: ['urgent', 'high', 'medium', 'low', 'none'],
327 |           description: 'The new priority of the issue'
328 |         },
329 |         start_date: {
330 |           type: 'string',
331 |           format: 'date',
332 |           description: 'The new start date of the issue (YYYY-MM-DD)'
333 |         },
334 |         target_date: {
335 |           type: 'string',
336 |           format: 'date',
337 |           description: 'The new target date of the issue (YYYY-MM-DD)'
338 |         },
339 |         estimate_point: {
340 |           type: 'number',
341 |           description: 'The new story points or time estimate for the issue'
342 |         },
343 |         state: {
344 |           type: 'string',
345 |           description: 'The new state ID for the issue'
346 |         },
347 |         assignees: {
348 |           type: 'array',
349 |           items: {
350 |             type: 'string'
351 |           },
352 |           description: 'New array of user IDs to assign to the issue'
353 |         },
354 |         labels: {
355 |           type: 'array',
356 |           items: {
357 |             type: 'string'
358 |           },
359 |           description: 'New array of label IDs to apply to the issue'
360 |         },
361 |         parent: {
362 |           type: 'string',
363 |           description: 'New parent issue ID (for sub-issues)'
364 |         },
365 |         is_draft: {
366 |           type: 'boolean',
367 |           description: 'Whether this issue should be marked as draft'
368 |         },
369 |         archived_at: {
370 |           type: 'string',
371 |           format: 'date-time',
372 |           description: 'When to archive the issue (ISO 8601 format)'
373 |         },
374 |         completed_at: {
375 |           type: 'string',
376 |           format: 'date-time',
377 |           description: 'When the issue was completed (ISO 8601 format)'
378 |         }
379 |       },
380 |       required: ['project_id', 'issue_id']
381 |     },
382 |     class: UpdateIssueTool
383 |   }
384 | ];
385 | 
386 | // Define tool capabilities
387 | export const toolCapabilities = {
388 |   // Projects
389 |   claudeus_plane_projects__list: true,
390 |   claudeus_plane_projects__get: false, // Coming soon
391 |   claudeus_plane_projects__create: true,
392 |   claudeus_plane_projects__update: true,
393 |   claudeus_plane_projects__delete: true,
394 |   
395 |   // Issues
396 |   claudeus_plane_issues__list: true,
397 |   claudeus_plane_issues__get: true,
398 |   claudeus_plane_issues__create: true,
399 |   claudeus_plane_issues__update: true,
400 |   claudeus_plane_issues__delete: false, // Coming soon
401 |   
402 |   // Cycles (Coming soon)
403 |   claudeus_plane_cycles__list: false,
404 |   claudeus_plane_cycles__get: false,
405 |   claudeus_plane_cycles__create: false,
406 |   claudeus_plane_cycles__update: false,
407 |   claudeus_plane_cycles__delete: false,
408 |   
409 |   // Modules (Coming soon)
410 |   claudeus_plane_modules__list: false,
411 |   claudeus_plane_modules__get: false,
412 |   claudeus_plane_modules__create: false,
413 |   claudeus_plane_modules__update: false,
414 |   claudeus_plane_modules__delete: false
415 | }; 
```

--------------------------------------------------------------------------------
/src/prompts/projects/handlers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { PromptHandler, PromptContext, PromptResponse } from '../../types/prompt.js';
  2 | import { PlaneApiClient, NotificationOptions, ToolExecutionOptions } from '../../api/client.js';
  3 | import { PlaneInstance } from '../../config/plane-config.js';
  4 | 
  5 | interface Project {
  6 |   name: string;
  7 |   total_members: number;
  8 |   total_cycles: number;
  9 |   total_modules: number;
 10 |   [key: string]: any;
 11 | }
 12 | 
 13 | interface ProjectMetrics {
 14 |   name: string;
 15 |   members: number;
 16 |   cycles: number;
 17 |   modules: number;
 18 |   complexity: number;
 19 | }
 20 | 
 21 | interface ProjectStructureAnalysis {
 22 |   name: string;
 23 |   missingFeatures: string[];
 24 |   cycleGap: number;
 25 |   moduleGap: number;
 26 |   memberGap: number;
 27 | }
 28 | 
 29 | interface ProjectRecommendation {
 30 |   project: string;
 31 |   recommendations: string[];
 32 | }
 33 | 
 34 | export const analyzeWorkspaceHealthHandler: PromptHandler = async (args: Record<string, unknown>, context: PromptContext): Promise<PromptResponse> => {
 35 |   try {
 36 |     const planeInstance: PlaneInstance = {
 37 |       name: 'default',
 38 |       baseUrl: process.env.PLANE_BASE_URL || '',
 39 |       apiKey: process.env.PLANE_API_KEY || ''
 40 |     };
 41 |     
 42 |     const client = new PlaneApiClient(planeInstance, context);
 43 |     const workspace_slug = args.workspace_slug as string | undefined;
 44 |     const include_archived = args.include_archived as boolean | undefined;
 45 | 
 46 |     // Get projects using the tool
 47 |     const toolResult = await client.executeTool('claudeus_plane_projects__list', {
 48 |       workspace_slug,
 49 |       include_archived
 50 |     });
 51 | 
 52 |     if (!toolResult?.content?.[0]?.text) {
 53 |       throw new Error('Invalid tool result format');
 54 |     }
 55 | 
 56 |     const projects = JSON.parse(toolResult.content[0].text) as Project[];
 57 | 
 58 |     // Analyze project health metrics
 59 |     const totalProjects = projects.length;
 60 |     const metrics = {
 61 |       memberDistribution: projects.map((p: Project) => ({
 62 |         name: p.name,
 63 |         memberCount: p.total_members
 64 |       })),
 65 |       cycleUsage: projects.map((p: Project) => ({
 66 |         name: p.name,
 67 |         cycleCount: p.total_cycles
 68 |       })),
 69 |       moduleUsage: projects.map((p: Project) => ({
 70 |         name: p.name,
 71 |         moduleCount: p.total_modules
 72 |       }))
 73 |     };
 74 | 
 75 |     // Generate insights
 76 |     const insights: string[] = [];
 77 |     
 78 |     // Member distribution insights
 79 |     const avgMembers = metrics.memberDistribution.reduce((acc: number, p) => acc + p.memberCount, 0) / totalProjects;
 80 |     insights.push(`Average team size: ${avgMembers.toFixed(1)} members per project`);
 81 |     
 82 |     const underStaffed = metrics.memberDistribution.filter(p => p.memberCount < avgMembers * 0.5);
 83 |     if (underStaffed.length > 0) {
 84 |       insights.push(`Potentially understaffed projects: ${underStaffed.map(p => p.name).join(', ')}`);
 85 |     }
 86 | 
 87 |     // Feature usage insights
 88 |     const lowCycleUsage = metrics.cycleUsage.filter(p => p.cycleCount === 0);
 89 |     if (lowCycleUsage.length > 0) {
 90 |       insights.push(`Projects not using cycles: ${lowCycleUsage.map(p => p.name).join(', ')}`);
 91 |     }
 92 | 
 93 |     const lowModuleUsage = metrics.moduleUsage.filter(p => p.moduleCount === 0);
 94 |     if (lowModuleUsage.length > 0) {
 95 |       insights.push(`Projects not using modules: ${lowModuleUsage.map(p => p.name).join(', ')}`);
 96 |     }
 97 | 
 98 |     const response: PromptResponse = {
 99 |       messages: [{
100 |         role: 'assistant',
101 |         content: {
102 |           type: 'text',
103 |           text: '# Workspace Health Analysis\n\n' +
104 |                 `Analyzed ${totalProjects} projects${workspace_slug ? ` in workspace ${workspace_slug}` : ''}\n\n` +
105 |                 '## Key Insights\n\n' +
106 |                 insights.map(i => `- ${i}`).join('\n') + '\n\n' +
107 |                 '## Recommendations\n\n' +
108 |                 '1. Consider redistributing team members for more balanced project staffing\n' +
109 |                 '2. Encourage use of cycles for better project planning\n' +
110 |                 '3. Leverage modules for improved project organization'
111 |         }
112 |       }],
113 |       metadata: {
114 |         metrics,
115 |         insights
116 |       }
117 |     };
118 | 
119 |     return response;
120 |   } catch (error) {
121 |     console.error('Failed to analyze workspace health:', error);
122 |     return {
123 |       messages: [{
124 |         role: 'assistant',
125 |         content: {
126 |           type: 'text',
127 |           text: `Error analyzing workspace health: ${error instanceof Error ? error.message : String(error)}`
128 |         }
129 |       }],
130 |       metadata: {
131 |         error: error instanceof Error ? error.message : String(error)
132 |       }
133 |     };
134 |   }
135 | };
136 | 
137 | export const suggestResourceAllocationHandler: PromptHandler = async (args: Record<string, unknown>, context: PromptContext): Promise<PromptResponse> => {
138 |   try {
139 |     const planeInstance: PlaneInstance = {
140 |       name: 'default',
141 |       baseUrl: process.env.PLANE_BASE_URL || '',
142 |       apiKey: process.env.PLANE_API_KEY || ''
143 |     };
144 |     
145 |     const client = new PlaneApiClient(planeInstance, context);
146 |     const workspace_slug = args.workspace_slug as string | undefined;
147 |     const focus_area = (args.focus_area as string) || 'members';
148 | 
149 |     // Get projects using the tool
150 |     const toolResult = await client.executeTool('claudeus_plane_projects__list', {
151 |       workspace_slug
152 |     });
153 | 
154 |     if (!toolResult?.content?.[0]?.text) {
155 |       throw new Error('Invalid tool result format');
156 |     }
157 | 
158 |     const projects = JSON.parse(toolResult.content[0].text) as Project[];
159 | 
160 |     // Analyze current allocation
161 |     const allocation: ProjectMetrics[] = projects.map(p => ({
162 |       name: p.name,
163 |       members: p.total_members,
164 |       cycles: p.total_cycles,
165 |       modules: p.total_modules,
166 |       complexity: (p.total_cycles * 0.4) + (p.total_modules * 0.6) // Weighted complexity score
167 |     }));
168 | 
169 |     // Generate recommendations based on focus area
170 |     const recommendations: string[] = [];
171 |     switch (focus_area) {
172 |       case 'members': {
173 |         const avgMembers = allocation.reduce((acc: number, p) => acc + p.members, 0) / projects.length;
174 |         allocation.forEach(p => {
175 |           const recommendedMembers = Math.ceil(p.complexity / avgMembers * p.members);
176 |           if (recommendedMembers !== p.members) {
177 |             recommendations.push(`${p.name}: ${p.members} → ${recommendedMembers} members (based on complexity)`);
178 |           }
179 |         });
180 |         break;
181 |       }
182 |       case 'cycles': {
183 |         const avgCycles = allocation.reduce((acc: number, p) => acc + p.cycles, 0) / projects.length;
184 |         allocation.forEach(p => {
185 |           if (p.cycles < avgCycles * 0.5) {
186 |             recommendations.push(`${p.name}: Consider increasing cycle usage (current: ${p.cycles}, avg: ${avgCycles.toFixed(1)})`);
187 |           }
188 |         });
189 |         break;
190 |       }
191 |       case 'modules': {
192 |         const avgModules = allocation.reduce((acc: number, p) => acc + p.modules, 0) / projects.length;
193 |         allocation.forEach(p => {
194 |           if (p.modules < avgModules * 0.5) {
195 |             recommendations.push(`${p.name}: Consider increasing module usage (current: ${p.modules}, avg: ${avgModules.toFixed(1)})`);
196 |           }
197 |         });
198 |         break;
199 |       }
200 |     }
201 | 
202 |     const response: PromptResponse = {
203 |       messages: [{
204 |         role: 'assistant',
205 |         content: {
206 |           type: 'text',
207 |           text: '# Resource Allocation Recommendations\n\n' +
208 |                 `Focus Area: ${focus_area}\n\n` +
209 |                 '## Recommendations\n\n' +
210 |                 recommendations.map(r => `- ${r}`).join('\n') + '\n\n' +
211 |                 '## Additional Notes\n\n' +
212 |                 '- Recommendations are based on project complexity and current resource usage\n' +
213 |                 '- Consider team expertise and project priorities when implementing changes'
214 |         }
215 |       }],
216 |       metadata: {
217 |         allocation,
218 |         recommendations
219 |       }
220 |     };
221 | 
222 |     return response;
223 |   } catch (error) {
224 |     console.error('Failed to analyze resource allocation:', error);
225 |     return {
226 |       messages: [{
227 |         role: 'assistant',
228 |         content: {
229 |           type: 'text',
230 |           text: `Error analyzing resource allocation: ${error instanceof Error ? error.message : String(error)}`
231 |         }
232 |       }],
233 |       metadata: {
234 |         error: error instanceof Error ? error.message : String(error)
235 |       }
236 |     };
237 |   }
238 | };
239 | 
240 | export const recommendProjectStructureHandler: PromptHandler = async (args: Record<string, unknown>, context: PromptContext): Promise<PromptResponse> => {
241 |   try {
242 |     const planeInstance: PlaneInstance = {
243 |       name: 'default',
244 |       baseUrl: process.env.PLANE_BASE_URL || '',
245 |       apiKey: process.env.PLANE_API_KEY || ''
246 |     };
247 |     
248 |     const client = new PlaneApiClient(planeInstance, context);
249 |     const workspace_slug = args.workspace_slug as string | undefined;
250 |     const template_project = args.template_project as string | undefined;
251 | 
252 |     // Get projects using the tool
253 |     const toolResult = await client.executeTool('claudeus_plane_projects__list', {
254 |       workspace_slug
255 |     });
256 | 
257 |     if (!toolResult?.content?.[0]?.text) {
258 |       throw new Error('Invalid tool result format');
259 |     }
260 | 
261 |     const projects = JSON.parse(toolResult.content[0].text) as Project[];
262 | 
263 |     // Define best practices
264 |     const bestPractices = {
265 |       minCycles: 1,
266 |       minModules: 2,
267 |       minMembers: 2,
268 |       recommendedFeatures: ['cycles', 'modules', 'project_lead'] as const
269 |     };
270 | 
271 |     // If template project specified, use its metrics as best practices
272 |     if (template_project) {
273 |       const templateData = projects.find((p: Project) => p.name === template_project);
274 |       if (templateData) {
275 |         bestPractices.minCycles = templateData.total_cycles;
276 |         bestPractices.minModules = templateData.total_modules;
277 |         bestPractices.minMembers = templateData.total_members;
278 |       }
279 |     }
280 | 
281 |     // Analyze each project
282 |     const structureAnalysis: ProjectStructureAnalysis[] = projects.map((p: Project) => ({
283 |       name: p.name,
284 |       missingFeatures: bestPractices.recommendedFeatures.filter((f) => !p[f]),
285 |       cycleGap: Math.max(0, bestPractices.minCycles - p.total_cycles),
286 |       moduleGap: Math.max(0, bestPractices.minModules - p.total_modules),
287 |       memberGap: Math.max(0, bestPractices.minMembers - p.total_members)
288 |     }));
289 | 
290 |     // Generate recommendations
291 |     const recommendations: ProjectRecommendation[] = structureAnalysis
292 |       .filter((p) => p.missingFeatures.length > 0 || p.cycleGap > 0 || p.moduleGap > 0 || p.memberGap > 0)
293 |       .map((p) => ({
294 |         project: p.name,
295 |         recommendations: [
296 |           ...p.missingFeatures.map((f) => `Enable ${f} feature`),
297 |           p.cycleGap > 0 ? `Add ${p.cycleGap} more cycle(s)` : null,
298 |           p.moduleGap > 0 ? `Add ${p.moduleGap} more module(s)` : null,
299 |           p.memberGap > 0 ? `Add ${p.memberGap} more team member(s)` : null
300 |         ].filter((rec): rec is string => rec !== null)
301 |       }));
302 | 
303 |     const response: PromptResponse = {
304 |       messages: [{
305 |         role: 'assistant',
306 |         content: {
307 |           type: 'text',
308 |           text: '# Project Structure Recommendations\n\n' +
309 |                 (template_project ? `Using "${template_project}" as template\n\n` : 'Using best practices as template\n\n') +
310 |                 '## Project-Specific Recommendations\n\n' +
311 |                 recommendations.map((r) => 
312 |                   `### ${r.project}\n` +
313 |                   r.recommendations.map((rec) => `- ${rec}`).join('\n')
314 |                 ).join('\n\n') + '\n\n' +
315 |                 '## General Guidelines\n\n' +
316 |                 '- Maintain consistent project structure across workspace\n' +
317 |                 '- Regularly review and update project organization\n' +
318 |                 '- Document project structure decisions'
319 |         }
320 |       }],
321 |       metadata: {
322 |         bestPractices,
323 |         structureAnalysis,
324 |         recommendations
325 |       }
326 |     };
327 | 
328 |     return response;
329 |   } catch (error) {
330 |     console.error('Failed to analyze project structure:', error);
331 |     return {
332 |       messages: [{
333 |         role: 'assistant',
334 |         content: {
335 |           type: 'text',
336 |           text: `Error analyzing project structure: ${error instanceof Error ? error.message : String(error)}`
337 |         }
338 |       }],
339 |       metadata: {
340 |         error: error instanceof Error ? error.message : String(error)
341 |       }
342 |     };
343 |   }
344 | };
345 | 
346 | export async function handleProjectPrompts(
347 |   promptId: string,
348 |   args: Record<string, unknown>,
349 |   client: PlaneApiClient
350 | ): Promise<PromptResponse> {
351 |   try {
352 |     switch (promptId) {
353 |       case 'analyze_workspace_health': {
354 |         const result = await client.listProjects();
355 |         return {
356 |           messages: [{
357 |             role: 'assistant',
358 |             content: {
359 |               type: 'text',
360 |               text: JSON.stringify(result, null, 2)
361 |             }
362 |           }]
363 |         };
364 |       }
365 | 
366 |       case 'suggest_resource_allocation': {
367 |         const result = await client.listProjects();
368 |         return {
369 |           messages: [{
370 |             role: 'assistant',
371 |             content: {
372 |               type: 'text',
373 |               text: JSON.stringify(result, null, 2)
374 |             }
375 |           }]
376 |         };
377 |       }
378 | 
379 |       case 'recommend_project_structure': {
380 |         const result = await client.listProjects();
381 |         return {
382 |           messages: [{
383 |             role: 'assistant',
384 |             content: {
385 |               type: 'text',
386 |               text: JSON.stringify(result, null, 2)
387 |             }
388 |           }]
389 |         };
390 |       }
391 | 
392 |       default:
393 |         throw new Error(`Unknown prompt: ${promptId}`);
394 |     }
395 |   } catch (error) {
396 |     return {
397 |       messages: [{
398 |         role: 'assistant',
399 |         content: {
400 |           type: 'text',
401 |           text: `Error: ${error instanceof Error ? error.message : String(error)}`
402 |         }
403 |       }]
404 |     };
405 |   }
406 | } 
407 | 
```
Page 2/2FirstPrevNextLast