diff --git a/packages/mcp-server/QUICK_TEST.md b/packages/mcp-server/QUICK_TEST.md new file mode 100644 index 00000000..d51fd7d7 --- /dev/null +++ b/packages/mcp-server/QUICK_TEST.md @@ -0,0 +1,121 @@ +# šŸš€ Quick Test Reference - MCP Server v0.5.0 + +## Setup (Do Once) +```bash +cd /Users/vinnycarpenter/Projects/gsd-taskmanager/packages/mcp-server +npm run build +``` + +**Update Claude Desktop Config:** +`~/Library/Application Support/Claude/claude_desktop_config.json` +```json +{ + "mcpServers": { + "gsd-taskmanager": { + "command": "node", + "args": ["/Users/vinnycarpenter/Projects/gsd-taskmanager/packages/mcp-server/dist/index.js"], + "env": { + "GSD_API_URL": "https://gsd.vinny.dev", + "GSD_AUTH_TOKEN": "YOUR_TOKEN", + "GSD_ENCRYPTION_PASSPHRASE": "YOUR_PASSPHRASE" + } + } + } +} +``` + +**Restart Claude Desktop** (Cmd+Q, then reopen) + +--- + +## 5-Minute Smoke Test + +### 1. Server Check +``` +"Validate my GSD MCP configuration" +``` +āœ… All 3 checks pass (API, Auth, Encryption) + +### 2. Read Test +``` +"List all my tasks" +``` +āœ… Returns tasks with full details + +### 3. Analytics Test +``` +"Show my productivity metrics" +``` +āœ… Returns completions, streaks, rates + +### 4. Write Test (Creates & Deletes) +``` +"Create a test task: 'MCP v0.5.0 Test', urgent=true, important=false" +``` +āœ… Task created, note the ID + +``` +"Delete the task we just created" +``` +āœ… Task deleted + +### 5. Prompt Test +``` +"Run the daily-standup prompt" +``` +āœ… Multi-tool report generated + +--- + +## Common Test Prompts + +### Configuration +- "Validate my setup" +- "Show me all available tools" +- "Get help with analytics tools" + +### Read Operations +- "List my urgent and important tasks" +- "Show tasks tagged #work" +- "Search for tasks about 'meeting'" +- "Get details for task ID abc123" + +### Analytics +- "What's my productivity this week?" +- "Analyze my quadrant distribution" +- "Show tag completion rates" +- "What deadlines are coming up?" + +### Write Operations (āš ļø Modifies data) +- "Create task: 'Test', urgent=true, important=true" +- "Update task abc123 to make it not urgent" +- "Mark task abc123 complete" +- "Delete task abc123" + +### Prompts +- "Run daily-standup" +- "Run focus-mode" +- "Run weekly-review" + +--- + +## Quick Checklist + +- [ ] Config validates successfully +- [ ] Can list tasks +- [ ] Can get analytics +- [ ] Can create/delete test task +- [ ] Prompts work +- [ ] No errors in Claude Desktop + +**Result:** ___________ + +--- + +## Rollback If Needed + +```bash +cp ~/Library/Application\ Support/Claude/claude_desktop_config.json.backup ~/Library/Application\ Support/Claude/claude_desktop_config.json +``` + +Restart Claude Desktop diff --git a/packages/mcp-server/TEST_PLAN.md b/packages/mcp-server/TEST_PLAN.md index bf81dd8c..066e7587 100644 --- a/packages/mcp-server/TEST_PLAN.md +++ b/packages/mcp-server/TEST_PLAN.md @@ -1,213 +1,320 @@ -# MCP Server Write Operations Test Plan +# MCP Server v0.5.0 - Pre-Deployment Test Plan -## Prerequisites -āœ… Built: v0.4.1 with schema fixes -āœ… Config: Claude Desktop pointing to local build -āœ… Auth: Token and encryption passphrase configured +## Overview +Test the refactored MCP server (v0.5.0) in Claude Desktop to ensure all functionality works before committing to git and publishing to npm. -## Setup +**Test Duration:** ~15-20 minutes +**Test Date:** ___________ +**Tester:** ___________ -1. **Restart Claude Desktop** to load the new v0.4.1 build - - Quit Claude Desktop completely - - Reopen Claude Desktop - - Wait for MCP server to initialize +--- -2. **Verify MCP server is running** - - Start a new conversation - - Ask: "Can you list my tasks using the gsd-tasks MCP server?" - - Should see your existing tasks (read operation test) +## Pre-Test Setup -## Test 1: create_task āœ“ +### 1. Build the New Version +```bash +cd /Users/vinnycarpenter/Projects/gsd-taskmanager/packages/mcp-server +npm run build +``` +āœ… Build successful +āœ… No TypeScript errors -**Test Case:** Create a simple task in Q1 (Urgent & Important) +### 2. Backup Current Claude Desktop Config +```bash +# macOS +cp ~/Library/Application\ Support/Claude/claude_desktop_config.json ~/Library/Application\ Support/Claude/claude_desktop_config.json.backup -**Prompt to Claude:** +# Verify backup exists +ls -l ~/Library/Application\ Support/Claude/claude_desktop_config.json.backup ``` -Using the gsd-tasks MCP server, create a new task: -- Title: "Test MCP Write Operations" -- Description: "Testing v0.4.1 schema fixes" -- Urgent: true -- Important: true -- Tags: ["testing", "mcp"] +āœ… Backup created: ___________ + +### 3. Update Claude Desktop Config to Use Local Build +Edit: `~/Library/Application Support/Claude/claude_desktop_config.json` + +**Replace the gsd-mcp-server entry with:** +```json +{ + "mcpServers": { + "gsd-taskmanager": { + "command": "node", + "args": [ + "/Users/vinnycarpenter/Projects/gsd-taskmanager/packages/mcp-server/dist/index.js" + ], + "env": { + "GSD_API_URL": "https://gsd.vinny.dev", + "GSD_AUTH_TOKEN": "YOUR_TOKEN_HERE", + "GSD_ENCRYPTION_PASSPHRASE": "YOUR_PASSPHRASE_HERE" + } + } + } +} ``` -**Expected Result:** -- āœ… Claude calls `create_task` tool -- āœ… Returns success message with task details -- āœ… Task shows quadrantId: "urgent-important" -- āœ… No 400 error from Worker +āœ… Config updated +āœ… Token and passphrase configured -**Verification:** -- Ask Claude: "List tasks with tag 'testing'" -- Should see the newly created task +### 4. Restart Claude Desktop +- Quit Claude Desktop completely (Cmd+Q) +- Reopen Claude Desktop +- Wait for MCP servers to initialize (~5 seconds) -## Test 2: update_task āœ“ +āœ… Claude Desktop restarted +āœ… No error messages in Claude Desktop logs -**Test Case:** Update the task we just created +--- -**Prompt to Claude:** -``` -Using the gsd-tasks MCP server, update the task "Test MCP Write Operations": -- Add a due date for tomorrow -- Add a new tag "bug-fix" -- Change description to "Testing v0.4.1 schema fixes - UPDATED" -``` +## Quick Test Suite (Core Functionality - 10 min) -**Expected Result:** -- āœ… Claude calls `update_task` tool -- āœ… Returns updated task with new values -- āœ… Tags now include "testing", "mcp", "bug-fix" -- āœ… Due date is set +### āœ… Test 1: Server Initialization +**Prompt:** "Use validate_config to check my GSD MCP server setup" +- ⬜ API Connectivity: success +- ⬜ Authentication: success +- ⬜ Encryption: success -**Verification:** -- Ask Claude: "Get the task 'Test MCP Write Operations' and show me all details" -- Verify all updates are present +### āœ… Test 2: Read Operations +**Prompt:** "List all my tasks" +- ⬜ Returns task list with all fields +- ⬜ No errors -## Test 3: complete_task āœ“ +### āœ… Test 3: Analytics +**Prompt:** "Show me my productivity metrics" +- ⬜ Returns metrics (completions, streaks, rates) +- ⬜ No errors -**Test Case:** Mark task as completed +### āœ… Test 4: Write Operation (āš ļø Creates test task) +**Prompt:** "Create a test task: Title 'MCP Test v0.5.0', urgent: true, important: false" +- ⬜ Task created successfully +- ⬜ Returns task ID: ___________ -**Prompt to Claude:** -``` -Using the gsd-tasks MCP server, mark the task "Test MCP Write Operations" as completed. -``` +**Prompt:** "Delete the task we just created" +- ⬜ Task deleted successfully -**Expected Result:** -- āœ… Claude calls `complete_task` tool -- āœ… Returns task with completed: true -- āœ… Task still exists with all data intact +### āœ… Test 5: Prompts +**Prompt:** "Run the daily-standup prompt" +- ⬜ Generates daily report +- ⬜ Uses multiple tools +- ⬜ Formatted output -**Verification:** -- Ask Claude: "Show me all completed tasks" -- Should see the test task in completed state +--- -## Test 4: Create Multiple Tasks (for Bulk Test) +## Full Test Suite (Comprehensive - 20 min) -**Prompt to Claude:** -``` -Using the gsd-tasks MCP server, create 3 new tasks: -1. "Bulk Test Task 1" - Urgent and Important -2. "Bulk Test Task 2" - Not Urgent but Important -3. "Bulk Test Task 3" - Urgent but Not Important -``` +### Phase 1: Configuration & Help -**Expected Result:** -- āœ… Claude creates 3 tasks successfully -- āœ… Each in correct quadrant +#### 1.1 Validate Config +**Prompt:** "Validate my GSD MCP configuration" +- ⬜ Pass ⬜ Fail +- API: ⬜ success Auth: ⬜ success Encryption: ⬜ success -## Test 5: bulk_update_tasks āœ“ +#### 1.2 Get Help +**Prompt:** "Show me all available GSD tools" +- ⬜ Pass ⬜ Fail +- Shows 18 tools organized by category -**Test Case 5a:** Complete multiple tasks at once +--- -**Prompt to Claude:** -``` -Using the gsd-tasks MCP server, mark all 3 "Bulk Test Task" tasks as completed in a single bulk operation. -``` +### Phase 2: Read Tools (Non-Destructive) -**Expected Result:** -- āœ… Claude calls `bulk_update_tasks` with type: 'complete' -- āœ… Returns count of updated tasks (3) -- āœ… All 3 tasks now completed +#### 2.1 Sync Status +**Prompt:** "What's my sync status?" +- ⬜ Pass ⬜ Fail +- Shows device count, last sync -**Test Case 5b:** Add tags to multiple tasks +#### 2.2 List Devices +**Prompt:** "Show my registered devices" +- ⬜ Pass ⬜ Fail +- Lists all devices -**Prompt to Claude:** -``` -Using the gsd-tasks MCP server, add the tag "bulk-test" to all 3 "Bulk Test Task" tasks in one operation. -``` +#### 2.3 Task Stats +**Prompt:** "Get my task statistics" +- ⬜ Pass ⬜ Fail +- Shows counts (no decryption needed) -**Expected Result:** -- āœ… Claude calls `bulk_update_tasks` with type: 'add_tags' -- āœ… All 3 tasks now have "bulk-test" tag +#### 2.4 List All Tasks +**Prompt:** "List all my tasks" +- ⬜ Pass ⬜ Fail +- Count: ___________ -**Test Case 5c:** Move tasks to different quadrant +#### 2.5 Filter Tasks +**Prompt:** "Show only urgent and important tasks" +- ⬜ Pass ⬜ Fail +- Correct Q1 filtering -**Prompt to Claude:** -``` -Using the gsd-tasks MCP server, move all "Bulk Test Task" tasks to Q4 (Not Urgent, Not Important) in one operation. -``` +#### 2.6 Get Single Task +**Prompt:** "Get details for task ID [use ID from 2.4]" +- ⬜ Pass ⬜ Fail +- Full task details returned -**Expected Result:** -- āœ… Claude calls `bulk_update_tasks` with type: 'move_quadrant' -- āœ… All tasks now in quadrant "not-urgent-not-important" +#### 2.7 Search Tasks +**Prompt:** "Search for tasks about 'project'" +- ⬜ Pass ⬜ Fail +- Matches: ___________ -## Test 6: delete_task āœ“ +--- -**Test Case:** Delete individual task +### Phase 3: Analytics Tools -**Prompt to Claude:** -``` -Using the gsd-tasks MCP server, delete the task "Test MCP Write Operations". -``` +#### 3.1 Productivity Metrics +**Prompt:** "Show productivity metrics" +- ⬜ Pass ⬜ Fail +- Completions, streaks, rates all present -**Expected Result:** -- āœ… Claude calls `delete_task` tool -- āœ… Returns success message -- āœ… Task no longer appears in list +#### 3.2 Quadrant Analysis +**Prompt:** "Analyze quadrant distribution" +- ⬜ Pass ⬜ Fail +- All 4 quadrants analyzed -**Verification:** -- Ask Claude: "Search for 'Test MCP Write Operations'" -- Should return no results +#### 3.3 Tag Analytics +**Prompt:** "Show tag statistics" +- ⬜ Pass ⬜ Fail +- Tags: ___________ -## Test 7: bulk_update_tasks (Delete Multiple) āœ“ +#### 3.4 Upcoming Deadlines +**Prompt:** "What are my upcoming deadlines?" +- ⬜ Pass ⬜ Fail +- Grouped: overdue, today, this week -**Prompt to Claude:** -``` -Using the gsd-tasks MCP server, delete all 3 "Bulk Test Task" tasks in a single bulk operation. -``` +#### 3.5 Task Insights +**Prompt:** "Give me task insights summary" +- ⬜ Pass ⬜ Fail +- AI-friendly summary format -**Expected Result:** -- āœ… Claude calls `bulk_update_tasks` with type: 'delete' -- āœ… Returns count of deleted tasks (3) -- āœ… All test tasks removed +--- + +### Phase 4: Write Tools (āš ļø Modifies Data) + +#### 4.1 Create Task +**Prompt:** "Create task: 'MCP Test v0.5.0', urgent=true, important=false, tags=['#test']" +- ⬜ Pass ⬜ Fail +- Task ID: ___________ + +#### 4.2 Update Task +**Prompt:** "Update [ID from 4.1] to make it not urgent" +- ⬜ Pass ⬜ Fail +- Quadrant changed -## Test 8: Verify Sync to Worker šŸ” +#### 4.3 Complete Task +**Prompt:** "Mark [ID from 4.1] complete" +- ⬜ Pass ⬜ Fail +- Status updated -**Check Worker Database:** +#### 4.4 Delete Task +**Prompt:** "Delete [ID from 4.1]" +- ⬜ Pass ⬜ Fail +- Task removed + +#### 4.5 Bulk Update (Optional) +**Prompt:** "Create 3 test tasks, then complete all at once" +- ⬜ Pass ⬜ Skip ⬜ Fail + +--- -Option A - Use Worker logs: +### Phase 5: Prompts + +#### 5.1 Daily Standup +**Prompt:** "Run daily-standup prompt" +- ⬜ Pass ⬜ Fail +- Concise daily report + +#### 5.2 Focus Mode +**Prompt:** "Run focus-mode prompt" +- ⬜ Pass ⬜ Fail +- Q1 tasks prioritized + +#### 5.3 Weekly Review +**Prompt:** "Run weekly-review prompt" +- ⬜ Pass ⬜ Fail +- Professional report format + +--- + +### Phase 6: Error Handling + +#### 6.1 Invalid Task ID +**Prompt:** "Get task 'invalid-id-12345'" +- ⬜ Pass ⬜ Fail +- Graceful error message + +--- + +### Phase 7: Performance + +#### 7.1 Sequential Requests +**Prompt:** "List tasks, show metrics, get tag analytics" +- ⬜ Pass ⬜ Fail +- All complete, time: ___________ + +--- + +## Results Summary + +**Tests Run:** ___ / 30 +**Passed:** ___ +**Failed:** ___ +**Skipped:** ___ + +### Critical Issues +1. ___________ +2. ___________ + +### Decision +- ⬜ āœ… DEPLOY - All critical tests passed +- ⬜ āš ļø DEPLOY WITH CAUTION - Minor issues only +- ⬜ āŒ DO NOT DEPLOY - Critical failures + +--- + +## Post-Test Actions + +### If PASS → Commit & Publish ```bash -cd /Users/vinnycarpenter/Projects/gsd-taskmanager/worker -timeout 30 npx wrangler tail --env production --format pretty -``` -Then perform a create operation and watch for logs. +cd /Users/vinnycarpenter/Projects/gsd-taskmanager/packages/mcp-server + +# Publish to npm +npm publish -Option B - Use webapp to verify: -1. Open https://gsd.vinny.dev -2. Check if created tasks appear -3. Verify updates, completions, deletions synced +# Then commit refactoring to git +cd ../.. +git add packages/mcp-server +git commit -m "refactor(mcp-server): Split index.ts into modular architecture (v0.5.0) -Option C - Use list_tasks MCP tool: +- Reduced main entry from 1,155 lines to 59 lines (95% reduction) +- Split into 14 focused modules (schemas, handlers, prompts, server) +- Added comprehensive test suite (32 tests, Vitest) +- All files now comply with 300-line guideline +- 100% backward compatible, no breaking changes + +Closes #" ``` -Ask Claude: "List all my tasks and show me the last 5 created" + +### If FAIL → Rollback +```bash +# Restore config backup +cp ~/Library/Application\ Support/Claude/claude_desktop_config.json.backup ~/Library/Application\ Support/Claude/claude_desktop_config.json + +# Restart Claude Desktop +# Investigate issues before deploying ``` -Verify the tasks we created are there with correct data. -## Success Criteria āœ… +--- -All tests pass if: -- [ ] No 400 errors from Worker API -- [ ] All operations return success messages -- [ ] Tasks persist across operations -- [ ] Changes visible in webapp (if checked) -- [ ] Vector clock handled by server (no errors) -- [ ] Encryption/decryption works both ways +## Notes -## Failure Scenarios āŒ +**What Worked:** +___________ -If any operation fails, capture: -1. The exact error message from Claude -2. Network error details (if shown) -3. Which operation failed (create/update/delete/bulk) -4. Worker logs (if accessible) +**Issues Found:** +___________ -## Notes +**Performance:** +___________ -- Testing in **production** Worker (gsd-sync-worker-production.vscarpenter.workers.dev) -- Using real auth token and encryption passphrase -- All created test tasks will persist unless deleted -- Can safely run tests multiple times (cleanup with delete operations) +**Recommendation:** +___________ --- -**After Testing:** Report results back to decide whether to publish v0.4.1 to npm. +**Tester:** ___________ +**Date:** ___________ +**Sign-off:** ⬜ Approved ⬜ Rejected diff --git a/packages/mcp-server/package-lock.json b/packages/mcp-server/package-lock.json index 427b5b5a..07cd2ae9 100644 --- a/packages/mcp-server/package-lock.json +++ b/packages/mcp-server/package-lock.json @@ -17,12 +17,463 @@ }, "devDependencies": { "@types/node": "^22.10.2", - "typescript": "^5.7.3" + "@vitest/ui": "^4.0.4", + "typescript": "^5.7.3", + "vitest": "^4.0.4" }, "engines": { "node": ">=18.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.20.2.tgz", @@ -46,16 +497,498 @@ "node": ">=18" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.18.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.12.tgz", "integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@vitest/expect": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.4.tgz", + "integrity": "sha512-0ioMscWJtfpyH7+P82sGpAi3Si30OVV73jD+tEqXm5+rIx9LgnfdaOn45uaFkKOncABi/PHL00Yn0oW/wK4cXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.4", + "@vitest/utils": "4.0.4", + "chai": "^6.0.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.4.tgz", + "integrity": "sha512-UTtKgpjWj+pvn3lUM55nSg34098obGhSHH+KlJcXesky8b5wCUgg7s60epxrS6yAG8slZ9W8T9jGWg4PisMf5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.19" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.4.tgz", + "integrity": "sha512-lHI2rbyrLVSd1TiHGJYyEtbOBo2SDndIsN3qY4o4xe2pBxoJLD6IICghNCvD7P+BFin6jeyHXiUICXqgl6vEaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.4.tgz", + "integrity": "sha512-99EDqiCkncCmvIZj3qJXBZbyoQ35ghOwVWNnQ5nj0Hnsv4Qm40HmrMJrceewjLVvsxV/JSU4qyx2CGcfMBmXJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.4.tgz", + "integrity": "sha512-XICqf5Gi4648FGoBIeRgnHWSNDp+7R5tpclGosFaUUFzY6SfcpsfHNMnC7oDu/iOLBxYfxVzaQpylEvpgii3zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.4", + "magic-string": "^0.30.19", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.4.tgz", + "integrity": "sha512-G9L13AFyYECo40QG7E07EdYnZZYCKMTSp83p9W8Vwed0IyCG1GnpDLxObkx8uOGPXfDpdeVf24P1Yka8/q1s9g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.4.tgz", + "integrity": "sha512-CmuFQLKw5SaLU/Flo8dLiQw2P2ONguhjfhBL9AYkTeDZPToE8laGvObXqRzS5G+4RD4SgWcI1USAmGxMVIqT0g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/utils": "4.0.4", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.0.4" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.4.tgz", + "integrity": "sha512-4bJLmSvZLyVbNsYFRpPYdJViG9jZyRvMZ35IF4ymXbRZoS+ycYghmwTGiscTXduUg2lgKK7POWIyXJNute1hjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.4", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -85,6 +1018,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -143,6 +1086,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chai": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", + "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -282,6 +1235,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -294,12 +1254,64 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -330,6 +1342,16 @@ "node": ">=18.0.0" } }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -400,6 +1422,31 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, "node_modules/finalhandler": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", @@ -417,6 +1464,13 @@ "node": ">= 0.8" } }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -435,6 +1489,21 @@ "node": ">= 0.8" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -587,6 +1656,16 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -638,12 +1717,41 @@ "node": ">= 0.6" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -723,6 +1831,33 @@ "url": "https://opencollective.com/express" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkce-challenge": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", @@ -732,6 +1867,35 @@ "node": ">=16.20.0" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -809,6 +1973,48 @@ "url": "https://opencollective.com/express" } }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -987,6 +2193,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -996,6 +2241,54 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1005,6 +2298,16 @@ "node": ">=0.6" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -1067,6 +2370,161 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.4.tgz", + "integrity": "sha512-hV31h0/bGbtmDQc0KqaxsTO1v4ZQeF8ojDFuy4sZhFadwAqqvJA0LDw68QUocctI5EDpFMql/jVWKuPYHIf2Ew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/expect": "4.0.4", + "@vitest/mocker": "4.0.4", + "@vitest/pretty-format": "4.0.4", + "@vitest/runner": "4.0.4", + "@vitest/snapshot": "4.0.4", + "@vitest/spy": "4.0.4", + "@vitest/utils": "4.0.4", + "debug": "^4.4.3", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.19", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.4", + "@vitest/browser-preview": "4.0.4", + "@vitest/browser-webdriverio": "4.0.4", + "@vitest/ui": "4.0.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1082,6 +2540,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index ddb5625f..59bfdadb 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "gsd-mcp-server", - "version": "0.4.7", + "version": "0.5.0", "description": "MCP server for GSD Task Manager - full task management with create/update/delete, analytics, interactive setup, and validation", "type": "module", "main": "dist/index.js", @@ -17,6 +17,10 @@ "build": "tsc", "dev": "tsc --watch", "start": "node dist/index.js", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", "prepublishOnly": "npm run build" }, "keywords": [ @@ -47,7 +51,9 @@ }, "devDependencies": { "@types/node": "^22.10.2", - "typescript": "^5.7.3" + "@vitest/ui": "^4.0.4", + "typescript": "^5.7.3", + "vitest": "^4.0.4" }, "engines": { "node": ">=18.0.0" diff --git a/packages/mcp-server/src/__tests__/tools/schemas.test.ts b/packages/mcp-server/src/__tests__/tools/schemas.test.ts new file mode 100644 index 00000000..45ed5572 --- /dev/null +++ b/packages/mcp-server/src/__tests__/tools/schemas.test.ts @@ -0,0 +1,262 @@ +import { describe, it, expect } from 'vitest'; +import { + allTools, + readTools, + writeTools, + analyticsTools, + systemTools, + getSyncStatusTool, + listDevicesTool, + getTaskStatsTool, + listTasksTool, + getTaskTool, + searchTasksTool, + getProductivityMetricsTool, + getQuadrantAnalysisTool, + getTagAnalyticsTool, + getUpcomingDeadlinesTool, + getTaskInsightsTool, + createTaskTool, + updateTaskTool, + completeTaskTool, + deleteTaskTool, + bulkUpdateTasksTool, + validateConfigTool, + getHelpTool, +} from '../../tools/schemas/index.js'; + +describe('Tool Schemas', () => { + describe('Schema Count Validation', () => { + it('should have exactly 18 tools total', () => { + expect(allTools).toHaveLength(18); + }); + + it('should have 6 read tools', () => { + expect(readTools).toHaveLength(6); + }); + + it('should have 5 write tools', () => { + expect(writeTools).toHaveLength(5); + }); + + it('should have 5 analytics tools', () => { + expect(analyticsTools).toHaveLength(5); + }); + + it('should have 2 system tools', () => { + expect(systemTools).toHaveLength(2); + }); + }); + + describe('Schema Structure Validation', () => { + it('each tool should have required MCP tool properties', () => { + allTools.forEach((tool) => { + expect(tool).toHaveProperty('name'); + expect(tool).toHaveProperty('description'); + expect(tool).toHaveProperty('inputSchema'); + expect(typeof tool.name).toBe('string'); + expect(typeof tool.description).toBe('string'); + expect(typeof tool.inputSchema).toBe('object'); + }); + }); + + it('each tool name should be unique', () => { + const names = allTools.map((t) => t.name); + const uniqueNames = new Set(names); + expect(uniqueNames.size).toBe(names.length); + }); + + it('each tool should have non-empty description', () => { + allTools.forEach((tool) => { + expect(tool.description).toBeDefined(); + expect(tool.description!.length).toBeGreaterThan(0); + }); + }); + + it('each inputSchema should have type and properties', () => { + allTools.forEach((tool) => { + expect(tool.inputSchema.type).toBe('object'); + expect(tool.inputSchema).toHaveProperty('properties'); + expect(tool.inputSchema).toHaveProperty('required'); + expect(Array.isArray(tool.inputSchema.required)).toBe(true); + }); + }); + }); + + describe('Read Tools', () => { + it('get_sync_status should have no required parameters', () => { + expect(getSyncStatusTool.name).toBe('get_sync_status'); + expect(getSyncStatusTool.inputSchema.required).toHaveLength(0); + }); + + it('list_devices should have no required parameters', () => { + expect(listDevicesTool.name).toBe('list_devices'); + expect(listDevicesTool.inputSchema.required).toHaveLength(0); + }); + + it('get_task_stats should have no required parameters', () => { + expect(getTaskStatsTool.name).toBe('get_task_stats'); + expect(getTaskStatsTool.inputSchema.required).toHaveLength(0); + }); + + it('list_tasks should have optional filter parameters', () => { + expect(listTasksTool.name).toBe('list_tasks'); + expect(listTasksTool.inputSchema.required).toHaveLength(0); + expect(listTasksTool.inputSchema.properties).toHaveProperty('quadrant'); + expect(listTasksTool.inputSchema.properties).toHaveProperty('completed'); + expect(listTasksTool.inputSchema.properties).toHaveProperty('tags'); + }); + + it('get_task should require taskId', () => { + expect(getTaskTool.name).toBe('get_task'); + expect(getTaskTool.inputSchema.required).toContain('taskId'); + expect(getTaskTool.inputSchema.properties).toHaveProperty('taskId'); + }); + + it('search_tasks should require query', () => { + expect(searchTasksTool.name).toBe('search_tasks'); + expect(searchTasksTool.inputSchema.required).toContain('query'); + expect(searchTasksTool.inputSchema.properties).toHaveProperty('query'); + }); + }); + + describe('Analytics Tools', () => { + it('all analytics tools should mention encryption requirement', () => { + analyticsTools.forEach((tool) => { + expect(tool.description).toMatch(/GSD_ENCRYPTION_PASSPHRASE/i); + }); + }); + + it('get_productivity_metrics should have no required parameters', () => { + expect(getProductivityMetricsTool.name).toBe('get_productivity_metrics'); + expect(getProductivityMetricsTool.inputSchema.required).toHaveLength(0); + }); + + it('get_tag_analytics should have optional limit parameter', () => { + expect(getTagAnalyticsTool.name).toBe('get_tag_analytics'); + expect(getTagAnalyticsTool.inputSchema.required).toHaveLength(0); + expect(getTagAnalyticsTool.inputSchema.properties).toHaveProperty('limit'); + }); + }); + + describe('Write Tools', () => { + it('all write tools should mention encryption requirement', () => { + writeTools.forEach((tool) => { + expect(tool.description).toMatch(/GSD_ENCRYPTION_PASSPHRASE/i); + }); + }); + + it('create_task should require title, urgent, and important', () => { + expect(createTaskTool.name).toBe('create_task'); + expect(createTaskTool.inputSchema.required).toContain('title'); + expect(createTaskTool.inputSchema.required).toContain('urgent'); + expect(createTaskTool.inputSchema.required).toContain('important'); + expect(createTaskTool.inputSchema.required).toHaveLength(3); + }); + + it('create_task should have all task properties', () => { + const properties = createTaskTool.inputSchema.properties; + expect(properties).toHaveProperty('title'); + expect(properties).toHaveProperty('description'); + expect(properties).toHaveProperty('urgent'); + expect(properties).toHaveProperty('important'); + expect(properties).toHaveProperty('dueDate'); + expect(properties).toHaveProperty('tags'); + expect(properties).toHaveProperty('subtasks'); + expect(properties).toHaveProperty('recurrence'); + expect(properties).toHaveProperty('dependencies'); + }); + + it('update_task should only require id', () => { + expect(updateTaskTool.name).toBe('update_task'); + expect(updateTaskTool.inputSchema.required).toContain('id'); + expect(updateTaskTool.inputSchema.required).toHaveLength(1); + }); + + it('complete_task should require id and completed', () => { + expect(completeTaskTool.name).toBe('complete_task'); + expect(completeTaskTool.inputSchema.required).toContain('id'); + expect(completeTaskTool.inputSchema.required).toContain('completed'); + expect(completeTaskTool.inputSchema.required).toHaveLength(2); + }); + + it('delete_task should require id', () => { + expect(deleteTaskTool.name).toBe('delete_task'); + expect(deleteTaskTool.inputSchema.required).toContain('id'); + expect(deleteTaskTool.inputSchema.required).toHaveLength(1); + }); + + it('bulk_update_tasks should require taskIds and operation', () => { + expect(bulkUpdateTasksTool.name).toBe('bulk_update_tasks'); + expect(bulkUpdateTasksTool.inputSchema.required).toContain('taskIds'); + expect(bulkUpdateTasksTool.inputSchema.required).toContain('operation'); + expect(bulkUpdateTasksTool.inputSchema.required).toHaveLength(2); + }); + + it('bulk_update_tasks should have operation type enum', () => { + const operationProp = bulkUpdateTasksTool.inputSchema.properties?.operation as any; + expect(operationProp).toBeDefined(); + expect(operationProp.properties.type).toHaveProperty('enum'); + const typeEnum = operationProp.properties.type.enum; + expect(typeEnum).toContain('complete'); + expect(typeEnum).toContain('move_quadrant'); + expect(typeEnum).toContain('add_tags'); + expect(typeEnum).toContain('remove_tags'); + expect(typeEnum).toContain('set_due_date'); + expect(typeEnum).toContain('delete'); + }); + }); + + describe('System Tools', () => { + it('validate_config should have no required parameters', () => { + expect(validateConfigTool.name).toBe('validate_config'); + expect(validateConfigTool.inputSchema.required).toHaveLength(0); + }); + + it('get_help should have optional topic parameter', () => { + expect(getHelpTool.name).toBe('get_help'); + expect(getHelpTool.inputSchema.required).toHaveLength(0); + expect(getHelpTool.inputSchema.properties).toHaveProperty('topic'); + }); + + it('get_help topic should have valid enum values', () => { + const topicProp = getHelpTool.inputSchema.properties?.topic as any; + expect(topicProp).toHaveProperty('enum'); + expect(topicProp.enum).toContain('tools'); + expect(topicProp.enum).toContain('analytics'); + expect(topicProp.enum).toContain('setup'); + expect(topicProp.enum).toContain('examples'); + expect(topicProp.enum).toContain('troubleshooting'); + }); + }); + + describe('Schema Consistency', () => { + it('quadrant enum should be consistent across tools', () => { + const listTasksQuadrant = (listTasksTool.inputSchema.properties?.quadrant as any)?.enum; + expect(listTasksQuadrant).toEqual([ + 'urgent-important', + 'not-urgent-important', + 'urgent-not-important', + 'not-urgent-not-important', + ]); + }); + + it('recurrence enum should be consistent across tools', () => { + const createRecurrence = (createTaskTool.inputSchema.properties?.recurrence as any)?.enum; + const updateRecurrence = (updateTaskTool.inputSchema.properties?.recurrence as any)?.enum; + expect(createRecurrence).toEqual(['none', 'daily', 'weekly', 'monthly']); + expect(updateRecurrence).toEqual(['none', 'daily', 'weekly', 'monthly']); + }); + + it('subtasks structure should be consistent across create and update', () => { + const createSubtasks = createTaskTool.inputSchema.properties?.subtasks as any; + const updateSubtasks = updateTaskTool.inputSchema.properties?.subtasks as any; + + expect(createSubtasks.items.properties).toHaveProperty('title'); + expect(createSubtasks.items.properties).toHaveProperty('completed'); + expect(updateSubtasks.items.properties).toHaveProperty('id'); + expect(updateSubtasks.items.properties).toHaveProperty('title'); + expect(updateSubtasks.items.properties).toHaveProperty('completed'); + }); + }); +}); diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 5bc91802..33c037c1 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -1,473 +1,16 @@ #!/usr/bin/env node -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import { - CallToolRequestSchema, - ListToolsRequestSchema, - ListPromptsRequestSchema, - GetPromptRequestSchema, - Tool, - Prompt, -} from '@modelcontextprotocol/sdk/types.js'; -import { z } from 'zod'; -import { - getSyncStatus, - listDevices, - getTaskStats, - listTasks, - getTask, - searchTasks, - type GsdConfig, -} from './tools.js'; -import { - calculateMetrics, - getQuadrantPerformance, - getUpcomingDeadlines, - generateInsightsSummary, -} from './analytics.js'; import { parseCLIArgs, showHelp, runSetupWizard, runValidation } from './cli.js'; -import { - createTask, - updateTask, - completeTask, - deleteTask, - bulkUpdateTasks, - type CreateTaskInput, - type UpdateTaskInput, - type BulkOperation, -} from './write-ops.js'; - -// Configuration schema -const configSchema = z.object({ - apiBaseUrl: z.string().url(), - authToken: z.string().min(1), - encryptionPassphrase: z.string().optional(), // Optional: for decrypting tasks -}); - -// Tool definitions -const tools: Tool[] = [ - { - name: 'get_sync_status', - description: - 'Get sync status for GSD tasks including last sync time, device count, storage usage, and conflict count. Useful for checking overall sync health.', - inputSchema: { - type: 'object', - properties: {}, - required: [], - }, - }, - { - name: 'list_devices', - description: - 'List all registered devices for the authenticated user. Shows device names, last seen timestamps, and active status. Useful for managing connected devices.', - inputSchema: { - type: 'object', - properties: {}, - required: [], - }, - }, - { - name: 'get_task_stats', - description: - 'Get statistics about tasks including total count, active count, deleted count, and last update timestamp. Provides high-level overview without accessing encrypted task content.', - inputSchema: { - type: 'object', - properties: {}, - required: [], - }, - }, - { - name: 'list_tasks', - description: - 'List all decrypted tasks. Requires GSD_ENCRYPTION_PASSPHRASE to be set. Returns full task details including titles, descriptions, quadrants, tags, subtasks, and dependencies. Optionally filter by quadrant, completion status, or tags.', - inputSchema: { - type: 'object', - properties: { - quadrant: { - type: 'string', - description: - 'Filter by quadrant ID (urgent-important, not-urgent-important, urgent-not-important, not-urgent-not-important)', - enum: [ - 'urgent-important', - 'not-urgent-important', - 'urgent-not-important', - 'not-urgent-not-important', - ], - }, - completed: { - type: 'boolean', - description: 'Filter by completion status (true for completed, false for active)', - }, - tags: { - type: 'array', - items: { type: 'string' }, - description: 'Filter by tags (tasks matching any of these tags will be returned)', - }, - }, - required: [], - }, - }, - { - name: 'get_task', - description: - 'Get a single decrypted task by ID. Requires GSD_ENCRYPTION_PASSPHRASE to be set. Returns full task details.', - inputSchema: { - type: 'object', - properties: { - taskId: { - type: 'string', - description: 'The unique ID of the task to retrieve', - }, - }, - required: ['taskId'], - }, - }, - { - name: 'search_tasks', - description: - 'Search decrypted tasks by text query. Requires GSD_ENCRYPTION_PASSPHRASE to be set. Searches across task titles, descriptions, tags, and subtask text. Returns matching tasks.', - inputSchema: { - type: 'object', - properties: { - query: { - type: 'string', - description: 'Search query to match against task content', - }, - }, - required: ['query'], - }, - }, - { - name: 'get_productivity_metrics', - description: - 'Get comprehensive productivity metrics including completion counts, streaks, rates, quadrant distribution, tag statistics, and due date tracking. Requires GSD_ENCRYPTION_PASSPHRASE.', - inputSchema: { - type: 'object', - properties: {}, - required: [], - }, - }, - { - name: 'get_quadrant_analysis', - description: - 'Analyze task distribution and performance across all four Eisenhower matrix quadrants. Shows completion rates, task counts, and identifies top-performing quadrants. Requires GSD_ENCRYPTION_PASSPHRASE.', - inputSchema: { - type: 'object', - properties: {}, - required: [], - }, - }, - { - name: 'get_tag_analytics', - description: - 'Get detailed statistics for all tags including usage counts, completion rates, and tag-based insights. Useful for understanding project/category performance. Requires GSD_ENCRYPTION_PASSPHRASE.', - inputSchema: { - type: 'object', - properties: { - limit: { - type: 'number', - description: 'Maximum number of tags to return (default: all)', - }, - }, - required: [], - }, - }, - { - name: 'get_upcoming_deadlines', - description: - 'Get tasks grouped by deadline urgency: overdue, due today, and due this week. Useful for prioritizing time-sensitive work. Requires GSD_ENCRYPTION_PASSPHRASE.', - inputSchema: { - type: 'object', - properties: {}, - required: [], - }, - }, - { - name: 'get_task_insights', - description: - 'Generate an AI-friendly summary of task insights including key metrics, streaks, deadlines, quadrant distribution, and top tags. Perfect for quick status overview. Requires GSD_ENCRYPTION_PASSPHRASE.', - inputSchema: { - type: 'object', - properties: {}, - required: [], - }, - }, - { - name: 'validate_config', - description: - 'Validate MCP server configuration and diagnose issues. Checks environment variables, API connectivity, authentication, encryption, and sync status. Returns detailed diagnostics.', - inputSchema: { - type: 'object', - properties: {}, - required: [], - }, - }, - { - name: 'get_help', - description: - 'Get comprehensive help documentation including available tools, usage examples, common queries, and troubleshooting tips. Perfect for discovering what the GSD MCP server can do.', - inputSchema: { - type: 'object', - properties: { - topic: { - type: 'string', - description: 'Optional help topic: "tools", "analytics", "setup", "examples", or "troubleshooting"', - enum: ['tools', 'analytics', 'setup', 'examples', 'troubleshooting'], - }, - }, - required: [], - }, - }, - { - name: 'create_task', - description: - 'Create a new task with natural language input. Supports all task properties including title, description, urgency, importance, due dates, tags, subtasks, recurrence, and dependencies. Requires GSD_ENCRYPTION_PASSPHRASE.', - inputSchema: { - type: 'object', - properties: { - title: { - type: 'string', - description: 'Task title (required)', - }, - description: { - type: 'string', - description: 'Task description', - }, - urgent: { - type: 'boolean', - description: 'Is this task urgent? (time-sensitive)', - }, - important: { - type: 'boolean', - description: 'Is this task important? (high-value, strategic)', - }, - dueDate: { - type: 'string', - description: 'Due date as ISO 8601 datetime string (e.g., "2025-10-27T12:00:00.000Z")', - }, - tags: { - type: 'array', - items: { type: 'string' }, - description: 'Tags for categorization (e.g., ["#work", "#project-alpha"])', - }, - subtasks: { - type: 'array', - items: { - type: 'object', - properties: { - title: { type: 'string' }, - completed: { type: 'boolean' }, - }, - required: ['title', 'completed'], - }, - description: 'Subtasks/checklist items', - }, - recurrence: { - type: 'string', - enum: ['none', 'daily', 'weekly', 'monthly'], - description: 'Recurrence pattern', - }, - dependencies: { - type: 'array', - items: { type: 'string' }, - description: 'Task IDs that must be completed before this task', - }, - }, - required: ['title', 'urgent', 'important'], - }, - }, - { - name: 'update_task', - description: - 'Update an existing task. All fields except ID are optional - only provide fields you want to change. Supports moving between quadrants, updating content, changing due dates, and more. Requires GSD_ENCRYPTION_PASSPHRASE.', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: 'Task ID (required)', - }, - title: { - type: 'string', - description: 'New task title', - }, - description: { - type: 'string', - description: 'New task description', - }, - urgent: { - type: 'boolean', - description: 'Change urgency (moves between quadrants)', - }, - important: { - type: 'boolean', - description: 'Change importance (moves between quadrants)', - }, - dueDate: { - type: 'string', - description: 'New due date as ISO 8601 datetime string (empty string to clear)', - }, - tags: { - type: 'array', - items: { type: 'string' }, - description: 'Replace tags entirely', - }, - subtasks: { - type: 'array', - items: { - type: 'object', - properties: { - id: { type: 'string' }, - title: { type: 'string' }, - completed: { type: 'boolean' }, - }, - required: ['id', 'title', 'completed'], - }, - description: 'Replace subtasks entirely', - }, - recurrence: { - type: 'string', - enum: ['none', 'daily', 'weekly', 'monthly'], - description: 'Change recurrence pattern', - }, - dependencies: { - type: 'array', - items: { type: 'string' }, - description: 'Replace dependencies entirely', - }, - completed: { - type: 'boolean', - description: 'Mark as complete/incomplete', - }, - }, - required: ['id'], - }, - }, - { - name: 'complete_task', - description: - 'Mark a task as complete or incomplete. Quick shortcut for updating completion status. Requires GSD_ENCRYPTION_PASSPHRASE.', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: 'Task ID', - }, - completed: { - type: 'boolean', - description: 'True to mark complete, false to mark incomplete', - }, - }, - required: ['id', 'completed'], - }, - }, - { - name: 'delete_task', - description: - 'Permanently delete a task. This action cannot be undone. Use with caution. Requires GSD_ENCRYPTION_PASSPHRASE.', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - description: 'Task ID to delete', - }, - }, - required: ['id'], - }, - }, - { - name: 'bulk_update_tasks', - description: - 'Update multiple tasks at once. Supports completing, moving quadrants, adding/removing tags, setting due dates, and deleting. Limited to 50 tasks per operation for safety. Requires GSD_ENCRYPTION_PASSPHRASE.', - inputSchema: { - type: 'object', - properties: { - taskIds: { - type: 'array', - items: { type: 'string' }, - description: 'Array of task IDs to update (max 50)', - }, - operation: { - type: 'object', - description: 'Operation to perform on all tasks', - properties: { - type: { - type: 'string', - enum: ['complete', 'move_quadrant', 'add_tags', 'remove_tags', 'set_due_date', 'delete'], - description: 'Type of bulk operation', - }, - // Conditional properties based on type - completed: { - type: 'boolean', - description: 'For type=complete: true/false', - }, - urgent: { - type: 'boolean', - description: 'For type=move_quadrant: urgency', - }, - important: { - type: 'boolean', - description: 'For type=move_quadrant: importance', - }, - tags: { - type: 'array', - items: { type: 'string' }, - description: 'For type=add_tags or remove_tags: array of tags', - }, - dueDate: { - type: 'string', - description: 'For type=set_due_date: ISO 8601 datetime string (empty string to clear)', - }, - }, - required: ['type'], - }, - maxTasks: { - type: 'number', - description: 'Safety limit (default: 50)', - }, - }, - required: ['taskIds', 'operation'], - }, - }, -]; - -// Prompt definitions for common workflows -const prompts: Prompt[] = [ - { - name: 'daily-standup', - description: 'Daily task review: today\'s tasks, overdue items, and productivity summary', - arguments: [], - }, - { - name: 'weekly-review', - description: 'Weekly productivity analysis: completion stats, trends, and top quadrants', - arguments: [], - }, - { - name: 'focus-mode', - description: 'Get urgent and important tasks (Q1: Do First) to focus on right now', - arguments: [], - }, - { - name: 'upcoming-deadlines', - description: 'Show all overdue tasks, tasks due today, and tasks due this week', - arguments: [], - }, - { - name: 'productivity-report', - description: 'Comprehensive productivity report with metrics, streaks, and insights', - arguments: [], - }, - { - name: 'tag-analysis', - description: 'Analyze task distribution and completion rates by tags/projects', - arguments: [], - }, -]; - +import { loadConfig } from './server/config.js'; +import { createServer, registerHandlers } from './server/setup.js'; + +/** + * GSD Task Manager MCP Server + * + * Main entry point for the Model Context Protocol server. + * Handles CLI argument parsing and server initialization. + */ async function main() { // Parse CLI arguments const options = parseCLIArgs(process.argv); @@ -489,658 +32,18 @@ async function main() { } // MCP mode: Load configuration from environment - let config: GsdConfig; + let config; try { - config = configSchema.parse({ - apiBaseUrl: process.env.GSD_API_URL, - authToken: process.env.GSD_AUTH_TOKEN, - encryptionPassphrase: process.env.GSD_ENCRYPTION_PASSPHRASE, - }); + config = loadConfig(); } catch (error) { - console.error('āŒ Configuration error:', error); - console.error('\nRequired environment variables:'); - console.error(' GSD_API_URL - Base URL of your GSD Worker API (e.g., https://gsd.vinny.dev)'); - console.error(' GSD_AUTH_TOKEN - JWT token from OAuth authentication'); - console.error('\nOptional environment variables:'); - console.error(' GSD_ENCRYPTION_PASSPHRASE - Your encryption passphrase (enables decrypted task access)'); - console.error('\nšŸ’” Tip: Run setup wizard with: npx gsd-mcp-server --setup'); process.exit(1); } // Create MCP server - const server = new Server( - { - name: 'gsd-task-manager', - version: '0.4.5', - }, - { - capabilities: { - tools: {}, - prompts: {}, - }, - } - ); - - // Handle tool list requests - server.setRequestHandler(ListToolsRequestSchema, async () => { - return { tools }; - }); - - // Handle prompt list requests - server.setRequestHandler(ListPromptsRequestSchema, async () => { - return { prompts }; - }); - - // Handle prompt execution - server.setRequestHandler(GetPromptRequestSchema, async (request) => { - const { name } = request.params; - - switch (name) { - case 'daily-standup': - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: 'Give me a daily standup report:\n\n1. Show tasks due today\n2. Show overdue tasks\n3. Show tasks I completed today\n4. Give me a quick productivity summary\n\nMake it concise and actionable.', - }, - }, - ], - }; - - case 'weekly-review': - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: 'Give me a weekly productivity review:\n\n1. Tasks completed this week vs last week\n2. Current streak status\n3. Quadrant distribution analysis\n4. Top tags and their completion rates\n5. Key insights and recommendations\n\nFormat as a professional status report.', - }, - }, - ], - }; - - case 'focus-mode': - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: 'Help me focus right now:\n\n1. List all urgent AND important tasks (Q1: Do First quadrant)\n2. Prioritize by due date (overdue first, then soonest)\n3. Highlight any blocking dependencies\n4. Suggest which task to start with\n\nKeep it brief - I need to get to work!', - }, - }, - ], - }; - - case 'upcoming-deadlines': - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: 'Show me all upcoming deadlines:\n\n1. Overdue tasks (most urgent)\n2. Tasks due today\n3. Tasks due this week\n\nFor each group, show task titles and how overdue/soon they are. Flag any that have dependencies.', - }, - }, - ], - }; - - case 'productivity-report': - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: 'Generate a comprehensive productivity report:\n\n1. Overall task statistics (active, completed, total)\n2. Completion metrics (today, this week, this month)\n3. Streak analysis (current and longest)\n4. Quadrant performance breakdown\n5. Tag-based insights\n6. Upcoming deadlines summary\n7. Key recommendations for improvement\n\nFormat as an executive summary with key takeaways.', - }, - }, - ], - }; - - case 'tag-analysis': - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: 'Analyze my tasks by tags:\n\n1. List all tags with task counts\n2. Show completion rates for each tag\n3. Identify high-performing and low-performing tags\n4. Suggest which projects/areas need attention\n5. Highlight any patterns (e.g., work tags vs personal tags)\n\nHelp me understand where my time is going.', - }, - }, - ], - }; - - default: - throw new Error(`Unknown prompt: ${name}`); - } - }); - - // Handle tool execution - server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - try { - switch (name) { - case 'get_sync_status': { - const status = await getSyncStatus(config); - return { - content: [ - { - type: 'text', - text: JSON.stringify(status, null, 2), - }, - ], - }; - } - - case 'list_devices': { - const devices = await listDevices(config); - return { - content: [ - { - type: 'text', - text: JSON.stringify(devices, null, 2), - }, - ], - }; - } - - case 'get_task_stats': { - const stats = await getTaskStats(config); - return { - content: [ - { - type: 'text', - text: JSON.stringify(stats, null, 2), - }, - ], - }; - } - - case 'list_tasks': { - const filters = args as { - quadrant?: string; - completed?: boolean; - tags?: string[]; - }; - const tasks = await listTasks(config, filters); - return { - content: [ - { - type: 'text', - text: JSON.stringify(tasks, null, 2), - }, - ], - }; - } - - case 'get_task': { - const { taskId } = args as { taskId: string }; - const task = await getTask(config, taskId); - return { - content: [ - { - type: 'text', - text: JSON.stringify(task, null, 2), - }, - ], - }; - } - - case 'search_tasks': { - const { query } = args as { query: string }; - const tasks = await searchTasks(config, query); - return { - content: [ - { - type: 'text', - text: JSON.stringify(tasks, null, 2), - }, - ], - }; - } - - case 'get_productivity_metrics': { - const tasks = await listTasks(config); - const metrics = calculateMetrics(tasks); - return { - content: [ - { - type: 'text', - text: JSON.stringify(metrics, null, 2), - }, - ], - }; - } - - case 'get_quadrant_analysis': { - const tasks = await listTasks(config); - const quadrants = getQuadrantPerformance(tasks); - return { - content: [ - { - type: 'text', - text: JSON.stringify(quadrants, null, 2), - }, - ], - }; - } - - case 'get_tag_analytics': { - const { limit } = (args as { limit?: number }) || {}; - const tasks = await listTasks(config); - const metrics = calculateMetrics(tasks); - const tagStats = limit ? metrics.tagStats.slice(0, limit) : metrics.tagStats; - return { - content: [ - { - type: 'text', - text: JSON.stringify(tagStats, null, 2), - }, - ], - }; - } - - case 'get_upcoming_deadlines': { - const tasks = await listTasks(config); - const deadlines = getUpcomingDeadlines(tasks); - return { - content: [ - { - type: 'text', - text: JSON.stringify(deadlines, null, 2), - }, - ], - }; - } - - case 'get_task_insights': { - const tasks = await listTasks(config); - const insights = generateInsightsSummary(tasks); - return { - content: [ - { - type: 'text', - text: insights, - }, - ], - }; - } - - case 'validate_config': { - // Run validation and return results as JSON - const checks: Array<{ - name: string; - status: 'success' | 'warning' | 'error'; - details: string; - }> = []; - - // Check API connectivity - try { - const response = await fetch(`${config.apiBaseUrl}/health`); - if (response.ok) { - checks.push({ - name: 'API Connectivity', - status: 'success', - details: `Connected to ${config.apiBaseUrl}`, - }); - } else { - checks.push({ - name: 'API Connectivity', - status: 'warning', - details: `Connected but got status ${response.status}`, - }); - } - } catch (error) { - checks.push({ - name: 'API Connectivity', - status: 'error', - details: `Failed to connect: ${error instanceof Error ? error.message : 'Unknown error'}`, - }); - } - - // Check authentication - try { - const status = await getSyncStatus(config); - checks.push({ - name: 'Authentication', - status: 'success', - details: `Token valid (${status.deviceCount} devices registered)`, - }); - } catch (error) { - checks.push({ - name: 'Authentication', - status: 'error', - details: error instanceof Error ? error.message : 'Token validation failed', - }); - } - - // Check encryption (if passphrase provided) - if (config.encryptionPassphrase) { - try { - const tasks = await listTasks(config); - checks.push({ - name: 'Encryption', - status: 'success', - details: `Successfully decrypted ${tasks.length} tasks`, - }); - } catch (error) { - checks.push({ - name: 'Encryption', - status: 'error', - details: error instanceof Error ? error.message : 'Decryption failed', - }); - } - } else { - checks.push({ - name: 'Encryption', - status: 'warning', - details: 'Passphrase not provided (task content not accessible)', - }); - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify({ checks }, null, 2), - }, - ], - }; - } - - case 'get_help': { - const { topic } = (args as { topic?: string }) || {}; - let helpText = ''; - - if (!topic || topic === 'tools') { - helpText += `# GSD Task Manager MCP Server - Help - -## Available Tools (13 total) - -### Metadata & Status Tools -- **get_sync_status** - Check sync health, device count, storage usage -- **list_devices** - View all registered devices and their status -- **get_task_stats** - Get high-level task statistics (metadata only) - -### Task Access Tools (require encryption passphrase) -- **list_tasks** - List all tasks with optional filtering (quadrant, status, tags) -- **get_task** - Get a single task by ID -- **search_tasks** - Search across titles, descriptions, tags, subtasks - -### Analytics Tools (require encryption passphrase) -- **get_productivity_metrics** - Comprehensive metrics (completions, streaks, rates) -- **get_quadrant_analysis** - Performance breakdown across all 4 quadrants -- **get_tag_analytics** - Tag usage statistics and completion rates -- **get_upcoming_deadlines** - Overdue, due today, and due this week tasks -- **get_task_insights** - AI-friendly summary of key metrics - -### Configuration Tools -- **validate_config** - Diagnose configuration issues -- **get_help** - This help message (supports topic filtering) - -`; - } - - if (!topic || topic === 'analytics') { - helpText += `## Analytics Capabilities - -The MCP server provides rich productivity analytics: - -**Productivity Metrics:** -- Completion counts (today, this week, this month) -- Active and longest completion streaks -- Overall completion rate percentage -- Quadrant distribution of active tasks -- Tag-based statistics -- Due date tracking (overdue, today, this week, no due date) - -**Quadrant Analysis:** -- Completion rates per quadrant -- Task counts (total, completed, active) per quadrant -- Performance ranking to identify top quadrants - -**Tag Analytics:** -- Usage counts per tag -- Completion rates per tag -- Identification of high/low performing tags - -**Deadline Management:** -- Overdue tasks grouped and sorted -- Tasks due today -- Tasks due within the next week -- Dependency tracking for blocked tasks - -`; - } - - if (!topic || topic === 'setup') { - helpText += `## Setup & Configuration - -**First-time Setup:** -\`\`\`bash -npx gsd-mcp-server --setup -\`\`\` - -The interactive wizard will: -1. Test API connectivity -2. Validate your auth token -3. Test encryption passphrase (optional) -4. Generate Claude Desktop config - -**Validate Existing Config:** -\`\`\`bash -npx gsd-mcp-server --validate -\`\`\` - -**Configuration Requirements:** -- GSD_API_URL - Worker API endpoint -- GSD_AUTH_TOKEN - JWT from OAuth login (7-day expiration) -- GSD_ENCRYPTION_PASSPHRASE - (Optional) For decrypted task access - -**Claude Desktop Config Location:** -- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json -- Windows: %APPDATA%\\Claude\\claude_desktop_config.json - -`; - } - - if (!topic || topic === 'examples') { - helpText += `## Usage Examples - -**Daily Workflow:** -- "What tasks are due today?" -- "Show me overdue tasks" -- "What's my completion streak?" -- "Give me a daily standup report" - -**Analytics:** -- "What's my productivity this week?" -- "Which quadrant has the most tasks?" -- "Show me tag completion rates" -- "Analyze my task distribution" - -**Task Management:** -- "List all urgent and important tasks" -- "Search for tasks about quarterly report" -- "Show me tasks tagged #work" -- "Find tasks with dependencies" - -**Configuration:** -- "Validate my MCP configuration" -- "Check if my setup is working" -- "Diagnose any issues" - -**Pro Tip:** Use the built-in prompts for common workflows: -- daily-standup -- weekly-review -- focus-mode -- upcoming-deadlines -- productivity-report -- tag-analysis - -`; - } - - if (!topic || topic === 'troubleshooting') { - helpText += `## Troubleshooting - -**"Configuration error" on startup:** -- Run: \`npx gsd-mcp-server --setup\` -- Ensure GSD_API_URL and GSD_AUTH_TOKEN are set - -**"Authentication failed (401)":** -- Your token has expired (7-day lifetime) -- Get new token: Visit GSD app → DevTools → Local Storage → gsd_auth_token -- Update Claude Desktop config → Restart Claude - -**"Encryption passphrase not provided":** -- Add GSD_ENCRYPTION_PASSPHRASE to Claude Desktop config -- Must match passphrase set in GSD app -- Restart Claude Desktop - -**"Failed to connect":** -- Check internet connection -- Verify GSD_API_URL is correct -- Ensure Worker is deployed and accessible - -**Decryption failures:** -- Verify passphrase is correct (case-sensitive) -- Ensure encryption is set up in GSD app (Settings → Sync) -- Try fetching new encryption salt - -**For more help:** -- Run: \`npx gsd-mcp-server --validate\` -- Check logs in Claude Desktop -- GitHub: https://github.com/vscarpenter/gsd-taskmanager/issues - -`; - } - - if (!topic) { - helpText += `## Additional Resources - -- **Full Documentation:** https://github.com/vscarpenter/gsd-taskmanager/tree/main/packages/mcp-server -- **Setup Guide:** Run \`npx gsd-mcp-server --setup\` -- **Validation:** Run \`npx gsd-mcp-server --validate\` -- **Issues/Support:** https://github.com/vscarpenter/gsd-taskmanager/issues - -**Version:** 0.4.0 -**Status:** Production-ready -**Privacy:** End-to-end encrypted, zero-knowledge server -**Capabilities:** Full task management (create, read, update, delete) -`; - } - - return { - content: [ - { - type: 'text', - text: helpText.trim(), - }, - ], - }; - } - - case 'create_task': { - const input = args as unknown as CreateTaskInput; - const newTask = await createTask(config, input); - return { - content: [ - { - type: 'text', - text: `āœ… Task created successfully!\n\n${JSON.stringify(newTask, null, 2)}`, - }, - ], - }; - } - - case 'update_task': { - const input = args as unknown as UpdateTaskInput; - const updatedTask = await updateTask(config, input); - return { - content: [ - { - type: 'text', - text: `āœ… Task updated successfully!\n\n${JSON.stringify(updatedTask, null, 2)}`, - }, - ], - }; - } - - case 'complete_task': { - const { id, completed } = args as { id: string; completed: boolean }; - const updatedTask = await completeTask(config, id, completed); - return { - content: [ - { - type: 'text', - text: `āœ… Task marked as ${completed ? 'complete' : 'incomplete'}!\n\n${JSON.stringify(updatedTask, null, 2)}`, - }, - ], - }; - } - - case 'delete_task': { - const { id } = args as { id: string }; - await deleteTask(config, id); - return { - content: [ - { - type: 'text', - text: `āœ… Task deleted successfully!\n\nTask ID: ${id}`, - }, - ], - }; - } - - case 'bulk_update_tasks': { - const { taskIds, operation, maxTasks } = args as { - taskIds: string[]; - operation: BulkOperation; - maxTasks?: number; - }; - const result = await bulkUpdateTasks(config, taskIds, operation, { maxTasks }); - - let message = `āœ… Bulk operation completed!\n\n`; - message += `Updated: ${result.updated} task(s)\n`; - if (result.errors.length > 0) { - message += `\nErrors (${result.errors.length}):\n`; - result.errors.forEach((err, idx) => { - message += `${idx + 1}. ${err}\n`; - }); - } - - return { - content: [ - { - type: 'text', - text: message, - }, - ], - }; - } + const server = createServer(); - default: - throw new Error(`Unknown tool: ${name}`); - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - return { - content: [ - { - type: 'text', - text: `Error: ${errorMessage}`, - }, - ], - isError: true, - }; - } - }); + // Register all request handlers + registerHandlers(server, config); // Start server with stdio transport const transport = new StdioServerTransport(); diff --git a/packages/mcp-server/src/server/config.ts b/packages/mcp-server/src/server/config.ts new file mode 100644 index 00000000..bf0d4ff5 --- /dev/null +++ b/packages/mcp-server/src/server/config.ts @@ -0,0 +1,67 @@ +import { z } from 'zod'; +import type { GsdConfig } from '../tools.js'; + +/** + * Configuration schema for GSD MCP Server + * Validates environment variables and ensures required fields are present + */ +export const configSchema = z.object({ + apiBaseUrl: z.string().url(), + authToken: z.string().min(1), + encryptionPassphrase: z.string().optional(), // Optional: for decrypting tasks +}); + +export type ConfigSchema = z.infer; + +/** + * Load and validate configuration from environment variables + * @throws {Error} If configuration is invalid or missing required fields + */ +export function loadConfig(): GsdConfig { + try { + return configSchema.parse({ + apiBaseUrl: process.env.GSD_API_URL, + authToken: process.env.GSD_AUTH_TOKEN, + encryptionPassphrase: process.env.GSD_ENCRYPTION_PASSPHRASE, + }); + } catch (error) { + console.error('āŒ Configuration error:', error); + console.error('\nRequired environment variables:'); + console.error(' GSD_API_URL - Base URL of your GSD Worker API (e.g., https://gsd.vinny.dev)'); + console.error(' GSD_AUTH_TOKEN - JWT token from OAuth authentication'); + console.error('\nOptional environment variables:'); + console.error(' GSD_ENCRYPTION_PASSPHRASE - Your encryption passphrase (enables decrypted task access)'); + console.error('\nšŸ’” Tip: Run setup wizard with: npx gsd-mcp-server --setup'); + throw error; + } +} + +/** + * Check if configuration is valid without throwing + * @returns {boolean} True if config is valid, false otherwise + */ +export function isConfigValid(): boolean { + try { + loadConfig(); + return true; + } catch { + return false; + } +} + +/** + * Get configuration status for diagnostics + */ +export function getConfigStatus(): { + hasApiUrl: boolean; + hasAuthToken: boolean; + hasEncryptionPassphrase: boolean; + isValid: boolean; +} { + return { + hasApiUrl: !!process.env.GSD_API_URL, + hasAuthToken: !!process.env.GSD_AUTH_TOKEN, + hasEncryptionPassphrase: !!process.env.GSD_ENCRYPTION_PASSPHRASE, + isValid: isConfigValid(), + }; +} diff --git a/packages/mcp-server/src/server/setup.ts b/packages/mcp-server/src/server/setup.ts new file mode 100644 index 00000000..4cfd8649 --- /dev/null +++ b/packages/mcp-server/src/server/setup.ts @@ -0,0 +1,59 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { + ListToolsRequestSchema, + ListPromptsRequestSchema, + GetPromptRequestSchema, + CallToolRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { allTools } from '../tools/schemas/index.js'; +import { allPrompts, getPromptMessage } from '../tools/prompts.js'; +import { handleToolCall } from '../tools/handlers/index.js'; +import type { GsdConfig } from '../tools.js'; + +/** + * Create and configure the MCP server instance + */ +export function createServer(): Server { + return new Server( + { + name: 'gsd-task-manager', + version: '0.5.0', + }, + { + capabilities: { + tools: {}, + prompts: {}, + }, + } + ); +} + +/** + * Register all request handlers with the MCP server + */ +export function registerHandlers(server: Server, config: GsdConfig): void { + // Register tool list handler + server.setRequestHandler(ListToolsRequestSchema, async () => { + return { tools: allTools }; + }); + + // Register prompt list handler + server.setRequestHandler(ListPromptsRequestSchema, async () => { + return { prompts: allPrompts }; + }); + + // Register prompt execution handler + server.setRequestHandler(GetPromptRequestSchema, async (request) => { + const { name } = request.params; + const message = getPromptMessage(name); + return { + messages: [message], + }; + }); + + // Register tool execution handler + server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + return handleToolCall(name, args || {}, config); + }); +} diff --git a/packages/mcp-server/src/tools/handlers/analytics-handlers.ts b/packages/mcp-server/src/tools/handlers/analytics-handlers.ts new file mode 100644 index 00000000..3fa9f634 --- /dev/null +++ b/packages/mcp-server/src/tools/handlers/analytics-handlers.ts @@ -0,0 +1,77 @@ +import { listTasks, type GsdConfig } from '../../tools.js'; +import { + calculateMetrics, + getQuadrantPerformance, + getUpcomingDeadlines, + generateInsightsSummary, +} from '../../analytics.js'; + +/** + * Analytics tool handlers for productivity metrics and insights + */ + +export async function handleGetProductivityMetrics(config: GsdConfig) { + const tasks = await listTasks(config); + const metrics = calculateMetrics(tasks); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(metrics, null, 2), + }, + ], + }; +} + +export async function handleGetQuadrantAnalysis(config: GsdConfig) { + const tasks = await listTasks(config); + const quadrants = getQuadrantPerformance(tasks); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(quadrants, null, 2), + }, + ], + }; +} + +export async function handleGetTagAnalytics(config: GsdConfig, args: { limit?: number }) { + const tasks = await listTasks(config); + const metrics = calculateMetrics(tasks); + const tagStats = args.limit ? metrics.tagStats.slice(0, args.limit) : metrics.tagStats; + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(tagStats, null, 2), + }, + ], + }; +} + +export async function handleGetUpcomingDeadlines(config: GsdConfig) { + const tasks = await listTasks(config); + const deadlines = getUpcomingDeadlines(tasks); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(deadlines, null, 2), + }, + ], + }; +} + +export async function handleGetTaskInsights(config: GsdConfig) { + const tasks = await listTasks(config); + const insights = generateInsightsSummary(tasks); + return { + content: [ + { + type: 'text' as const, + text: insights, + }, + ], + }; +} diff --git a/packages/mcp-server/src/tools/handlers/index.ts b/packages/mcp-server/src/tools/handlers/index.ts new file mode 100644 index 00000000..4c4bb670 --- /dev/null +++ b/packages/mcp-server/src/tools/handlers/index.ts @@ -0,0 +1,130 @@ +/** + * Tool handler dispatcher + * Routes tool calls to appropriate handler functions + */ + +import type { GsdConfig } from '../../tools.js'; +import { + handleGetSyncStatus, + handleListDevices, + handleGetTaskStats, + handleListTasks, + handleGetTask, + handleSearchTasks, +} from './read-handlers.js'; +import { + handleGetProductivityMetrics, + handleGetQuadrantAnalysis, + handleGetTagAnalytics, + handleGetUpcomingDeadlines, + handleGetTaskInsights, +} from './analytics-handlers.js'; +import { + handleCreateTask, + handleUpdateTask, + handleCompleteTask, + handleDeleteTask, + handleBulkUpdateTasks, +} from './write-handlers.js'; +import { + handleValidateConfig, + handleGetHelp, +} from './system-handlers.js'; + +// Re-export all handlers +export * from './read-handlers.js'; +export * from './analytics-handlers.js'; +export * from './write-handlers.js'; +export * from './system-handlers.js'; + +/** + * Handle a tool call request + * @param name - Tool name + * @param args - Tool arguments + * @param config - GSD configuration + * @returns Tool response content + */ +export async function handleToolCall( + name: string, + args: Record, + config: GsdConfig +): Promise<{ + content: Array<{ type: 'text'; text: string }>; + isError?: boolean; +}> { + try { + switch (name) { + // Read tools + case 'get_sync_status': + return await handleGetSyncStatus(config); + + case 'list_devices': + return await handleListDevices(config); + + case 'get_task_stats': + return await handleGetTaskStats(config); + + case 'list_tasks': + return await handleListTasks(config, args as any); + + case 'get_task': + return await handleGetTask(config, args as any); + + case 'search_tasks': + return await handleSearchTasks(config, args as any); + + // Analytics tools + case 'get_productivity_metrics': + return await handleGetProductivityMetrics(config); + + case 'get_quadrant_analysis': + return await handleGetQuadrantAnalysis(config); + + case 'get_tag_analytics': + return await handleGetTagAnalytics(config, args as any); + + case 'get_upcoming_deadlines': + return await handleGetUpcomingDeadlines(config); + + case 'get_task_insights': + return await handleGetTaskInsights(config); + + // Write tools + case 'create_task': + return await handleCreateTask(config, args as any); + + case 'update_task': + return await handleUpdateTask(config, args as any); + + case 'complete_task': + return await handleCompleteTask(config, args as any); + + case 'delete_task': + return await handleDeleteTask(config, args as any); + + case 'bulk_update_tasks': + return await handleBulkUpdateTasks(config, args as any); + + // System tools + case 'validate_config': + return await handleValidateConfig(config); + + case 'get_help': + return await handleGetHelp(args as any); + + default: + throw new Error(`Unknown tool: ${name}`); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { + content: [ + { + type: 'text', + text: `Error: ${errorMessage}`, + }, + ], + isError: true, + }; + } +} diff --git a/packages/mcp-server/src/tools/handlers/read-handlers.ts b/packages/mcp-server/src/tools/handlers/read-handlers.ts new file mode 100644 index 00000000..aec9b168 --- /dev/null +++ b/packages/mcp-server/src/tools/handlers/read-handlers.ts @@ -0,0 +1,88 @@ +import { + getSyncStatus, + listDevices, + getTaskStats, + listTasks, + getTask, + searchTasks, + type GsdConfig, +} from '../../tools.js'; + +/** + * Read-only tool handlers for accessing task data and metadata + */ + +export async function handleGetSyncStatus(config: GsdConfig) { + const status = await getSyncStatus(config); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(status, null, 2), + }, + ], + }; +} + +export async function handleListDevices(config: GsdConfig) { + const devices = await listDevices(config); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(devices, null, 2), + }, + ], + }; +} + +export async function handleGetTaskStats(config: GsdConfig) { + const stats = await getTaskStats(config); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(stats, null, 2), + }, + ], + }; +} + +export async function handleListTasks( + config: GsdConfig, + args: { quadrant?: string; completed?: boolean; tags?: string[] } +) { + const tasks = await listTasks(config, args); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(tasks, null, 2), + }, + ], + }; +} + +export async function handleGetTask(config: GsdConfig, args: { taskId: string }) { + const task = await getTask(config, args.taskId); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(task, null, 2), + }, + ], + }; +} + +export async function handleSearchTasks(config: GsdConfig, args: { query: string }) { + const tasks = await searchTasks(config, args.query); + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify(tasks, null, 2), + }, + ], + }; +} diff --git a/packages/mcp-server/src/tools/handlers/system-handlers.ts b/packages/mcp-server/src/tools/handlers/system-handlers.ts new file mode 100644 index 00000000..960565cb --- /dev/null +++ b/packages/mcp-server/src/tools/handlers/system-handlers.ts @@ -0,0 +1,292 @@ +import { getSyncStatus, listTasks, type GsdConfig } from '../../tools.js'; + +/** + * System tool handlers for configuration validation and help + */ + +export async function handleValidateConfig(config: GsdConfig) { + const checks: Array<{ + name: string; + status: 'success' | 'warning' | 'error'; + details: string; + }> = []; + + // Check API connectivity + try { + const response = await fetch(`${config.apiBaseUrl}/health`); + if (response.ok) { + checks.push({ + name: 'API Connectivity', + status: 'success', + details: `Connected to ${config.apiBaseUrl}`, + }); + } else { + checks.push({ + name: 'API Connectivity', + status: 'warning', + details: `Connected but got status ${response.status}`, + }); + } + } catch (error) { + checks.push({ + name: 'API Connectivity', + status: 'error', + details: `Failed to connect: ${error instanceof Error ? error.message : 'Unknown error'}`, + }); + } + + // Check authentication + try { + const status = await getSyncStatus(config); + checks.push({ + name: 'Authentication', + status: 'success', + details: `Token valid (${status.deviceCount} devices registered)`, + }); + } catch (error) { + checks.push({ + name: 'Authentication', + status: 'error', + details: error instanceof Error ? error.message : 'Token validation failed', + }); + } + + // Check encryption (if passphrase provided) + if (config.encryptionPassphrase) { + try { + const tasks = await listTasks(config); + checks.push({ + name: 'Encryption', + status: 'success', + details: `Successfully decrypted ${tasks.length} tasks`, + }); + } catch (error) { + checks.push({ + name: 'Encryption', + status: 'error', + details: error instanceof Error ? error.message : 'Decryption failed', + }); + } + } else { + checks.push({ + name: 'Encryption', + status: 'warning', + details: 'Passphrase not provided (task content not accessible)', + }); + } + + return { + content: [ + { + type: 'text' as const, + text: JSON.stringify({ checks }, null, 2), + }, + ], + }; +} + +export async function handleGetHelp(args: { topic?: string }) { + const { topic } = args; + let helpText = ''; + + if (!topic || topic === 'tools') { + helpText += `# GSD Task Manager MCP Server - Help + +## Available Tools (18 total) + +### Metadata & Status Tools +- **get_sync_status** - Check sync health, device count, storage usage +- **list_devices** - View all registered devices and their status +- **get_task_stats** - Get high-level task statistics (metadata only) + +### Task Access Tools (require encryption passphrase) +- **list_tasks** - List all tasks with optional filtering (quadrant, status, tags) +- **get_task** - Get a single task by ID +- **search_tasks** - Search across titles, descriptions, tags, subtasks + +### Analytics Tools (require encryption passphrase) +- **get_productivity_metrics** - Comprehensive metrics (completions, streaks, rates) +- **get_quadrant_analysis** - Performance breakdown across all 4 quadrants +- **get_tag_analytics** - Tag usage statistics and completion rates +- **get_upcoming_deadlines** - Overdue, due today, and due this week tasks +- **get_task_insights** - AI-friendly summary of key metrics + +### Write Tools (require encryption passphrase) +- **create_task** - Create a new task with full properties +- **update_task** - Update an existing task (partial updates supported) +- **complete_task** - Quick toggle for task completion status +- **delete_task** - Permanently delete a task +- **bulk_update_tasks** - Update multiple tasks at once (max 50) + +### Configuration Tools +- **validate_config** - Diagnose configuration issues +- **get_help** - This help message (supports topic filtering) + +`; + } + + if (!topic || topic === 'analytics') { + helpText += `## Analytics Capabilities + +The MCP server provides rich productivity analytics: + +**Productivity Metrics:** +- Completion counts (today, this week, this month) +- Active and longest completion streaks +- Overall completion rate percentage +- Quadrant distribution of active tasks +- Tag-based statistics +- Due date tracking (overdue, today, this week, no due date) + +**Quadrant Analysis:** +- Completion rates per quadrant +- Task counts (total, completed, active) per quadrant +- Performance ranking to identify top quadrants + +**Tag Analytics:** +- Usage counts per tag +- Completion rates per tag +- Identification of high/low performing tags + +**Deadline Management:** +- Overdue tasks grouped and sorted +- Tasks due today +- Tasks due within the next week +- Dependency tracking for blocked tasks + +`; + } + + if (!topic || topic === 'setup') { + helpText += `## Setup & Configuration + +**First-time Setup:** +\`\`\`bash +npx gsd-mcp-server --setup +\`\`\` + +The interactive wizard will: +1. Test API connectivity +2. Validate your auth token +3. Test encryption passphrase (optional) +4. Generate Claude Desktop config + +**Validate Existing Config:** +\`\`\`bash +npx gsd-mcp-server --validate +\`\`\` + +**Configuration Requirements:** +- GSD_API_URL - Worker API endpoint +- GSD_AUTH_TOKEN - JWT from OAuth login (7-day expiration) +- GSD_ENCRYPTION_PASSPHRASE - (Optional) For decrypted task access + +**Claude Desktop Config Location:** +- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json +- Windows: %APPDATA%\\Claude\\claude_desktop_config.json + +`; + } + + if (!topic || topic === 'examples') { + helpText += `## Usage Examples + +**Daily Workflow:** +- "What tasks are due today?" +- "Show me overdue tasks" +- "What's my completion streak?" +- "Give me a daily standup report" + +**Analytics:** +- "What's my productivity this week?" +- "Which quadrant has the most tasks?" +- "Show me tag completion rates" +- "Analyze my task distribution" + +**Task Management:** +- "List all urgent and important tasks" +- "Search for tasks about quarterly report" +- "Show me tasks tagged #work" +- "Find tasks with dependencies" + +**Write Operations:** +- "Create a task: Review quarterly budget" +- "Mark task abc123 as complete" +- "Delete task xyz789" +- "Update task abc123 to be urgent" + +**Configuration:** +- "Validate my MCP configuration" +- "Check if my setup is working" +- "Diagnose any issues" + +**Pro Tip:** Use the built-in prompts for common workflows: +- daily-standup +- weekly-review +- focus-mode +- upcoming-deadlines +- productivity-report +- tag-analysis + +`; + } + + if (!topic || topic === 'troubleshooting') { + helpText += `## Troubleshooting + +**"Configuration error" on startup:** +- Run: \`npx gsd-mcp-server --setup\` +- Ensure GSD_API_URL and GSD_AUTH_TOKEN are set + +**"Authentication failed (401)":** +- Your token has expired (7-day lifetime) +- Get new token: Visit GSD app → DevTools → Local Storage → gsd_auth_token +- Update Claude Desktop config → Restart Claude + +**"Encryption passphrase not provided":** +- Add GSD_ENCRYPTION_PASSPHRASE to Claude Desktop config +- Must match passphrase set in GSD app +- Restart Claude Desktop + +**"Failed to connect":** +- Check internet connection +- Verify GSD_API_URL is correct +- Ensure Worker is deployed and accessible + +**Decryption failures:** +- Verify passphrase is correct (case-sensitive) +- Ensure encryption is set up in GSD app (Settings → Sync) +- Try fetching new encryption salt + +**For more help:** +- Run: \`npx gsd-mcp-server --validate\` +- Check logs in Claude Desktop +- GitHub: https://github.com/vscarpenter/gsd-taskmanager/issues + +`; + } + + if (!topic) { + helpText += `## Additional Resources + +- **Full Documentation:** https://github.com/vscarpenter/gsd-taskmanager/tree/main/packages/mcp-server +- **Setup Guide:** Run \`npx gsd-mcp-server --setup\` +- **Validation:** Run \`npx gsd-mcp-server --validate\` +- **Issues/Support:** https://github.com/vscarpenter/gsd-taskmanager/issues + +**Version:** 0.5.0 +**Status:** Production-ready +**Privacy:** End-to-end encrypted, zero-knowledge server +**Capabilities:** Full task management (create, read, update, delete) +`; + } + + return { + content: [ + { + type: 'text' as const, + text: helpText.trim(), + }, + ], + }; +} diff --git a/packages/mcp-server/src/tools/handlers/write-handlers.ts b/packages/mcp-server/src/tools/handlers/write-handlers.ts new file mode 100644 index 00000000..b18e5c4a --- /dev/null +++ b/packages/mcp-server/src/tools/handlers/write-handlers.ts @@ -0,0 +1,88 @@ +import { + createTask, + updateTask, + completeTask, + deleteTask, + bulkUpdateTasks, + type CreateTaskInput, + type UpdateTaskInput, + type BulkOperation, +} from '../../write-ops.js'; +import type { GsdConfig } from '../../tools.js'; + +/** + * Write operation tool handlers for modifying task data + */ + +export async function handleCreateTask(config: GsdConfig, args: CreateTaskInput) { + const newTask = await createTask(config, args); + return { + content: [ + { + type: 'text' as const, + text: `āœ… Task created successfully!\n\n${JSON.stringify(newTask, null, 2)}`, + }, + ], + }; +} + +export async function handleUpdateTask(config: GsdConfig, args: UpdateTaskInput) { + const updatedTask = await updateTask(config, args); + return { + content: [ + { + type: 'text' as const, + text: `āœ… Task updated successfully!\n\n${JSON.stringify(updatedTask, null, 2)}`, + }, + ], + }; +} + +export async function handleCompleteTask(config: GsdConfig, args: { id: string; completed: boolean }) { + const updatedTask = await completeTask(config, args.id, args.completed); + return { + content: [ + { + type: 'text' as const, + text: `āœ… Task marked as ${args.completed ? 'complete' : 'incomplete'}!\n\n${JSON.stringify(updatedTask, null, 2)}`, + }, + ], + }; +} + +export async function handleDeleteTask(config: GsdConfig, args: { id: string }) { + await deleteTask(config, args.id); + return { + content: [ + { + type: 'text' as const, + text: `āœ… Task deleted successfully!\n\nTask ID: ${args.id}`, + }, + ], + }; +} + +export async function handleBulkUpdateTasks( + config: GsdConfig, + args: { taskIds: string[]; operation: BulkOperation; maxTasks?: number } +) { + const result = await bulkUpdateTasks(config, args.taskIds, args.operation, { maxTasks: args.maxTasks }); + + let message = `āœ… Bulk operation completed!\n\n`; + message += `Updated: ${result.updated} task(s)\n`; + if (result.errors.length > 0) { + message += `\nErrors (${result.errors.length}):\n`; + result.errors.forEach((err, idx) => { + message += `${idx + 1}. ${err}\n`; + }); + } + + return { + content: [ + { + type: 'text' as const, + text: message, + }, + ], + }; +} diff --git a/packages/mcp-server/src/tools/prompts.ts b/packages/mcp-server/src/tools/prompts.ts new file mode 100644 index 00000000..3ce34e9d --- /dev/null +++ b/packages/mcp-server/src/tools/prompts.ts @@ -0,0 +1,129 @@ +import type { Prompt } from '@modelcontextprotocol/sdk/types.js'; + +/** + * Prompt definitions for common task management workflows + * Prompts provide pre-built conversational templates for Claude + */ + +export const dailyStandupPrompt: Prompt = { + name: 'daily-standup', + description: "Daily task review: today's tasks, overdue items, and productivity summary", + arguments: [], +}; + +export const weeklyReviewPrompt: Prompt = { + name: 'weekly-review', + description: 'Weekly productivity analysis: completion stats, trends, and top quadrants', + arguments: [], +}; + +export const focusModePrompt: Prompt = { + name: 'focus-mode', + description: 'Get urgent and important tasks (Q1: Do First) to focus on right now', + arguments: [], +}; + +export const upcomingDeadlinesPrompt: Prompt = { + name: 'upcoming-deadlines', + description: 'Show all overdue tasks, tasks due today, and tasks due this week', + arguments: [], +}; + +export const productivityReportPrompt: Prompt = { + name: 'productivity-report', + description: 'Comprehensive productivity report with metrics, streaks, and insights', + arguments: [], +}; + +export const tagAnalysisPrompt: Prompt = { + name: 'tag-analysis', + description: 'Analyze task distribution and completion rates by tags/projects', + arguments: [], +}; + +export const allPrompts: Prompt[] = [ + dailyStandupPrompt, + weeklyReviewPrompt, + focusModePrompt, + upcomingDeadlinesPrompt, + productivityReportPrompt, + tagAnalysisPrompt, +]; + +/** + * Get prompt message for a given prompt name + */ +export function getPromptMessage(name: string): { role: string; content: { type: string; text: string } } { + const prompts: Record = { + 'daily-standup': `Give me a daily standup report: + +1. Show tasks due today +2. Show overdue tasks +3. Show tasks I completed today +4. Give me a quick productivity summary + +Make it concise and actionable.`, + + 'weekly-review': `Give me a weekly productivity review: + +1. Tasks completed this week vs last week +2. Current streak status +3. Quadrant distribution analysis +4. Top tags and their completion rates +5. Key insights and recommendations + +Format as a professional status report.`, + + 'focus-mode': `Help me focus right now: + +1. List all urgent AND important tasks (Q1: Do First quadrant) +2. Prioritize by due date (overdue first, then soonest) +3. Highlight any blocking dependencies +4. Suggest which task to start with + +Keep it brief - I need to get to work!`, + + 'upcoming-deadlines': `Show me all upcoming deadlines: + +1. Overdue tasks (most urgent) +2. Tasks due today +3. Tasks due this week + +For each group, show task titles and how overdue/soon they are. Flag any that have dependencies.`, + + 'productivity-report': `Generate a comprehensive productivity report: + +1. Overall task statistics (active, completed, total) +2. Completion metrics (today, this week, this month) +3. Streak analysis (current and longest) +4. Quadrant performance breakdown +5. Tag-based insights +6. Upcoming deadlines summary +7. Key recommendations for improvement + +Format as an executive summary with key takeaways.`, + + 'tag-analysis': `Analyze my tasks by tags: + +1. List all tags with task counts +2. Show completion rates for each tag +3. Identify high-performing and low-performing tags +4. Suggest which projects/areas need attention +5. Highlight any patterns (e.g., work tags vs personal tags) + +Help me understand where my time is going.`, + }; + + const text = prompts[name]; + if (!text) { + throw new Error(`Unknown prompt: ${name}`); + } + + return { + role: 'user', + content: { + type: 'text', + text, + }, + }; +} diff --git a/packages/mcp-server/src/tools/schemas/analytics-tools.ts b/packages/mcp-server/src/tools/schemas/analytics-tools.ts new file mode 100644 index 00000000..80092774 --- /dev/null +++ b/packages/mcp-server/src/tools/schemas/analytics-tools.ts @@ -0,0 +1,74 @@ +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; + +/** + * Analytics tool schemas for productivity metrics and insights + * All require GSD_ENCRYPTION_PASSPHRASE to access decrypted task data + */ + +export const getProductivityMetricsTool: Tool = { + name: 'get_productivity_metrics', + description: + 'Get comprehensive productivity metrics including completion counts, streaks, rates, quadrant distribution, tag statistics, and due date tracking. Requires GSD_ENCRYPTION_PASSPHRASE.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, +}; + +export const getQuadrantAnalysisTool: Tool = { + name: 'get_quadrant_analysis', + description: + 'Analyze task distribution and performance across all four Eisenhower matrix quadrants. Shows completion rates, task counts, and identifies top-performing quadrants. Requires GSD_ENCRYPTION_PASSPHRASE.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, +}; + +export const getTagAnalyticsTool: Tool = { + name: 'get_tag_analytics', + description: + 'Get detailed statistics for all tags including usage counts, completion rates, and tag-based insights. Useful for understanding project/category performance. Requires GSD_ENCRYPTION_PASSPHRASE.', + inputSchema: { + type: 'object', + properties: { + limit: { + type: 'number', + description: 'Maximum number of tags to return (default: all)', + }, + }, + required: [], + }, +}; + +export const getUpcomingDeadlinesTool: Tool = { + name: 'get_upcoming_deadlines', + description: + 'Get tasks grouped by deadline urgency: overdue, due today, and due this week. Useful for prioritizing time-sensitive work. Requires GSD_ENCRYPTION_PASSPHRASE.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, +}; + +export const getTaskInsightsTool: Tool = { + name: 'get_task_insights', + description: + 'Generate an AI-friendly summary of task insights including key metrics, streaks, deadlines, quadrant distribution, and top tags. Perfect for quick status overview. Requires GSD_ENCRYPTION_PASSPHRASE.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, +}; + +export const analyticsTools: Tool[] = [ + getProductivityMetricsTool, + getQuadrantAnalysisTool, + getTagAnalyticsTool, + getUpcomingDeadlinesTool, + getTaskInsightsTool, +]; diff --git a/packages/mcp-server/src/tools/schemas/index.ts b/packages/mcp-server/src/tools/schemas/index.ts new file mode 100644 index 00000000..6b14db17 --- /dev/null +++ b/packages/mcp-server/src/tools/schemas/index.ts @@ -0,0 +1,26 @@ +/** + * Tool schema definitions for the GSD MCP Server + * Organized by functionality: read, write, analytics, and system tools + */ + +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { readTools } from './read-tools.js'; +import { writeTools } from './write-tools.js'; +import { analyticsTools } from './analytics-tools.js'; +import { systemTools } from './system-tools.js'; + +// Re-export individual tool categories +export * from './read-tools.js'; +export * from './write-tools.js'; +export * from './analytics-tools.js'; +export * from './system-tools.js'; + +/** + * All MCP tool schemas (16 total) + */ +export const allTools: Tool[] = [ + ...readTools, // 6 tools + ...analyticsTools, // 5 tools + ...writeTools, // 5 tools + ...systemTools, // 2 tools +]; diff --git a/packages/mcp-server/src/tools/schemas/read-tools.ts b/packages/mcp-server/src/tools/schemas/read-tools.ts new file mode 100644 index 00000000..b246b646 --- /dev/null +++ b/packages/mcp-server/src/tools/schemas/read-tools.ts @@ -0,0 +1,112 @@ +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; + +/** + * Read-only tool schemas for accessing task data and metadata + * These tools do not modify any data + */ + +export const getSyncStatusTool: Tool = { + name: 'get_sync_status', + description: + 'Get sync status for GSD tasks including last sync time, device count, storage usage, and conflict count. Useful for checking overall sync health.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, +}; + +export const listDevicesTool: Tool = { + name: 'list_devices', + description: + 'List all registered devices for the authenticated user. Shows device names, last seen timestamps, and active status. Useful for managing connected devices.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, +}; + +export const getTaskStatsTool: Tool = { + name: 'get_task_stats', + description: + 'Get statistics about tasks including total count, active count, deleted count, and last update timestamp. Provides high-level overview without accessing encrypted task content.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, +}; + +export const listTasksTool: Tool = { + name: 'list_tasks', + description: + 'List all decrypted tasks. Requires GSD_ENCRYPTION_PASSPHRASE to be set. Returns full task details including titles, descriptions, quadrants, tags, subtasks, and dependencies. Optionally filter by quadrant, completion status, or tags.', + inputSchema: { + type: 'object', + properties: { + quadrant: { + type: 'string', + description: + 'Filter by quadrant ID (urgent-important, not-urgent-important, urgent-not-important, not-urgent-not-important)', + enum: [ + 'urgent-important', + 'not-urgent-important', + 'urgent-not-important', + 'not-urgent-not-important', + ], + }, + completed: { + type: 'boolean', + description: 'Filter by completion status (true for completed, false for active)', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Filter by tags (tasks matching any of these tags will be returned)', + }, + }, + required: [], + }, +}; + +export const getTaskTool: Tool = { + name: 'get_task', + description: + 'Get a single decrypted task by ID. Requires GSD_ENCRYPTION_PASSPHRASE to be set. Returns full task details.', + inputSchema: { + type: 'object', + properties: { + taskId: { + type: 'string', + description: 'The unique ID of the task to retrieve', + }, + }, + required: ['taskId'], + }, +}; + +export const searchTasksTool: Tool = { + name: 'search_tasks', + description: + 'Search decrypted tasks by text query. Requires GSD_ENCRYPTION_PASSPHRASE to be set. Searches across task titles, descriptions, tags, and subtask text. Returns matching tasks.', + inputSchema: { + type: 'object', + properties: { + query: { + type: 'string', + description: 'Search query to match against task content', + }, + }, + required: ['query'], + }, +}; + +export const readTools: Tool[] = [ + getSyncStatusTool, + listDevicesTool, + getTaskStatsTool, + listTasksTool, + getTaskTool, + searchTasksTool, +]; diff --git a/packages/mcp-server/src/tools/schemas/system-tools.ts b/packages/mcp-server/src/tools/schemas/system-tools.ts new file mode 100644 index 00000000..75b875fe --- /dev/null +++ b/packages/mcp-server/src/tools/schemas/system-tools.ts @@ -0,0 +1,38 @@ +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; + +/** + * System tool schemas for configuration validation and help + */ + +export const validateConfigTool: Tool = { + name: 'validate_config', + description: + 'Validate MCP server configuration and diagnose issues. Checks environment variables, API connectivity, authentication, encryption, and sync status. Returns detailed diagnostics.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, +}; + +export const getHelpTool: Tool = { + name: 'get_help', + description: + 'Get comprehensive help documentation including available tools, usage examples, common queries, and troubleshooting tips. Perfect for discovering what the GSD MCP server can do.', + inputSchema: { + type: 'object', + properties: { + topic: { + type: 'string', + description: 'Optional help topic: "tools", "analytics", "setup", "examples", or "troubleshooting"', + enum: ['tools', 'analytics', 'setup', 'examples', 'troubleshooting'], + }, + }, + required: [], + }, +}; + +export const systemTools: Tool[] = [ + validateConfigTool, + getHelpTool, +]; diff --git a/packages/mcp-server/src/tools/schemas/write-tools.ts b/packages/mcp-server/src/tools/schemas/write-tools.ts new file mode 100644 index 00000000..170463d4 --- /dev/null +++ b/packages/mcp-server/src/tools/schemas/write-tools.ts @@ -0,0 +1,232 @@ +import type { Tool } from '@modelcontextprotocol/sdk/types.js'; + +/** + * Write operation tool schemas for modifying task data + * All require GSD_ENCRYPTION_PASSPHRASE for encryption + */ + +export const createTaskTool: Tool = { + name: 'create_task', + description: + 'Create a new task with natural language input. Supports all task properties including title, description, urgency, importance, due dates, tags, subtasks, recurrence, and dependencies. Requires GSD_ENCRYPTION_PASSPHRASE.', + inputSchema: { + type: 'object', + properties: { + title: { + type: 'string', + description: 'Task title (required)', + }, + description: { + type: 'string', + description: 'Task description', + }, + urgent: { + type: 'boolean', + description: 'Is this task urgent? (time-sensitive)', + }, + important: { + type: 'boolean', + description: 'Is this task important? (high-value, strategic)', + }, + dueDate: { + type: 'string', + description: 'Due date as ISO 8601 datetime string (e.g., "2025-10-27T12:00:00.000Z")', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Tags for categorization (e.g., ["#work", "#project-alpha"])', + }, + subtasks: { + type: 'array', + items: { + type: 'object', + properties: { + title: { type: 'string' }, + completed: { type: 'boolean' }, + }, + required: ['title', 'completed'], + }, + description: 'Subtasks/checklist items', + }, + recurrence: { + type: 'string', + enum: ['none', 'daily', 'weekly', 'monthly'], + description: 'Recurrence pattern', + }, + dependencies: { + type: 'array', + items: { type: 'string' }, + description: 'Task IDs that must be completed before this task', + }, + }, + required: ['title', 'urgent', 'important'], + }, +}; + +export const updateTaskTool: Tool = { + name: 'update_task', + description: + 'Update an existing task. All fields except ID are optional - only provide fields you want to change. Supports moving between quadrants, updating content, changing due dates, and more. Requires GSD_ENCRYPTION_PASSPHRASE.', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'Task ID (required)', + }, + title: { + type: 'string', + description: 'New task title', + }, + description: { + type: 'string', + description: 'New task description', + }, + urgent: { + type: 'boolean', + description: 'Change urgency (moves between quadrants)', + }, + important: { + type: 'boolean', + description: 'Change importance (moves between quadrants)', + }, + dueDate: { + type: 'string', + description: 'New due date as ISO 8601 datetime string (empty string to clear)', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Replace tags entirely', + }, + subtasks: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'string' }, + title: { type: 'string' }, + completed: { type: 'boolean' }, + }, + required: ['id', 'title', 'completed'], + }, + description: 'Replace subtasks entirely', + }, + recurrence: { + type: 'string', + enum: ['none', 'daily', 'weekly', 'monthly'], + description: 'Change recurrence pattern', + }, + dependencies: { + type: 'array', + items: { type: 'string' }, + description: 'Replace dependencies entirely', + }, + completed: { + type: 'boolean', + description: 'Mark as complete/incomplete', + }, + }, + required: ['id'], + }, +}; + +export const completeTaskTool: Tool = { + name: 'complete_task', + description: + 'Mark a task as complete or incomplete. Quick shortcut for updating completion status. Requires GSD_ENCRYPTION_PASSPHRASE.', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'Task ID', + }, + completed: { + type: 'boolean', + description: 'True to mark complete, false to mark incomplete', + }, + }, + required: ['id', 'completed'], + }, +}; + +export const deleteTaskTool: Tool = { + name: 'delete_task', + description: + 'Permanently delete a task. This action cannot be undone. Use with caution. Requires GSD_ENCRYPTION_PASSPHRASE.', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'Task ID to delete', + }, + }, + required: ['id'], + }, +}; + +export const bulkUpdateTasksTool: Tool = { + name: 'bulk_update_tasks', + description: + 'Update multiple tasks at once. Supports completing, moving quadrants, adding/removing tags, setting due dates, and deleting. Limited to 50 tasks per operation for safety. Requires GSD_ENCRYPTION_PASSPHRASE.', + inputSchema: { + type: 'object', + properties: { + taskIds: { + type: 'array', + items: { type: 'string' }, + description: 'Array of task IDs to update (max 50)', + }, + operation: { + type: 'object', + description: 'Operation to perform on all tasks', + properties: { + type: { + type: 'string', + enum: ['complete', 'move_quadrant', 'add_tags', 'remove_tags', 'set_due_date', 'delete'], + description: 'Type of bulk operation', + }, + // Conditional properties based on type + completed: { + type: 'boolean', + description: 'For type=complete: true/false', + }, + urgent: { + type: 'boolean', + description: 'For type=move_quadrant: urgency', + }, + important: { + type: 'boolean', + description: 'For type=move_quadrant: importance', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'For type=add_tags or remove_tags: array of tags', + }, + dueDate: { + type: 'string', + description: 'For type=set_due_date: ISO 8601 datetime string (empty string to clear)', + }, + }, + required: ['type'], + }, + maxTasks: { + type: 'number', + description: 'Safety limit (default: 50)', + }, + }, + required: ['taskIds', 'operation'], + }, +}; + +export const writeTools: Tool[] = [ + createTaskTool, + updateTaskTool, + completeTaskTool, + deleteTaskTool, + bulkUpdateTasksTool, +]; diff --git a/packages/mcp-server/vitest.config.ts b/packages/mcp-server/vitest.config.ts new file mode 100644 index 00000000..d03a4060 --- /dev/null +++ b/packages/mcp-server/vitest.config.ts @@ -0,0 +1,25 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/**', + 'dist/**', + '**/*.test.ts', + '**/*.spec.ts', + 'src/index.ts', // Main entry will be tested via integration + ], + thresholds: { + statements: 80, + branches: 75, + functions: 80, + lines: 80, + }, + }, + }, +});