├── babel.config.js ├── server.schema.json ├── .claude-flow └── metrics │ └── agent-metrics.json ├── mise.toml ├── trello-mcp-logo.avif ├── compose.yml ├── .prettierrc ├── .env.template ├── .dockerignore ├── docs ├── session │ └── goal.md ├── progress.md ├── HEALTH_MONITORING.md └── REASONINGBANK_EXAMPLES.md ├── tsconfig.json ├── example.env ├── .git-commit-message-generator-config.json ├── server.json.bak ├── server.json.current ├── .eslintrc.json ├── smithery.yaml ├── .github ├── workflows │ ├── publish-npm.yml │ └── publish-registry.yml └── copilot-instructions.md ├── LICENSE ├── .gitignore ├── server.json ├── Dockerfile ├── validate-server.mjs ├── scripts └── versionbump.js ├── package.json ├── CONTRIBUTING.md ├── src ├── rate-limiter.ts ├── evals │ └── evals.ts ├── types.ts ├── validators.ts ├── health │ ├── health-endpoints.ts │ └── health-monitor.ts └── index.ts.backup ├── CHANGELOG.md ├── examples ├── README.md ├── javascript-examples.js └── python-examples.py └── CLAUDE.md /babel.config.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server.schema.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.claude-flow/metrics/agent-metrics.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | node = "latest" 3 | pnpm = "latest" 4 | -------------------------------------------------------------------------------- /trello-mcp-logo.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/delorenj/mcp-server-trello/HEAD/trello-mcp-logo.avif -------------------------------------------------------------------------------- /compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mcp-server-trello: 3 | container_name: mcp-server-trello 4 | build: . 5 | environment: 6 | TRELLO_API_KEY: ${TRELLO_API_KEY} 7 | TRELLO_TOKEN: ${TRELLO_TOKEN} 8 | restart: 'no' 9 | 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "printWidth": 100, 6 | "tabWidth": 2, 7 | "useTabs": false, 8 | "bracketSpacing": true, 9 | "arrowParens": "avoid", 10 | "endOfLine": "lf" 11 | } 12 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | # Trello API Configuration 2 | # Get your API key from https://trello.com/app-key 3 | TRELLO_API_KEY=your-api-key-here 4 | 5 | # Generate your token using your API key 6 | TRELLO_TOKEN=your-token-here 7 | 8 | # Optional: Initial workspace ID (can be changed later using set_active_workspace) 9 | # TRELLO_WORKSPACE_ID=your-workspace-id-here 10 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Build output 8 | build 9 | dist 10 | *.tsbuildinfo 11 | 12 | # Environment variables 13 | .env 14 | .env.* 15 | !.env.template 16 | 17 | # Git 18 | .git 19 | .gitignore 20 | 21 | # IDE 22 | .idea 23 | .vscode 24 | *.swp 25 | *.swo 26 | *~ 27 | 28 | # Logs 29 | logs 30 | *.log 31 | 32 | # Test coverage 33 | coverage -------------------------------------------------------------------------------- /docs/session/goal.md: -------------------------------------------------------------------------------- 1 | # Goal 2 | 3 | Help me thoroughly test this server's functionality. 4 | 5 | ## Requirements 6 | 7 | - [x] Test `get_cards_by_list_id` tool 8 | - [x] Test `get_lists` tool 9 | - [ ] Test `get_recent_activity` tool 10 | - [ ] Test `add_card_to_list` tool 11 | - [ ] Test `update_card_details` tool 12 | - [ ] Test `archive_card` tool 13 | - [ ] Test `add_list_to_board` tool 14 | - [ ] Test `archive_list` tool 15 | - [ ] Test `get_my_cards` tool -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "moduleResolution": "node", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "declaration": true, 13 | "sourceMap": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "build"] 17 | } -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | # If your board is `https://trello.com/b/7aABC123A/myorg` 2 | # replace `/myorg` with `.json` like this: 3 | # 4 | # https://trello.com/b/7aABC123A.json` 5 | # 6 | # The first field (`:id`) in the rendered json block is the board id 7 | TRELLO_BOARD_ID= 8 | 9 | TRELLO_API_KEY= # https://trello.com/app-key 10 | TRELLO_TOKEN= # https://trello.com/1/authorize?expiration=never&name=YOUR_TRELLO_WORKSPACE&scope=read,write&response_type=token&key=YOUR_API_KEY 11 | -------------------------------------------------------------------------------- /.git-commit-message-generator-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "AI": { 3 | "provider": "aws-bedrock", 4 | "model_id": "anthropic.claude-3-5-sonnet-20240620-v1:0", 5 | "user_prompt": "Generate a git commit message for the following changes: {diff}. Start with a high-level summary of the changes, then a short changelog breaking down the major changes. Output should contain ONLY the commit message. There should be no mention of a commit message in the output.", 6 | "system_prompt": "You are a helpful assistant tasked with generating accurate, concise idiomatic git commit messages" 7 | }, 8 | "OpenAI": {}, 9 | "AWS": { 10 | "profile_name": "delorenj" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /server.json.bak: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", 3 | "name": "io.github.delorenj/mcp-server-trello", 4 | "description": "MCP server for Trello boards with rate limiting, type safety, and comprehensive API integration.", 5 | "status": "active", 6 | "website_url": "https://github.com/delorenj/mcp-server-trello", 7 | "repository": { 8 | "url": "https://github.com/delorenj/mcp-server-trello", 9 | "source": "github" 10 | }, 11 | "version": "1.5.4", 12 | "packages": [ 13 | { 14 | "identifier": "@delorenj/mcp-server-trello", 15 | "registryType": "npm", 16 | "version": "1.5.4", 17 | "transport": { 18 | "type": "stdio" 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /server.json.current: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", 3 | "name": "io.github.delorenj/mcp-server-trello", 4 | "description": "MCP server for Trello boards with rate limiting, type safety, and comprehensive API integration.", 5 | "status": "active", 6 | "website_url": "https://github.com/delorenj/mcp-server-trello", 7 | "repository": { 8 | "url": "https://github.com/delorenj/mcp-server-trello", 9 | "source": "github" 10 | }, 11 | "version": "1.5.4", 12 | "packages": [ 13 | { 14 | "identifier": "@delorenj/mcp-server-trello", 15 | "registryType": "npm", 16 | "version": "1.5.4", 17 | "transport": { 18 | "type": "stdio" 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true, 5 | "jest": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:prettier/recommended" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": "latest", 15 | "sourceType": "module" 16 | }, 17 | "plugins": ["@typescript-eslint", "prettier"], 18 | "rules": { 19 | "prettier/prettier": "error", 20 | "@typescript-eslint/explicit-function-return-type": "off", 21 | "@typescript-eslint/no-explicit-any": "warn", 22 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 23 | "no-console": ["error", { "allow": ["error", "warn"] }] 24 | }, 25 | "ignorePatterns": ["build/", "node_modules/", "coverage/"] 26 | } 27 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - trelloApiKey 10 | - trelloToken 11 | - trelloBoardId 12 | properties: 13 | trelloApiKey: 14 | type: string 15 | description: The API key for the Trello server. 16 | trelloToken: 17 | type: string 18 | description: The token for authenticating with Trello. 19 | trelloBoardId: 20 | type: string 21 | description: The ID of the Trello board to interact with. 22 | commandFunction: 23 | # A function that produces the CLI command to start the MCP on stdio. 24 | |- 25 | (config) => ({command:'node',args:['build/index.js'],env:{TRELLO_API_KEY:config.trelloApiKey,TRELLO_TOKEN:config.trelloToken,TRELLO_BOARD_ID:config.trelloBoardId}}) -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | branches: [main] 7 | 8 | jobs: 9 | publish-npm: 10 | # Only run if PR was merged (not just closed) 11 | if: github.event.pull_request.merged == true 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read 15 | id-token: write 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup Bun 22 | uses: oven-sh/setup-bun@v2 23 | with: 24 | bun-version: latest 25 | 26 | - name: Setup Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: '20.x' 30 | registry-url: 'https://registry.npmjs.org' 31 | 32 | - name: Install dependencies 33 | run: bun install 34 | 35 | - name: Build project 36 | run: bun run build 37 | 38 | - name: Publish to NPM 39 | run: npm publish --provenance 40 | env: 41 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Model Context Protocol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/publish-registry.yml: -------------------------------------------------------------------------------- 1 | name: Publish to MCP Registry 2 | 3 | on: 4 | release: 5 | types: [published] 6 | push: 7 | branches: [main] 8 | paths: ['server.json', 'package.json'] 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | id-token: write 15 | contents: read 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup Bun 22 | uses: oven-sh/setup-bun@v2 23 | with: 24 | bun-version: latest 25 | 26 | - name: Install dependencies 27 | run: bun install 28 | 29 | - name: Build project 30 | run: bun run build 31 | 32 | - name: Build latest mcp-publisher 33 | run: | 34 | git clone https://github.com/modelcontextprotocol/registry.git /tmp/registry 35 | cd /tmp/registry 36 | make publisher 37 | cp bin/mcp-publisher $GITHUB_WORKSPACE/ 38 | 39 | - name: Authenticate with GitHub OIDC 40 | run: ./mcp-publisher login github-oidc 41 | 42 | - name: Publish to MCP Registry 43 | run: ./mcp-publisher publish 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Build output 8 | build/ 9 | dist/ 10 | *.tsbuildinfo 11 | 12 | # IDE and editor files 13 | .idea/ 14 | .vscode/ 15 | *.swp 16 | *.swo 17 | *~ 18 | 19 | # Environment variables 20 | .env 21 | .env.local 22 | .env.*.local 23 | 24 | # Operating System 25 | .DS_Store 26 | Thumbs.db 27 | 28 | # Test coverage 29 | coverage/ 30 | 31 | # Logs 32 | logs/ 33 | *.log 34 | 35 | 36 | # Claude Flow generated files 37 | .claude/settings.local.json 38 | .mcp.json 39 | claude-flow.config.json 40 | .swarm/ 41 | .hive-mind/ 42 | memory/claude-flow-data.json 43 | memory/sessions/* 44 | !memory/sessions/README.md 45 | memory/agents/* 46 | !memory/agents/README.md 47 | coordination/memory_bank/* 48 | coordination/subtasks/* 49 | coordination/orchestration/* 50 | *.db 51 | *.db-journal 52 | *.db-wal 53 | *.sqlite 54 | *.sqlite-journal 55 | *.sqlite-wal 56 | claude-flow 57 | claude-flow.bat 58 | claude-flow.ps1 59 | hive-mind-prompt-*.txt 60 | validate-server.cjs 61 | mcp-publisher 62 | **/system-metrics.json 63 | 64 | # MCP Registry tokens (secrets) 65 | .mcpregistry_github_token 66 | .mcpregistry_registry_token 67 | .claude-flow/metrics/**/* 68 | -------------------------------------------------------------------------------- /server.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-16/server.schema.json", 3 | "name": "io.github.delorenj/mcp-server-trello", 4 | "description": "MCP server for Trello boards with rate limiting, type safety, and comprehensive API integration.", 5 | "status": "active", 6 | "websiteUrl": "https://delorenj.github.io/mcp-server-trello", 7 | "repository": { 8 | "url": "https://github.com/delorenj/mcp-server-trello", 9 | "source": "github" 10 | }, 11 | "version": "1.5.6", 12 | "packages": [ 13 | { 14 | "registryType": "npm", 15 | "registryBaseUrl": "https://registry.npmjs.org", 16 | "identifier": "@delorenj/mcp-server-trello", 17 | "version": "1.5.6", 18 | "transport": { 19 | "type": "stdio" 20 | }, 21 | "environmentVariables": [ 22 | { 23 | "name": "TRELLO_API_KEY", 24 | "description": "Your Trello API key", 25 | "isRequired": true, 26 | "format": "string", 27 | "isSecret": true 28 | }, 29 | { 30 | "name": "TRELLO_TOKEN", 31 | "description": "Your Trello token", 32 | "isRequired": true, 33 | "format": "string", 34 | "isSecret": true 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use official Bun image as builder 2 | FROM oven/bun:1-alpine AS builder 3 | LABEL org.opencontainers.image.source=https://github.com/delorenj/mcp-server-trello 4 | 5 | # Set the working directory to /app 6 | WORKDIR /app 7 | 8 | # Install build dependencies 9 | RUN apk add --no-cache python3 make g++ 10 | 11 | # Copy package files first to leverage Docker cache 12 | COPY package.json bun.lock ./ 13 | 14 | # Install all dependencies (including dev dependencies) 15 | RUN bun install --frozen 16 | 17 | # Copy the rest of the code 18 | COPY . . 19 | 20 | # Build TypeScript 21 | RUN bun run build 22 | 23 | # Use official Bun image for runtime 24 | FROM oven/bun:1-alpine AS release 25 | 26 | # Set the working directory to /app 27 | WORKDIR /app 28 | 29 | # Copy only the necessary files from builder 30 | COPY --from=builder /app/build ./build 31 | COPY --from=builder /app/package.json ./ 32 | COPY --from=builder /app/bun.lock ./ 33 | 34 | # Install only production dependencies without running scripts 35 | RUN bun install --production --frozen 36 | 37 | # The environment variables should be passed at runtime, not baked into the image 38 | # They can be provided via docker run -e or docker compose environment section 39 | ENV NODE_ENV=production 40 | 41 | # Run the MCP server using Bun 42 | CMD ["bun", "build/index.js"] 43 | -------------------------------------------------------------------------------- /validate-server.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Validates server.json against basic MCP Registry requirements 5 | * Used in CI/CD pipeline before publishing 6 | */ 7 | 8 | import { readFileSync } from 'fs'; 9 | 10 | try { 11 | const serverJson = readFileSync('server.json', 'utf8'); 12 | const parsed = JSON.parse(serverJson); 13 | 14 | console.log('✅ server.json is valid JSON'); 15 | console.log('Server name:', parsed.name); 16 | console.log('Version:', parsed.version); 17 | console.log('Description:', parsed.description); 18 | console.log('Package identifier:', parsed.packages?.[0]?.identifier); 19 | console.log('Environment variables:', parsed.packages?.[0]?.environment_variables?.length || 0); 20 | 21 | // Basic validation checks 22 | if (!parsed.name || !parsed.name.startsWith('io.github.delorenj/')) { 23 | console.error('❌ Invalid name format. Must start with io.github.delorenj/'); 24 | process.exit(1); 25 | } 26 | 27 | if (!parsed.packages || parsed.packages.length === 0) { 28 | console.error('❌ Missing packages array'); 29 | process.exit(1); 30 | } 31 | 32 | const npmPackage = parsed.packages.find(p => p.registry_type === 'npm'); 33 | if (!npmPackage) { 34 | console.error('❌ Missing npm package definition'); 35 | process.exit(1); 36 | } 37 | 38 | if (npmPackage.identifier !== '@delorenj/mcp-server-trello') { 39 | console.error('❌ Package identifier mismatch'); 40 | process.exit(1); 41 | } 42 | 43 | console.log('✅ All basic validations passed'); 44 | 45 | } catch (error) { 46 | console.error('❌ server.json validation failed:', error.message); 47 | process.exit(1); 48 | } -------------------------------------------------------------------------------- /scripts/versionbump.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from 'fs'; 4 | import { execFileSync } from 'child_process'; 5 | 6 | // Get the version bump type from command line arguments 7 | const args = process.argv.slice(2); 8 | const bumpType = args[0]?.replace('--', ''); 9 | 10 | if (!bumpType || !['patch', 'minor', 'major'].includes(bumpType)) { 11 | console.error('Usage: bun versionbump --[patch|minor|major]'); 12 | process.exit(1); 13 | } 14 | 15 | // Read package.json 16 | const packageJsonPath = './package.json'; 17 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); 18 | 19 | // Parse current version 20 | const versionRegex = /^(\d+)\.(\d+)\.(\d+)(.*)$/; 21 | const match = packageJson.version.match(versionRegex); 22 | 23 | if (!match) { 24 | console.error('Invalid version format in package.json'); 25 | process.exit(1); 26 | } 27 | 28 | const [, major, minor, patch, suffix] = match; 29 | let [newMajor, newMinor, newPatch] = [parseInt(major), parseInt(minor), parseInt(patch)]; 30 | 31 | // Bump version based on type 32 | switch (bumpType) { 33 | case 'major': 34 | newMajor++; 35 | newMinor = 0; 36 | newPatch = 0; 37 | break; 38 | case 'minor': 39 | newMinor++; 40 | newPatch = 0; 41 | break; 42 | case 'patch': 43 | newPatch++; 44 | break; 45 | } 46 | 47 | // Create new version string 48 | const newVersion = `${newMajor}.${newMinor}.${newPatch}${suffix || ''}`; 49 | 50 | // Update package.json 51 | packageJson.version = newVersion; 52 | fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); 53 | 54 | console.log(`Version bumped to ${newVersion}`); 55 | 56 | // Commit the change if git is available 57 | try { 58 | execFileSync('git', ['add', 'package.json']); 59 | execFileSync('git', ['commit', '-m', `Bump version to ${newVersion}`]); 60 | console.log(`Committed version bump to ${newVersion}`); 61 | } catch (error) { 62 | console.log('Git commit skipped or failed:', error.message); 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@delorenj/mcp-server-trello", 3 | "mcpName": "io.github.delorenj/mcp-server-trello", 4 | "version": "1.7.1", 5 | "description": "An MCP server for Trello boards, powered by Bun for maximum performance.", 6 | "keywords": [ 7 | "mcp", 8 | "trello", 9 | "bun", 10 | "automation", 11 | "productivity", 12 | "model-context-protocol", 13 | "api", 14 | "integration", 15 | "typescript", 16 | "board-management", 17 | "task-management", 18 | "kanban" 19 | ], 20 | "author": "Jarad DeLorenzo", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/delorenj/mcp-server-trello/issues" 24 | }, 25 | "homepage": "https://github.com/delorenj/mcp-server-trello#readme", 26 | "dependencies": { 27 | "@modelcontextprotocol/sdk": "^1.24.3", 28 | "axios": "^1.13.2", 29 | "form-data": "^4.0.5", 30 | "mcp-evals": "^1.0.18", 31 | "zod": "^3.25.76" 32 | }, 33 | "devDependencies": { 34 | "@ai-sdk/openai": "^1.3.24", 35 | "bun-types": "latest", 36 | "@typescript-eslint/eslint-plugin": "^6.21.0", 37 | "@typescript-eslint/parser": "^6.21.0", 38 | "eslint": "^8.57.1", 39 | "eslint-config-prettier": "^9.1.2", 40 | "eslint-plugin-prettier": "^5.5.4", 41 | "prettier": "^3.7.4", 42 | "terser": "^5.44.1", 43 | "typescript": "^5.9.3" 44 | }, 45 | "files": [ 46 | "build/**/*" 47 | ], 48 | "bin": { 49 | "mcp-server-trello": "build/index.js" 50 | }, 51 | "type": "module", 52 | "engines": { 53 | "bun": ">=1.0.0" 54 | }, 55 | "publishConfig": { 56 | "access": "public" 57 | }, 58 | "scripts": { 59 | "build": "bun build src/index.ts --outdir ./build --target node --format esm", 60 | "build:types": "tsc --emitDeclarationOnly || echo 'Warning: Type declarations generation failed, but build succeeded'", 61 | "build:prod": "npm run build", 62 | "dev": "bun --watch src/index.ts", 63 | "versionbump": "bun ./scripts/versionbump.js", 64 | "versionbump:patch": "bun run versionbump --patch", 65 | "versionbump:minor": "bun run versionbump --minor", 66 | "versionbump:major": "bun run versionbump --major", 67 | "publish": "bun run build && npm publish" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to MCP Trello Server 2 | 3 | We love your input! We want to make contributing to MCP Trello Server as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## We Develop with Github 12 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. 13 | 14 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html) 15 | Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests: 16 | 17 | 1. Fork the repo and create your branch from `main`. 18 | 2. If you've added code that should be tested, add tests. 19 | 3. If you've changed APIs, update the documentation. 20 | 4. Ensure the test suite passes. 21 | 5. Make sure your code lints. 22 | 6. Issue that pull request! 23 | 24 | ## Any contributions you make will be under the MIT Software License 25 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 26 | 27 | ## Report bugs using Github's [issue tracker](https://github.com/modelcontextprotocol/server-trello/issues) 28 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/modelcontextprotocol/server-trello/issues/new); it's that easy! 29 | 30 | ## Write bug reports with detail, background, and sample code 31 | 32 | **Great Bug Reports** tend to have: 33 | 34 | - A quick summary and/or background 35 | - Steps to reproduce 36 | - Be specific! 37 | - Give sample code if you can. 38 | - What you expected would happen 39 | - What actually happens 40 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 41 | 42 | ## Use a Consistent Coding Style 43 | 44 | * Use TypeScript for all new code 45 | * 2 spaces for indentation rather than tabs 46 | * You can try running `npm run lint` for style unification 47 | 48 | ## License 49 | By contributing, you agree that your contributions will be licensed under its MIT License. 50 | -------------------------------------------------------------------------------- /src/rate-limiter.ts: -------------------------------------------------------------------------------- 1 | import { RateLimiter } from './types.js'; 2 | 3 | export class TokenBucketRateLimiter implements RateLimiter { 4 | private tokens: number; 5 | private lastRefill: number; 6 | private readonly maxTokens: number; 7 | private readonly refillRate: number; // tokens per millisecond 8 | private readonly refillInterval: number; // milliseconds 9 | 10 | constructor(maxRequests: number, windowMs: number) { 11 | this.maxTokens = maxRequests; 12 | this.tokens = maxRequests; 13 | this.lastRefill = Date.now(); 14 | this.refillInterval = windowMs; 15 | this.refillRate = maxRequests / windowMs; 16 | } 17 | 18 | private refillTokens(): void { 19 | const now = Date.now(); 20 | const timePassed = now - this.lastRefill; 21 | const newTokens = timePassed * this.refillRate; 22 | this.tokens = Math.min(this.maxTokens, this.tokens + newTokens); 23 | this.lastRefill = now; 24 | } 25 | 26 | canMakeRequest(): boolean { 27 | this.refillTokens(); 28 | if (this.tokens >= 1) { 29 | this.tokens -= 1; 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | async waitForAvailableToken(): Promise { 36 | return new Promise(resolve => { 37 | const check = () => { 38 | if (this.canMakeRequest()) { 39 | resolve(); 40 | } else { 41 | // Calculate time until next token is available 42 | const tokensNeeded = 1 - this.tokens; 43 | const msToWait = (tokensNeeded / this.refillRate) * 1000; 44 | setTimeout(check, Math.min(msToWait, 100)); // Check at most every 100ms 45 | } 46 | }; 47 | check(); 48 | }); 49 | } 50 | } 51 | 52 | // Create rate limiters based on Trello's limits 53 | export const createTrelloRateLimiters = () => { 54 | const apiKeyLimiter = new TokenBucketRateLimiter(300, 10000); // 300 requests per 10 seconds 55 | const tokenLimiter = new TokenBucketRateLimiter(100, 10000); // 100 requests per 10 seconds 56 | 57 | return { 58 | apiKeyLimiter, 59 | tokenLimiter, 60 | /** 61 | * Checks if a request can be made without hitting rate limits. 62 | * This is useful for pre-checking before attempting a request, 63 | * allowing for more graceful handling of rate limits without blocking. 64 | * 65 | * @returns {boolean} True if both API key and token limiters have available tokens 66 | */ 67 | canMakeRequest(): boolean { 68 | return apiKeyLimiter.canMakeRequest() && tokenLimiter.canMakeRequest(); 69 | }, 70 | /** 71 | * Waits until tokens are available for both API key and token limiters. 72 | * This method blocks execution until rate limits allow the request to proceed. 73 | * Used by the axios interceptor to ensure all requests respect Trello's rate limits. 74 | * 75 | * @returns {Promise} Resolves when tokens are available 76 | */ 77 | async waitForAvailableToken(): Promise { 78 | await Promise.all([ 79 | apiKeyLimiter.waitForAvailableToken(), 80 | tokenLimiter.waitForAvailableToken(), 81 | ]); 82 | }, 83 | }; 84 | }; 85 | -------------------------------------------------------------------------------- /docs/progress.md: -------------------------------------------------------------------------------- 1 | # MCP Server Trello - Progress Report 2 | 3 | ## Current Implementation Status 4 | 5 | ### Core Infrastructure 6 | - ✅ Basic MCP server setup with proper initialization and error handling 7 | - ✅ TypeScript implementation with comprehensive type definitions 8 | - ✅ Environment variable configuration for API key, token, and board ID 9 | - ✅ Stdio transport implementation for MCP communication 10 | 11 | ### Rate Limiting 12 | - ✅ Token bucket algorithm implementation 13 | - ✅ Separate rate limiters for API key (300/10s) and token (100/10s) 14 | - ✅ Automatic request queuing when limits are reached 15 | - ✅ Graceful handling of rate limit errors with automatic retry 16 | 17 | ### API Client 18 | - ✅ Axios-based Trello API client 19 | - ✅ Automatic error handling and retries 20 | - ✅ Base URL and authentication configuration 21 | - ✅ Rate limiting interceptor integration 22 | 23 | ### Input Validation 24 | - ✅ Comprehensive validation for all tool inputs 25 | - ✅ Type checking for strings, numbers, and arrays 26 | - ✅ Required field validation 27 | - ✅ Custom error messages for validation failures 28 | 29 | ### Implemented Tools 30 | 1. Board Management: 31 | - ✅ get_lists: Retrieve all lists from board 32 | - ✅ add_list_to_board: Create new list 33 | - ✅ archive_list: Archive existing list 34 | - ✅ get_recent_activity: Fetch board activity 35 | 36 | 2. Card Operations: 37 | - ✅ get_cards_by_list_id: Fetch cards in a list 38 | - ✅ add_card_to_list: Create new card 39 | - ✅ update_card_details: Modify existing card 40 | - ✅ archive_card: Archive card 41 | - ✅ get_my_cards: Fetch user's assigned cards 42 | 43 | ### Type Definitions 44 | - ✅ TrelloConfig: Server configuration interface 45 | - ✅ TrelloCard: Card data structure 46 | - ✅ TrelloList: List data structure 47 | - ✅ TrelloAction: Activity data structure 48 | - ✅ TrelloLabel: Label data structure 49 | - ✅ TrelloMember: Member data structure 50 | - ✅ RateLimiter: Rate limiting interface 51 | 52 | ## Next Steps 53 | 54 | ### Potential Enhancements 55 | 1. Additional Card Features: 56 | - Add comment support 57 | - Add attachment handling 58 | - Add checklist management 59 | - Add member assignment capabilities 60 | 61 | 2. Label Management: 62 | - Create labels 63 | - Edit labels 64 | - Delete labels 65 | - Get available labels 66 | 67 | 3. Board Features: 68 | - Board customization options 69 | - Custom field support 70 | - Power-up integration 71 | 72 | 4. Enhanced Error Handling: 73 | - More detailed error messages 74 | - Better network error recovery 75 | - Offline mode support 76 | 77 | 5. Performance Optimizations: 78 | - Request batching 79 | - Response caching 80 | - Parallel request handling 81 | 82 | 6. Testing: 83 | - Unit tests 84 | - Integration tests 85 | - End-to-end tests 86 | - Mock API responses 87 | 88 | ### Documentation Needs 89 | 1. API Documentation: 90 | - Detailed tool descriptions 91 | - Example usage for each tool 92 | - Error handling guide 93 | - Rate limiting explanation 94 | 95 | 2. Setup Guide: 96 | - Installation instructions 97 | - Configuration details 98 | - Environment variable setup 99 | - Troubleshooting guide 100 | 101 | 3. Contributing Guide: 102 | - Development setup 103 | - Code style guide 104 | - Pull request process 105 | - Testing requirements 106 | -------------------------------------------------------------------------------- /src/evals/evals.ts: -------------------------------------------------------------------------------- 1 | //evals.ts 2 | 3 | import { EvalConfig } from 'mcp-evals'; 4 | import { openai } from '@ai-sdk/openai'; 5 | import { grade, EvalFunction } from 'mcp-evals'; 6 | 7 | const get_cards_by_list_idEval: EvalFunction = { 8 | name: 'get_cards_by_list_id Tool Evaluation', 9 | description: 'Evaluates the get_cards_by_list_id tool functionality', 10 | run: async () => { 11 | const result = await grade( 12 | openai('gpt-4'), 13 | 'Can you fetch all cards from the Trello list with ID abc123?' 14 | ); 15 | return JSON.parse(result); 16 | }, 17 | }; 18 | 19 | const get_listsEval: EvalFunction = { 20 | name: 'get_lists Tool Evaluation', 21 | description: 'Evaluates the get_lists tool by retrieving all lists from a specified board', 22 | run: async () => { 23 | const result = await grade( 24 | openai('gpt-4'), 25 | 'Please retrieve all lists from the board with ID 12345 and provide their names.' 26 | ); 27 | return JSON.parse(result); 28 | }, 29 | }; 30 | 31 | const get_recent_activityEvalFunction: EvalFunction = { 32 | name: 'get_recent_activity Tool Evaluation', 33 | description: 'Evaluates the ability to fetch recent activity on the Trello board', 34 | run: async () => { 35 | const result = await grade( 36 | openai('gpt-4'), 37 | 'Fetch the recent activity on the Trello board, limit it to 5 items' 38 | ); 39 | return JSON.parse(result); 40 | }, 41 | }; 42 | 43 | const add_card_to_listEval: EvalFunction = { 44 | name: 'add_card_to_listEval', 45 | description: 'Evaluates the add_card_to_list tool', 46 | run: async () => { 47 | const result = await grade( 48 | openai('gpt-4'), 49 | "Please add a new card named 'Demo Card' to the list with ID 'abc123', with a description of 'This is a test card', due date '2023-12-31T12:00:00Z', start date '2025-08-05', and a label 'priority'." 50 | ); 51 | return JSON.parse(result); 52 | }, 53 | }; 54 | 55 | const update_card_detailsEval: EvalFunction = { 56 | name: 'update_card_details Evaluation', 57 | description: 'Evaluates the update_card_details tool functionality', 58 | run: async () => { 59 | const result = await grade( 60 | openai('gpt-4'), 61 | "Please update the card with ID 'abc123' to have the name 'Updated Card Name', the description 'New description for the card', a due date of '2024-01-01T10:00:00Z', start date '2025-08-05', and labels ['priority','review']." 62 | ); 63 | return JSON.parse(result); 64 | }, 65 | }; 66 | 67 | const mark_card_completeEval: EvalFunction = { 68 | name: 'mark_card_complete Evaluation', 69 | description: 'Evaluates the ability to mark a card as complete using dueComplete', 70 | run: async () => { 71 | const result = await grade( 72 | openai('gpt-4'), 73 | "Please mark the card with ID 'xyz789' as complete by setting dueComplete to true." 74 | ); 75 | return JSON.parse(result); 76 | }, 77 | }; 78 | 79 | const config: EvalConfig = { 80 | model: openai('gpt-4'), 81 | evals: [ 82 | get_cards_by_list_idEval, 83 | get_listsEval, 84 | get_recent_activityEvalFunction, 85 | add_card_to_listEval, 86 | update_card_detailsEval, 87 | mark_card_completeEval, 88 | ], 89 | }; 90 | 91 | export default config; 92 | 93 | export const evals = [ 94 | get_cards_by_list_idEval, 95 | get_listsEval, 96 | get_recent_activityEvalFunction, 97 | add_card_to_listEval, 98 | update_card_detailsEval, 99 | mark_card_completeEval, 100 | ]; 101 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface TrelloConfig { 2 | apiKey: string; 3 | token: string; 4 | defaultBoardId?: string; 5 | boardId?: string; 6 | workspaceId?: string; 7 | } 8 | 9 | export interface TrelloBoard { 10 | id: string; 11 | name: string; 12 | desc: string; 13 | closed: boolean; 14 | idOrganization: string; 15 | url: string; 16 | shortUrl: string; 17 | } 18 | 19 | export interface TrelloWorkspace { 20 | id: string; 21 | name: string; 22 | displayName: string; 23 | desc?: string; 24 | url: string; 25 | website?: string; 26 | } 27 | 28 | export interface TrelloCard { 29 | id: string; 30 | name: string; 31 | desc: string; 32 | due: string | null; 33 | idList: string; 34 | idLabels: string[]; 35 | closed: boolean; 36 | url: string; 37 | dateLastActivity: string; 38 | } 39 | 40 | export interface TrelloList { 41 | id: string; 42 | name: string; 43 | closed: boolean; 44 | idBoard: string; 45 | pos: number; 46 | } 47 | 48 | export interface TrelloAction { 49 | id: string; 50 | idMemberCreator: string; 51 | type: string; 52 | date: string; 53 | data: { 54 | text?: string; 55 | card?: { 56 | id: string; 57 | name: string; 58 | }; 59 | list?: { 60 | id: string; 61 | name: string; 62 | }; 63 | board: { 64 | id: string; 65 | name: string; 66 | }; 67 | }; 68 | memberCreator: { 69 | id: string; 70 | fullName: string; 71 | username: string; 72 | }; 73 | } 74 | 75 | export interface TrelloLabel { 76 | id: string; 77 | name: string; 78 | color: string; 79 | } 80 | 81 | export interface TrelloMember { 82 | id: string; 83 | fullName: string; 84 | username: string; 85 | avatarUrl: string | null; 86 | } 87 | 88 | export interface TrelloAttachment { 89 | id: string; 90 | name: string; 91 | url: string; 92 | fileName: string | null; 93 | bytes: number | null; 94 | date: string; 95 | mimeType: string; 96 | previews: Array<{ 97 | id: string; 98 | url: string; 99 | width: number; 100 | height: number; 101 | }>; 102 | isUpload: boolean; 103 | } 104 | 105 | export interface TrelloCheckItem { 106 | id: string; 107 | name: string; 108 | state: 'complete' | 'incomplete'; 109 | pos: number; 110 | due?: string | null; 111 | dueReminder?: number | null; 112 | idMember?: string | null; 113 | } 114 | 115 | export interface TrelloChecklist { 116 | id: string; 117 | name: string; 118 | idCard: string; 119 | pos: number; 120 | checkItems: TrelloCheckItem[]; 121 | } 122 | 123 | export interface TrelloLabelDetails { 124 | id: string; 125 | idBoard: string; 126 | name: string; 127 | color: string; 128 | } 129 | 130 | export interface TrelloComment { 131 | id: string; 132 | date: string; 133 | data: { 134 | text: string; 135 | card?: { 136 | id: string; 137 | name: string; 138 | }; 139 | }; 140 | memberCreator: { 141 | id: string; 142 | fullName: string; 143 | username: string; 144 | avatarUrl?: string; 145 | }; 146 | } 147 | 148 | export interface TrelloCustomField { 149 | id: string; 150 | name: string; 151 | type: string; 152 | value?: unknown; 153 | } 154 | 155 | export interface TrelloBadges { 156 | attachmentsByType?: { 157 | trello?: { 158 | board: number; 159 | card: number; 160 | }; 161 | }; 162 | location: boolean; 163 | votes: number; 164 | viewingMemberVoted: boolean; 165 | subscribed: boolean; 166 | fogbugz: string; 167 | checkItems: number; 168 | checkItemsChecked: number; 169 | checkItemsEarliestDue?: string | null; 170 | comments: number; 171 | attachments: number; 172 | description: boolean; 173 | due?: string | null; 174 | dueComplete: boolean; 175 | start?: string | null; 176 | } 177 | 178 | export interface TrelloCover { 179 | idAttachment?: string | null; 180 | color?: string | null; 181 | idUploadedBackground?: string | null; 182 | size: 'normal' | 'full'; 183 | brightness: 'light' | 'dark'; 184 | isTemplate: boolean; 185 | } 186 | 187 | export interface EnhancedTrelloCard { 188 | // Basic fields 189 | id: string; 190 | name: string; 191 | desc: string; 192 | descData?: { 193 | emoji?: Record; 194 | }; 195 | due: string | null; 196 | dueComplete: boolean; 197 | dueReminder: number | null; 198 | start: string | null; 199 | idList: string; 200 | idBoard: string; 201 | closed: boolean; 202 | url: string; 203 | shortUrl: string; 204 | dateLastActivity: string; 205 | pos: number; 206 | 207 | // Enhanced fields 208 | labels: TrelloLabelDetails[]; 209 | idLabels: string[]; 210 | attachments: TrelloAttachment[]; 211 | checklists: TrelloChecklist[]; 212 | members: TrelloMember[]; 213 | idMembers: string[]; 214 | comments: TrelloComment[]; 215 | customFieldItems?: TrelloCustomField[]; 216 | badges: TrelloBadges; 217 | cover: TrelloCover; 218 | 219 | // List and board info 220 | list?: { 221 | id: string; 222 | name: string; 223 | }; 224 | board?: { 225 | id: string; 226 | name: string; 227 | url: string; 228 | }; 229 | } 230 | 231 | export interface RateLimiter { 232 | canMakeRequest(): boolean; 233 | waitForAvailableToken(): Promise; 234 | } 235 | 236 | // Enhanced checklist types for MCP tools 237 | export interface CheckList { 238 | id: string; 239 | name: string; 240 | items: CheckListItem[]; 241 | percentComplete: number; 242 | } 243 | 244 | export interface CheckListItem { 245 | id: string; 246 | text: string; 247 | complete: boolean; 248 | parentCheckListId: string; 249 | } 250 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.7.0] - 2025-12-09 9 | 10 | ### Changed 11 | 12 | - **Release Preparation**: Version bump for PR #34 merge and 1.7.0 release 13 | 14 | ## [1.6.3] - 2025-10-22 15 | 16 | ### Added 17 | - **Release Script**: Added `release` script to package.json for streamlined release workflow 18 | - The release script now runs `bun run build && npm publish` to build and publish in one command 19 | - **GitHub Workflow**: Added automatic NPM publishing workflow that triggers on PR merge to main 20 | 21 | ### Changed 22 | 23 | - **Version Bump**: Updated version from 1.6.2 to 1.6.3 24 | 25 | ## [1.6.1] - 2025-10-16 26 | 27 | ### Fixed 28 | 29 | - **CHANGELOG**: Corrected release date for v1.6.0 from 2025-01-16 to 2025-10-16 30 | 31 | ## [1.6.0] - 2025-10-16 32 | 33 | ### Added 34 | 35 | - **Complete Comment Management**: Four new tools for managing comments on Trello cards: 36 | - `add_comment(cardId, text)` - Add a comment to a card (previously available) 37 | - `update_comment(commentId, text)` - Update an existing comment (previously available) 38 | - `delete_comment(commentId)` - Delete a comment from a card (NEW) 39 | - `get_card_comments(cardId, limit?)` - Retrieve all comments from a card without fetching all card data (NEW) 40 | - Enhanced comment functionality addresses feature request #24 41 | 42 | ## [1.5.1] - 2025-09-21 43 | 44 | ### Fixed 45 | 46 | - **Build Process**: Fixed the build process by adding a `build` script to `package.json` that runs `bunx tsc`. 47 | - **Dockerfile**: Migrated the `Dockerfile` from `pnpm` to `bun`. 48 | 49 | ### Changed 50 | 51 | - **README.md**: Updated the `README.md` file with the correct build and test instructions. 52 | - **Dependencies**: Removed `pnpm-lock.yaml` and `package-lock.json`. 53 | 54 | ## [1.5.0] - 2025-09-21 55 | 56 | ### Changed 57 | 58 | - **Complete Node.js → Bun Migration**: Migrated the entire project to the Bun runtime, resulting in a 2.8-4.4x performance boost. 59 | - Replaced `@types/node` and `ts-node` with `bun-types` for native Bun support. 60 | - Updated build scripts and test commands to use `bun` instead of `node` and `npm`. 61 | - Updated `package.json` to reflect the new engine requirement. 62 | 63 | ### Added 64 | 65 | - Documentation updated to recommend `bunx` for optimal performance, while maintaining full backward compatibility with `npx` and `pnpx`. 66 | 67 | ## [1.4.0] - 2025-09-21 68 | 69 | ### Added 70 | 71 | - **Comprehensive Examples**: Added a new `examples` directory with detailed implementations in JavaScript, Python, and TypeScript. 72 | - **Extensive Usage Documentation**: Created `usage-examples.md` with over 800 lines of documentation, including: 73 | - Advanced workflow examples for sprint management, bug tracking, and release management. 74 | - AI integration examples, including a workflow with the Ideogram MCP server. 75 | - Best practices for production-ready patterns, error handling, and advanced features like template systems and time tracking. 76 | - `.gitignore` updated to exclude `system-metrics.json`. 77 | 78 | ### Changed 79 | 80 | - **Version Bump**: Updated `package.json` version from `1.3.1` to `1.4.0`. 81 | 82 | ## [1.2.0] - 2025-01-07 83 | 84 | ### Added 85 | 86 | - **Comprehensive Checklist Management Tools** - Five new tools for managing Trello checklists: 87 | - `get_checklist_items(name)` - Retrieve all items from a checklist by name 88 | - `add_checklist_item(text, checkListName)` - Add new items to existing checklists 89 | - `find_checklist_items_by_description(description)` - Search checklist items by text content 90 | - `get_acceptance_criteria()` - Convenience method for "Acceptance Criteria" checklists 91 | - `get_checklist_by_name(name)` - Get complete checklist with completion percentage 92 | 93 | ### Added - Data Types 94 | 95 | - `CheckList` interface with id, name, items, and percentComplete fields 96 | - `CheckListItem` interface with id, text, complete, and parentCheckListId fields 97 | - Type conversion utilities between Trello API types and MCP types 98 | 99 | ### Added - Documentation 100 | 101 | - Comprehensive `CHECKLIST_TOOLS.md` documentation with examples and best practices 102 | - API reference for all checklist tools 103 | - Usage examples and integration guidance 104 | 105 | ### Changed 106 | 107 | - **BREAKING**: Refactored from low-level `Server` class to modern `McpServer` class 108 | - **BREAKING**: Replaced manual tool registration with `registerTool()` method 109 | - **BREAKING**: Updated all tool handlers to use Zod schema validation 110 | - Improved error handling with consistent error response format 111 | - Enhanced type safety with proper TypeScript types and `as const` assertions 112 | 113 | ### Added - Dependencies 114 | 115 | - `zod: ^3.22.4` for runtime schema validation and TypeScript type generation 116 | 117 | ### Technical Improvements 118 | 119 | - Modern MCP TypeScript SDK compliance following latest best practices 120 | - Automatic tool discovery and registration via SDK 121 | - Runtime input validation with descriptive error messages 122 | - Cleaner code structure with individual tool registration 123 | - Better maintainability for adding/modifying tools 124 | 125 | ### Fixed 126 | 127 | - TypeScript compilation errors with proper MCP response types 128 | - Consistent error handling across all tools 129 | - Proper type assertions for MCP content responses 130 | 131 | ## [1.1.0] - Previous Release 132 | 133 | ### Added 134 | 135 | - Initial MCP server implementation with 18 core Trello tools 136 | - Board and workspace management 137 | - Card CRUD operations 138 | - List management 139 | - Activity tracking 140 | - Image attachment support 141 | - Configuration persistence 142 | 143 | ### Features 144 | 145 | - Support for multiple boards and workspaces 146 | - Rate limiting for Trello API compliance 147 | - Markdown formatting for card details 148 | - Environment variable configuration 149 | - Docker support 150 | 151 | --- 152 | 153 | ## Migration Guide for v1.2.0 154 | 155 | ### For Users 156 | 157 | - No breaking changes for end users 158 | - All existing tools continue to work as before 159 | - New checklist tools are available immediately 160 | - Improved error messages and validation 161 | 162 | ### For Developers 163 | 164 | - The internal architecture has been modernized but the external API remains the same 165 | - If extending the server, use the new `registerTool()` pattern with Zod schemas 166 | - See `CHECKLIST_TOOLS.md` for examples of the new implementation pattern 167 | 168 | ### New Capabilities 169 | 170 | - Full checklist lifecycle management 171 | - Advanced search capabilities across checklists 172 | - Completion tracking and progress monitoring 173 | - Seamless integration with existing card and board tools 174 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # MCP Server Trello - Examples 2 | 3 | This directory contains comprehensive examples demonstrating how to use the MCP Server Trello in various scenarios and programming languages. 4 | 5 | ## 📚 Available Examples 6 | 7 | ### 1. [Usage Examples](./usage-examples.md) 8 | Comprehensive guide with practical examples covering: 9 | - Initial setup and configuration 10 | - Board and workspace management 11 | - Card management workflows 12 | - List operations 13 | - Checklist management 14 | - Comments and collaboration 15 | - File and image attachments 16 | - Advanced workflows (Sprint planning, Daily standup, etc.) 17 | - Real-world scenarios 18 | 19 | ### 2. [JavaScript Examples](./javascript-examples.js) 20 | Node.js/JavaScript implementation examples featuring: 21 | - Sprint Management System 22 | - Bug Tracking System 23 | - Release Management 24 | - Daily Standup Assistant 25 | - Complete working classes ready to use 26 | 27 | ### 3. [Python Examples](./python-examples.py) 28 | Python implementation examples including: 29 | - Task Priority System 30 | - Kanban Board Automation 31 | - Retrospective Management 32 | - Project Analytics Dashboard 33 | - Type-safe implementations with dataclasses 34 | 35 | ### 4. [TypeScript Examples](./typescript-examples.ts) 36 | TypeScript examples with full type safety: 37 | - Agile Board Manager 38 | - Automation Rules Engine 39 | - Time Tracking System 40 | - Template System 41 | - Complete type definitions and interfaces 42 | 43 | ## 🚀 Quick Start 44 | 45 | ### Prerequisites 46 | 47 | 1. **Set up MCP Server Trello**: 48 | ```bash 49 | npm install -g @delorenj/mcp-server-trello 50 | ``` 51 | 52 | 2. **Configure your environment**: 53 | ```bash 54 | export TRELLO_API_KEY="your-api-key" 55 | export TRELLO_TOKEN="your-token" 56 | ``` 57 | 58 | 3. **Get your Trello credentials**: 59 | - API Key: https://trello.com/app-key 60 | - Token: Generate using your API key 61 | 62 | ### Using the Examples 63 | 64 | #### JavaScript/Node.js 65 | ```javascript 66 | const { TrelloMCPClient, SprintManager } = require('./javascript-examples'); 67 | 68 | const client = new TrelloMCPClient(); 69 | const sprintManager = new SprintManager(client); 70 | 71 | // Initialize a new sprint 72 | await sprintManager.initializeSprint(23, '2025-01-22', '2025-02-05'); 73 | ``` 74 | 75 | #### Python 76 | ```python 77 | from python_examples import TrelloMCPClient, TaskManager, Priority, Task 78 | 79 | client = TrelloMCPClient() 80 | task_manager = TaskManager(client) 81 | 82 | # Create a high-priority task 83 | task = Task( 84 | name="Critical Bug Fix", 85 | description="Fix production issue", 86 | priority=Priority.CRITICAL 87 | ) 88 | await task_manager.create_task(task) 89 | ``` 90 | 91 | #### TypeScript 92 | ```typescript 93 | import { TrelloMCPClient, AgileBoard, UserStory } from './typescript-examples'; 94 | 95 | const client = new TrelloMCPClient(); 96 | const board = new AgileBoard(client); 97 | 98 | // Create a user story 99 | const story: UserStory = { 100 | title: 'New Feature', 101 | description: 'Implement new feature', 102 | acceptanceCriteria: ['AC1', 'AC2'], 103 | priority: 'high' 104 | }; 105 | await board.createUserStory(story); 106 | ``` 107 | 108 | ## 📖 Example Categories 109 | 110 | ### Project Management 111 | - Sprint Planning and Management 112 | - Agile Board Workflows 113 | - Kanban Board Automation 114 | - Release Management 115 | - Retrospective Management 116 | 117 | ### Task Management 118 | - Priority-based Task Systems 119 | - Bug Tracking Workflows 120 | - User Story Management 121 | - Time Tracking 122 | - Task Templates 123 | 124 | ### Automation 125 | - Automation Rules Engine 126 | - Workflow Automation 127 | - Status Updates 128 | - Notification Systems 129 | - Card Movement Rules 130 | 131 | ### Analytics & Reporting 132 | - Sprint Velocity Tracking 133 | - Burndown Charts 134 | - Project Health Metrics 135 | - Team Performance Analytics 136 | - Cycle Time Analysis 137 | 138 | ### Integration Examples 139 | - AI Image Generation (with Ideogram) 140 | - CI/CD Pipeline Integration 141 | - GitHub Integration Patterns 142 | - Slack Notifications 143 | - Custom Webhook Handlers 144 | 145 | ## 🏗️ Architecture Patterns 146 | 147 | ### Client Wrapper Pattern 148 | All examples use a client wrapper pattern for cleaner code: 149 | ```javascript 150 | class TrelloMCPClient { 151 | async callTool(toolName, args) { 152 | // MCP tool invocation 153 | } 154 | } 155 | ``` 156 | 157 | ### Manager Classes 158 | Organize functionality into logical manager classes: 159 | - `SprintManager` - Sprint-specific operations 160 | - `BugTracker` - Bug management workflows 161 | - `ReleaseManager` - Release coordination 162 | - `TaskManager` - General task operations 163 | 164 | ### Type Safety (TypeScript) 165 | Full type definitions for all Trello entities: 166 | ```typescript 167 | interface TrelloCard { 168 | id: string; 169 | name: string; 170 | // ... complete type definition 171 | } 172 | ``` 173 | 174 | ## 🔧 Common Patterns 175 | 176 | ### Error Handling 177 | ```javascript 178 | try { 179 | const card = await client.addCardToList({...}); 180 | } catch (error) { 181 | if (error.message.includes('rate limit')) { 182 | // Handle rate limiting 183 | } else if (error.message.includes('unauthorized')) { 184 | // Handle auth errors 185 | } 186 | } 187 | ``` 188 | 189 | ### Batch Operations 190 | ```javascript 191 | // Process multiple cards efficiently 192 | const cards = await client.getCardsByListId(listId); 193 | const updates = cards.map(card => updateCard(card)); 194 | await Promise.all(updates); 195 | ``` 196 | 197 | ### Template Usage 198 | ```javascript 199 | // Use templates for consistent card creation 200 | const template = templateManager.getTemplate('bug-report'); 201 | const card = await templateManager.createFromTemplate( 202 | 'bug-report', 203 | listId, 204 | customValues 205 | ); 206 | ``` 207 | 208 | ## 🎯 Best Practices 209 | 210 | 1. **Always specify boardId** when working with multiple boards 211 | 2. **Use templates** for consistent card creation 212 | 3. **Implement retry logic** for rate limit handling 213 | 4. **Cache frequently accessed data** (lists, labels, etc.) 214 | 5. **Use batch operations** when possible 215 | 6. **Add meaningful comments** for collaboration 216 | 7. **Structure descriptions** with markdown 217 | 8. **Set appropriate due dates** based on priority 218 | 9. **Archive completed items** to keep boards clean 219 | 10. **Monitor API usage** to stay within limits 220 | 221 | ## 📊 Performance Considerations 222 | 223 | - **Rate Limits**: 300 requests/10s per API key, 100 requests/10s per token 224 | - **Batch Operations**: Group related operations to minimize API calls 225 | - **Caching**: Cache board structure (lists, labels) to reduce lookups 226 | - **Pagination**: Use limit parameters for large data sets 227 | - **Async Operations**: Use Promise.all() for parallel operations 228 | 229 | ## 🔗 Related Resources 230 | 231 | - [MCP Server Trello Documentation](../README.md) 232 | - [Trello API Documentation](https://developer.atlassian.com/cloud/trello/rest/) 233 | - [MCP Protocol Documentation](https://modelcontextprotocol.io/) 234 | - [Trello Power-Ups](https://trello.com/power-ups) 235 | 236 | ## 💡 Contributing 237 | 238 | Have a great example to share? Contributions are welcome! Please: 239 | 1. Follow the existing example structure 240 | 2. Include comprehensive comments 241 | 3. Provide error handling 242 | 4. Add to this README 243 | 5. Test your examples 244 | 245 | ## 📝 License 246 | 247 | These examples are part of the MCP Server Trello project and are licensed under the MIT License. -------------------------------------------------------------------------------- /src/validators.ts: -------------------------------------------------------------------------------- 1 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; 2 | 3 | export function validateString(value: unknown, field: string): string { 4 | if (typeof value !== 'string') { 5 | throw new McpError(ErrorCode.InvalidParams, `${field} must be a string`); 6 | } 7 | return value; 8 | } 9 | 10 | export function validateOptionalString(value: unknown): string | undefined { 11 | if (value === undefined) return undefined; 12 | return validateString(value, 'value'); 13 | } 14 | 15 | export function validateNumber(value: unknown, field: string): number { 16 | if (typeof value !== 'number') { 17 | throw new McpError(ErrorCode.InvalidParams, `${field} must be a number`); 18 | } 19 | return value; 20 | } 21 | 22 | export function validateOptionalNumber(value: unknown): number | undefined { 23 | if (value === undefined) return undefined; 24 | return validateNumber(value, 'value'); 25 | } 26 | 27 | export function validateStringArray(value: unknown): string[] { 28 | if (!Array.isArray(value) || !value.every(item => typeof item === 'string')) { 29 | throw new McpError(ErrorCode.InvalidParams, 'Value must be an array of strings'); 30 | } 31 | return value; 32 | } 33 | 34 | export function validateOptionalStringArray(value: unknown): string[] | undefined { 35 | if (value === undefined) return undefined; 36 | return validateStringArray(value); 37 | } 38 | 39 | export function validateBoolean(value: unknown, field: string): boolean { 40 | if (typeof value !== 'boolean') { 41 | throw new McpError(ErrorCode.InvalidParams, `${field} must be a boolean`); 42 | } 43 | return value; 44 | } 45 | 46 | export function validateOptionalBoolean(value: unknown): boolean | undefined { 47 | if (value === undefined) return undefined; 48 | return validateBoolean(value, 'value'); 49 | } 50 | 51 | export function validateOptionalDateString(value: unknown): string | undefined { 52 | if (value === undefined) return undefined; 53 | const dateStr = validateString(value, 'date'); 54 | if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { 55 | throw new McpError(ErrorCode.InvalidParams, 'date must be in YYYY-MM-DD format'); 56 | } 57 | return dateStr; 58 | } 59 | 60 | export function validateGetCardsListRequest(args: Record): { 61 | boardId?: string; 62 | listId: string; 63 | } { 64 | if (!args.listId) { 65 | throw new McpError(ErrorCode.InvalidParams, 'listId is required'); 66 | } 67 | return { 68 | boardId: args.boardId ? validateString(args.boardId, 'boardId') : undefined, 69 | listId: validateString(args.listId, 'listId'), 70 | }; 71 | } 72 | 73 | export function validateGetListsRequest(args: Record): { boardId?: string } { 74 | return { 75 | boardId: args.boardId ? validateString(args.boardId, 'boardId') : undefined, 76 | }; 77 | } 78 | 79 | export function validateGetRecentActivityRequest(args: Record): { 80 | boardId?: string; 81 | limit?: number; 82 | } { 83 | return { 84 | boardId: args.boardId ? validateString(args.boardId, 'boardId') : undefined, 85 | limit: validateOptionalNumber(args.limit), 86 | }; 87 | } 88 | 89 | export function validateAddCardRequest(args: Record): { 90 | boardId?: string; 91 | listId: string; 92 | name: string; 93 | description?: string; 94 | dueDate?: string; 95 | start?: string; 96 | labels?: string[]; 97 | } { 98 | if (!args.listId || !args.name) { 99 | throw new McpError(ErrorCode.InvalidParams, 'listId and name are required'); 100 | } 101 | 102 | return { 103 | boardId: args.boardId ? validateString(args.boardId, 'boardId') : undefined, 104 | listId: validateString(args.listId, 'listId'), 105 | name: validateString(args.name, 'name'), 106 | description: validateOptionalString(args.description), 107 | dueDate: validateOptionalString(args.dueDate), 108 | start: validateOptionalDateString(args.start), 109 | labels: validateOptionalStringArray(args.labels), 110 | }; 111 | } 112 | 113 | export function validateUpdateCardRequest(args: Record): { 114 | boardId?: string; 115 | cardId: string; 116 | name?: string; 117 | description?: string; 118 | dueDate?: string; 119 | start?: string; 120 | dueComplete?: boolean; 121 | labels?: string[]; 122 | } { 123 | if (!args.cardId) { 124 | throw new McpError(ErrorCode.InvalidParams, 'cardId is required'); 125 | } 126 | return { 127 | boardId: args.boardId ? validateString(args.boardId, 'boardId') : undefined, 128 | cardId: validateString(args.cardId, 'cardId'), 129 | name: validateOptionalString(args.name), 130 | description: validateOptionalString(args.description), 131 | dueDate: validateOptionalString(args.dueDate), 132 | start: validateOptionalDateString(args.start), 133 | dueComplete: validateOptionalBoolean(args.dueComplete), 134 | labels: validateOptionalStringArray(args.labels), 135 | }; 136 | } 137 | 138 | export function validateArchiveCardRequest(args: Record): { 139 | boardId?: string; 140 | cardId: string; 141 | } { 142 | if (!args.cardId) { 143 | throw new McpError(ErrorCode.InvalidParams, 'cardId is required'); 144 | } 145 | return { 146 | boardId: args.boardId ? validateString(args.boardId, 'boardId') : undefined, 147 | cardId: validateString(args.cardId, 'cardId'), 148 | }; 149 | } 150 | 151 | export function validateAddListRequest(args: Record): { 152 | boardId?: string; 153 | name: string; 154 | } { 155 | if (!args.name) { 156 | throw new McpError(ErrorCode.InvalidParams, 'name is required'); 157 | } 158 | return { 159 | boardId: args.boardId ? validateString(args.boardId, 'boardId') : undefined, 160 | name: validateString(args.name, 'name'), 161 | }; 162 | } 163 | 164 | export function validateArchiveListRequest(args: Record): { 165 | boardId?: string; 166 | listId: string; 167 | } { 168 | if (!args.listId) { 169 | throw new McpError(ErrorCode.InvalidParams, 'listId is required'); 170 | } 171 | return { 172 | boardId: args.boardId ? validateString(args.boardId, 'boardId') : undefined, 173 | listId: validateString(args.listId, 'listId'), 174 | }; 175 | } 176 | 177 | export function validateMoveCardRequest(args: Record): { 178 | boardId?: string; 179 | cardId: string; 180 | listId: string; 181 | } { 182 | if (!args.cardId || !args.listId) { 183 | throw new McpError(ErrorCode.InvalidParams, 'cardId and listId are required'); 184 | } 185 | return { 186 | boardId: args.boardId ? validateString(args.boardId, 'boardId') : undefined, 187 | cardId: validateString(args.cardId, 'cardId'), 188 | listId: validateString(args.listId, 'listId'), 189 | }; 190 | } 191 | 192 | export function validateAttachImageRequest(args: Record): { 193 | boardId?: string; 194 | cardId: string; 195 | imageUrl: string; 196 | name?: string; 197 | } { 198 | if (!args.cardId || !args.imageUrl) { 199 | throw new McpError(ErrorCode.InvalidParams, 'cardId and imageUrl are required'); 200 | } 201 | 202 | const imageUrl = validateString(args.imageUrl, 'imageUrl'); 203 | try { 204 | new URL(imageUrl); 205 | } catch (e) { 206 | throw new McpError(ErrorCode.InvalidParams, 'imageUrl must be a valid URL'); 207 | } 208 | 209 | return { 210 | boardId: args.boardId ? validateString(args.boardId, 'boardId') : undefined, 211 | cardId: validateString(args.cardId, 'cardId'), 212 | imageUrl: imageUrl, 213 | name: validateOptionalString(args.name), 214 | }; 215 | } 216 | 217 | export function validateSetActiveBoardRequest(args: Record): { boardId: string } { 218 | if (!args.boardId) { 219 | throw new McpError(ErrorCode.InvalidParams, 'boardId is required'); 220 | } 221 | return { 222 | boardId: validateString(args.boardId, 'boardId'), 223 | }; 224 | } 225 | 226 | export function validateSetActiveWorkspaceRequest(args: Record): { 227 | workspaceId: string; 228 | } { 229 | if (!args.workspaceId) { 230 | throw new McpError(ErrorCode.InvalidParams, 'workspaceId is required'); 231 | } 232 | return { 233 | workspaceId: validateString(args.workspaceId, 'workspaceId'), 234 | }; 235 | } 236 | 237 | export function validateListBoardsInWorkspaceRequest(args: Record): { 238 | workspaceId: string; 239 | } { 240 | if (!args.workspaceId) { 241 | throw new McpError(ErrorCode.InvalidParams, 'workspaceId is required'); 242 | } 243 | return { 244 | workspaceId: validateString(args.workspaceId, 'workspaceId'), 245 | }; 246 | } 247 | 248 | export function validateGetCardRequest(args: Record): { 249 | cardId: string; 250 | includeMarkdown?: boolean; 251 | } { 252 | if (!args.cardId) { 253 | throw new McpError(ErrorCode.InvalidParams, 'cardId is required'); 254 | } 255 | return { 256 | cardId: validateString(args.cardId, 'cardId'), 257 | includeMarkdown: validateOptionalBoolean(args.includeMarkdown), 258 | }; 259 | } 260 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # Claude Code Configuration - SPARC Development Environment 2 | 3 | ## 🚨 CRITICAL: CONCURRENT EXECUTION & FILE MANAGEMENT 4 | 5 | **ABSOLUTE RULES**: 6 | 1. ALL operations MUST be concurrent/parallel in a single message 7 | 2. **NEVER save working files, text/mds and tests to the root folder** 8 | 3. ALWAYS organize files in appropriate subdirectories 9 | 10 | ### ⚡ GOLDEN RULE: "1 MESSAGE = ALL RELATED OPERATIONS" 11 | 12 | **MANDATORY PATTERNS:** 13 | - **TodoWrite**: ALWAYS batch ALL todos in ONE call (5-10+ todos minimum) 14 | - **Task tool**: ALWAYS spawn ALL agents in ONE message with full instructions 15 | - **File operations**: ALWAYS batch ALL reads/writes/edits in ONE message 16 | - **Bash commands**: ALWAYS batch ALL terminal operations in ONE message 17 | - **Memory operations**: ALWAYS batch ALL memory store/retrieve in ONE message 18 | 19 | ### 📁 File Organization Rules 20 | 21 | **NEVER save to root folder. Use these directories:** 22 | - `/src` - Source code files 23 | - `/tests` - Test files 24 | - `/docs` - Documentation and markdown files 25 | - `/config` - Configuration files 26 | - `/scripts` - Utility scripts 27 | - `/examples` - Example code 28 | 29 | ## Project Overview 30 | 31 | This project uses SPARC (Specification, Pseudocode, Architecture, Refinement, Completion) methodology with Claude-Flow orchestration for systematic Test-Driven Development. 32 | 33 | ## SPARC Commands 34 | 35 | ### Core Commands 36 | - `npx claude-flow sparc modes` - List available modes 37 | - `npx claude-flow sparc run ""` - Execute specific mode 38 | - `npx claude-flow sparc tdd ""` - Run complete TDD workflow 39 | - `npx claude-flow sparc info ` - Get mode details 40 | 41 | ### Batchtools Commands 42 | - `npx claude-flow sparc batch ""` - Parallel execution 43 | - `npx claude-flow sparc pipeline ""` - Full pipeline processing 44 | - `npx claude-flow sparc concurrent ""` - Multi-task processing 45 | 46 | ### Build Commands 47 | - `npm run build` - Build project 48 | - `npm run test` - Run tests 49 | - `npm run lint` - Linting 50 | - `npm run typecheck` - Type checking 51 | 52 | ## SPARC Workflow Phases 53 | 54 | 1. **Specification** - Requirements analysis (`sparc run spec-pseudocode`) 55 | 2. **Pseudocode** - Algorithm design (`sparc run spec-pseudocode`) 56 | 3. **Architecture** - System design (`sparc run architect`) 57 | 4. **Refinement** - TDD implementation (`sparc tdd`) 58 | 5. **Completion** - Integration (`sparc run integration`) 59 | 60 | ## Code Style & Best Practices 61 | 62 | - **Modular Design**: Files under 500 lines 63 | - **Environment Safety**: Never hardcode secrets 64 | - **Test-First**: Write tests before implementation 65 | - **Clean Architecture**: Separate concerns 66 | - **Documentation**: Keep updated 67 | 68 | ## 🚀 Available Agents (54 Total) 69 | 70 | ### Core Development 71 | `coder`, `reviewer`, `tester`, `planner`, `researcher` 72 | 73 | ### Swarm Coordination 74 | `hierarchical-coordinator`, `mesh-coordinator`, `adaptive-coordinator`, `collective-intelligence-coordinator`, `swarm-memory-manager` 75 | 76 | ### Consensus & Distributed 77 | `byzantine-coordinator`, `raft-manager`, `gossip-coordinator`, `consensus-builder`, `crdt-synchronizer`, `quorum-manager`, `security-manager` 78 | 79 | ### Performance & Optimization 80 | `perf-analyzer`, `performance-benchmarker`, `task-orchestrator`, `memory-coordinator`, `smart-agent` 81 | 82 | ### GitHub & Repository 83 | `github-modes`, `pr-manager`, `code-review-swarm`, `issue-tracker`, `release-manager`, `workflow-automation`, `project-board-sync`, `repo-architect`, `multi-repo-swarm` 84 | 85 | ### SPARC Methodology 86 | `sparc-coord`, `sparc-coder`, `specification`, `pseudocode`, `architecture`, `refinement` 87 | 88 | ### Specialized Development 89 | `backend-dev`, `mobile-dev`, `ml-developer`, `cicd-engineer`, `api-docs`, `system-architect`, `code-analyzer`, `base-template-generator` 90 | 91 | ### Testing & Validation 92 | `tdd-london-swarm`, `production-validator` 93 | 94 | ### Migration & Planning 95 | `migration-planner`, `swarm-init` 96 | 97 | ## 🎯 Claude Code vs MCP Tools 98 | 99 | ### Claude Code Handles ALL: 100 | - File operations (Read, Write, Edit, MultiEdit, Glob, Grep) 101 | - Code generation and programming 102 | - Bash commands and system operations 103 | - Implementation work 104 | - Project navigation and analysis 105 | - TodoWrite and task management 106 | - Git operations 107 | - Package management 108 | - Testing and debugging 109 | 110 | ### MCP Tools ONLY: 111 | - Coordination and planning 112 | - Memory management 113 | - Neural features 114 | - Performance tracking 115 | - Swarm orchestration 116 | - GitHub integration 117 | 118 | **KEY**: MCP coordinates, Claude Code executes. 119 | 120 | ## 🚀 Quick Setup 121 | 122 | ```bash 123 | # Add Claude Flow MCP server 124 | claude mcp add claude-flow npx claude-flow@alpha mcp start 125 | ``` 126 | 127 | ## MCP Tool Categories 128 | 129 | ### Coordination 130 | `swarm_init`, `agent_spawn`, `task_orchestrate` 131 | 132 | ### Monitoring 133 | `swarm_status`, `agent_list`, `agent_metrics`, `task_status`, `task_results` 134 | 135 | ### Memory & Neural 136 | `memory_usage`, `neural_status`, `neural_train`, `neural_patterns` 137 | 138 | ### GitHub Integration 139 | `github_swarm`, `repo_analyze`, `pr_enhance`, `issue_triage`, `code_review` 140 | 141 | ### System 142 | `benchmark_run`, `features_detect`, `swarm_monitor` 143 | 144 | ## 📋 Agent Coordination Protocol 145 | 146 | ### Every Agent MUST: 147 | 148 | **1️⃣ BEFORE Work:** 149 | ```bash 150 | npx claude-flow@alpha hooks pre-task --description "[task]" 151 | npx claude-flow@alpha hooks session-restore --session-id "swarm-[id]" 152 | ``` 153 | 154 | **2️⃣ DURING Work:** 155 | ```bash 156 | npx claude-flow@alpha hooks post-edit --file "[file]" --memory-key "swarm/[agent]/[step]" 157 | npx claude-flow@alpha hooks notify --message "[what was done]" 158 | ``` 159 | 160 | **3️⃣ AFTER Work:** 161 | ```bash 162 | npx claude-flow@alpha hooks post-task --task-id "[task]" 163 | npx claude-flow@alpha hooks session-end --export-metrics true 164 | ``` 165 | 166 | ## 🎯 Concurrent Execution Examples 167 | 168 | ### ✅ CORRECT (Single Message): 169 | ```javascript 170 | [BatchTool]: 171 | // Initialize swarm 172 | mcp__claude-flow__swarm_init { topology: "mesh", maxAgents: 6 } 173 | mcp__claude-flow__agent_spawn { type: "researcher" } 174 | mcp__claude-flow__agent_spawn { type: "coder" } 175 | mcp__claude-flow__agent_spawn { type: "tester" } 176 | 177 | // Spawn agents with Task tool 178 | Task("Research agent: Analyze requirements...") 179 | Task("Coder agent: Implement features...") 180 | Task("Tester agent: Create test suite...") 181 | 182 | // Batch todos 183 | TodoWrite { todos: [ 184 | {id: "1", content: "Research", status: "in_progress", priority: "high"}, 185 | {id: "2", content: "Design", status: "pending", priority: "high"}, 186 | {id: "3", content: "Implement", status: "pending", priority: "high"}, 187 | {id: "4", content: "Test", status: "pending", priority: "medium"}, 188 | {id: "5", content: "Document", status: "pending", priority: "low"} 189 | ]} 190 | 191 | // File operations 192 | Bash "mkdir -p app/{src,tests,docs}" 193 | Write "app/src/index.js" 194 | Write "app/tests/index.test.js" 195 | Write "app/docs/README.md" 196 | ``` 197 | 198 | ### ❌ WRONG (Multiple Messages): 199 | ```javascript 200 | Message 1: mcp__claude-flow__swarm_init 201 | Message 2: Task("agent 1") 202 | Message 3: TodoWrite { todos: [single todo] } 203 | Message 4: Write "file.js" 204 | // This breaks parallel coordination! 205 | ``` 206 | 207 | ## Performance Benefits 208 | 209 | - **84.8% SWE-Bench solve rate** 210 | - **32.3% token reduction** 211 | - **2.8-4.4x speed improvement** 212 | - **27+ neural models** 213 | 214 | ## Hooks Integration 215 | 216 | ### Pre-Operation 217 | - Auto-assign agents by file type 218 | - Validate commands for safety 219 | - Prepare resources automatically 220 | - Optimize topology by complexity 221 | - Cache searches 222 | 223 | ### Post-Operation 224 | - Auto-format code 225 | - Train neural patterns 226 | - Update memory 227 | - Analyze performance 228 | - Track token usage 229 | 230 | ### Session Management 231 | - Generate summaries 232 | - Persist state 233 | - Track metrics 234 | - Restore context 235 | - Export workflows 236 | 237 | ## Advanced Features (v2.0.0) 238 | 239 | - 🚀 Automatic Topology Selection 240 | - ⚡ Parallel Execution (2.8-4.4x speed) 241 | - 🧠 Neural Training 242 | - 📊 Bottleneck Analysis 243 | - 🤖 Smart Auto-Spawning 244 | - 🛡️ Self-Healing Workflows 245 | - 💾 Cross-Session Memory 246 | - 🔗 GitHub Integration 247 | 248 | ## Integration Tips 249 | 250 | 1. Start with basic swarm init 251 | 2. Scale agents gradually 252 | 3. Use memory for context 253 | 4. Monitor progress regularly 254 | 5. Train patterns from success 255 | 6. Enable hooks automation 256 | 7. Use GitHub tools first 257 | 258 | ## Support 259 | 260 | - Documentation: https://github.com/ruvnet/claude-flow 261 | - Issues: https://github.com/ruvnet/claude-flow/issues 262 | 263 | --- 264 | 265 | Remember: **Claude Flow coordinates, Claude Code creates!** -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # GitHub Copilot Instructions for MCP Server Trello 2 | 3 | This document provides context and guidelines for GitHub Copilot when working with the MCP Server Trello codebase. 4 | 5 | ## Project Overview 6 | 7 | MCP Server Trello is a Model Context Protocol (MCP) server that provides tools for interacting with Trello boards. The project is powered by Bun runtime for maximum performance and is written in TypeScript with strict type safety. 8 | 9 | ## Tech Stack 10 | 11 | - **Runtime**: Bun (v1.0.0+), but also compatible with Node.js via npm/npx 12 | - **Language**: TypeScript 5.3+ 13 | - **Build Tool**: TypeScript Compiler (tsc) 14 | - **Primary Dependencies**: 15 | - `@modelcontextprotocol/sdk` - MCP protocol implementation 16 | - `axios` - HTTP client for Trello API 17 | - `zod` - Runtime type validation 18 | - `form-data` - File upload handling 19 | - **Code Quality**: ESLint + Prettier 20 | - **Testing**: Not yet implemented (contributions welcome) 21 | 22 | ## Architecture 23 | 24 | ### Project Structure 25 | 26 | ``` 27 | src/ 28 | ├── index.ts # Main MCP server implementation and tool registration 29 | ├── trello-client.ts # Trello API client with rate limiting 30 | ├── rate-limiter.ts # Token bucket rate limiter implementation 31 | ├── types.ts # TypeScript type definitions 32 | ├── validators.ts # Zod schemas for input validation 33 | ├── health/ # Health monitoring endpoints 34 | │ ├── health-endpoints.ts # Health check MCP tools 35 | │ └── health-monitor.ts # Health monitoring logic 36 | └── evals/ # Evaluation tests (using mcp-evals) 37 | ``` 38 | 39 | ### Key Components 40 | 41 | 1. **TrelloServer** (`src/index.ts`): Main server class that: 42 | - Registers MCP tools via `registerTool()` 43 | - Handles tool invocations 44 | - Manages error handling 45 | - Sets up health monitoring 46 | 47 | 2. **TrelloClient** (`src/trello-client.ts`): API client that: 48 | - Manages authentication (API key + token) 49 | - Implements rate limiting (300 req/10s per key, 100 req/10s per token) 50 | - Provides methods for all Trello operations 51 | - Handles board/workspace persistence via `~/.trello-mcp/config.json` 52 | 53 | 3. **Rate Limiter** (`src/rate-limiter.ts`): 54 | - Token bucket algorithm implementation 55 | - Separate buckets for API key and token limits 56 | - Automatic request queuing when limits are reached 57 | 58 | ## Development Guidelines 59 | 60 | ### Building 61 | 62 | ```bash 63 | # Preferred (if Bun is installed) 64 | bun run build 65 | 66 | # Alternative (works anywhere) 67 | npx tsc 68 | ``` 69 | 70 | The build output goes to the `build/` directory. 71 | 72 | ### Code Style 73 | 74 | - **Indentation**: 2 spaces (no tabs) 75 | - **Quotes**: Single quotes preferred 76 | - **Semicolons**: Required 77 | - **Line Length**: 100 characters max 78 | - **Import Style**: ES modules only 79 | - **Trailing Commas**: ES5 style 80 | - **Arrow Functions**: Avoid parentheses for single parameters 81 | 82 | Run linting: 83 | ```bash 84 | npx eslint src --ext .ts 85 | ``` 86 | 87 | Run formatting: 88 | ```bash 89 | npx prettier --write src 90 | ``` 91 | 92 | ### TypeScript Guidelines 93 | 94 | - **Strict mode enabled**: All code must comply with strict TypeScript 95 | - **No implicit any**: Always provide explicit types 96 | - **Return types**: Explicit return types are preferred for public methods 97 | - **Error handling**: Use `try/catch` and return structured error objects 98 | - **Async/await**: Preferred over raw promises 99 | 100 | ### Environment Variables 101 | 102 | Required: 103 | - `TRELLO_API_KEY` - Get from https://trello.com/app-key 104 | - `TRELLO_TOKEN` - Generate using API key authorization flow 105 | 106 | Optional: 107 | - `TRELLO_BOARD_ID` - Default board (can be changed via `set_active_board` tool) 108 | - `TRELLO_WORKSPACE_ID` - Initial workspace (can be changed via `set_active_workspace` tool) 109 | 110 | ### Adding New Tools 111 | 112 | When adding a new MCP tool: 113 | 114 | 1. **Define the tool schema** using Zod in the `registerTool()` call 115 | 2. **Implement validation** using the validators in `src/validators.ts` 116 | 3. **Add the method** to `TrelloClient` if it requires API calls 117 | 4. **Handle errors gracefully** using the `handleError()` pattern 118 | 5. **Update README.md** with the new tool documentation 119 | 6. **Consider rate limiting** - all API calls are automatically rate-limited 120 | 121 | Example tool structure: 122 | ```typescript 123 | this.server.registerTool( 124 | 'tool_name', 125 | { 126 | title: 'Human Readable Title', 127 | description: 'Clear description of what this tool does', 128 | inputSchema: { 129 | param1: z.string().describe('Description of param1'), 130 | param2: z.string().optional().describe('Optional description'), 131 | }, 132 | }, 133 | async ({ param1, param2 }) => { 134 | try { 135 | const result = await this.trelloClient.someMethod(param1, param2); 136 | return { 137 | content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], 138 | }; 139 | } catch (error) { 140 | return this.handleError(error); 141 | } 142 | } 143 | ); 144 | ``` 145 | 146 | ### Working with the Trello API 147 | 148 | - **API Documentation**: https://developer.atlassian.com/cloud/trello/rest/ 149 | - **Rate Limits**: Automatically handled by `RateLimiter` class 150 | - **Authentication**: API key + token passed via query parameters 151 | - **Error Handling**: Use `McpError` from the SDK for user-facing errors 152 | - **Board Management**: Support both explicit `boardId` parameter and default board 153 | 154 | ### Common Patterns 155 | 156 | **Date Formats**: 157 | - `dueDate`: Full ISO 8601 with time (e.g., `2023-12-31T12:00:00Z`) 158 | - `start`: Date only in YYYY-MM-DD format (e.g., `2025-08-05`) 159 | 160 | **Board ID Resolution**: 161 | ```typescript 162 | // Most methods accept optional boardId, falling back to default 163 | const effectiveBoardId = boardId || this.config.boardId; 164 | if (!effectiveBoardId) { 165 | throw new McpError( 166 | ErrorCode.InvalidRequest, 167 | 'No board ID provided and no default board set' 168 | ); 169 | } 170 | ``` 171 | 172 | **Error Response Format**: 173 | ```typescript 174 | return { 175 | content: [ 176 | { 177 | type: 'text' as const, 178 | text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`, 179 | }, 180 | ], 181 | isError: true, 182 | }; 183 | ``` 184 | 185 | ## Testing 186 | 187 | Currently, there is no automated test suite. Contributions to add testing infrastructure are welcome! 188 | 189 | For manual testing: 190 | 1. Set up environment variables in `.env` (copy from `.env.template`) 191 | 2. Build the project: `npx tsc` 192 | 3. Run via MCP client or use the examples in the `examples/` directory 193 | 194 | For evaluations (requires OpenAI API key): 195 | ```bash 196 | OPENAI_API_KEY=your-key npx mcp-eval src/evals/evals.ts src/index.ts 197 | ``` 198 | 199 | ## Docker Development 200 | 201 | The project includes Docker support: 202 | 203 | ```bash 204 | # Copy environment template 205 | cp .env.template .env 206 | # Edit .env with your credentials 207 | 208 | # Build and run 209 | docker compose up --build 210 | ``` 211 | 212 | ## Contributing 213 | 214 | Please review `CONTRIBUTING.md` for detailed contribution guidelines. 215 | 216 | Key points: 217 | - Fork and create a branch from `main` 218 | - Add tests for new features (when test infrastructure exists) 219 | - Update documentation for API changes 220 | - Ensure code passes linting: `npx eslint src --ext .ts` 221 | - Format code with Prettier: `npx prettier --write src` 222 | - Follow the GitHub Flow workflow 223 | 224 | ## Files to Avoid Modifying 225 | 226 | - `package-lock.json` - Managed by npm, don't edit manually 227 | - `build/` - Generated output, not committed to git 228 | - `node_modules/` - Dependencies, not committed to git 229 | - `.env` - Local environment variables, not committed (use `.env.template` as reference) 230 | 231 | ## Important Notes 232 | 233 | 1. **Backward Compatibility**: The project supports both Bun and Node.js runtimes. Ensure changes work with both. 234 | 235 | 2. **MCP Protocol**: Follow MCP SDK conventions for tool registration and responses. 236 | 237 | 3. **Rate Limiting**: Never bypass the rate limiter - Trello will ban the API key if limits are exceeded. 238 | 239 | 4. **Error Messages**: Provide clear, actionable error messages to end users. 240 | 241 | 5. **Security**: Never commit API keys or tokens. Use environment variables. 242 | 243 | ## Useful Commands 244 | 245 | ```bash 246 | # Install dependencies 247 | npm install 248 | 249 | # Build the project 250 | npx tsc 251 | 252 | # Format code 253 | npx prettier --write src 254 | 255 | # Lint code 256 | npx eslint src --ext .ts 257 | 258 | # Publish to npm (maintainers only) 259 | npm publish 260 | 261 | # Run via npx (users) 262 | npx @delorenj/mcp-server-trello 263 | 264 | # Run via bunx (users, faster) 265 | bunx @delorenj/mcp-server-trello 266 | ``` 267 | 268 | ## Resources 269 | 270 | - [MCP Documentation](https://modelcontextprotocol.io) 271 | - [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) 272 | - [Trello REST API](https://developer.atlassian.com/cloud/trello/rest/) 273 | - [MCP Registry](https://registry.modelcontextprotocol.io/) 274 | -------------------------------------------------------------------------------- /docs/HEALTH_MONITORING.md: -------------------------------------------------------------------------------- 1 | # Trello MCP Health Monitoring System 🏥 2 | 3 | ## Overview 4 | 5 | The Trello MCP server now includes a comprehensive health monitoring system that provides real-time diagnostics and performance analysis. Think of it as having a team of world-class physicians constantly monitoring your API's cardiovascular health! 6 | 7 | ## Available Health Endpoints 8 | 9 | ### 1. Basic Health Check - `get_health` 10 | **Quick pulse check for monitoring systems** 11 | 12 | ```json 13 | { 14 | "status": "healthy|degraded|critical", 15 | "timestamp": "2025-09-01T03:13:26Z", 16 | "uptime_ms": 3600000, 17 | "checks_passed": 4, 18 | "total_checks": 4, 19 | "response_time_ms": 156, 20 | "success_rate": "98.5%" 21 | } 22 | ``` 23 | 24 | Perfect for: 25 | - Load balancer health checks 26 | - Monitoring dashboard integration 27 | - Quick operational status verification 28 | 29 | ### 2. Detailed Health Diagnostic - `get_health_detailed` 30 | **Comprehensive medical examination** 31 | 32 | Includes: 33 | - ✅ Trello API connectivity verification 34 | - 🏗️ Board access validation 35 | - ⚡ Rate limiter health analysis 36 | - 📊 Performance metrics assessment 37 | - 📋 List operations testing (detailed mode) 38 | - 🎴 Card operations verification (detailed mode) 39 | - ☑️ Checklist functionality testing (detailed mode) 40 | - 🏢 Workspace access validation (detailed mode) 41 | 42 | Returns complete `SystemHealthReport` with: 43 | - Individual check results with timing 44 | - Overall system status determination 45 | - Automated repair recommendations 46 | - Performance metrics analysis 47 | 48 | ### 3. Metadata Consistency Check - `get_health_metadata` 49 | **Data integrity scanner** 50 | 51 | Verifies consistency between: 52 | - Board configuration and accessibility 53 | - List structure and organization 54 | - Card distribution and assignments 55 | - Checklist availability and completeness 56 | - Workspace settings alignment 57 | 58 | ### 4. Performance Analysis - `get_health_performance` 59 | **Cardiovascular stress test** 60 | 61 | Provides detailed metrics: 62 | - Response time analysis with grading (A+ to F) 63 | - Success rate monitoring 64 | - Throughput measurement (requests per minute) 65 | - Rate limit utilization tracking 66 | - Performance trend analysis 67 | 68 | ### 5. Automated System Repair - `perform_system_repair` 69 | **Digital emergency room** 70 | 71 | Attempts to automatically fix: 72 | - Missing active board configuration 73 | - Workspace inconsistencies 74 | - Basic connectivity issues 75 | 76 | ## Health Status Levels 77 | 78 | - **🟢 HEALTHY**: All systems operating optimally 79 | - **🟡 DEGRADED**: Minor issues detected, functionality maintained 80 | - **🔴 CRITICAL**: Serious issues requiring immediate attention 81 | - **⚪ UNKNOWN**: Unable to determine status 82 | 83 | ## Performance Grading System 84 | 85 | The health monitor assigns letter grades (A+ to F) based on: 86 | 87 | - **Response Time (40% weight)**: 88 | - A+/A: < 200ms (excellent) 89 | - B: 200-500ms (good) 90 | - C: 500-1000ms (fair) 91 | - D: 1000-2000ms (slow) 92 | - F: > 2000ms (very slow) 93 | 94 | - **Success Rate (35% weight)**: 95 | - A+: ≥ 99% success rate 96 | - A: ≥ 95% success rate 97 | - B: ≥ 90% success rate 98 | - C: ≥ 80% success rate 99 | - D/F: < 80% success rate 100 | 101 | - **Rate Limit Utilization (25% weight)**: 102 | - A+/A: < 50% utilization (optimal) 103 | - B: 50-70% utilization (moderate) 104 | - C: 70-85% utilization (high) 105 | - D: 85-95% utilization (near limit) 106 | - F: > 95% utilization (critical) 107 | 108 | ## Usage Examples 109 | 110 | ### Basic Health Check 111 | ```typescript 112 | // Check basic system status 113 | const health = await mcpClient.callTool('get_health'); 114 | console.log('System status:', health.status); 115 | ``` 116 | 117 | ### Detailed Diagnostic 118 | ```typescript 119 | // Get comprehensive health report 120 | const detailedHealth = await mcpClient.callTool('get_health_detailed'); 121 | console.log('Recommendations:', detailedHealth.recommendations); 122 | console.log('Performance grade:', detailedHealth.performance_metrics); 123 | ``` 124 | 125 | ### Metadata Verification 126 | ```typescript 127 | // Check data consistency 128 | const metadataHealth = await mcpClient.callTool('get_health_metadata'); 129 | if (!metadataHealth.metadata_consistency.consistent) { 130 | console.log('Issues found:', metadataHealth.metadata_consistency.issues); 131 | } 132 | ``` 133 | 134 | ### Performance Analysis 135 | ```typescript 136 | // Analyze system performance 137 | const performance = await mcpClient.callTool('get_health_performance'); 138 | console.log('Grade:', performance.performance_grade); 139 | console.log('Response time rating:', performance.analysis.response_time_rating); 140 | ``` 141 | 142 | ### Automated Repair 143 | ```typescript 144 | // Attempt system repair 145 | const repair = await mcpClient.callTool('perform_system_repair'); 146 | if (repair.success) { 147 | console.log('Repairs completed:', repair.actions_taken); 148 | } 149 | ``` 150 | 151 | ## Monitoring Integration 152 | 153 | ### Prometheus Metrics 154 | The health endpoints return structured data perfect for Prometheus scraping: 155 | 156 | ```yaml 157 | # prometheus.yml 158 | scrape_configs: 159 | - job_name: 'trello-mcp-health' 160 | static_configs: 161 | - targets: ['localhost:3000'] 162 | metrics_path: '/health' 163 | scrape_interval: 30s 164 | ``` 165 | 166 | ### Grafana Dashboard 167 | Create visualizations using the performance metrics: 168 | - Response time trends 169 | - Success rate monitoring 170 | - Rate limit utilization 171 | - Health check status over time 172 | 173 | ### Alerting Rules 174 | Set up alerts based on health status: 175 | - Critical status: Immediate notification 176 | - Degraded status: Warning notification 177 | - Performance grade below B: Performance alert 178 | 179 | ## Background Monitoring 180 | 181 | The health monitor automatically: 182 | - 🔄 Tracks performance metrics for all API calls 183 | - 📊 Calculates rolling averages and success rates 184 | - 🧹 Cleans up old metrics to prevent memory leaks 185 | - 💾 Maintains request history for analysis 186 | - ⏰ Provides uptime tracking from service start 187 | 188 | ## Architecture 189 | 190 | ``` 191 | TrelloHealthMonitor 192 | ├── Performance Tracking 193 | │ ├── Request duration measurement 194 | │ ├── Success rate calculation 195 | │ └── Rate limit utilization monitoring 196 | ├── Health Checks 197 | │ ├── API connectivity verification 198 | │ ├── Board access validation 199 | │ ├── Rate limiter status 200 | │ └── Subsystem testing (detailed mode) 201 | └── Automated Analysis 202 | ├── Status determination 203 | ├── Recommendation generation 204 | └── Repair opportunity detection 205 | ``` 206 | 207 | ## Error Handling 208 | 209 | All health endpoints include robust error handling: 210 | - Graceful degradation during API issues 211 | - Detailed error reporting with context 212 | - Fallback to cached results when possible 213 | - Safe failure modes that don't impact main functionality 214 | 215 | ## Security Considerations 216 | 217 | - Health endpoints don't expose sensitive credentials 218 | - API key and token information is redacted from responses 219 | - Rate limiting protects against health check abuse 220 | - Error messages are sanitized to prevent information leakage 221 | 222 | ## Best Practices 223 | 224 | 1. **Regular Monitoring**: Check basic health every 30-60 seconds 225 | 2. **Detailed Diagnostics**: Run comprehensive checks every 5-10 minutes 226 | 3. **Performance Tracking**: Monitor trends over time, not just snapshots 227 | 4. **Alert Thresholds**: Set appropriate thresholds for your use case 228 | 5. **Repair Usage**: Use automated repair sparingly for non-critical issues 229 | 230 | ## Troubleshooting Guide 231 | 232 | ### Common Issues and Solutions 233 | 234 | **Status: DEGRADED - "No active board configured"** 235 | - Solution: Use `set_active_board` tool with a valid board ID 236 | - Prevention: Always configure a default board in environment variables 237 | 238 | **Status: CRITICAL - "Trello API connectivity failed"** 239 | - Check: Network connectivity to api.trello.com 240 | - Verify: API key and token are valid and not expired 241 | - Consider: Rate limiting or temporary API outages 242 | 243 | **Performance Grade: D or F** 244 | - Investigate: Network latency and bandwidth 245 | - Check: Trello API status page for service issues 246 | - Optimize: Reduce request frequency or implement caching 247 | 248 | **High Rate Limit Utilization** 249 | - Implement: Request batching where possible 250 | - Add: Caching layer for frequently accessed data 251 | - Consider: Distributing load across multiple API keys/tokens 252 | 253 | ## API Reference 254 | 255 | All health endpoints return consistent response formats: 256 | 257 | ```typescript 258 | interface HealthResponse { 259 | content: Array<{ type: 'text'; text: string }>; 260 | isError?: boolean; 261 | } 262 | ``` 263 | 264 | The `text` field contains JSON-formatted health data specific to each endpoint. 265 | 266 | ## Future Enhancements 267 | 268 | Planned improvements include: 269 | - Historical trend analysis 270 | - Predictive failure detection 271 | - Integration with external monitoring systems 272 | - Custom health check definitions 273 | - Advanced repair capabilities 274 | - Performance optimization recommendations 275 | 276 | --- 277 | 278 | *The health monitoring system: Because your API deserves world-class medical care!* 🩺✨ -------------------------------------------------------------------------------- /docs/REASONINGBANK_EXAMPLES.md: -------------------------------------------------------------------------------- 1 | # ReasoningBank Examples & Code Snippets 2 | 3 | ## Quick Reference 4 | 5 | This document provides ready-to-use examples and code snippets for common ReasoningBank operations. 6 | 7 | ## Table of Contents 8 | 9 | - [CLI Examples](#cli-examples) 10 | - [JavaScript/TypeScript Examples](#javascripttypescript-examples) 11 | - [Use Case Examples](#use-case-examples) 12 | - [Integration Patterns](#integration-patterns) 13 | 14 | --- 15 | 16 | ## CLI Examples 17 | 18 | ### Basic Operations 19 | 20 | ```bash 21 | # Store a simple pattern 22 | npx claude-flow@alpha memory store api_key \ 23 | "JWT tokens with HMAC SHA256 signing" \ 24 | --namespace backend --reasoningbank 25 | 26 | # Query with semantic search 27 | npx claude-flow@alpha memory query "authentication" \ 28 | --namespace backend --reasoningbank 29 | 30 | # List all patterns in namespace 31 | npx claude-flow@alpha memory list --namespace backend --reasoningbank 32 | 33 | # Delete a pattern 34 | npx claude-flow@alpha memory delete api_key \ 35 | --namespace backend --reasoningbank 36 | 37 | # Check system status 38 | npx claude-flow@alpha memory status --reasoningbank 39 | ``` 40 | 41 | ### Advanced Queries 42 | 43 | ```bash 44 | # Query with confidence threshold 45 | npx claude-flow@alpha memory query "security" \ 46 | --min-confidence 0.7 --reasoningbank 47 | 48 | # Query with similarity threshold 49 | npx claude-flow@alpha memory query "performance" \ 50 | --min-similarity 0.8 --reasoningbank 51 | 52 | # Cross-namespace search 53 | npx claude-flow@alpha memory query "API design" --reasoningbank 54 | 55 | # Query with limit 56 | npx claude-flow@alpha memory query "optimization" \ 57 | --limit 5 --reasoningbank 58 | ``` 59 | 60 | ### Cognitive Patterns 61 | 62 | ```bash 63 | # Store with cognitive pattern 64 | npx claude-flow@alpha memory store debug_strategy \ 65 | "Use binary search to isolate bugs" \ 66 | --cognitive-pattern convergent \ 67 | --reasoningbank 68 | 69 | # Query by cognitive pattern 70 | npx claude-flow@alpha memory query "problem solving" \ 71 | --cognitive-pattern divergent --reasoningbank 72 | ``` 73 | 74 | --- 75 | 76 | ## JavaScript/TypeScript Examples 77 | 78 | ### Basic Integration 79 | 80 | ```javascript 81 | import { spawn } from 'child_process'; 82 | import path from 'path'; 83 | 84 | class ReasoningBankClient { 85 | constructor() { 86 | this.messageId = 0; 87 | this.pendingRequests = new Map(); 88 | } 89 | 90 | async initialize() { 91 | // Spawn agentic-flow process 92 | const agenticFlowPath = path.join( 93 | process.cwd(), 94 | 'node_modules/agentic-flow/dist/index.js' 95 | ); 96 | 97 | this.process = spawn('node', [agenticFlowPath]); 98 | 99 | // Handle responses 100 | this.process.stdout.on('data', data => { 101 | try { 102 | const response = JSON.parse(data.toString()); 103 | const handler = this.pendingRequests.get(response.id); 104 | 105 | if (handler) { 106 | if (response.error) { 107 | handler.reject(new Error(response.error.message)); 108 | } else { 109 | handler.resolve(response.result); 110 | } 111 | this.pendingRequests.delete(response.id); 112 | } 113 | } catch (err) { 114 | console.error('Failed to parse response:', err); 115 | } 116 | }); 117 | 118 | this.process.stderr.on('data', data => { 119 | console.error('agentic-flow error:', data.toString()); 120 | }); 121 | } 122 | 123 | async sendRequest(method, params) { 124 | const id = ++this.messageId; 125 | 126 | return new Promise((resolve, reject) => { 127 | const timeout = setTimeout(() => { 128 | this.pendingRequests.delete(id); 129 | reject(new Error('Request timeout')); 130 | }, 30000); // 30 second timeout 131 | 132 | this.pendingRequests.set(id, { 133 | resolve: result => { 134 | clearTimeout(timeout); 135 | resolve(result); 136 | }, 137 | reject: error => { 138 | clearTimeout(timeout); 139 | reject(error); 140 | } 141 | }); 142 | 143 | const request = JSON.stringify({ 144 | jsonrpc: '2.0', 145 | method, 146 | params, 147 | id 148 | }); 149 | 150 | this.process.stdin.write(request + '\n'); 151 | }); 152 | } 153 | 154 | async storePattern(title, content, options = {}) { 155 | return this.sendRequest('storePattern', { 156 | title, 157 | content, 158 | namespace: options.namespace || 'default', 159 | components: options.components || {} 160 | }); 161 | } 162 | 163 | async query(query, options = {}) { 164 | return this.sendRequest('searchPatterns', { 165 | query, 166 | namespace: options.namespace || 'default', 167 | limit: options.limit || 10, 168 | minConfidence: options.minConfidence, 169 | minSimilarity: options.minSimilarity 170 | }); 171 | } 172 | 173 | async getPattern(id) { 174 | return this.sendRequest('getPattern', { id }); 175 | } 176 | 177 | async deletePattern(id) { 178 | return this.sendRequest('deletePattern', { id }); 179 | } 180 | 181 | async getStatistics(namespace) { 182 | return this.sendRequest('getStatistics', { namespace }); 183 | } 184 | 185 | shutdown() { 186 | if (this.process) { 187 | this.process.kill(); 188 | } 189 | } 190 | } 191 | 192 | // Usage 193 | const client = new ReasoningBankClient(); 194 | await client.initialize(); 195 | 196 | // Store a pattern 197 | await client.storePattern('jwt_auth', 'JWT with refresh tokens', { 198 | namespace: 'backend', 199 | components: { reliability: 0.8 } 200 | }); 201 | 202 | // Query patterns 203 | const results = await client.query('authentication', { 204 | namespace: 'backend', 205 | limit: 5 206 | }); 207 | 208 | console.log('Found patterns:', results); 209 | 210 | // Cleanup 211 | client.shutdown(); 212 | ``` 213 | 214 | ### Self-Learning Agent 215 | 216 | ```javascript 217 | class SelfLearningAgent { 218 | constructor(namespace) { 219 | this.namespace = namespace; 220 | this.client = new ReasoningBankClient(); 221 | } 222 | 223 | async initialize() { 224 | await this.client.initialize(); 225 | } 226 | 227 | async learn(task, outcome, notes) { 228 | // Store the task as a pattern 229 | await this.client.storePattern( 230 | `task_${Date.now()}`, 231 | `${task} - ${notes}`, 232 | { 233 | namespace: this.namespace, 234 | components: { 235 | reliability: outcome === 'success' ? 0.7 : 0.3, 236 | task_type: this.classifyTask(task), 237 | outcome, 238 | timestamp: Date.now() 239 | } 240 | } 241 | ); 242 | } 243 | 244 | async recall(query, options = {}) { 245 | // Query relevant past experiences 246 | const results = await this.client.query(query, { 247 | namespace: this.namespace, 248 | minConfidence: options.minConfidence || 0.5, 249 | limit: options.limit || 5 250 | }); 251 | 252 | // Sort by reliability 253 | return results.sort((a, b) => 254 | (b.components?.reliability || 0) - (a.components?.reliability || 0) 255 | ); 256 | } 257 | 258 | async solve(task) { 259 | // 1. Recall similar past tasks 260 | const similarTasks = await this.recall(task, { minConfidence: 0.6 }); 261 | 262 | if (similarTasks.length === 0) { 263 | console.log('No prior experience found. Exploring...'); 264 | return this.exploreNewSolution(task); 265 | } 266 | 267 | // 2. Apply highest confidence approach 268 | const bestApproach = similarTasks[0]; 269 | console.log(`Applying approach: ${bestApproach.title}`); 270 | console.log(`Confidence: ${(bestApproach.components.reliability * 100).toFixed(0)}%`); 271 | 272 | // 3. Execute approach 273 | const result = await this.executeApproach(bestApproach, task); 274 | 275 | // 4. Learn from outcome 276 | await this.learn(task, result.success ? 'success' : 'failure', result.notes); 277 | 278 | return result; 279 | } 280 | 281 | async exploreNewSolution(task) { 282 | // Implement exploration logic 283 | return { 284 | success: false, 285 | notes: 'Exploration in progress' 286 | }; 287 | } 288 | 289 | async executeApproach(approach, task) { 290 | // Implement execution logic 291 | return { 292 | success: true, 293 | notes: `Successfully applied ${approach.title}` 294 | }; 295 | } 296 | 297 | classifyTask(task) { 298 | // Simple classification based on keywords 299 | const keywords = task.toLowerCase(); 300 | 301 | if (keywords.includes('auth') || keywords.includes('login')) { 302 | return 'authentication'; 303 | } else if (keywords.includes('database') || keywords.includes('query')) { 304 | return 'database'; 305 | } else if (keywords.includes('api')) { 306 | return 'api'; 307 | } else { 308 | return 'general'; 309 | } 310 | } 311 | 312 | shutdown() { 313 | this.client.shutdown(); 314 | } 315 | } 316 | 317 | // Usage 318 | const agent = new SelfLearningAgent('development'); 319 | await agent.initialize(); 320 | 321 | // Agent learns from experience 322 | await agent.solve('Implement user authentication'); 323 | await agent.solve('Build REST API endpoints'); 324 | await agent.solve('Add JWT token validation'); 325 | 326 | // Agent gets smarter over time! 327 | const approaches = await agent.recall('authentication'); 328 | console.log('Learned approaches:', approaches); 329 | 330 | agent.shutdown(); 331 | ``` 332 | 333 | --- 334 | 335 | ## Use Case Examples 336 | 337 | ### 1. Team Knowledge Base 338 | 339 | ```bash 340 | # Team members store architectural decisions 341 | npx claude-flow@alpha memory store arch_001 \ 342 | "Microservices with event-driven architecture using Kafka" \ 343 | --namespace team_decisions --reasoningbank 344 | 345 | npx claude-flow@alpha memory store arch_002 \ 346 | "Use PostgreSQL for transactional data, Redis for caching" \ 347 | --namespace team_decisions --reasoningbank 348 | 349 | npx claude-flow@alpha memory store arch_003 \ 350 | "API Gateway pattern with Kong for rate limiting and auth" \ 351 | --namespace team_decisions --reasoningbank 352 | 353 | # New team member queries decisions 354 | npx claude-flow@alpha memory query "message queue" \ 355 | --namespace team_decisions --reasoningbank 356 | # Returns: arch_001 (Kafka decision) 357 | 358 | npx claude-flow@alpha memory query "database choice" \ 359 | --namespace team_decisions --reasoningbank 360 | # Returns: arch_002 (PostgreSQL/Redis) 361 | ``` 362 | 363 | ### 2. Bug Solution Database 364 | 365 | ```bash 366 | # Store bug solutions as you fix them 367 | npx claude-flow@alpha memory store bug_cors_error \ 368 | "CORS error on API calls: Add Access-Control-Allow-Origin header in Express middleware" \ 369 | --namespace debugging --reasoningbank 370 | 371 | npx claude-flow@alpha memory store bug_react_rerender \ 372 | "Infinite re-renders in useEffect: Use useRef for values that don't need re-renders" \ 373 | --namespace debugging --reasoningbank 374 | 375 | npx claude-flow@alpha memory store bug_memory_leak \ 376 | "Memory leak in React: Clean up subscriptions in useEffect return function" \ 377 | --namespace debugging --reasoningbank 378 | 379 | # Later, when you encounter similar bugs 380 | npx claude-flow@alpha memory query "React infinite loop" \ 381 | --namespace debugging --reasoningbank 382 | # Instantly finds the solution! 383 | ``` 384 | 385 | ### 3. API Best Practices Library 386 | 387 | ```bash 388 | # Build a library of API patterns 389 | npx claude-flow@alpha memory store api_versioning \ 390 | "Use /api/v1/, /api/v2/ URL versioning for backward compatibility" \ 391 | --namespace api_patterns --reasoningbank 392 | 393 | npx claude-flow@alpha memory store api_pagination \ 394 | "Cursor-based pagination: /api/items?limit=20&cursor=abc123" \ 395 | --namespace api_patterns --reasoningbank 396 | 397 | npx claude-flow@alpha memory store api_error_format \ 398 | 'Consistent error format: {"error": true, "message": "...", "code": 400}' \ 399 | --namespace api_patterns --reasoningbank 400 | 401 | npx claude-flow@alpha memory store api_rate_limiting \ 402 | "100 requests/minute per IP using sliding window algorithm" \ 403 | --namespace api_patterns --reasoningbank 404 | 405 | # Query when designing new API 406 | npx claude-flow@alpha memory query "API response format" \ 407 | --namespace api_patterns --reasoningbank 408 | ``` 409 | 410 | --- 411 | 412 | ## Integration Patterns 413 | 414 | ### Pattern 1: Pre-Task Retrieval 415 | 416 | ```javascript 417 | // Before starting a task, retrieve relevant patterns 418 | async function executeTask(task) { 419 | // 1. Query ReasoningBank 420 | const relevantPatterns = await reasoningBank.query(task.description, { 421 | namespace: task.domain, 422 | limit: 3 423 | }); 424 | 425 | // 2. Display patterns to user/agent 426 | console.log('Relevant past experiences:'); 427 | relevantPatterns.forEach((pattern, i) => { 428 | console.log(`${i + 1}. ${pattern.title} (${(pattern.score * 100).toFixed(0)}% match)`); 429 | console.log(` ${pattern.content}`); 430 | }); 431 | 432 | // 3. Ask user/agent to select or proceed with new approach 433 | const selected = await promptUser('Use which pattern? (0 for new)'); 434 | 435 | if (selected > 0) { 436 | return applyPattern(relevantPatterns[selected - 1], task); 437 | } else { 438 | return exploreNewApproach(task); 439 | } 440 | } 441 | ``` 442 | 443 | ### Pattern 2: Post-Task Learning 444 | 445 | ```javascript 446 | // After completing a task, store the outcome 447 | async function completeTask(task, outcome) { 448 | // 1. Store pattern with outcome 449 | await reasoningBank.storePattern( 450 | `${task.type}_${Date.now()}`, 451 | task.solution, 452 | { 453 | namespace: task.domain, 454 | components: { 455 | reliability: outcome.success ? 0.7 : 0.3, 456 | duration_ms: outcome.duration, 457 | outcome: outcome.success ? 'success' : 'failure', 458 | error: outcome.error 459 | } 460 | } 461 | ); 462 | 463 | // 2. If similar pattern exists, update its confidence 464 | if (outcome.basedOnPattern) { 465 | const existingPattern = await reasoningBank.getPattern(outcome.basedOnPattern); 466 | 467 | if (outcome.success) { 468 | existingPattern.components.reliability += 0.1; 469 | } else { 470 | existingPattern.components.reliability -= 0.15; 471 | } 472 | 473 | await reasoningBank.updatePattern(existingPattern.id, existingPattern); 474 | } 475 | } 476 | ``` 477 | 478 | ### Pattern 3: Continuous Learning Loop 479 | 480 | ```javascript 481 | // Implement SAFLA (Self-Aware Feedback Loop Algorithm) 482 | class SAFLAAgent { 483 | async executeSAFLACycle(task) { 484 | // 1. OBSERVE: Retrieve relevant patterns 485 | const patterns = await this.reasoningBank.query(task, { 486 | namespace: this.namespace, 487 | minConfidence: 0.4 488 | }); 489 | 490 | // 2. ANALYZE: Evaluate pattern relevance and confidence 491 | const analysis = this.analyzePatterns(patterns); 492 | 493 | // 3. LEARN: Select best approach 494 | const selectedPattern = analysis.bestPattern; 495 | 496 | // 4. ADAPT: Execute with selected pattern 497 | const result = await this.execute(selectedPattern, task); 498 | 499 | // 5. FEEDBACK: Update confidence and store outcome 500 | await this.updateConfidence(selectedPattern.id, result.success); 501 | await this.storeOutcome(task, result); 502 | 503 | // Return to step 1 for next task 504 | return result; 505 | } 506 | 507 | analyzePatterns(patterns) { 508 | return { 509 | bestPattern: patterns[0], 510 | alternatives: patterns.slice(1), 511 | confidence: patterns[0]?.components.reliability || 0.5 512 | }; 513 | } 514 | 515 | async updateConfidence(patternId, success) { 516 | const pattern = await this.reasoningBank.getPattern(patternId); 517 | 518 | if (success) { 519 | pattern.components.reliability = Math.min( 520 | 1.0, 521 | pattern.components.reliability + 0.15 522 | ); 523 | } else { 524 | pattern.components.reliability = Math.max( 525 | 0.0, 526 | pattern.components.reliability - 0.1 527 | ); 528 | } 529 | 530 | await this.reasoningBank.updatePattern(patternId, pattern); 531 | } 532 | } 533 | ``` 534 | 535 | --- 536 | 537 | ## Shell Scripts 538 | 539 | ### Bulk Import 540 | 541 | ```bash 542 | #!/bin/bash 543 | # bulk-import.sh - Import patterns from CSV file 544 | 545 | # CSV format: title,content,namespace 546 | while IFS=',' read -r title content namespace; do 547 | npx claude-flow@alpha memory store "$title" "$content" \ 548 | --namespace "$namespace" --reasoningbank 549 | done < patterns.csv 550 | 551 | echo "Import complete!" 552 | ``` 553 | 554 | ### Backup & Restore 555 | 556 | ```bash 557 | #!/bin/bash 558 | # backup-memory.sh - Backup ReasoningBank database 559 | 560 | BACKUP_DIR="backups/$(date +%Y%m%d_%H%M%S)" 561 | mkdir -p "$BACKUP_DIR" 562 | 563 | # Copy database 564 | cp .swarm/memory.db "$BACKUP_DIR/" 565 | 566 | # Export as JSON (if feature available) 567 | npx claude-flow@alpha memory export --all > "$BACKUP_DIR/patterns.json" 568 | 569 | echo "Backup saved to $BACKUP_DIR" 570 | ``` 571 | 572 | ### Generate Report 573 | 574 | ```bash 575 | #!/bin/bash 576 | # report.sh - Generate usage report 577 | 578 | echo "ReasoningBank Usage Report" 579 | echo "==========================" 580 | echo "" 581 | 582 | npx claude-flow@alpha memory status --detailed --reasoningbank | \ 583 | grep -E "(Total Patterns|Most Used|Average Confidence)" 584 | 585 | echo "" 586 | echo "Namespaces:" 587 | npx claude-flow@alpha memory list --reasoningbank | \ 588 | grep -E "^├──|^└──" 589 | ``` 590 | 591 | --- 592 | 593 | ## Summary 594 | 595 | This document provided: 596 | 597 | - ✅ CLI command examples 598 | - ✅ JavaScript/TypeScript integration code 599 | - ✅ Real-world use cases 600 | - ✅ Integration patterns (pre-task, post-task, SAFLA) 601 | - ✅ Shell scripts for automation 602 | 603 | For more examples, see: 604 | - **[Basic Tutorial](./tutorial-basic.md)** - Step-by-step learning 605 | - **[Advanced Tutorial](./tutorial-advanced.md)** - Advanced patterns 606 | - **[Architecture](./architecture.md)** - Implementation details 607 | 608 | --- 609 | 610 | **Last Updated**: 2025-10-14 611 | **Version**: v2.7.0-alpha.10 612 | -------------------------------------------------------------------------------- /src/health/health-endpoints.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { TrelloHealthMonitor, SystemHealthReport, HealthStatus } from './health-monitor.js'; 3 | import { TrelloClient } from '../trello-client.js'; 4 | 5 | /** 6 | * Health endpoint result structure for MCP tools 7 | */ 8 | interface HealthEndpointResult { 9 | [x: string]: unknown; 10 | content: Array<{ type: 'text'; text: string }>; 11 | isError?: boolean; 12 | } 13 | 14 | /** 15 | * Repair operation result 16 | */ 17 | interface RepairResult { 18 | attempted: boolean; 19 | success: boolean; 20 | actions_taken: string[]; 21 | message: string; 22 | } 23 | 24 | /** 25 | * THE MAGNIFICENT HEALTH ENDPOINTS COLLECTION! 🏥 26 | * 27 | * This class provides all the cardiovascular monitoring APIs that keep 28 | * our Trello MCP organism in peak condition. It's like having a team of 29 | * world-class physicians monitoring your API 24/7! 30 | * 31 | * Available endpoints: 32 | * - /health - Quick health check 33 | * - /health/detailed - Comprehensive diagnostic report 34 | * - /health/metadata - Metadata consistency verification 35 | * - /health/performance - Performance metrics analysis 36 | * - /admin/repair - Automated repair capabilities (when available) 37 | */ 38 | export class TrelloHealthEndpoints { 39 | private healthMonitor: TrelloHealthMonitor; 40 | private trelloClient: TrelloClient; 41 | 42 | constructor(trelloClient: TrelloClient) { 43 | this.trelloClient = trelloClient; 44 | this.healthMonitor = new TrelloHealthMonitor(trelloClient); 45 | } 46 | 47 | /** 48 | * GET /health 49 | * Quick health status check - the digital pulse check! 50 | * Perfect for load balancers and monitoring systems. 51 | */ 52 | async getBasicHealth(): Promise { 53 | try { 54 | const healthReport = await this.healthMonitor.getSystemHealth(false); 55 | 56 | const quickReport = { 57 | status: healthReport.overall_status, 58 | timestamp: healthReport.timestamp, 59 | uptime_ms: healthReport.uptime_ms, 60 | checks_passed: healthReport.checks.filter(c => c.status === HealthStatus.HEALTHY).length, 61 | total_checks: healthReport.checks.length, 62 | response_time_ms: Math.round(healthReport.performance_metrics.avg_response_time_ms), 63 | success_rate: `${healthReport.performance_metrics.success_rate_percent}%`, 64 | }; 65 | 66 | return { 67 | content: [ 68 | { 69 | type: 'text', 70 | text: JSON.stringify(quickReport, null, 2), 71 | }, 72 | ], 73 | isError: healthReport.overall_status === HealthStatus.CRITICAL, 74 | }; 75 | } catch (error) { 76 | return this.createErrorResponse('Health check failed', error); 77 | } 78 | } 79 | 80 | /** 81 | * GET /health/detailed 82 | * Comprehensive health diagnostic - the full medical examination! 83 | * Includes all subsystem checks, performance metrics, and recommendations. 84 | */ 85 | async getDetailedHealth(): Promise { 86 | try { 87 | const healthReport = await this.healthMonitor.getSystemHealth(true); 88 | 89 | return { 90 | content: [ 91 | { 92 | type: 'text', 93 | text: JSON.stringify(healthReport, null, 2), 94 | }, 95 | ], 96 | isError: healthReport.overall_status === HealthStatus.CRITICAL, 97 | }; 98 | } catch (error) { 99 | return this.createErrorResponse('Detailed health check failed', error); 100 | } 101 | } 102 | 103 | /** 104 | * GET /health/metadata 105 | * Metadata consistency verification - the data integrity scanner! 106 | * Checks for consistency between boards, lists, cards, and checklists. 107 | */ 108 | async getMetadataHealth(): Promise { 109 | try { 110 | const startTime = Date.now(); 111 | const metadataReport = await this.performMetadataConsistencyCheck(); 112 | const duration = Date.now() - startTime; 113 | 114 | const result = { 115 | status: metadataReport.consistent ? HealthStatus.HEALTHY : HealthStatus.DEGRADED, 116 | timestamp: new Date().toISOString(), 117 | duration_ms: duration, 118 | metadata_consistency: metadataReport, 119 | recommendations: this.generateMetadataRecommendations(metadataReport), 120 | }; 121 | 122 | return { 123 | content: [ 124 | { 125 | type: 'text', 126 | text: JSON.stringify(result, null, 2), 127 | }, 128 | ], 129 | isError: !metadataReport.consistent, 130 | }; 131 | } catch (error) { 132 | return this.createErrorResponse('Metadata health check failed', error); 133 | } 134 | } 135 | 136 | /** 137 | * GET /health/performance 138 | * Performance metrics analysis - the cardiovascular stress test! 139 | * Deep dive into response times, throughput, and system efficiency. 140 | */ 141 | async getPerformanceHealth(): Promise { 142 | try { 143 | const healthReport = await this.healthMonitor.getSystemHealth(false); 144 | const performanceAnalysis = this.analyzePerformanceMetrics(healthReport); 145 | 146 | return { 147 | content: [ 148 | { 149 | type: 'text', 150 | text: JSON.stringify(performanceAnalysis, null, 2), 151 | }, 152 | ], 153 | isError: performanceAnalysis.status === HealthStatus.CRITICAL, 154 | }; 155 | } catch (error) { 156 | return this.createErrorResponse('Performance health check failed', error); 157 | } 158 | } 159 | 160 | /** 161 | * POST /admin/repair 162 | * Automated system repair - the digital emergency room! 163 | * Attempts to automatically fix common issues when possible. 164 | */ 165 | async performRepair(): Promise { 166 | try { 167 | const healthReport = await this.healthMonitor.getSystemHealth(true); 168 | 169 | if (!healthReport.repair_available) { 170 | return { 171 | content: [ 172 | { 173 | type: 'text', 174 | text: JSON.stringify( 175 | { 176 | repair_attempted: false, 177 | reason: 'No repairable issues detected or system in critical state', 178 | status: healthReport.overall_status, 179 | recommendations: healthReport.recommendations, 180 | }, 181 | null, 182 | 2 183 | ), 184 | }, 185 | ], 186 | }; 187 | } 188 | 189 | const repairResult = await this.attemptSystemRepair(healthReport); 190 | 191 | return { 192 | content: [ 193 | { 194 | type: 'text', 195 | text: JSON.stringify(repairResult, null, 2), 196 | }, 197 | ], 198 | isError: !repairResult.success, 199 | }; 200 | } catch (error) { 201 | return this.createErrorResponse('System repair failed', error); 202 | } 203 | } 204 | 205 | /** 206 | * Perform comprehensive metadata consistency check 207 | */ 208 | private async performMetadataConsistencyCheck() { 209 | const results = { 210 | consistent: true, 211 | issues: [] as string[], 212 | statistics: {} as Record, 213 | last_check: new Date().toISOString(), 214 | }; 215 | 216 | try { 217 | // Check if we have an active board 218 | const boardId = this.trelloClient.activeBoardId; 219 | if (!boardId) { 220 | results.consistent = false; 221 | results.issues.push('No active board configured'); 222 | return results; 223 | } 224 | 225 | // Get board information 226 | const board = await this.trelloClient.getBoardById(boardId); 227 | if (board.closed) { 228 | results.consistent = false; 229 | results.issues.push('Active board is closed/archived'); 230 | } 231 | 232 | // Get lists and check consistency 233 | const lists = await this.trelloClient.getLists(); 234 | results.statistics.total_lists = lists.length; 235 | results.statistics.open_lists = lists.filter(l => !l.closed).length; 236 | results.statistics.closed_lists = lists.filter(l => l.closed).length; 237 | 238 | // Check for empty board 239 | if (lists.length === 0) { 240 | results.issues.push('Board has no lists'); 241 | } 242 | 243 | // Get user cards for comparison 244 | const myCards = await this.trelloClient.getMyCards(); 245 | results.statistics.total_user_cards = myCards.length; 246 | results.statistics.open_user_cards = myCards.filter(c => !c.closed).length; 247 | 248 | // Check workspace consistency 249 | const workspaceId = this.trelloClient.activeWorkspaceId; 250 | if (workspaceId) { 251 | try { 252 | const workspace = await this.trelloClient.getWorkspaceById(workspaceId); 253 | results.statistics.active_workspace = workspace.displayName; 254 | } catch (error) { 255 | results.consistent = false; 256 | results.issues.push('Active workspace is inaccessible'); 257 | } 258 | } 259 | 260 | // Check checklist accessibility (non-critical) 261 | try { 262 | const acceptanceCriteria = await this.trelloClient.getAcceptanceCriteria(); 263 | results.statistics.acceptance_criteria_items = acceptanceCriteria.length; 264 | } catch (error) { 265 | // This is not critical for consistency 266 | results.statistics.checklist_note = 267 | 'Acceptance Criteria checklist not found (non-critical)'; 268 | } 269 | } catch (error) { 270 | results.consistent = false; 271 | results.issues.push( 272 | `Metadata check error: ${error instanceof Error ? error.message : 'Unknown error'}` 273 | ); 274 | } 275 | 276 | return results; 277 | } 278 | 279 | /** 280 | * Generate metadata-specific recommendations 281 | */ 282 | private generateMetadataRecommendations(metadataReport: any): string[] { 283 | const recommendations: string[] = []; 284 | 285 | if (metadataReport.issues.some((issue: string) => issue.includes('No active board'))) { 286 | recommendations.push('Use set_active_board tool to configure an active board'); 287 | } 288 | 289 | if (metadataReport.issues.some((issue: string) => issue.includes('closed/archived'))) { 290 | recommendations.push('Set a different active board that is not closed/archived'); 291 | } 292 | 293 | if (metadataReport.issues.some((issue: string) => issue.includes('no lists'))) { 294 | recommendations.push('Create lists in your board using add_list_to_board tool'); 295 | } 296 | 297 | if (metadataReport.statistics.total_user_cards === 0) { 298 | recommendations.push( 299 | 'Consider assigning yourself to some cards for better workflow tracking' 300 | ); 301 | } 302 | 303 | if (recommendations.length === 0) { 304 | recommendations.push('Metadata consistency is excellent - no action required'); 305 | } 306 | 307 | return recommendations; 308 | } 309 | 310 | /** 311 | * Analyze performance metrics in detail 312 | */ 313 | private analyzePerformanceMetrics(healthReport: SystemHealthReport) { 314 | const metrics = healthReport.performance_metrics; 315 | const performanceGrade = this.calculatePerformanceGrade(metrics); 316 | 317 | return { 318 | status: this.getPerformanceStatus(performanceGrade), 319 | timestamp: healthReport.timestamp, 320 | performance_grade: performanceGrade, 321 | metrics: { 322 | ...metrics, 323 | uptime_hours: Math.round((healthReport.uptime_ms / (1000 * 60 * 60)) * 100) / 100, 324 | health_check_duration_ms: healthReport.checks.reduce((sum, c) => sum + c.duration_ms, 0), 325 | }, 326 | analysis: { 327 | response_time_rating: this.rateResponseTime(metrics.avg_response_time_ms), 328 | success_rate_rating: this.rateSuccessRate(metrics.success_rate_percent), 329 | throughput_rating: this.rateThroughput(metrics.requests_per_minute), 330 | rate_limit_health: this.rateRateLimitUtilization(metrics.rate_limit_utilization_percent), 331 | }, 332 | recommendations: this.generatePerformanceRecommendations(metrics), 333 | }; 334 | } 335 | 336 | /** 337 | * Calculate overall performance grade 338 | */ 339 | private calculatePerformanceGrade(metrics: any): string { 340 | let score = 0; 341 | 342 | // Response time scoring (40% weight) 343 | if (metrics.avg_response_time_ms < 200) score += 40; 344 | else if (metrics.avg_response_time_ms < 500) score += 35; 345 | else if (metrics.avg_response_time_ms < 1000) score += 25; 346 | else if (metrics.avg_response_time_ms < 2000) score += 15; 347 | else score += 5; 348 | 349 | // Success rate scoring (35% weight) 350 | if (metrics.success_rate_percent >= 99) score += 35; 351 | else if (metrics.success_rate_percent >= 95) score += 30; 352 | else if (metrics.success_rate_percent >= 90) score += 20; 353 | else if (metrics.success_rate_percent >= 80) score += 10; 354 | else score += 5; 355 | 356 | // Rate limit utilization scoring (25% weight) 357 | if (metrics.rate_limit_utilization_percent < 50) score += 25; 358 | else if (metrics.rate_limit_utilization_percent < 70) score += 20; 359 | else if (metrics.rate_limit_utilization_percent < 85) score += 15; 360 | else if (metrics.rate_limit_utilization_percent < 95) score += 10; 361 | else score += 5; 362 | 363 | if (score >= 90) return 'A+'; 364 | if (score >= 80) return 'A'; 365 | if (score >= 70) return 'B'; 366 | if (score >= 60) return 'C'; 367 | if (score >= 50) return 'D'; 368 | return 'F'; 369 | } 370 | 371 | /** 372 | * Get performance status based on grade 373 | */ 374 | private getPerformanceStatus(grade: string): HealthStatus { 375 | if (['A+', 'A', 'B'].includes(grade)) return HealthStatus.HEALTHY; 376 | if (['C', 'D'].includes(grade)) return HealthStatus.DEGRADED; 377 | return HealthStatus.CRITICAL; 378 | } 379 | 380 | /** 381 | * Rate individual performance aspects 382 | */ 383 | private rateResponseTime(avgMs: number): string { 384 | if (avgMs < 200) return 'excellent'; 385 | if (avgMs < 500) return 'good'; 386 | if (avgMs < 1000) return 'fair'; 387 | if (avgMs < 2000) return 'slow'; 388 | return 'very_slow'; 389 | } 390 | 391 | private rateSuccessRate(percent: number): string { 392 | if (percent >= 99) return 'excellent'; 393 | if (percent >= 95) return 'good'; 394 | if (percent >= 90) return 'fair'; 395 | if (percent >= 80) return 'poor'; 396 | return 'critical'; 397 | } 398 | 399 | private rateThroughput(requestsPerMin: number): string { 400 | if (requestsPerMin > 30) return 'high'; 401 | if (requestsPerMin > 15) return 'moderate'; 402 | if (requestsPerMin > 5) return 'low'; 403 | return 'very_low'; 404 | } 405 | 406 | private rateRateLimitUtilization(percent: number): string { 407 | if (percent < 50) return 'optimal'; 408 | if (percent < 70) return 'moderate'; 409 | if (percent < 85) return 'high'; 410 | if (percent < 95) return 'near_limit'; 411 | return 'critical'; 412 | } 413 | 414 | /** 415 | * Generate performance-specific recommendations 416 | */ 417 | private generatePerformanceRecommendations(metrics: any): string[] { 418 | const recommendations: string[] = []; 419 | 420 | if (metrics.avg_response_time_ms > 1000) { 421 | recommendations.push( 422 | 'High response times detected - check network connectivity and Trello API status' 423 | ); 424 | } 425 | 426 | if (metrics.success_rate_percent < 95) { 427 | recommendations.push( 428 | 'Low success rate - investigate error patterns and implement retry logic' 429 | ); 430 | } 431 | 432 | if (metrics.rate_limit_utilization_percent > 80) { 433 | recommendations.push( 434 | 'High rate limit utilization - consider implementing request caching or batching' 435 | ); 436 | } 437 | 438 | if (metrics.requests_per_minute < 1) { 439 | recommendations.push('Very low API usage - ensure the MCP server is being actively used'); 440 | } 441 | 442 | if (recommendations.length === 0) { 443 | recommendations.push('Performance is excellent - maintain current usage patterns'); 444 | } 445 | 446 | return recommendations; 447 | } 448 | 449 | /** 450 | * Attempt to repair common system issues 451 | */ 452 | private async attemptSystemRepair(healthReport: SystemHealthReport): Promise { 453 | const result: RepairResult = { 454 | attempted: true, 455 | success: false, 456 | actions_taken: [], 457 | message: '', 458 | }; 459 | 460 | try { 461 | // Check for repairable issues 462 | const boardCheck = healthReport.checks.find(c => c.name === 'board_access'); 463 | 464 | if ( 465 | boardCheck?.status === HealthStatus.DEGRADED && 466 | boardCheck.message.includes('No active board configured') 467 | ) { 468 | // Attempt to set first available board as active 469 | const boards = await this.trelloClient.listBoards(); 470 | const openBoards = boards.filter(b => !b.closed); 471 | 472 | if (openBoards.length > 0) { 473 | await this.trelloClient.setActiveBoard(openBoards[0].id); 474 | result.actions_taken.push(`Set active board to "${openBoards[0].name}"`); 475 | } 476 | } 477 | 478 | // Add more repair logic here as needed 479 | 480 | result.success = result.actions_taken.length > 0; 481 | result.message = result.success 482 | ? 'System repair completed successfully' 483 | : 'No repairable issues found'; 484 | } catch (error) { 485 | result.success = false; 486 | result.message = `Repair failed: ${error instanceof Error ? error.message : 'Unknown error'}`; 487 | } 488 | 489 | return result; 490 | } 491 | 492 | /** 493 | * Create standardized error response 494 | */ 495 | private createErrorResponse(message: string, error: unknown): HealthEndpointResult { 496 | const errorMessage = error instanceof Error ? error.message : 'Unknown error'; 497 | 498 | return { 499 | content: [ 500 | { 501 | type: 'text', 502 | text: JSON.stringify( 503 | { 504 | error: message, 505 | details: errorMessage, 506 | timestamp: new Date().toISOString(), 507 | status: HealthStatus.CRITICAL, 508 | }, 509 | null, 510 | 2 511 | ), 512 | }, 513 | ], 514 | isError: true, 515 | }; 516 | } 517 | } 518 | 519 | /** 520 | * Zod schemas for health endpoint validation 521 | */ 522 | export const HealthEndpointSchemas = { 523 | basicHealth: { 524 | title: 'Get Basic Health', 525 | description: 'Get quick system health status for monitoring and load balancing', 526 | inputSchema: {}, 527 | }, 528 | 529 | detailedHealth: { 530 | title: 'Get Detailed Health', 531 | description: 'Get comprehensive system health diagnostic with all subsystem checks', 532 | inputSchema: {}, 533 | }, 534 | 535 | metadataHealth: { 536 | title: 'Get Metadata Health', 537 | description: 'Verify metadata consistency between boards, lists, cards, and checklists', 538 | inputSchema: {}, 539 | }, 540 | 541 | performanceHealth: { 542 | title: 'Get Performance Health', 543 | description: 'Get detailed performance metrics and analysis', 544 | inputSchema: {}, 545 | }, 546 | 547 | repair: { 548 | title: 'Perform System Repair', 549 | description: 'Attempt to automatically repair common system issues', 550 | inputSchema: {}, 551 | }, 552 | }; 553 | -------------------------------------------------------------------------------- /examples/javascript-examples.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript Examples for MCP Server Trello 3 | * 4 | * These examples demonstrate how to use the MCP Server Trello 5 | * with JavaScript/Node.js applications. 6 | */ 7 | 8 | // Example 1: Basic Setup and Configuration 9 | class TrelloMCPClient { 10 | constructor(serverName = 'trello') { 11 | this.serverName = serverName; 12 | } 13 | 14 | async callTool(toolName, /* args */) { 15 | // This would be replaced with your actual MCP client implementation 16 | // return await use_mcp_tool({ 17 | // server_name: this.serverName, 18 | // tool_name: toolName, 19 | // arguments: args, 20 | // }); 21 | return Promise.resolve({}); 22 | } 23 | } 24 | 25 | // Example 2: Sprint Management System 26 | class SprintManager { 27 | constructor(trelloClient) { 28 | this.trello = trelloClient; 29 | this.sprintNumber = null; 30 | this.sprintListId = null; 31 | } 32 | 33 | async initializeSprint(sprintNumber, startDate, endDate) { 34 | this.sprintNumber = sprintNumber; 35 | 36 | // Create sprint list 37 | const list = await this.trello.callTool('add_list_to_board', { 38 | name: `Sprint ${sprintNumber} (${startDate} - ${endDate})`, 39 | }); 40 | 41 | this.sprintListId = list.id; 42 | 43 | // Create sprint planning card 44 | const planningCard = await this.trello.callTool('add_card_to_list', { 45 | listId: this.sprintListId, 46 | name: `Sprint ${sprintNumber} Planning`, 47 | description: `# Sprint ${sprintNumber}\n\n**Duration**: ${startDate} to ${endDate}\n\n## Sprint Goals\n- [ ] Goal 1\n- [ ] Goal 2\n- [ ] Goal 3\n\n## Team Capacity\n- Dev: X points\n- QA: Y points`, 48 | dueDate: new Date(endDate).toISOString(), 49 | }); 50 | 51 | return { list, planningCard }; 52 | } 53 | 54 | async addTaskToSprint(task) { 55 | const card = await this.trello.callTool('add_card_to_list', { 56 | listId: this.sprintListId, 57 | name: task.name, 58 | description: task.description, 59 | labels: task.labels || ['sprint-task'], 60 | dueDate: task.dueDate, 61 | }); 62 | 63 | // Add acceptance criteria 64 | if (task.acceptanceCriteria) { 65 | for (const criteria of task.acceptanceCriteria) { 66 | await this.trello.callTool('add_checklist_item', { 67 | text: criteria, 68 | checkListName: 'Acceptance Criteria', 69 | }); 70 | } 71 | } 72 | 73 | return card; 74 | } 75 | 76 | async moveToInProgress(cardId) { 77 | const lists = await this.trello.callTool('get_lists', {}); 78 | const inProgressList = lists.find(l => l.name === 'In Progress'); 79 | 80 | if (inProgressList) { 81 | await this.trello.callTool('move_card', { 82 | cardId: cardId, 83 | listId: inProgressList.id, 84 | }); 85 | 86 | await this.trello.callTool('add_comment', { 87 | cardId: cardId, 88 | text: `Work started at ${new Date().toISOString()}`, 89 | }); 90 | } 91 | } 92 | 93 | async getSprintVelocity() { 94 | const cards = await this.trello.callTool('get_cards_by_list_id', { 95 | listId: this.sprintListId, 96 | }); 97 | 98 | let completed = 0; 99 | let total = cards.length; 100 | 101 | for (const card of cards) { 102 | const fullCard = await this.trello.callTool('get_card', { 103 | cardId: card.id, 104 | }); 105 | 106 | if (fullCard.dueComplete) { 107 | completed++; 108 | } 109 | } 110 | 111 | return { 112 | total, 113 | completed, 114 | velocity: (completed / total) * 100, 115 | }; 116 | } 117 | } 118 | 119 | // Example 3: Bug Tracking System 120 | class BugTracker { 121 | constructor(trelloClient) { 122 | this.trello = trelloClient; 123 | this.bugListId = null; 124 | } 125 | 126 | async initialize() { 127 | const lists = await this.trello.callTool('get_lists', {}); 128 | let bugList = lists.find(l => l.name === 'Bugs'); 129 | 130 | if (!bugList) { 131 | bugList = await this.trello.callTool('add_list_to_board', { 132 | name: 'Bugs', 133 | }); 134 | } 135 | 136 | this.bugListId = bugList.id; 137 | } 138 | 139 | async reportBug({ 140 | title, 141 | description, 142 | severity = 'medium', 143 | environment = 'production', 144 | stepsToReproduce = [], 145 | expectedBehavior, 146 | actualBehavior, 147 | screenshotUrl, 148 | userId, 149 | }) { 150 | // Determine priority based on severity 151 | const dueDateOffset = { 152 | critical: 1, // 1 day 153 | high: 3, // 3 days 154 | medium: 7, // 1 week 155 | low: 14, // 2 weeks 156 | }; 157 | 158 | const dueDate = new Date(); 159 | dueDate.setDate(dueDate.getDate() + (dueDateOffset[severity] || 7)); 160 | 161 | // Create bug card 162 | const bugCard = await this.trello.callTool('add_card_to_list', { 163 | listId: this.bugListId, 164 | name: `🐛 [${severity.toUpperCase()}] ${title}`, 165 | description: this.formatBugDescription({ 166 | description, 167 | environment, 168 | stepsToReproduce, 169 | expectedBehavior, 170 | actualBehavior, 171 | reportedBy: userId, 172 | reportedAt: new Date().toISOString(), 173 | }), 174 | labels: ['bug', severity, environment], 175 | dueDate: dueDate.toISOString(), 176 | }); 177 | 178 | // Add QA checklist 179 | const qaChecklist = [ 180 | 'Bug is reproducible', 181 | 'Root cause identified', 182 | 'Fix implemented', 183 | 'Unit tests added', 184 | 'Integration tests passed', 185 | 'Regression testing completed', 186 | 'Fix verified in staging', 187 | 'Documentation updated', 188 | ]; 189 | 190 | for (const item of qaChecklist) { 191 | await this.trello.callTool('add_checklist_item', { 192 | text: item, 193 | checkListName: 'QA Checklist', 194 | }); 195 | } 196 | 197 | // Attach screenshot if provided 198 | if (screenshotUrl) { 199 | await this.trello.callTool('attach_image_to_card', { 200 | cardId: bugCard.id, 201 | imageUrl: screenshotUrl, 202 | name: 'Bug Screenshot', 203 | }); 204 | } 205 | 206 | return bugCard; 207 | } 208 | 209 | formatBugDescription({ 210 | description, 211 | environment, 212 | stepsToReproduce, 213 | expectedBehavior, 214 | actualBehavior, 215 | reportedBy, 216 | reportedAt, 217 | }) { 218 | return `## Bug Report 219 | 220 | **Reported by**: ${reportedBy} 221 | **Date**: ${reportedAt} 222 | **Environment**: ${environment} 223 | 224 | ### Description 225 | ${description} 226 | 227 | ### Steps to Reproduce 228 | ${stepsToReproduce.map((step, i) => `${i + 1}. ${step}`).join('\n')} 229 | 230 | ### Expected Behavior 231 | ${expectedBehavior} 232 | 233 | ### Actual Behavior 234 | ${actualBehavior} 235 | 236 | ### Technical Details 237 | - Browser: [To be filled] 238 | - OS: [To be filled] 239 | - Version: [To be filled] 240 | 241 | ### Impact 242 | - Users affected: [To be estimated] 243 | - Business impact: [To be assessed]`; 244 | } 245 | 246 | async updateBugStatus(cardId, status, notes) { 247 | const statusToList = { 248 | triaged: 'Triaged', 249 | 'in-progress': 'In Progress', 250 | fixed: 'Fixed', 251 | verified: 'Verified', 252 | closed: 'Done', 253 | }; 254 | 255 | // Get the appropriate list 256 | const lists = await this.trello.callTool('get_lists', {}); 257 | const targetList = lists.find(l => l.name === statusToList[status]); 258 | 259 | if (targetList) { 260 | // Move card to new list 261 | await this.trello.callTool('move_card', { 262 | cardId: cardId, 263 | listId: targetList.id, 264 | }); 265 | 266 | // Add status update comment 267 | await this.trello.callTool('add_comment', { 268 | cardId: cardId, 269 | text: `**Status Update**: ${status}\n\n${notes}\n\nUpdated at: ${new Date().toISOString()}`, 270 | }); 271 | 272 | // Update labels 273 | const card = await this.trello.callTool('get_card', { 274 | cardId: cardId, 275 | }); 276 | 277 | const newLabels = card.labels.filter( 278 | l => !['triaged', 'in-progress', 'fixed', 'verified'].includes(l) 279 | ); 280 | newLabels.push(status); 281 | 282 | await this.trello.callTool('update_card_details', { 283 | cardId: cardId, 284 | labels: newLabels, 285 | }); 286 | } 287 | } 288 | 289 | async getBugMetrics() { 290 | const lists = await this.trello.callTool('get_lists', {}); 291 | const metrics = { 292 | total: 0, 293 | bySeverity: { critical: 0, high: 0, medium: 0, low: 0 }, 294 | byStatus: {}, 295 | overdue: [], 296 | }; 297 | 298 | for (const list of lists) { 299 | const cards = await this.trello.callTool('get_cards_by_list_id', { 300 | listId: list.id, 301 | }); 302 | 303 | const bugCards = cards.filter( 304 | card => card.labels && card.labels.some(label => label === 'bug' || label.name === 'bug') 305 | ); 306 | 307 | metrics.byStatus[list.name] = bugCards.length; 308 | metrics.total += bugCards.length; 309 | 310 | for (const card of bugCards) { 311 | // Count by severity 312 | for (const severity of ['critical', 'high', 'medium', 'low']) { 313 | if (card.labels.some(label => label === severity || label.name === severity)) { 314 | metrics.bySeverity[severity]++; 315 | } 316 | } 317 | 318 | // Check for overdue 319 | if (card.due && new Date(card.due) < new Date() && !card.dueComplete) { 320 | metrics.overdue.push({ 321 | name: card.name, 322 | due: card.due, 323 | list: list.name, 324 | }); 325 | } 326 | } 327 | } 328 | 329 | return metrics; 330 | } 331 | } 332 | 333 | // Example 4: Release Management 334 | class ReleaseManager { 335 | constructor(trelloClient) { 336 | this.trello = trelloClient; 337 | } 338 | 339 | async createRelease(version, targetDate, features = []) { 340 | // Create release card 341 | const releaseCard = await this.trello.callTool('add_card_to_list', { 342 | listId: 'releases-list-id', // You'd need to get this dynamically 343 | name: `Release v${version}`, 344 | description: this.formatReleaseNotes(version, targetDate, features), 345 | dueDate: new Date(targetDate).toISOString(), 346 | labels: ['release', 'milestone'], 347 | }); 348 | 349 | // Add release checklist 350 | const checklist = [ 351 | 'Code freeze announcement sent', 352 | 'Feature freeze confirmed', 353 | 'All PRs merged', 354 | 'Build pipeline successful', 355 | 'Automated tests passing', 356 | 'Security scan completed', 357 | 'Performance benchmarks met', 358 | 'Documentation updated', 359 | 'Release notes finalized', 360 | 'Staging deployment successful', 361 | 'UAT sign-off received', 362 | 'Production deployment approved', 363 | 'Deployment executed', 364 | 'Smoke tests passed', 365 | 'Monitoring alerts configured', 366 | 'Rollback plan tested', 367 | 'Stakeholders notified', 368 | ]; 369 | 370 | for (const item of checklist) { 371 | await this.trello.callTool('add_checklist_item', { 372 | text: item, 373 | checkListName: 'Release Checklist', 374 | }); 375 | } 376 | 377 | // Link feature cards 378 | for (const featureCardId of features) { 379 | await this.trello.callTool('add_comment', { 380 | cardId: featureCardId, 381 | text: `✅ Included in Release v${version}`, 382 | }); 383 | 384 | // Move to release column 385 | await this.trello.callTool('move_card', { 386 | cardId: featureCardId, 387 | listId: 'ready-for-release-list-id', 388 | }); 389 | } 390 | 391 | return releaseCard; 392 | } 393 | 394 | formatReleaseNotes(version, targetDate, features) { 395 | return `# Release v${version} 396 | 397 | **Target Date**: ${targetDate} 398 | **Status**: Planning 399 | 400 | ## Release Summary 401 | This release includes ${features.length} features and improvements. 402 | 403 | ## What's New 404 | - Feature 1 405 | - Feature 2 406 | - Improvements 407 | 408 | ## Bug Fixes 409 | - Fixed issue with... 410 | - Resolved problem in... 411 | 412 | ## Breaking Changes 413 | None 414 | 415 | ## Migration Guide 416 | No migration required 417 | 418 | ## Rollback Plan 419 | In case of critical issues: 420 | 1. Execute rollback script 421 | 2. Restore previous version 422 | 3. Notify stakeholders 423 | 424 | ## Deployment Schedule 425 | - **Code Freeze**: [Date] 426 | - **Staging Deployment**: [Date] 427 | - **Production Deployment**: ${targetDate} 428 | 429 | ## Stakeholders 430 | - Product: @product-team 431 | - Engineering: @eng-team 432 | - QA: @qa-team 433 | - DevOps: @devops-team`; 434 | } 435 | 436 | async updateReleaseProgress(releaseCardId) { 437 | // Get checklist status 438 | const card = await this.trello.callTool('get_card', { 439 | cardId: releaseCardId, 440 | }); 441 | 442 | // Calculate progress 443 | let totalItems = 0; 444 | let completedItems = 0; 445 | 446 | if (card.checklists) { 447 | for (const checklist of card.checklists) { 448 | totalItems += checklist.checkItems.length; 449 | completedItems += checklist.checkItems.filter(item => item.state === 'complete').length; 450 | } 451 | } 452 | 453 | const progress = totalItems > 0 ? (completedItems / totalItems) * 100 : 0; 454 | 455 | // Update card with progress 456 | await this.trello.callTool('add_comment', { 457 | cardId: releaseCardId, 458 | text: `📊 **Release Progress Update**\n\nProgress: ${progress.toFixed(1)}%\nCompleted: ${completedItems}/${totalItems} items\nUpdated: ${new Date().toISOString()}`, 459 | }); 460 | 461 | return { progress, completedItems, totalItems }; 462 | } 463 | } 464 | 465 | // Example 5: Daily Standup Assistant 466 | class StandupAssistant { 467 | constructor(trelloClient) { 468 | this.trello = trelloClient; 469 | } 470 | 471 | async generateStandupReport(userName) { 472 | // Get user's cards 473 | const myCards = await this.trello.callTool('get_my_cards', {}); 474 | 475 | const report = { 476 | date: new Date().toLocaleDateString(), 477 | user: userName, 478 | yesterday: [], 479 | today: [], 480 | blockers: [], 481 | metrics: { 482 | cardsInProgress: 0, 483 | cardsCompleted: 0, 484 | overdueCards: 0, 485 | }, 486 | }; 487 | 488 | // Get recent activity 489 | const recentActivity = await this.trello.callTool('get_recent_activity', { 490 | limit: 50, 491 | }); 492 | 493 | // Process cards 494 | for (const card of myCards) { 495 | const fullCard = await this.trello.callTool('get_card', { 496 | cardId: card.id, 497 | }); 498 | 499 | // Categorize by list 500 | if (fullCard.list.name === 'Done' || fullCard.list.name === 'Completed') { 501 | // Check if completed yesterday 502 | const yesterday = new Date(); 503 | yesterday.setDate(yesterday.getDate() - 1); 504 | 505 | const completedYesterday = recentActivity.some( 506 | activity => 507 | activity.data.card?.id === card.id && 508 | activity.type === 'updateCard' && 509 | new Date(activity.date) > yesterday 510 | ); 511 | 512 | if (completedYesterday) { 513 | report.yesterday.push({ 514 | name: card.name, 515 | url: card.url, 516 | }); 517 | report.metrics.cardsCompleted++; 518 | } 519 | } else if (fullCard.list.name === 'In Progress' || fullCard.list.name === 'Doing') { 520 | report.today.push({ 521 | name: card.name, 522 | url: card.url, 523 | progress: this.getCardProgress(fullCard), 524 | }); 525 | report.metrics.cardsInProgress++; 526 | } else if (fullCard.list.name === 'Blocked') { 527 | report.blockers.push({ 528 | name: card.name, 529 | url: card.url, 530 | reason: this.getBlockerReason(fullCard), 531 | }); 532 | } 533 | 534 | // Check for overdue 535 | if (card.due && new Date(card.due) < new Date() && !card.dueComplete) { 536 | report.metrics.overdueCards++; 537 | } 538 | } 539 | 540 | return this.formatStandupReport(report); 541 | } 542 | 543 | getCardProgress(card) { 544 | if (!card.checklists || card.checklists.length === 0) { 545 | return 'No checklist'; 546 | } 547 | 548 | let total = 0; 549 | let completed = 0; 550 | 551 | for (const checklist of card.checklists) { 552 | total += checklist.checkItems.length; 553 | completed += checklist.checkItems.filter(item => item.state === 'complete').length; 554 | } 555 | 556 | return `${completed}/${total} items complete`; 557 | } 558 | 559 | getBlockerReason(card) { 560 | // Look for blocker reason in comments 561 | if (card.actions) { 562 | const recentComment = card.actions 563 | .filter(action => action.type === 'commentCard') 564 | .sort((a, b) => new Date(b.date) - new Date(a.date))[0]; 565 | 566 | if (recentComment && recentComment.data.text.toLowerCase().includes('blocked')) { 567 | return recentComment.data.text; 568 | } 569 | } 570 | 571 | return 'No reason specified'; 572 | } 573 | 574 | formatStandupReport(report) { 575 | return `# Daily Standup - ${report.user} 576 | **Date**: ${report.date} 577 | 578 | ## 📅 Yesterday 579 | ${ 580 | report.yesterday.length > 0 581 | ? report.yesterday.map(task => `- ✅ ${task.name}`).join('\n') 582 | : '- No completed tasks' 583 | } 584 | 585 | ## 📋 Today 586 | ${ 587 | report.today.length > 0 588 | ? report.today.map(task => `- 🔄 ${task.name} (${task.progress})`).join('\n') 589 | : '- No tasks in progress' 590 | } 591 | 592 | ## 🚧 Blockers 593 | ${ 594 | report.blockers.length > 0 595 | ? report.blockers 596 | .map(blocker => `- ⛔ ${blocker.name}\n - Reason: ${blocker.reason}`) 597 | .join('\n') 598 | : '- No blockers' 599 | } 600 | 601 | ## 📊 Metrics 602 | - Cards in Progress: ${report.metrics.cardsInProgress} 603 | - Cards Completed Yesterday: ${report.metrics.cardsCompleted} 604 | - Overdue Cards: ${report.metrics.overdueCards}`; 605 | } 606 | 607 | async postStandupToCard(report, standupCardId) { 608 | await this.trello.callTool('add_comment', { 609 | cardId: standupCardId, 610 | text: report, 611 | }); 612 | } 613 | } 614 | 615 | // Example Usage 616 | /* async function main() { 617 | const trello = new TrelloMCPClient(); 618 | 619 | // Initialize sprint management 620 | const sprintManager = new SprintManager(trello); 621 | await sprintManager.initializeSprint(23, '2025-01-22', '2025-02-05'); 622 | 623 | // Add task to sprint 624 | await sprintManager.addTaskToSprint({ 625 | name: 'Implement user authentication', 626 | description: 'Add OAuth 2.0 support', 627 | acceptanceCriteria: [ 628 | 'Users can login with Google', 629 | 'Users can login with GitHub', 630 | 'Session management works correctly', 631 | ], 632 | dueDate: '2025-01-30T17:00:00Z', 633 | }); 634 | 635 | // Initialize bug tracker 636 | const bugTracker = new BugTracker(trello); 637 | await bugTracker.initialize(); 638 | 639 | // Report a bug 640 | /* const bug = */ await bugTracker.reportBug({ 641 | title: 'Login button not responding', 642 | description: 'The login button on the homepage does not respond to clicks', 643 | severity: 'high', 644 | environment: 'production', 645 | stepsToReproduce: ['Navigate to homepage', 'Click on login button', 'Nothing happens'], 646 | expectedBehavior: 'Login modal should appear', 647 | actualBehavior: 'No response to click', 648 | screenshotUrl: 'https://example.com/screenshot.png', 649 | userId: 'john.doe@example.com', 650 | }); 651 | 652 | // Generate standup report 653 | const standupAssistant = new StandupAssistant(trello); 654 | /* const standupReport = */ await standupAssistant.generateStandupReport('John Doe'); 655 | // console.log(standupReport); 656 | } */ 657 | 658 | // Export for use in other modules 659 | module.exports = { 660 | TrelloMCPClient, 661 | SprintManager, 662 | BugTracker, 663 | ReleaseManager, 664 | StandupAssistant, 665 | }; 666 | -------------------------------------------------------------------------------- /src/health/health-monitor.ts: -------------------------------------------------------------------------------- 1 | import { TrelloClient } from '../trello-client.js'; 2 | import { AxiosError } from 'axios'; 3 | import { performance } from 'perf_hooks'; 4 | import { RateLimiter } from '../types.js'; 5 | 6 | /** 7 | * Health status levels for our magnificent Trello organism 8 | */ 9 | export enum HealthStatus { 10 | HEALTHY = 'healthy', 11 | DEGRADED = 'degraded', 12 | CRITICAL = 'critical', 13 | UNKNOWN = 'unknown', 14 | } 15 | 16 | /** 17 | * Individual health check result 18 | */ 19 | export interface HealthCheck { 20 | name: string; 21 | status: HealthStatus; 22 | message: string; 23 | duration_ms: number; 24 | timestamp: string; 25 | metadata?: Record; 26 | } 27 | 28 | /** 29 | * Complete system health report 30 | */ 31 | export interface SystemHealthReport { 32 | overall_status: HealthStatus; 33 | timestamp: string; 34 | checks: HealthCheck[]; 35 | recommendations: string[]; 36 | repair_available: boolean; 37 | uptime_ms: number; 38 | performance_metrics: { 39 | avg_response_time_ms: number; 40 | success_rate_percent: number; 41 | rate_limit_utilization_percent: number; 42 | requests_per_minute: number; 43 | }; 44 | } 45 | 46 | /** 47 | * Performance metrics tracking 48 | */ 49 | interface PerformanceTracker { 50 | requests: Array<{ timestamp: number; duration: number; success: boolean }>; 51 | startTime: number; 52 | } 53 | 54 | /** 55 | * The magnificent HEALTH MONITORING system for our Trello MCP organism! 56 | * 57 | * This class performs comprehensive cardiovascular diagnostics to ensure 58 | * our digital creature remains healthy and happy. It's like having a 59 | * personal physician for your API! 🩺 60 | * 61 | * Features include: 62 | * - Real-time health status monitoring 63 | * - Performance metrics tracking 64 | * - Rate limit utilization analysis 65 | * - Automatic repair recommendations 66 | * - Detailed diagnostic reporting 67 | */ 68 | export class TrelloHealthMonitor { 69 | private performanceTracker: PerformanceTracker; 70 | private lastHealthCheck?: SystemHealthReport; 71 | private readonly trelloClient: TrelloClient; 72 | private rateLimiter: any; // Will get injected from TrelloClient 73 | 74 | constructor(trelloClient: TrelloClient) { 75 | this.trelloClient = trelloClient; 76 | this.performanceTracker = { 77 | requests: [], 78 | startTime: Date.now(), 79 | }; 80 | 81 | // Start monitoring performance in the background 82 | this.startPerformanceMonitoring(); 83 | } 84 | 85 | /** 86 | * Get comprehensive system health status 87 | * This is the main cardiovascular examination! 🫀 88 | */ 89 | async getSystemHealth(detailed: boolean = false): Promise { 90 | const startTime = performance.now(); 91 | const checks: HealthCheck[] = []; 92 | 93 | // Run all health checks in parallel for maximum efficiency 94 | const checkPromises = [ 95 | this.checkTrelloApiConnectivity(), 96 | this.checkBoardAccess(), 97 | this.checkRateLimitHealth(), 98 | this.checkPerformanceMetrics(), 99 | ]; 100 | 101 | if (detailed) { 102 | checkPromises.push( 103 | this.checkListOperations(), 104 | this.checkCardOperations(), 105 | this.checkChecklistOperations(), 106 | this.checkWorkspaceAccess() 107 | ); 108 | } 109 | 110 | try { 111 | const checkResults = await Promise.all(checkPromises); 112 | checks.push(...checkResults); 113 | } catch (error) { 114 | // If parallel execution fails, run checks sequentially 115 | checks.push(await this.createErrorCheck('parallel_execution', error)); 116 | } 117 | 118 | // Calculate overall status 119 | const overallStatus = this.calculateOverallStatus(checks); 120 | 121 | // Generate recommendations 122 | const recommendations = this.generateRecommendations(checks, overallStatus); 123 | 124 | // Create health report 125 | const report: SystemHealthReport = { 126 | overall_status: overallStatus, 127 | timestamp: new Date().toISOString(), 128 | checks, 129 | recommendations, 130 | repair_available: this.isRepairAvailable(checks), 131 | uptime_ms: Date.now() - this.performanceTracker.startTime, 132 | performance_metrics: this.calculatePerformanceMetrics(), 133 | }; 134 | 135 | this.lastHealthCheck = report; 136 | return report; 137 | } 138 | 139 | /** 140 | * Check basic Trello API connectivity 141 | */ 142 | private async checkTrelloApiConnectivity(): Promise { 143 | const startTime = performance.now(); 144 | const checkName = 'trello_api_connectivity'; 145 | 146 | try { 147 | // Simple "me" endpoint check - lowest impact way to verify connectivity 148 | await this.trelloClient.listBoards(); 149 | 150 | const duration = performance.now() - startTime; 151 | this.recordPerformanceMetric(duration, true); 152 | 153 | return { 154 | name: checkName, 155 | status: HealthStatus.HEALTHY, 156 | message: 'Trello API connectivity is excellent', 157 | duration_ms: Math.round(duration), 158 | timestamp: new Date().toISOString(), 159 | metadata: { 160 | endpoint: '/members/me/boards', 161 | response_time_category: this.categorizeResponseTime(duration), 162 | }, 163 | }; 164 | } catch (error) { 165 | const duration = performance.now() - startTime; 166 | this.recordPerformanceMetric(duration, false); 167 | 168 | return this.createErrorCheck(checkName, error, duration); 169 | } 170 | } 171 | 172 | /** 173 | * Check if we can access the active board 174 | */ 175 | private async checkBoardAccess(): Promise { 176 | const startTime = performance.now(); 177 | const checkName = 'board_access'; 178 | 179 | try { 180 | const boardId = this.trelloClient.activeBoardId; 181 | if (!boardId) { 182 | return { 183 | name: checkName, 184 | status: HealthStatus.DEGRADED, 185 | message: 'No active board configured', 186 | duration_ms: Math.round(performance.now() - startTime), 187 | timestamp: new Date().toISOString(), 188 | metadata: { 189 | suggestion: 'Set an active board using set_active_board tool', 190 | }, 191 | }; 192 | } 193 | 194 | const board = await this.trelloClient.getBoardById(boardId); 195 | const duration = performance.now() - startTime; 196 | this.recordPerformanceMetric(duration, true); 197 | 198 | return { 199 | name: checkName, 200 | status: board.closed ? HealthStatus.CRITICAL : HealthStatus.HEALTHY, 201 | message: board.closed 202 | ? 'Active board is closed/archived' 203 | : `Board "${board.name}" is accessible`, 204 | duration_ms: Math.round(duration), 205 | timestamp: new Date().toISOString(), 206 | metadata: { 207 | board_id: board.id, 208 | board_name: board.name, 209 | board_closed: board.closed, 210 | board_url: board.url, 211 | }, 212 | }; 213 | } catch (error) { 214 | const duration = performance.now() - startTime; 215 | this.recordPerformanceMetric(duration, false); 216 | 217 | return this.createErrorCheck(checkName, error, duration); 218 | } 219 | } 220 | 221 | /** 222 | * Check rate limiter health and utilization 223 | */ 224 | private async checkRateLimitHealth(): Promise { 225 | const startTime = performance.now(); 226 | const checkName = 'rate_limit_health'; 227 | 228 | try { 229 | // Get rate limiter from client (this is a bit hacky but necessary) 230 | // In a real implementation, we'd expose this properly from TrelloClient 231 | const rateLimiterInfo = { 232 | can_make_request: true, // We'll approximate this 233 | utilization_percent: this.calculateRateLimitUtilization(), 234 | }; 235 | 236 | const duration = performance.now() - startTime; 237 | let status = HealthStatus.HEALTHY; 238 | let message = 'Rate limiting is functioning optimally'; 239 | 240 | if (rateLimiterInfo.utilization_percent > 80) { 241 | status = HealthStatus.DEGRADED; 242 | message = 'High rate limit utilization detected'; 243 | } else if (rateLimiterInfo.utilization_percent > 95) { 244 | status = HealthStatus.CRITICAL; 245 | message = 'Rate limit near exhaustion'; 246 | } 247 | 248 | return { 249 | name: checkName, 250 | status, 251 | message, 252 | duration_ms: Math.round(duration), 253 | timestamp: new Date().toISOString(), 254 | metadata: { 255 | utilization_percent: rateLimiterInfo.utilization_percent, 256 | can_make_request: rateLimiterInfo.can_make_request, 257 | trello_limits: { 258 | api_key_limit: '300 requests / 10 seconds', 259 | token_limit: '100 requests / 10 seconds', 260 | }, 261 | }, 262 | }; 263 | } catch (error) { 264 | const duration = performance.now() - startTime; 265 | return this.createErrorCheck(checkName, error, duration); 266 | } 267 | } 268 | 269 | /** 270 | * Check performance metrics health 271 | */ 272 | private async checkPerformanceMetrics(): Promise { 273 | const startTime = performance.now(); 274 | const checkName = 'performance_metrics'; 275 | 276 | try { 277 | const metrics = this.calculatePerformanceMetrics(); 278 | const duration = performance.now() - startTime; 279 | 280 | let status = HealthStatus.HEALTHY; 281 | let message = 'Performance metrics are excellent'; 282 | 283 | if (metrics.avg_response_time_ms > 2000) { 284 | status = HealthStatus.DEGRADED; 285 | message = 'Slower than optimal response times detected'; 286 | } else if (metrics.success_rate_percent < 95) { 287 | status = HealthStatus.CRITICAL; 288 | message = 'Low success rate detected'; 289 | } 290 | 291 | return { 292 | name: checkName, 293 | status, 294 | message, 295 | duration_ms: Math.round(duration), 296 | timestamp: new Date().toISOString(), 297 | metadata: { 298 | ...metrics, 299 | total_requests: this.performanceTracker.requests.length, 300 | }, 301 | }; 302 | } catch (error) { 303 | const duration = performance.now() - startTime; 304 | return this.createErrorCheck(checkName, error, duration); 305 | } 306 | } 307 | 308 | /** 309 | * Check list operations (detailed check) 310 | */ 311 | private async checkListOperations(): Promise { 312 | const startTime = performance.now(); 313 | const checkName = 'list_operations'; 314 | 315 | try { 316 | const lists = await this.trelloClient.getLists(); 317 | const duration = performance.now() - startTime; 318 | this.recordPerformanceMetric(duration, true); 319 | 320 | return { 321 | name: checkName, 322 | status: HealthStatus.HEALTHY, 323 | message: `Successfully retrieved ${lists.length} lists`, 324 | duration_ms: Math.round(duration), 325 | timestamp: new Date().toISOString(), 326 | metadata: { 327 | total_lists: lists.length, 328 | open_lists: lists.filter(l => !l.closed).length, 329 | closed_lists: lists.filter(l => l.closed).length, 330 | }, 331 | }; 332 | } catch (error) { 333 | const duration = performance.now() - startTime; 334 | this.recordPerformanceMetric(duration, false); 335 | 336 | return this.createErrorCheck(checkName, error, duration); 337 | } 338 | } 339 | 340 | /** 341 | * Check card operations (detailed check) 342 | */ 343 | private async checkCardOperations(): Promise { 344 | const startTime = performance.now(); 345 | const checkName = 'card_operations'; 346 | 347 | try { 348 | const myCards = await this.trelloClient.getMyCards(); 349 | const duration = performance.now() - startTime; 350 | this.recordPerformanceMetric(duration, true); 351 | 352 | return { 353 | name: checkName, 354 | status: HealthStatus.HEALTHY, 355 | message: `Successfully retrieved ${myCards.length} user cards`, 356 | duration_ms: Math.round(duration), 357 | timestamp: new Date().toISOString(), 358 | metadata: { 359 | total_cards: myCards.length, 360 | open_cards: myCards.filter(c => !c.closed).length, 361 | closed_cards: myCards.filter(c => c.closed).length, 362 | }, 363 | }; 364 | } catch (error) { 365 | const duration = performance.now() - startTime; 366 | this.recordPerformanceMetric(duration, false); 367 | 368 | return this.createErrorCheck(checkName, error, duration); 369 | } 370 | } 371 | 372 | /** 373 | * Check checklist operations (detailed check) 374 | */ 375 | private async checkChecklistOperations(): Promise { 376 | const startTime = performance.now(); 377 | const checkName = 'checklist_operations'; 378 | 379 | try { 380 | // Try to get acceptance criteria as a test 381 | const criteria = await this.trelloClient.getAcceptanceCriteria(); 382 | const duration = performance.now() - startTime; 383 | this.recordPerformanceMetric(duration, true); 384 | 385 | return { 386 | name: checkName, 387 | status: HealthStatus.HEALTHY, 388 | message: `Checklist operations functioning (${criteria.length} acceptance criteria found)`, 389 | duration_ms: Math.round(duration), 390 | timestamp: new Date().toISOString(), 391 | metadata: { 392 | acceptance_criteria_count: criteria.length, 393 | completed_items: criteria.filter(item => item.complete).length, 394 | }, 395 | }; 396 | } catch (error) { 397 | const duration = performance.now() - startTime; 398 | this.recordPerformanceMetric(duration, false); 399 | 400 | // Checklist failure might not be critical if it's just missing checklists 401 | const isConfigError = 402 | error instanceof Error && 403 | (error.message.includes('not found') || error.message.includes('No board ID')); 404 | 405 | return this.createErrorCheck( 406 | checkName, 407 | error, 408 | duration, 409 | isConfigError ? HealthStatus.DEGRADED : HealthStatus.CRITICAL 410 | ); 411 | } 412 | } 413 | 414 | /** 415 | * Check workspace access (detailed check) 416 | */ 417 | private async checkWorkspaceAccess(): Promise { 418 | const startTime = performance.now(); 419 | const checkName = 'workspace_access'; 420 | 421 | try { 422 | const workspaces = await this.trelloClient.listWorkspaces(); 423 | const duration = performance.now() - startTime; 424 | this.recordPerformanceMetric(duration, true); 425 | 426 | return { 427 | name: checkName, 428 | status: HealthStatus.HEALTHY, 429 | message: `Access to ${workspaces.length} workspaces confirmed`, 430 | duration_ms: Math.round(duration), 431 | timestamp: new Date().toISOString(), 432 | metadata: { 433 | total_workspaces: workspaces.length, 434 | active_workspace_id: this.trelloClient.activeWorkspaceId, 435 | workspace_names: workspaces.map(w => w.displayName), 436 | }, 437 | }; 438 | } catch (error) { 439 | const duration = performance.now() - startTime; 440 | this.recordPerformanceMetric(duration, false); 441 | 442 | return this.createErrorCheck(checkName, error, duration); 443 | } 444 | } 445 | 446 | /** 447 | * Calculate overall system health status 448 | */ 449 | private calculateOverallStatus(checks: HealthCheck[]): HealthStatus { 450 | if (checks.some(c => c.status === HealthStatus.CRITICAL)) { 451 | return HealthStatus.CRITICAL; 452 | } 453 | if (checks.some(c => c.status === HealthStatus.DEGRADED)) { 454 | return HealthStatus.DEGRADED; 455 | } 456 | if (checks.every(c => c.status === HealthStatus.HEALTHY)) { 457 | return HealthStatus.HEALTHY; 458 | } 459 | return HealthStatus.UNKNOWN; 460 | } 461 | 462 | /** 463 | * Generate health-based recommendations 464 | */ 465 | private generateRecommendations(checks: HealthCheck[], overallStatus: HealthStatus): string[] { 466 | const recommendations: string[] = []; 467 | 468 | // Check for specific issues and provide targeted advice 469 | const boardCheck = checks.find(c => c.name === 'board_access'); 470 | if (boardCheck?.status === HealthStatus.DEGRADED && boardCheck.metadata?.suggestion) { 471 | recommendations.push(boardCheck.metadata.suggestion); 472 | } 473 | 474 | const rateLimitCheck = checks.find(c => c.name === 'rate_limit_health'); 475 | if (rateLimitCheck?.status === HealthStatus.DEGRADED) { 476 | recommendations.push( 477 | 'Consider implementing request throttling or caching to reduce API usage' 478 | ); 479 | } 480 | 481 | const performanceCheck = checks.find(c => c.name === 'performance_metrics'); 482 | if (performanceCheck?.status === HealthStatus.DEGRADED) { 483 | recommendations.push( 484 | 'Investigate slow response times - consider network conditions or API load' 485 | ); 486 | } 487 | 488 | // Overall status recommendations 489 | if (overallStatus === HealthStatus.HEALTHY) { 490 | recommendations.push('All systems operating normally - maintain current configuration'); 491 | } else if (overallStatus === HealthStatus.CRITICAL) { 492 | recommendations.push('Immediate attention required - check error logs and connectivity'); 493 | } 494 | 495 | return recommendations.length > 0 496 | ? recommendations 497 | : ['System assessment complete - no specific recommendations']; 498 | } 499 | 500 | /** 501 | * Check if repair functionality is available 502 | */ 503 | private isRepairAvailable(checks: HealthCheck[]): boolean { 504 | // Simple heuristic: repair available if we have degraded but not critical issues 505 | return ( 506 | checks.some(c => c.status === HealthStatus.DEGRADED) && 507 | !checks.some(c => c.status === HealthStatus.CRITICAL) 508 | ); 509 | } 510 | 511 | /** 512 | * Create a standardized error check result 513 | */ 514 | private createErrorCheck( 515 | checkName: string, 516 | error: unknown, 517 | duration?: number, 518 | status: HealthStatus = HealthStatus.CRITICAL 519 | ): HealthCheck { 520 | let message = 'Unknown error occurred'; 521 | let errorCode: string | undefined; 522 | 523 | if (error instanceof Error) { 524 | message = error.message; 525 | } 526 | 527 | if (error && typeof error === 'object' && 'response' in error) { 528 | const axiosError = error as AxiosError; 529 | errorCode = axiosError.response?.status?.toString(); 530 | message = `HTTP ${axiosError.response?.status}: ${axiosError.message}`; 531 | } 532 | 533 | return { 534 | name: checkName, 535 | status, 536 | message, 537 | duration_ms: Math.round(duration || 0), 538 | timestamp: new Date().toISOString(), 539 | metadata: { 540 | error_type: error?.constructor?.name || 'Unknown', 541 | error_code: errorCode, 542 | error_details: error instanceof Error ? error.stack : undefined, 543 | }, 544 | }; 545 | } 546 | 547 | /** 548 | * Record performance metrics for tracking 549 | */ 550 | private recordPerformanceMetric(duration: number, success: boolean): void { 551 | const now = Date.now(); 552 | this.performanceTracker.requests.push({ timestamp: now, duration, success }); 553 | 554 | // Keep only last 100 requests to prevent memory leaks 555 | if (this.performanceTracker.requests.length > 100) { 556 | this.performanceTracker.requests = this.performanceTracker.requests.slice(-100); 557 | } 558 | } 559 | 560 | /** 561 | * Calculate comprehensive performance metrics 562 | */ 563 | private calculatePerformanceMetrics() { 564 | const requests = this.performanceTracker.requests; 565 | if (requests.length === 0) { 566 | return { 567 | avg_response_time_ms: 0, 568 | success_rate_percent: 100, 569 | rate_limit_utilization_percent: 0, 570 | requests_per_minute: 0, 571 | }; 572 | } 573 | 574 | const avgResponseTime = requests.reduce((sum, r) => sum + r.duration, 0) / requests.length; 575 | const successRate = (requests.filter(r => r.success).length / requests.length) * 100; 576 | 577 | // Calculate requests per minute based on recent activity 578 | const oneMinuteAgo = Date.now() - 60000; 579 | const recentRequests = requests.filter(r => r.timestamp > oneMinuteAgo); 580 | const requestsPerMinute = recentRequests.length; 581 | 582 | return { 583 | avg_response_time_ms: Math.round(avgResponseTime), 584 | success_rate_percent: Math.round(successRate * 100) / 100, 585 | rate_limit_utilization_percent: this.calculateRateLimitUtilization(), 586 | requests_per_minute: requestsPerMinute, 587 | }; 588 | } 589 | 590 | /** 591 | * Calculate rate limit utilization (approximation) 592 | */ 593 | private calculateRateLimitUtilization(): number { 594 | const requests = this.performanceTracker.requests; 595 | const tenSecondsAgo = Date.now() - 10000; 596 | const recentRequests = requests.filter(r => r.timestamp > tenSecondsAgo).length; 597 | 598 | // Use the more restrictive limit (100 per 10 seconds for tokens) 599 | return Math.min(100, (recentRequests / 100) * 100); 600 | } 601 | 602 | /** 603 | * Categorize response times for reporting 604 | */ 605 | private categorizeResponseTime(duration: number): string { 606 | if (duration < 200) return 'excellent'; 607 | if (duration < 500) return 'good'; 608 | if (duration < 1000) return 'fair'; 609 | if (duration < 2000) return 'slow'; 610 | return 'very_slow'; 611 | } 612 | 613 | /** 614 | * Start background performance monitoring 615 | */ 616 | private startPerformanceMonitoring(): void { 617 | // Simple monitoring - in a real implementation, this might be more sophisticated 618 | setInterval(() => { 619 | // Clean up old metrics to prevent memory leaks 620 | const fiveMinutesAgo = Date.now() - 300000; 621 | this.performanceTracker.requests = this.performanceTracker.requests.filter( 622 | r => r.timestamp > fiveMinutesAgo 623 | ); 624 | }, 60000); // Clean up every minute 625 | } 626 | 627 | /** 628 | * Get the last health check result 629 | */ 630 | getLastHealthCheck(): SystemHealthReport | undefined { 631 | return this.lastHealthCheck; 632 | } 633 | } 634 | -------------------------------------------------------------------------------- /src/index.ts.backup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { 5 | CallToolRequestSchema, 6 | ErrorCode, 7 | ListToolsRequestSchema, 8 | McpError, 9 | } from '@modelcontextprotocol/sdk/types.js'; 10 | import { TrelloClient } from './trello-client.js'; 11 | import { 12 | validateGetCardsListRequest, 13 | validateGetRecentActivityRequest, 14 | validateAddCardRequest, 15 | validateUpdateCardRequest, 16 | validateArchiveCardRequest, 17 | validateAddListRequest, 18 | validateArchiveListRequest, 19 | validateMoveCardRequest, 20 | validateAttachImageRequest, 21 | validateGetListsRequest, 22 | validateSetActiveBoardRequest, 23 | validateSetActiveWorkspaceRequest, 24 | validateListBoardsInWorkspaceRequest, 25 | } from './validators.js'; 26 | 27 | class TrelloServer { 28 | private server: Server; 29 | private trelloClient: TrelloClient; 30 | 31 | constructor() { 32 | const apiKey = process.env.TRELLO_API_KEY; 33 | const token = process.env.TRELLO_TOKEN; 34 | const defaultBoardId = process.env.TRELLO_BOARD_ID; 35 | 36 | if (!apiKey || !token) { 37 | throw new Error('TRELLO_API_KEY and TRELLO_TOKEN environment variables are required'); 38 | } 39 | 40 | this.trelloClient = new TrelloClient({ 41 | apiKey, 42 | token, 43 | defaultBoardId, 44 | boardId: defaultBoardId // Use defaultBoardId as initial boardId if provided 45 | }); 46 | 47 | this.server = new Server( 48 | { 49 | name: 'trello-server', 50 | version: '0.3.0', 51 | }, 52 | { 53 | capabilities: { 54 | tools: {}, 55 | }, 56 | } 57 | ); 58 | 59 | this.setupToolHandlers(); 60 | 61 | // Error handling 62 | this.server.onerror = error => console.error('[MCP Error]', error); 63 | process.on('SIGINT', async () => { 64 | await this.server.close(); 65 | process.exit(0); 66 | }); 67 | } 68 | 69 | private setupToolHandlers() { 70 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 71 | tools: [ 72 | { 73 | name: 'get_cards_by_list_id', 74 | description: 'Fetch cards from a specific Trello list on a specific board', 75 | inputSchema: { 76 | type: 'object', 77 | properties: { 78 | boardId: { 79 | type: 'string', 80 | description: 'ID of the Trello board (uses default if not provided)', 81 | }, 82 | listId: { 83 | type: 'string', 84 | description: 'ID of the Trello list', 85 | }, 86 | }, 87 | required: ['listId'], 88 | }, 89 | }, 90 | { 91 | name: 'get_lists', 92 | description: 'Retrieve all lists from the specified board', 93 | inputSchema: { 94 | type: 'object', 95 | properties: { 96 | boardId: { 97 | type: 'string', 98 | description: 'ID of the Trello board (uses default if not provided)', 99 | }, 100 | }, 101 | required: [], 102 | }, 103 | }, 104 | { 105 | name: 'get_recent_activity', 106 | description: 'Fetch recent activity on the Trello board', 107 | inputSchema: { 108 | type: 'object', 109 | properties: { 110 | boardId: { 111 | type: 'string', 112 | description: 'ID of the Trello board (uses default if not provided)', 113 | }, 114 | limit: { 115 | type: 'number', 116 | description: 'Number of activities to fetch (default: 10)', 117 | }, 118 | }, 119 | required: [], 120 | }, 121 | }, 122 | { 123 | name: 'add_card_to_list', 124 | description: 'Add a new card to a specified list on a specific board', 125 | inputSchema: { 126 | type: 'object', 127 | properties: { 128 | boardId: { 129 | type: 'string', 130 | description: 'ID of the Trello board (uses default if not provided)', 131 | }, 132 | listId: { 133 | type: 'string', 134 | description: 'ID of the list to add the card to', 135 | }, 136 | name: { 137 | type: 'string', 138 | description: 'Name of the card', 139 | }, 140 | description: { 141 | type: 'string', 142 | description: 'Description of the card', 143 | }, 144 | dueDate: { 145 | type: 'string', 146 | description: 'Due date for the card (ISO 8601 format)', 147 | }, 148 | labels: { 149 | type: 'array', 150 | items: { 151 | type: 'string', 152 | }, 153 | description: 'Array of label IDs to apply to the card', 154 | }, 155 | }, 156 | required: ['listId', 'name'], 157 | }, 158 | }, 159 | { 160 | name: 'update_card_details', 161 | description: "Update an existing card's details on a specific board", 162 | inputSchema: { 163 | type: 'object', 164 | properties: { 165 | boardId: { 166 | type: 'string', 167 | description: 'ID of the Trello board (uses default if not provided)', 168 | }, 169 | cardId: { 170 | type: 'string', 171 | description: 'ID of the card to update', 172 | }, 173 | name: { 174 | type: 'string', 175 | description: 'New name for the card', 176 | }, 177 | description: { 178 | type: 'string', 179 | description: 'New description for the card', 180 | }, 181 | dueDate: { 182 | type: 'string', 183 | description: 'New due date for the card (ISO 8601 format)', 184 | }, 185 | labels: { 186 | type: 'array', 187 | items: { 188 | type: 'string', 189 | }, 190 | description: 'New array of label IDs for the card', 191 | }, 192 | }, 193 | required: ['cardId'], 194 | }, 195 | }, 196 | { 197 | name: 'archive_card', 198 | description: 'Send a card to the archive on a specific board', 199 | inputSchema: { 200 | type: 'object', 201 | properties: { 202 | boardId: { 203 | type: 'string', 204 | description: 'ID of the Trello board (uses default if not provided)', 205 | }, 206 | cardId: { 207 | type: 'string', 208 | description: 'ID of the card to archive', 209 | }, 210 | }, 211 | required: ['cardId'], 212 | }, 213 | }, 214 | { 215 | name: 'move_card', 216 | description: 'Move a card to a different list, potentially on a different board', 217 | inputSchema: { 218 | type: 'object', 219 | properties: { 220 | boardId: { 221 | type: 'string', 222 | description: 223 | 'ID of the target Trello board (where the listId resides, uses default if not provided)', 224 | }, 225 | cardId: { 226 | type: 'string', 227 | description: 'ID of the card to move', 228 | }, 229 | listId: { 230 | type: 'string', 231 | description: 'ID of the target list', 232 | }, 233 | }, 234 | required: ['cardId', 'listId'], 235 | }, 236 | }, 237 | { 238 | name: 'add_list_to_board', 239 | description: 'Add a new list to the specified board', 240 | inputSchema: { 241 | type: 'object', 242 | properties: { 243 | boardId: { 244 | type: 'string', 245 | description: 'ID of the Trello board (uses default if not provided)', 246 | }, 247 | name: { 248 | type: 'string', 249 | description: 'Name of the new list', 250 | }, 251 | }, 252 | required: ['name'], 253 | }, 254 | }, 255 | { 256 | name: 'archive_list', 257 | description: 'Send a list to the archive on a specific board', 258 | inputSchema: { 259 | type: 'object', 260 | properties: { 261 | boardId: { 262 | type: 'string', 263 | description: 'ID of the Trello board (uses default if not provided)', 264 | }, 265 | listId: { 266 | type: 'string', 267 | description: 'ID of the list to archive', 268 | }, 269 | }, 270 | required: ['listId'], 271 | }, 272 | }, 273 | { 274 | name: 'get_my_cards', 275 | description: 'Fetch all cards assigned to the current user', 276 | inputSchema: { 277 | type: 'object', 278 | properties: {}, 279 | required: [], 280 | }, 281 | }, 282 | { 283 | name: 'attach_image_to_card', 284 | description: 'Attach an image to a card from a URL on a specific board', 285 | inputSchema: { 286 | type: 'object', 287 | properties: { 288 | boardId: { 289 | type: 'string', 290 | description: 291 | 'ID of the Trello board where the card exists (uses default if not provided)', 292 | }, 293 | cardId: { 294 | type: 'string', 295 | description: 'ID of the card to attach the image to', 296 | }, 297 | imageUrl: { 298 | type: 'string', 299 | description: 'URL of the image to attach', 300 | }, 301 | name: { 302 | type: 'string', 303 | description: 'Optional name for the attachment (defaults to "Image Attachment")', 304 | }, 305 | }, 306 | required: ['cardId', 'imageUrl'], 307 | }, 308 | }, 309 | { 310 | name: 'list_boards', 311 | description: 'List all boards the user has access to', 312 | inputSchema: { 313 | type: 'object', 314 | properties: {}, 315 | required: [], 316 | }, 317 | }, 318 | { 319 | name: 'set_active_board', 320 | description: 'Set the active board for future operations', 321 | inputSchema: { 322 | type: 'object', 323 | properties: { 324 | boardId: { 325 | type: 'string', 326 | description: 'ID of the board to set as active', 327 | }, 328 | }, 329 | required: ['boardId'], 330 | }, 331 | }, 332 | { 333 | name: 'list_workspaces', 334 | description: 'List all workspaces the user has access to', 335 | inputSchema: { 336 | type: 'object', 337 | properties: {}, 338 | required: [], 339 | }, 340 | }, 341 | { 342 | name: 'set_active_workspace', 343 | description: 'Set the active workspace for future operations', 344 | inputSchema: { 345 | type: 'object', 346 | properties: { 347 | workspaceId: { 348 | type: 'string', 349 | description: 'ID of the workspace to set as active', 350 | }, 351 | }, 352 | required: ['workspaceId'], 353 | }, 354 | }, 355 | { 356 | name: 'list_boards_in_workspace', 357 | description: 'List all boards in a specific workspace', 358 | inputSchema: { 359 | type: 'object', 360 | properties: { 361 | workspaceId: { 362 | type: 'string', 363 | description: 'ID of the workspace to list boards from', 364 | }, 365 | }, 366 | required: ['workspaceId'], 367 | }, 368 | }, 369 | { 370 | name: 'get_active_board_info', 371 | description: 'Get information about the currently active board', 372 | inputSchema: { 373 | type: 'object', 374 | properties: {}, 375 | required: [], 376 | }, 377 | }, 378 | ], 379 | })); 380 | 381 | this.server.setRequestHandler(CallToolRequestSchema, async request => { 382 | try { 383 | if (!request.params.arguments) { 384 | throw new McpError(ErrorCode.InvalidParams, 'Missing arguments'); 385 | } 386 | 387 | const args = request.params.arguments as Record; 388 | 389 | switch (request.params.name) { 390 | case 'get_cards_by_list_id': { 391 | const validArgs = validateGetCardsListRequest(args); 392 | const cards = await this.trelloClient.getCardsByList( 393 | validArgs.boardId, 394 | validArgs.listId 395 | ); 396 | return { 397 | content: [{ type: 'text', text: JSON.stringify(cards, null, 2) }], 398 | }; 399 | } 400 | 401 | case 'list_boards': { 402 | const boards = await this.trelloClient.listBoards(); 403 | return { 404 | content: [{ type: 'text', text: JSON.stringify(boards, null, 2) }], 405 | }; 406 | } 407 | 408 | case 'get_lists': { 409 | const validArgs = validateGetListsRequest(args); 410 | const lists = await this.trelloClient.getLists(validArgs.boardId); 411 | return { 412 | content: [{ type: 'text', text: JSON.stringify(lists, null, 2) }], 413 | }; 414 | } 415 | 416 | case 'get_recent_activity': { 417 | const validArgs = validateGetRecentActivityRequest(args); 418 | const activity = await this.trelloClient.getRecentActivity( 419 | validArgs.boardId, 420 | validArgs.limit 421 | ); 422 | return { 423 | content: [{ type: 'text', text: JSON.stringify(activity, null, 2) }], 424 | }; 425 | } 426 | 427 | case 'add_card_to_list': { 428 | const validArgs = validateAddCardRequest(args); 429 | const card = await this.trelloClient.addCard(validArgs.boardId, validArgs); 430 | return { 431 | content: [{ type: 'text', text: JSON.stringify(card, null, 2) }], 432 | }; 433 | } 434 | 435 | case 'update_card_details': { 436 | const validArgs = validateUpdateCardRequest(args); 437 | const card = await this.trelloClient.updateCard(validArgs.boardId, validArgs); 438 | return { 439 | content: [{ type: 'text', text: JSON.stringify(card, null, 2) }], 440 | }; 441 | } 442 | 443 | case 'archive_card': { 444 | const validArgs = validateArchiveCardRequest(args); 445 | const card = await this.trelloClient.archiveCard(validArgs.boardId, validArgs.cardId); 446 | return { 447 | content: [{ type: 'text', text: JSON.stringify(card, null, 2) }], 448 | }; 449 | } 450 | 451 | case 'move_card': { 452 | const validArgs = validateMoveCardRequest(args); 453 | const card = await this.trelloClient.moveCard( 454 | validArgs.boardId, 455 | validArgs.cardId, 456 | validArgs.listId 457 | ); 458 | return { 459 | content: [{ type: 'text', text: JSON.stringify(card, null, 2) }], 460 | }; 461 | } 462 | 463 | case 'add_list_to_board': { 464 | const validArgs = validateAddListRequest(args); 465 | const list = await this.trelloClient.addList(validArgs.boardId, validArgs.name); 466 | return { 467 | content: [{ type: 'text', text: JSON.stringify(list, null, 2) }], 468 | }; 469 | } 470 | 471 | case 'archive_list': { 472 | const validArgs = validateArchiveListRequest(args); 473 | const list = await this.trelloClient.archiveList(validArgs.boardId, validArgs.listId); 474 | return { 475 | content: [{ type: 'text', text: JSON.stringify(list, null, 2) }], 476 | }; 477 | } 478 | 479 | case 'get_my_cards': { 480 | const cards = await this.trelloClient.getMyCards(); 481 | return { 482 | content: [{ type: 'text', text: JSON.stringify(cards, null, 2) }], 483 | }; 484 | } 485 | 486 | case 'attach_image_to_card': { 487 | const validArgs = validateAttachImageRequest(args); 488 | <<<<<<< HEAD 489 | const attachment = await this.trelloClient.attachImageToCard( 490 | validArgs.boardId, 491 | validArgs.cardId, 492 | validArgs.imageUrl, 493 | validArgs.name 494 | ); 495 | ======= 496 | try { 497 | const attachment = await this.trelloClient.attachImageToCard( 498 | validArgs.cardId, 499 | validArgs.imageUrl, 500 | validArgs.name 501 | ); 502 | return { 503 | content: [{ type: 'text', text: JSON.stringify(attachment, null, 2) }], 504 | }; 505 | } catch (error) { 506 | return this.handleErrorResponse(error); 507 | } 508 | } 509 | 510 | case 'list_boards': { 511 | const boards = await this.trelloClient.listBoards(); 512 | >>>>>>> origin/main 513 | return { 514 | content: [{ type: 'text', text: JSON.stringify(boards, null, 2) }], 515 | }; 516 | } 517 | 518 | case 'set_active_board': { 519 | const validArgs = validateSetActiveBoardRequest(args); 520 | try { 521 | const board = await this.trelloClient.setActiveBoard(validArgs.boardId); 522 | return { 523 | content: [{ 524 | type: 'text', 525 | text: `Successfully set active board to "${board.name}" (${board.id})` 526 | }], 527 | }; 528 | } catch (error) { 529 | return this.handleErrorResponse(error); 530 | } 531 | } 532 | 533 | case 'list_workspaces': { 534 | const workspaces = await this.trelloClient.listWorkspaces(); 535 | return { 536 | content: [{ type: 'text', text: JSON.stringify(workspaces, null, 2) }], 537 | }; 538 | } 539 | 540 | case 'set_active_workspace': { 541 | const validArgs = validateSetActiveWorkspaceRequest(args); 542 | try { 543 | const workspace = await this.trelloClient.setActiveWorkspace(validArgs.workspaceId); 544 | return { 545 | content: [{ 546 | type: 'text', 547 | text: `Successfully set active workspace to "${workspace.displayName}" (${workspace.id})` 548 | }], 549 | }; 550 | } catch (error) { 551 | return this.handleErrorResponse(error); 552 | } 553 | } 554 | 555 | case 'list_boards_in_workspace': { 556 | const validArgs = validateListBoardsInWorkspaceRequest(args); 557 | try { 558 | const boards = await this.trelloClient.listBoardsInWorkspace(validArgs.workspaceId); 559 | return { 560 | content: [{ type: 'text', text: JSON.stringify(boards, null, 2) }], 561 | }; 562 | } catch (error) { 563 | return this.handleErrorResponse(error); 564 | } 565 | } 566 | 567 | case 'get_active_board_info': { 568 | try { 569 | const boardId = this.trelloClient.activeBoardId; 570 | const board = await this.trelloClient.getBoardById(boardId); 571 | return { 572 | content: [{ 573 | type: 'text', 574 | text: JSON.stringify({ 575 | ...board, 576 | isActive: true, 577 | activeWorkspaceId: this.trelloClient.activeWorkspaceId || 'Not set' 578 | }, null, 2) 579 | }], 580 | }; 581 | } catch (error) { 582 | return this.handleErrorResponse(error); 583 | } 584 | } 585 | 586 | default: 587 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); 588 | } 589 | } catch (error) { 590 | return { 591 | content: [ 592 | { 593 | type: 'text', 594 | text: error instanceof Error ? error.message : 'Unknown error occurred', 595 | }, 596 | ], 597 | isError: true, 598 | }; 599 | } 600 | }); 601 | } 602 | 603 | private handleErrorResponse(error: unknown) { 604 | return { 605 | content: [ 606 | { 607 | type: 'text', 608 | text: `Error: ${error instanceof Error ? error.message : 'Unknown error occurred'}`, 609 | }, 610 | ], 611 | isError: true, 612 | }; 613 | } 614 | 615 | async run() { 616 | const transport = new StdioServerTransport(); 617 | // Load configuration before starting the server 618 | await this.trelloClient.loadConfig().catch((error) => { 619 | console.error('Failed to load saved configuration:', error); 620 | // Continue with default config if loading fails 621 | }); 622 | await this.server.connect(transport); 623 | console.error('Trello MCP server running on stdio'); 624 | } 625 | } 626 | 627 | const server = new TrelloServer(); 628 | server.run().catch(console.error); 629 | -------------------------------------------------------------------------------- /examples/python-examples.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python Examples for MCP Server Trello 3 | 4 | These examples demonstrate how to use the MCP Server Trello 5 | with Python applications. 6 | """ 7 | 8 | import json 9 | from datetime import datetime, timedelta 10 | from typing import Dict, List, Optional, Any 11 | from dataclasses import dataclass 12 | from enum import Enum 13 | 14 | 15 | # Example 1: Basic MCP Client Wrapper 16 | class TrelloMCPClient: 17 | """Wrapper for MCP Server Trello interactions.""" 18 | 19 | def __init__(self, server_name: str = 'trello'): 20 | self.server_name = server_name 21 | 22 | async def call_tool(self, tool_name: str, arguments: Dict[str, Any] = None) -> Dict: 23 | """ 24 | Call an MCP tool with the given arguments. 25 | This would be replaced with your actual MCP client implementation. 26 | """ 27 | # Placeholder for actual MCP client call 28 | # In practice, you would use an MCP client library 29 | return await use_mcp_tool({ 30 | 'server_name': self.server_name, 31 | 'tool_name': tool_name, 32 | 'arguments': arguments or {} 33 | }) 34 | 35 | 36 | # Example 2: Task Priority System 37 | class Priority(Enum): 38 | """Task priority levels.""" 39 | CRITICAL = "critical" 40 | HIGH = "high" 41 | MEDIUM = "medium" 42 | LOW = "low" 43 | 44 | 45 | @dataclass 46 | class Task: 47 | """Represents a Trello task.""" 48 | name: str 49 | description: str 50 | priority: Priority 51 | assignee: Optional[str] = None 52 | due_date: Optional[datetime] = None 53 | labels: List[str] = None 54 | checklist_items: List[str] = None 55 | 56 | 57 | class TaskManager: 58 | """Manages tasks in Trello with priority-based workflows.""" 59 | 60 | def __init__(self, trello_client: TrelloMCPClient): 61 | self.trello = trello_client 62 | self.priority_lists = {} 63 | 64 | async def initialize_priority_lists(self): 65 | """Create lists for each priority level if they don't exist.""" 66 | lists = await self.trello.call_tool('get_lists') 67 | existing_list_names = {lst['name'] for lst in lists} 68 | 69 | for priority in Priority: 70 | list_name = f"Priority: {priority.value.capitalize()}" 71 | if list_name not in existing_list_names: 72 | new_list = await self.trello.call_tool('add_list_to_board', { 73 | 'name': list_name 74 | }) 75 | self.priority_lists[priority] = new_list['id'] 76 | else: 77 | # Find the existing list ID 78 | for lst in lists: 79 | if lst['name'] == list_name: 80 | self.priority_lists[priority] = lst['id'] 81 | break 82 | 83 | async def create_task(self, task: Task) -> Dict: 84 | """Create a task card in the appropriate priority list.""" 85 | # Calculate due date based on priority if not specified 86 | if not task.due_date: 87 | days_by_priority = { 88 | Priority.CRITICAL: 1, 89 | Priority.HIGH: 3, 90 | Priority.MEDIUM: 7, 91 | Priority.LOW: 14 92 | } 93 | task.due_date = datetime.now() + timedelta(days=days_by_priority[task.priority]) 94 | 95 | # Create the card 96 | card = await self.trello.call_tool('add_card_to_list', { 97 | 'listId': self.priority_lists[task.priority], 98 | 'name': task.name, 99 | 'description': self._format_task_description(task), 100 | 'dueDate': task.due_date.isoformat(), 101 | 'labels': task.labels or [task.priority.value] 102 | }) 103 | 104 | # Add checklist items if provided 105 | if task.checklist_items: 106 | for item in task.checklist_items: 107 | await self.trello.call_tool('add_checklist_item', { 108 | 'text': item, 109 | 'checkListName': 'Task Checklist' 110 | }) 111 | 112 | return card 113 | 114 | def _format_task_description(self, task: Task) -> str: 115 | """Format task description with metadata.""" 116 | return f"""## Task Details 117 | 118 | **Priority**: {task.priority.value.upper()} 119 | **Assigned to**: {task.assignee or 'Unassigned'} 120 | **Created**: {datetime.now().isoformat()} 121 | 122 | ### Description 123 | {task.description} 124 | 125 | ### Metadata 126 | - Priority Level: {task.priority.value} 127 | - Due Date: {task.due_date.strftime('%Y-%m-%d %H:%M') if task.due_date else 'Not set'} 128 | - Labels: {', '.join(task.labels) if task.labels else 'None'} 129 | """ 130 | 131 | async def escalate_task(self, card_id: str, reason: str): 132 | """Escalate a task to a higher priority.""" 133 | # Get current card details 134 | card = await self.trello.call_tool('get_card', { 135 | 'cardId': card_id 136 | }) 137 | 138 | # Determine new priority 139 | current_priority = None 140 | for label in card.get('labels', []): 141 | try: 142 | current_priority = Priority(label.get('name', label)) 143 | break 144 | except ValueError: 145 | continue 146 | 147 | if current_priority == Priority.CRITICAL: 148 | print("Task is already at highest priority") 149 | return 150 | 151 | # Map to next higher priority 152 | priority_order = [Priority.LOW, Priority.MEDIUM, Priority.HIGH, Priority.CRITICAL] 153 | current_index = priority_order.index(current_priority) 154 | new_priority = priority_order[current_index + 1] 155 | 156 | # Move to new priority list 157 | await self.trello.call_tool('move_card', { 158 | 'cardId': card_id, 159 | 'listId': self.priority_lists[new_priority] 160 | }) 161 | 162 | # Update labels 163 | new_labels = [label for label in card.get('labels', []) 164 | if label.get('name', label) != current_priority.value] 165 | new_labels.append(new_priority.value) 166 | 167 | await self.trello.call_tool('update_card_details', { 168 | 'cardId': card_id, 169 | 'labels': new_labels 170 | }) 171 | 172 | # Add escalation comment 173 | await self.trello.call_tool('add_comment', { 174 | 'cardId': card_id, 175 | 'text': f"""⚠️ **Task Escalated** 176 | 177 | **From**: {current_priority.value.upper()} 178 | **To**: {new_priority.value.upper()} 179 | **Reason**: {reason} 180 | **Escalated by**: System 181 | **Time**: {datetime.now().isoformat()}""" 182 | }) 183 | 184 | 185 | # Example 3: Kanban Board Automation 186 | class KanbanAutomation: 187 | """Automates Kanban board workflows.""" 188 | 189 | def __init__(self, trello_client: TrelloMCPClient): 190 | self.trello = trello_client 191 | self.workflow_stages = [ 192 | "Backlog", 193 | "To Do", 194 | "In Progress", 195 | "Review", 196 | "Testing", 197 | "Done" 198 | ] 199 | self.stage_lists = {} 200 | 201 | async def setup_kanban_board(self): 202 | """Set up a standard Kanban board structure.""" 203 | for stage in self.workflow_stages: 204 | list_data = await self.trello.call_tool('add_list_to_board', { 205 | 'name': stage 206 | }) 207 | self.stage_lists[stage] = list_data['id'] 208 | 209 | async def move_card_to_stage(self, card_id: str, stage: str, comment: str = None): 210 | """Move a card to a specific stage in the workflow.""" 211 | if stage not in self.stage_lists: 212 | raise ValueError(f"Invalid stage: {stage}") 213 | 214 | # Move the card 215 | await self.trello.call_tool('move_card', { 216 | 'cardId': card_id, 217 | 'listId': self.stage_lists[stage] 218 | }) 219 | 220 | # Add transition comment 221 | if comment: 222 | await self.trello.call_tool('add_comment', { 223 | 'cardId': card_id, 224 | 'text': f"➡️ Moved to **{stage}**\n\n{comment}" 225 | }) 226 | 227 | async def calculate_cycle_time(self, card_id: str) -> Dict: 228 | """Calculate cycle time for a card.""" 229 | card = await self.trello.call_tool('get_card', { 230 | 'cardId': card_id 231 | }) 232 | 233 | # Get card activity 234 | activities = await self.trello.call_tool('get_recent_activity', { 235 | 'limit': 100 236 | }) 237 | 238 | # Filter activities for this card 239 | card_activities = [ 240 | a for a in activities 241 | if a.get('data', {}).get('card', {}).get('id') == card_id 242 | ] 243 | 244 | # Calculate time in each stage 245 | stage_times = {} 246 | current_stage = card.get('list', {}).get('name') 247 | 248 | # Simple cycle time calculation 249 | created_date = datetime.fromisoformat(card.get('dateLastActivity', datetime.now().isoformat())) 250 | current_date = datetime.now() 251 | total_cycle_time = current_date - created_date 252 | 253 | return { 254 | 'card_name': card.get('name'), 255 | 'current_stage': current_stage, 256 | 'total_cycle_time_hours': total_cycle_time.total_seconds() / 3600, 257 | 'created': created_date.isoformat(), 258 | 'last_activity': card.get('dateLastActivity') 259 | } 260 | 261 | async def apply_wip_limits(self, limits: Dict[str, int]): 262 | """Apply Work-In-Progress limits to lists.""" 263 | for stage, limit in limits.items(): 264 | if stage not in self.stage_lists: 265 | continue 266 | 267 | # Get cards in this list 268 | cards = await self.trello.call_tool('get_cards_by_list_id', { 269 | 'listId': self.stage_lists[stage] 270 | }) 271 | 272 | if len(cards) > limit: 273 | # Create warning card 274 | await self.trello.call_tool('add_card_to_list', { 275 | 'listId': self.stage_lists[stage], 276 | 'name': f'⚠️ WIP LIMIT EXCEEDED', 277 | 'description': f"""## WIP Limit Violation 278 | 279 | The **{stage}** column has exceeded its WIP limit. 280 | 281 | - **Current cards**: {len(cards)} 282 | - **WIP Limit**: {limit} 283 | - **Excess**: {len(cards) - limit} 284 | 285 | ### Action Required 286 | Please complete work in progress before pulling new items.""", 287 | 'labels': ['warning', 'wip-violation'] 288 | }) 289 | 290 | 291 | # Example 4: Retrospective Management 292 | class RetroManager: 293 | """Manages sprint retrospectives in Trello.""" 294 | 295 | def __init__(self, trello_client: TrelloMCPClient): 296 | self.trello = trello_client 297 | 298 | async def create_retro_board(self, sprint_number: int) -> Dict: 299 | """Create a retrospective board with standard columns.""" 300 | # Create lists for retrospective 301 | retro_lists = { 302 | "What Went Well": "green", 303 | "What Could Be Improved": "yellow", 304 | "Action Items": "red", 305 | "Kudos": "blue" 306 | } 307 | 308 | list_ids = {} 309 | for list_name, color in retro_lists.items(): 310 | list_data = await self.trello.call_tool('add_list_to_board', { 311 | 'name': f"Sprint {sprint_number} - {list_name}" 312 | }) 313 | list_ids[list_name] = list_data['id'] 314 | 315 | # Create intro card 316 | await self.trello.call_tool('add_card_to_list', { 317 | 'listId': list_ids["What Went Well"], 318 | 'name': f'Sprint {sprint_number} Retrospective', 319 | 'description': f"""# Sprint {sprint_number} Retrospective 320 | 321 | ## Guidelines 322 | 1. Be constructive and specific 323 | 2. Focus on process, not people 324 | 3. Suggest actionable improvements 325 | 4. Celebrate successes 326 | 327 | ## Format 328 | - **What Went Well**: Positive outcomes and successes 329 | - **What Could Be Improved**: Areas for improvement 330 | - **Action Items**: Specific actions to take 331 | - **Kudos**: Recognition for team members 332 | 333 | **Date**: {datetime.now().strftime('%Y-%m-%d')}""" 334 | }) 335 | 336 | return list_ids 337 | 338 | async def add_retro_item(self, list_name: str, title: str, 339 | description: str, votes: int = 0): 340 | """Add an item to the retrospective.""" 341 | lists = await self.trello.call_tool('get_lists') 342 | target_list = None 343 | 344 | for lst in lists: 345 | if list_name in lst['name']: 346 | target_list = lst['id'] 347 | break 348 | 349 | if not target_list: 350 | raise ValueError(f"List '{list_name}' not found") 351 | 352 | card = await self.trello.call_tool('add_card_to_list', { 353 | 'listId': target_list, 354 | 'name': f"{title} (👍 {votes})", 355 | 'description': f"""{description} 356 | 357 | --- 358 | **Votes**: {votes} 359 | **Added by**: Team 360 | **Date**: {datetime.now().isoformat()}""" 361 | }) 362 | 363 | return card 364 | 365 | async def convert_to_action_items(self, retro_cards: List[str], 366 | target_board_id: str): 367 | """Convert retrospective items to actionable tasks.""" 368 | action_items = [] 369 | 370 | for card_id in retro_cards: 371 | # Get card details 372 | card = await self.trello.call_tool('get_card', { 373 | 'cardId': card_id 374 | }) 375 | 376 | # Create action item in target board 377 | action_card = await self.trello.call_tool('add_card_to_list', { 378 | 'boardId': target_board_id, 379 | 'listId': 'backlog-list-id', # Would be fetched dynamically 380 | 'name': f"[RETRO ACTION] {card['name']}", 381 | 'description': f"""## Retrospective Action Item 382 | 383 | **Origin**: Sprint Retrospective 384 | **Original Issue**: {card['name']} 385 | 386 | ### Description 387 | {card.get('desc', 'No description provided')} 388 | 389 | ### Success Criteria 390 | - [ ] Issue addressed 391 | - [ ] Process updated 392 | - [ ] Team informed 393 | - [ ] Documentation updated 394 | 395 | **Created from retro**: {datetime.now().isoformat()}""", 396 | 'labels': ['retro-action', 'improvement'] 397 | }) 398 | 399 | action_items.append(action_card) 400 | 401 | return action_items 402 | 403 | 404 | # Example 5: Project Analytics Dashboard 405 | class ProjectAnalytics: 406 | """Generate analytics and metrics from Trello boards.""" 407 | 408 | def __init__(self, trello_client: TrelloMCPClient): 409 | self.trello = trello_client 410 | 411 | async def generate_burndown_data(self) -> Dict: 412 | """Generate data for a burndown chart.""" 413 | lists = await self.trello.call_tool('get_lists') 414 | 415 | burndown_data = { 416 | 'date': datetime.now().isoformat(), 417 | 'total_points': 0, 418 | 'completed_points': 0, 419 | 'remaining_points': 0, 420 | 'by_list': {} 421 | } 422 | 423 | for lst in lists: 424 | cards = await self.trello.call_tool('get_cards_by_list_id', { 425 | 'listId': lst['id'] 426 | }) 427 | 428 | list_points = 0 429 | for card in cards: 430 | # Extract story points from card name (e.g., "[5] Feature Name") 431 | import re 432 | points_match = re.match(r'\[(\d+)\]', card.get('name', '')) 433 | if points_match: 434 | points = int(points_match.group(1)) 435 | list_points += points 436 | burndown_data['total_points'] += points 437 | 438 | if lst['name'] in ['Done', 'Completed', 'Deployed']: 439 | burndown_data['completed_points'] += points 440 | 441 | burndown_data['by_list'][lst['name']] = list_points 442 | 443 | burndown_data['remaining_points'] = ( 444 | burndown_data['total_points'] - burndown_data['completed_points'] 445 | ) 446 | 447 | return burndown_data 448 | 449 | async def team_velocity_report(self, sprints: int = 3) -> Dict: 450 | """Calculate team velocity over recent sprints.""" 451 | velocity_data = { 452 | 'sprints': [], 453 | 'average_velocity': 0, 454 | 'trend': 'stable' 455 | } 456 | 457 | # This would typically look at archived cards with sprint labels 458 | lists = await self.trello.call_tool('get_lists') 459 | done_list = None 460 | 461 | for lst in lists: 462 | if lst['name'] in ['Done', 'Completed']: 463 | done_list = lst['id'] 464 | break 465 | 466 | if done_list: 467 | cards = await self.trello.call_tool('get_cards_by_list_id', { 468 | 'listId': done_list 469 | }) 470 | 471 | # Group cards by sprint (simplified - would use labels/dates in practice) 472 | sprint_points = {} 473 | for card in cards: 474 | # Extract sprint number from labels 475 | for label in card.get('labels', []): 476 | if 'sprint-' in label.get('name', '').lower(): 477 | sprint_num = label['name'].split('-')[1] 478 | if sprint_num not in sprint_points: 479 | sprint_points[sprint_num] = 0 480 | 481 | # Extract story points 482 | import re 483 | points_match = re.match(r'\[(\d+)\]', card.get('name', '')) 484 | if points_match: 485 | sprint_points[sprint_num] += int(points_match.group(1)) 486 | 487 | # Calculate average 488 | if sprint_points: 489 | velocities = list(sprint_points.values()) 490 | velocity_data['average_velocity'] = sum(velocities) / len(velocities) 491 | 492 | # Determine trend 493 | if len(velocities) >= 2: 494 | if velocities[-1] > velocities[-2]: 495 | velocity_data['trend'] = 'increasing' 496 | elif velocities[-1] < velocities[-2]: 497 | velocity_data['trend'] = 'decreasing' 498 | 499 | return velocity_data 500 | 501 | async def generate_health_metrics(self) -> Dict: 502 | """Generate overall project health metrics.""" 503 | metrics = { 504 | 'timestamp': datetime.now().isoformat(), 505 | 'lists': {}, 506 | 'overdue_cards': [], 507 | 'blocked_cards': [], 508 | 'stale_cards': [], 509 | 'total_cards': 0, 510 | 'health_score': 100 511 | } 512 | 513 | lists = await self.trello.call_tool('get_lists') 514 | now = datetime.now() 515 | 516 | for lst in lists: 517 | cards = await self.trello.call_tool('get_cards_by_list_id', { 518 | 'listId': lst['id'] 519 | }) 520 | 521 | metrics['lists'][lst['name']] = { 522 | 'count': len(cards), 523 | 'percentage': 0 524 | } 525 | metrics['total_cards'] += len(cards) 526 | 527 | for card in cards: 528 | # Check for overdue cards 529 | if card.get('due'): 530 | due_date = datetime.fromisoformat(card['due'].replace('Z', '+00:00')) 531 | if due_date < now and not card.get('dueComplete'): 532 | metrics['overdue_cards'].append({ 533 | 'name': card['name'], 534 | 'due': card['due'], 535 | 'list': lst['name'] 536 | }) 537 | metrics['health_score'] -= 5 538 | 539 | # Check for blocked cards 540 | if any(label.get('name', '') == 'blocked' for label in card.get('labels', [])): 541 | metrics['blocked_cards'].append({ 542 | 'name': card['name'], 543 | 'list': lst['name'] 544 | }) 545 | metrics['health_score'] -= 3 546 | 547 | # Check for stale cards (no activity in 14 days) 548 | if card.get('dateLastActivity'): 549 | last_activity = datetime.fromisoformat( 550 | card['dateLastActivity'].replace('Z', '+00:00') 551 | ) 552 | if (now - last_activity).days > 14: 553 | metrics['stale_cards'].append({ 554 | 'name': card['name'], 555 | 'last_activity': card['dateLastActivity'], 556 | 'list': lst['name'] 557 | }) 558 | metrics['health_score'] -= 1 559 | 560 | # Calculate percentages 561 | if metrics['total_cards'] > 0: 562 | for list_name in metrics['lists']: 563 | metrics['lists'][list_name]['percentage'] = ( 564 | metrics['lists'][list_name]['count'] / metrics['total_cards'] * 100 565 | ) 566 | 567 | # Ensure health score doesn't go below 0 568 | metrics['health_score'] = max(0, metrics['health_score']) 569 | 570 | return metrics 571 | 572 | 573 | # Example Usage 574 | async def main(): 575 | """Example usage of the Trello automation classes.""" 576 | 577 | # Initialize client 578 | trello = TrelloMCPClient() 579 | 580 | # Task Management Example 581 | task_manager = TaskManager(trello) 582 | await task_manager.initialize_priority_lists() 583 | 584 | # Create a high-priority task 585 | critical_task = Task( 586 | name="Fix Production Database Issue", 587 | description="Database connection pool exhaustion causing service outages", 588 | priority=Priority.CRITICAL, 589 | assignee="devops-team", 590 | labels=["production", "database", "incident"], 591 | checklist_items=[ 592 | "Identify root cause", 593 | "Implement fix", 594 | "Test in staging", 595 | "Deploy to production", 596 | "Monitor for 24 hours" 597 | ] 598 | ) 599 | 600 | card = await task_manager.create_task(critical_task) 601 | print(f"Created critical task: {card['id']}") 602 | 603 | # Kanban Automation Example 604 | kanban = KanbanAutomation(trello) 605 | await kanban.setup_kanban_board() 606 | 607 | # Move card through workflow 608 | await kanban.move_card_to_stage( 609 | card['id'], 610 | "In Progress", 611 | "Started investigation into database connection issues" 612 | ) 613 | 614 | # Apply WIP limits 615 | await kanban.apply_wip_limits({ 616 | "In Progress": 3, 617 | "Review": 2, 618 | "Testing": 2 619 | }) 620 | 621 | # Analytics Example 622 | analytics = ProjectAnalytics(trello) 623 | 624 | # Generate health metrics 625 | health = await analytics.generate_health_metrics() 626 | print(f"Project Health Score: {health['health_score']}/100") 627 | print(f"Overdue Cards: {len(health['overdue_cards'])}") 628 | print(f"Blocked Cards: {len(health['blocked_cards'])}") 629 | 630 | # Generate burndown data 631 | burndown = await analytics.generate_burndown_data() 632 | print(f"Sprint Progress: {burndown['completed_points']}/{burndown['total_points']} points") 633 | 634 | # Retrospective Example 635 | retro = RetroManager(trello) 636 | retro_lists = await retro.create_retro_board(sprint_number=23) 637 | 638 | # Add retrospective items 639 | await retro.add_retro_item( 640 | "What Went Well", 641 | "Completed all planned features", 642 | "The team delivered all committed user stories for the sprint", 643 | votes=8 644 | ) 645 | 646 | await retro.add_retro_item( 647 | "What Could Be Improved", 648 | "Better estimation of complex tasks", 649 | "Several tasks took longer than estimated, causing end-of-sprint rush", 650 | votes=5 651 | ) 652 | 653 | 654 | if __name__ == "__main__": 655 | import asyncio 656 | asyncio.run(main()) --------------------------------------------------------------------------------