@vibescope/mcp-server 0.5.0 → 0.5.1

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 (161) 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/tasks.js +7 -1
  7. package/dist/handlers/tool-docs.js +1216 -1216
  8. package/dist/index.js +73 -73
  9. package/dist/templates/agent-guidelines.d.ts +1 -1
  10. package/dist/templates/agent-guidelines.js +205 -205
  11. package/dist/templates/help-content.js +1621 -1621
  12. package/dist/tools/bodies-of-work.js +6 -6
  13. package/dist/tools/cloud-agents.js +22 -22
  14. package/dist/tools/milestones.js +2 -2
  15. package/dist/tools/requests.js +1 -1
  16. package/dist/tools/session.js +11 -11
  17. package/dist/tools/sprints.js +9 -9
  18. package/dist/tools/tasks.js +43 -35
  19. package/dist/tools/worktrees.js +14 -14
  20. package/dist/utils.js +11 -11
  21. package/docs/TOOLS.md +2687 -2685
  22. package/package.json +53 -53
  23. package/scripts/generate-docs.ts +212 -212
  24. package/scripts/version-bump.ts +203 -203
  25. package/src/api-client/blockers.ts +86 -86
  26. package/src/api-client/bodies-of-work.ts +194 -194
  27. package/src/api-client/chat.ts +50 -50
  28. package/src/api-client/connectors.ts +152 -152
  29. package/src/api-client/cost.ts +185 -185
  30. package/src/api-client/decisions.ts +87 -87
  31. package/src/api-client/deployment.ts +313 -313
  32. package/src/api-client/discovery.ts +81 -81
  33. package/src/api-client/fallback.ts +52 -52
  34. package/src/api-client/file-checkouts.ts +115 -115
  35. package/src/api-client/findings.ts +100 -100
  36. package/src/api-client/git-issues.ts +88 -88
  37. package/src/api-client/ideas.ts +112 -112
  38. package/src/api-client/index.ts +592 -592
  39. package/src/api-client/milestones.ts +83 -83
  40. package/src/api-client/organizations.ts +185 -185
  41. package/src/api-client/progress.ts +94 -94
  42. package/src/api-client/project.ts +181 -181
  43. package/src/api-client/requests.ts +54 -54
  44. package/src/api-client/session.ts +220 -220
  45. package/src/api-client/sprints.ts +227 -227
  46. package/src/api-client/subtasks.ts +57 -57
  47. package/src/api-client/tasks.ts +451 -450
  48. package/src/api-client/types.ts +32 -32
  49. package/src/api-client/validation.ts +60 -60
  50. package/src/api-client/worktrees.ts +53 -53
  51. package/src/api-client.test.ts +847 -847
  52. package/src/api-client.ts +2728 -2728
  53. package/src/cli-init.ts +558 -558
  54. package/src/cli.test.ts +284 -284
  55. package/src/cli.ts +204 -204
  56. package/src/handlers/__test-setup__.ts +240 -240
  57. package/src/handlers/__test-utils__.ts +89 -89
  58. package/src/handlers/blockers.test.ts +468 -468
  59. package/src/handlers/blockers.ts +172 -172
  60. package/src/handlers/bodies-of-work.test.ts +704 -704
  61. package/src/handlers/bodies-of-work.ts +526 -526
  62. package/src/handlers/chat.test.ts +185 -185
  63. package/src/handlers/chat.ts +101 -101
  64. package/src/handlers/cloud-agents.test.ts +438 -438
  65. package/src/handlers/cloud-agents.ts +156 -156
  66. package/src/handlers/connectors.test.ts +834 -834
  67. package/src/handlers/connectors.ts +229 -229
  68. package/src/handlers/cost.test.ts +462 -462
  69. package/src/handlers/cost.ts +285 -285
  70. package/src/handlers/decisions.test.ts +382 -382
  71. package/src/handlers/decisions.ts +153 -153
  72. package/src/handlers/deployment.test.ts +551 -551
  73. package/src/handlers/deployment.ts +570 -570
  74. package/src/handlers/discovery.test.ts +206 -206
  75. package/src/handlers/discovery.ts +433 -433
  76. package/src/handlers/fallback.test.ts +537 -537
  77. package/src/handlers/fallback.ts +194 -194
  78. package/src/handlers/file-checkouts.test.ts +750 -750
  79. package/src/handlers/file-checkouts.ts +185 -185
  80. package/src/handlers/findings.test.ts +633 -633
  81. package/src/handlers/findings.ts +239 -239
  82. package/src/handlers/git-issues.test.ts +631 -631
  83. package/src/handlers/git-issues.ts +136 -136
  84. package/src/handlers/ideas.test.ts +644 -644
  85. package/src/handlers/ideas.ts +207 -207
  86. package/src/handlers/index.ts +93 -93
  87. package/src/handlers/milestones.test.ts +475 -475
  88. package/src/handlers/milestones.ts +180 -180
  89. package/src/handlers/organizations.test.ts +826 -826
  90. package/src/handlers/organizations.ts +315 -315
  91. package/src/handlers/progress.test.ts +269 -269
  92. package/src/handlers/progress.ts +77 -77
  93. package/src/handlers/project.test.ts +546 -546
  94. package/src/handlers/project.ts +245 -245
  95. package/src/handlers/requests.test.ts +303 -303
  96. package/src/handlers/requests.ts +99 -99
  97. package/src/handlers/roles.test.ts +305 -305
  98. package/src/handlers/roles.ts +219 -219
  99. package/src/handlers/session.test.ts +998 -998
  100. package/src/handlers/session.ts +1105 -1105
  101. package/src/handlers/sprints.test.ts +732 -732
  102. package/src/handlers/sprints.ts +537 -537
  103. package/src/handlers/tasks.test.ts +931 -931
  104. package/src/handlers/tasks.ts +1144 -1137
  105. package/src/handlers/tool-categories.test.ts +66 -66
  106. package/src/handlers/tool-docs.test.ts +511 -511
  107. package/src/handlers/tool-docs.ts +1595 -1595
  108. package/src/handlers/types.test.ts +259 -259
  109. package/src/handlers/types.ts +176 -176
  110. package/src/handlers/validation.test.ts +582 -582
  111. package/src/handlers/validation.ts +164 -164
  112. package/src/handlers/version.ts +63 -63
  113. package/src/index.test.ts +674 -674
  114. package/src/index.ts +884 -884
  115. package/src/setup.test.ts +243 -243
  116. package/src/setup.ts +410 -410
  117. package/src/templates/agent-guidelines.ts +233 -233
  118. package/src/templates/help-content.ts +1751 -1751
  119. package/src/token-tracking.test.ts +463 -463
  120. package/src/token-tracking.ts +167 -167
  121. package/src/tools/blockers.ts +122 -122
  122. package/src/tools/bodies-of-work.ts +283 -283
  123. package/src/tools/chat.ts +72 -72
  124. package/src/tools/cloud-agents.ts +101 -101
  125. package/src/tools/connectors.ts +191 -191
  126. package/src/tools/cost.ts +111 -111
  127. package/src/tools/decisions.ts +111 -111
  128. package/src/tools/deployment.ts +455 -455
  129. package/src/tools/discovery.ts +76 -76
  130. package/src/tools/fallback.ts +111 -111
  131. package/src/tools/features.ts +154 -154
  132. package/src/tools/file-checkouts.ts +145 -145
  133. package/src/tools/findings.ts +101 -101
  134. package/src/tools/git-issues.ts +130 -130
  135. package/src/tools/ideas.ts +162 -162
  136. package/src/tools/index.ts +145 -145
  137. package/src/tools/milestones.ts +118 -118
  138. package/src/tools/organizations.ts +224 -224
  139. package/src/tools/persona-templates.ts +25 -25
  140. package/src/tools/progress.ts +73 -73
  141. package/src/tools/project.ts +210 -210
  142. package/src/tools/requests.ts +68 -68
  143. package/src/tools/roles.ts +112 -112
  144. package/src/tools/session.ts +181 -181
  145. package/src/tools/sprints.ts +298 -298
  146. package/src/tools/tasks.ts +583 -575
  147. package/src/tools/tools.test.ts +222 -222
  148. package/src/tools/types.ts +9 -9
  149. package/src/tools/validation.ts +75 -75
  150. package/src/tools/version.ts +34 -34
  151. package/src/tools/worktrees.ts +66 -66
  152. package/src/tools.test.ts +416 -416
  153. package/src/utils.test.ts +1014 -1014
  154. package/src/utils.ts +586 -586
  155. package/src/validators.test.ts +223 -223
  156. package/src/validators.ts +249 -249
  157. package/src/version.ts +162 -162
  158. package/tsconfig.json +16 -16
  159. package/vitest.config.ts +14 -14
  160. package/dist/tools.d.ts +0 -2
  161. 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
+ });