@vibescope/mcp-server 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/CHANGELOG.md +84 -84
  2. package/README.md +194 -194
  3. package/dist/api-client/tasks.d.ts +1 -0
  4. package/dist/cli-init.js +21 -21
  5. package/dist/cli.js +26 -26
  6. package/dist/handlers/session.js +3 -1
  7. package/dist/handlers/tasks.js +7 -1
  8. package/dist/handlers/tool-docs.js +1216 -1216
  9. package/dist/index.js +73 -73
  10. package/dist/templates/agent-guidelines.d.ts +1 -1
  11. package/dist/templates/agent-guidelines.js +205 -205
  12. package/dist/templates/help-content.js +1621 -1621
  13. package/dist/tools/bodies-of-work.js +6 -6
  14. package/dist/tools/cloud-agents.js +22 -22
  15. package/dist/tools/milestones.js +2 -2
  16. package/dist/tools/requests.js +1 -1
  17. package/dist/tools/session.js +11 -11
  18. package/dist/tools/sprints.js +9 -9
  19. package/dist/tools/tasks.js +43 -35
  20. package/dist/tools/worktrees.js +14 -14
  21. package/dist/utils.js +11 -11
  22. package/docs/TOOLS.md +2687 -2685
  23. package/package.json +53 -53
  24. package/scripts/generate-docs.ts +212 -212
  25. package/scripts/version-bump.ts +203 -203
  26. package/src/api-client/blockers.ts +86 -86
  27. package/src/api-client/bodies-of-work.ts +194 -194
  28. package/src/api-client/chat.ts +50 -50
  29. package/src/api-client/connectors.ts +152 -152
  30. package/src/api-client/cost.ts +185 -185
  31. package/src/api-client/decisions.ts +87 -87
  32. package/src/api-client/deployment.ts +313 -313
  33. package/src/api-client/discovery.ts +81 -81
  34. package/src/api-client/fallback.ts +52 -52
  35. package/src/api-client/file-checkouts.ts +115 -115
  36. package/src/api-client/findings.ts +100 -100
  37. package/src/api-client/git-issues.ts +88 -88
  38. package/src/api-client/ideas.ts +112 -112
  39. package/src/api-client/index.ts +592 -592
  40. package/src/api-client/milestones.ts +83 -83
  41. package/src/api-client/organizations.ts +185 -185
  42. package/src/api-client/progress.ts +94 -94
  43. package/src/api-client/project.ts +181 -181
  44. package/src/api-client/requests.ts +54 -54
  45. package/src/api-client/session.ts +220 -220
  46. package/src/api-client/sprints.ts +227 -227
  47. package/src/api-client/subtasks.ts +57 -57
  48. package/src/api-client/tasks.ts +451 -450
  49. package/src/api-client/types.ts +32 -32
  50. package/src/api-client/validation.ts +60 -60
  51. package/src/api-client/worktrees.ts +53 -53
  52. package/src/api-client.test.ts +847 -847
  53. package/src/api-client.ts +2728 -2728
  54. package/src/cli-init.ts +558 -558
  55. package/src/cli.test.ts +284 -284
  56. package/src/cli.ts +204 -204
  57. package/src/handlers/__test-setup__.ts +240 -240
  58. package/src/handlers/__test-utils__.ts +89 -89
  59. package/src/handlers/blockers.test.ts +468 -468
  60. package/src/handlers/blockers.ts +172 -172
  61. package/src/handlers/bodies-of-work.test.ts +704 -704
  62. package/src/handlers/bodies-of-work.ts +526 -526
  63. package/src/handlers/chat.test.ts +185 -185
  64. package/src/handlers/chat.ts +101 -101
  65. package/src/handlers/cloud-agents.test.ts +438 -438
  66. package/src/handlers/cloud-agents.ts +156 -156
  67. package/src/handlers/connectors.test.ts +834 -834
  68. package/src/handlers/connectors.ts +229 -229
  69. package/src/handlers/cost.test.ts +462 -462
  70. package/src/handlers/cost.ts +285 -285
  71. package/src/handlers/decisions.test.ts +382 -382
  72. package/src/handlers/decisions.ts +153 -153
  73. package/src/handlers/deployment.test.ts +551 -551
  74. package/src/handlers/deployment.ts +570 -570
  75. package/src/handlers/discovery.test.ts +206 -206
  76. package/src/handlers/discovery.ts +433 -433
  77. package/src/handlers/fallback.test.ts +537 -537
  78. package/src/handlers/fallback.ts +194 -194
  79. package/src/handlers/file-checkouts.test.ts +750 -750
  80. package/src/handlers/file-checkouts.ts +185 -185
  81. package/src/handlers/findings.test.ts +633 -633
  82. package/src/handlers/findings.ts +239 -239
  83. package/src/handlers/git-issues.test.ts +631 -631
  84. package/src/handlers/git-issues.ts +136 -136
  85. package/src/handlers/ideas.test.ts +644 -644
  86. package/src/handlers/ideas.ts +207 -207
  87. package/src/handlers/index.ts +93 -93
  88. package/src/handlers/milestones.test.ts +475 -475
  89. package/src/handlers/milestones.ts +180 -180
  90. package/src/handlers/organizations.test.ts +826 -826
  91. package/src/handlers/organizations.ts +315 -315
  92. package/src/handlers/progress.test.ts +269 -269
  93. package/src/handlers/progress.ts +77 -77
  94. package/src/handlers/project.test.ts +546 -546
  95. package/src/handlers/project.ts +245 -245
  96. package/src/handlers/requests.test.ts +303 -303
  97. package/src/handlers/requests.ts +99 -99
  98. package/src/handlers/roles.test.ts +305 -305
  99. package/src/handlers/roles.ts +219 -219
  100. package/src/handlers/session.test.ts +998 -998
  101. package/src/handlers/session.ts +1107 -1105
  102. package/src/handlers/sprints.test.ts +732 -732
  103. package/src/handlers/sprints.ts +537 -537
  104. package/src/handlers/tasks.test.ts +931 -931
  105. package/src/handlers/tasks.ts +1144 -1137
  106. package/src/handlers/tool-categories.test.ts +66 -66
  107. package/src/handlers/tool-docs.test.ts +511 -511
  108. package/src/handlers/tool-docs.ts +1595 -1595
  109. package/src/handlers/types.test.ts +259 -259
  110. package/src/handlers/types.ts +176 -176
  111. package/src/handlers/validation.test.ts +582 -582
  112. package/src/handlers/validation.ts +164 -164
  113. package/src/handlers/version.ts +63 -63
  114. package/src/index.test.ts +674 -674
  115. package/src/index.ts +884 -884
  116. package/src/setup.test.ts +243 -243
  117. package/src/setup.ts +410 -410
  118. package/src/templates/agent-guidelines.ts +233 -233
  119. package/src/templates/help-content.ts +1751 -1751
  120. package/src/token-tracking.test.ts +463 -463
  121. package/src/token-tracking.ts +167 -167
  122. package/src/tools/blockers.ts +122 -122
  123. package/src/tools/bodies-of-work.ts +283 -283
  124. package/src/tools/chat.ts +72 -72
  125. package/src/tools/cloud-agents.ts +101 -101
  126. package/src/tools/connectors.ts +191 -191
  127. package/src/tools/cost.ts +111 -111
  128. package/src/tools/decisions.ts +111 -111
  129. package/src/tools/deployment.ts +455 -455
  130. package/src/tools/discovery.ts +76 -76
  131. package/src/tools/fallback.ts +111 -111
  132. package/src/tools/features.ts +154 -154
  133. package/src/tools/file-checkouts.ts +145 -145
  134. package/src/tools/findings.ts +101 -101
  135. package/src/tools/git-issues.ts +130 -130
  136. package/src/tools/ideas.ts +162 -162
  137. package/src/tools/index.ts +145 -145
  138. package/src/tools/milestones.ts +118 -118
  139. package/src/tools/organizations.ts +224 -224
  140. package/src/tools/persona-templates.ts +25 -25
  141. package/src/tools/progress.ts +73 -73
  142. package/src/tools/project.ts +210 -210
  143. package/src/tools/requests.ts +68 -68
  144. package/src/tools/roles.ts +112 -112
  145. package/src/tools/session.ts +181 -181
  146. package/src/tools/sprints.ts +298 -298
  147. package/src/tools/tasks.ts +583 -575
  148. package/src/tools/tools.test.ts +222 -222
  149. package/src/tools/types.ts +9 -9
  150. package/src/tools/validation.ts +75 -75
  151. package/src/tools/version.ts +34 -34
  152. package/src/tools/worktrees.ts +66 -66
  153. package/src/tools.test.ts +416 -416
  154. package/src/utils.test.ts +1014 -1014
  155. package/src/utils.ts +586 -586
  156. package/src/validators.test.ts +223 -223
  157. package/src/validators.ts +249 -249
  158. package/src/version.ts +162 -162
  159. package/tsconfig.json +16 -16
  160. package/vitest.config.ts +14 -14
  161. package/dist/tools.d.ts +0 -2
  162. package/dist/tools.js +0 -3602
package/src/index.ts CHANGED
@@ -1,884 +1,884 @@
1
- #!/usr/bin/env node
2
-
3
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
- import {
6
- CallToolRequestSchema,
7
- ListToolsRequestSchema,
8
- type Tool,
9
- } from '@modelcontextprotocol/sdk/types.js';
10
- import { randomUUID } from 'crypto';
11
- import { initApiClient, getApiClient } from './api-client.js';
12
- import {
13
- ValidationError,
14
- validateRequired,
15
- validateUUID,
16
- validateTaskStatus,
17
- validateProjectStatus,
18
- validatePriority,
19
- validateProgressPercentage,
20
- validateEstimatedMinutes,
21
- validateEnvironment,
22
- VALID_TASK_STATUSES,
23
- VALID_PROJECT_STATUSES,
24
- VALID_BLOCKER_STATUSES,
25
- VALID_DEPLOYMENT_STATUSES,
26
- VALID_ENVIRONMENTS,
27
- } from './validators.js';
28
- import {
29
- AGENT_PERSONAS,
30
- FALLBACK_ACTIVITIES,
31
- getRandomFallbackActivity,
32
- selectPersona,
33
- RateLimiter,
34
- extractProjectNameFromGitUrl,
35
- isValidStatusTransition,
36
- getErrorMessage,
37
- } from './utils.js';
38
- import { buildHandlerRegistry, type HandlerContext } from './handlers/index.js';
39
- import { tools } from './tools/index.js';
40
- import {
41
- createTokenUsage,
42
- trackTokenUsage as trackTokens,
43
- setCurrentModel,
44
- type TokenUsage,
45
- } from './token-tracking.js';
46
- import { getUpdateWarning } from './version.js';
47
-
48
- // ============================================================================
49
- // Agent Instance Tracking
50
- // ============================================================================
51
-
52
- // Unique identifier for this agent instance (survives tool calls within same session)
53
- const INSTANCE_ID = randomUUID();
54
-
55
- // Current session ID (set when start_work_session is called)
56
- let currentSessionId: string | null = null;
57
-
58
- // Assigned persona for this agent instance
59
- let currentPersona: string | null = null;
60
-
61
- // Current role for this agent instance
62
- let currentRole: 'developer' | 'validator' | 'deployer' | 'reviewer' | 'maintainer' | null = null;
63
-
64
- // Current project ID for this agent instance
65
- let currentProjectId: string | null = null;
66
-
67
- // Token usage tracking for this session (using token-tracking module)
68
- let sessionTokenUsage: TokenUsage = createTokenUsage();
69
-
70
- // Wrapper function to track token usage with the session's usage object
71
- function trackTokenUsage(toolName: string, args: unknown, response: unknown): void {
72
- trackTokens(sessionTokenUsage, toolName, args, response);
73
- }
74
-
75
- // Global rate limiter instance (60 requests per minute per API key)
76
- const rateLimiter = new RateLimiter(60, 60000);
77
-
78
- // Cleanup expired entries every 5 minutes (store interval ID for cleanup)
79
- const rateLimiterCleanupInterval = setInterval(() => rateLimiter.cleanup(), 5 * 60 * 1000);
80
-
81
- // Cleanup on process exit to prevent memory leaks
82
- process.on('exit', () => clearInterval(rateLimiterCleanupInterval));
83
- process.on('SIGINT', () => { clearInterval(rateLimiterCleanupInterval); process.exit(0); });
84
- process.on('SIGTERM', () => { clearInterval(rateLimiterCleanupInterval); process.exit(0); });
85
-
86
- // Build handler registry from modular handlers
87
- const handlerRegistry = buildHandlerRegistry();
88
-
89
- // ============================================================================
90
- // Embedded Knowledge Base (on-demand help topics)
91
- // ============================================================================
92
-
93
- const KNOWLEDGE_BASE: Record<string, string> = {
94
- getting_started: `# Getting Started
95
- 1. Call start_work_session(git_url, model: "your-model-name") to initialize
96
- - IMPORTANT: Pass your model for accurate cost tracking (e.g., "opus", "sonnet", "gemini", "gpt-4o")
97
- - Check your system prompt for "You are powered by the model named..." to find it
98
- 2. Response includes next_task - start working on it immediately
99
- 3. Use update_task to mark in_progress and track progress
100
- 4. Call complete_task when done - it returns your next task
101
- 5. Use get_help(topic) when you need guidance on specific workflows`,
102
-
103
- tasks: `# Task Workflow
104
- - Mark task in_progress with update_task before starting
105
- - Update progress_percentage regularly (every 15-20% progress)
106
- - Include progress_note to auto-log milestones
107
- - One task at a time - complete current before starting another
108
- - complete_task returns next_task and context counts (validation, blockers, deployment)
109
- - Priority: 1=highest, 5=lowest`,
110
-
111
- validation: `# Task Validation
112
- Completed tasks need validation before deployment. PRIORITIZE validation over new tasks.
113
- 1. Check: get_tasks_awaiting_validation(project_id)
114
- 2. Claim: claim_validation(task_id) - marks "being reviewed" on dashboard
115
- 3. Review code changes and run tests
116
- 4. Complete: validate_task(task_id, approved: true/false, validation_notes: "...")
117
- Self-validation allowed when single-agent or to unblock deployment.`,
118
-
119
- deployment: `# Deployment Workflow
120
- 1. Ensure all completed tasks are validated first
121
- 2. request_deployment(project_id, environment: "production")
122
- 3. claim_deployment_validation(project_id) - claim for validation
123
- 4. Run: pnpm build && pnpm test
124
- 5. report_validation(project_id, build_passed: true, tests_passed: true)
125
- 6. start_deployment(project_id) - returns project's deployment_instructions
126
- 7. Follow the instructions (e.g., push to main, run deploy command)
127
- 8. complete_deployment(project_id, success: true, summary: "...")`,
128
-
129
- git: `# Git Workflow
130
- Call get_git_workflow(project_id) for project-specific config.
131
- Workflows: none, trunk-based, github-flow, git-flow
132
- - trunk-based: commit directly to main
133
- - github-flow: feature branches, merge via PR
134
- - git-flow: develop/release branches
135
- Update task git_branch when working on a branch.`,
136
-
137
- blockers: `# Working with Blockers
138
- When stuck and need human input:
139
- 1. add_blocker(project_id, description: "What's blocking")
140
- 2. Ask your question to the user
141
- 3. resolve_blocker(blocker_id, resolution_note: "How resolved")
142
- Only use for genuine blockers requiring decisions - not routine questions.`,
143
-
144
- milestones: `# Task Milestones
145
- For complex tasks, break into milestones:
146
- 1. add_milestone(task_id, title: "Design schema")
147
- 2. add_milestone(task_id, title: "Implement API")
148
- 3. Update with complete_milestone(milestone_id) as you go
149
- Dashboard shows progress bar with completed/total.`,
150
-
151
- fallback: `# Fallback Activities
152
- When no tasks available, get_next_task suggests activities:
153
- - feature_ideation, code_review, performance_audit
154
- - security_review, test_coverage, documentation_review
155
- 1. start_fallback_activity(project_id, activity: "code_review")
156
- 2. Do the work, use add_finding for issues, add_idea for improvements
157
- 3. stop_fallback_activity(project_id, summary: "...")`,
158
-
159
- session: `# Session Management
160
- - start_work_session(git_url, model) initializes and returns next_task
161
- - ALWAYS pass model parameter for cost tracking (e.g., "opus", "sonnet", "gemini", "gpt-4o")
162
- - Use mode:'lite' (default) or mode:'full' for complete context
163
- - heartbeat every 30-60 seconds maintains active status
164
- - end_work_session releases claimed tasks and returns summary
165
- - NEVER STOP: After completing a task, immediately start the next one
166
- - When context grows large: /clear then start_work_session to continue fresh
167
- - Your progress is saved to the dashboard - nothing is lost on /clear`,
168
-
169
- tokens: `# Token Efficiency
170
- Be mindful of token costs - every tool call has a cost.
171
- - Use mode:'lite' (default) - saves ~85% vs full mode
172
- - Call get_token_usage() to check consumption
173
- - /compact when context grows large
174
- - Batch related updates when possible
175
- - Trust lite mode; only use full mode for initial exploration`,
176
-
177
- topics: `# Available Help Topics
178
- - getting_started: Basic workflow overview
179
- - tasks: Working on tasks, progress tracking
180
- - validation: Cross-agent task validation
181
- - deployment: Deployment coordination
182
- - git: Git workflow configuration
183
- - blockers: Handling blockers
184
- - milestones: Breaking down complex tasks
185
- - fallback: Background activities when idle
186
- - session: Session management
187
- - tokens: Token efficiency tips`,
188
- };
189
-
190
-
191
- // ============================================================================
192
- // Types
193
- // ============================================================================
194
-
195
- interface Database {
196
- public: {
197
- Tables: {
198
- api_keys: {
199
- Row: {
200
- id: string;
201
- user_id: string;
202
- key_hash: string;
203
- name: string;
204
- last_used_at: string | null;
205
- };
206
- Insert: {
207
- id?: string;
208
- user_id: string;
209
- key_hash: string;
210
- name: string;
211
- last_used_at?: string | null;
212
- };
213
- Update: {
214
- id?: string;
215
- user_id?: string;
216
- key_hash?: string;
217
- name?: string;
218
- last_used_at?: string | null;
219
- };
220
- };
221
- projects: {
222
- Row: {
223
- id: string;
224
- user_id: string;
225
- name: string;
226
- description: string | null;
227
- goal: string | null;
228
- status: string;
229
- git_url: string | null;
230
- agent_instructions: string | null;
231
- tech_stack: string[] | null;
232
- git_workflow: 'none' | 'trunk-based' | 'github-flow' | 'git-flow';
233
- git_main_branch: string;
234
- git_develop_branch: string | null;
235
- git_auto_branch: boolean;
236
- git_auto_tag: boolean;
237
- created_at: string;
238
- updated_at: string;
239
- };
240
- Insert: {
241
- id?: string;
242
- user_id: string;
243
- name: string;
244
- description?: string | null;
245
- goal?: string | null;
246
- status?: string;
247
- git_url?: string | null;
248
- agent_instructions?: string | null;
249
- tech_stack?: string[] | null;
250
- git_workflow?: 'none' | 'trunk-based' | 'github-flow' | 'git-flow';
251
- git_main_branch?: string;
252
- git_develop_branch?: string | null;
253
- git_auto_branch?: boolean;
254
- git_auto_tag?: boolean;
255
- created_at?: string;
256
- updated_at?: string;
257
- };
258
- Update: {
259
- id?: string;
260
- user_id?: string;
261
- name?: string;
262
- description?: string | null;
263
- goal?: string | null;
264
- status?: string;
265
- git_url?: string | null;
266
- agent_instructions?: string | null;
267
- tech_stack?: string[] | null;
268
- git_workflow?: 'none' | 'trunk-based' | 'github-flow' | 'git-flow';
269
- git_main_branch?: string;
270
- git_develop_branch?: string | null;
271
- git_auto_branch?: boolean;
272
- git_auto_tag?: boolean;
273
- created_at?: string;
274
- updated_at?: string;
275
- };
276
- };
277
- tasks: {
278
- Row: {
279
- id: string;
280
- project_id: string;
281
- title: string;
282
- description: string | null;
283
- priority: number;
284
- status: string;
285
- created_by: string;
286
- created_at: string;
287
- completed_at: string | null;
288
- progress_percentage: number;
289
- estimated_minutes: number | null;
290
- started_at: string | null;
291
- working_agent_session_id: string | null;
292
- git_branch: string | null;
293
- references: { url: string; label?: string }[] | null;
294
- };
295
- Insert: {
296
- id?: string;
297
- project_id: string;
298
- title: string;
299
- description?: string | null;
300
- priority?: number;
301
- status?: string;
302
- created_by?: string;
303
- created_at?: string;
304
- completed_at?: string | null;
305
- progress_percentage?: number;
306
- estimated_minutes?: number | null;
307
- started_at?: string | null;
308
- working_agent_session_id?: string | null;
309
- git_branch?: string | null;
310
- references?: { url: string; label?: string }[] | null;
311
- };
312
- Update: {
313
- id?: string;
314
- project_id?: string;
315
- title?: string;
316
- description?: string | null;
317
- priority?: number;
318
- status?: string;
319
- created_by?: string;
320
- created_at?: string;
321
- completed_at?: string | null;
322
- progress_percentage?: number;
323
- estimated_minutes?: number | null;
324
- started_at?: string | null;
325
- working_agent_session_id?: string | null;
326
- git_branch?: string | null;
327
- references?: { url: string; label?: string }[] | null;
328
- };
329
- };
330
- progress_logs: {
331
- Row: {
332
- id: string;
333
- project_id: string;
334
- task_id: string | null;
335
- summary: string;
336
- details: string | null;
337
- created_by: string;
338
- created_at: string;
339
- };
340
- Insert: {
341
- id?: string;
342
- project_id: string;
343
- task_id?: string | null;
344
- summary: string;
345
- details?: string | null;
346
- created_by?: string;
347
- created_at?: string;
348
- };
349
- Update: {
350
- id?: string;
351
- project_id?: string;
352
- task_id?: string | null;
353
- summary?: string;
354
- details?: string | null;
355
- created_by?: string;
356
- created_at?: string;
357
- };
358
- };
359
- blockers: {
360
- Row: {
361
- id: string;
362
- project_id: string;
363
- description: string;
364
- status: string;
365
- created_by: string;
366
- created_at: string;
367
- resolved_at: string | null;
368
- resolution_note: string | null;
369
- };
370
- Insert: {
371
- id?: string;
372
- project_id: string;
373
- description: string;
374
- status?: string;
375
- created_by?: string;
376
- created_at?: string;
377
- resolved_at?: string | null;
378
- resolution_note?: string | null;
379
- };
380
- Update: {
381
- id?: string;
382
- project_id?: string;
383
- description?: string;
384
- status?: string;
385
- created_by?: string;
386
- created_at?: string;
387
- resolved_at?: string | null;
388
- resolution_note?: string | null;
389
- };
390
- };
391
- ideas: {
392
- Row: {
393
- id: string;
394
- project_id: string;
395
- title: string;
396
- description: string | null;
397
- created_by: string;
398
- created_at: string;
399
- };
400
- Insert: {
401
- id?: string;
402
- project_id: string;
403
- title: string;
404
- description?: string | null;
405
- created_by?: string;
406
- created_at?: string;
407
- };
408
- Update: {
409
- id?: string;
410
- project_id?: string;
411
- title?: string;
412
- description?: string | null;
413
- created_by?: string;
414
- created_at?: string;
415
- };
416
- };
417
- decisions: {
418
- Row: {
419
- id: string;
420
- project_id: string;
421
- title: string;
422
- description: string;
423
- rationale: string | null;
424
- alternatives_considered: string[] | null;
425
- created_by: string;
426
- created_at: string;
427
- };
428
- Insert: {
429
- id?: string;
430
- project_id: string;
431
- title: string;
432
- description: string;
433
- rationale?: string | null;
434
- alternatives_considered?: string[] | null;
435
- created_by?: string;
436
- created_at?: string;
437
- };
438
- Update: {
439
- id?: string;
440
- project_id?: string;
441
- title?: string;
442
- description?: string;
443
- rationale?: string | null;
444
- alternatives_considered?: string[] | null;
445
- created_by?: string;
446
- created_at?: string;
447
- };
448
- };
449
- agent_sessions: {
450
- Row: {
451
- id: string;
452
- api_key_id: string;
453
- project_id: string;
454
- last_synced_at: string;
455
- agent_name: string | null;
456
- agent_version: string | null;
457
- instance_id: string | null;
458
- current_task_id: string | null;
459
- status: string;
460
- };
461
- Insert: {
462
- id?: string;
463
- api_key_id: string;
464
- project_id: string;
465
- last_synced_at?: string;
466
- agent_name?: string | null;
467
- agent_version?: string | null;
468
- instance_id?: string | null;
469
- current_task_id?: string | null;
470
- status?: string;
471
- };
472
- Update: {
473
- id?: string;
474
- api_key_id?: string;
475
- project_id?: string;
476
- last_synced_at?: string;
477
- agent_name?: string | null;
478
- agent_version?: string | null;
479
- instance_id?: string | null;
480
- current_task_id?: string | null;
481
- status?: string;
482
- };
483
- };
484
- agent_heartbeats: {
485
- Row: {
486
- id: string;
487
- session_id: string;
488
- created_at: string;
489
- };
490
- Insert: {
491
- id?: string;
492
- session_id: string;
493
- created_at?: string;
494
- };
495
- Update: {
496
- id?: string;
497
- session_id?: string;
498
- created_at?: string;
499
- };
500
- };
501
- task_milestones: {
502
- Row: {
503
- id: string;
504
- task_id: string;
505
- title: string;
506
- description: string | null;
507
- order_index: number;
508
- status: string;
509
- created_by: string;
510
- created_at: string;
511
- completed_at: string | null;
512
- created_by_session_id: string | null;
513
- };
514
- Insert: {
515
- id?: string;
516
- task_id: string;
517
- title: string;
518
- description?: string | null;
519
- order_index?: number;
520
- status?: string;
521
- created_by?: string;
522
- created_at?: string;
523
- completed_at?: string | null;
524
- created_by_session_id?: string | null;
525
- };
526
- Update: {
527
- id?: string;
528
- task_id?: string;
529
- title?: string;
530
- description?: string | null;
531
- order_index?: number;
532
- status?: string;
533
- created_by?: string;
534
- created_at?: string;
535
- completed_at?: string | null;
536
- created_by_session_id?: string | null;
537
- };
538
- };
539
- };
540
- Views: Record<string, never>;
541
- Functions: Record<string, never>;
542
- Enums: Record<string, never>;
543
- };
544
- }
545
-
546
- interface AuthContext {
547
- userId: string;
548
- apiKeyId: string;
549
- organizationId?: string;
550
- scope: 'personal' | 'organization';
551
- }
552
-
553
- interface UserUpdates {
554
- tasks: Array<{ id: string; title: string; created_at: string }>;
555
- blockers: Array<{ id: string; description: string; created_at: string }>;
556
- ideas: Array<{ id: string; title: string; created_at: string }>;
557
- }
558
-
559
- // ============================================================================
560
- // Configuration
561
- // ============================================================================
562
-
563
- const API_KEY = process.env.VIBESCOPE_API_KEY;
564
-
565
- if (!API_KEY) {
566
- console.error('Missing required environment variable: VIBESCOPE_API_KEY');
567
- process.exit(1);
568
- }
569
-
570
- // Initialize the API client
571
- initApiClient({ apiKey: API_KEY });
572
-
573
- // ============================================================================
574
- // Authentication
575
- // ============================================================================
576
-
577
- async function validateApiKey(timeoutMs?: number): Promise<AuthContext | null> {
578
- const apiClient = getApiClient();
579
- const response = await apiClient.validateAuth(timeoutMs ? { timeoutMs } : undefined);
580
-
581
- if (!response.ok || !response.data?.valid) {
582
- return null;
583
- }
584
-
585
- return {
586
- userId: response.data.user_id,
587
- apiKeyId: response.data.api_key_id,
588
- scope: 'personal', // API handles authorization, scope not needed locally
589
- };
590
- }
591
-
592
- // Deferred auth: started eagerly but awaited on first tool call
593
- let deferredAuthPromise: Promise<AuthContext | null> | null = null;
594
- let resolvedAuth: AuthContext | null = null;
595
- let authError: string | null = null;
596
-
597
- function startDeferredAuth(): void {
598
- deferredAuthPromise = validateApiKey(10000)
599
- .then((auth) => {
600
- resolvedAuth = auth;
601
- if (!auth) authError = 'Invalid API key';
602
- return auth;
603
- })
604
- .catch((err) => {
605
- authError = err instanceof Error ? err.message : 'Auth validation failed';
606
- return null;
607
- });
608
- }
609
-
610
- async function getAuth(): Promise<AuthContext> {
611
- // If already resolved, return immediately
612
- if (resolvedAuth) return resolvedAuth;
613
-
614
- // Await the deferred promise
615
- if (deferredAuthPromise) {
616
- const auth = await deferredAuthPromise;
617
- if (auth) return auth;
618
- }
619
-
620
- // Auth failed — return structured error
621
- const troubleshooting = [
622
- 'MCP auth validation failed.',
623
- authError ? `Reason: ${authError}` : '',
624
- 'Troubleshooting:',
625
- '1. Check your API key is valid at https://vibescope.dev/dashboard/settings',
626
- '2. Verify VIBESCOPE_API_KEY environment variable is set correctly',
627
- '3. Check network connectivity to vibescope.dev',
628
- '4. Restart Claude Code and try again',
629
- ].filter(Boolean).join('\n');
630
-
631
- throw new Error(troubleshooting);
632
- }
633
-
634
- // Deferred update warning: fire-and-forget, delivered on first tool call
635
- let updateWarningPromise: Promise<string | null> | null = null;
636
- let resolvedUpdateWarning: string | null | undefined = undefined; // undefined = not yet resolved
637
-
638
- function startDeferredUpdateCheck(): void {
639
- updateWarningPromise = getUpdateWarning().catch(() => null);
640
- updateWarningPromise.then((warning) => {
641
- resolvedUpdateWarning = warning;
642
- });
643
- }
644
-
645
- function consumeUpdateWarning(): string | null {
646
- if (resolvedUpdateWarning === undefined) return null; // not ready yet
647
- const warning = resolvedUpdateWarning;
648
- resolvedUpdateWarning = null; // deliver only once
649
- return warning;
650
- }
651
-
652
- // Tool definitions imported from tools.ts
653
-
654
-
655
- // ============================================================================
656
- // Tool Handlers
657
- // ============================================================================
658
-
659
- async function handleTool(
660
- auth: AuthContext,
661
- name: string,
662
- args: Record<string, unknown>
663
- ): Promise<{ result: unknown; user_updates?: UserUpdates; reminder?: string }> {
664
- // Update session on every tool call via API:
665
- // - last_synced_at: keeps session alive and visible as "active" on dashboard
666
- // - token tracking: synced periodically for cost monitoring
667
- // Skip for start_work_session since it handles its own session setup
668
- if (currentSessionId && name !== 'start_work_session') {
669
- const apiClient = getApiClient();
670
- // Fire and forget - don't block tool execution on session sync
671
- apiClient.syncSession(currentSessionId).catch(() => {
672
- // Silently ignore sync errors - session tracking is non-critical
673
- });
674
- }
675
-
676
- // Check if handler exists in the modular registry
677
- const handler = handlerRegistry[name];
678
- if (handler) {
679
- // Build handler context
680
- const ctx: HandlerContext = {
681
- auth,
682
- session: {
683
- instanceId: INSTANCE_ID,
684
- currentSessionId,
685
- currentPersona,
686
- currentRole,
687
- currentProjectId,
688
- tokenUsage: sessionTokenUsage,
689
- },
690
- updateSession: (updates) => {
691
- if (updates.currentSessionId !== undefined) currentSessionId = updates.currentSessionId;
692
- if (updates.currentPersona !== undefined) currentPersona = updates.currentPersona;
693
- if (updates.currentRole !== undefined) currentRole = updates.currentRole;
694
- if (updates.currentProjectId !== undefined) currentProjectId = updates.currentProjectId;
695
- if (updates.tokenUsage !== undefined) sessionTokenUsage = updates.tokenUsage;
696
- },
697
- };
698
-
699
- const handlerResult = await handler(args, ctx);
700
- return handlerResult as { result: unknown; user_updates?: UserUpdates; reminder?: string };
701
- }
702
-
703
- throw new Error(`Unknown tool: ${name}`);
704
- }
705
-
706
- // ============================================================================
707
- // Server Setup
708
- // ============================================================================
709
-
710
- async function main() {
711
- // Start auth validation eagerly in background (10s timeout) — don't block startup
712
- startDeferredAuth();
713
-
714
- // Start update check in background — delivered on first tool call if available
715
- startDeferredUpdateCheck();
716
-
717
- const server = new Server(
718
- {
719
- name: 'vibescope',
720
- version: '0.1.0',
721
- },
722
- {
723
- capabilities: {
724
- tools: {},
725
- },
726
- instructions: 'Vibescope MCP server - AI project tracking and coordination tools.',
727
- }
728
- );
729
-
730
- // List available tools
731
- server.setRequestHandler(ListToolsRequestSchema, async () => {
732
- return { tools };
733
- });
734
-
735
- // Get reminder nudge - only for critical workflow reminders to save context
736
- function getReminder(toolName: string): string | null {
737
- // Most reminders removed to reduce context bloat - instructions are in CLAUDE.md
738
- // Only keep critical continuation reminder for complete_task
739
- const reminders: Record<string, string> = {
740
- 'complete_task': 'CONTINUE WORKING: Call get_next_task or start the next_task. If awaiting_validation tasks exist, validate them FIRST before new work. If context is large, run /clear then start_work_session to refresh.',
741
- 'batch_complete_tasks': 'CONTINUE WORKING: Call get_next_task. If awaiting_validation tasks exist, validate them FIRST before new work.',
742
- };
743
- return reminders[toolName] ?? null;
744
- }
745
-
746
- // Handle tool calls
747
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
748
- const { name, arguments: args } = request.params;
749
-
750
- // Await deferred auth on first tool call (usually already resolved)
751
- let auth: AuthContext;
752
- try {
753
- auth = await getAuth();
754
- } catch (err) {
755
- return {
756
- content: [
757
- {
758
- type: 'text',
759
- text: err instanceof Error ? err.message : 'Auth validation failed',
760
- },
761
- ],
762
- isError: true,
763
- };
764
- }
765
-
766
- // Check rate limit
767
- const rateCheck = rateLimiter.check(auth.apiKeyId);
768
- if (!rateCheck.allowed) {
769
- const resetSeconds = Math.ceil(rateCheck.resetIn / 1000);
770
- return {
771
- content: [
772
- {
773
- type: 'text',
774
- text: `Rate limit exceeded. Too many requests. Please wait ${resetSeconds} seconds before trying again. (Limit: 60 requests per minute)`,
775
- },
776
- ],
777
- isError: true,
778
- };
779
- }
780
-
781
- try {
782
- const toolArgs = (args as Record<string, unknown>) || {};
783
- const { result, user_updates } = await handleTool(
784
- auth,
785
- name,
786
- toolArgs
787
- );
788
-
789
- // Track token usage for this call (both input args and output result)
790
- trackTokenUsage(name, toolArgs, result);
791
-
792
- const content: { type: 'text'; text: string }[] = [
793
- {
794
- type: 'text',
795
- text: JSON.stringify(result, null, 2),
796
- },
797
- ];
798
-
799
- // Deliver update warning on first tool call (if available)
800
- const updateWarning = consumeUpdateWarning();
801
- if (updateWarning) {
802
- content.push({
803
- type: 'text',
804
- text: `\n--- ${updateWarning} ---`,
805
- });
806
- }
807
-
808
- // Include reminder nudge if applicable
809
- const reminder = getReminder(name);
810
- if (reminder) {
811
- content.push({
812
- type: 'text',
813
- text: `\n--- ${reminder} ---`,
814
- });
815
- }
816
-
817
- // Include user updates only if there are new items (null means no updates)
818
- if (user_updates) {
819
- content.push({
820
- type: 'text',
821
- text: `\n--- New User Updates ---\n${JSON.stringify(user_updates, null, 2)}`,
822
- });
823
- }
824
-
825
- // Add rate limit warning if getting low
826
- if (rateCheck.remaining < 10) {
827
- content.push({
828
- type: 'text',
829
- text: `\n--- Rate Limit Warning: ${rateCheck.remaining} requests remaining this minute ---`,
830
- });
831
- }
832
-
833
- return { content };
834
- } catch (error) {
835
- // Handle validation errors with structured output
836
- if (error instanceof ValidationError) {
837
- return {
838
- content: [
839
- {
840
- type: 'text',
841
- text: JSON.stringify(error.toJSON(), null, 2),
842
- },
843
- ],
844
- isError: true,
845
- };
846
- }
847
-
848
- // Handle database errors with better context
849
- const errorMessage = getErrorMessage(error);
850
- let hint: string | undefined;
851
-
852
- if (errorMessage.includes('violates foreign key constraint')) {
853
- hint = 'The referenced ID does not exist. Check that the project_id, task_id, or other IDs are correct.';
854
- } else if (errorMessage.includes('duplicate key')) {
855
- hint = 'A record with this identifier already exists.';
856
- } else if (errorMessage.includes('not found') || errorMessage.includes('no rows')) {
857
- hint = 'The requested resource was not found. Verify the ID is correct.';
858
- }
859
-
860
- return {
861
- content: [
862
- {
863
- type: 'text',
864
- text: JSON.stringify({
865
- error: 'operation_failed',
866
- message: errorMessage,
867
- hint,
868
- }, null, 2),
869
- },
870
- ],
871
- isError: true,
872
- };
873
- }
874
- });
875
-
876
- // Start server
877
- const transport = new StdioServerTransport();
878
- await server.connect(transport);
879
- }
880
-
881
- main().catch((error) => {
882
- console.error('Fatal error:', error);
883
- process.exit(1);
884
- });
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ type Tool,
9
+ } from '@modelcontextprotocol/sdk/types.js';
10
+ import { randomUUID } from 'crypto';
11
+ import { initApiClient, getApiClient } from './api-client.js';
12
+ import {
13
+ ValidationError,
14
+ validateRequired,
15
+ validateUUID,
16
+ validateTaskStatus,
17
+ validateProjectStatus,
18
+ validatePriority,
19
+ validateProgressPercentage,
20
+ validateEstimatedMinutes,
21
+ validateEnvironment,
22
+ VALID_TASK_STATUSES,
23
+ VALID_PROJECT_STATUSES,
24
+ VALID_BLOCKER_STATUSES,
25
+ VALID_DEPLOYMENT_STATUSES,
26
+ VALID_ENVIRONMENTS,
27
+ } from './validators.js';
28
+ import {
29
+ AGENT_PERSONAS,
30
+ FALLBACK_ACTIVITIES,
31
+ getRandomFallbackActivity,
32
+ selectPersona,
33
+ RateLimiter,
34
+ extractProjectNameFromGitUrl,
35
+ isValidStatusTransition,
36
+ getErrorMessage,
37
+ } from './utils.js';
38
+ import { buildHandlerRegistry, type HandlerContext } from './handlers/index.js';
39
+ import { tools } from './tools/index.js';
40
+ import {
41
+ createTokenUsage,
42
+ trackTokenUsage as trackTokens,
43
+ setCurrentModel,
44
+ type TokenUsage,
45
+ } from './token-tracking.js';
46
+ import { getUpdateWarning } from './version.js';
47
+
48
+ // ============================================================================
49
+ // Agent Instance Tracking
50
+ // ============================================================================
51
+
52
+ // Unique identifier for this agent instance (survives tool calls within same session)
53
+ const INSTANCE_ID = randomUUID();
54
+
55
+ // Current session ID (set when start_work_session is called)
56
+ let currentSessionId: string | null = null;
57
+
58
+ // Assigned persona for this agent instance
59
+ let currentPersona: string | null = null;
60
+
61
+ // Current role for this agent instance
62
+ let currentRole: 'developer' | 'validator' | 'deployer' | 'reviewer' | 'maintainer' | null = null;
63
+
64
+ // Current project ID for this agent instance
65
+ let currentProjectId: string | null = null;
66
+
67
+ // Token usage tracking for this session (using token-tracking module)
68
+ let sessionTokenUsage: TokenUsage = createTokenUsage();
69
+
70
+ // Wrapper function to track token usage with the session's usage object
71
+ function trackTokenUsage(toolName: string, args: unknown, response: unknown): void {
72
+ trackTokens(sessionTokenUsage, toolName, args, response);
73
+ }
74
+
75
+ // Global rate limiter instance (60 requests per minute per API key)
76
+ const rateLimiter = new RateLimiter(60, 60000);
77
+
78
+ // Cleanup expired entries every 5 minutes (store interval ID for cleanup)
79
+ const rateLimiterCleanupInterval = setInterval(() => rateLimiter.cleanup(), 5 * 60 * 1000);
80
+
81
+ // Cleanup on process exit to prevent memory leaks
82
+ process.on('exit', () => clearInterval(rateLimiterCleanupInterval));
83
+ process.on('SIGINT', () => { clearInterval(rateLimiterCleanupInterval); process.exit(0); });
84
+ process.on('SIGTERM', () => { clearInterval(rateLimiterCleanupInterval); process.exit(0); });
85
+
86
+ // Build handler registry from modular handlers
87
+ const handlerRegistry = buildHandlerRegistry();
88
+
89
+ // ============================================================================
90
+ // Embedded Knowledge Base (on-demand help topics)
91
+ // ============================================================================
92
+
93
+ const KNOWLEDGE_BASE: Record<string, string> = {
94
+ getting_started: `# Getting Started
95
+ 1. Call start_work_session(git_url, model: "your-model-name") to initialize
96
+ - IMPORTANT: Pass your model for accurate cost tracking (e.g., "opus", "sonnet", "gemini", "gpt-4o")
97
+ - Check your system prompt for "You are powered by the model named..." to find it
98
+ 2. Response includes next_task - start working on it immediately
99
+ 3. Use update_task to mark in_progress and track progress
100
+ 4. Call complete_task when done - it returns your next task
101
+ 5. Use get_help(topic) when you need guidance on specific workflows`,
102
+
103
+ tasks: `# Task Workflow
104
+ - Mark task in_progress with update_task before starting
105
+ - Update progress_percentage regularly (every 15-20% progress)
106
+ - Include progress_note to auto-log milestones
107
+ - One task at a time - complete current before starting another
108
+ - complete_task returns next_task and context counts (validation, blockers, deployment)
109
+ - Priority: 1=highest, 5=lowest`,
110
+
111
+ validation: `# Task Validation
112
+ Completed tasks need validation before deployment. PRIORITIZE validation over new tasks.
113
+ 1. Check: get_tasks_awaiting_validation(project_id)
114
+ 2. Claim: claim_validation(task_id) - marks "being reviewed" on dashboard
115
+ 3. Review code changes and run tests
116
+ 4. Complete: validate_task(task_id, approved: true/false, validation_notes: "...")
117
+ Self-validation allowed when single-agent or to unblock deployment.`,
118
+
119
+ deployment: `# Deployment Workflow
120
+ 1. Ensure all completed tasks are validated first
121
+ 2. request_deployment(project_id, environment: "production")
122
+ 3. claim_deployment_validation(project_id) - claim for validation
123
+ 4. Run: pnpm build && pnpm test
124
+ 5. report_validation(project_id, build_passed: true, tests_passed: true)
125
+ 6. start_deployment(project_id) - returns project's deployment_instructions
126
+ 7. Follow the instructions (e.g., push to main, run deploy command)
127
+ 8. complete_deployment(project_id, success: true, summary: "...")`,
128
+
129
+ git: `# Git Workflow
130
+ Call get_git_workflow(project_id) for project-specific config.
131
+ Workflows: none, trunk-based, github-flow, git-flow
132
+ - trunk-based: commit directly to main
133
+ - github-flow: feature branches, merge via PR
134
+ - git-flow: develop/release branches
135
+ Update task git_branch when working on a branch.`,
136
+
137
+ blockers: `# Working with Blockers
138
+ When stuck and need human input:
139
+ 1. add_blocker(project_id, description: "What's blocking")
140
+ 2. Ask your question to the user
141
+ 3. resolve_blocker(blocker_id, resolution_note: "How resolved")
142
+ Only use for genuine blockers requiring decisions - not routine questions.`,
143
+
144
+ milestones: `# Task Milestones
145
+ For complex tasks, break into milestones:
146
+ 1. add_milestone(task_id, title: "Design schema")
147
+ 2. add_milestone(task_id, title: "Implement API")
148
+ 3. Update with complete_milestone(milestone_id) as you go
149
+ Dashboard shows progress bar with completed/total.`,
150
+
151
+ fallback: `# Fallback Activities
152
+ When no tasks available, get_next_task suggests activities:
153
+ - feature_ideation, code_review, performance_audit
154
+ - security_review, test_coverage, documentation_review
155
+ 1. start_fallback_activity(project_id, activity: "code_review")
156
+ 2. Do the work, use add_finding for issues, add_idea for improvements
157
+ 3. stop_fallback_activity(project_id, summary: "...")`,
158
+
159
+ session: `# Session Management
160
+ - start_work_session(git_url, model) initializes and returns next_task
161
+ - ALWAYS pass model parameter for cost tracking (e.g., "opus", "sonnet", "gemini", "gpt-4o")
162
+ - Use mode:'lite' (default) or mode:'full' for complete context
163
+ - heartbeat every 30-60 seconds maintains active status
164
+ - end_work_session releases claimed tasks and returns summary
165
+ - NEVER STOP: After completing a task, immediately start the next one
166
+ - When context grows large: /clear then start_work_session to continue fresh
167
+ - Your progress is saved to the dashboard - nothing is lost on /clear`,
168
+
169
+ tokens: `# Token Efficiency
170
+ Be mindful of token costs - every tool call has a cost.
171
+ - Use mode:'lite' (default) - saves ~85% vs full mode
172
+ - Call get_token_usage() to check consumption
173
+ - /compact when context grows large
174
+ - Batch related updates when possible
175
+ - Trust lite mode; only use full mode for initial exploration`,
176
+
177
+ topics: `# Available Help Topics
178
+ - getting_started: Basic workflow overview
179
+ - tasks: Working on tasks, progress tracking
180
+ - validation: Cross-agent task validation
181
+ - deployment: Deployment coordination
182
+ - git: Git workflow configuration
183
+ - blockers: Handling blockers
184
+ - milestones: Breaking down complex tasks
185
+ - fallback: Background activities when idle
186
+ - session: Session management
187
+ - tokens: Token efficiency tips`,
188
+ };
189
+
190
+
191
+ // ============================================================================
192
+ // Types
193
+ // ============================================================================
194
+
195
+ interface Database {
196
+ public: {
197
+ Tables: {
198
+ api_keys: {
199
+ Row: {
200
+ id: string;
201
+ user_id: string;
202
+ key_hash: string;
203
+ name: string;
204
+ last_used_at: string | null;
205
+ };
206
+ Insert: {
207
+ id?: string;
208
+ user_id: string;
209
+ key_hash: string;
210
+ name: string;
211
+ last_used_at?: string | null;
212
+ };
213
+ Update: {
214
+ id?: string;
215
+ user_id?: string;
216
+ key_hash?: string;
217
+ name?: string;
218
+ last_used_at?: string | null;
219
+ };
220
+ };
221
+ projects: {
222
+ Row: {
223
+ id: string;
224
+ user_id: string;
225
+ name: string;
226
+ description: string | null;
227
+ goal: string | null;
228
+ status: string;
229
+ git_url: string | null;
230
+ agent_instructions: string | null;
231
+ tech_stack: string[] | null;
232
+ git_workflow: 'none' | 'trunk-based' | 'github-flow' | 'git-flow';
233
+ git_main_branch: string;
234
+ git_develop_branch: string | null;
235
+ git_auto_branch: boolean;
236
+ git_auto_tag: boolean;
237
+ created_at: string;
238
+ updated_at: string;
239
+ };
240
+ Insert: {
241
+ id?: string;
242
+ user_id: string;
243
+ name: string;
244
+ description?: string | null;
245
+ goal?: string | null;
246
+ status?: string;
247
+ git_url?: string | null;
248
+ agent_instructions?: string | null;
249
+ tech_stack?: string[] | null;
250
+ git_workflow?: 'none' | 'trunk-based' | 'github-flow' | 'git-flow';
251
+ git_main_branch?: string;
252
+ git_develop_branch?: string | null;
253
+ git_auto_branch?: boolean;
254
+ git_auto_tag?: boolean;
255
+ created_at?: string;
256
+ updated_at?: string;
257
+ };
258
+ Update: {
259
+ id?: string;
260
+ user_id?: string;
261
+ name?: string;
262
+ description?: string | null;
263
+ goal?: string | null;
264
+ status?: string;
265
+ git_url?: string | null;
266
+ agent_instructions?: string | null;
267
+ tech_stack?: string[] | null;
268
+ git_workflow?: 'none' | 'trunk-based' | 'github-flow' | 'git-flow';
269
+ git_main_branch?: string;
270
+ git_develop_branch?: string | null;
271
+ git_auto_branch?: boolean;
272
+ git_auto_tag?: boolean;
273
+ created_at?: string;
274
+ updated_at?: string;
275
+ };
276
+ };
277
+ tasks: {
278
+ Row: {
279
+ id: string;
280
+ project_id: string;
281
+ title: string;
282
+ description: string | null;
283
+ priority: number;
284
+ status: string;
285
+ created_by: string;
286
+ created_at: string;
287
+ completed_at: string | null;
288
+ progress_percentage: number;
289
+ estimated_minutes: number | null;
290
+ started_at: string | null;
291
+ working_agent_session_id: string | null;
292
+ git_branch: string | null;
293
+ references: { url: string; label?: string }[] | null;
294
+ };
295
+ Insert: {
296
+ id?: string;
297
+ project_id: string;
298
+ title: string;
299
+ description?: string | null;
300
+ priority?: number;
301
+ status?: string;
302
+ created_by?: string;
303
+ created_at?: string;
304
+ completed_at?: string | null;
305
+ progress_percentage?: number;
306
+ estimated_minutes?: number | null;
307
+ started_at?: string | null;
308
+ working_agent_session_id?: string | null;
309
+ git_branch?: string | null;
310
+ references?: { url: string; label?: string }[] | null;
311
+ };
312
+ Update: {
313
+ id?: string;
314
+ project_id?: string;
315
+ title?: string;
316
+ description?: string | null;
317
+ priority?: number;
318
+ status?: string;
319
+ created_by?: string;
320
+ created_at?: string;
321
+ completed_at?: string | null;
322
+ progress_percentage?: number;
323
+ estimated_minutes?: number | null;
324
+ started_at?: string | null;
325
+ working_agent_session_id?: string | null;
326
+ git_branch?: string | null;
327
+ references?: { url: string; label?: string }[] | null;
328
+ };
329
+ };
330
+ progress_logs: {
331
+ Row: {
332
+ id: string;
333
+ project_id: string;
334
+ task_id: string | null;
335
+ summary: string;
336
+ details: string | null;
337
+ created_by: string;
338
+ created_at: string;
339
+ };
340
+ Insert: {
341
+ id?: string;
342
+ project_id: string;
343
+ task_id?: string | null;
344
+ summary: string;
345
+ details?: string | null;
346
+ created_by?: string;
347
+ created_at?: string;
348
+ };
349
+ Update: {
350
+ id?: string;
351
+ project_id?: string;
352
+ task_id?: string | null;
353
+ summary?: string;
354
+ details?: string | null;
355
+ created_by?: string;
356
+ created_at?: string;
357
+ };
358
+ };
359
+ blockers: {
360
+ Row: {
361
+ id: string;
362
+ project_id: string;
363
+ description: string;
364
+ status: string;
365
+ created_by: string;
366
+ created_at: string;
367
+ resolved_at: string | null;
368
+ resolution_note: string | null;
369
+ };
370
+ Insert: {
371
+ id?: string;
372
+ project_id: string;
373
+ description: string;
374
+ status?: string;
375
+ created_by?: string;
376
+ created_at?: string;
377
+ resolved_at?: string | null;
378
+ resolution_note?: string | null;
379
+ };
380
+ Update: {
381
+ id?: string;
382
+ project_id?: string;
383
+ description?: string;
384
+ status?: string;
385
+ created_by?: string;
386
+ created_at?: string;
387
+ resolved_at?: string | null;
388
+ resolution_note?: string | null;
389
+ };
390
+ };
391
+ ideas: {
392
+ Row: {
393
+ id: string;
394
+ project_id: string;
395
+ title: string;
396
+ description: string | null;
397
+ created_by: string;
398
+ created_at: string;
399
+ };
400
+ Insert: {
401
+ id?: string;
402
+ project_id: string;
403
+ title: string;
404
+ description?: string | null;
405
+ created_by?: string;
406
+ created_at?: string;
407
+ };
408
+ Update: {
409
+ id?: string;
410
+ project_id?: string;
411
+ title?: string;
412
+ description?: string | null;
413
+ created_by?: string;
414
+ created_at?: string;
415
+ };
416
+ };
417
+ decisions: {
418
+ Row: {
419
+ id: string;
420
+ project_id: string;
421
+ title: string;
422
+ description: string;
423
+ rationale: string | null;
424
+ alternatives_considered: string[] | null;
425
+ created_by: string;
426
+ created_at: string;
427
+ };
428
+ Insert: {
429
+ id?: string;
430
+ project_id: string;
431
+ title: string;
432
+ description: string;
433
+ rationale?: string | null;
434
+ alternatives_considered?: string[] | null;
435
+ created_by?: string;
436
+ created_at?: string;
437
+ };
438
+ Update: {
439
+ id?: string;
440
+ project_id?: string;
441
+ title?: string;
442
+ description?: string;
443
+ rationale?: string | null;
444
+ alternatives_considered?: string[] | null;
445
+ created_by?: string;
446
+ created_at?: string;
447
+ };
448
+ };
449
+ agent_sessions: {
450
+ Row: {
451
+ id: string;
452
+ api_key_id: string;
453
+ project_id: string;
454
+ last_synced_at: string;
455
+ agent_name: string | null;
456
+ agent_version: string | null;
457
+ instance_id: string | null;
458
+ current_task_id: string | null;
459
+ status: string;
460
+ };
461
+ Insert: {
462
+ id?: string;
463
+ api_key_id: string;
464
+ project_id: string;
465
+ last_synced_at?: string;
466
+ agent_name?: string | null;
467
+ agent_version?: string | null;
468
+ instance_id?: string | null;
469
+ current_task_id?: string | null;
470
+ status?: string;
471
+ };
472
+ Update: {
473
+ id?: string;
474
+ api_key_id?: string;
475
+ project_id?: string;
476
+ last_synced_at?: string;
477
+ agent_name?: string | null;
478
+ agent_version?: string | null;
479
+ instance_id?: string | null;
480
+ current_task_id?: string | null;
481
+ status?: string;
482
+ };
483
+ };
484
+ agent_heartbeats: {
485
+ Row: {
486
+ id: string;
487
+ session_id: string;
488
+ created_at: string;
489
+ };
490
+ Insert: {
491
+ id?: string;
492
+ session_id: string;
493
+ created_at?: string;
494
+ };
495
+ Update: {
496
+ id?: string;
497
+ session_id?: string;
498
+ created_at?: string;
499
+ };
500
+ };
501
+ task_milestones: {
502
+ Row: {
503
+ id: string;
504
+ task_id: string;
505
+ title: string;
506
+ description: string | null;
507
+ order_index: number;
508
+ status: string;
509
+ created_by: string;
510
+ created_at: string;
511
+ completed_at: string | null;
512
+ created_by_session_id: string | null;
513
+ };
514
+ Insert: {
515
+ id?: string;
516
+ task_id: string;
517
+ title: string;
518
+ description?: string | null;
519
+ order_index?: number;
520
+ status?: string;
521
+ created_by?: string;
522
+ created_at?: string;
523
+ completed_at?: string | null;
524
+ created_by_session_id?: string | null;
525
+ };
526
+ Update: {
527
+ id?: string;
528
+ task_id?: string;
529
+ title?: string;
530
+ description?: string | null;
531
+ order_index?: number;
532
+ status?: string;
533
+ created_by?: string;
534
+ created_at?: string;
535
+ completed_at?: string | null;
536
+ created_by_session_id?: string | null;
537
+ };
538
+ };
539
+ };
540
+ Views: Record<string, never>;
541
+ Functions: Record<string, never>;
542
+ Enums: Record<string, never>;
543
+ };
544
+ }
545
+
546
+ interface AuthContext {
547
+ userId: string;
548
+ apiKeyId: string;
549
+ organizationId?: string;
550
+ scope: 'personal' | 'organization';
551
+ }
552
+
553
+ interface UserUpdates {
554
+ tasks: Array<{ id: string; title: string; created_at: string }>;
555
+ blockers: Array<{ id: string; description: string; created_at: string }>;
556
+ ideas: Array<{ id: string; title: string; created_at: string }>;
557
+ }
558
+
559
+ // ============================================================================
560
+ // Configuration
561
+ // ============================================================================
562
+
563
+ const API_KEY = process.env.VIBESCOPE_API_KEY;
564
+
565
+ if (!API_KEY) {
566
+ console.error('Missing required environment variable: VIBESCOPE_API_KEY');
567
+ process.exit(1);
568
+ }
569
+
570
+ // Initialize the API client
571
+ initApiClient({ apiKey: API_KEY });
572
+
573
+ // ============================================================================
574
+ // Authentication
575
+ // ============================================================================
576
+
577
+ async function validateApiKey(timeoutMs?: number): Promise<AuthContext | null> {
578
+ const apiClient = getApiClient();
579
+ const response = await apiClient.validateAuth(timeoutMs ? { timeoutMs } : undefined);
580
+
581
+ if (!response.ok || !response.data?.valid) {
582
+ return null;
583
+ }
584
+
585
+ return {
586
+ userId: response.data.user_id,
587
+ apiKeyId: response.data.api_key_id,
588
+ scope: 'personal', // API handles authorization, scope not needed locally
589
+ };
590
+ }
591
+
592
+ // Deferred auth: started eagerly but awaited on first tool call
593
+ let deferredAuthPromise: Promise<AuthContext | null> | null = null;
594
+ let resolvedAuth: AuthContext | null = null;
595
+ let authError: string | null = null;
596
+
597
+ function startDeferredAuth(): void {
598
+ deferredAuthPromise = validateApiKey(10000)
599
+ .then((auth) => {
600
+ resolvedAuth = auth;
601
+ if (!auth) authError = 'Invalid API key';
602
+ return auth;
603
+ })
604
+ .catch((err) => {
605
+ authError = err instanceof Error ? err.message : 'Auth validation failed';
606
+ return null;
607
+ });
608
+ }
609
+
610
+ async function getAuth(): Promise<AuthContext> {
611
+ // If already resolved, return immediately
612
+ if (resolvedAuth) return resolvedAuth;
613
+
614
+ // Await the deferred promise
615
+ if (deferredAuthPromise) {
616
+ const auth = await deferredAuthPromise;
617
+ if (auth) return auth;
618
+ }
619
+
620
+ // Auth failed — return structured error
621
+ const troubleshooting = [
622
+ 'MCP auth validation failed.',
623
+ authError ? `Reason: ${authError}` : '',
624
+ 'Troubleshooting:',
625
+ '1. Check your API key is valid at https://vibescope.dev/dashboard/settings',
626
+ '2. Verify VIBESCOPE_API_KEY environment variable is set correctly',
627
+ '3. Check network connectivity to vibescope.dev',
628
+ '4. Restart Claude Code and try again',
629
+ ].filter(Boolean).join('\n');
630
+
631
+ throw new Error(troubleshooting);
632
+ }
633
+
634
+ // Deferred update warning: fire-and-forget, delivered on first tool call
635
+ let updateWarningPromise: Promise<string | null> | null = null;
636
+ let resolvedUpdateWarning: string | null | undefined = undefined; // undefined = not yet resolved
637
+
638
+ function startDeferredUpdateCheck(): void {
639
+ updateWarningPromise = getUpdateWarning().catch(() => null);
640
+ updateWarningPromise.then((warning) => {
641
+ resolvedUpdateWarning = warning;
642
+ });
643
+ }
644
+
645
+ function consumeUpdateWarning(): string | null {
646
+ if (resolvedUpdateWarning === undefined) return null; // not ready yet
647
+ const warning = resolvedUpdateWarning;
648
+ resolvedUpdateWarning = null; // deliver only once
649
+ return warning;
650
+ }
651
+
652
+ // Tool definitions imported from tools.ts
653
+
654
+
655
+ // ============================================================================
656
+ // Tool Handlers
657
+ // ============================================================================
658
+
659
+ async function handleTool(
660
+ auth: AuthContext,
661
+ name: string,
662
+ args: Record<string, unknown>
663
+ ): Promise<{ result: unknown; user_updates?: UserUpdates; reminder?: string }> {
664
+ // Update session on every tool call via API:
665
+ // - last_synced_at: keeps session alive and visible as "active" on dashboard
666
+ // - token tracking: synced periodically for cost monitoring
667
+ // Skip for start_work_session since it handles its own session setup
668
+ if (currentSessionId && name !== 'start_work_session') {
669
+ const apiClient = getApiClient();
670
+ // Fire and forget - don't block tool execution on session sync
671
+ apiClient.syncSession(currentSessionId).catch(() => {
672
+ // Silently ignore sync errors - session tracking is non-critical
673
+ });
674
+ }
675
+
676
+ // Check if handler exists in the modular registry
677
+ const handler = handlerRegistry[name];
678
+ if (handler) {
679
+ // Build handler context
680
+ const ctx: HandlerContext = {
681
+ auth,
682
+ session: {
683
+ instanceId: INSTANCE_ID,
684
+ currentSessionId,
685
+ currentPersona,
686
+ currentRole,
687
+ currentProjectId,
688
+ tokenUsage: sessionTokenUsage,
689
+ },
690
+ updateSession: (updates) => {
691
+ if (updates.currentSessionId !== undefined) currentSessionId = updates.currentSessionId;
692
+ if (updates.currentPersona !== undefined) currentPersona = updates.currentPersona;
693
+ if (updates.currentRole !== undefined) currentRole = updates.currentRole;
694
+ if (updates.currentProjectId !== undefined) currentProjectId = updates.currentProjectId;
695
+ if (updates.tokenUsage !== undefined) sessionTokenUsage = updates.tokenUsage;
696
+ },
697
+ };
698
+
699
+ const handlerResult = await handler(args, ctx);
700
+ return handlerResult as { result: unknown; user_updates?: UserUpdates; reminder?: string };
701
+ }
702
+
703
+ throw new Error(`Unknown tool: ${name}`);
704
+ }
705
+
706
+ // ============================================================================
707
+ // Server Setup
708
+ // ============================================================================
709
+
710
+ async function main() {
711
+ // Start auth validation eagerly in background (10s timeout) — don't block startup
712
+ startDeferredAuth();
713
+
714
+ // Start update check in background — delivered on first tool call if available
715
+ startDeferredUpdateCheck();
716
+
717
+ const server = new Server(
718
+ {
719
+ name: 'vibescope',
720
+ version: '0.1.0',
721
+ },
722
+ {
723
+ capabilities: {
724
+ tools: {},
725
+ },
726
+ instructions: 'Vibescope MCP server - AI project tracking and coordination tools.',
727
+ }
728
+ );
729
+
730
+ // List available tools
731
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
732
+ return { tools };
733
+ });
734
+
735
+ // Get reminder nudge - only for critical workflow reminders to save context
736
+ function getReminder(toolName: string): string | null {
737
+ // Most reminders removed to reduce context bloat - instructions are in CLAUDE.md
738
+ // Only keep critical continuation reminder for complete_task
739
+ const reminders: Record<string, string> = {
740
+ 'complete_task': 'CONTINUE WORKING: Call get_next_task or start the next_task. If awaiting_validation tasks exist, validate them FIRST before new work. If context is large, run /clear then start_work_session to refresh.',
741
+ 'batch_complete_tasks': 'CONTINUE WORKING: Call get_next_task. If awaiting_validation tasks exist, validate them FIRST before new work.',
742
+ };
743
+ return reminders[toolName] ?? null;
744
+ }
745
+
746
+ // Handle tool calls
747
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
748
+ const { name, arguments: args } = request.params;
749
+
750
+ // Await deferred auth on first tool call (usually already resolved)
751
+ let auth: AuthContext;
752
+ try {
753
+ auth = await getAuth();
754
+ } catch (err) {
755
+ return {
756
+ content: [
757
+ {
758
+ type: 'text',
759
+ text: err instanceof Error ? err.message : 'Auth validation failed',
760
+ },
761
+ ],
762
+ isError: true,
763
+ };
764
+ }
765
+
766
+ // Check rate limit
767
+ const rateCheck = rateLimiter.check(auth.apiKeyId);
768
+ if (!rateCheck.allowed) {
769
+ const resetSeconds = Math.ceil(rateCheck.resetIn / 1000);
770
+ return {
771
+ content: [
772
+ {
773
+ type: 'text',
774
+ text: `Rate limit exceeded. Too many requests. Please wait ${resetSeconds} seconds before trying again. (Limit: 60 requests per minute)`,
775
+ },
776
+ ],
777
+ isError: true,
778
+ };
779
+ }
780
+
781
+ try {
782
+ const toolArgs = (args as Record<string, unknown>) || {};
783
+ const { result, user_updates } = await handleTool(
784
+ auth,
785
+ name,
786
+ toolArgs
787
+ );
788
+
789
+ // Track token usage for this call (both input args and output result)
790
+ trackTokenUsage(name, toolArgs, result);
791
+
792
+ const content: { type: 'text'; text: string }[] = [
793
+ {
794
+ type: 'text',
795
+ text: JSON.stringify(result, null, 2),
796
+ },
797
+ ];
798
+
799
+ // Deliver update warning on first tool call (if available)
800
+ const updateWarning = consumeUpdateWarning();
801
+ if (updateWarning) {
802
+ content.push({
803
+ type: 'text',
804
+ text: `\n--- ${updateWarning} ---`,
805
+ });
806
+ }
807
+
808
+ // Include reminder nudge if applicable
809
+ const reminder = getReminder(name);
810
+ if (reminder) {
811
+ content.push({
812
+ type: 'text',
813
+ text: `\n--- ${reminder} ---`,
814
+ });
815
+ }
816
+
817
+ // Include user updates only if there are new items (null means no updates)
818
+ if (user_updates) {
819
+ content.push({
820
+ type: 'text',
821
+ text: `\n--- New User Updates ---\n${JSON.stringify(user_updates, null, 2)}`,
822
+ });
823
+ }
824
+
825
+ // Add rate limit warning if getting low
826
+ if (rateCheck.remaining < 10) {
827
+ content.push({
828
+ type: 'text',
829
+ text: `\n--- Rate Limit Warning: ${rateCheck.remaining} requests remaining this minute ---`,
830
+ });
831
+ }
832
+
833
+ return { content };
834
+ } catch (error) {
835
+ // Handle validation errors with structured output
836
+ if (error instanceof ValidationError) {
837
+ return {
838
+ content: [
839
+ {
840
+ type: 'text',
841
+ text: JSON.stringify(error.toJSON(), null, 2),
842
+ },
843
+ ],
844
+ isError: true,
845
+ };
846
+ }
847
+
848
+ // Handle database errors with better context
849
+ const errorMessage = getErrorMessage(error);
850
+ let hint: string | undefined;
851
+
852
+ if (errorMessage.includes('violates foreign key constraint')) {
853
+ hint = 'The referenced ID does not exist. Check that the project_id, task_id, or other IDs are correct.';
854
+ } else if (errorMessage.includes('duplicate key')) {
855
+ hint = 'A record with this identifier already exists.';
856
+ } else if (errorMessage.includes('not found') || errorMessage.includes('no rows')) {
857
+ hint = 'The requested resource was not found. Verify the ID is correct.';
858
+ }
859
+
860
+ return {
861
+ content: [
862
+ {
863
+ type: 'text',
864
+ text: JSON.stringify({
865
+ error: 'operation_failed',
866
+ message: errorMessage,
867
+ hint,
868
+ }, null, 2),
869
+ },
870
+ ],
871
+ isError: true,
872
+ };
873
+ }
874
+ });
875
+
876
+ // Start server
877
+ const transport = new StdioServerTransport();
878
+ await server.connect(transport);
879
+ }
880
+
881
+ main().catch((error) => {
882
+ console.error('Fatal error:', error);
883
+ process.exit(1);
884
+ });