@vibescope/mcp-server 0.4.4 → 0.4.6
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.
- package/CHANGELOG.md +84 -84
- package/README.md +194 -194
- package/dist/api-client/bodies-of-work.d.ts +125 -0
- package/dist/api-client/bodies-of-work.js +78 -0
- package/dist/api-client/chat.d.ts +26 -0
- package/dist/api-client/chat.js +20 -0
- package/dist/api-client/connectors.d.ts +104 -0
- package/dist/api-client/connectors.js +46 -0
- package/dist/api-client/deployment.d.ts +190 -0
- package/dist/api-client/deployment.js +113 -0
- package/dist/api-client/file-checkouts.d.ts +71 -0
- package/dist/api-client/file-checkouts.js +43 -0
- package/dist/api-client/git-issues.d.ts +55 -0
- package/dist/api-client/git-issues.js +34 -0
- package/dist/api-client/index.d.ts +619 -1
- package/dist/api-client/index.js +148 -0
- package/dist/api-client/organizations.d.ts +101 -0
- package/dist/api-client/organizations.js +86 -0
- package/dist/api-client/progress.d.ts +61 -0
- package/dist/api-client/progress.js +34 -0
- package/dist/api-client/project.d.ts +1 -0
- package/dist/api-client/requests.d.ts +28 -0
- package/dist/api-client/requests.js +28 -0
- package/dist/api-client/sprints.d.ts +153 -0
- package/dist/api-client/sprints.js +82 -0
- package/dist/api-client/subtasks.d.ts +37 -0
- package/dist/api-client/subtasks.js +23 -0
- package/dist/api-client.d.ts +23 -0
- package/dist/api-client.js +15 -0
- package/dist/cli-init.js +21 -21
- package/dist/cli.js +26 -26
- package/dist/handlers/blockers.js +4 -0
- package/dist/handlers/chat.d.ts +23 -0
- package/dist/handlers/chat.js +84 -0
- package/dist/handlers/deployment.d.ts +3 -0
- package/dist/handlers/deployment.js +23 -0
- package/dist/handlers/discovery.js +13 -0
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/project.js +4 -2
- package/dist/handlers/session.js +7 -0
- package/dist/handlers/tasks.js +7 -0
- package/dist/handlers/tool-docs.js +1204 -1131
- package/dist/index.js +73 -73
- package/dist/templates/agent-guidelines.d.ts +1 -1
- package/dist/templates/agent-guidelines.js +205 -187
- package/dist/templates/help-content.js +1621 -1621
- package/dist/tools/bodies-of-work.js +6 -6
- package/dist/tools/chat.d.ts +1 -0
- package/dist/tools/chat.js +24 -0
- package/dist/tools/cloud-agents.js +22 -22
- package/dist/tools/deployment.js +13 -0
- package/dist/tools/features.d.ts +13 -0
- package/dist/tools/features.js +151 -0
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/index.js +4 -1
- package/dist/tools/milestones.js +2 -2
- package/dist/tools/project.js +4 -0
- package/dist/tools/requests.js +1 -1
- package/dist/tools/session.js +11 -11
- package/dist/tools/sprints.js +9 -9
- package/dist/tools/tasks.js +35 -35
- package/dist/tools/worktrees.js +14 -14
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +3602 -0
- package/dist/utils.js +11 -11
- package/docs/TOOLS.md +2663 -2545
- package/package.json +53 -53
- package/scripts/generate-docs.ts +212 -212
- package/scripts/version-bump.ts +203 -203
- package/src/api-client/blockers.ts +86 -86
- package/src/api-client/bodies-of-work.ts +194 -0
- package/src/api-client/chat.ts +50 -0
- package/src/api-client/connectors.ts +152 -0
- package/src/api-client/cost.ts +185 -185
- package/src/api-client/decisions.ts +87 -87
- package/src/api-client/deployment.ts +313 -0
- package/src/api-client/discovery.ts +81 -81
- package/src/api-client/fallback.ts +52 -52
- package/src/api-client/file-checkouts.ts +115 -0
- package/src/api-client/findings.ts +100 -100
- package/src/api-client/git-issues.ts +88 -0
- package/src/api-client/ideas.ts +112 -112
- package/src/api-client/index.ts +592 -426
- package/src/api-client/milestones.ts +83 -83
- package/src/api-client/organizations.ts +185 -0
- package/src/api-client/progress.ts +94 -0
- package/src/api-client/project.ts +180 -179
- package/src/api-client/requests.ts +54 -0
- package/src/api-client/session.ts +220 -220
- package/src/api-client/sprints.ts +227 -0
- package/src/api-client/subtasks.ts +57 -0
- package/src/api-client/tasks.ts +450 -450
- package/src/api-client/types.ts +32 -32
- package/src/api-client/validation.ts +60 -60
- package/src/api-client/worktrees.ts +53 -53
- package/src/api-client.test.ts +847 -850
- package/src/api-client.ts +2707 -2672
- package/src/cli-init.ts +557 -557
- package/src/cli.test.ts +284 -284
- package/src/cli.ts +204 -204
- package/src/handlers/__test-setup__.ts +240 -236
- package/src/handlers/__test-utils__.ts +89 -89
- package/src/handlers/blockers.test.ts +468 -468
- package/src/handlers/blockers.ts +172 -163
- package/src/handlers/bodies-of-work.test.ts +704 -704
- package/src/handlers/bodies-of-work.ts +526 -526
- package/src/handlers/chat.test.ts +185 -0
- package/src/handlers/chat.ts +101 -0
- package/src/handlers/cloud-agents.test.ts +438 -438
- package/src/handlers/cloud-agents.ts +156 -156
- package/src/handlers/connectors.test.ts +834 -834
- package/src/handlers/connectors.ts +229 -229
- package/src/handlers/cost.test.ts +462 -462
- package/src/handlers/cost.ts +285 -285
- package/src/handlers/decisions.test.ts +382 -382
- package/src/handlers/decisions.ts +153 -153
- package/src/handlers/deployment.test.ts +551 -551
- package/src/handlers/deployment.ts +570 -541
- package/src/handlers/discovery.test.ts +206 -206
- package/src/handlers/discovery.ts +427 -414
- package/src/handlers/fallback.test.ts +537 -537
- package/src/handlers/fallback.ts +194 -194
- package/src/handlers/file-checkouts.test.ts +750 -750
- package/src/handlers/file-checkouts.ts +185 -185
- package/src/handlers/findings.test.ts +633 -633
- package/src/handlers/findings.ts +239 -239
- package/src/handlers/git-issues.test.ts +631 -631
- package/src/handlers/git-issues.ts +136 -136
- package/src/handlers/ideas.test.ts +644 -644
- package/src/handlers/ideas.ts +207 -207
- package/src/handlers/index.ts +93 -90
- package/src/handlers/milestones.test.ts +475 -475
- package/src/handlers/milestones.ts +180 -180
- package/src/handlers/organizations.test.ts +826 -826
- package/src/handlers/organizations.ts +315 -315
- package/src/handlers/progress.test.ts +269 -269
- package/src/handlers/progress.ts +77 -77
- package/src/handlers/project.test.ts +546 -546
- package/src/handlers/project.ts +242 -239
- package/src/handlers/requests.test.ts +303 -303
- package/src/handlers/requests.ts +99 -99
- package/src/handlers/roles.test.ts +305 -305
- package/src/handlers/roles.ts +219 -219
- package/src/handlers/session.test.ts +998 -998
- package/src/handlers/session.ts +1105 -1093
- package/src/handlers/sprints.test.ts +732 -732
- package/src/handlers/sprints.ts +537 -537
- package/src/handlers/tasks.test.ts +931 -931
- package/src/handlers/tasks.ts +1133 -1121
- package/src/handlers/tool-categories.test.ts +66 -66
- package/src/handlers/tool-docs.test.ts +511 -511
- package/src/handlers/tool-docs.ts +1571 -1491
- package/src/handlers/types.test.ts +259 -259
- package/src/handlers/types.ts +176 -176
- package/src/handlers/validation.test.ts +582 -582
- package/src/handlers/validation.ts +164 -164
- package/src/handlers/version.ts +63 -63
- package/src/index.test.ts +674 -674
- package/src/index.ts +807 -807
- package/src/setup.test.ts +233 -233
- package/src/setup.ts +404 -404
- package/src/templates/agent-guidelines.ts +233 -215
- package/src/templates/help-content.ts +1751 -1751
- package/src/token-tracking.test.ts +463 -463
- package/src/token-tracking.ts +167 -167
- package/src/tools/blockers.ts +122 -122
- package/src/tools/bodies-of-work.ts +283 -283
- package/src/tools/chat.ts +72 -46
- package/src/tools/cloud-agents.ts +101 -101
- package/src/tools/connectors.ts +191 -191
- package/src/tools/cost.ts +111 -111
- package/src/tools/decisions.ts +111 -111
- package/src/tools/deployment.ts +455 -442
- package/src/tools/discovery.ts +76 -76
- package/src/tools/fallback.ts +111 -111
- package/src/tools/features.ts +154 -0
- package/src/tools/file-checkouts.ts +145 -145
- package/src/tools/findings.ts +101 -101
- package/src/tools/git-issues.ts +130 -130
- package/src/tools/ideas.ts +162 -162
- package/src/tools/index.ts +141 -137
- package/src/tools/milestones.ts +118 -118
- package/src/tools/organizations.ts +224 -224
- package/src/tools/progress.ts +73 -73
- package/src/tools/project.ts +206 -202
- package/src/tools/requests.ts +68 -68
- package/src/tools/roles.ts +112 -112
- package/src/tools/session.ts +181 -181
- package/src/tools/sprints.ts +298 -298
- package/src/tools/tasks.ts +550 -550
- package/src/tools/tools.test.ts +222 -222
- package/src/tools/types.ts +9 -9
- package/src/tools/validation.ts +75 -75
- package/src/tools/version.ts +34 -34
- package/src/tools/worktrees.ts +66 -66
- package/src/tools.test.ts +416 -416
- package/src/utils.test.ts +1014 -1014
- package/src/utils.ts +586 -586
- package/src/validators.test.ts +223 -223
- package/src/validators.ts +249 -249
- package/src/version.ts +109 -109
- package/tsconfig.json +16 -16
- package/vitest.config.ts +14 -14
package/src/index.ts
CHANGED
|
@@ -1,807 +1,807 @@
|
|
|
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(): Promise<AuthContext | null> {
|
|
578
|
-
const apiClient = getApiClient();
|
|
579
|
-
const response = await apiClient.validateAuth();
|
|
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
|
-
// Tool definitions imported from tools.ts
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
// ============================================================================
|
|
596
|
-
// Tool Handlers
|
|
597
|
-
// ============================================================================
|
|
598
|
-
|
|
599
|
-
async function handleTool(
|
|
600
|
-
auth: AuthContext,
|
|
601
|
-
name: string,
|
|
602
|
-
args: Record<string, unknown>
|
|
603
|
-
): Promise<{ result: unknown; user_updates?: UserUpdates; reminder?: string }> {
|
|
604
|
-
// Update session on every tool call via API:
|
|
605
|
-
// - last_synced_at: keeps session alive and visible as "active" on dashboard
|
|
606
|
-
// - token tracking: synced periodically for cost monitoring
|
|
607
|
-
// Skip for start_work_session since it handles its own session setup
|
|
608
|
-
if (currentSessionId && name !== 'start_work_session') {
|
|
609
|
-
const apiClient = getApiClient();
|
|
610
|
-
// Fire and forget - don't block tool execution on session sync
|
|
611
|
-
apiClient.syncSession(currentSessionId).catch(() => {
|
|
612
|
-
// Silently ignore sync errors - session tracking is non-critical
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Check if handler exists in the modular registry
|
|
617
|
-
const handler = handlerRegistry[name];
|
|
618
|
-
if (handler) {
|
|
619
|
-
// Build handler context
|
|
620
|
-
const ctx: HandlerContext = {
|
|
621
|
-
auth,
|
|
622
|
-
session: {
|
|
623
|
-
instanceId: INSTANCE_ID,
|
|
624
|
-
currentSessionId,
|
|
625
|
-
currentPersona,
|
|
626
|
-
currentRole,
|
|
627
|
-
currentProjectId,
|
|
628
|
-
tokenUsage: sessionTokenUsage,
|
|
629
|
-
},
|
|
630
|
-
updateSession: (updates) => {
|
|
631
|
-
if (updates.currentSessionId !== undefined) currentSessionId = updates.currentSessionId;
|
|
632
|
-
if (updates.currentPersona !== undefined) currentPersona = updates.currentPersona;
|
|
633
|
-
if (updates.currentRole !== undefined) currentRole = updates.currentRole;
|
|
634
|
-
if (updates.currentProjectId !== undefined) currentProjectId = updates.currentProjectId;
|
|
635
|
-
if (updates.tokenUsage !== undefined) sessionTokenUsage = updates.tokenUsage;
|
|
636
|
-
},
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
const handlerResult = await handler(args, ctx);
|
|
640
|
-
return handlerResult as { result: unknown; user_updates?: UserUpdates; reminder?: string };
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// ============================================================================
|
|
647
|
-
// Server Setup
|
|
648
|
-
// ============================================================================
|
|
649
|
-
|
|
650
|
-
async function main() {
|
|
651
|
-
// Validate API key on startup via API
|
|
652
|
-
const auth = await validateApiKey();
|
|
653
|
-
if (!auth) {
|
|
654
|
-
console.error('Invalid API key');
|
|
655
|
-
process.exit(1);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Check for updates (non-blocking, with timeout)
|
|
659
|
-
const updateWarning = await getUpdateWarning();
|
|
660
|
-
|
|
661
|
-
const serverInstructions = updateWarning
|
|
662
|
-
? `${updateWarning}\n\nVibescope MCP server - AI project tracking and coordination tools.`
|
|
663
|
-
: 'Vibescope MCP server - AI project tracking and coordination tools.';
|
|
664
|
-
|
|
665
|
-
const server = new Server(
|
|
666
|
-
{
|
|
667
|
-
name: 'vibescope',
|
|
668
|
-
version: '0.1.0',
|
|
669
|
-
},
|
|
670
|
-
{
|
|
671
|
-
capabilities: {
|
|
672
|
-
tools: {},
|
|
673
|
-
},
|
|
674
|
-
instructions: serverInstructions,
|
|
675
|
-
}
|
|
676
|
-
);
|
|
677
|
-
|
|
678
|
-
// List available tools
|
|
679
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
680
|
-
return { tools };
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
// Get reminder nudge - only for critical workflow reminders to save context
|
|
684
|
-
function getReminder(toolName: string): string | null {
|
|
685
|
-
// Most reminders removed to reduce context bloat - instructions are in CLAUDE.md
|
|
686
|
-
// Only keep critical continuation reminder for complete_task
|
|
687
|
-
const reminders: Record<string, string> = {
|
|
688
|
-
'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.',
|
|
689
|
-
'batch_complete_tasks': 'CONTINUE WORKING: Call get_next_task. If awaiting_validation tasks exist, validate them FIRST before new work.',
|
|
690
|
-
};
|
|
691
|
-
return reminders[toolName] ?? null;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Handle tool calls
|
|
695
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
696
|
-
const { name, arguments: args } = request.params;
|
|
697
|
-
|
|
698
|
-
// Check rate limit
|
|
699
|
-
const rateCheck = rateLimiter.check(auth.apiKeyId);
|
|
700
|
-
if (!rateCheck.allowed) {
|
|
701
|
-
const resetSeconds = Math.ceil(rateCheck.resetIn / 1000);
|
|
702
|
-
return {
|
|
703
|
-
content: [
|
|
704
|
-
{
|
|
705
|
-
type: 'text',
|
|
706
|
-
text: `Rate limit exceeded. Too many requests. Please wait ${resetSeconds} seconds before trying again. (Limit: 60 requests per minute)`,
|
|
707
|
-
},
|
|
708
|
-
],
|
|
709
|
-
isError: true,
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
try {
|
|
714
|
-
const toolArgs = (args as Record<string, unknown>) || {};
|
|
715
|
-
const { result, user_updates } = await handleTool(
|
|
716
|
-
auth,
|
|
717
|
-
name,
|
|
718
|
-
toolArgs
|
|
719
|
-
);
|
|
720
|
-
|
|
721
|
-
// Track token usage for this call (both input args and output result)
|
|
722
|
-
trackTokenUsage(name, toolArgs, result);
|
|
723
|
-
|
|
724
|
-
const content: { type: 'text'; text: string }[] = [
|
|
725
|
-
{
|
|
726
|
-
type: 'text',
|
|
727
|
-
text: JSON.stringify(result, null, 2),
|
|
728
|
-
},
|
|
729
|
-
];
|
|
730
|
-
|
|
731
|
-
// Include reminder nudge if applicable
|
|
732
|
-
const reminder = getReminder(name);
|
|
733
|
-
if (reminder) {
|
|
734
|
-
content.push({
|
|
735
|
-
type: 'text',
|
|
736
|
-
text: `\n--- ${reminder} ---`,
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// Include user updates only if there are new items (null means no updates)
|
|
741
|
-
if (user_updates) {
|
|
742
|
-
content.push({
|
|
743
|
-
type: 'text',
|
|
744
|
-
text: `\n--- New User Updates ---\n${JSON.stringify(user_updates, null, 2)}`,
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// Add rate limit warning if getting low
|
|
749
|
-
if (rateCheck.remaining < 10) {
|
|
750
|
-
content.push({
|
|
751
|
-
type: 'text',
|
|
752
|
-
text: `\n--- Rate Limit Warning: ${rateCheck.remaining} requests remaining this minute ---`,
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
return { content };
|
|
757
|
-
} catch (error) {
|
|
758
|
-
// Handle validation errors with structured output
|
|
759
|
-
if (error instanceof ValidationError) {
|
|
760
|
-
return {
|
|
761
|
-
content: [
|
|
762
|
-
{
|
|
763
|
-
type: 'text',
|
|
764
|
-
text: JSON.stringify(error.toJSON(), null, 2),
|
|
765
|
-
},
|
|
766
|
-
],
|
|
767
|
-
isError: true,
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Handle database errors with better context
|
|
772
|
-
const errorMessage = getErrorMessage(error);
|
|
773
|
-
let hint: string | undefined;
|
|
774
|
-
|
|
775
|
-
if (errorMessage.includes('violates foreign key constraint')) {
|
|
776
|
-
hint = 'The referenced ID does not exist. Check that the project_id, task_id, or other IDs are correct.';
|
|
777
|
-
} else if (errorMessage.includes('duplicate key')) {
|
|
778
|
-
hint = 'A record with this identifier already exists.';
|
|
779
|
-
} else if (errorMessage.includes('not found') || errorMessage.includes('no rows')) {
|
|
780
|
-
hint = 'The requested resource was not found. Verify the ID is correct.';
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
return {
|
|
784
|
-
content: [
|
|
785
|
-
{
|
|
786
|
-
type: 'text',
|
|
787
|
-
text: JSON.stringify({
|
|
788
|
-
error: 'operation_failed',
|
|
789
|
-
message: errorMessage,
|
|
790
|
-
hint,
|
|
791
|
-
}, null, 2),
|
|
792
|
-
},
|
|
793
|
-
],
|
|
794
|
-
isError: true,
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
});
|
|
798
|
-
|
|
799
|
-
// Start server
|
|
800
|
-
const transport = new StdioServerTransport();
|
|
801
|
-
await server.connect(transport);
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
main().catch((error) => {
|
|
805
|
-
console.error('Fatal error:', error);
|
|
806
|
-
process.exit(1);
|
|
807
|
-
});
|
|
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(): Promise<AuthContext | null> {
|
|
578
|
+
const apiClient = getApiClient();
|
|
579
|
+
const response = await apiClient.validateAuth();
|
|
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
|
+
// Tool definitions imported from tools.ts
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
// ============================================================================
|
|
596
|
+
// Tool Handlers
|
|
597
|
+
// ============================================================================
|
|
598
|
+
|
|
599
|
+
async function handleTool(
|
|
600
|
+
auth: AuthContext,
|
|
601
|
+
name: string,
|
|
602
|
+
args: Record<string, unknown>
|
|
603
|
+
): Promise<{ result: unknown; user_updates?: UserUpdates; reminder?: string }> {
|
|
604
|
+
// Update session on every tool call via API:
|
|
605
|
+
// - last_synced_at: keeps session alive and visible as "active" on dashboard
|
|
606
|
+
// - token tracking: synced periodically for cost monitoring
|
|
607
|
+
// Skip for start_work_session since it handles its own session setup
|
|
608
|
+
if (currentSessionId && name !== 'start_work_session') {
|
|
609
|
+
const apiClient = getApiClient();
|
|
610
|
+
// Fire and forget - don't block tool execution on session sync
|
|
611
|
+
apiClient.syncSession(currentSessionId).catch(() => {
|
|
612
|
+
// Silently ignore sync errors - session tracking is non-critical
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Check if handler exists in the modular registry
|
|
617
|
+
const handler = handlerRegistry[name];
|
|
618
|
+
if (handler) {
|
|
619
|
+
// Build handler context
|
|
620
|
+
const ctx: HandlerContext = {
|
|
621
|
+
auth,
|
|
622
|
+
session: {
|
|
623
|
+
instanceId: INSTANCE_ID,
|
|
624
|
+
currentSessionId,
|
|
625
|
+
currentPersona,
|
|
626
|
+
currentRole,
|
|
627
|
+
currentProjectId,
|
|
628
|
+
tokenUsage: sessionTokenUsage,
|
|
629
|
+
},
|
|
630
|
+
updateSession: (updates) => {
|
|
631
|
+
if (updates.currentSessionId !== undefined) currentSessionId = updates.currentSessionId;
|
|
632
|
+
if (updates.currentPersona !== undefined) currentPersona = updates.currentPersona;
|
|
633
|
+
if (updates.currentRole !== undefined) currentRole = updates.currentRole;
|
|
634
|
+
if (updates.currentProjectId !== undefined) currentProjectId = updates.currentProjectId;
|
|
635
|
+
if (updates.tokenUsage !== undefined) sessionTokenUsage = updates.tokenUsage;
|
|
636
|
+
},
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
const handlerResult = await handler(args, ctx);
|
|
640
|
+
return handlerResult as { result: unknown; user_updates?: UserUpdates; reminder?: string };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ============================================================================
|
|
647
|
+
// Server Setup
|
|
648
|
+
// ============================================================================
|
|
649
|
+
|
|
650
|
+
async function main() {
|
|
651
|
+
// Validate API key on startup via API
|
|
652
|
+
const auth = await validateApiKey();
|
|
653
|
+
if (!auth) {
|
|
654
|
+
console.error('Invalid API key');
|
|
655
|
+
process.exit(1);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Check for updates (non-blocking, with timeout)
|
|
659
|
+
const updateWarning = await getUpdateWarning();
|
|
660
|
+
|
|
661
|
+
const serverInstructions = updateWarning
|
|
662
|
+
? `${updateWarning}\n\nVibescope MCP server - AI project tracking and coordination tools.`
|
|
663
|
+
: 'Vibescope MCP server - AI project tracking and coordination tools.';
|
|
664
|
+
|
|
665
|
+
const server = new Server(
|
|
666
|
+
{
|
|
667
|
+
name: 'vibescope',
|
|
668
|
+
version: '0.1.0',
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
capabilities: {
|
|
672
|
+
tools: {},
|
|
673
|
+
},
|
|
674
|
+
instructions: serverInstructions,
|
|
675
|
+
}
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
// List available tools
|
|
679
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
680
|
+
return { tools };
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// Get reminder nudge - only for critical workflow reminders to save context
|
|
684
|
+
function getReminder(toolName: string): string | null {
|
|
685
|
+
// Most reminders removed to reduce context bloat - instructions are in CLAUDE.md
|
|
686
|
+
// Only keep critical continuation reminder for complete_task
|
|
687
|
+
const reminders: Record<string, string> = {
|
|
688
|
+
'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.',
|
|
689
|
+
'batch_complete_tasks': 'CONTINUE WORKING: Call get_next_task. If awaiting_validation tasks exist, validate them FIRST before new work.',
|
|
690
|
+
};
|
|
691
|
+
return reminders[toolName] ?? null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Handle tool calls
|
|
695
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
696
|
+
const { name, arguments: args } = request.params;
|
|
697
|
+
|
|
698
|
+
// Check rate limit
|
|
699
|
+
const rateCheck = rateLimiter.check(auth.apiKeyId);
|
|
700
|
+
if (!rateCheck.allowed) {
|
|
701
|
+
const resetSeconds = Math.ceil(rateCheck.resetIn / 1000);
|
|
702
|
+
return {
|
|
703
|
+
content: [
|
|
704
|
+
{
|
|
705
|
+
type: 'text',
|
|
706
|
+
text: `Rate limit exceeded. Too many requests. Please wait ${resetSeconds} seconds before trying again. (Limit: 60 requests per minute)`,
|
|
707
|
+
},
|
|
708
|
+
],
|
|
709
|
+
isError: true,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
try {
|
|
714
|
+
const toolArgs = (args as Record<string, unknown>) || {};
|
|
715
|
+
const { result, user_updates } = await handleTool(
|
|
716
|
+
auth,
|
|
717
|
+
name,
|
|
718
|
+
toolArgs
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
// Track token usage for this call (both input args and output result)
|
|
722
|
+
trackTokenUsage(name, toolArgs, result);
|
|
723
|
+
|
|
724
|
+
const content: { type: 'text'; text: string }[] = [
|
|
725
|
+
{
|
|
726
|
+
type: 'text',
|
|
727
|
+
text: JSON.stringify(result, null, 2),
|
|
728
|
+
},
|
|
729
|
+
];
|
|
730
|
+
|
|
731
|
+
// Include reminder nudge if applicable
|
|
732
|
+
const reminder = getReminder(name);
|
|
733
|
+
if (reminder) {
|
|
734
|
+
content.push({
|
|
735
|
+
type: 'text',
|
|
736
|
+
text: `\n--- ${reminder} ---`,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Include user updates only if there are new items (null means no updates)
|
|
741
|
+
if (user_updates) {
|
|
742
|
+
content.push({
|
|
743
|
+
type: 'text',
|
|
744
|
+
text: `\n--- New User Updates ---\n${JSON.stringify(user_updates, null, 2)}`,
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Add rate limit warning if getting low
|
|
749
|
+
if (rateCheck.remaining < 10) {
|
|
750
|
+
content.push({
|
|
751
|
+
type: 'text',
|
|
752
|
+
text: `\n--- Rate Limit Warning: ${rateCheck.remaining} requests remaining this minute ---`,
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return { content };
|
|
757
|
+
} catch (error) {
|
|
758
|
+
// Handle validation errors with structured output
|
|
759
|
+
if (error instanceof ValidationError) {
|
|
760
|
+
return {
|
|
761
|
+
content: [
|
|
762
|
+
{
|
|
763
|
+
type: 'text',
|
|
764
|
+
text: JSON.stringify(error.toJSON(), null, 2),
|
|
765
|
+
},
|
|
766
|
+
],
|
|
767
|
+
isError: true,
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Handle database errors with better context
|
|
772
|
+
const errorMessage = getErrorMessage(error);
|
|
773
|
+
let hint: string | undefined;
|
|
774
|
+
|
|
775
|
+
if (errorMessage.includes('violates foreign key constraint')) {
|
|
776
|
+
hint = 'The referenced ID does not exist. Check that the project_id, task_id, or other IDs are correct.';
|
|
777
|
+
} else if (errorMessage.includes('duplicate key')) {
|
|
778
|
+
hint = 'A record with this identifier already exists.';
|
|
779
|
+
} else if (errorMessage.includes('not found') || errorMessage.includes('no rows')) {
|
|
780
|
+
hint = 'The requested resource was not found. Verify the ID is correct.';
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return {
|
|
784
|
+
content: [
|
|
785
|
+
{
|
|
786
|
+
type: 'text',
|
|
787
|
+
text: JSON.stringify({
|
|
788
|
+
error: 'operation_failed',
|
|
789
|
+
message: errorMessage,
|
|
790
|
+
hint,
|
|
791
|
+
}, null, 2),
|
|
792
|
+
},
|
|
793
|
+
],
|
|
794
|
+
isError: true,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// Start server
|
|
800
|
+
const transport = new StdioServerTransport();
|
|
801
|
+
await server.connect(transport);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
main().catch((error) => {
|
|
805
|
+
console.error('Fatal error:', error);
|
|
806
|
+
process.exit(1);
|
|
807
|
+
});
|