@vibescope/mcp-server 0.2.7 → 0.2.9

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.
@@ -29,10 +29,10 @@ export declare class VibescopeApiClient {
29
29
  project_id?: string;
30
30
  git_url?: string;
31
31
  mode?: 'lite' | 'full';
32
- model?: 'opus' | 'sonnet' | 'haiku';
33
- role?: 'developer' | 'validator' | 'deployer' | 'reviewer' | 'maintainer';
32
+ model?: string;
33
+ role?: string;
34
34
  hostname?: string;
35
- agent_type?: 'claude' | 'gemini' | 'cursor' | 'windsurf' | 'other';
35
+ agent_type?: string;
36
36
  }): Promise<ApiResponse<{
37
37
  session_started: boolean;
38
38
  session_id?: string;
@@ -970,7 +970,7 @@ export declare class VibescopeApiClient {
970
970
  reportTokenUsage(sessionId: string, params: {
971
971
  input_tokens: number;
972
972
  output_tokens: number;
973
- model?: 'opus' | 'sonnet' | 'haiku';
973
+ model?: string;
974
974
  }): Promise<ApiResponse<{
975
975
  success: boolean;
976
976
  reported: {
@@ -1676,7 +1676,7 @@ export declare class VibescopeApiClient {
1676
1676
  * Confirm that agent setup is complete for a project.
1677
1677
  * This marks the agent type as onboarded so future sessions don't receive setup instructions.
1678
1678
  */
1679
- confirmAgentSetup(projectId: string, agentType: 'claude' | 'gemini' | 'cursor' | 'windsurf' | 'other'): Promise<ApiResponse<{
1679
+ confirmAgentSetup(projectId: string, agentType: string): Promise<ApiResponse<{
1680
1680
  success: boolean;
1681
1681
  project_id: string;
1682
1682
  agent_type: string;
@@ -63,14 +63,7 @@ export const setSessionRole = async (args, ctx) => {
63
63
  result: { error: 'role is required' },
64
64
  };
65
65
  }
66
- const validRoles = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'];
67
- if (!validRoles.includes(role)) {
68
- return {
69
- result: {
70
- error: `Invalid role: ${role}. Must be one of: ${validRoles.join(', ')}`,
71
- },
72
- };
73
- }
66
+ // Role is now open-ended - any role name accepted
74
67
  // Update local session state
75
68
  updateSession({ currentRole: role });
76
69
  // If there's an active session, update it on the server too
@@ -12,21 +12,19 @@ import os from 'os';
12
12
  import { parseArgs, createEnumValidator } from '../validators.js';
13
13
  import { getApiClient } from '../api-client.js';
14
14
  import { getAgentGuidelinesTemplate, getAgentGuidelinesSummary } from '../templates/agent-guidelines.js';
15
+ import { getFallbackHelpContent, getAvailableHelpTopics } from '../templates/help-content.js';
15
16
  // Auto-detect machine hostname for worktree tracking
16
17
  const MACHINE_HOSTNAME = os.hostname();
17
18
  const VALID_MODES = ['lite', 'full'];
18
- const VALID_MODELS = ['opus', 'sonnet', 'haiku'];
19
- const VALID_ROLES = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'];
20
- const VALID_AGENT_TYPES = ['claude', 'gemini', 'cursor', 'windsurf', 'other'];
21
19
  // Argument schemas for type-safe parsing
22
20
  const startWorkSessionSchema = {
23
21
  project_id: { type: 'string' },
24
22
  git_url: { type: 'string' },
25
23
  mode: { type: 'string', default: 'lite', validate: createEnumValidator(VALID_MODES) },
26
- model: { type: 'string', validate: createEnumValidator(VALID_MODELS) },
27
- role: { type: 'string', default: 'developer', validate: createEnumValidator(VALID_ROLES) },
24
+ model: { type: 'string' }, // Open-ended - any model name accepted
25
+ role: { type: 'string', default: 'developer' }, // Open-ended - any role name accepted
28
26
  hostname: { type: 'string' }, // Machine hostname for worktree tracking
29
- agent_type: { type: 'string', validate: createEnumValidator(VALID_AGENT_TYPES) }, // Agent type for onboarding
27
+ agent_type: { type: 'string' }, // Open-ended - any agent type accepted
30
28
  };
31
29
  const heartbeatSchema = {
32
30
  session_id: { type: 'string' },
@@ -45,17 +43,15 @@ export const startWorkSession = async (args, ctx) => {
45
43
  const hostname = providedHostname || MACHINE_HOSTNAME;
46
44
  const { session, updateSession } = ctx;
47
45
  // Reset token tracking for new session with model info
46
+ // Model is now open-ended - use as-is (normalize Claude model names for consistency)
48
47
  const normalizedModel = model ? model.toLowerCase().replace(/^claude[- ]*/i, '') : null;
49
- const validModel = normalizedModel && VALID_MODELS.includes(normalizedModel)
50
- ? normalizedModel
51
- : null;
52
48
  updateSession({
53
49
  tokenUsage: {
54
50
  callCount: 0,
55
51
  totalTokens: 0,
56
52
  byTool: {},
57
53
  byModel: {},
58
- currentModel: validModel,
54
+ currentModel: normalizedModel,
59
55
  },
60
56
  });
61
57
  // Require project_id or git_url
@@ -337,28 +333,23 @@ export const getHelp = async (args, _ctx) => {
337
333
  const { topic } = parseArgs(args, getHelpSchema);
338
334
  const apiClient = getApiClient();
339
335
  const response = await apiClient.getHelpTopic(topic);
340
- if (!response.ok) {
341
- // If database fetch fails, return error
342
- return {
343
- result: {
344
- error: response.error || `Failed to fetch help topic: ${topic}`,
345
- },
346
- };
336
+ // Try database content first
337
+ if (response.ok && response.data?.content) {
338
+ return { result: { topic, content: response.data.content } };
347
339
  }
348
- if (!response.data) {
349
- // Topic not found - fetch available topics
350
- const topicsResponse = await apiClient.getHelpTopics();
351
- const available = topicsResponse.ok && topicsResponse.data
352
- ? topicsResponse.data.map(t => t.slug)
353
- : ['getting_started', 'tasks', 'validation', 'deployment', 'git', 'blockers', 'milestones', 'fallback', 'session', 'tokens', 'sprints', 'topics'];
354
- return {
355
- result: {
356
- error: `Unknown topic: ${topic}`,
357
- available,
358
- },
359
- };
340
+ // Fall back to local content if database is empty or unavailable
341
+ const fallback = getFallbackHelpContent(topic);
342
+ if (fallback) {
343
+ return { result: { topic, content: fallback.content } };
360
344
  }
361
- return { result: { topic, content: response.data.content } };
345
+ // Topic not found in either source - show available topics
346
+ const available = getAvailableHelpTopics();
347
+ return {
348
+ result: {
349
+ error: `Unknown topic: ${topic}`,
350
+ available,
351
+ },
352
+ };
362
353
  };
363
354
  const MODEL_PRICING = {
364
355
  standard: {
@@ -479,11 +470,11 @@ export const getTokenUsage = async (_args, ctx) => {
479
470
  const reportTokenUsageSchema = {
480
471
  input_tokens: { type: 'number', required: true },
481
472
  output_tokens: { type: 'number', required: true },
482
- model: { type: 'string', validate: createEnumValidator(VALID_MODELS) },
473
+ model: { type: 'string' }, // Open-ended - any model name accepted
483
474
  };
484
475
  const confirmAgentSetupSchema = {
485
476
  project_id: { type: 'string', required: true },
486
- agent_type: { type: 'string', required: true, validate: createEnumValidator(VALID_AGENT_TYPES) },
477
+ agent_type: { type: 'string', required: true }, // Open-ended - any agent type accepted
487
478
  };
488
479
  /**
489
480
  * Report actual Claude API token usage for accurate cost tracking.
@@ -6,6 +6,7 @@
6
6
  * - claim_validation
7
7
  * - validate_task
8
8
  */
9
+ import { error, success } from './types.js';
9
10
  import { parseArgs, uuidValidator } from '../validators.js';
10
11
  import { getApiClient } from '../api-client.js';
11
12
  // Argument schemas for type-safe parsing
@@ -26,9 +27,14 @@ export const getTasksAwaitingValidation = async (args, _ctx) => {
26
27
  const apiClient = getApiClient();
27
28
  const response = await apiClient.getTasksAwaitingValidation(project_id);
28
29
  if (!response.ok) {
29
- return { result: { error: response.error || 'Failed to fetch tasks awaiting validation' }, isError: true };
30
+ return error(response.error || 'Failed to fetch tasks awaiting validation');
30
31
  }
31
- return { result: response.data };
32
+ // Check for application-level errors (200 OK but error in body)
33
+ if (response.data && typeof response.data === 'object' && 'error' in response.data) {
34
+ const errMsg = typeof response.data.error === 'string' ? response.data.error : 'Unknown error';
35
+ return error(errMsg);
36
+ }
37
+ return success(response.data);
32
38
  };
33
39
  export const claimValidation = async (args, ctx) => {
34
40
  const { task_id } = parseArgs(args, claimValidationSchema);
@@ -37,9 +43,14 @@ export const claimValidation = async (args, ctx) => {
37
43
  const apiClient = getApiClient();
38
44
  const response = await apiClient.claimValidation(task_id, currentSessionId || undefined);
39
45
  if (!response.ok) {
40
- return { result: { error: response.error || 'Failed to claim task for validation' }, isError: true };
46
+ return error(response.error || 'Failed to claim task for validation');
47
+ }
48
+ // Check for application-level errors (200 OK but error in body)
49
+ if (response.data && typeof response.data === 'object' && 'error' in response.data) {
50
+ const errMsg = typeof response.data.error === 'string' ? response.data.error : 'Unknown error';
51
+ return error(errMsg);
41
52
  }
42
- return { result: response.data };
53
+ return success(response.data);
43
54
  };
44
55
  export const validateTask = async (args, ctx) => {
45
56
  const { task_id, approved, validation_notes, skip_pr_check } = parseArgs(args, validateTaskSchema);
@@ -54,18 +65,20 @@ export const validateTask = async (args, ctx) => {
54
65
  if (!response.ok) {
55
66
  // Handle PR required error specially
56
67
  if (response.error === 'pr_required') {
57
- return {
58
- result: {
59
- error: 'pr_required',
60
- message: response.data?.message || 'A Pull Request is required before validation approval. Create a PR and add it via add_task_reference.',
61
- workflow: response.data?.workflow,
62
- action_required: 'Create a PR for this task and add it via add_task_reference(task_id, pr_url, label: "Pull Request")',
63
- },
64
- };
68
+ return error('pr_required', {
69
+ message: response.data?.message || 'A Pull Request is required before validation approval. Create a PR and add it via add_task_reference.',
70
+ workflow: response.data?.workflow,
71
+ action_required: 'Create a PR for this task and add it via add_task_reference(task_id, pr_url, label: "Pull Request")',
72
+ });
65
73
  }
66
- return { result: { error: response.error || 'Failed to validate task' }, isError: true };
74
+ return error(response.error || 'Failed to validate task');
75
+ }
76
+ // Check for application-level errors (200 OK but error in body)
77
+ if (response.data && typeof response.data === 'object' && 'error' in response.data) {
78
+ const errMsg = typeof response.data.error === 'string' ? response.data.error : 'Unknown error';
79
+ return error(errMsg);
67
80
  }
68
- return { result: response.data };
81
+ return success(response.data);
69
82
  };
70
83
  /**
71
84
  * Validation handlers registry
package/dist/index.js CHANGED
@@ -41,8 +41,8 @@ const handlerRegistry = buildHandlerRegistry();
41
41
  // ============================================================================
42
42
  const KNOWLEDGE_BASE = {
43
43
  getting_started: `# Getting Started
44
- 1. Call start_work_session(git_url, model: "opus"|"sonnet"|"haiku") to initialize
45
- - IMPORTANT: Pass your model for accurate cost tracking
44
+ 1. Call start_work_session(git_url, model: "your-model-name") to initialize
45
+ - IMPORTANT: Pass your model for accurate cost tracking (e.g., "opus", "sonnet", "gemini", "gpt-4o")
46
46
  - Check your system prompt for "You are powered by the model named..." to find it
47
47
  2. Response includes next_task - start working on it immediately
48
48
  3. Use update_task to mark in_progress and track progress
@@ -99,7 +99,7 @@ When no tasks available, get_next_task suggests activities:
99
99
  3. stop_fallback_activity(project_id, summary: "...")`,
100
100
  session: `# Session Management
101
101
  - start_work_session(git_url, model) initializes and returns next_task
102
- - ALWAYS pass model parameter ("opus", "sonnet", "haiku") for cost tracking
102
+ - ALWAYS pass model parameter for cost tracking (e.g., "opus", "sonnet", "gemini", "gpt-4o")
103
103
  - Use mode:'lite' (default) or mode:'full' for complete context
104
104
  - heartbeat every 30-60 seconds maintains active status
105
105
  - end_work_session releases claimed tasks and returns summary
package/dist/setup.js CHANGED
@@ -10,7 +10,7 @@ import { homedir, platform } from 'node:os';
10
10
  import { join, dirname } from 'node:path';
11
11
  import { exec } from 'node:child_process';
12
12
  const VIBESCOPE_SETTINGS_URL = 'https://vibescope.dev/dashboard/settings';
13
- const DEFAULT_SUPABASE_URL = 'https://uuneucmuubpgswvfijwd.supabase.co';
13
+ const VIBESCOPE_API_URL = 'https://vibescope.dev';
14
14
  // ============================================================================
15
15
  // Config Path Helpers
16
16
  // ============================================================================
@@ -134,18 +134,21 @@ function openBrowser(url) {
134
134
  }
135
135
  export async function validateApiKey(apiKey) {
136
136
  try {
137
- const response = await fetch(DEFAULT_SUPABASE_URL + '/rest/v1/api_keys?key=eq.' + apiKey + '&select=id', {
137
+ const response = await fetch(VIBESCOPE_API_URL + '/api/mcp/auth/validate', {
138
+ method: 'POST',
138
139
  headers: {
139
- 'apikey': apiKey,
140
- 'Authorization': 'Bearer ' + apiKey,
140
+ 'Content-Type': 'application/json',
141
+ 'X-API-Key': apiKey,
141
142
  },
143
+ body: JSON.stringify({ api_key: apiKey }),
142
144
  });
143
145
  if (response.ok) {
144
- return { valid: true, message: 'API key validated successfully!' };
145
- }
146
- else {
147
- return { valid: false, message: 'API key appears to be invalid.' };
146
+ const data = await response.json();
147
+ if (data.valid) {
148
+ return { valid: true, message: 'API key validated successfully!' };
149
+ }
148
150
  }
151
+ return { valid: false, message: 'API key appears to be invalid.' };
149
152
  }
150
153
  catch {
151
154
  return { valid: true, message: 'Could not validate API key (network issue), but proceeding.' };
@@ -239,23 +242,43 @@ export async function runSetup() {
239
242
  }
240
243
  }
241
244
  }
242
- // Step 4: Get API key
245
+ // Step 4: Get API key (with retry logic)
243
246
  console.log('\n--- Step 1: Get your API key ---\n');
244
247
  console.log('Opening Vibescope settings page in your browser...');
245
248
  console.log("Create an API key if you don't have one, then copy it.\n");
246
249
  await openBrowser(VIBESCOPE_SETTINGS_URL);
247
- const apiKey = await prompt('Paste your Vibescope API key: ');
248
- if (!apiKey) {
249
- console.error('\nError: API key is required.');
250
- process.exit(1);
251
- }
252
- // Validate API key
253
- console.log('\nValidating API key...');
254
- const validation = await validateApiKey(apiKey);
255
- console.log(validation.message);
256
- if (!validation.valid) {
257
- const proceed = await prompt('Proceed anyway? (y/N): ');
258
- if (proceed.toLowerCase() !== 'y') {
250
+ const MAX_ATTEMPTS = 3;
251
+ let apiKey = '';
252
+ let attempts = 0;
253
+ while (attempts < MAX_ATTEMPTS) {
254
+ attempts++;
255
+ const attemptsRemaining = MAX_ATTEMPTS - attempts;
256
+ apiKey = await prompt('Paste your Vibescope API key: ');
257
+ if (!apiKey) {
258
+ if (attemptsRemaining > 0) {
259
+ console.log('\nAPI key is required. Please try again. (' + attemptsRemaining + ' attempt' + (attemptsRemaining === 1 ? '' : 's') + ' remaining)\n');
260
+ continue;
261
+ }
262
+ else {
263
+ console.error('\nError: API key is required. Setup cancelled.');
264
+ process.exit(1);
265
+ }
266
+ }
267
+ // Validate API key
268
+ console.log('\nValidating API key...');
269
+ const validation = await validateApiKey(apiKey);
270
+ console.log(validation.message);
271
+ if (validation.valid) {
272
+ break; // Success - exit the retry loop
273
+ }
274
+ // Invalid key
275
+ if (attemptsRemaining > 0) {
276
+ console.log('\nPlease check your API key and try again. (' + attemptsRemaining + ' attempt' + (attemptsRemaining === 1 ? '' : 's') + ' remaining)\n');
277
+ }
278
+ else {
279
+ console.error('\nMaximum attempts reached. Please verify your API key at:');
280
+ console.error(' ' + VIBESCOPE_SETTINGS_URL);
281
+ console.error('\nSetup cancelled.');
259
282
  process.exit(1);
260
283
  }
261
284
  }
@@ -302,7 +325,14 @@ export async function runSetup() {
302
325
  console.log('Next steps:');
303
326
  console.log(' 1. Restart Gemini CLI');
304
327
  console.log(' 2. Verify connection: gemini mcp list');
305
- console.log(' 3. Start using Vibescope in your conversation');
328
+ console.log(' 3. In your conversation, call the MCP tool: start_work_session(git_url: "...", agent_type: "gemini")');
329
+ console.log('');
330
+ console.log('IMPORTANT: Vibescope tools are MCP tool calls, NOT CLI commands.');
331
+ console.log('Do NOT run "vibescope-cli start_work_session" - that command does not exist.');
332
+ console.log('');
333
+ console.log('NOTE: If you manually edit the config later, do NOT use $VAR_NAME syntax');
334
+ console.log('for environment variables - Gemini CLI has a bug where this does not work.');
335
+ console.log('Always hardcode API keys directly or use ~/.gemini/.env instead.');
306
336
  break;
307
337
  default:
308
338
  console.log('Next steps:');
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Fallback Help Content
3
+ *
4
+ * This provides comprehensive help content for each topic when the backend
5
+ * database has no content populated. Ensures agents always get useful guidance.
6
+ */
7
+ export declare const HELP_TOPICS: Record<string, {
8
+ title: string;
9
+ content: string;
10
+ }>;
11
+ /**
12
+ * Get fallback help content for a topic
13
+ * @param topic The help topic slug
14
+ * @returns The help content or null if topic not found
15
+ */
16
+ export declare function getFallbackHelpContent(topic: string): {
17
+ title: string;
18
+ content: string;
19
+ } | null;
20
+ /**
21
+ * Get list of available help topics
22
+ * @returns Array of topic slugs
23
+ */
24
+ export declare function getAvailableHelpTopics(): string[];