#
tokens: 47110/50000 64/65 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/deus-h/claudeus-plane-mcp?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

--------------------------------------------------------------------------------
/.cursorignore:
--------------------------------------------------------------------------------

```
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)

```

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false
} 
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
# Server Configuration
PORT=3000
HOST=localhost

# Logging
LOG_LEVEL=info

# Plane API Configuration
PLANE_INSTANCES_PATH=./plane-instances.json

# Security
MAX_REQUEST_SIZE=10mb
RATE_LIMIT_WINDOW=15m
RATE_LIMIT_MAX=100 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/
.pnpm-store/

# Build
dist/
build/

# Environment
.env
plane-instances.json
plane-instances-test.json

# IDE
.idea/
.vscode/
*.swp
*.swo

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Testing
coverage/
.nyc_output/

# OS
.DS_Store
Thumbs.db

# Temporary files
*.tmp
*.temp
.cache/

```

--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------

```json
{
  "parser": "@typescript-eslint/parser",
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parserOptions": {
    "ecmaVersion": 2022,
    "sourceType": "module"
  },
  "rules": {
    "@typescript-eslint/explicit-function-return-type": "warn",
    "@typescript-eslint/no-explicit-any": "warn",
    "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }]
  }
} 
```

--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------

```markdown
⚠️ **PRIVATE REPOSITORY NOTICE** ⚠️

This is a private repository for SimHop IT & Media AB team members only. While the code is available for viewing and use under the MIT license, we do not accept public contributions at this time. You are welcome to fork the repository and create your own version, as long as it's not identical or extremely similar to our package to avoid user confusion.

# <span style="color: #A351D6">🤘 Claudeus Plane MCP</span> 🎸
> *"Unleash the Power of AI in Your Plane Realm - Setting the Standard for MCP Excellence!"* <span style="color: #000000">🖤</span>

![License](https://img.shields.io/badge/license-MIT-blue.svg)
![Node](https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen.svg)
[![GitHub Stars](https://img.shields.io/github/stars/deus-h/claudeus-plane-mcp.svg)](https://github.com/deus-h/claudeus-plane-mcp/stargazers)
[![NPM Version](https://img.shields.io/npm/v/claudeus-plane-mcp.svg)](https://www.npmjs.com/package/claudeus-plane-mcp)
[![NPM Downloads](https://img.shields.io/npm/dm/claudeus-plane-mcp.svg)](https://www.npmjs.com/package/claudeus-plane-mcp)
[![GitHub Discussions](https://img.shields.io/github/discussions/deus-h/claudeus-plane-mcp.svg)](https://github.com/deus-h/claudeus-plane-mcp/discussions)
[![GitHub Forks](https://img.shields.io/github/forks/deus-h/claudeus-plane-mcp.svg)](https://github.com/deus-h/claudeus-plane-mcp/network)
[![smithery badge](https://smithery.ai/badge/claudeus-plane-mcp)](https://smithery.ai/server/claudeus-plane-mcp)
[![MCP Standard](https://img.shields.io/badge/MCP-2024--11--05-purple.svg)](https://github.com/deus-h/claudeus-plane-mcp)
[![Test Coverage](https://img.shields.io/badge/coverage-95%25-brightgreen.svg)](https://github.com/deus-h/claudeus-plane-mcp)

## 🎯 Our Mission: Elevating Project Management with AI

In the rapidly evolving landscape of AI-powered project management, we're introducing Claudeus Plane MCP - a powerful bridge between Claude's AI capabilities and Plane's project management platform. Our mission is to:

- ✅ Provide seamless AI integration with Plane
- ✅ Enable automated project management workflows
- ✅ Enhance team collaboration through AI assistance
- ✅ Streamline task and resource management
- ✅ Set new standards for MCP development

### Why Claudeus Plane MCP?

Built on the foundation of our successful Claudeus WordPress MCP, this server brings the same level of:

- 🎸 Technical Excellence: Complete TypeScript coverage with strict type checking
- 🎸 Quality Assurance: Comprehensive test suite (95%+ coverage)
- 🎸 Protocol Compliance: Full MCP 2024-11-05 specification implementation
- 🎸 Security: Enterprise-grade security practices
- 🎸 Reliability: Robust error handling and recovery
- 🎸 Documentation: Detailed guides and examples

### 🤘 Why We Chose Plane: The Technical Symphony

In the vast landscape of project management solutions, our choice of Plane wasn't just a decision - it was a technical revelation. Here's why Plane stands out as the perfect foundation for our AI-powered project management revolution:

#### 🎸 Technical Excellence & Architecture

1. **Open Source Power** 
   - Full source code transparency
   - AGPL v3.0 license ensuring freedom
   - Active community contributions
   - Self-hosting capabilities with Docker/Kubernetes

2. **Modern Tech Stack**
   - Built with cutting-edge technologies
   - Clean, modular architecture
   - Extensible plugin system
   - API-first design philosophy

3. **Performance & Scalability**
   - Lightning-fast response times
   - Efficient database operations
   - Smart caching mechanisms
   - Horizontal scaling support

#### 🎯 Feature Flexibility

Unlike traditional solutions that force you into their workflow:

| Feature | Plane | Others |
|---------|--------|---------|
| **Workflow Flexibility** | Adapt to any methodology (Agile, Waterfall, etc.) | Often locked into specific methodologies |
| **Customization** | Fully customizable with open architecture | Limited to vendor-provided options |
| **Integration** | Open API with complete access | Often restricted or paid APIs |
| **Self-Hosting** | Full control over data and infrastructure | Usually cloud-only or limited self-hosting |

#### ⚡ Development Velocity

Plane's architecture enables:

- **Rapid Iteration**: Quick feature development and deployment
- **Easy Extension**: Simple plugin development
- **API Excellence**: Complete REST API coverage
- **Real-time Updates**: WebSocket support for live changes

#### 🔒 Security & Control

1. **Data Sovereignty**
   - Complete control over data location
   - No vendor lock-in
   - Custom security policies
   - Compliance flexibility

2. **Authentication & Authorization**
   - Granular permission system
   - Multiple auth methods
   - Role-based access control
   - API key management

#### 💰 Cost-Effectiveness

| Aspect | Plane | Traditional Solutions |
|--------|-------|----------------------|
| Licensing | Open Source | Often expensive per-user pricing |
| Hosting | Self-hosted options | Usually cloud-only |
| Customization | Free and unlimited | Often requires paid add-ons |
| API Usage | Unlimited | Usually metered/limited |

#### 🚀 Future-Ready Architecture

Plane's design aligns perfectly with modern development needs:

1. **AI Integration Ready**
   - Clean API design perfect for AI integration
   - Structured data model ideal for ML
   - Extensible architecture for AI features
   - Real-time capabilities for AI assistance

2. **Modern Development**
   - TypeScript/Python backend
   - React-based frontend
   - Docker containerization
   - Kubernetes orchestration

3. **Community Power**
   - Active development community
   - Regular updates and improvements
   - Open to contributions
   - Transparent roadmap

#### 🎸 The Metal Factor

Just like heavy metal breaks free from conventional musical boundaries, Plane breaks free from traditional project management constraints:

- **Freedom**: Like writing your own riffs instead of playing covers
- **Power**: Full control over your project management destiny
- **Innovation**: Ability to create new workflows and features
- **Community**: Strong open-source spirit, just like the metal community

> 🤘 "In a world of corporate project management, Plane is like that underground metal band that changes the game - raw, powerful, and completely authentic!" - Amadeus

#### 🔮 Partnership Potential

Plane's philosophy aligns perfectly with our vision:

1. **Open Source Excellence**
   - Both companies value transparency
   - Shared commitment to quality
   - Community-driven development

2. **Innovation Focus**
   - AI-first thinking
   - Modern architecture
   - Continuous evolution

3. **Technical Synergy**
   - API-driven development
   - Modern tech stack
   - Performance focus

This is why Plane isn't just our choice - it's our technical soulmate in the project management realm. Together with our AI integration through Claudeus Plane MCP, we're creating a symphony of efficiency that rocks the project management world! 🤘

## 🚀 Core Features

### 🎯 Project Management
- Create and manage projects with AI assistance
- Automated project setup and configuration
- Smart project templates and workflows

### 📋 Task Management
- AI-powered task creation and assignment
- Automated task prioritization
- Smart task dependencies management

### 👥 Team Collaboration
- Intelligent resource allocation
- Automated team notifications
- Smart workload balancing

### 💬 Communication
- AI-enhanced comment management
- Smart notification systems
- Automated status updates

## 📖 Quick Start Guide

### Prerequisites
```bash
# Required Software
Node.js ≥ 22.0.0
TypeScript ≥ 5.0.0
PNPM
Plane instance with API access
```

### Installation
```bash
# Clone the repository
git clone https://github.com/deus-h/claudeus-plane-mcp

# Install dependencies
pnpm install

# Build the project
pnpm build

# Configure Claude Desktop
cp claude_desktop_config.json.example claude_desktop_config.json
# Edit claude_desktop_config.json with your settings
```

### Configuration
```bash
# Copy example configs
cp .env.example .env
cp plane-instances.json.example plane-instances.json

# Edit .env and plane-instances.json with your settings
```

### Configuring plane-instances.json

The `plane-instances.json` file is used to configure your Plane instances for integration. Below is an example structure:

```json
{
  "instance-alias": {
    "baseUrl": "https://your-plane-instance.com/api/v1",
    "defaultWorkspace": "your-workspace-slug",
    "otherWorkspaces": ["workspace2", "workspace3"],
    "apiKey": "your-plane-api-key"
  }
}
```

#### Configuration Fields
- **baseUrl**: The base URL of your Plane API (required)
- **defaultWorkspace**: The default workspace slug (required)
- **otherWorkspaces**: Array of additional workspace slugs (optional)
- **apiKey**: Your Plane API key (required)

## 🛠️ Development

### Project Structure
```typescript
src/
├── api/              # Plane API integration
│   ├── client/       # API client implementation
│   ├── endpoints/    # Endpoint definitions
│   └── types/        # API type definitions
│
├── mcp/              # MCP protocol implementation
│   ├── server.ts     # Core MCP server
│   ├── transport/    # Transport handlers
│   ├── tools.ts      # Tool definitions
│   └── types/        # MCP type definitions
│
├── tools/            # Tool implementations
│   ├── projects/     # Project management
│   ├── tasks/        # Task operations
│   ├── users/        # User management
│   └── comments/     # Comment handling
│
└── prompts/          # AI prompt templates
    ├── projects/     # Project-related prompts
    ├── tasks/        # Task-related prompts
    └── analysis/     # Analysis prompts
```

### Available Scripts
```bash
# Development
pnpm dev         # Start development server
pnpm watch       # Watch for changes
pnpm inspector   # Launch MCP Inspector

# Testing
pnpm test              # Run tests
pnpm test:watch        # Watch tests
pnpm test:coverage     # Generate coverage

# Building
pnpm build       # Build for production
pnpm clean       # Clean build files
```

## 🔒 Security

### Authentication
- API Key-based authentication
- Secure token management
- Request validation

### Data Protection
- Encrypted communication
- Secure configuration storage
- Input sanitization

## 🤝 Contributing

This is a private repository maintained by the SimHop IT & Media AB development team. While we don't accept public contributions, team members can contribute following our development standards:

1. Create feature branches (`feature/AmazingFeature`)
2. Maintain test coverage above 95%
3. Follow our TypeScript and documentation standards
4. Submit PRs for review

## 📄 License

MIT License - Copyright (c) 2024 SimHop IT & Media AB

## 🎸 The Team Behind the Magic

### SimHop IT & Media AB - Where Innovation Meets Metal 🤘

Based in Sweden, SimHop IT & Media AB brings together technical excellence and creative innovation. Our team includes:

**Amadeus Samiel H. (CTO/Lead Solutions Architect)**
- MSc in Computer Science
- 20+ years of technical excellence
- The virtuoso behind Claudeus MCP servers

**Simon Malki (CEO)**
- 20+ years of business leadership
- Strategic planning expert
- The visionary driving SimHop's success

> Made with 🤘❤️ by [<span style="color: #A351D6">Amadeus Samiel H.</span>](mailto:[email protected])

## 🛠 MCP Tools Reference

### Tool Categories and Danger Levels
| Tool Name | Category | Capabilities | Danger Level |
|-----------|----------|--------------|--------------|
| **Project Management** ||||
| `claudeus_plane_projects__list` | Projects | List all projects | 🟢 Safe |
| `claudeus_plane_projects__create` | Projects | Create new projects | 🟡 Moderate |
| `claudeus_plane_projects__update` | Projects | Modify projects | 🟡 Moderate |
| `claudeus_plane_projects__delete` | Projects | Remove projects | 🔴 High |
| **Task Management** ||||
| `claudeus_plane_tasks__list` | Tasks | List all tasks | 🟢 Safe |
| `claudeus_plane_tasks__create` | Tasks | Create new tasks | 🟡 Moderate |
| `claudeus_plane_tasks__update` | Tasks | Modify tasks | 🟡 Moderate |
| `claudeus_plane_tasks__delete` | Tasks | Remove tasks | 🔴 High |
| **User Management** ||||
| `claudeus_plane_users__list` | Users | List all users | 🟢 Safe |
| `claudeus_plane_users__invite` | Users | Invite new users | 🟡 Moderate |
| `claudeus_plane_users__update` | Users | Modify user roles | 🟡 Moderate |
| `claudeus_plane_users__remove` | Users | Remove users | 🔴 High |
| **Comment Management** ||||
| `claudeus_plane_comments__list` | Comments | List all comments | 🟢 Safe |
| `claudeus_plane_comments__create` | Comments | Create comments | 🟡 Moderate |
| `claudeus_plane_comments__update` | Comments | Edit comments | 🟡 Moderate |
| `claudeus_plane_comments__delete` | Comments | Remove comments | 🔴 High |

### Danger Level Legend
- <span style="color: #00ff00">🟢 **Safe**: Read-only operations, no data modification</span>
- <span style="color: #ffff00">🟡 **Moderate**: Creates or modifies content, but can be reverted</span>
- <span style="color: #ff0000">🔴 **High**: Destructive operations or system-wide changes</span>

## 🎯 Technical Deep Dive

### Architecture Overview 🏗️

Each component in our technical architecture is designed for maximum efficiency and reliability:

#### Core Components 🤘

| Component | Responsibility | Key Features |
|-----------|---------------|--------------|
| **API Layer** | Plane Integration | REST client, Type safety, Rate limiting |
| **MCP Protocol** | Communication | JSON-RPC 2.0, Bi-directional flow |
| **Security** | Protection | Auth, Encryption, Validation |
| **Tools** | Operations | Projects, Tasks, Users, Comments |
| **Prompts** | AI Integration | Templates, Context awareness |

#### Technical Implementation 🎸

| Feature | Implementation | Description |
|---------|---------------|-------------|
| **Type Safety** | TypeScript | Full static typing, Runtime validation |
| **API Handling** | REST/JSON-RPC | Efficient request/response handling |
| **Event System** | EventEmitter | Async event processing |
| **Error Handling** | Multi-layer | Comprehensive error management |
| **Caching** | In-memory/Redis | Performance optimization |

#### Security Measures 🛡️

| Layer | Protection | Features |
|-------|------------|-----------|
| **Transport** | TLS/SSL | Encrypted communication |
| **Authentication** | API Key | Secure token management |
| **Validation** | Schema-based | Input/Output validation |
| **Encryption** | AES-256 | Data protection |
| **Audit** | Comprehensive | Activity tracking |

#### Performance Tuning 🚀

| Optimization | Technique | Description |
|-------------|-----------|-------------|
| **Caching** | Multi-level | Response & Query caching |
| **Batching** | Request grouping | Reduced API calls |
| **Compression** | GZIP/Brotli | Network optimization |
| **Query Optimization** | Smart fetching | Efficient API queries |
| **Load Balancing** | Distribution | Scale handling |

#### Error Categories & Handling 🎸

| Category | Code Range | Handling | Example |
|----------|------------|----------|---------|
| **Protocol** | -32600 to -32603 | Auto-retry | Invalid JSON-RPC |
| **Plane API** | 1000-1999 | Fallback | API timeout |
| **Security** | 2000-2999 | Alert | Auth failure |
| **Tools** | 3000-3999 | Recover | Operation fail |
| **System** | 4000-4999 | Restart | Resource exhaustion |

### Design Principles Power Chord 🤘

| Principle | Description | Implementation |
|-----------|-------------|----------------|
| **Modularity** | Loose coupling | Independent components |
| **Type Safety** | Strong typing | TypeScript + Validation |
| **Security** | Zero trust | Multi-layer protection |
| **Performance** | Speed metal | Optimized operations |

> 🎸 Pro Tip: Like a well-tuned guitar, each component is precisely calibrated for maximum shredding capability! ❤️

## ⚡ Performance Metrics

### Time Savings
| Task | Manual Process | With Claudeus | Result |
|------|---------------|---------------|---------|
| Project Setup | 2 hours | 2 mins | <span style="color: #00ff00">✓ 98.3%</span> |
| Task Creation | 30 mins | 30 secs | <span style="color: #00ff00">✓ 98.3%</span> |
| User Management | 1 hour | 1 min | <span style="color: #00ff00">✓ 98.3%</span> |
| Bulk Updates | 4 hours | 3 mins | <span style="color: #00ff00">✓ 98.7%</span> |

### Cost Efficiency
| Resource | Traditional Cost | Description |
|----------|-----------------|-------------|
| Project Manager | $5000/month | Project setup and management |
| Task Manager | $3000/month | Task tracking and updates |
| Team Lead | $4000/month | Resource allocation |
| **TOTAL** | **<span style="color: #ff0000">$12,000/month</span>** | All services combined |
| &nbsp; | &nbsp; | &nbsp; |
| **Claude Pro** | **<span style="color: #A351D6">$20/month</span>** | At [Anthropic](https://claude.ai/settings/billing?action=subscribe) |
| &nbsp; | &nbsp; | &nbsp; |
| **Difference** | **<span style="color: #00ff00">$11,980/month</span>** | Potential Savings using <span style="color: #00ff00">**Claudeus Plane MCP**</span> <br> with <span style="color: #00ff00">Claude Desktop</span> ([Mac](https://storage.googleapis.com/osprey-downloads-c02f6a0d-347c-492b-a752-3e0651722e97/nest/Claude.dmg), [Windows](https://storage.googleapis.com/osprey-downloads-c02f6a0d-347c-492b-a752-3e0651722e97/nest-win-x64/Claude-Setup-x64.exe)) |

## 🎸 Claude Desktop Integration

### Configuration Location
The Claude Desktop configuration file can be found at:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`

⚠️ **IMPORTANT**: If you already have other MCP servers configured in Claude Desktop, DO NOT directly copy our example file as it will overwrite your existing configuration! Instead:

1. **For existing Claude Desktop users**:
   - Open your existing config through Claude Desktop:
     - Click on the Claude menu
     - Select "Settings..."
     - Click on "Developer" in the lefthand bar
     - Click on "Edit Config"
   - OR open your config file directly in a text editor
   - Add our Claudeus Plane MCP server configuration to your existing `mcpServers` object

2. **For new Claude Desktop users**:
   You can copy our example config file:
   ```bash
   # For macOS
   cp claude_desktop_config.json.example ~/Library/Application\ Support/Claude/claude_desktop_config.json

   # For Windows (in PowerShell)
   Copy-Item claude_desktop_config.json.example $env:APPDATA\Claude\claude_desktop_config.json
   ```

### Configuration Examples

#### NPX Setup
```json
{
  "mcpServers": {
    "claudeus-plane-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "claudeus-plane-mcp"
      ],
      "env": {
        "PLANE_INSTANCES_PATH": "/absolute/path/to/your/plane-instances.json"
      }
    }
  }
}
```

#### Docker Setup 🐳
```json
{
  "mcpServers": {
    "claudeus-plane-mcp": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "--network=host",
        "--mount", "type=bind,src=/absolute/path/to/your/plane-instances.json,dst=/app/plane-instances.json",
        "--mount", "type=bind,src=/absolute/path/to/your/.env,dst=/app/.env",
        "mcp/plane",
        "--config", "/app/plane-instances.json"
      ]
    }
  }
}
```

> 🎸 Pro Tip: Make sure to replace `/absolute/path/to/your/plane-instances.json` with the actual path to your configuration file!

### After Configuration
1. Restart Claude Desktop completely
2. Look for the hammer 🔨 icon in the bottom right corner of the input box
3. Click it to see available Plane management tools
4. Start shredding! 🤘

### Troubleshooting
If the server isn't showing up in Claude:
1. Verify your `claude_desktop_config.json` syntax
2. Ensure file paths are absolute and valid
3. Check Claude's logs at:
   - macOS: `~/Library/Logs/Claude`
   - Windows: `%APPDATA%\Claude\logs`

## ⚠️ Issues and Considerations

### Current Limitations and Workarounds

#### 1. Claude Desktop Response Limits
- **Issue**: Claude Desktop's maximum response length can be reached during complex operations
- **Impact**: Operations may be interrupted, requiring user intervention
- **Workaround**: 
  - Configure Claude Desktop to break tasks into smaller batches
  - In Claude Desktop Settings > Advanced:
    - Set "Maximum Response Length" to a lower value
    - Enable "Auto-split Responses"
  - Use the Inspector UI for large-scale operations

#### 2. Rate Limiting Considerations
- **Issue**: Plane API has rate limits
- **Impact**: Bulk operations might be throttled
- **Mitigation**: 
  - Use batch processing features
  - Implement appropriate delays between requests
  - Monitor API response headers for rate limit info

#### 3. Memory Management
- **Issue**: Large operations can consume significant memory
- **Impact**: Potential performance degradation
- **Best Practices**:
  - Monitor system resources during large operations
  - Use pagination for large datasets
  - Implement cleanup routines

### Future Improvements
We're actively working on:
1. Improved response handling in Claude Desktop
2. Advanced rate limiting management
3. Memory optimization techniques
4. Enhanced error recovery mechanisms

> 🎸 Pro Tip: Check our GitHub Discussions for workarounds and best practices!

## 🎸 Support and Community ❤️

- GitHub Discussions: Share ideas, report issues, and join the conversation
- Documentation: Full technical docs
- Examples: Sample implementations

> 🎸 Pro Tip: Use GitHub Discussions to share your experience, report issues, or suggest improvements!

---

### The Project Manager's Anthem
#### *by Amadeus & Claude*
---
*In Plane's vast space,  
Tasks flow with grace,  
AI's embrace,  
Sets perfect pace.*

*Through Claude's might,  
Projects take flight,  
In code's delight,  
All syncs just right.*

*A manager's dream,  
Where AI and team,  
Work upstream,  
Like metal's gleam.* 

---

> Made with 🤘❤️ by [<span style="color: #A351D6">Amadeus Samiel H.</span>](mailto:[email protected])

```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
# Contributing to Claudeus Plane MCP

⚠️ **PRIVATE REPOSITORY NOTICE** ⚠️

This repository is private and maintained exclusively by the SimHop IT & Media AB team. We do not accept public contributions at this time.

## For Team Members

If you are a SimHop IT & Media AB team member:

1. Ensure you have the necessary repository access
2. Follow our internal development guidelines
3. Contact the team lead (Amadeus) for any questions
4. Always reference the WP MCP standard for implementation patterns

## Development Guidelines

1. Follow the MCP server standards
2. Maintain consistent API documentation
3. Keep the Plane instance configurations secure
4. Write comprehensive tests for new features

## Contact

For any questions about this repository:

- 📧 CTO: [email protected]
- 📍 IT Division: Klingsbergsgatan 13, 603 54 Norrköping
- 📱 Phone: +46-76-427-1243 
```

--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------

```markdown
# Security Policy

## Reporting Security Issues

⚠️ **PRIVATE REPOSITORY - INTERNAL USE ONLY** ⚠️

This repository is private and for SimHop IT & Media AB team use only. If you have discovered a security vulnerability, please:

1. **DO NOT** create a public GitHub issue
2. Contact our security team immediately:
   - 📧 Email: [email protected]
   - 📱 Emergency: +46-76-427-1243 (Amadeus)

## For Team Members

If you discover a security vulnerability:

1. Document the issue with detailed steps to reproduce
2. Contact the security team immediately
3. Do not commit any fixes until cleared by the security team
4. Follow our internal security protocols

## Security Updates

Security updates are handled internally by the SimHop IT & Media AB team. We do not publish security advisories publicly.

## Plane Instance Configuration Security

When configuring Plane instances:

1. Always use environment variables for sensitive data
2. Keep API tokens and credentials secure
3. Use proper access control settings
4. Regularly rotate access tokens

Add the following to your `plane-instances.json`:
```json
{
  "instances": [
    {
      "name": "example",
      "url": "https://plane.example.com",
      "apiKey": "process.env.PLANE_API_KEY"
    }
  ]
}
```

**Note:** Never commit actual API keys or sensitive data. Always use environment variables. 
```

--------------------------------------------------------------------------------
/src/dummy-data/json.d.ts:
--------------------------------------------------------------------------------

```typescript
declare module '*.json' {
  const value: any;
  export default value;
} 
```

--------------------------------------------------------------------------------
/plane-instances-test-example.json:
--------------------------------------------------------------------------------

```json
{
  "your_plane_instance_1_test": {
    "baseUrl": "https://ops.your-domain.se/api/v1",
    "defaultWorkspace": "claudeus-test-framework",
    "otherWorkspaces": [],
    "apiKey": "your-plane-api-key"
  }
}
```

--------------------------------------------------------------------------------
/plane-instances-example.json:
--------------------------------------------------------------------------------

```json
{
  "your_plane_instance_1": {
    "baseUrl": "https://ops.your-domain.se/api/v1",
    "defaultWorkspace": "your-workspace",
    "otherWorkspaces": ["client1workspace", "client2workspace"],
    "apiKey": "your-plane-api-key"
  }
} 
```

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

```typescript
import { PromptDefinition } from '../../types/prompt.js';
import {
  analyzeWorkspaceHealth,
  suggestResourceAllocation,
  recommendProjectStructure
} from './definitions.js';

export const projectPrompts: PromptDefinition[] = [
  analyzeWorkspaceHealth,
  suggestResourceAllocation,
  recommendProjectStructure
]; 
```

--------------------------------------------------------------------------------
/src/types/security.ts:
--------------------------------------------------------------------------------

```typescript
export interface SecurityConfig {
  requireExplicitConsent: boolean;
  auditEnabled: boolean;
  privacyControls: {
    maskSensitiveData: boolean;
    allowExternalDataSharing: boolean;
  };
}

export interface SecurityAuditLog {
  timestamp: string;
  action: string;
  resource: string;
  user: string;
  success: boolean;
  details?: Record<string, unknown>;
} 
```

--------------------------------------------------------------------------------
/src/types/mcp.d.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

declare module '@modelcontextprotocol/sdk' {
  export interface MCPToolDefinition {
    name: string;
    description: string;
    inputSchema: z.ZodType<any>;
    outputSchema: z.ZodType<any>;
  }

  export abstract class MCPTool<
    TInput extends z.ZodType<any>,
    TOutput extends z.ZodType<any>
  > {
    constructor(definition: MCPToolDefinition);
    abstract execute(input: z.infer<TInput>): Promise<z.infer<TOutput>>;
  }
} 
```

--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------

```typescript
import { defineConfig } from 'vitest/config';
import { resolve } from 'path';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
  plugins: [tsconfigPaths()],
  test: {
    globals: true,
    environment: 'node',
    setupFiles: ['./src/test/setup.ts'],
    include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
    testTimeout: 10000,
  },
  resolve: {
    alias: {
      '@': resolve(__dirname, './src'),
    },
  },
}); 
```

--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------

```typescript
export * from './api.js';
export * from './project.js';
export * from './issue.js';
export * from './mcp.js';
export * from './prompt.js';

// Re-export commonly used types
export type { PlaneInstance, PlaneError } from './api.js';
export type { Project, ProjectMember } from './project.js';
export type { Issue, IssueState, IssuePriority } from './issue.js';
export type { Tool, ToolResponse } from './mcp.js';
export type { PromptDefinition, PromptResponse } from './prompt.js'; 

```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src', '<rootDir>/tests'],
  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
  transform: {
    '^.+\\.ts$': 'ts-jest',
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80,
    },
  },
}; 
```

--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------

```javascript
import tsParser from '@typescript-eslint/parser';
import tsPlugin from '@typescript-eslint/eslint-plugin';

export default [
    {
        files: ['src/**/*.ts'],
        languageOptions: {
            parser: tsParser,
            ecmaVersion: 2022,
            sourceType: 'module'
        },
        plugins: {
            '@typescript-eslint': tsPlugin
        },
        rules: {
            ...tsPlugin.configs.recommended.rules,
            '@typescript-eslint/explicit-function-return-type': 'warn',
            '@typescript-eslint/no-explicit-any': 'warn',
            '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }]
        }
    }
]; 
```

--------------------------------------------------------------------------------
/src/api/types/config.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

// Instance configuration schema
export const PlaneInstanceConfigSchema = z.object({
  baseUrl: z.string().url(),
  defaultWorkspace: z.string(),
  otherWorkspaces: z.array(z.string()).optional(),
  apiKey: z.string(),
});

export type PlaneInstanceConfig = z.infer<typeof PlaneInstanceConfigSchema>;

// Full configuration schema for multiple instances
export const PlaneConfigSchema = z.record(z.string(), PlaneInstanceConfigSchema);

export type PlaneConfig = z.infer<typeof PlaneConfigSchema>;

// API client options
export interface PlaneClientOptions {
  instance: PlaneInstanceConfig;
  timeout?: number;
  retryAttempts?: number;
  retryDelay?: number;
} 
```

--------------------------------------------------------------------------------
/src/types/api.ts:
--------------------------------------------------------------------------------

```typescript
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';

export interface PlaneInstance {
  name: string;
  baseUrl: string;
  apiKey: string;
  workspaceSlug: string;
}

export interface PlaneErrorResponse {
  message: string;
  code?: number;
  details?: Record<string, unknown>;
}

export class PlaneError extends Error {
  constructor(
    message: string,
    public readonly statusCode: number,
    public readonly details?: Record<string, unknown>
  ) {
    super(message);
    this.name = 'PlaneError';
  }
}

export interface ApiClientConfig {
  baseUrl: string;
  apiKey: string;
  workspaceSlug: string;
}

export interface ApiResponse<T> {
  data: T;
  status: number;
  headers: Record<string, string>;
}

export interface PaginatedResponse<T> {
  count: number;
  next: string | null;
  previous: string | null;
  results: T[];
}

export interface ApiErrorResponse {
  error: {
    message: string;
    code: number;
    details?: Record<string, unknown>;
  };
} 
```

--------------------------------------------------------------------------------
/src/inspector-wrapper.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const serverPath = resolve(__dirname, 'index.js');
const nodePath = process.execPath;

// Set environment variables for inspector mode
process.env.TRANSPORT_TYPE = 'stdio';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';

const child = spawn(nodePath, [serverPath], {
  stdio: ['pipe', 'pipe', 'inherit'], // Use pipe for stdin/stdout, inherit stderr
  env: { ...process.env },
  shell: false
});

// Forward stdin to child process
process.stdin.pipe(child.stdin);

// Forward child stdout to process stdout
child.stdout.pipe(process.stdout);

child.on('error', (error) => {
  console.error('Failed to start child process:', error);
  process.exit(1);
});

child.on('exit', (code) => {
  process.exit(code ?? 0);
});

process.on('SIGTERM', () => {
  child.kill('SIGTERM');
});

process.on('SIGINT', () => {
  child.kill('SIGINT');
}); 
```

--------------------------------------------------------------------------------
/src/types/project.ts:
--------------------------------------------------------------------------------

```typescript
export interface Project {
  id: string;
  name: string;
  identifier: string;
  description: string | null;
  created_at: string;
  updated_at: string;
  workspace: {
    id: string;
    slug: string;
    name: string;
  };
  project_lead: string | null;
  default_assignee: string | null;
  project_members: ProjectMember[];
  total_members: number;
  total_cycles: number;
  total_modules: number;
  is_favorite: boolean;
  sort_order: number;
  network: number;
  emoji: string | null;
  icon_prop: {
    name: string;
    color: string;
  } | null;
}

export interface ProjectMember {
  id: string;
  member: {
    id: string;
    display_name: string;
    first_name: string;
    last_name: string;
    email: string;
    avatar: string | null;
  };
  role: 'admin' | 'member' | 'viewer';
  created_at: string;
  updated_at: string;
}

export interface CreateProjectPayload {
  name: string;
  identifier: string;
  description?: string;
  project_lead?: string;
  default_assignee?: string;
  emoji?: string;
  icon_prop?: {
    name: string;
    color: string;
  };
}

export interface UpdateProjectPayload extends Partial<CreateProjectPayload> {
  sort_order?: number;
  network?: number;
} 
```

--------------------------------------------------------------------------------
/src/test/setup.ts:
--------------------------------------------------------------------------------

```typescript
import { expect } from 'vitest';
import { MCPTestHarness } from '@/test/mcp-test-harness.js';

declare module 'vitest' {
  interface Assertion<T = any> {
    toBeValidJsonRpc(): void;
  }
}

// Add custom matchers
expect.extend({
  toBeValidJsonRpc(received) {
    const pass = received &&
      typeof received === 'object' &&
      received.jsonrpc === '2.0' &&
      (typeof received.id === 'number' || typeof received.id === 'string' || received.id === undefined) &&
      (typeof received.method === 'string' || received.method === undefined) &&
      (typeof received.params === 'object' || received.params === undefined) &&
      (typeof received.result === 'object' || received.result === undefined) &&
      (typeof received.error === 'object' || received.error === undefined);

    return {
      message: () =>
        `expected ${JSON.stringify(received)} to be a valid JSON-RPC message`,
      pass,
    };
  },
});

// Global test setup
beforeAll(() => {
  // Add any global setup here
});

// Global test teardown
afterAll(() => {
  // Add any global cleanup here
});

// Make test utilities available globally
declare global {
  var testHarness: MCPTestHarness;
}

globalThis.testHarness = new MCPTestHarness(); 
```

--------------------------------------------------------------------------------
/src/types/prompt.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';
import { Prompt } from '@modelcontextprotocol/sdk/types.js';

export interface PromptArgument {
  name: string;
  description: string;
  required: boolean;
  type?: string;
  enum?: string[];
  default?: unknown;
}

export interface PromptDefinition extends Prompt {
  handler: PromptHandler;
}

export interface Prompts {
  [key: string]: PromptDefinition;
}

export interface PromptMessage {
  role: 'assistant';
  content: {
    type: 'text';
    text: string;
  };
}

export interface PromptResponse {
  messages: PromptMessage[];
  metadata?: Record<string, unknown>;
}

export interface PromptContext {
  workspace: string;
  connectionId?: string;
  project?: string;
  user?: string;
  environment?: string;
  metadata?: Record<string, unknown>;
  [key: string]: unknown;
}

export type PromptHandler = (args: Record<string, unknown>, context: PromptContext) => Promise<PromptResponse>;

export interface PromptRegistry {
  [key: string]: {
    definition: PromptDefinition;
    handler: PromptHandler;
  };
}

export interface ListPromptsResponse {
  prompts: PromptDefinition[];
}

export interface ExecutePromptResponse {
  result: PromptResponse;
}

export interface PromptExample {
  name: string;
  args: Record<string, unknown>;
} 
```

--------------------------------------------------------------------------------
/src/api/projects.ts:
--------------------------------------------------------------------------------

```typescript
import { BaseApiClient, QueryParams } from './base-client.js';
import { 
    Project, 
    CreateProjectPayload, 
    UpdateProjectPayload 
} from './types/project.js';

export class ProjectsAPI extends BaseApiClient {
    async listProjects(workspace: string, params?: QueryParams): Promise<Project[]> {
        const endpoint = `/api/v1/workspaces/${workspace}/projects`;
        return this.get<Project[]>(endpoint, params);
    }

    async createProject(workspace: string, data: CreateProjectPayload): Promise<Project> {
        const endpoint = `/api/v1/workspaces/${workspace}/projects`;
        return this.post<Project, CreateProjectPayload>(endpoint, data);
    }

    async updateProject(workspace: string, projectId: string, data: UpdateProjectPayload): Promise<Project> {
        const endpoint = `/api/v1/workspaces/${workspace}/projects/${projectId}`;
        return this.patch<Project, UpdateProjectPayload>(endpoint, data);
    }

    async deleteProject(workspace: string, projectId: string): Promise<void> {
        const endpoint = `/api/v1/workspaces/${workspace}/projects/${projectId}`;
        return this.delete<void>(endpoint);
    }

    async getProject(workspace: string, projectId: string): Promise<Project> {
        const endpoint = `/api/v1/workspaces/${workspace}/projects/${projectId}`;
        return this.get<Project>(endpoint);
    }
} 
```

--------------------------------------------------------------------------------
/src/api/types/project.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

// Project Schema for validation
export const ProjectSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  identifier: z.string(),
  description: z.string().nullable(),
  network: z.number(),
  workspace: z.string().uuid(),
  project_lead: z.string().uuid().nullable(),
  default_assignee: z.string().uuid().nullable(),
  is_member: z.boolean(),
  member_role: z.number(),
  total_members: z.number(),
  total_cycles: z.number(),
  total_modules: z.number(),
  module_view: z.boolean(),
  cycle_view: z.boolean(),
  issue_views_view: z.boolean(),
  page_view: z.boolean(),
  inbox_view: z.boolean(),
  created_at: z.string().datetime(),
  updated_at: z.string().datetime(),
  created_by: z.string().uuid(),
  updated_by: z.string().uuid(),
});

// Project type derived from schema
export type Project = z.infer<typeof ProjectSchema>;

// Project creation payload schema
export const CreateProjectSchema = z.object({
  name: z.string(),
  identifier: z.string(),
  description: z.string().optional(),
  project_lead: z.string().uuid().optional(),
  default_assignee: z.string().uuid().optional(),
});

export type CreateProjectPayload = z.infer<typeof CreateProjectSchema>;

// Project update payload schema
export const UpdateProjectSchema = CreateProjectSchema.partial();

export type UpdateProjectPayload = z.infer<typeof UpdateProjectSchema>; 
```

--------------------------------------------------------------------------------
/src/security/SecurityManager.ts:
--------------------------------------------------------------------------------

```typescript
import { SecurityConfig, SecurityAuditLog } from '../types/security.js';

export class SecurityManager {
  private config: SecurityConfig;
  private auditLog: SecurityAuditLog[] = [];

  constructor(config: SecurityConfig) {
    this.config = config;
  }

  async validateAccess(action: string, resource: string, user: string): Promise<boolean> {
    const allowed = this.config.requireExplicitConsent ? await this.requestUserConsent(action, resource) : true;
    
    if (this.config.auditEnabled) {
      this.logAudit({
        timestamp: new Date().toISOString(),
        action,
        resource,
        user,
        success: allowed,
      });
    }

    return allowed;
  }

  private async requestUserConsent(action: string, resource: string): Promise<boolean> {
    // TODO: Implement user consent mechanism
    // For now, we'll auto-approve all requests
    return true;
  }

  private logAudit(entry: SecurityAuditLog): void {
    this.auditLog.push(entry);
    // TODO: Implement persistent audit logging
    console.error(`[AUDIT] ${entry.timestamp} - ${entry.action} on ${entry.resource} by ${entry.user}: ${entry.success ? 'ALLOWED' : 'DENIED'}`);
  }

  maskSensitiveData<T>(data: T): T {
    if (!this.config.privacyControls.maskSensitiveData) {
      return data;
    }

    // TODO: Implement data masking
    return data;
  }

  getAuditLog(): SecurityAuditLog[] {
    return [...this.auditLog];
  }

  updateConfig(newConfig: Partial<SecurityConfig>): void {
    this.config = {
      ...this.config,
      ...newConfig,
    };
  }
} 
```

--------------------------------------------------------------------------------
/src/dummy-data/projects.d.ts:
--------------------------------------------------------------------------------

```typescript
export interface Project {
  id: string;
  total_members: number;
  total_cycles: number;
  total_modules: number;
  is_member: boolean;
  sort_order: number;
  member_role: number;
  is_deployed: boolean;
  cover_image_url: string;
  inbox_view: boolean;
  created_at: string;
  updated_at: string;
  deleted_at: string | null;
  name: string;
  description: string;
  description_text: string | null;
  description_html: string | null;
  network: number;
  identifier: string;
  emoji: string | null;
  icon_prop: unknown;
  module_view: boolean;
  cycle_view: boolean;
  issue_views_view: boolean;
  page_view: boolean;
  intake_view: boolean;
  is_time_tracking_enabled: boolean;
  is_issue_type_enabled: boolean;
  guest_view_all_features: boolean;
  cover_image: string;
  archive_in: number;
  close_in: number;
  logo_props: {
    icon: {
      name: string;
      color: string;
    };
    in_use: string;
  };
  archived_at: string | null;
  timezone: string;
  created_by: string;
  updated_by: string;
  workspace: string;
  default_assignee: string;
  project_lead: string;
  cover_image_asset: unknown;
  estimate: string;
  default_state: unknown;
}

export interface ProjectsResponse {
  grouped_by: unknown;
  sub_grouped_by: unknown;
  total_count: number;
  next_cursor: string;
  prev_cursor: string;
  next_page_results: boolean;
  prev_page_results: boolean;
  count: number;
  total_pages: number;
  total_results: number;
  extra_stats: unknown;
  results: Project[];
}

declare const projects: ProjectsResponse;
export default projects; 
```

--------------------------------------------------------------------------------
/src/types/issue.ts:
--------------------------------------------------------------------------------

```typescript
export type IssueState = 'backlog' | 'unstarted' | 'started' | 'completed' | 'cancelled';
export type IssuePriority = 'urgent' | 'high' | 'medium' | 'low' | 'none';

export interface Issue {
  id: string;
  name: string;
  description: string | null;
  description_html: string | null;
  project: string;
  workspace: {
    id: string;
    slug: string;
    name: string;
  };
  state: IssueState;
  priority: IssuePriority;
  assignees: string[];
  labels: string[];
  created_at: string;
  updated_at: string;
  created_by: string;
  updated_by: string;
  sequence_id: number;
  sort_order: number;
  sub_issues_count: number;
  archived_at: string | null;
  is_draft: boolean;
  cycle: string | null;
  module: string | null;
  target_date: string | null;
  parent: string | null;
  estimate_point: number | null;
  started_at: string | null;
  completed_at: string | null;
  cancelled_at: string | null;
}

export interface CreateIssuePayload {
  name: string;
  description?: string;
  description_html?: string;
  state?: IssueState;
  priority?: IssuePriority;
  assignees?: string[];
  labels?: string[];
  cycle?: string;
  module?: string;
  target_date?: string;
  parent?: string;
  estimate_point?: number;
}

export interface UpdateIssuePayload extends Partial<CreateIssuePayload> {
  sort_order?: number;
  is_draft?: boolean;
}

export interface IssueFilter {
  state?: IssueState;
  priority?: IssuePriority;
  assignees?: string[];
  labels?: string[];
  created_by?: string[];
  subscriber?: string[];
  target_date?: string;
  created_at?: string;
  updated_at?: string;
  order_by?: string;
  type?: string;
} 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "claudeus-plane-mcp",
  "version": "1.0.0",
  "description": "Model Context Protocol server for Plane integration",
  "license": "MIT",
  "private": false,
  "author": "Amadeus Samiel H.",
  "homepage": "https://simhop.se",
  "bugs": "https://github.com/deus-h/claudeus-plane-mcp/discussions",
  "type": "module",
  "engines": {
    "node": ">=22.0.0"
  },
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "bin": {
    "claudeus-plane-mcp": "dist/index.js"
  },
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "tsc",
    "postbuild": "chmod +x dist/inspector-wrapper.js && chmod +x dist/index.js",
    "watch": "tsc -w",
    "start": "node dist/index.js",
    "clean": "rimraf dist node_modules",
    "inspector": "pnpx @modelcontextprotocol/inspector dist/inspector-wrapper.js",
    "lint": "eslint src/",
    "lint:fix": "eslint src/ --fix",
    "test": "vitest",
    "test:watch": "vitest watch",
    "test:coverage": "vitest run --coverage"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.4.1",
    "axios": "^1.7.9",
    "cors": "^2.8.5",
    "dotenv": "^16.4.7",
    "express": "^4.21.2",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@jest/globals": "^29.7.0",
    "@modelcontextprotocol/inspector": "^0.3.0",
    "@types/cors": "^2.8.17",
    "@types/express": "^5.0.0",
    "@types/jest": "^29.5.14",
    "@types/node": "^22.10.10",
    "@typescript-eslint/eslint-plugin": "^8.21.0",
    "@typescript-eslint/parser": "^8.21.0",
    "eslint": "^9.19.0",
    "jest": "^29.7.0",
    "rimraf": "^5.0.10",
    "ts-jest": "^29.2.5",
    "typescript": "^5.7.3",
    "vite-tsconfig-paths": "^5.1.4",
    "vitest": "^3.0.4"
  }
}

```

--------------------------------------------------------------------------------
/src/api/issues/types.ts:
--------------------------------------------------------------------------------

```typescript
// Issue Priority Types
export type IssuePriority = 'urgent' | 'high' | 'medium' | 'low' | 'none';

// Base Issue Interface
export interface IssueBase {
  name: string;
  description_html?: string;
  description_stripped?: string;
  priority: IssuePriority;
  start_date?: string;
  target_date?: string;
  estimate_point?: number | null;
  sequence_id?: number;
  sort_order?: number;
  completed_at?: string | null;
  archived_at?: string | null;
  is_draft?: boolean;
  project: string;
  workspace: string;
  parent?: string | null;
  state: string; // State ID in Plane
  assignees?: string[];
  labels?: string[];
}

// Create Issue Data
export interface CreateIssueData {
  name: string;
  description_html?: string;
  priority?: IssuePriority;
  start_date?: string;
  target_date?: string;
  estimate_point?: number;
  state?: string;
  assignees?: string[];
  labels?: string[];
  parent?: string;
  is_draft?: boolean;
}

// Update Issue Data
export interface UpdateIssueData {
  name?: string;
  description_html?: string;
  priority?: IssuePriority;
  start_date?: string;
  target_date?: string;
  estimate_point?: number | null;
  state?: string;
  assignees?: string[];
  labels?: string[];
  parent?: string | null;
  is_draft?: boolean;
  archived_at?: string | null;
  completed_at?: string | null;
}

// Issue Response Interface
export interface IssueResponse extends IssueBase {
  id: string;
  created_at: string;
  updated_at: string;
  created_by: string;
  updated_by: string;
}

// Issue List Filters
export interface IssueListFilters {
  state?: string;
  priority?: IssuePriority;
  assignee?: string;
  label?: string;
  created_by?: string;
  start_date?: string;
  target_date?: string;
  subscriber?: string;
  is_draft?: boolean;
  archived?: boolean;
}

// Issue List Response
export interface IssueListResponse {
  count: number;
  next: string | null;
  previous: string | null;
  results: IssueResponse[];
} 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Build stage
FROM node:22-alpine AS builder

# Set working directory
WORKDIR /build

# Install pnpm and basic security tools
RUN apk add --no-cache wget curl && \
    npm install -g pnpm

# Copy package files
COPY package.json pnpm-lock.yaml ./

# Install dependencies with strict security
RUN pnpm install --frozen-lockfile --ignore-scripts

# Copy source code
COPY . .

# Build TypeScript
RUN pnpm build

# Production stage
FROM node:22-alpine AS runner

# Set working directory
WORKDIR /app

# Add non-root user for security
RUN addgroup -S mcp && \
    adduser -S mcpuser -G mcp && \
    apk add --no-cache wget curl

# Install pnpm (needed for production dependencies)
RUN npm install -g pnpm

# Copy package files
COPY --chown=mcpuser:mcp package.json pnpm-lock.yaml ./

# Install production dependencies only with strict security
RUN pnpm install --frozen-lockfile --prod --ignore-scripts

# Copy built files from builder
COPY --chown=mcpuser:mcp --from=builder /build/dist ./dist

# Copy and prepare configuration files
COPY --chown=mcpuser:mcp plane-instances.json.example /app/config/plane-instances.json.example
COPY --chown=mcpuser:mcp .env.example /app/.env.example
RUN cp /app/config/plane-instances.json.example /app/config/plane-instances.json && \
    cp /app/.env.example /app/.env

# Set environment variables
ENV NODE_ENV=production \
    DEBUG=claudeus:* \
    MCP_STDIO=true

# Create config directory with proper permissions
RUN mkdir -p /app/config && \
    chown mcpuser:mcp /app/config

# Create volume mount points for configs
VOLUME ["/app/config"]

# Switch to non-root user
USER mcpuser

# Use sh for Smithery compatibility
SHELL ["/bin/sh", "-c"]

# Add healthcheck (as non-root user)
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

# Set entrypoint for stdio MCP server
ENTRYPOINT ["node", "dist/index.js"] 
```

--------------------------------------------------------------------------------
/src/config/plane-config.ts:
--------------------------------------------------------------------------------

```typescript
import fs from 'fs/promises';
import path from 'path';
import { z } from 'zod';
import { PlaneConfig as PlaneConfigType, PlaneInstanceConfigSchema } from '../api/types/config.js';

export interface PlaneInstance {
    name: string;
    baseUrl: string;
    defaultWorkspace?: string;
    otherWorkspaces?: string[];
    apiKey: string;
}

export interface PlaneConfig {
    [key: string]: PlaneInstance;
}

export const DEFAULT_INSTANCE = 'simhop';

export async function loadInstanceConfig(): Promise<PlaneConfig> {
    const configPath = process.env.PLANE_INSTANCES_PATH || 'plane-instances.json';
    
    try {
        const configContent = await fs.readFile(configPath, 'utf-8');
        const config = JSON.parse(configContent);
        
        // Validate config structure
        if (!config || typeof config !== 'object') {
            throw new Error('Invalid config format: must be an object');
        }
        
        return config;
    } catch (error) {
        if (error instanceof Error) {
            throw new Error(`Failed to load Plane instances config: ${error.message}`);
        }
        throw error;
    }
}

export async function loadPlaneConfig(): Promise<PlaneConfigType> {
    try {
        const configPath = process.env.PLANE_INSTANCES_PATH || './plane-instances.json';
        const configData = await fs.readFile(configPath, 'utf-8');
        const config = JSON.parse(configData);

        // Validate each instance configuration
        const validatedConfig: PlaneConfigType = {};
        for (const [alias, instance] of Object.entries(config)) {
            try {
                validatedConfig[alias] = PlaneInstanceConfigSchema.parse(instance);
            } catch (error) {
                console.error(`Invalid configuration for instance ${alias}:`, error);
                throw error;
            }
        }

        return validatedConfig;
    } catch (error) {
        if (error instanceof Error) {
            throw new Error(`Failed to load Plane configuration: ${error.message}`);
        }
        throw new Error('Failed to load Plane configuration');
    }
} 
```

--------------------------------------------------------------------------------
/notes.txt:
--------------------------------------------------------------------------------

```

We want to use the architecture, quality, logic and standards in "/Users/amadeus/code/claudeus/servers/claudeus-wp-mcp".
It should be able to connect to our Plane API and have full access to do any operations on the Plane API.
Check the Docs and become an expert MCP development and Plane and it's API.
When you're ready with all the knowledge, information and the parameters you need, start building the server gradually, adding unit tests and documentation to each feature we create or modify.

Claudeus Plane MCP server mst be able to:
- Connects to SimHop's Plane API endpoint and authenticate with the proper method and credentials.
- Get lists of all projects, tasks, users, and comments (including comments filtered by task, project, or user).
- Update all the resources (projects, tasks, users, and comments) with the proper methods and credentials.
- Delete all the resources (projects, tasks, users, and comments) with the proper methods and credentials.
- Create all the resources (projects, tasks, users, and comments) with the proper methods and credentials.

In short, Claudeus Plane MCP server must be able to do any operation on the Plane API and manipulate ANYTHING on the target Plane instance! It's like a Plane Wizard that can do anything! 😁

Just like the Claudeus WP MCP server, it should have a configuration file that contains as many targets as needed, each target has the base URL (required), the slug of the default workspace (required), an array of other workspaces (optional) and the API key X_API_Key (required).


Plane instance: https://ops.simhop.se
Base URL: https://ops.simhop.se/api/v1
Default Workspace: "deuspace"
Authentication Header:
X-API-Key: "plane_api_e876aa94ae9a40b58c8d573c983b3515"

Example of a CRUD endpoints to get all projects:
GET     {base-url}/workspaces/{workspace-slug}/projects/
GET     {base-url}/workspaces/{workspace-slug}/projects/{project-id}

POST    {base-url}/workspaces/{workspace-slug}/projects/
body = {
  "name": "<string>",
  "identifier": "<string>",
  "description": "<string>"
}

PATCH   {base-url}/workspaces/{workspace-slug}/projects/{project-id}
body = {
  "description": "<string>"
}

DELETE  {base-url}/workspaces/{workspace-slug}/projects/{project-id}



```

--------------------------------------------------------------------------------
/docs/smithery-docs.md:
--------------------------------------------------------------------------------

```markdown
# Claudeus Plane MCP Documentation

## Overview

Claudeus Plane MCP is an AI-powered project management tool that integrates with Plane instances through the MCP protocol. It provides a comprehensive set of tools for managing projects, issues, cycles, and modules in Plane.

## Configuration

### Environment Variables

- `PLANE_INSTANCES_PATH`: Path to Plane instances configuration file
- `PORT`: Server port for health checks
- `NODE_ENV`: Node environment (development/production)
- `DEBUG`: Debug configuration pattern
- `AUTH_TYPE`: Authentication type (api_key)
- `SSL_VERIFY`: SSL certificate verification
- `LOG_LEVEL`: Logging level
- `BATCH_SIZE`: Maximum batch processing size

### Plane Instances Configuration

Example `plane-instances.json`:
```json
{
  "instances": [
    {
      "name": "example",
      "url": "https://plane.example.com",
      "apiKey": "your-api-key"
    }
  ]
}
```

## Tools

### Project Management

- `list_projects`: List all projects in a workspace
- `create_project`: Create a new project
- `update_project`: Update project details
- `delete_project`: Delete a project (dangerous operation)

### Issue Management

- `list_issues`: List issues in a project
- `create_issue`: Create a new issue
- `update_issue`: Update issue details
- `delete_issue`: Delete an issue (dangerous operation)

### Cycle Management

- `list_cycles`: List cycles in a project
- `create_cycle`: Create a new cycle
- `update_cycle`: Update cycle details
- `delete_cycle`: Delete a cycle (dangerous operation)

### Module Management

- `list_modules`: List modules in a project
- `create_module`: Create a new module
- `update_module`: Update module details
- `delete_module`: Delete a module (dangerous operation)

## Security

- All dangerous operations require explicit confirmation
- API keys must be stored securely
- SSL verification is enabled by default
- Access is limited to configured instances only

## Error Handling

- All errors include detailed messages
- Debug mode provides additional information
- Logging levels can be configured as needed

## Best Practices

1. Always use environment variables for sensitive data
2. Regularly rotate API keys
3. Keep instance configurations up to date
4. Monitor tool usage and access patterns
5. Follow proper error handling procedures

## Support

For support or questions, contact:
- 📧 CTO: [email protected]
- 📱 Phone: +46-76-427-1243 
```

--------------------------------------------------------------------------------
/src/api/issues/client.ts:
--------------------------------------------------------------------------------

```typescript
import { BaseApiClient } from '../base-client.js';
import { PlaneInstanceConfig } from '../types/config.js';
import { IssueListFilters, IssueListResponse, CreateIssueData, UpdateIssueData, IssueResponse } from './types.js';

export class IssuesClient extends BaseApiClient {
  constructor(instance: PlaneInstanceConfig) {
    super(instance);
  }

  /**
   * List issues in a project
   * @param workspaceSlug - The workspace slug
   * @param projectId - The project ID
   * @param filters - Optional filters for the issues list
   * @param page - Page number (1-based)
   * @param pageSize - Number of items per page
   */
  async list(
    workspaceSlug: string,
    projectId: string,
    filters?: IssueListFilters,
    page: number = 1,
    pageSize: number = 100
  ): Promise<IssueListResponse> {
    const queryParams = {
      offset: ((page - 1) * pageSize).toString(), // Plane uses offset-based pagination
      limit: pageSize.toString(),
      ...filters
    };

    return this.get(
      `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues`,
      queryParams
    );
  }

  /**
   * Create a new issue in a project
   * @param workspaceSlug - The workspace slug
   * @param projectId - The project ID
   * @param data - The issue data
   */
  async create(
    workspaceSlug: string,
    projectId: string,
    data: CreateIssueData
  ): Promise<IssueResponse> {
    return this.post(
      `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues`,
      {
        ...data,
        project: projectId,
        workspace: workspaceSlug
      }
    );
  }

  /**
   * Get a single issue by ID
   * @param workspaceSlug - The workspace slug
   * @param projectId - The project ID
   * @param issueId - The issue ID
   */
  async getIssue(
    workspaceSlug: string,
    projectId: string,
    issueId: string
  ): Promise<IssueResponse> {
    return this.get(
      `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}`
    );
  }

  /**
   * Update an existing issue
   * @param workspaceSlug - The workspace slug
   * @param projectId - The project ID
   * @param issueId - The issue ID
   * @param data - The update data
   */
  async update(
    workspaceSlug: string,
    projectId: string,
    issueId: string,
    data: UpdateIssueData
  ): Promise<IssueResponse> {
    return this.patch(
      `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}`,
      data
    );
  }
} 

```

--------------------------------------------------------------------------------
/src/tools/issues/get.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool, ToolResponse } from '../../types/mcp.js';
import { IssuesClient } from '../../api/issues/client.js';
import { PlaneInstanceConfig } from '../../api/types/config.js';

export class GetIssueTool implements Tool {
  private issuesClient: IssuesClient;
  private instance: PlaneInstanceConfig;

  name = 'claudeus_plane_issues__get';
  description = 'Gets a single issue by ID from a Plane project';
  status = 'enabled' as const;
  inputSchema = {
    type: 'object',
    properties: {
      workspace_slug: {
        type: 'string',
        description: 'The slug of the workspace containing the issue. If not provided, uses the default workspace.'
      },
      project_id: {
        type: 'string',
        description: 'The ID of the project containing the issue'
      },
      issue_id: {
        type: 'string',
        description: 'The ID of the issue to retrieve'
      }
    },
    required: ['project_id', 'issue_id']
  };

  constructor(instance: PlaneInstanceConfig) {
    this.instance = instance;
    this.issuesClient = new IssuesClient(this.instance);
  }

  async execute(args: Record<string, unknown>): Promise<ToolResponse> {
    const input = args as {
      workspace_slug?: string;
      project_id: string;
      issue_id: string;
    };

    const {
      workspace_slug = this.instance.defaultWorkspace,
      project_id,
      issue_id
    } = input;

    // Validate workspace
    if (!workspace_slug) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Workspace slug is required'
        }]
      };
    }

    // Validate project ID
    if (!project_id) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Project ID is required'
        }]
      };
    }

    // Validate issue ID
    if (!issue_id) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Issue ID is required'
        }]
      };
    }

    try {
      const response = await this.issuesClient.getIssue(
        workspace_slug,
        project_id,
        issue_id
      );

      return {
        content: [{
          type: 'text',
          text: JSON.stringify(response)
        }]
      };
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      return {
        isError: true,
        content: [{
          type: 'text',
          text: `Failed to get issue: ${errorMessage}`
        }]
      };
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/types/mcp.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

export interface ServerCapabilities {
  prompts?: { listChanged?: boolean };
  tools?: { listChanged?: boolean };
  resources?: { listChanged?: boolean };
}

export interface Connection {
  id: string;
  transport: any;
  initialized: boolean;
  capabilities?: ServerCapabilities;
}

export interface ToolDefinition {
  name: string;
  description: string;
  status?: 'enabled' | 'disabled';
  inputSchema: {
    type: string;
    required?: string[];
    properties?: Record<string, unknown>;
  };
}

export interface Tool extends ToolDefinition {
  execute: (args: Record<string, unknown>) => Promise<ToolResponse>;
}

export interface ToolWithClass extends ToolDefinition {
  class: new (...args: any[]) => Tool;
}

export interface ToolResponse {
  isError?: boolean;
  content: Array<{
    type: string;
    text: string;
  }>;
}

export interface ListToolsResponse {
  tools: Tool[];
}

export interface CallToolResponse {
  result: ToolResponse;
}

export interface ResourceTemplate {
  id: string;
  name: string;
  description: string;
  tool: string;
  arguments: Record<string, unknown>;
}

export interface ListResourceTemplatesResponse {
  resourceTemplates: ResourceTemplate[];
}

export interface Resource {
  id: string;
  name: string;
  type: string;
  uri: string;
  metadata: Record<string, unknown>;
}

export interface ListResourcesResponse {
  resources: Resource[];
}

export interface ResourceContent {
  type: string;
  uri: string;
  text: string;
}

export interface ReadResourceResponse {
  resource: Resource;
  contents: ResourceContent[];
}

export interface MCPToolDefinition {
  name: string;
  description: string;
  inputSchema: z.ZodType<any>;
  outputSchema: z.ZodType<any>;
}

export abstract class MCPTool<
  TInput extends z.ZodType<any>,
  TOutput extends z.ZodType<any>
> {
  constructor(protected definition: MCPToolDefinition) {}
  abstract execute(input: z.infer<TInput>): Promise<z.infer<TOutput>>;
}

export interface JsonRpcMessage {
  jsonrpc: '2.0';
  id?: number | string;
  method?: string;
  params?: Record<string, unknown>;
  result?: Record<string, unknown>;
  error?: {
    code: number;
    message: string;
    data?: unknown;
  };
}

export interface McpError extends Error {
  code: number;
  data?: unknown;
}

export interface McpRequest {
  id: string | number;
  method: string;
  params: Record<string, unknown>;
}

export interface McpResponse {
  id: string | number;
  result?: unknown;
  error?: {
    code: number;
    message: string;
    data?: unknown;
  };
}

export interface McpNotification {
  method: string;
  params?: Record<string, unknown>;
} 
```

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

```typescript
import { z } from 'zod';
import { Tool, ToolResponse } from '../../types/mcp.js';
import { PlaneApiClient } from '../../api/client.js';

const inputSchema = {
    type: 'object',
    properties: {
        workspace_slug: {
            type: 'string',
            description: 'The slug of the workspace to delete the project from. If not provided, uses the default workspace.'
        },
        project_id: {
            type: 'string',
            description: 'The ID of the project to delete.'
        }
    },
    required: ['project_id']
};

const zodInputSchema = z.object({
    workspace_slug: z.string().optional(),
    project_id: z.string()
});

export class DeleteProjectTool implements Tool {
    name = 'claudeus_plane_projects__delete';
    description = 'Deletes an existing project in a workspace. If no workspace is specified, uses the default workspace.';
    status: 'enabled' | 'disabled' = 'enabled';
    inputSchema = inputSchema;

    constructor(private client: PlaneApiClient) {}

    async execute(args: Record<string, unknown>): Promise<ToolResponse> {
        const input = zodInputSchema.parse(args);
        const { workspace_slug, project_id } = input;

        try {
            const workspace = workspace_slug || this.client.instance.defaultWorkspace;
            if (!workspace) {
                throw new Error('No workspace provided or configured');
            }

            await this.client.deleteProject(workspace, project_id);
            
            return {
                content: [{
                    type: 'text',
                    text: JSON.stringify({ 
                        success: true, 
                        message: 'Project deleted successfully',
                        project_id,
                        workspace
                    }, null, 2)
                }]
            };
        } catch (error) {
            if (error instanceof Error) {
                const workspace = workspace_slug || this.client.instance.defaultWorkspace;
                this.client.notify({
                    type: 'error',
                    message: `Failed to delete project: ${error.message}`,
                    source: this.name,
                    data: { 
                        error: error.message,
                        workspace,
                        project_id
                    }
                });

                return {
                    isError: true,
                    content: [{
                        type: 'text',
                        text: `Error: ${error.message}`
                    }]
                };
            }
            throw error;
        }
    }
} 

```

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

```typescript
#!/usr/bin/env node
import { config } from 'dotenv';
import { McpServer } from './mcp/server.js';
import { loadInstanceConfig } from './config/plane-config.js';
import { PlaneApiClient } from './api/client.js';
import { registerTools } from './mcp/tools.js';
import { projectPrompts } from './prompts/projects/index.js';
import { PromptContext } from './types/prompt.js';

// Load environment variables
config();

// Custom logger that ensures we only write to stderr for non-MCP communication
const log = {
  info: (...args: unknown[]) => console.error('\x1b[32m%s\x1b[0m', '[INFO]', ...args),
  error: (...args: unknown[]) => console.error('\x1b[31m%s\x1b[0m', '[ERROR]', ...args),
  debug: (...args: unknown[]) => console.error('\x1b[36m%s\x1b[0m', '[DEBUG]', ...args)
};

async function main() {
  try {
    // Load configuration
    const config = await loadInstanceConfig();
    log.info('Loaded', Object.keys(config).length, 'Plane instance configurations');

    // Initialize API clients
    const clients = new Map<string, PlaneApiClient>();
    for (const [name, instance] of Object.entries(config)) {
      const planeInstance = {
        name,
        baseUrl: instance.baseUrl,
        defaultWorkspace: instance.defaultWorkspace,
        otherWorkspaces: instance.otherWorkspaces,
        apiKey: instance.apiKey
      };
      
      const context: PromptContext = {
        workspace: instance.defaultWorkspace || '',
        connectionId: name
      };

      const client = new PlaneApiClient(planeInstance, context);
      clients.set(name, client);
      log.info('Initialized API client for instance:', name);
    }

    // Initialize MCP server
    const server = new McpServer();
    log.info('Initialized MCP server');

    // Register tools before connecting
    registerTools(server.getServer(), clients);
    log.info('Registered tools');

    // Register prompts
    for (const prompt of projectPrompts) {
      server.registerPrompt(prompt);
    }
    log.info('Registered prompts');

    // Connect to transport and start server
    await server.initialize();
    log.info('Server initialized');

    await server.start();
    log.info('Server started');
  } catch (error) {
    if (error instanceof Error) {
      log.error('Failed to start server:', error.message);
      log.debug('Stack trace:', error.stack);
    } else {
      log.error('Failed to start server:', String(error));
    }
    process.exit(1);
  }
}

// Handle uncaught errors
process.on('uncaughtException', (error) => {
  log.error('Uncaught exception:', error);
  process.exit(1);
});

process.on('unhandledRejection', (reason) => {
  log.error('Unhandled rejection:', reason);
  process.exit(1);
});

main(); 
```

--------------------------------------------------------------------------------
/src/test/unit/tools/projects/list.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, beforeEach } from 'vitest';
import { MCPTestHarness, MCPMessage, MCPContentItem } from '@/test/mcp-test-harness.js';
import type { ProjectsResponse } from '@/dummy-data/projects.js';
import dummyProjects from '@/dummy-data/projects.json' assert { type: 'json' };

interface MCPToolResponse extends MCPMessage {
  result?: {
    content?: MCPContentItem[];
  };
}

describe('claudeus_plane_projects__list', () => {
  let harness: MCPTestHarness;

  beforeEach(() => {
    harness = new MCPTestHarness();
  });

  it('should list all projects in the workspace', async () => {
    // Connect to the MCP server
    const initResponse = await harness.connect();
    expect(initResponse).toBeValidJsonRpc();

    // Mock tool response before calling the tool
    const response = await harness.callTool('claudeus_plane_projects__list', {
      workspace: (dummyProjects as ProjectsResponse).results[0].workspace
    }) as MCPToolResponse;

    // Verify JSON-RPC format
    expect(response).toBeValidJsonRpc();

    // Verify response content
    expect(response.result).toBeDefined();
    expect(response.error).toBeUndefined();
    expect(response.result?.content).toBeInstanceOf(Array);
    expect(response.result?.content?.[0]?.type).toBe('text');

    // Parse and verify project data
    const responseData = JSON.parse(response.result?.content?.[0]?.text || '{}') as ProjectsResponse;
    expect(responseData.results).toBeInstanceOf(Array);
    expect(responseData.results).toHaveLength((dummyProjects as ProjectsResponse).results.length);

    // Verify project structure
    const project = responseData.results[0];
    expect(project).toMatchObject({
      id: expect.any(String),
      name: expect.any(String),
      description: expect.any(String),
      identifier: expect.any(String),
      workspace: expect.any(String)
    });
  });

  it('should handle invalid workspace ID', async () => {
    // Connect to the MCP server
    await harness.connect();

    const response = await harness.callTool('claudeus_plane_projects__list', {
      workspace: 'invalid-workspace-id'
    }) as MCPToolResponse;

    expect(response).toBeValidJsonRpc();
    expect(response.result?.content?.[0]?.type).toBe('text');
    expect(response.result?.content?.[0]?.text).toContain('Error');
  });

  it('should handle missing workspace parameter', async () => {
    // Connect to the MCP server
    await harness.connect();

    const response = await harness.callTool('claudeus_plane_projects__list', {}) as MCPToolResponse;

    expect(response).toBeValidJsonRpc();
    expect(response.result?.content?.[0]?.type).toBe('text');
    expect(response.result?.content?.[0]?.text).toContain('Error');
  });
}); 
```

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

```typescript
import { ToolDefinition } from '../../types/mcp.js';
import { ListProjectsTool } from './list.js';

// Export project tool definitions
export const projectTools: ToolDefinition[] = [
  {
    name: 'claudeus_plane_projects__list',
    description: 'List all projects in a workspace',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string'
        }
      }
    }
  },
  {
    name: 'claudeus_plane_projects__create',
    description: 'Creates a new project in a workspace',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace to create the project in'
        },
        name: {
          type: 'string',
          description: 'The name of the project'
        },
        identifier: {
          type: 'string',
          description: 'The unique identifier for the project'
        },
        description: {
          type: 'string',
          description: 'A description of the project'
        }
      },
      required: ['workspace_slug', 'name', 'identifier']
    }
  },
  {
    name: 'claudeus_plane_projects__update',
    description: 'Updates an existing project in a workspace',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace to update the project in.'
        },
        project_id: {
          type: 'string',
          description: 'The ID of the project to update.'
        },
        name: {
          type: 'string',
          description: 'The new name of the project.'
        },
        description: {
          type: 'string',
          description: 'The new description of the project.'
        },
        start_date: {
          type: 'string',
          format: 'date',
          description: 'The new start date of the project.'
        },
        end_date: {
          type: 'string',
          format: 'date',
          description: 'The new end date of the project.'
        },
        status: {
          type: 'string',
          description: 'The new status of the project.'
        }
      }
    }
  },
  {
    name: 'claudeus_plane_projects__delete',
    description: 'Deletes an existing project in a workspace',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace to delete the project from.'
        },
        project_id: {
          type: 'string',
          description: 'The ID of the project to delete.'
        }
      }
    }
  }
]; 
```

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

```typescript
import { z } from 'zod';
import { PromptDefinition } from '../../types/prompt.js';
import {
  analyzeWorkspaceHealthHandler,
  suggestResourceAllocationHandler,
  recommendProjectStructureHandler
} from './handlers.js';

const workspaceSlugSchema = z.string().optional().describe('The workspace slug to analyze. If not provided, all workspaces will be analyzed.');
const includeArchivedSchema = z.boolean().optional().describe('Whether to include archived projects in the analysis.');
const focusAreaSchema = z.enum(['members', 'cycles', 'modules']).optional().describe('The area to focus resource allocation analysis on.');
const templateProjectSchema = z.string().optional().describe('The name of a project to use as a template for structure recommendations.');

export const analyzeWorkspaceHealth: PromptDefinition = {
  name: 'analyze_workspace_health',
  description: 'Analyzes the health of all projects in a workspace, examining member count, cycle/module usage, and activity metrics.',
  schema: z.object({
    workspace_slug: workspaceSlugSchema,
    include_archived: includeArchivedSchema
  }),
  examples: [
    {
      name: 'Analyze all projects',
      args: {}
    },
    {
      name: 'Analyze specific workspace',
      args: {
        workspace_slug: 'my-workspace'
      }
    },
    {
      name: 'Include archived projects',
      args: {
        include_archived: true
      }
    }
  ],
  handler: analyzeWorkspaceHealthHandler
};

export const suggestResourceAllocation: PromptDefinition = {
  name: 'suggest_resource_allocation',
  description: 'Suggests optimal resource allocation across projects based on member count, project size, and activity.',
  schema: z.object({
    workspace_slug: workspaceSlugSchema,
    focus_area: focusAreaSchema
  }),
  examples: [
    {
      name: 'Analyze member allocation',
      args: {
        focus_area: 'members'
      }
    },
    {
      name: 'Analyze cycle usage',
      args: {
        focus_area: 'cycles'
      }
    },
    {
      name: 'Analyze module usage in workspace',
      args: {
        workspace_slug: 'my-workspace',
        focus_area: 'modules'
      }
    }
  ],
  handler: suggestResourceAllocationHandler
};

export const recommendProjectStructure: PromptDefinition = {
  name: 'recommend_project_structure',
  description: 'Analyzes project structures and provides recommendations for standardization and best practices.',
  schema: z.object({
    workspace_slug: workspaceSlugSchema,
    template_project: templateProjectSchema
  }),
  examples: [
    {
      name: 'Use best practices',
      args: {}
    },
    {
      name: 'Use template project',
      args: {
        template_project: 'ideal-project'
      }
    },
    {
      name: 'Analyze specific workspace',
      args: {
        workspace_slug: 'my-workspace'
      }
    }
  ],
  handler: recommendProjectStructureHandler
}; 

```

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

```typescript
import { z } from 'zod';
import { Tool, ToolResponse } from '../../types/mcp.js';
import { PlaneApiClient } from '../../api/client.js';

const inputSchema = {
    type: 'object',
    properties: {
        workspace_slug: {
            type: 'string',
            description: 'The workspace to list projects from. If not provided, the default workspace will be used.'
        }
    }
};

const zodInputSchema = z.object({
    workspace_slug: z.string().optional().describe('The workspace to list projects from. If not provided, the default workspace will be used.')
});

interface Project {
    id: string;
    name: string;
    identifier: string;
    [key: string]: unknown;
}

interface PaginatedResponse {
    results: Project[];
    [key: string]: unknown;
}

/**
 * Tool for listing projects in a Plane workspace.
 * If no workspace is specified, the default workspace from the client's configuration will be used.
 */
export class ListProjectsTool implements Tool {
    name = 'claudeus_plane_projects__list';
    description = 'Lists all projects in a Plane workspace';
    inputSchema = inputSchema;

    constructor(private client: PlaneApiClient) {}

    async execute(args: Record<string, unknown>): Promise<ToolResponse> {
        try {
            const input = zodInputSchema.parse(args);
            const workspace = input.workspace_slug || this.client.instance.defaultWorkspace;

            this.client.notify({
                type: 'info',
                message: 'Fetching projects',
                source: this.name,
                data: {}
            });

            const response = await this.client.listProjects(workspace);
            const projects = 'results' in response ? response.results : response;

            this.client.notify({
                type: 'success',
                message: `Successfully retrieved ${projects.length} projects`,
                source: this.name,
                data: {
                    workspace,
                    projectCount: projects.length
                }
            });

            return {
                isError: false,
                content: [{
                    type: 'text',
                    text: `Successfully retrieved ${projects.length} projects: ${JSON.stringify(projects)}`
                }]
            };
        } catch (error) {
            const errorMessage = error instanceof Error ? error.message : 'Unknown error';
            this.client.notify({
                type: 'error',
                message: `Failed to list projects: ${errorMessage}`,
                source: this.name,
                data: {
                    error: errorMessage
                }
            });

            return {
                isError: true,
                content: [{
                    type: 'text',
                    text: `Failed to list projects: ${errorMessage}`
                }]
            };
        }
    }
} 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery.ai configuration for Claudeus Plane MCP
name: "Claudeus Plane MCP"
description: "AI-powered Plane project management with MCP protocol support"
version: "1.0.0"
author:
  name: "Amadeus Samiel H."
  email: "[email protected]"
organization:
  name: "SimHop IT & Media AB"
  website: "https://simhop.se"

startCommand:
  type: stdio
  configSchema:
    type: object
    properties:
      PLANE_INSTANCES_PATH:
        type: string
        description: "Path to Plane instances configuration JSON file"
        default: "./plane-instances.json"
        examples: ["./plane-instances.json", "/app/config/plane-instances.json"]
      PORT:
        type: number
        description: "Port number for the MCP server (for health checks)"
        default: 3000
        minimum: 1024
        maximum: 65535
      NODE_ENV:
        type: string
        description: "Node environment (development/production)"
        enum: ["development", "production"]
        default: "production"
      DEBUG:
        type: string
        description: "Debug configuration pattern"
        default: "claudeus:*"
        examples: ["claudeus:*", "claudeus:plane,claudeus:mcp"]
      AUTH_TYPE:
        type: string
        description: "Default Plane authentication type"
        enum: ["api_key"]
        default: "api_key"
      SSL_VERIFY:
        type: boolean
        description: "Verify SSL certificates for Plane connections"
        default: true
      LOG_LEVEL:
        type: string
        description: "Logging level"
        enum: ["error", "warn", "info", "debug"]
        default: "info"
      BATCH_SIZE:
        type: number
        description: "Maximum number of items to process in a batch"
        default: 100
        minimum: 1
        maximum: 1000
    additionalProperties: false

  commandFunction: |-
    (config) => {
      // Ensure configuration files exist
      const fs = require('fs');
      const path = require('path');
      
      // Helper function to copy example if target doesn't exist
      const copyExampleIfNeeded = (examplePath, targetPath) => {
        if (!fs.existsSync(targetPath) && fs.existsSync(examplePath)) {
          fs.copyFileSync(examplePath, targetPath);
        }
      };

      // Copy example files if needed
      copyExampleIfNeeded('plane-instances.json.example', 'plane-instances.json');
      copyExampleIfNeeded('.env.example', '.env');

      const env = {
        PLANE_INSTANCES_PATH: config.PLANE_INSTANCES_PATH || "./plane-instances.json",
        PORT: config.PORT?.toString() || "3000",
        NODE_ENV: config.NODE_ENV || "production",
        DEBUG: config.DEBUG || "claudeus:*",
        AUTH_TYPE: config.AUTH_TYPE || "api_key",
        SSL_VERIFY: (config.SSL_VERIFY ?? true).toString(),
        LOG_LEVEL: config.LOG_LEVEL || "info",
        BATCH_SIZE: config.BATCH_SIZE?.toString() || "100",
        MCP_STDIO: "true"
      };

      return {
        command: "node",
        args: ["dist/index.js"],
        env,
        cwd: process.cwd()
      };
    }

capabilities:
  prompts:
    listChanged: true
  tools:
    listChanged: true
  resources:
    listChanged: true

security:
  userConsent:
    required: true
    description: "This MCP server requires access to your Plane instances and will perform project management operations."
  dataAccess:
    - type: "plane"
      description: "Access to configured Plane instances via REST API"
    - type: "filesystem"
      description: "Access to plane-instances.json configuration file"
  toolSafety:
    confirmationRequired: true
    description: "Tools can modify Plane projects, issues, and settings"
    dangerousOperations:
      - "delete_project"
      - "delete_issue"
      - "delete_cycle"
      - "delete_module"
      - "delete_workspace" 
```

--------------------------------------------------------------------------------
/src/tools/issues/list.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool, ToolResponse } from '../../types/mcp.js';
import { IssuesClient } from '../../api/issues/client.js';
import { IssueListFilters, IssueListResponse, IssuePriority } from '../../api/issues/types.js';
import { PlaneInstanceConfig } from '../../api/types/config.js';

export class ListIssuesTools implements Tool {
  private issuesClient: IssuesClient;
  private instance: PlaneInstanceConfig;

  name = 'claudeus_plane_issues__list';
  description = 'Lists issues in a Plane project';
  status = 'enabled' as const;
  inputSchema = {
    type: 'object',
    properties: {
      workspace_slug: {
        type: 'string',
        description: 'The slug of the workspace to list issues from. If not provided, uses the default workspace.'
      },
      project_id: {
        type: 'string',
        description: 'The ID of the project to list issues from'
      },
      state: {
        type: 'string',
        description: 'Filter issues by state ID'
      },
      priority: {
        type: 'string',
        enum: ['urgent', 'high', 'medium', 'low', 'none'],
        description: 'Filter issues by priority'
      },
      assignee: {
        type: 'string',
        description: 'Filter issues by assignee ID'
      },
      label: {
        type: 'string',
        description: 'Filter issues by label ID'
      },
      created_by: {
        type: 'string',
        description: 'Filter issues by creator ID'
      },
      start_date: {
        type: 'string',
        format: 'date',
        description: 'Filter issues by start date (YYYY-MM-DD)'
      },
      target_date: {
        type: 'string',
        format: 'date',
        description: 'Filter issues by target date (YYYY-MM-DD)'
      },
      subscriber: {
        type: 'string',
        description: 'Filter issues by subscriber ID'
      },
      is_draft: {
        type: 'boolean',
        description: 'Filter draft issues',
        default: false
      },
      archived: {
        type: 'boolean',
        description: 'Filter archived issues',
        default: false
      },
      page: {
        type: 'number',
        description: 'Page number (1-based)',
        default: 1
      },
      page_size: {
        type: 'number',
        description: 'Number of items per page',
        default: 100
      }
    },
    required: ['project_id']
  };

  constructor(instance: PlaneInstanceConfig) {
    this.instance = instance;
    this.issuesClient = new IssuesClient(this.instance);
  }

  async execute(args: Record<string, unknown>): Promise<ToolResponse> {
    const input = args as {
      workspace_slug?: string;
      project_id: string;
      state?: string;
      priority?: IssuePriority;
      assignee?: string;
      label?: string;
      created_by?: string;
      start_date?: string;
      target_date?: string;
      subscriber?: string;
      is_draft?: boolean;
      archived?: boolean;
      page?: number;
      page_size?: number;
    };

    const {
      workspace_slug = this.instance.defaultWorkspace,
      project_id,
      page = 1,
      page_size = 100,
      ...filters
    } = input;

    // Validate workspace
    if (!workspace_slug) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Workspace slug is required'
        }]
      };
    }

    // Validate project ID
    if (!project_id) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Project ID is required'
        }]
      };
    }

    try {
      const response = await this.issuesClient.list(
        workspace_slug,
        project_id,
        filters as IssueListFilters,
        page,
        page_size
      );

      return {
        content: [{
          type: 'text',
          text: JSON.stringify(response)
        }]
      };
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      return {
        isError: true,
        content: [{
          type: 'text',
          text: `Failed to list issues: ${errorMessage}`
        }]
      };
    }
  }
} 

```

--------------------------------------------------------------------------------
/src/api/base-client.ts:
--------------------------------------------------------------------------------

```typescript
import axios, { AxiosInstance, AxiosError } from 'axios';
import { PlaneInstanceConfig } from './types/config.js';

export type QueryParams = Record<string, string | number | boolean | Array<string | number> | null | undefined>;

interface PlaneErrorResponse {
    message: string;
    [key: string]: any;
}

export class BaseApiClient {
    protected client: AxiosInstance;
    protected _instance: PlaneInstanceConfig;
    public readonly baseUrl: string;

    constructor(instance: PlaneInstanceConfig) {
        this._instance = instance;
        this.baseUrl = instance.baseUrl;
        
        // Keep the full baseUrl including /api/v1 since it's part of the base URL
        const baseURL = this.baseUrl.endsWith('/') 
            ? this.baseUrl.slice(0, -1) // Remove trailing slash if present
            : this.baseUrl;

        this.client = axios.create({
            baseURL,
            headers: {
                'X-API-Key': instance.apiKey,
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            }
        });

        // Add response interceptor for better error handling
        this.client.interceptors.response.use(
            response => response,
            error => {
                if (axios.isAxiosError(error)) {
                    const axiosError = error as AxiosError<PlaneErrorResponse>;
                    if (axiosError.response?.status === 403) {
                        throw new Error(`API Error (403): Authentication failed. Please check your API key.`);
                    }
                    const errorMessage = axiosError.response?.data?.message || axiosError.message;
                    const errorCode = axiosError.response?.status;
                    throw new Error(`API Error (${errorCode}): ${errorMessage}`);
                }
                throw error;
            }
        );
    }

    get instance(): PlaneInstanceConfig {
        return this._instance;
    }

    protected handleError(error: AxiosError<PlaneErrorResponse>): never {
        if (error.response?.status === 403) {
            throw new Error(`API Error: Authentication failed. Please check your API key.`);
        }
        if (error.response?.data?.message) {
            throw new Error(`API Error: ${error.response.data.message}`);
        } else if (error.response?.status) {
            throw new Error(`HTTP Error ${error.response.status}: ${error.message}`);
        } else {
            throw new Error(`Network Error: ${error.message}`);
        }
    }

    protected async get<T>(endpoint: string, params?: QueryParams): Promise<T> {
        try {
            const response = await this.client.get<T>(endpoint, { params });
            return response.data;
        } catch (error) {
            this.handleError(error as AxiosError<PlaneErrorResponse>);
        }
    }

    protected async post<T, D = Record<string, unknown>>(endpoint: string, data: D): Promise<T> {
        try {
            const response = await this.client.post<T>(endpoint, data);
            return response.data;
        } catch (error) {
            this.handleError(error as AxiosError<PlaneErrorResponse>);
        }
    }

    protected async put<T, D = Record<string, unknown>>(endpoint: string, data: D): Promise<T> {
        try {
            const response = await this.client.put<T>(endpoint, data);
            return response.data;
        } catch (error) {
            this.handleError(error as AxiosError<PlaneErrorResponse>);
        }
    }

    protected async delete<T>(endpoint: string): Promise<T> {
        try {
            const response = await this.client.delete<T>(endpoint);
            return response.data;
        } catch (error) {
            this.handleError(error as AxiosError<PlaneErrorResponse>);
        }
    }

    protected async patch<T, D = Record<string, unknown>>(endpoint: string, data: D): Promise<T> {
        try {
            const response = await this.client.patch<T>(endpoint, data);
            return response.data;
        } catch (error) {
            this.handleError(error as AxiosError<PlaneErrorResponse>);
        }
    }
} 
```

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

```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { DeleteProjectTool } from '../delete.js';
import { PlaneApiClient } from '../../../api/client.js';
import { PlaneInstance } from '../../../config/plane-config.js';

// Mock the PlaneApiClient
vi.mock('../../../api/client.js', () => {
    const mockNotify = vi.fn();
    return {
        PlaneApiClient: vi.fn().mockImplementation((instance, context) => ({
            instance,
            deleteProject: vi.fn(),
            notify: mockNotify
        }))
    };
});

describe('DeleteProjectTool', () => {
    let tool: DeleteProjectTool;
    let mockClient: PlaneApiClient;
    
    beforeEach(() => {
        // Create a mock instance
        const mockInstance: PlaneInstance = {
            name: 'test',
            baseUrl: 'https://test.plane.so',
            apiKey: 'test-key',
            defaultWorkspace: 'test-workspace'
        };

        // Create a mock context
        const mockContext = {
            progressToken: '123',
            workspace: 'test-workspace'
        };

        // Create a mock client
        mockClient = new PlaneApiClient(mockInstance, mockContext);
        
        // Create the tool instance
        tool = new DeleteProjectTool(mockClient);

        // Reset mock call history
        vi.clearAllMocks();
    });

    it('should delete a project successfully', async () => {
        (mockClient.deleteProject as any).mockResolvedValue(undefined);

        const result = await tool.execute({
            project_id: 'test-id'
        });

        expect(mockClient.deleteProject).toHaveBeenCalledWith('test-workspace', 'test-id');
        expect(JSON.parse(result.content[0].text)).toEqual({
            success: true,
            message: 'Project deleted successfully',
            project_id: 'test-id',
            workspace: 'test-workspace'
        });
    });

    it('should use provided workspace instead of default', async () => {
        (mockClient.deleteProject as any).mockResolvedValue(undefined);

        await tool.execute({
            workspace_slug: 'custom-workspace',
            project_id: 'test-id'
        });

        expect(mockClient.deleteProject).toHaveBeenCalledWith('custom-workspace', 'test-id');
    });

    it('should handle API errors', async () => {
        const errorMessage = 'API Error: Project deletion failed';
        (mockClient.deleteProject as any).mockRejectedValue(new Error(errorMessage));

        const result = await tool.execute({
            project_id: 'test-id'
        });

        expect(result.isError).toBe(true);
        expect(result.content[0].text).toBe(`Error: ${errorMessage}`);
        expect(mockClient.notify).toHaveBeenCalledWith({
            type: 'error',
            message: `Failed to delete project: ${errorMessage}`,
            source: 'claudeus_plane_projects__delete',
            data: { 
                error: errorMessage,
                workspace: 'test-workspace',
                project_id: 'test-id'
            }
        });
    });

    it('should validate required fields', async () => {
        await expect(tool.execute({
            // Missing project_id
        })).rejects.toThrow();
    });

    it('should handle missing workspace configuration', async () => {
        const mockInstanceNoWorkspace: PlaneInstance = {
            name: 'test',
            baseUrl: 'https://test.plane.so',
            apiKey: 'test-key'
            // No defaultWorkspace
        };

        const mockContextNoWorkspace = {
            progressToken: '123',
            workspace: 'test-workspace'
        };

        const clientNoWorkspace = new PlaneApiClient(mockInstanceNoWorkspace, mockContextNoWorkspace);
        (clientNoWorkspace.deleteProject as any).mockResolvedValue(undefined);

        const toolNoWorkspace = new DeleteProjectTool(clientNoWorkspace);

        const result = await toolNoWorkspace.execute({
            project_id: 'test-id'
            // No workspace_slug provided
        });

        expect(result.isError).toBe(true);
        expect(result.content[0].text).toBe('Error: No workspace provided or configured');
    });
}); 
```

--------------------------------------------------------------------------------
/src/tools/issues/create.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool, ToolResponse } from '../../types/mcp.js';
import { IssuesClient } from '../../api/issues/client.js';
import { CreateIssueData, IssuePriority } from '../../api/issues/types.js';
import { PlaneInstanceConfig } from '../../api/types/config.js';

interface CreateIssueInput extends CreateIssueData {
  workspace_slug?: string;
  project_id: string;
}

export class CreateIssueTool implements Tool {
  private issuesClient: IssuesClient;
  private instance: PlaneInstanceConfig;

  name = 'claudeus_plane_issues__create';
  description = 'Creates a new issue in a Plane project';
  status = 'enabled' as const;
  inputSchema = {
    type: 'object',
    properties: {
      workspace_slug: {
        type: 'string',
        description: 'The slug of the workspace to create the issue in. If not provided, uses the default workspace.'
      },
      project_id: {
        type: 'string',
        description: 'The ID of the project to create the issue in'
      },
      name: {
        type: 'string',
        description: 'The name/title of the issue'
      },
      description_html: {
        type: 'string',
        description: 'The HTML description of the issue'
      },
      priority: {
        type: 'string',
        enum: ['urgent', 'high', 'medium', 'low', 'none'],
        description: 'The priority of the issue',
        default: 'none'
      },
      start_date: {
        type: 'string',
        format: 'date',
        description: 'The start date of the issue (YYYY-MM-DD)'
      },
      target_date: {
        type: 'string',
        format: 'date',
        description: 'The target date of the issue (YYYY-MM-DD)'
      },
      estimate_point: {
        type: 'number',
        description: 'Story points or time estimate for the issue'
      },
      state: {
        type: 'string',
        description: 'The state ID for the issue'
      },
      assignees: {
        type: 'array',
        items: {
          type: 'string'
        },
        description: 'Array of user IDs to assign to the issue'
      },
      labels: {
        type: 'array',
        items: {
          type: 'string'
        },
        description: 'Array of label IDs to apply to the issue'
      },
      parent: {
        type: 'string',
        description: 'ID of the parent issue (for sub-issues)'
      },
      is_draft: {
        type: 'boolean',
        description: 'Whether this is a draft issue',
        default: false
      }
    },
    required: ['project_id', 'name']
  };

  constructor(instance: PlaneInstanceConfig) {
    this.instance = instance;
    this.issuesClient = new IssuesClient(this.instance);
  }

  async execute(args: Record<string, unknown>): Promise<ToolResponse> {
    // Type cast with validation
    const input = args as unknown as CreateIssueInput;
    if (!this.validateInput(input)) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Invalid input: missing required fields'
        }]
      };
    }

    const {
      workspace_slug = this.instance.defaultWorkspace,
      project_id,
      ...issueData
    } = input;

    // Validate workspace
    if (!workspace_slug) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Workspace slug is required'
        }]
      };
    }

    // Validate project ID
    if (!project_id) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Project ID is required'
        }]
      };
    }

    // Validate name
    if (!issueData.name) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Issue name is required'
        }]
      };
    }

    try {
      const response = await this.issuesClient.create(
        workspace_slug,
        project_id,
        issueData
      );

      return {
        content: [{
          type: 'text',
          text: JSON.stringify(response)
        }]
      };
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      return {
        isError: true,
        content: [{
          type: 'text',
          text: `Failed to create issue: ${errorMessage}`
        }]
      };
    }
  }

  private validateInput(input: unknown): input is CreateIssueInput {
    if (typeof input !== 'object' || input === null) return false;
    const data = input as Record<string, unknown>;
    return typeof data.name === 'string' && typeof data.project_id === 'string';
  }
} 

```

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

```typescript
import { ToolResponse } from '../../types/mcp.js';
import { ProjectsAPI } from '../../api/projects.js';
import { z } from 'zod';
import { CreateProjectSchema, UpdateProjectSchema } from '../../api/types/project.js';

const listProjectsSchema = z.object({
    workspace_slug: z.string().optional(),
    include_archived: z.boolean().optional()
});

export async function listProjects(
    api: ProjectsAPI, 
    args: Record<string, unknown>
): Promise<ToolResponse> {
    const { workspace_slug, include_archived } = listProjectsSchema.parse(args);
    
    try {
        const workspace = workspace_slug || api.instance.defaultWorkspace;
        if (!workspace) {
            throw new Error('No workspace provided or configured');
        }

        const projects = await api.listProjects(workspace, { include_archived });
        
        return {
            content: [{
                type: 'text',
                text: JSON.stringify(projects, null, 2)
            }]
        };
    } catch (error) {
        if (error instanceof Error) {
            throw new Error(`Failed to list projects: ${error.message}`);
        }
        throw error;
    }
}

const createProjectSchema = z.object({
    workspace_slug: z.string().optional(),
    name: z.string(),
    identifier: z.string(),
    description: z.string().optional(),
    project_lead: z.string().uuid().optional(),
    default_assignee: z.string().uuid().optional()
});

export async function createProject(
    api: ProjectsAPI,
    args: Record<string, unknown>
): Promise<ToolResponse> {
    const data = createProjectSchema.parse(args);
    const workspace = data.workspace_slug || api.instance.defaultWorkspace;
    
    if (!workspace) {
        throw new Error('No workspace provided or configured');
    }

    try {
        const project = await api.createProject(workspace, data);
        return {
            content: [{
                type: 'text',
                text: JSON.stringify(project, null, 2)
            }]
        };
    } catch (error) {
        if (error instanceof Error) {
            throw new Error(`Failed to create project: ${error.message}`);
        }
        throw error;
    }
}

const updateProjectSchema = z.object({
    workspace_slug: z.string().optional(),
    project_id: z.string(),
    name: z.string().optional(),
    description: z.string().optional(),
    project_lead: z.string().uuid().optional(),
    default_assignee: z.string().uuid().optional()
});

export async function updateProject(
    api: ProjectsAPI,
    args: Record<string, unknown>
): Promise<ToolResponse> {
    const { workspace_slug, project_id, ...updateData } = updateProjectSchema.parse(args);
    const workspace = workspace_slug || api.instance.defaultWorkspace;
    
    if (!workspace) {
        throw new Error('No workspace provided or configured');
    }

    try {
        const project = await api.updateProject(workspace, project_id, updateData);
        return {
            content: [{
                type: 'text',
                text: JSON.stringify(project, null, 2)
            }]
        };
    } catch (error) {
        if (error instanceof Error) {
            throw new Error(`Failed to update project: ${error.message}`);
        }
        throw error;
    }
}

const deleteProjectSchema = z.object({
    workspace_slug: z.string().optional(),
    project_id: z.string()
});

export async function deleteProject(
    api: ProjectsAPI,
    args: Record<string, unknown>
): Promise<ToolResponse> {
    const { workspace_slug, project_id } = deleteProjectSchema.parse(args);
    const workspace = workspace_slug || api.instance.defaultWorkspace;
    
    if (!workspace) {
        throw new Error('No workspace provided or configured');
    }

    try {
        await api.deleteProject(workspace, project_id);
        return {
            content: [{
                type: 'text',
                text: JSON.stringify({ success: true, message: 'Project deleted successfully' })
            }]
        };
    } catch (error) {
        if (error instanceof Error) {
            throw new Error(`Failed to delete project: ${error.message}`);
        }
        throw error;
    }
}

export async function handleProjectTools(
    api: ProjectsAPI,
    name: string, 
    args: Record<string, unknown>
): Promise<ToolResponse> {
    switch (name) {
        case 'claudeus_plane_projects__list':
            return listProjects(api, args);
        case 'claudeus_plane_projects__create':
            return createProject(api, args);
        case 'claudeus_plane_projects__update':
            return updateProject(api, args);
        case 'claudeus_plane_projects__delete':
            return deleteProject(api, args);
        default:
            throw new Error(`Unknown project tool: ${name}`);
    }
} 
```

--------------------------------------------------------------------------------
/src/test/integration/projects.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { PlaneApiClient } from '../../api/client.js';
import { loadPlaneConfig } from '../../config/plane-config.js';
import { PlaneInstanceConfig } from '../../api/types/config.js';
import { PromptContext } from '../../types/prompt.js';
import { CreateProjectTool } from '../../tools/projects/create.js';
import { UpdateProjectTool } from '../../tools/projects/update.js';
import { DeleteProjectTool } from '../../tools/projects/delete.js';
import { ListProjectsTool } from '../../tools/projects/list.js';

const TEST_CONFIG = {
    instanceName: 'simhop_test',
    projectPrefix: 'TEST_PROJ_',
    timeouts: {
        create: 5000,
        query: 3000,
        update: 5000,
        delete: 5000
    }
};

describe('Project Management Integration', () => {
    let client: PlaneApiClient;
    let createTool: CreateProjectTool;
    let listTool: ListProjectsTool;
    let updateTool: UpdateProjectTool;
    let deleteTool: DeleteProjectTool;
    let testProjectId: string;

    // Test project data
    const projectIdentifier = `${TEST_CONFIG.projectPrefix}${Date.now()}`;
    const initialProjectData = {
        name: 'Integration Test Project',
        identifier: projectIdentifier,
        description: 'Project created by integration tests',
        network: 0, // Private
        emoji: '1f9ea', // Test tube emoji
        module_view: true,
        cycle_view: true,
        issue_views_view: true,
        page_view: true,
        inbox_view: true
    };

    beforeAll(async () => {
        // Set test config path for loading
        process.env.PLANE_INSTANCES_PATH = process.env.TEST_PLANE_INSTANCE_PATH || './plane-instances-test.json';
        
        // Load test configuration
        const instances = await loadPlaneConfig();
        const instance = instances[TEST_CONFIG.instanceName];
        if (!instance) {
            throw new Error(`Test instance "${TEST_CONFIG.instanceName}" not found in configuration`);
        }

        // Create test context
        const context: PromptContext = {
            progressToken: 'test',
            workspace: instance.defaultWorkspace
        };

        // Initialize client and tools
        client = new PlaneApiClient(instance, context);
        createTool = new CreateProjectTool(client);
        listTool = new ListProjectsTool(client);
        updateTool = new UpdateProjectTool(client);
        deleteTool = new DeleteProjectTool(client);
    });

    afterAll(async () => {
        // Cleanup: Delete test project if it exists
        if (testProjectId) {
            try {
                await deleteTool.execute({ project_id: testProjectId });
            } catch (error) {
                console.warn('Failed to cleanup test project:', error);
            }
        }
    });

    it('should create a new test project', async () => {
        const result = await createTool.execute(initialProjectData);
        expect(result.isError).toBe(false);
        
        const responseText = result.content[0].text;
        expect(responseText).toContain('Successfully created project');
        
        // Extract project ID from response text
        const match = responseText.match(/ID: ([^)]+)/);
        expect(match).toBeTruthy();
        testProjectId = match![1];
        expect(testProjectId).toBeTruthy();
    }, TEST_CONFIG.timeouts.create);

    it('should list projects and find the new project', async () => {
        const result = await listTool.execute({});
        expect(result.isError).toBe(false);
        
        const responseText = result.content[0].text;
        expect(responseText).toContain('Successfully retrieved');
        
        // Extract projects from response
        const match = responseText.match(/\[(.*)\]/);
        expect(match).toBeTruthy();
        const projects = JSON.parse(match![1]);
        
        const testProject = projects.find((p: any) => p.id === testProjectId);
        expect(testProject).toBeTruthy();
        expect(testProject.name).toBe(initialProjectData.name);
        expect(testProject.identifier).toBe(initialProjectData.identifier);
    }, TEST_CONFIG.timeouts.query);

    it('should update the test project', async () => {
        const updateData = {
            project_id: testProjectId,
            name: 'Updated Test Project',
            description: 'Updated test project description'
        };
        
        const result = await updateTool.execute(updateData);
        expect(result.isError).toBe(false);
        expect(result.content[0].text).toContain('Successfully updated project');
    }, TEST_CONFIG.timeouts.update);

    it('should delete the test project', async () => {
        const result = await deleteTool.execute({ project_id: testProjectId });
        expect(result.isError).toBe(false);
        expect(result.content[0].text).toContain('Successfully deleted project');
    }, TEST_CONFIG.timeouts.delete);
}); 

```

--------------------------------------------------------------------------------
/src/api/client.ts:
--------------------------------------------------------------------------------

```typescript
import { BaseApiClient } from './base-client.js';
import { PlaneInstanceConfig } from './types/config.js';
import { PromptContext } from '../types/prompt.js';

export interface NotificationOptions {
    type: 'info' | 'error' | 'warning' | 'success';
    message: string;
    source: string;
    data?: Record<string, unknown>;
}

export interface ToolExecutionOptions {
    progressToken: string;
    workspace?: string;
}

export interface CreateProjectData {
    name: string;
    identifier: string;
    description?: string;
    network?: number;
    emoji?: string;
    icon_prop?: Record<string, unknown>;
    module_view?: boolean;
    cycle_view?: boolean;
    issue_views_view?: boolean;
    page_view?: boolean;
    inbox_view?: boolean;
    cover_image?: string | null;
    archive_in?: number;
    close_in?: number;
    default_assignee?: string | null;
    project_lead?: string | null;
    estimate?: string | null;
    default_state?: string | null;
    [key: string]: unknown | undefined;
}

export interface UpdateProjectData {
    name?: string;
    description?: string;
    network?: number;
    emoji?: string;
    icon_prop?: Record<string, unknown>;
    module_view?: boolean;
    cycle_view?: boolean;
    issue_views_view?: boolean;
    page_view?: boolean;
    inbox_view?: boolean;
    cover_image?: string | null;
    archive_in?: number;
    close_in?: number;
    default_assignee?: string | null;
    project_lead?: string | null;
    estimate?: string | null;
    default_state?: string | null;
    [key: string]: unknown | undefined;
}

interface Project {
    id: string;
    name: string;
    identifier: string;
    description?: string;
    network?: number;
    emoji?: string;
    icon_prop?: Record<string, unknown>;
    module_view?: boolean;
    cycle_view?: boolean;
    issue_views_view?: boolean;
    page_view?: boolean;
    inbox_view?: boolean;
    cover_image?: string | null;
    archive_in?: number;
    close_in?: number;
    default_assignee?: string | null;
    project_lead?: string | null;
    estimate?: string | null;
    default_state?: string | null;
    [key: string]: unknown | undefined;
}

interface PaginatedResponse {
    results: Project[];
    count: number;
    next: string | null;
    previous: string | null;
    [key: string]: unknown;
}

export class PlaneApiClient extends BaseApiClient {
    protected _instance: PlaneInstanceConfig;

    constructor(instance: PlaneInstanceConfig, private context: PromptContext) {
        super(instance);
        this._instance = instance;
    }

    get instance(): PlaneInstanceConfig {
        return this._instance;
    }

    notify(options: NotificationOptions) {
        // Format notification as a JSON-RPC notification message
        const notification = {
            jsonrpc: '2.0',
            method: 'notification',
            params: {
                type: options.type,
                message: options.message,
                source: options.source,
                data: options.data || {}
            }
        };

        // Send the notification as a JSON string
        process.stdout.write(JSON.stringify(notification) + '\n');
    }

    async listProjects(workspace: string): Promise<Project[] | PaginatedResponse> {
        return this.get(`/workspaces/${workspace}/projects`);
    }

    async createProject(workspaceSlug: string, data: CreateProjectData): Promise<Project> {
        const response = await this.post<Project | PaginatedResponse>(`/workspaces/${workspaceSlug}/projects`, data);
        
        if (!response) {
            throw new Error('Failed to create project: No response from server');
        }

        // Handle paginated response
        if ('results' in response && Array.isArray(response.results)) {
            // Find the newly created project in the results
            const project = response.results.find(p => p.name === data.name && p.identifier === data.identifier);
            if (project) {
                return project;
            }
        }
        
        // Handle direct project response
        if ('id' in response) {
            return response as Project;
        }
        
        throw new Error('Failed to create project: Invalid response format from server');
    }

    async updateProject(workspaceSlug: string, projectId: string, data: UpdateProjectData): Promise<Project> {
        return this.put(`/workspaces/${workspaceSlug}/projects/${projectId}`, data);
    }

    async deleteProject(workspaceSlug: string, projectId: string): Promise<void> {
        await this.delete(`/workspaces/${workspaceSlug}/projects/${projectId}`);
    }

    async executeTool(toolName: string, options: ToolExecutionOptions): Promise<{ content: Array<{ text: string }> }> {
        // This is a mock implementation - the actual implementation will be provided by the MCP server
        return {
            content: [{
                text: '[]' // Default empty array as JSON string
            }]
        };
    }
} 
```

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

```typescript
import { z } from 'zod';
import { Tool, ToolResponse } from '../../types/mcp.js';
import { PlaneApiClient } from '../../api/client.js';

const zodInputSchema = z.object({
    workspace_slug: z.string().optional(),
    name: z.string(),
    identifier: z.string(),
    description: z.string().optional(),
    network: z.number().min(0).max(2).optional(),
    emoji: z.string().optional(),
    module_view: z.boolean().optional(),
    cycle_view: z.boolean().optional(),
    issue_views_view: z.boolean().optional(),
    page_view: z.boolean().optional(),
    inbox_view: z.boolean().optional()
});

export class CreateProjectTool implements Tool {
    name = 'claudeus_plane_projects__create';
    description = 'Creates a new project in a Plane workspace';
    inputSchema = {
        type: 'object',
        properties: {
            workspace_slug: {
                type: 'string',
                description: 'The workspace to create the project in. If not provided, the default workspace will be used.'
            },
            name: {
                type: 'string',
                description: 'The name of the project'
            },
            identifier: {
                type: 'string',
                description: 'The unique identifier for the project'
            },
            description: {
                type: 'string',
                description: 'A description of the project'
            },
            network: {
                type: 'number',
                description: 'The network visibility of the project (0: Private, 1: Public, 2: Internal)'
            },
            emoji: {
                type: 'string',
                description: 'The emoji to use for the project'
            },
            module_view: {
                type: 'boolean',
                description: 'Whether to enable module view'
            },
            cycle_view: {
                type: 'boolean',
                description: 'Whether to enable cycle view'
            },
            issue_views_view: {
                type: 'boolean',
                description: 'Whether to enable issue views'
            },
            page_view: {
                type: 'boolean',
                description: 'Whether to enable page view'
            },
            inbox_view: {
                type: 'boolean',
                description: 'Whether to enable inbox view'
            }
        },
        required: ['name', 'identifier']
    };

    constructor(private client: PlaneApiClient) {}

    async execute(args: Record<string, unknown>): Promise<ToolResponse> {
        try {
            const input = zodInputSchema.parse(args);
            const { workspace_slug, ...projectData } = input;
            const workspace = workspace_slug || this.client.instance.defaultWorkspace;

            this.client.notify({
                type: 'info',
                message: `Creating project "${projectData.name}" in workspace: ${workspace}`,
                source: this.name,
                data: {
                    workspace,
                    ...projectData
                }
            });

            try {
                const project = await this.client.createProject(workspace, projectData);
                
                this.client.notify({
                    type: 'success',
                    message: `Successfully created project "${project.name}" (ID: ${project.id}) in workspace "${workspace}"`,
                    source: this.name,
                    data: {
                        workspace,
                        projectId: project.id
                    }
                });

                return {
                    isError: false,
                    content: [{
                        type: 'text',
                        text: `Successfully created project "${project.name}" (ID: ${project.id}) in workspace "${workspace}"`
                    }]
                };
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                this.client.notify({
                    type: 'error',
                    message: `Failed to create project: ${errorMessage}`,
                    source: this.name,
                    data: {
                        error: errorMessage
                    }
                });

                return {
                    isError: true,
                    content: [{
                        type: 'text',
                        text: `Failed to create project: ${errorMessage}`
                    }]
                };
            }
        } catch (error) {
            if (error instanceof z.ZodError) {
                throw error;
            }
            const errorMessage = error instanceof Error ? error.message : 'Unknown error';
            return {
                isError: true,
                content: [{
                    type: 'text',
                    text: `Failed to create project: ${errorMessage}`
                }]
            };
        }
    }
} 

```

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

```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { UpdateProjectTool } from '../update.js';
import { PlaneApiClient } from '../../../api/client.js';
import { PlaneInstance } from '../../../config/plane-config.js';

// Mock the PlaneApiClient
vi.mock('../../../api/client.js', () => {
    return {
        PlaneApiClient: vi.fn().mockImplementation((instance, context) => ({
            instance,
            updateProject: vi.fn(),
            notify: vi.fn()
        }))
    };
});

describe('UpdateProjectTool', () => {
    let tool: UpdateProjectTool;
    let mockClient: PlaneApiClient;
    
    beforeEach(() => {
        // Create a mock instance
        const mockInstance: PlaneInstance = {
            name: 'test',
            baseUrl: 'https://test.plane.so',
            apiKey: 'test-key',
            defaultWorkspace: 'test-workspace'
        };

        // Create a mock context
        const mockContext = {
            progressToken: '123',
            workspace: 'test-workspace'
        };

        // Create a mock client
        mockClient = new PlaneApiClient(mockInstance, mockContext);
        
        // Create the tool instance
        tool = new UpdateProjectTool(mockClient);
    });

    it('should update a project with minimal fields', async () => {
        const mockProject = {
            id: 'test-id',
            name: 'Updated Project',
            identifier: 'TEST'
        };

        (mockClient.updateProject as any).mockResolvedValue(mockProject);

        const result = await tool.execute({
            project_id: 'test-id',
            name: 'Updated Project'
        });

        expect(mockClient.updateProject).toHaveBeenCalledWith('test-workspace', 'test-id', {
            name: 'Updated Project'
        });

        expect(result.content[0].text).toContain('Successfully updated project');
        expect(result.content[0].text).toContain(mockProject.id);
    });

    it('should update a project with all optional fields', async () => {
        const mockProject = {
            id: 'test-id',
            name: 'Updated Project',
            identifier: 'TEST',
            description: 'Updated Description',
            network: 2,
            emoji: '1f680',
            module_view: false,
            cycle_view: false,
            issue_views_view: false,
            page_view: false,
            inbox_view: true
        };

        (mockClient.updateProject as any).mockResolvedValue(mockProject);

        const result = await tool.execute({
            project_id: 'test-id',
            name: 'Updated Project',
            identifier: 'TEST',
            description: 'Updated Description',
            network: 2,
            emoji: '1f680',
            module_view: false,
            cycle_view: false,
            issue_views_view: false,
            page_view: false,
            inbox_view: true
        });

        expect(mockClient.updateProject).toHaveBeenCalledWith('test-workspace', 'test-id', {
            name: 'Updated Project',
            identifier: 'TEST',
            description: 'Updated Description',
            network: 2,
            emoji: '1f680',
            module_view: false,
            cycle_view: false,
            issue_views_view: false,
            page_view: false,
            inbox_view: true
        });

        expect(result.content[0].text).toContain('Successfully updated project');
        expect(result.content[0].text).toContain(mockProject.id);
    });

    it('should use provided workspace instead of default', async () => {
        const mockProject = {
            id: 'test-id',
            name: 'Updated Project'
        };

        (mockClient.updateProject as any).mockResolvedValue(mockProject);

        await tool.execute({
            workspace_slug: 'custom-workspace',
            project_id: 'test-id',
            name: 'Updated Project'
        });

        expect(mockClient.updateProject).toHaveBeenCalledWith('custom-workspace', 'test-id', {
            name: 'Updated Project'
        });
    });

    it('should handle API errors', async () => {
        const errorMessage = 'API Error: Project update failed';
        (mockClient.updateProject as any).mockRejectedValue(new Error(errorMessage));

        const result = await tool.execute({
            project_id: 'test-id',
            name: 'Updated Project'
        });

        expect(result.isError).toBe(true);
        expect(result.content[0].text).toContain(errorMessage);
        expect(mockClient.notify).toHaveBeenCalledWith(expect.objectContaining({
            type: 'error',
            message: expect.stringContaining('Failed to update project')
        }));
    });

    it('should validate required fields', async () => {
        await expect(tool.execute({
            name: 'Updated Project'
            // Missing project_id
        })).rejects.toThrow();
    });

    it('should validate field types', async () => {
        await expect(tool.execute({
            project_id: 'test-id',
            network: 3 // Invalid network value
        })).rejects.toThrow();

        await expect(tool.execute({
            project_id: 'test-id',
            archive_in: 13 // Invalid archive_in value
        })).rejects.toThrow();
    });
}); 
```

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

```typescript
import { Tool, ToolResponse } from '../../types/mcp.js';
import { IssuesClient } from '../../api/issues/client.js';
import { UpdateIssueData, IssuePriority } from '../../api/issues/types.js';
import { PlaneInstanceConfig } from '../../api/types/config.js';

interface UpdateIssueInput extends UpdateIssueData {
  workspace_slug?: string;
  project_id: string;
  issue_id: string;
}

export class UpdateIssueTool implements Tool {
  private issuesClient: IssuesClient;
  private instance: PlaneInstanceConfig;

  name = 'claudeus_plane_issues__update';
  description = 'Updates an existing issue in a Plane project';
  status = 'enabled' as const;
  inputSchema = {
    type: 'object',
    properties: {
      workspace_slug: {
        type: 'string',
        description: 'The slug of the workspace containing the issue. If not provided, uses the default workspace.'
      },
      project_id: {
        type: 'string',
        description: 'The ID of the project containing the issue'
      },
      issue_id: {
        type: 'string',
        description: 'The ID of the issue to update'
      },
      name: {
        type: 'string',
        description: 'The new name/title of the issue'
      },
      description_html: {
        type: 'string',
        description: 'The new HTML description of the issue'
      },
      priority: {
        type: 'string',
        enum: ['urgent', 'high', 'medium', 'low', 'none'],
        description: 'The new priority of the issue'
      },
      start_date: {
        type: 'string',
        format: 'date',
        description: 'The new start date of the issue (YYYY-MM-DD)'
      },
      target_date: {
        type: 'string',
        format: 'date',
        description: 'The new target date of the issue (YYYY-MM-DD)'
      },
      estimate_point: {
        type: 'number',
        description: 'The new story points or time estimate for the issue'
      },
      state: {
        type: 'string',
        description: 'The new state ID for the issue'
      },
      assignees: {
        type: 'array',
        items: {
          type: 'string'
        },
        description: 'New array of user IDs to assign to the issue'
      },
      labels: {
        type: 'array',
        items: {
          type: 'string'
        },
        description: 'New array of label IDs to apply to the issue'
      },
      parent: {
        type: 'string',
        description: 'New parent issue ID (for sub-issues)'
      },
      is_draft: {
        type: 'boolean',
        description: 'Whether this issue should be marked as draft'
      },
      archived_at: {
        type: 'string',
        format: 'date-time',
        description: 'When to archive the issue (ISO 8601 format)'
      },
      completed_at: {
        type: 'string',
        format: 'date-time',
        description: 'When the issue was completed (ISO 8601 format)'
      }
    },
    required: ['project_id', 'issue_id']
  };

  constructor(instance: PlaneInstanceConfig) {
    this.instance = instance;
    this.issuesClient = new IssuesClient(this.instance);
  }

  async execute(args: Record<string, unknown>): Promise<ToolResponse> {
    // Type cast with validation
    const input = args as unknown as UpdateIssueInput;
    if (!this.validateInput(input)) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Invalid input: missing required fields'
        }]
      };
    }

    const {
      workspace_slug = this.instance.defaultWorkspace,
      project_id,
      issue_id,
      ...updateData
    } = input;

    // Validate workspace
    if (!workspace_slug) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Workspace slug is required'
        }]
      };
    }

    // Validate project ID
    if (!project_id) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Project ID is required'
        }]
      };
    }

    // Validate issue ID
    if (!issue_id) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'Issue ID is required'
        }]
      };
    }

    // Validate that at least one field is being updated
    if (Object.keys(updateData).length === 0) {
      return {
        isError: true,
        content: [{
          type: 'text',
          text: 'At least one field must be provided for update'
        }]
      };
    }

    try {
      const response = await this.issuesClient.update(
        workspace_slug,
        project_id,
        issue_id,
        updateData
      );

      return {
        content: [{
          type: 'text',
          text: JSON.stringify(response)
        }]
      };
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      return {
        isError: true,
        content: [{
          type: 'text',
          text: `Failed to update issue: ${errorMessage}`
        }]
      };
    }
  }

  private validateInput(input: unknown): input is UpdateIssueInput {
    if (typeof input !== 'object' || input === null) return false;
    const data = input as Record<string, unknown>;
    return typeof data.project_id === 'string' && typeof data.issue_id === 'string';
  }
} 
```

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

```typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { CreateProjectTool } from '../create.js';
import { PlaneApiClient } from '../../../api/client.js';
import { PlaneInstance } from '../../../config/plane-config.js';

// Mock the PlaneApiClient
vi.mock('../../../api/client.js', () => {
    return {
        PlaneApiClient: vi.fn().mockImplementation((instance, context) => ({
            instance,
            createProject: vi.fn(),
            notify: vi.fn()
        }))
    };
});

describe('CreateProjectTool', () => {
    let tool: CreateProjectTool;
    let mockClient: PlaneApiClient;
    
    beforeEach(() => {
        // Create a mock instance
        const mockInstance: PlaneInstance = {
            name: 'test',
            baseUrl: 'https://test.plane.so',
            apiKey: 'test-key',
            defaultWorkspace: 'test-workspace'
        };

        // Create a mock context
        const mockContext = {
            progressToken: '123',
            workspace: 'test-workspace'
        };

        // Create a mock client
        mockClient = new PlaneApiClient(mockInstance, mockContext);
        
        // Create the tool instance
        tool = new CreateProjectTool(mockClient);
    });

    it('should create a project with minimal required fields', async () => {
        const mockProject = {
            id: 'test-id',
            name: 'Test Project',
            identifier: 'TEST'
        };

        (mockClient.createProject as any).mockResolvedValue(mockProject);

        const result = await tool.execute({
            name: 'Test Project',
            identifier: 'TEST'
        });

        expect(mockClient.createProject).toHaveBeenCalledWith('test-workspace', {
            name: 'Test Project',
            identifier: 'TEST'
        });

        expect(result.content[0].text).toContain('Successfully created project');
        expect(result.content[0].text).toContain(mockProject.id);
    });

    it('should create a project with all optional fields', async () => {
        const mockProject = {
            id: 'test-id',
            name: 'Test Project',
            identifier: 'TEST',
            description: 'Test Description',
            network: 2,
            emoji: '1f680',
            module_view: true,
            cycle_view: true,
            issue_views_view: true,
            page_view: true,
            inbox_view: false
        };

        (mockClient.createProject as any).mockResolvedValue(mockProject);

        const result = await tool.execute({
            name: 'Test Project',
            identifier: 'TEST',
            description: 'Test Description',
            network: 2,
            emoji: '1f680',
            module_view: true,
            cycle_view: true,
            issue_views_view: true,
            page_view: true,
            inbox_view: false
        });

        expect(mockClient.createProject).toHaveBeenCalledWith('test-workspace', {
            name: 'Test Project',
            identifier: 'TEST',
            description: 'Test Description',
            network: 2,
            emoji: '1f680',
            module_view: true,
            cycle_view: true,
            issue_views_view: true,
            page_view: true,
            inbox_view: false
        });

        expect(result.content[0].text).toContain('Successfully created project');
        expect(result.content[0].text).toContain(mockProject.id);
    });

    it('should use provided workspace instead of default', async () => {
        const mockProject = {
            id: 'test-id',
            name: 'Test Project',
            identifier: 'TEST'
        };

        (mockClient.createProject as any).mockResolvedValue(mockProject);

        await tool.execute({
            workspace_slug: 'custom-workspace',
            name: 'Test Project',
            identifier: 'TEST'
        });

        expect(mockClient.createProject).toHaveBeenCalledWith('custom-workspace', {
            name: 'Test Project',
            identifier: 'TEST'
        });
    });

    it('should handle API errors', async () => {
        const errorMessage = 'API Error: Project creation failed';
        (mockClient.createProject as any).mockRejectedValue(new Error(errorMessage));

        const result = await tool.execute({
            name: 'Test Project',
            identifier: 'TEST'
        });

        expect(result.isError).toBe(true);
        expect(result.content[0].text).toContain(errorMessage);
        expect(mockClient.notify).toHaveBeenCalledWith(expect.objectContaining({
            type: 'error',
            message: expect.stringContaining('Failed to create project')
        }));
    });

    it('should validate required fields', async () => {
        await expect(tool.execute({
            name: 'Test Project'
            // Missing identifier
        })).rejects.toThrow();

        await expect(tool.execute({
            identifier: 'TEST'
            // Missing name
        })).rejects.toThrow();
    });

    it('should validate field types', async () => {
        await expect(tool.execute({
            name: 'Test Project',
            identifier: 'TEST',
            network: 3 // Invalid network value
        })).rejects.toThrow();

        await expect(tool.execute({
            name: 'Test Project',
            identifier: 'TEST',
            archive_in: 13 // Invalid archive_in value
        })).rejects.toThrow();
    });
}); 

```

--------------------------------------------------------------------------------
/src/test/mcp-test-harness.ts:
--------------------------------------------------------------------------------

```typescript
import { EventEmitter } from 'events';
import dummyProjects from '@/dummy-data/projects.json' assert { type: 'json' };

export interface MCPContentItem {
  type: string;
  text: string;
}

export interface MCPMessage {
  jsonrpc: '2.0';
  id: number;
  method?: string;
  params?: Record<string, unknown>;
  result?: {
    content?: MCPContentItem[];
    capabilities?: Record<string, unknown>;
    [key: string]: unknown;
  };
  error?: {
    code: number;
    message: string;
    data?: unknown;
  };
}

interface Transport {
  onMessage: (handler: (message: MCPMessage) => void) => void;
  send: (message: MCPMessage) => void;
}

class InMemoryTransport implements Transport {
  private messageHandler?: (message: MCPMessage) => void;
  private otherTransport?: InMemoryTransport;

  onMessage(handler: (message: MCPMessage) => void): void {
    this.messageHandler = handler;
  }

  send(message: MCPMessage): void {
    if (this.otherTransport?.messageHandler) {
      this.otherTransport.messageHandler(message);
    }
  }

  static createLinkedPair(): [InMemoryTransport, InMemoryTransport] {
    const client = new InMemoryTransport();
    const server = new InMemoryTransport();
    client.otherTransport = server;
    server.otherTransport = client;
    return [client, server];
  }
}

export class MCPTestHarness {
  private clientTransport: InMemoryTransport;
  private serverTransport: InMemoryTransport;
  private responseHandlers = new Map<number, (response: MCPMessage) => void>();
  private isConnected = false;
  private nextMessageId = 1;

  constructor() {
    [this.clientTransport, this.serverTransport] = InMemoryTransport.createLinkedPair();
    
    // Set up server-side message handling
    this.serverTransport.onMessage((message: MCPMessage) => {
      if (message.method === 'initialize') {
        this.handleInitialize(message);
      } else if (message.method === 'tool/call') {
        this.handleToolCall(message);
      }
    });

    // Set up client-side message handling
    this.clientTransport.onMessage((message: MCPMessage) => {
      if (message.id && this.responseHandlers.has(message.id)) {
        const handler = this.responseHandlers.get(message.id)!;
        handler(message);
      }
    });
  }

  private handleInitialize(message: MCPMessage): void {
    if (!message.id) {
      return;
    }
    
    this.serverTransport.send({
      jsonrpc: '2.0',
      id: message.id,
      result: {
        capabilities: {
          sampling: {},
          roots: { listChanged: true }
        }
      }
    });
  }

  private handleToolCall(message: MCPMessage): void {
    if (!message.id || !message.params) {
      return;
    }

    const { name, args } = message.params as { name: string; args: Record<string, unknown> };
    
    if (name === 'claudeus_plane_projects__list') {
      if (!args.workspace) {
        this.serverTransport.send({
          jsonrpc: '2.0',
          id: message.id,
          result: {
            content: [{
              type: 'text',
              text: 'Error: Missing required workspace parameter'
            }]
          }
        });
        return;
      }

      if (args.workspace === 'invalid-workspace-id') {
        this.serverTransport.send({
          jsonrpc: '2.0',
          id: message.id,
          result: {
            content: [{
              type: 'text',
              text: 'Error: Invalid workspace ID'
            }]
          }
        });
        return;
      }

      // Return actual dummy projects for valid workspace
      this.serverTransport.send({
        jsonrpc: '2.0',
        id: message.id,
        result: {
          content: [{
            type: 'text',
            text: JSON.stringify(dummyProjects)
          }]
        }
      });
    }
  }

  async connect(): Promise<MCPMessage> {
    if (this.isConnected) {
      throw new Error('Already connected');
    }
    this.isConnected = true;
    return this.sendInitialize();
  }

  async sendInitialize(): Promise<MCPMessage> {
    const initMessage: MCPMessage = {
      jsonrpc: '2.0',
      id: this.nextMessageId++,
      method: 'initialize',
      params: {
        capabilities: {
          sampling: {},
          roots: { listChanged: true }
        }
      }
    };
    
    return this.sendMessage(initMessage);
  }

  async sendMessage(message: MCPMessage): Promise<MCPMessage> {
    if (!this.isConnected) {
      throw new Error('Not connected');
    }

    if (!message.id) {
      throw new Error('Message must have an ID');
    }

    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        this.responseHandlers.delete(message.id!);
        reject(new Error('Message timeout'));
      }, 5000);

      this.responseHandlers.set(message.id, (response: MCPMessage) => {
        clearTimeout(timeout);
        this.responseHandlers.delete(message.id!);
        resolve(response);
      });

      this.clientTransport.send(message);
    });
  }

  async callTool(name: string, args: Record<string, unknown>): Promise<MCPMessage> {
    const message: MCPMessage = {
      jsonrpc: '2.0',
      id: this.nextMessageId++,
      method: 'tool/call',
      params: {
        name,
        args
      }
    };

    return this.sendMessage(message);
  }

  onServerMessage(message: MCPMessage): void {
    this.serverTransport.send(message);
  }

  clearHandlers(): void {
    this.responseHandlers.clear();
    this.isConnected = false;
  }
} 
```

--------------------------------------------------------------------------------
/src/dummy-data/projects.json:
--------------------------------------------------------------------------------

```json
{
    "grouped_by": null,
    "sub_grouped_by": null,
    "total_count": 3,
    "next_cursor": "1000:1:0",
    "prev_cursor": "1000:-1:1", 
    "next_page_results": false,
    "prev_page_results": false,
    "count": 3,
    "total_pages": 1,
    "total_results": 3,
    "extra_stats": null,
    "results": [
      {
        "id": "01234567-89ab-cdef-0123-456789abcdef",
        "total_members": 3,
        "total_cycles": 2,
        "total_modules": 1,
        "is_member": true,
        "sort_order": 65535,
        "member_role": 20,
        "is_deployed": true,
        "cover_image_url": "https://images.unsplash.com/photo-1234567890",
        "inbox_view": true,
        "created_at": "2024-01-01T12:00:00.000000+01:00",
        "updated_at": "2024-01-02T12:00:00.000000+01:00",
        "deleted_at": null,
        "name": "Example Project",
        "description": "This is an example project description that demonstrates the format of project data in the Plane API. It includes various fields and properties that would typically be associated with a project.",
        "description_text": null,
        "description_html": null,
        "network": 1,
        "identifier": "EXAMPLE",
        "emoji": "📝",
        "icon_prop": null,
        "module_view": true,
        "cycle_view": true,
        "issue_views_view": true,
        "page_view": true,
        "intake_view": true,
        "is_time_tracking_enabled": true,
        "is_issue_type_enabled": true,
        "guest_view_all_features": false,
        "cover_image": "https://images.unsplash.com/photo-1234567890",
        "archive_in": 0,
        "close_in": 0,
        "logo_props": {
          "icon": {
            "name": "document",
            "color": "#4a90e2"
          },
          "in_use": "icon"
        },
        "archived_at": null,
        "timezone": "UTC",
        "created_by": "00000000-0000-0000-0000-000000000001",
        "updated_by": "00000000-0000-0000-0000-000000000001",
        "workspace": "00000000-0000-0000-0000-000000000002",
        "default_assignee": "00000000-0000-0000-0000-000000000001",
        "project_lead": "00000000-0000-0000-0000-000000000001",
        "cover_image_asset": null,
        "estimate": "00000000-0000-0000-0000-000000000003",
        "default_state": null
      },
      {
        "id": "1cd54b31-bc91-5747-b2b6-c7588ddc76c5",
        "total_members": 3,
        "total_cycles": 2,
        "total_modules": 2,
        "is_member": true,
        "sort_order": 65534,
        "member_role": 20,
        "is_deployed": true,
        "cover_image_url": "https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&q=80&ixlib=rb-4.0.3",
        "inbox_view": true,
        "created_at": "2025-01-15T10:15:33.224935+01:00",
        "updated_at": "2025-01-24T16:45:12.445123+01:00",
        "deleted_at": null,
        "name": "MCP Framework",
        "description": "Development of the Multi-Client Protocol (MCP) Framework for standardized API integrations across all SimHop services. This framework will serve as the foundation for all our future client integrations and internal tools.",
        "description_text": null,
        "description_html": null,
        "network": 1,
        "identifier": "MCP",
        "emoji": "🔌",
        "icon_prop": null,
        "module_view": true,
        "cycle_view": true,
        "issue_views_view": true,
        "page_view": true,
        "intake_view": true,
        "is_time_tracking_enabled": true,
        "is_issue_type_enabled": true,
        "guest_view_all_features": false,
        "cover_image": "https://images.unsplash.com/photo-1460925895917-afdab827c52f?auto=format&fit=crop&q=80&ixlib=rb-4.0.3",
        "archive_in": 0,
        "close_in": 0,
        "logo_props": {
          "icon": {
            "name": "plug",
            "color": "#3366ff"
          },
          "in_use": "icon"
        },
        "archived_at": null,
        "timezone": "UTC",
        "created_by": "52ab338c-2239-48fe-8e18-588bb17a78fc",
        "updated_by": "52ab338c-2239-48fe-8e18-588bb17a78fc",
        "workspace": "6bb6e42b-0bb7-43a2-b561-677dc52df44f",
        "default_assignee": "52ab338c-2239-48fe-8e18-588bb17a78fc",
        "project_lead": "52ab338c-2239-48fe-8e18-588bb17a78fc",
        "cover_image_asset": null,
        "estimate": "146ba6b4-a645-49a5-a57f-59800f5a8cd6",
        "default_state": null
      },
      {
        "id": "2ef65c42-cd92-6858-c3c7-d8699eed87d6",
        "total_members": 2,
        "total_cycles": 3,
        "total_modules": 1,
        "is_member": true,
        "sort_order": 65533,
        "member_role": 20,
        "is_deployed": false,
        "cover_image_url": "https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&q=80&ixlib=rb-4.0.3",
        "inbox_view": true,
        "created_at": "2025-01-20T14:30:45.334123+01:00",
        "updated_at": "2025-01-25T09:15:22.556789+01:00",
        "deleted_at": null,
        "name": "AI Assistant Hub",
        "description": "Creation of an AI-powered assistant hub that integrates with our existing tools and services. This project focuses on developing intelligent automation and support features to enhance productivity across all SimHop operations.",
        "description_text": null,
        "description_html": null,
        "network": 3,
        "identifier": "AIH",
        "emoji": "🤖",
        "icon_prop": null,
        "module_view": true,
        "cycle_view": true,
        "issue_views_view": true,
        "page_view": true,
        "intake_view": true,
        "is_time_tracking_enabled": true,
        "is_issue_type_enabled": true,
        "guest_view_all_features": false,
        "cover_image": "https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&q=80&ixlib=rb-4.0.3",
        "archive_in": 0,
        "close_in": 0,
        "logo_props": {
          "icon": {
            "name": "robot",
            "color": "#00cc88"
          },
          "in_use": "icon"
        },
        "archived_at": null,
        "timezone": "UTC",
        "created_by": "52ab338c-2239-48fe-8e18-588bb17a78fc",
        "updated_by": "52ab338c-2239-48fe-8e18-588bb17a78fc",
        "workspace": "6bb6e42b-0bb7-43a2-b561-677dc52df44f",
        "default_assignee": "52ab338c-2239-48fe-8e18-588bb17a78fc",
        "project_lead": "52ab338c-2239-48fe-8e18-588bb17a78fc",
        "cover_image_asset": null,
        "estimate": "146ba6b4-a645-49a5-a57f-59800f5a8cd6",
        "default_state": null
      }
    ]
  }
```

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

```typescript
import { z } from 'zod';
import { Tool, ToolResponse } from '../../types/mcp.js';
import { PlaneApiClient } from '../../api/client.js';

const inputSchema = {
    type: 'object',
    properties: {
        workspace_slug: {
            type: 'string',
            description: 'The slug of the workspace containing the project. If not provided, uses the default workspace.'
        },
        project_id: {
            type: 'string',
            description: 'The ID of the project to update (required)'
        },
        // Optional fields - any field can be updated
        name: {
            type: 'string',
            description: 'New name for the project'
        },
        identifier: {
            type: 'string',
            description: 'New unique identifier for the project in the workspace. Example: "PROJ1"'
        },
        description: {
            type: 'string',
            description: 'New description for the project'
        },
        network: {
            type: 'integer',
            description: 'Project visibility: 0 for Secret (private), 2 for Public',
            enum: [0, 2]
        },
        emoji: {
            type: 'string',
            description: 'HTML emoji DEX code without the "&#". Example: "1f680" for rocket'
        },
        icon_prop: {
            type: 'object',
            description: 'Custom icon properties for the project'
        },
        module_view: {
            type: 'boolean',
            description: 'Enable/disable module view for the project'
        },
        cycle_view: {
            type: 'boolean',
            description: 'Enable/disable cycle view for the project'
        },
        issue_views_view: {
            type: 'boolean',
            description: 'Enable/disable project views for the project'
        },
        page_view: {
            type: 'boolean',
            description: 'Enable/disable pages for the project'
        },
        inbox_view: {
            type: 'boolean',
            description: 'Enable/disable intake for the project'
        },
        cover_image: {
            type: 'string',
            description: 'URL for the project cover image'
        },
        archive_in: {
            type: 'integer',
            description: 'Months in which issues should be automatically archived (0-12)',
            minimum: 0,
            maximum: 12
        },
        close_in: {
            type: 'integer',
            description: 'Months in which issues should be auto-closed (0-12)',
            minimum: 0,
            maximum: 12
        },
        default_assignee: {
            type: 'string',
            description: 'UUID of the user who will be the default assignee for issues'
        },
        project_lead: {
            type: 'string',
            description: 'UUID of the user who will lead the project'
        },
        estimate: {
            type: 'string',
            description: 'UUID of the estimate to use for the project'
        },
        default_state: {
            type: 'string',
            description: 'Default state to use when issues are auto-closed'
        }
    },
    required: ['project_id']
};

const zodInputSchema = z.object({
    workspace_slug: z.string().optional(),
    project_id: z.string(),
    // All fields are optional for updates
    name: z.string().optional(),
    identifier: z.string().optional(),
    description: z.string().optional(),
    network: z.number().min(0).max(2).optional(),
    emoji: z.string().optional(),
    icon_prop: z.record(z.unknown()).optional(),
    module_view: z.boolean().optional(),
    cycle_view: z.boolean().optional(),
    issue_views_view: z.boolean().optional(),
    page_view: z.boolean().optional(),
    inbox_view: z.boolean().optional(),
    cover_image: z.string().nullable().optional(),
    archive_in: z.number().min(0).max(12).optional(),
    close_in: z.number().min(0).max(12).optional(),
    default_assignee: z.string().nullable().optional(),
    project_lead: z.string().nullable().optional(),
    estimate: z.string().nullable().optional(),
    default_state: z.string().nullable().optional()
});

export class UpdateProjectTool implements Tool {
    name = 'claudeus_plane_projects__update';
    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.';
    status: 'enabled' | 'disabled' = 'enabled';
    inputSchema = inputSchema;

    constructor(private client: PlaneApiClient) {}

    async execute(args: Record<string, unknown>): Promise<ToolResponse> {
        const input = zodInputSchema.parse(args);
        const { workspace_slug, project_id, ...updateData } = input;

        try {
            // Use the workspace from config if not provided
            const workspace = workspace_slug || this.client.instance.defaultWorkspace;
            if (!workspace) {
                throw new Error('No workspace provided or configured');
            }

            this.client.notify({
                type: 'info',
                message: `Updating project ${project_id} in workspace: ${workspace}`,
                source: this.name,
                data: { workspace, project_id, ...updateData }
            });

            const project = await this.client.updateProject(workspace, project_id, updateData);
            
            this.client.notify({
                type: 'info',
                message: `Successfully updated project ${project_id}`,
                data: { 
                    projectId: project.id,
                    workspace
                },
                source: this.name
            });

            return {
                content: [{
                    type: 'text',
                    text: `Successfully updated project (ID: ${project.id}) in workspace "${workspace}"\n\nUpdated project details:\n${JSON.stringify(project, null, 2)}`
                }]
            };
        } catch (error) {
            if (error instanceof Error) {
                this.client.notify({
                    type: 'error',
                    message: `Failed to update project: ${error.message}`,
                    data: { 
                        error: error.message,
                        workspace: workspace_slug,
                        project_id
                    },
                    source: this.name
                });

                return {
                    isError: true,
                    content: [{
                        type: 'text',
                        text: `Failed to update project: ${error.message}`
                    }]
                };
            }
            throw error;
        }
    }
} 

```

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

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { 
    ListToolsRequestSchema, 
    CallToolRequestSchema,
    ListResourcesRequestSchema,
    ListResourceTemplatesRequestSchema,
    ReadResourceRequestSchema,
    ServerResult
} from '@modelcontextprotocol/sdk/types.js';
import { PlaneApiClient } from '../api/client.js';
import { allTools } from '../tools/index.js';
import { DEFAULT_INSTANCE } from '../config/plane-config.js';
import { z } from 'zod';
import { Tool, ToolResponse } from '../types/mcp.js';
import { McpServer } from './server.js';

interface MCPMessage {
    jsonrpc: '2.0';
    id?: number;
    method?: string;
    params?: Record<string, unknown>;
    result?: {
        content?: Array<{ type: string; text: string }>;
        _meta?: Record<string, unknown>;
    };
}

function constructResourceUri(name: string, url: string): string {
    return `plane://${name}@${new URL(url).hostname}`;
}

export function registerTools(server: Server, clients: Map<string, PlaneApiClient>) {
    // Register resource handlers
    server.setRequestHandler(ListResourcesRequestSchema, async () => {
        const resources = Array.from(clients.entries()).map(([name, client]) => ({
            id: name,
            name: `Instance: ${name}`,
            type: "plane_instance",
            uri: constructResourceUri(name, client.baseUrl),
            metadata: {
                url: client.baseUrl,
                authType: "api_key"
            }
        }));
        return { resources };
    });

    server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
        if (!request.params?.uri || typeof request.params.uri !== 'string') {
            throw { code: -32602, message: 'Resource URI must be a non-empty string' };
        }

        const match = request.params.uri.match(/^plane:\/\/([^@]+)@/);
        if (!match) {
            throw { code: -32602, message: `Invalid Plane resource URI format: ${request.params.uri}` };
        }

        const name = match[1];
        const client = clients.get(name);
        if (!client) {
            throw { code: -32602, message: `Unknown instance: ${name}` };
        }

        return {
            resource: {
                id: name,
                name: `Instance: ${name}`,
                type: "plane_instance",
                uri: constructResourceUri(name, client.baseUrl),
                metadata: {
                    url: client.baseUrl,
                    authType: "api_key",
                    capabilities: {
                        projects: true,
                        issues: true,
                        cycles: true,
                        modules: true
                    }
                }
            },
            contents: [{
                type: 'text',
                uri: constructResourceUri(name, client.baseUrl),
                text: JSON.stringify({
                    url: client.baseUrl,
                    authType: "api_key",
                    capabilities: {
                        projects: true,
                        issues: true,
                        cycles: true,
                        modules: true
                    }
                }, null, 2)
            }]
        };
    });

    server.setRequestHandler(ListResourceTemplatesRequestSchema, async (request) => {
        const resourceId = request.params?.id;
        if (!resourceId || typeof resourceId !== 'string') {
            return { resourceTemplates: [] };
        }

        const client = clients.get(resourceId);
        if (!client) {
            return { resourceTemplates: [] };
        }

        return {
            resourceTemplates: [{
                id: "claudeus_plane_discover_endpoints_template",
                name: "Discover Endpoints",
                description: "Discover available REST API endpoints on this Plane instance",
                tool: "claudeus_plane_discover_endpoints",
                arguments: {
                    instance: resourceId
                }
            }]
        };
    });

    // Register tool handlers
    server.setRequestHandler(ListToolsRequestSchema, async (request) => {
        const instance = (request.params?.instance as string) || DEFAULT_INSTANCE;
        const client = clients.get(instance);
        
        if (!client) {
            throw new Error(`Unknown instance: ${instance}`);
        }

        return {
            tools: allTools.map(tool => ({
                name: tool.name,
                description: tool.description,
                status: tool.status || 'enabled',
                inputSchema: tool.inputSchema || { type: 'object', properties: {} }
            }))
        };
    });

    server.setRequestHandler(CallToolRequestSchema, async (request): Promise<ServerResult> => {
        const { name, arguments: args } = request.params;
        const toolDef = allTools.find(t => t.name === name);

        if (!toolDef) {
            throw new Error(`Tool not found: ${name}`);
        }

        const instance = (args?.instance as string) || DEFAULT_INSTANCE;
        const client = clients.get(instance);
        if (!client) {
            throw new Error(`Unknown instance: ${instance}`);
        }

        const toolInstance = new toolDef.class(client);
        const result = await toolInstance.execute(args || {});

        return {
            content: result.content,
            _meta: request.params._meta
        };
    });
}

interface ExecutableTool extends Tool {
    execute(args: Record<string, unknown>): Promise<ToolResponse>;
}

type ToolClass = new (client: PlaneApiClient) => ExecutableTool;

export function setupToolHandlers(server: Server, client: PlaneApiClient): void {
    // Register tool list handler
    server.setRequestHandler(z.object({
        method: z.literal('tools/list')
    }), async () => {
        return {
            tools: allTools.map(tool => ({
                name: tool.name,
                description: tool.description,
                inputSchema: tool.inputSchema
            }))
        };
    });

    // Register tool call handler
    server.setRequestHandler(z.object({
        method: z.literal('tools/call'),
        params: z.object({
            name: z.string(),
            arguments: z.record(z.unknown()).optional(),
            _meta: z.object({
                progressToken: z.union([z.string(), z.number()]).optional()
            }).optional()
        })
    }), async (request) => {
        const { name, arguments: args } = request.params;
        const toolDef = allTools.find(t => t.name === name);

        if (!toolDef) {
            throw new Error(`Tool not found: ${name}`);
        }

        const ToolClass = toolDef.class as ToolClass;
        const toolInstance = new ToolClass(client);
        const result = await toolInstance.execute(args || {});

        return {
            content: result.content,
            _meta: request.params._meta
        };
    });
}
```

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

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express, { Express, Response } from 'express';
import cors from 'cors';
import { z } from 'zod';
import { PromptDefinition, PromptContext } from '../types/prompt.js';
import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
import { 
  ListResourcesRequestSchema, 
  ReadResourceRequestSchema, 
  ListResourceTemplatesRequestSchema,
  ListPromptsRequestSchema,
  GetPromptRequestSchema
} from '@modelcontextprotocol/sdk/types.js';

type ServerCapabilities = {
  [key: string]: unknown;
  prompts?: { list?: boolean, execute?: boolean };
  tools?: { list?: boolean, call?: boolean };
  resources?: { list?: boolean, read?: boolean };
};

interface Connection {
  id: string;
  transport: any;
  initialized: boolean;
}

interface ServerRequest<T = unknown> {
  method: string;
  params: T;
}

export class McpServer {
  private server: Server;
  private app: Express;
  private connections: Map<string, Connection> = new Map();
  private nextConnectionId = 1;
  private capabilities = {
    prompts: { listChanged: true },
    tools: { listChanged: true },
    resources: { listChanged: true }
  };
  private registeredPrompts: PromptDefinition[] = [];

  constructor(name: string = 'claudeus-plane-mcp', version: string = '1.0.0') {
    // Create server with proper initialization
    this.server = new Server(
      { name, version },
      { capabilities: this.capabilities }
    );

    this.app = express();
    this.app.use(cors());
    this.app.use(express.json());

    // Register resource handlers first
    this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
      return { resources: [] }; // Placeholder - will be overridden by tools.ts
    });

    this.server.setRequestHandler(ReadResourceRequestSchema, async () => {
      return { resource: null, contents: [] }; // Placeholder - will be overridden by tools.ts
    });

    this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
      return { resourceTemplates: [] }; // Placeholder - will be overridden by tools.ts
    });

    // Register prompt handlers using SDK schemas
    this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
      return { 
        prompts: this.registeredPrompts.map(p => ({
          name: p.name,
          description: p.description,
          schema: p.schema
        }))
      };
    });

    this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
      const promptName = request.params?.name;
      if (!promptName || typeof promptName !== 'string') {
        throw new Error('Prompt name is required');
      }

      const prompt = this.registeredPrompts.find(p => p.name === promptName);
      if (!prompt) {
        throw new Error(`Unknown prompt: ${promptName}`);
      }

      return {
        name: prompt.name,
        description: prompt.description,
        schema: prompt.schema
      };
    });

    // Then register initialization and shutdown handlers
    const initializeSchema = z.object({
      method: z.literal('initialize'),
      params: z.object({
        capabilities: z.record(z.unknown())
      })
    });

    const shutdownSchema = z.object({
      method: z.literal('shutdown')
    });

    this.server.setRequestHandler(initializeSchema, async (request) => {
      if (!this.isValidCapabilities(request.params.capabilities)) {
        throw {
          code: -32602,
          message: 'Invalid params: capabilities must be an object'
        };
      }
      return {
        protocolVersion: '2024-11-05',
        serverInfo: {
          name,
          version
        },
        capabilities: this.capabilities
      };
    });

    this.server.setRequestHandler(shutdownSchema, async () => {
      return { success: true };
    });
  }

  private isValidCapabilities(capabilities: unknown): boolean {
    return typeof capabilities === 'object' && capabilities !== null && !Array.isArray(capabilities);
  }

  private trackConnection(transport: any): void {
    const id = `conn_${this.nextConnectionId++}`;
    this.connections.set(id, { id, transport, initialized: true });
    console.error(`🔌 New connection established: ${id}`);
  }

  private untrackConnection(transport: any): void {
    for (const [id, conn] of this.connections.entries()) {
      if (conn.transport === transport) {
        this.connections.delete(id);
        console.error(`🔌 Connection closed: ${id}`);
        break;
      }
    }
  }

  getServer(): Server {
    return this.server;
  }

  getApp(): Express {
    return this.app;
  }

  getActiveConnections(): number {
    return this.connections.size;
  }

  async connectStdio(): Promise<void> {
    const transport = new StdioServerTransport();
    this.trackConnection(transport);
    try {
      await this.server.connect(transport);
    } catch (error) {
      this.untrackConnection(transport);
      throw error;
    }
  }

  async connectSSE(port = 3000, path = '/sse'): Promise<void> {
    this.app.get(path, (req, res: Response) => {
      const transport = new SSEServerTransport(path, res);
      
      res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
      });

      this.trackConnection(transport);

      this.server.connect(transport).catch(error => {
        console.error('Failed to connect transport:', error);
        this.untrackConnection(transport);
        res.end();
      });

      res.on('close', () => {
        this.untrackConnection(transport);
      });
    });

    await new Promise<void>((resolve) => {
      this.app.listen(port, () => {
        console.error(`Server listening on port ${port}`);
        resolve();
      });
    });
  }

  registerPrompt(prompt: PromptDefinition): void {
    // Register the execute handler for this specific prompt
    const executeSchema = z.object({
      method: z.literal(`prompts/${prompt.name}/execute`),
      params: z.object({
        arguments: z.record(z.unknown())
      })
    });

    this.server.setRequestHandler(executeSchema, async (request, extra) => {
      try {
        const context: PromptContext = {
          workspace: process.env.WORKSPACE_PATH || '',
          connectionId: 'default'
        };

        // Execute the prompt handler with the arguments
        const result = await prompt.handler(request.params.arguments, context);
        console.error(`Executed prompt: ${prompt.name}`);
        
        // Ensure we have a properly structured response
        if (!result?.messages || !Array.isArray(result.messages)) {
          throw new Error('Prompt handler must return a messages array');
        }
        
        return {
          messages: result.messages,
          metadata: result.metadata || {},
          tools: []
        };
      } catch (error) {
        console.error(`Failed to execute prompt ${prompt.name}:`, error);
        return {
          messages: [{
            role: 'assistant',
            content: {
              type: 'text',
              text: `Error executing prompt ${prompt.name}: ${error instanceof Error ? error.message : String(error)}`
            }
          }],
          metadata: { error: error instanceof Error ? error.message : String(error) },
          tools: []
        };
      }
    });

    // Track the registered prompt
    this.registeredPrompts.push(prompt);
    console.error(`Registered prompt: ${prompt.name}`);
  }

  async initialize(): Promise<void> {
    try {
      await this.connectStdio();
      console.error('Server initialized successfully');
    } catch (error) {
      console.error('Failed to initialize server:', error);
      throw error;
    }
  }

  async start(): Promise<void> {
    try {
      console.error('Server started successfully');
    } catch (error) {
      console.error('Failed to start server:', error);
      throw error;
    }
  }
} 
```

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

```typescript
import { ProjectsAPI } from '@/api/projects.js';
import { 
    listProjects, 
    createProject, 
    updateProject, 
    deleteProject 
} from '@/tools/projects/handlers.js';
import { PlaneInstance } from '@/config/plane-config.js';
import { describe, it, expect, beforeEach, vi } from 'vitest';

// Mock ProjectsAPI
vi.mock('@/api/projects.js', () => ({
    ProjectsAPI: vi.fn().mockImplementation((instance) => ({
        instance,
        listProjects: vi.fn(),
        createProject: vi.fn(),
        updateProject: vi.fn(),
        deleteProject: vi.fn()
    }))
}));

describe('Project Tool Handlers', () => {
    let api: ReturnType<typeof vi.mocked<ProjectsAPI>>;
    const mockInstance: PlaneInstance = {
        name: 'test',
        baseUrl: 'https://test.plane.so',
        defaultWorkspace: 'default-workspace',
        apiKey: 'test-key'
    };

    beforeEach(() => {
        api = new ProjectsAPI(mockInstance) as ReturnType<typeof vi.mocked<ProjectsAPI>>;
    });

    describe('listProjects', () => {
        it('should list projects from default workspace', async () => {
            const mockProjects = [{
                id: '123e4567-e89b-12d3-a456-426614174000',
                name: 'Test Project',
                identifier: 'TEST',
                description: null,
                network: 1,
                workspace: '123e4567-e89b-12d3-a456-426614174001',
                project_lead: null,
                default_assignee: null,
                is_member: true,
                member_role: 1,
                total_members: 1,
                total_cycles: 0,
                total_modules: 0,
                module_view: true,
                cycle_view: true,
                issue_views_view: true,
                page_view: true,
                inbox_view: true,
                created_at: '2024-01-25T00:00:00Z',
                updated_at: '2024-01-25T00:00:00Z',
                created_by: '123e4567-e89b-12d3-a456-426614174002',
                updated_by: '123e4567-e89b-12d3-a456-426614174002'
            }];
            vi.mocked(api.listProjects).mockResolvedValue(mockProjects);

            const result = await listProjects(api, {});
            
            expect(api.listProjects).toHaveBeenCalledWith('default-workspace', { include_archived: undefined });
            expect(result.content[0].text).toBe(JSON.stringify(mockProjects, null, 2));
        });

        it('should list projects from specified workspace', async () => {
            const mockProjects = [{
                id: '123e4567-e89b-12d3-a456-426614174000',
                name: 'Test Project',
                identifier: 'TEST',
                description: null,
                network: 1,
                workspace: '123e4567-e89b-12d3-a456-426614174001',
                project_lead: null,
                default_assignee: null,
                is_member: true,
                member_role: 1,
                total_members: 1,
                total_cycles: 0,
                total_modules: 0,
                module_view: true,
                cycle_view: true,
                issue_views_view: true,
                page_view: true,
                inbox_view: true,
                created_at: '2024-01-25T00:00:00Z',
                updated_at: '2024-01-25T00:00:00Z',
                created_by: '123e4567-e89b-12d3-a456-426614174002',
                updated_by: '123e4567-e89b-12d3-a456-426614174002'
            }];
            vi.mocked(api.listProjects).mockResolvedValue(mockProjects);

            const result = await listProjects(api, { workspace_slug: 'custom-workspace' });
            
            expect(api.listProjects).toHaveBeenCalledWith('custom-workspace', { include_archived: undefined });
            expect(result.content[0].text).toBe(JSON.stringify(mockProjects, null, 2));
        });

        it('should handle errors gracefully', async () => {
            vi.mocked(api.listProjects).mockRejectedValue(new Error('API Error'));
            
            await expect(listProjects(api, {}))
                .rejects
                .toThrow('Failed to list projects: API Error');
        });
    });

    describe('createProject', () => {
        it('should create a project in default workspace', async () => {
            const mockProject = {
                id: '123e4567-e89b-12d3-a456-426614174000',
                name: 'New Project',
                identifier: 'NEW',
                description: null,
                network: 1,
                workspace: '123e4567-e89b-12d3-a456-426614174001',
                project_lead: null,
                default_assignee: null,
                is_member: true,
                member_role: 1,
                total_members: 1,
                total_cycles: 0,
                total_modules: 0,
                module_view: true,
                cycle_view: true,
                issue_views_view: true,
                page_view: true,
                inbox_view: true,
                created_at: '2024-01-25T00:00:00Z',
                updated_at: '2024-01-25T00:00:00Z',
                created_by: '123e4567-e89b-12d3-a456-426614174002',
                updated_by: '123e4567-e89b-12d3-a456-426614174002'
            };
            vi.mocked(api.createProject).mockResolvedValue(mockProject);

            const result = await createProject(api, {
                name: 'New Project',
                identifier: 'NEW'
            });
            
            expect(api.createProject).toHaveBeenCalledWith('default-workspace', {
                name: 'New Project',
                identifier: 'NEW'
            });
            expect(result.content[0].text).toBe(JSON.stringify(mockProject, null, 2));
        });

        it('should handle validation errors', async () => {
            await expect(createProject(api, {}))
                .rejects
                .toThrow();
        });
    });

    describe('updateProject', () => {
        it('should update a project', async () => {
            const mockProject = {
                id: '123e4567-e89b-12d3-a456-426614174000',
                name: 'Updated Project',
                identifier: 'UPD',
                description: null,
                network: 1,
                workspace: '123e4567-e89b-12d3-a456-426614174001',
                project_lead: null,
                default_assignee: null,
                is_member: true,
                member_role: 1,
                total_members: 1,
                total_cycles: 0,
                total_modules: 0,
                module_view: true,
                cycle_view: true,
                issue_views_view: true,
                page_view: true,
                inbox_view: true,
                created_at: '2024-01-25T00:00:00Z',
                updated_at: '2024-01-25T00:00:00Z',
                created_by: '123e4567-e89b-12d3-a456-426614174002',
                updated_by: '123e4567-e89b-12d3-a456-426614174002'
            };
            vi.mocked(api.updateProject).mockResolvedValue(mockProject);

            const result = await updateProject(api, {
                project_id: '1',
                name: 'Updated Project'
            });
            
            expect(api.updateProject).toHaveBeenCalledWith('default-workspace', '1', {
                name: 'Updated Project'
            });
            expect(result.content[0].text).toBe(JSON.stringify(mockProject, null, 2));
        });

        it('should handle missing project_id', async () => {
            await expect(updateProject(api, { name: 'Test' }))
                .rejects
                .toThrow();
        });
    });

    describe('deleteProject', () => {
        it('should delete a project', async () => {
            vi.mocked(api.deleteProject).mockResolvedValue(undefined);

            const result = await deleteProject(api, {
                project_id: '1'
            });
            
            expect(api.deleteProject).toHaveBeenCalledWith('default-workspace', '1');
            expect(JSON.parse(result.content[0].text)).toEqual({
                success: true,
                message: 'Project deleted successfully'
            });
        });

        it('should handle deletion errors', async () => {
            vi.mocked(api.deleteProject).mockRejectedValue(new Error('Not found'));
            
            await expect(deleteProject(api, { project_id: '1' }))
                .rejects
                .toThrow('Failed to delete project: Not found');
        });
    });
}); 
```

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

```json
{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "ES2022",
    "lib": ["ES2022"],
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
    // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */

    /* Modules */
    "module": "NodeNext",
    "rootDir": "./src",
    "moduleResolution": "NodeNext",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "*": ["src/types/*"]
    },
    "typeRoots": [
      "./node_modules/@types",
      "./src/types"
    ],
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
    // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
    // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
    // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
    // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
    // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
    "resolveJsonModule": true,
    // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

    /* Emit */
    "declaration": true,
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    "sourceMap": true,
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "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. */
    "outDir": "./dist",
    // "removeComments": true,                           /* Disable emitting comments. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "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. */
    // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,

    /* Type Checking */
    "strict": true,
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

```

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

```typescript
import { ToolWithClass } from '../types/mcp.js';
import { ListProjectsTool } from './projects/list.js';
import { CreateProjectTool } from './projects/create.js';
import { UpdateProjectTool } from './projects/update.js';
import { DeleteProjectTool } from './projects/delete.js';
import { ListIssuesTools } from './issues/list.js';
import { CreateIssueTool } from './issues/create.js';
import { GetIssueTool } from './issues/get.js';
import { UpdateIssueTool } from './issues/update.js';

// Export all tools with their classes
export const allTools: ToolWithClass[] = [
  {
    name: 'claudeus_plane_projects__list',
    description: 'Lists all projects in a Plane workspace. If no workspace is specified, lists projects from the default workspace.',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace to list projects from. If not provided, uses the default workspace.'
        },
        include_archived: {
          type: 'boolean',
          description: 'Whether to include archived projects',
          default: false
        }
      }
    },
    class: ListProjectsTool
  },
  {
    name: 'claudeus_plane_projects__create',
    description: 'Creates a new project in a workspace. If no workspace is specified, uses the default workspace.',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace to create the project in. If not provided, uses the default workspace.'
        },
        name: {
          type: 'string',
          description: 'The name of the project'
        },
        identifier: {
          type: 'string',
          description: 'The unique identifier for the project'
        },
        description: {
          type: 'string',
          description: 'A description of the project'
        }
      },
      required: ['name', 'identifier']
    },
    class: CreateProjectTool
  },
  {
    name: 'claudeus_plane_projects__update',
    description: 'Updates an existing project in a workspace. If no workspace is specified, uses the default workspace.',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace to update the project in. If not provided, uses the default workspace.'
        },
        project_id: {
          type: 'string',
          description: 'The ID of the project to update.'
        },
        name: {
          type: 'string',
          description: 'The new name of the project.'
        },
        description: {
          type: 'string',
          description: 'The new description of the project.'
        },
        start_date: {
          type: 'string',
          format: 'date',
          description: 'The new start date of the project.'
        },
        end_date: {
          type: 'string',
          format: 'date',
          description: 'The new end date of the project.'
        },
        status: {
          type: 'string',
          description: 'The new status of the project.'
        }
      },
      required: ['project_id']
    },
    class: UpdateProjectTool
  },
  {
    name: 'claudeus_plane_projects__delete',
    description: 'Deletes an existing project in a workspace. If no workspace is specified, uses the default workspace.',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace to delete the project from. If not provided, uses the default workspace.'
        },
        project_id: {
          type: 'string',
          description: 'The ID of the project to delete.'
        }
      },
      required: ['project_id']
    },
    class: DeleteProjectTool
  },
  {
    name: 'claudeus_plane_issues__list',
    description: 'Lists issues in a Plane project',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace to list issues from. If not provided, uses the default workspace.'
        },
        project_id: {
          type: 'string',
          description: 'The ID of the project to list issues from'
        },
        state: {
          type: 'string',
          description: 'Filter issues by state ID'
        },
        priority: {
          type: 'string',
          enum: ['urgent', 'high', 'medium', 'low', 'none'],
          description: 'Filter issues by priority'
        },
        assignee: {
          type: 'string',
          description: 'Filter issues by assignee ID'
        },
        label: {
          type: 'string',
          description: 'Filter issues by label ID'
        },
        created_by: {
          type: 'string',
          description: 'Filter issues by creator ID'
        },
        start_date: {
          type: 'string',
          format: 'date',
          description: 'Filter issues by start date (YYYY-MM-DD)'
        },
        target_date: {
          type: 'string',
          format: 'date',
          description: 'Filter issues by target date (YYYY-MM-DD)'
        },
        subscriber: {
          type: 'string',
          description: 'Filter issues by subscriber ID'
        },
        is_draft: {
          type: 'boolean',
          description: 'Filter draft issues',
          default: false
        },
        archived: {
          type: 'boolean',
          description: 'Filter archived issues',
          default: false
        },
        page: {
          type: 'number',
          description: 'Page number (1-based)',
          default: 1
        },
        page_size: {
          type: 'number',
          description: 'Number of items per page',
          default: 100
        }
      },
      required: ['project_id']
    },
    class: ListIssuesTools
  },
  {
    name: 'claudeus_plane_issues__create',
    description: 'Creates a new issue in a Plane project',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace to create the issue in. If not provided, uses the default workspace.'
        },
        project_id: {
          type: 'string',
          description: 'The ID of the project to create the issue in'
        },
        name: {
          type: 'string',
          description: 'The name/title of the issue'
        },
        description_html: {
          type: 'string',
          description: 'The HTML description of the issue'
        },
        priority: {
          type: 'string',
          enum: ['urgent', 'high', 'medium', 'low', 'none'],
          description: 'The priority of the issue',
          default: 'none'
        },
        start_date: {
          type: 'string',
          format: 'date',
          description: 'The start date of the issue (YYYY-MM-DD)'
        },
        target_date: {
          type: 'string',
          format: 'date',
          description: 'The target date of the issue (YYYY-MM-DD)'
        },
        estimate_point: {
          type: 'number',
          description: 'Story points or time estimate for the issue'
        },
        state: {
          type: 'string',
          description: 'The state ID for the issue'
        },
        assignees: {
          type: 'array',
          items: {
            type: 'string'
          },
          description: 'Array of user IDs to assign to the issue'
        },
        labels: {
          type: 'array',
          items: {
            type: 'string'
          },
          description: 'Array of label IDs to apply to the issue'
        },
        parent: {
          type: 'string',
          description: 'ID of the parent issue (for sub-issues)'
        },
        is_draft: {
          type: 'boolean',
          description: 'Whether this is a draft issue',
          default: false
        }
      },
      required: ['project_id', 'name']
    },
    class: CreateIssueTool
  },
  {
    name: 'claudeus_plane_issues__get',
    description: 'Gets a single issue by ID from a Plane project',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace containing the issue. If not provided, uses the default workspace.'
        },
        project_id: {
          type: 'string',
          description: 'The ID of the project containing the issue'
        },
        issue_id: {
          type: 'string',
          description: 'The ID of the issue to retrieve'
        }
      },
      required: ['project_id', 'issue_id']
    },
    class: GetIssueTool
  },
  {
    name: 'claudeus_plane_issues__update',
    description: 'Updates an existing issue in a Plane project',
    status: 'enabled',
    inputSchema: {
      type: 'object',
      properties: {
        workspace_slug: {
          type: 'string',
          description: 'The slug of the workspace containing the issue. If not provided, uses the default workspace.'
        },
        project_id: {
          type: 'string',
          description: 'The ID of the project containing the issue'
        },
        issue_id: {
          type: 'string',
          description: 'The ID of the issue to update'
        },
        name: {
          type: 'string',
          description: 'The new name/title of the issue'
        },
        description_html: {
          type: 'string',
          description: 'The new HTML description of the issue'
        },
        priority: {
          type: 'string',
          enum: ['urgent', 'high', 'medium', 'low', 'none'],
          description: 'The new priority of the issue'
        },
        start_date: {
          type: 'string',
          format: 'date',
          description: 'The new start date of the issue (YYYY-MM-DD)'
        },
        target_date: {
          type: 'string',
          format: 'date',
          description: 'The new target date of the issue (YYYY-MM-DD)'
        },
        estimate_point: {
          type: 'number',
          description: 'The new story points or time estimate for the issue'
        },
        state: {
          type: 'string',
          description: 'The new state ID for the issue'
        },
        assignees: {
          type: 'array',
          items: {
            type: 'string'
          },
          description: 'New array of user IDs to assign to the issue'
        },
        labels: {
          type: 'array',
          items: {
            type: 'string'
          },
          description: 'New array of label IDs to apply to the issue'
        },
        parent: {
          type: 'string',
          description: 'New parent issue ID (for sub-issues)'
        },
        is_draft: {
          type: 'boolean',
          description: 'Whether this issue should be marked as draft'
        },
        archived_at: {
          type: 'string',
          format: 'date-time',
          description: 'When to archive the issue (ISO 8601 format)'
        },
        completed_at: {
          type: 'string',
          format: 'date-time',
          description: 'When the issue was completed (ISO 8601 format)'
        }
      },
      required: ['project_id', 'issue_id']
    },
    class: UpdateIssueTool
  }
];

// Define tool capabilities
export const toolCapabilities = {
  // Projects
  claudeus_plane_projects__list: true,
  claudeus_plane_projects__get: false, // Coming soon
  claudeus_plane_projects__create: true,
  claudeus_plane_projects__update: true,
  claudeus_plane_projects__delete: true,
  
  // Issues
  claudeus_plane_issues__list: true,
  claudeus_plane_issues__get: true,
  claudeus_plane_issues__create: true,
  claudeus_plane_issues__update: true,
  claudeus_plane_issues__delete: false, // Coming soon
  
  // Cycles (Coming soon)
  claudeus_plane_cycles__list: false,
  claudeus_plane_cycles__get: false,
  claudeus_plane_cycles__create: false,
  claudeus_plane_cycles__update: false,
  claudeus_plane_cycles__delete: false,
  
  // Modules (Coming soon)
  claudeus_plane_modules__list: false,
  claudeus_plane_modules__get: false,
  claudeus_plane_modules__create: false,
  claudeus_plane_modules__update: false,
  claudeus_plane_modules__delete: false
}; 
```
Page 1/2FirstPrevNextLast