@vibescope/mcp-server 0.0.1 → 0.1.0

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 (170) hide show
  1. package/README.md +113 -98
  2. package/dist/api-client.d.ts +1114 -0
  3. package/dist/api-client.js +698 -0
  4. package/dist/cli.d.ts +1 -6
  5. package/dist/cli.js +39 -240
  6. package/dist/config/tool-categories.d.ts +31 -0
  7. package/dist/config/tool-categories.js +253 -0
  8. package/dist/handlers/blockers.js +57 -58
  9. package/dist/handlers/bodies-of-work.d.ts +2 -0
  10. package/dist/handlers/bodies-of-work.js +106 -476
  11. package/dist/handlers/cost.d.ts +1 -0
  12. package/dist/handlers/cost.js +35 -113
  13. package/dist/handlers/decisions.d.ts +2 -0
  14. package/dist/handlers/decisions.js +28 -27
  15. package/dist/handlers/deployment.js +112 -828
  16. package/dist/handlers/discovery.js +31 -0
  17. package/dist/handlers/fallback.d.ts +2 -0
  18. package/dist/handlers/fallback.js +39 -134
  19. package/dist/handlers/findings.js +43 -67
  20. package/dist/handlers/git-issues.d.ts +9 -13
  21. package/dist/handlers/git-issues.js +80 -225
  22. package/dist/handlers/ideas.d.ts +3 -0
  23. package/dist/handlers/ideas.js +53 -134
  24. package/dist/handlers/index.d.ts +2 -0
  25. package/dist/handlers/index.js +6 -0
  26. package/dist/handlers/milestones.d.ts +2 -0
  27. package/dist/handlers/milestones.js +51 -98
  28. package/dist/handlers/organizations.js +79 -275
  29. package/dist/handlers/progress.d.ts +2 -0
  30. package/dist/handlers/progress.js +25 -123
  31. package/dist/handlers/project.js +42 -221
  32. package/dist/handlers/requests.d.ts +2 -0
  33. package/dist/handlers/requests.js +23 -83
  34. package/dist/handlers/session.js +99 -585
  35. package/dist/handlers/sprints.d.ts +32 -0
  36. package/dist/handlers/sprints.js +274 -0
  37. package/dist/handlers/tasks.d.ts +7 -10
  38. package/dist/handlers/tasks.js +230 -900
  39. package/dist/handlers/tool-docs.d.ts +8 -0
  40. package/dist/handlers/tool-docs.js +657 -0
  41. package/dist/handlers/types.d.ts +11 -3
  42. package/dist/handlers/validation.d.ts +1 -1
  43. package/dist/handlers/validation.js +26 -153
  44. package/dist/index.js +473 -160
  45. package/dist/knowledge.js +106 -9
  46. package/dist/tools.js +4 -0
  47. package/dist/validators.d.ts +21 -0
  48. package/dist/validators.js +91 -0
  49. package/package.json +2 -3
  50. package/src/api-client.ts +1752 -0
  51. package/src/cli.test.ts +128 -302
  52. package/src/cli.ts +41 -285
  53. package/src/handlers/__test-setup__.ts +210 -0
  54. package/src/handlers/__test-utils__.ts +4 -134
  55. package/src/handlers/blockers.test.ts +114 -124
  56. package/src/handlers/blockers.ts +68 -70
  57. package/src/handlers/bodies-of-work.test.ts +236 -831
  58. package/src/handlers/bodies-of-work.ts +194 -525
  59. package/src/handlers/cost.test.ts +149 -113
  60. package/src/handlers/cost.ts +44 -132
  61. package/src/handlers/decisions.test.ts +111 -209
  62. package/src/handlers/decisions.ts +35 -27
  63. package/src/handlers/deployment.test.ts +193 -239
  64. package/src/handlers/deployment.ts +140 -895
  65. package/src/handlers/discovery.test.ts +20 -67
  66. package/src/handlers/discovery.ts +32 -0
  67. package/src/handlers/fallback.test.ts +128 -361
  68. package/src/handlers/fallback.ts +62 -148
  69. package/src/handlers/findings.test.ts +127 -345
  70. package/src/handlers/findings.ts +49 -66
  71. package/src/handlers/git-issues.test.ts +623 -0
  72. package/src/handlers/git-issues.ts +174 -0
  73. package/src/handlers/ideas.test.ts +229 -343
  74. package/src/handlers/ideas.ts +69 -143
  75. package/src/handlers/index.ts +6 -0
  76. package/src/handlers/milestones.test.ts +167 -281
  77. package/src/handlers/milestones.ts +54 -93
  78. package/src/handlers/organizations.test.ts +275 -467
  79. package/src/handlers/organizations.ts +84 -294
  80. package/src/handlers/progress.test.ts +112 -218
  81. package/src/handlers/progress.ts +29 -142
  82. package/src/handlers/project.test.ts +203 -226
  83. package/src/handlers/project.ts +48 -238
  84. package/src/handlers/requests.test.ts +74 -342
  85. package/src/handlers/requests.ts +25 -83
  86. package/src/handlers/session.test.ts +241 -206
  87. package/src/handlers/session.ts +110 -657
  88. package/src/handlers/sprints.test.ts +711 -0
  89. package/src/handlers/sprints.ts +497 -0
  90. package/src/handlers/tasks.test.ts +608 -353
  91. package/src/handlers/tasks.ts +248 -1025
  92. package/src/handlers/types.ts +12 -4
  93. package/src/handlers/validation.test.ts +189 -572
  94. package/src/handlers/validation.ts +29 -166
  95. package/src/index.ts +473 -184
  96. package/src/knowledge.ts +107 -9
  97. package/src/tools.ts +2506 -0
  98. package/src/validators.test.ts +223 -223
  99. package/src/validators.ts +127 -0
  100. package/tsconfig.json +1 -1
  101. package/vitest.config.ts +14 -13
  102. package/dist/cli.test.d.ts +0 -1
  103. package/dist/cli.test.js +0 -367
  104. package/dist/handlers/__test-utils__.d.ts +0 -72
  105. package/dist/handlers/__test-utils__.js +0 -176
  106. package/dist/handlers/checkouts.d.ts +0 -37
  107. package/dist/handlers/checkouts.js +0 -377
  108. package/dist/handlers/knowledge-query.d.ts +0 -22
  109. package/dist/handlers/knowledge-query.js +0 -253
  110. package/dist/handlers/knowledge.d.ts +0 -12
  111. package/dist/handlers/knowledge.js +0 -108
  112. package/dist/handlers/roles.d.ts +0 -30
  113. package/dist/handlers/roles.js +0 -281
  114. package/dist/handlers/tasks.test.d.ts +0 -1
  115. package/dist/handlers/tasks.test.js +0 -431
  116. package/dist/utils.test.d.ts +0 -1
  117. package/dist/utils.test.js +0 -532
  118. package/dist/validators.test.d.ts +0 -1
  119. package/dist/validators.test.js +0 -176
  120. package/src/tmpclaude-0078-cwd +0 -1
  121. package/src/tmpclaude-0ee1-cwd +0 -1
  122. package/src/tmpclaude-2dd5-cwd +0 -1
  123. package/src/tmpclaude-344c-cwd +0 -1
  124. package/src/tmpclaude-3860-cwd +0 -1
  125. package/src/tmpclaude-4b63-cwd +0 -1
  126. package/src/tmpclaude-5c73-cwd +0 -1
  127. package/src/tmpclaude-5ee3-cwd +0 -1
  128. package/src/tmpclaude-6795-cwd +0 -1
  129. package/src/tmpclaude-709e-cwd +0 -1
  130. package/src/tmpclaude-9839-cwd +0 -1
  131. package/src/tmpclaude-d829-cwd +0 -1
  132. package/src/tmpclaude-e072-cwd +0 -1
  133. package/src/tmpclaude-f6ee-cwd +0 -1
  134. package/tmpclaude-0439-cwd +0 -1
  135. package/tmpclaude-132f-cwd +0 -1
  136. package/tmpclaude-15bb-cwd +0 -1
  137. package/tmpclaude-165a-cwd +0 -1
  138. package/tmpclaude-1ba9-cwd +0 -1
  139. package/tmpclaude-21a3-cwd +0 -1
  140. package/tmpclaude-2a38-cwd +0 -1
  141. package/tmpclaude-2adf-cwd +0 -1
  142. package/tmpclaude-2f56-cwd +0 -1
  143. package/tmpclaude-3626-cwd +0 -1
  144. package/tmpclaude-3727-cwd +0 -1
  145. package/tmpclaude-40bc-cwd +0 -1
  146. package/tmpclaude-436f-cwd +0 -1
  147. package/tmpclaude-4783-cwd +0 -1
  148. package/tmpclaude-4b6d-cwd +0 -1
  149. package/tmpclaude-4ba4-cwd +0 -1
  150. package/tmpclaude-51e6-cwd +0 -1
  151. package/tmpclaude-5ecf-cwd +0 -1
  152. package/tmpclaude-6f97-cwd +0 -1
  153. package/tmpclaude-7fb2-cwd +0 -1
  154. package/tmpclaude-825c-cwd +0 -1
  155. package/tmpclaude-8baf-cwd +0 -1
  156. package/tmpclaude-8d9f-cwd +0 -1
  157. package/tmpclaude-975c-cwd +0 -1
  158. package/tmpclaude-9983-cwd +0 -1
  159. package/tmpclaude-a045-cwd +0 -1
  160. package/tmpclaude-ac4a-cwd +0 -1
  161. package/tmpclaude-b593-cwd +0 -1
  162. package/tmpclaude-b891-cwd +0 -1
  163. package/tmpclaude-c032-cwd +0 -1
  164. package/tmpclaude-cf43-cwd +0 -1
  165. package/tmpclaude-d040-cwd +0 -1
  166. package/tmpclaude-dcdd-cwd +0 -1
  167. package/tmpclaude-dcee-cwd +0 -1
  168. package/tmpclaude-e16b-cwd +0 -1
  169. package/tmpclaude-ecd2-cwd +0 -1
  170. package/tmpclaude-f48d-cwd +0 -1
package/dist/index.js CHANGED
@@ -2,11 +2,10 @@
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
- import { createClient } from '@supabase/supabase-js';
6
5
  import { randomUUID } from 'crypto';
6
+ import { initApiClient, getApiClient } from './api-client.js';
7
7
  import { ValidationError, } from './validators.js';
8
8
  import { RateLimiter, } from './utils.js';
9
- import { hashApiKey } from './cli.js';
10
9
  import { buildHandlerRegistry } from './handlers/index.js';
11
10
  // ============================================================================
12
11
  // Agent Instance Tracking
@@ -17,6 +16,8 @@ const INSTANCE_ID = randomUUID();
17
16
  let currentSessionId = null;
18
17
  // Assigned persona for this agent instance
19
18
  let currentPersona = null;
19
+ // Current role for this agent instance
20
+ let currentRole = null;
20
21
  let sessionTokenUsage = {
21
22
  callCount: 0,
22
23
  totalTokens: 0,
@@ -152,158 +153,29 @@ Be mindful of token costs - every tool call has a cost.
152
153
  // ============================================================================
153
154
  // Configuration
154
155
  // ============================================================================
155
- // Default Supabase URL for Vibescope production instance
156
- const DEFAULT_SUPABASE_URL = 'https://uuneucmuubpgswvfijwd.supabase.co';
157
- const SUPABASE_URL = process.env.SUPABASE_URL || process.env.PUBLIC_SUPABASE_URL || DEFAULT_SUPABASE_URL;
158
- const SUPABASE_SERVICE_KEY = process.env.SUPABASE_SERVICE_KEY;
159
156
  const API_KEY = process.env.VIBESCOPE_API_KEY;
160
- if (!SUPABASE_SERVICE_KEY) {
161
- console.error('Missing required environment variable: SUPABASE_SERVICE_KEY');
162
- process.exit(1);
163
- }
164
157
  if (!API_KEY) {
165
158
  console.error('Missing required environment variable: VIBESCOPE_API_KEY');
166
159
  process.exit(1);
167
160
  }
168
- // ============================================================================
169
- // Database Client
170
- // ============================================================================
171
- // Using untyped client for MCP server - data validation happens at runtime
172
- const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY);
161
+ // Initialize the API client
162
+ initApiClient({ apiKey: API_KEY });
173
163
  // ============================================================================
174
164
  // Authentication
175
165
  // ============================================================================
176
- async function validateApiKey(apiKey) {
177
- const keyHash = hashApiKey(apiKey);
178
- const { data, error } = await supabase
179
- .from('api_keys')
180
- .select('id, user_id, organization_id, scope, is_valid')
181
- .eq('key_hash', keyHash)
182
- .single();
183
- if (error || !data) {
184
- return null;
185
- }
186
- // Check if key has been invalidated (e.g., user left org)
187
- if (data.is_valid === false) {
166
+ async function validateApiKey() {
167
+ const apiClient = getApiClient();
168
+ const response = await apiClient.validateAuth();
169
+ if (!response.ok || !response.data?.valid) {
188
170
  return null;
189
171
  }
190
- // For org-scoped keys, verify user is still a member
191
- if (data.scope === 'organization' && data.organization_id) {
192
- const { data: member } = await supabase
193
- .from('organization_members')
194
- .select('role')
195
- .eq('organization_id', data.organization_id)
196
- .eq('user_id', data.user_id)
197
- .single();
198
- if (!member) {
199
- // User is no longer a member, invalidate the key
200
- await supabase
201
- .from('api_keys')
202
- .update({ is_valid: false })
203
- .eq('id', data.id);
204
- return null;
205
- }
206
- }
207
- // Update last_used_at
208
- await supabase
209
- .from('api_keys')
210
- .update({ last_used_at: new Date().toISOString() })
211
- .eq('id', data.id);
212
172
  return {
213
- userId: data.user_id,
214
- apiKeyId: data.id,
215
- organizationId: data.organization_id || undefined,
216
- scope: data.scope || 'personal',
173
+ userId: response.data.user_id,
174
+ apiKeyId: response.data.api_key_id,
175
+ scope: 'personal', // API handles authorization, scope not needed locally
217
176
  };
218
177
  }
219
178
  // ============================================================================
220
- // Session Status Checking (Multi-Agent Coordination)
221
- // ============================================================================
222
- /**
223
- * Check if an agent session is active or stale.
224
- * A session is considered stale if:
225
- * - It doesn't exist (orphaned reference)
226
- * - Its status is 'disconnected'
227
- * - Its last_synced_at is older than 5 minutes
228
- */
229
- async function checkSessionStatus(sessionId) {
230
- const { data: session } = await supabase
231
- .from('agent_sessions')
232
- .select('id, status, last_synced_at, agent_name, instance_id')
233
- .eq('id', sessionId)
234
- .single();
235
- if (!session) {
236
- return { exists: false, isActive: false };
237
- }
238
- const lastSync = new Date(session.last_synced_at).getTime();
239
- const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
240
- const isActive = session.status !== 'disconnected' && lastSync > fiveMinutesAgo;
241
- return {
242
- exists: true,
243
- isActive,
244
- agentName: session.agent_name || `Agent ${session.instance_id?.slice(0, 8) || sessionId.slice(0, 8)}`,
245
- };
246
- }
247
- // ============================================================================
248
- // User Updates Tracking
249
- // ============================================================================
250
- async function getUserUpdates(auth, projectId) {
251
- // Get session's last_synced_at - prefer currentSessionId for accuracy
252
- let lastSyncedAt;
253
- if (currentSessionId) {
254
- const { data: session } = await supabase
255
- .from('agent_sessions')
256
- .select('last_synced_at')
257
- .eq('id', currentSessionId)
258
- .single();
259
- lastSyncedAt = session?.last_synced_at || new Date(0).toISOString();
260
- }
261
- else {
262
- const { data: session } = await supabase
263
- .from('agent_sessions')
264
- .select('last_synced_at')
265
- .eq('api_key_id', auth.apiKeyId)
266
- .eq('project_id', projectId)
267
- .single();
268
- lastSyncedAt = session?.last_synced_at || new Date(0).toISOString();
269
- }
270
- // Fetch user-created items since last sync (limit to 5 each for context efficiency)
271
- const [tasksResult, blockersResult, ideasResult] = await Promise.all([
272
- supabase
273
- .from('tasks')
274
- .select('id, title, created_at')
275
- .eq('project_id', projectId)
276
- .eq('created_by', 'user')
277
- .gt('created_at', lastSyncedAt)
278
- .order('created_at', { ascending: false })
279
- .limit(5),
280
- supabase
281
- .from('blockers')
282
- .select('id, description, created_at')
283
- .eq('project_id', projectId)
284
- .eq('created_by', 'user')
285
- .gt('created_at', lastSyncedAt)
286
- .order('created_at', { ascending: false })
287
- .limit(5),
288
- supabase
289
- .from('ideas')
290
- .select('id, title, created_at')
291
- .eq('project_id', projectId)
292
- .eq('created_by', 'user')
293
- .gt('created_at', lastSyncedAt)
294
- .order('created_at', { ascending: false })
295
- .limit(5),
296
- ]);
297
- const tasks = tasksResult.data || [];
298
- const blockers = blockersResult.data || [];
299
- const ideas = ideasResult.data || [];
300
- // Return undefined if no new updates (saves context window space)
301
- if (tasks.length === 0 && blockers.length === 0 && ideas.length === 0) {
302
- return undefined;
303
- }
304
- return { tasks, blockers, ideas };
305
- }
306
- // ============================================================================
307
179
  // Tool Definitions
308
180
  // ============================================================================
309
181
  const tools = [
@@ -343,7 +215,7 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
343
215
  properties: {
344
216
  topic: {
345
217
  type: 'string',
346
- enum: ['getting_started', 'tasks', 'validation', 'deployment', 'git', 'blockers', 'milestones', 'fallback', 'session', 'tokens', 'topics'],
218
+ enum: ['getting_started', 'tasks', 'validation', 'deployment', 'git', 'blockers', 'milestones', 'fallback', 'session', 'tokens', 'sprints', 'topics'],
347
219
  description: 'Help topic. Use "topics" to list all available.',
348
220
  },
349
221
  },
@@ -669,6 +541,14 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
669
541
  type: 'number',
670
542
  description: 'Max number of tasks to return (default 50)',
671
543
  },
544
+ offset: {
545
+ type: 'number',
546
+ description: 'Number of tasks to skip for pagination (default 0)',
547
+ },
548
+ search_query: {
549
+ type: 'string',
550
+ description: 'Search tasks by title',
551
+ },
672
552
  include_subtasks: {
673
553
  type: 'boolean',
674
554
  description: 'Include subtasks in results (default false). Use get_subtasks for subtasks of a specific task.',
@@ -724,6 +604,11 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
724
604
  type: 'boolean',
725
605
  description: 'When true, this task blocks all other work until complete (used for deployment finalization)',
726
606
  },
607
+ task_type: {
608
+ type: 'string',
609
+ enum: ['frontend', 'backend', 'database', 'mcp', 'testing', 'docs', 'infra', 'other'],
610
+ description: 'Task category for visual grouping (frontend, backend, database, mcp, testing, docs, infra, other)',
611
+ },
727
612
  },
728
613
  required: ['project_id', 'title'],
729
614
  },
@@ -768,6 +653,11 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
768
653
  type: 'string',
769
654
  description: 'Git branch associated with this task',
770
655
  },
656
+ task_type: {
657
+ type: 'string',
658
+ enum: ['frontend', 'backend', 'database', 'mcp', 'testing', 'docs', 'infra', 'other'],
659
+ description: 'Task category (frontend, backend, database, mcp, testing, docs, infra, other)',
660
+ },
771
661
  },
772
662
  required: ['task_id'],
773
663
  },
@@ -867,6 +757,18 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
867
757
  enum: ['open', 'resolved'],
868
758
  description: 'Filter by status (default: open)',
869
759
  },
760
+ limit: {
761
+ type: 'number',
762
+ description: 'Max number of blockers to return (default 50, max 200)',
763
+ },
764
+ offset: {
765
+ type: 'number',
766
+ description: 'Number of blockers to skip for pagination (default 0)',
767
+ },
768
+ search_query: {
769
+ type: 'string',
770
+ description: 'Search blockers by description',
771
+ },
870
772
  },
871
773
  required: ['project_id'],
872
774
  },
@@ -912,6 +814,18 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
912
814
  type: 'string',
913
815
  description: 'Project UUID',
914
816
  },
817
+ limit: {
818
+ type: 'number',
819
+ description: 'Max number of decisions to return (default 50, max 200)',
820
+ },
821
+ offset: {
822
+ type: 'number',
823
+ description: 'Number of decisions to skip for pagination (default 0)',
824
+ },
825
+ search_query: {
826
+ type: 'string',
827
+ description: 'Search decisions by title or description',
828
+ },
915
829
  },
916
830
  required: ['project_id'],
917
831
  },
@@ -989,6 +903,18 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
989
903
  enum: ['raw', 'exploring', 'planned', 'in_development', 'shipped'],
990
904
  description: 'Filter by status (optional)',
991
905
  },
906
+ limit: {
907
+ type: 'number',
908
+ description: 'Max number of ideas to return (default 50, max 100)',
909
+ },
910
+ offset: {
911
+ type: 'number',
912
+ description: 'Number of ideas to skip for pagination (default 0)',
913
+ },
914
+ search_query: {
915
+ type: 'string',
916
+ description: 'Search ideas by title or description',
917
+ },
992
918
  },
993
919
  required: ['project_id'],
994
920
  },
@@ -1119,7 +1045,9 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1119
1045
  category: { type: 'string', enum: ['performance', 'security', 'code_quality', 'accessibility', 'documentation', 'architecture', 'testing', 'other'], description: 'Filter by category (optional)' },
1120
1046
  severity: { type: 'string', enum: ['info', 'low', 'medium', 'high', 'critical'], description: 'Filter by severity (optional)' },
1121
1047
  status: { type: 'string', enum: ['open', 'addressed', 'dismissed', 'wontfix'], description: 'Filter by status (default: all)' },
1122
- limit: { type: 'number', description: 'Max number of findings to return (default 50)' },
1048
+ limit: { type: 'number', description: 'Max number of findings to return (default 50, max 200)' },
1049
+ offset: { type: 'number', description: 'Number of findings to skip for pagination (default 0)' },
1050
+ search_query: { type: 'string', description: 'Search findings by title or description' },
1123
1051
  },
1124
1052
  required: ['project_id'],
1125
1053
  },
@@ -1165,6 +1093,119 @@ Returns session info, persona, and next task. Use mode:'full' for complete conte
1165
1093
  required: ['blocker_id'],
1166
1094
  },
1167
1095
  },
1096
+ // Git Issues tools
1097
+ {
1098
+ name: 'add_git_issue',
1099
+ description: `Record a git-related issue (merge conflict, push failure, etc.). Auto-created by claim_validation when conflicts detected.`,
1100
+ inputSchema: {
1101
+ type: 'object',
1102
+ properties: {
1103
+ project_id: {
1104
+ type: 'string',
1105
+ description: 'Project UUID',
1106
+ },
1107
+ issue_type: {
1108
+ type: 'string',
1109
+ enum: ['merge_conflict', 'push_failed', 'rebase_needed', 'branch_diverged', 'pr_not_mergeable'],
1110
+ description: 'Type of git issue',
1111
+ },
1112
+ branch: {
1113
+ type: 'string',
1114
+ description: 'Branch where the issue occurred',
1115
+ },
1116
+ target_branch: {
1117
+ type: 'string',
1118
+ description: 'Target branch for merge/rebase (optional)',
1119
+ },
1120
+ pr_url: {
1121
+ type: 'string',
1122
+ description: 'Pull request URL if applicable (optional)',
1123
+ },
1124
+ conflicting_files: {
1125
+ type: 'array',
1126
+ items: { type: 'string' },
1127
+ description: 'List of files with conflicts (optional)',
1128
+ },
1129
+ error_message: {
1130
+ type: 'string',
1131
+ description: 'Error message from git operation (optional)',
1132
+ },
1133
+ task_id: {
1134
+ type: 'string',
1135
+ description: 'Related task UUID (optional)',
1136
+ },
1137
+ },
1138
+ required: ['project_id', 'issue_type', 'branch'],
1139
+ },
1140
+ },
1141
+ {
1142
+ name: 'resolve_git_issue',
1143
+ description: `Mark a git issue as resolved.`,
1144
+ inputSchema: {
1145
+ type: 'object',
1146
+ properties: {
1147
+ git_issue_id: {
1148
+ type: 'string',
1149
+ description: 'Git issue UUID',
1150
+ },
1151
+ resolution_note: {
1152
+ type: 'string',
1153
+ description: 'How the issue was resolved (optional)',
1154
+ },
1155
+ auto_resolved: {
1156
+ type: 'boolean',
1157
+ description: 'Whether this was auto-resolved (e.g., PR became mergeable)',
1158
+ },
1159
+ },
1160
+ required: ['git_issue_id'],
1161
+ },
1162
+ },
1163
+ {
1164
+ name: 'get_git_issues',
1165
+ description: `Get git issues for a project, optionally filtered by status, type, or branch.`,
1166
+ inputSchema: {
1167
+ type: 'object',
1168
+ properties: {
1169
+ project_id: {
1170
+ type: 'string',
1171
+ description: 'Project UUID',
1172
+ },
1173
+ status: {
1174
+ type: 'string',
1175
+ enum: ['open', 'resolved'],
1176
+ description: 'Filter by status (default: open)',
1177
+ },
1178
+ issue_type: {
1179
+ type: 'string',
1180
+ enum: ['merge_conflict', 'push_failed', 'rebase_needed', 'branch_diverged', 'pr_not_mergeable'],
1181
+ description: 'Filter by issue type (optional)',
1182
+ },
1183
+ branch: {
1184
+ type: 'string',
1185
+ description: 'Filter by branch (optional)',
1186
+ },
1187
+ limit: {
1188
+ type: 'number',
1189
+ description: 'Max issues to return (default: 50)',
1190
+ },
1191
+ },
1192
+ required: ['project_id'],
1193
+ },
1194
+ },
1195
+ {
1196
+ name: 'delete_git_issue',
1197
+ description: `Delete a git issue.`,
1198
+ inputSchema: {
1199
+ type: 'object',
1200
+ properties: {
1201
+ git_issue_id: {
1202
+ type: 'string',
1203
+ description: 'Git issue UUID',
1204
+ },
1205
+ },
1206
+ required: ['git_issue_id'],
1207
+ },
1208
+ },
1168
1209
  {
1169
1210
  name: 'delete_decision',
1170
1211
  description: `Delete a decision.`,
@@ -2129,6 +2170,18 @@ Bodies of work allow organizing related tasks with optional auto-deployment on c
2129
2170
  enum: ['draft', 'active', 'completed', 'cancelled'],
2130
2171
  description: 'Filter by status (optional)',
2131
2172
  },
2173
+ limit: {
2174
+ type: 'number',
2175
+ description: 'Max number of bodies of work to return (default 50, max 100)',
2176
+ },
2177
+ offset: {
2178
+ type: 'number',
2179
+ description: 'Number of items to skip for pagination (default 0)',
2180
+ },
2181
+ search_query: {
2182
+ type: 'string',
2183
+ description: 'Search bodies of work by title or description',
2184
+ },
2132
2185
  },
2133
2186
  required: ['project_id'],
2134
2187
  },
@@ -2279,6 +2332,273 @@ Only returns tasks where all dependencies are completed.`,
2279
2332
  },
2280
2333
  },
2281
2334
  // ============================================================================
2335
+ // Sprint Tools
2336
+ // ============================================================================
2337
+ {
2338
+ name: 'create_sprint',
2339
+ description: `Create a new sprint. Sprints are time-bounded bodies of work with velocity tracking.
2340
+ Sprints start in 'planning' status where tasks can be added with story points.`,
2341
+ inputSchema: {
2342
+ type: 'object',
2343
+ properties: {
2344
+ project_id: {
2345
+ type: 'string',
2346
+ description: 'Project UUID',
2347
+ },
2348
+ title: {
2349
+ type: 'string',
2350
+ description: 'Sprint title (e.g., "Sprint 5" or "Q1 Release")',
2351
+ },
2352
+ goal: {
2353
+ type: 'string',
2354
+ description: 'Sprint goal statement',
2355
+ },
2356
+ start_date: {
2357
+ type: 'string',
2358
+ description: 'Start date (YYYY-MM-DD)',
2359
+ },
2360
+ end_date: {
2361
+ type: 'string',
2362
+ description: 'End date (YYYY-MM-DD)',
2363
+ },
2364
+ auto_deploy_on_completion: {
2365
+ type: 'boolean',
2366
+ description: 'Automatically request deployment when sprint completes (default: false)',
2367
+ },
2368
+ deploy_environment: {
2369
+ type: 'string',
2370
+ enum: ['development', 'staging', 'production'],
2371
+ description: 'Target environment for auto-deploy (default: production)',
2372
+ },
2373
+ deploy_version_bump: {
2374
+ type: 'string',
2375
+ enum: ['patch', 'minor', 'major'],
2376
+ description: 'Version bump for auto-deploy (default: minor)',
2377
+ },
2378
+ },
2379
+ required: ['project_id', 'title', 'start_date', 'end_date'],
2380
+ },
2381
+ },
2382
+ {
2383
+ name: 'update_sprint',
2384
+ description: `Update a sprint's details. Can update title, goal, dates, and deployment settings.`,
2385
+ inputSchema: {
2386
+ type: 'object',
2387
+ properties: {
2388
+ sprint_id: {
2389
+ type: 'string',
2390
+ description: 'Sprint UUID',
2391
+ },
2392
+ title: {
2393
+ type: 'string',
2394
+ description: 'New sprint title',
2395
+ },
2396
+ goal: {
2397
+ type: 'string',
2398
+ description: 'New sprint goal',
2399
+ },
2400
+ start_date: {
2401
+ type: 'string',
2402
+ description: 'New start date (YYYY-MM-DD)',
2403
+ },
2404
+ end_date: {
2405
+ type: 'string',
2406
+ description: 'New end date (YYYY-MM-DD)',
2407
+ },
2408
+ auto_deploy_on_completion: {
2409
+ type: 'boolean',
2410
+ description: 'Auto-deploy setting',
2411
+ },
2412
+ deploy_environment: {
2413
+ type: 'string',
2414
+ enum: ['development', 'staging', 'production'],
2415
+ description: 'Target environment',
2416
+ },
2417
+ deploy_version_bump: {
2418
+ type: 'string',
2419
+ enum: ['patch', 'minor', 'major'],
2420
+ description: 'Version bump type',
2421
+ },
2422
+ },
2423
+ required: ['sprint_id'],
2424
+ },
2425
+ },
2426
+ {
2427
+ name: 'get_sprint',
2428
+ description: `Get a sprint with all its tasks organized by phase (pre/core/post).
2429
+ Includes progress percentage, velocity points, and committed points.`,
2430
+ inputSchema: {
2431
+ type: 'object',
2432
+ properties: {
2433
+ sprint_id: {
2434
+ type: 'string',
2435
+ description: 'Sprint UUID',
2436
+ },
2437
+ },
2438
+ required: ['sprint_id'],
2439
+ },
2440
+ },
2441
+ {
2442
+ name: 'get_sprints',
2443
+ description: `List sprints for a project with velocity metrics.
2444
+ Returns sprints sorted by sprint_number descending (most recent first).`,
2445
+ inputSchema: {
2446
+ type: 'object',
2447
+ properties: {
2448
+ project_id: {
2449
+ type: 'string',
2450
+ description: 'Project UUID',
2451
+ },
2452
+ status: {
2453
+ type: 'string',
2454
+ enum: ['planning', 'active', 'in_review', 'retrospective', 'completed', 'cancelled'],
2455
+ description: 'Filter by sprint status (optional)',
2456
+ },
2457
+ limit: {
2458
+ type: 'number',
2459
+ description: 'Max sprints to return (default: 20, max: 100)',
2460
+ },
2461
+ },
2462
+ required: ['project_id'],
2463
+ },
2464
+ },
2465
+ {
2466
+ name: 'delete_sprint',
2467
+ description: `Delete a sprint. Tasks are preserved but no longer grouped.`,
2468
+ inputSchema: {
2469
+ type: 'object',
2470
+ properties: {
2471
+ sprint_id: {
2472
+ type: 'string',
2473
+ description: 'Sprint UUID',
2474
+ },
2475
+ },
2476
+ required: ['sprint_id'],
2477
+ },
2478
+ },
2479
+ {
2480
+ name: 'start_sprint',
2481
+ description: `Start a sprint. Transitions from 'planning' to 'active' status.
2482
+ Locks the committed_points at the current total story points.`,
2483
+ inputSchema: {
2484
+ type: 'object',
2485
+ properties: {
2486
+ sprint_id: {
2487
+ type: 'string',
2488
+ description: 'Sprint UUID',
2489
+ },
2490
+ },
2491
+ required: ['sprint_id'],
2492
+ },
2493
+ },
2494
+ {
2495
+ name: 'complete_sprint',
2496
+ description: `Complete a sprint. Handles retrospective phase and auto-deployment if configured.
2497
+ Status flow: active → in_review → retrospective → completed`,
2498
+ inputSchema: {
2499
+ type: 'object',
2500
+ properties: {
2501
+ sprint_id: {
2502
+ type: 'string',
2503
+ description: 'Sprint UUID',
2504
+ },
2505
+ retrospective_notes: {
2506
+ type: 'string',
2507
+ description: 'Sprint retrospective notes',
2508
+ },
2509
+ skip_retrospective: {
2510
+ type: 'boolean',
2511
+ description: 'Skip retrospective phase and go directly to completed (default: false)',
2512
+ },
2513
+ },
2514
+ required: ['sprint_id'],
2515
+ },
2516
+ },
2517
+ {
2518
+ name: 'add_task_to_sprint',
2519
+ description: `Add a task to a sprint with optional story points.
2520
+ Tasks can be added during 'planning' status. Story points contribute to committed_points.`,
2521
+ inputSchema: {
2522
+ type: 'object',
2523
+ properties: {
2524
+ sprint_id: {
2525
+ type: 'string',
2526
+ description: 'Sprint UUID',
2527
+ },
2528
+ task_id: {
2529
+ type: 'string',
2530
+ description: 'Task UUID to add',
2531
+ },
2532
+ story_points: {
2533
+ type: 'number',
2534
+ description: 'Story point estimate (optional, must be non-negative integer)',
2535
+ },
2536
+ phase: {
2537
+ type: 'string',
2538
+ enum: ['pre', 'core', 'post'],
2539
+ description: 'Task phase (default: core)',
2540
+ },
2541
+ },
2542
+ required: ['sprint_id', 'task_id'],
2543
+ },
2544
+ },
2545
+ {
2546
+ name: 'remove_task_from_sprint',
2547
+ description: `Remove a task from a sprint. Task is preserved but returns to backlog.`,
2548
+ inputSchema: {
2549
+ type: 'object',
2550
+ properties: {
2551
+ sprint_id: {
2552
+ type: 'string',
2553
+ description: 'Sprint UUID',
2554
+ },
2555
+ task_id: {
2556
+ type: 'string',
2557
+ description: 'Task UUID to remove',
2558
+ },
2559
+ },
2560
+ required: ['sprint_id', 'task_id'],
2561
+ },
2562
+ },
2563
+ {
2564
+ name: 'get_sprint_backlog',
2565
+ description: `Get tasks from backlog/pending that can be added to a sprint.
2566
+ Returns tasks not already assigned to any body of work or sprint.`,
2567
+ inputSchema: {
2568
+ type: 'object',
2569
+ properties: {
2570
+ project_id: {
2571
+ type: 'string',
2572
+ description: 'Project UUID',
2573
+ },
2574
+ sprint_id: {
2575
+ type: 'string',
2576
+ description: 'Sprint UUID to exclude already-added tasks (optional)',
2577
+ },
2578
+ },
2579
+ required: ['project_id'],
2580
+ },
2581
+ },
2582
+ {
2583
+ name: 'get_sprint_velocity',
2584
+ description: `Get velocity metrics for completed sprints.
2585
+ Returns committed vs completed points and average velocity.`,
2586
+ inputSchema: {
2587
+ type: 'object',
2588
+ properties: {
2589
+ project_id: {
2590
+ type: 'string',
2591
+ description: 'Project UUID',
2592
+ },
2593
+ limit: {
2594
+ type: 'number',
2595
+ description: 'Number of sprints to analyze (default: 10, max: 50)',
2596
+ },
2597
+ },
2598
+ required: ['project_id'],
2599
+ },
2600
+ },
2601
+ // ============================================================================
2282
2602
  // Organization Tools
2283
2603
  // ============================================================================
2284
2604
  {
@@ -2526,24 +2846,15 @@ Only returns tasks where all dependencies are completed.`,
2526
2846
  // Tool Handlers
2527
2847
  // ============================================================================
2528
2848
  async function handleTool(auth, name, args) {
2529
- // Update session on every tool call:
2849
+ // Update session on every tool call via API:
2530
2850
  // - last_synced_at: keeps session alive and visible as "active" on dashboard
2531
- // - tool_call_count: incremented for token efficiency tracking
2532
- // - last_tool_at: timestamp of last tool call
2851
+ // - token tracking: synced periodically for cost monitoring
2533
2852
  // Skip for start_work_session since it handles its own session setup
2534
2853
  if (currentSessionId && name !== 'start_work_session') {
2535
- const now = new Date().toISOString();
2536
- await supabase.rpc('increment_tool_call_count', {
2537
- p_session_id: currentSessionId,
2538
- p_timestamp: now
2539
- }).then(({ error }) => {
2540
- // Fallback to direct update if RPC doesn't exist (pre-migration)
2541
- if (error) {
2542
- return supabase
2543
- .from('agent_sessions')
2544
- .update({ last_synced_at: now })
2545
- .eq('id', currentSessionId);
2546
- }
2854
+ const apiClient = getApiClient();
2855
+ // Fire and forget - don't block tool execution on session sync
2856
+ apiClient.syncSession(currentSessionId).catch(() => {
2857
+ // Silently ignore sync errors - session tracking is non-critical
2547
2858
  });
2548
2859
  }
2549
2860
  // Check if handler exists in the modular registry
@@ -2551,12 +2862,12 @@ async function handleTool(auth, name, args) {
2551
2862
  if (handler) {
2552
2863
  // Build handler context
2553
2864
  const ctx = {
2554
- supabase,
2555
2865
  auth,
2556
2866
  session: {
2557
2867
  instanceId: INSTANCE_ID,
2558
2868
  currentSessionId,
2559
2869
  currentPersona,
2870
+ currentRole,
2560
2871
  tokenUsage: sessionTokenUsage,
2561
2872
  },
2562
2873
  updateSession: (updates) => {
@@ -2564,6 +2875,8 @@ async function handleTool(auth, name, args) {
2564
2875
  currentSessionId = updates.currentSessionId;
2565
2876
  if (updates.currentPersona !== undefined)
2566
2877
  currentPersona = updates.currentPersona;
2878
+ if (updates.currentRole !== undefined)
2879
+ currentRole = updates.currentRole;
2567
2880
  if (updates.tokenUsage !== undefined)
2568
2881
  sessionTokenUsage = updates.tokenUsage;
2569
2882
  },
@@ -2577,8 +2890,8 @@ async function handleTool(auth, name, args) {
2577
2890
  // Server Setup
2578
2891
  // ============================================================================
2579
2892
  async function main() {
2580
- // Validate API key on startup
2581
- const auth = await validateApiKey(API_KEY);
2893
+ // Validate API key on startup via API
2894
+ const auth = await validateApiKey();
2582
2895
  if (!auth) {
2583
2896
  console.error('Invalid API key');
2584
2897
  process.exit(1);