From 79688fccf6a2e9b71f65a541eda5e73f1b860597 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Tue, 15 Jul 2025 13:44:36 -0400 Subject: [PATCH 1/6] feat: improve MCP server installation --- README.md | 71 +++++++++++++++++++++++++++++-- bin/mcp | 6 +++ bin/mcp.cmd | 3 ++ oclif.manifest.json | 2 +- package.json | 2 +- src/mcp/index.ts | 98 ++++++++++++++++++++++++++++++++++++++++++- src/mcp/server.ts | 83 ++++++++++++++++++++++++++++++++++-- src/mcp/utils/auth.ts | 73 +++++++++++++++++++++++++++----- 8 files changed, 317 insertions(+), 21 deletions(-) create mode 100755 bin/mcp create mode 100644 bin/mcp.cmd diff --git a/README.md b/README.md index f38903d43..794b8cf35 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ The CLI can be customized in several ways using command-line args or by creating * [Usage](#usage) * [Command Topics](#command-topics) * [MCP Server for AI Assistants](#mcp-server-for-ai-assistants) +* [This installs both 'dvc' CLI and 'dvc-mcp' server](#this-installs-both-dvc-cli-and-dvc-mcp-server) +* [Access via: npx dvc-mcp](#access-via-npx-dvc-mcp) * [Repo Configuration](#repo-configuration) # Setup @@ -149,7 +151,27 @@ USAGE The DevCycle CLI includes an MCP (Model Context Protocol) server that enables AI coding assistants like Cursor and Claude to manage feature flags directly. This allows you to create, update, and manage feature flags without leaving your coding environment. -## Quick Setup +## Installation + +### Option 1: Global Installation (Recommended) +```bash +npm install -g @devcycle/cli +# This installs both 'dvc' CLI and 'dvc-mcp' server +``` + +### Option 2: Project-Specific Installation +```bash +npm install --save-dev @devcycle/cli +# Access via: npx dvc-mcp +``` + +### Verify Installation +```bash +dvc-mcp --version # Should display the DevCycle CLI version +dvc --version # Verify CLI is also installed +``` + +## Configuration ### For Cursor Add to `.cursor/mcp_settings.json`: @@ -165,6 +187,10 @@ Add to `.cursor/mcp_settings.json`: ### For Claude Desktop Add to your Claude configuration file: + +**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` +**Windows:** `%APPDATA%\Claude\claude_desktop_config.json` + ```json { "mcpServers": { @@ -175,9 +201,48 @@ Add to your Claude configuration file: } ``` -The MCP server uses the same authentication as the CLI. Simply run `dvc login sso` first, then your AI assistant can manage feature flags on your behalf. +### For Project-Specific Installation +If you installed locally, update the command path: +```json +{ + "mcpServers": { + "devcycle": { + "command": "npx", + "args": ["dvc-mcp"] + } + } +} +``` + +## Authentication + +The MCP server uses the same authentication as the CLI: + +1. **Authenticate with DevCycle:** + ```bash + dvc login sso + ``` + +2. **Select your project:** + ```bash + dvc projects select + ``` + +3. **Verify setup:** + ```bash + dvc status + ``` + +Your AI assistant can now manage feature flags on your behalf. + +## Troubleshooting + +- **Command not found:** Ensure the CLI is installed globally or use `npx dvc-mcp` +- **Authentication errors:** Run `dvc login sso` to re-authenticate +- **No project selected:** Run `dvc projects select` to choose a project +- **Permission issues:** On Unix systems, you may need to restart your terminal after global installation -For detailed documentation, see [docs/mcp.md](docs/mcp.md). +For detailed documentation and advanced usage, see [docs/mcp.md](docs/mcp.md). # Repo Configuration The following commands can only be run from the root of a configured repository diff --git a/bin/mcp b/bin/mcp new file mode 100755 index 000000000..08faa650b --- /dev/null +++ b/bin/mcp @@ -0,0 +1,6 @@ +#!/usr/bin/env node + +const path = require('path') + +// Run the MCP server directly +require(path.join(__dirname, '..', 'dist', 'mcp', 'index.js')) \ No newline at end of file diff --git a/bin/mcp.cmd b/bin/mcp.cmd new file mode 100644 index 000000000..3803e0eeb --- /dev/null +++ b/bin/mcp.cmd @@ -0,0 +1,3 @@ +@echo off + +node "%~dp0\mcp" %* \ No newline at end of file diff --git a/oclif.manifest.json b/oclif.manifest.json index ee18e43e6..790e81773 100644 --- a/oclif.manifest.json +++ b/oclif.manifest.json @@ -1,5 +1,5 @@ { - "version": "5.21.0", + "version": "5.21.1", "commands": { "authCommand": { "id": "authCommand", diff --git a/package.json b/package.json index d33244942..b937944df 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "support@devcycle.com", "bin": { "dvc": "./bin/run", - "dvc-mcp": "./dist/mcp/index.js" + "dvc-mcp": "./bin/mcp" }, "homepage": "https://github.com/DevCycleHQ/cli", "license": "MIT", diff --git a/src/mcp/index.ts b/src/mcp/index.ts index 55671d126..0470ec99d 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -3,12 +3,60 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { DevCycleMCPServer } from './server' +import { readFileSync } from 'fs' +import { join } from 'path' + +// Handle command line arguments +const args = process.argv.slice(2) +if (args.includes('--version') || args.includes('-v')) { + try { + const packagePath = join(__dirname, '..', '..', 'package.json') + const packageJson = JSON.parse(readFileSync(packagePath, 'utf8')) + console.log(packageJson.version) + process.exit(0) + } catch (error) { + console.error('Unable to read version') + process.exit(1) + } +} + +if (args.includes('--help') || args.includes('-h')) { + console.log('DevCycle MCP Server') + console.log('') + console.log( + 'A Model Context Protocol server for DevCycle feature flag management.', + ) + console.log( + 'Designed to be used with AI coding assistants like Cursor and Claude.', + ) + console.log('') + console.log('Usage:') + console.log(' dvc-mcp Start the MCP server') + console.log(' dvc-mcp --version Show version information') + console.log(' dvc-mcp --help Show this help message') + console.log('') + console.log( + 'For setup instructions, see: https://github.com/DevCycleHQ/cli#mcp-server-for-ai-assistants', + ) + process.exit(0) +} + +// Get version for MCP server +function getVersion(): string { + try { + const packagePath = join(__dirname, '..', '..', 'package.json') + const packageJson = JSON.parse(readFileSync(packagePath, 'utf8')) + return packageJson.version + } catch (error) { + return '0.0.1' + } +} async function main() { const server = new Server( { name: 'devcycle', - version: '0.0.1', + version: getVersion(), }, { capabilities: { @@ -27,6 +75,52 @@ async function main() { } main().catch((error) => { - console.error('Failed to start DevCycle MCP server:', error) + console.error('❌ Failed to start DevCycle MCP server') + console.error('') + + if (error instanceof Error) { + // Check for common error patterns and provide helpful guidance + if ( + error.message.includes('authentication') || + error.message.includes('DEVCYCLE_CLIENT_ID') + ) { + console.error('🔐 Authentication Error:') + console.error(` ${error.message}`) + console.error('') + console.error('💡 To fix this:') + console.error(' 1. Run: dvc login sso') + console.error(' 2. Or set environment variables:') + console.error(' export DEVCYCLE_CLIENT_ID="your-client-id"') + console.error( + ' export DEVCYCLE_CLIENT_SECRET="your-client-secret"', + ) + } else if ( + error.message.includes('project') || + error.message.includes('DEVCYCLE_PROJECT_KEY') + ) { + console.error('📁 Project Configuration Error:') + console.error(` ${error.message}`) + console.error('') + console.error('💡 To fix this:') + console.error(' 1. Run: dvc projects select') + console.error(' 2. Or set environment variable:') + console.error( + ' export DEVCYCLE_PROJECT_KEY="your-project-key"', + ) + } else { + console.error('⚠️ Unexpected Error:') + console.error(` ${error.message}`) + console.error('') + console.error('💡 For help:') + console.error(' - Run: dvc status') + console.error(' - Check: https://docs.devcycle.com') + console.error(' - Contact: support@devcycle.com') + } + } else { + console.error('⚠️ Unknown error occurred') + console.error(` ${error}`) + } + + console.error('') process.exit(1) }) diff --git a/src/mcp/server.ts b/src/mcp/server.ts index cbbd57113..8b5217961 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -112,21 +112,98 @@ export class DevCycleMCPServer { } catch (error) { console.error(`Error in tool handler ${name}:`, error) - // Safely extract error message, handling undefined/null cases + // Enhanced error categorization and messages let errorMessage = 'Unknown error' - if (error instanceof Error && error.message) { + let errorType = 'UNKNOWN_ERROR' + let suggestions: string[] = [] + + if (error instanceof Error) { errorMessage = error.message + + // Categorize common error types + if ( + error.message.includes('401') || + error.message.includes('Unauthorized') + ) { + errorType = 'AUTHENTICATION_ERROR' + suggestions = [ + 'Run "dvc login sso" to re-authenticate the devcycle cli', + 'Verify your API credentials are correct', + 'Check if your token has expired', + ] + } else if ( + error.message.includes('403') || + error.message.includes('Forbidden') + ) { + errorType = 'PERMISSION_ERROR' + suggestions = [ + 'Verify your account has permissions for this operation', + 'Check if you have access to the selected project', + 'Contact your DevCycle admin for permissions', + ] + } else if ( + error.message.includes('404') || + error.message.includes('Not Found') + ) { + errorType = 'RESOURCE_NOT_FOUND' + suggestions = [ + 'Verify the resource key/ID is correct', + 'Check if the resource exists in the selected project', + "Ensure you're in the correct environment", + ] + } else if ( + error.message.includes('400') || + error.message.includes('Bad Request') + ) { + errorType = 'VALIDATION_ERROR' + suggestions = [ + 'Check the provided parameters are valid', + 'Verify required fields are not missing', + 'Review parameter format and constraints', + ] + } else if ( + error.message.includes('429') || + error.message.includes('rate limit') + ) { + errorType = 'RATE_LIMIT_ERROR' + suggestions = [ + 'Wait a moment before trying again', + 'Consider reducing the frequency of requests', + ] + } else if ( + error.message.includes('ENOTFOUND') || + error.message.includes('network') + ) { + errorType = 'NETWORK_ERROR' + suggestions = [ + 'Check your internet connection', + 'Verify firewall settings allow DevCycle API access', + 'Try again in a few moments', + ] + } else if ( + error.message.includes('project') && + error.message.includes('not found') + ) { + errorType = 'PROJECT_ERROR' + suggestions = [ + 'Run "dvc projects select" to choose a valid project', + 'Verify the project key is correct', + 'Check if you have access to this project', + ] + } } else if (error && typeof error === 'string') { errorMessage = error } else if (error && typeof error === 'object') { errorMessage = JSON.stringify(error) } - // Return error as JSON to maintain consistent response format + // Return enhanced error response const errorResponse = { error: true, + type: errorType, message: errorMessage, tool: name, + suggestions, timestamp: new Date().toISOString(), } diff --git a/src/mcp/utils/auth.ts b/src/mcp/utils/auth.ts index e325dde48..1a57fd81f 100644 --- a/src/mcp/utils/auth.ts +++ b/src/mcp/utils/auth.ts @@ -44,25 +44,69 @@ export class DevCycleAuth { this._authToken = await this.apiAuth.getToken(flags, this._orgId) if (!this._authToken) { - throw new Error( - 'No authentication found. Please set DEVCYCLE_CLIENT_ID and DEVCYCLE_CLIENT_SECRET environment variables, ' + - 'or run "dvc login sso" in the CLI first.', - ) + const hasEnvVars = + process.env.DEVCYCLE_CLIENT_ID && + process.env.DEVCYCLE_CLIENT_SECRET + + if (hasEnvVars) { + throw new Error( + 'Authentication failed with provided environment variables. ' + + 'Please verify your DEVCYCLE_CLIENT_ID and DEVCYCLE_CLIENT_SECRET are correct, ' + + 'or run "dvc login sso" to authenticate with SSO.', + ) + } else { + throw new Error( + 'No authentication found. Please either:\n' + + ' 1. Run "dvc login sso" in the CLI to authenticate with SSO\n' + + ' 2. Or set environment variables:\n' + + ' - DEVCYCLE_CLIENT_ID="your-client-id"\n' + + ' - DEVCYCLE_CLIENT_SECRET="your-client-secret"', + ) + } } if (!this._projectKey) { - throw new Error( - 'No project configured. Please set DEVCYCLE_PROJECT_KEY environment variable, ' + - 'or configure a project using "dvc projects select" in the CLI.', - ) + const hasProjectEnv = process.env.DEVCYCLE_PROJECT_KEY + + if (hasProjectEnv) { + throw new Error( + `Invalid project key "${this._projectKey}" in environment variable. ` + + 'Please verify DEVCYCLE_PROJECT_KEY is correct, or run "dvc projects select" to configure a project.', + ) + } else { + throw new Error( + 'No project configured. Please either:\n' + + ' 1. Run "dvc projects select" in the CLI to choose a project\n' + + ' 2. Or set environment variable: DEVCYCLE_PROJECT_KEY="your-project-key"\n' + + ' 3. Or add project to .devcycle/config.yml in your repository', + ) + } } } catch (error) { console.error( 'Failed to initialize DevCycle authentication:', error, ) + + // Preserve the original error message if it's already detailed + if ( + error instanceof Error && + (error.message.includes('authentication') || + error.message.includes('project') || + error.message.includes('DEVCYCLE_')) + ) { + throw error // Re-throw the original detailed error + } + + // For other errors, wrap with context + const errorMessage = + error instanceof Error ? error.message : 'Unknown error' throw new Error( - `Failed to initialize DevCycle authentication: ${error instanceof Error ? error.message : 'Unknown error'}`, + `Failed to initialize DevCycle authentication: ${errorMessage}\n\n` + + 'Common solutions:\n' + + ' 1. Run "dvc status" to check your configuration\n' + + ' 2. Run "dvc login sso" to authenticate\n' + + ' 3. Run "dvc projects select" to choose a project', ) } } @@ -142,7 +186,11 @@ export class DevCycleAuth { requireAuth(): void { if (!this.hasToken()) { throw new Error( - 'Authentication required. Please configure DevCycle credentials.', + 'Authentication required. Please either:\n' + + ' 1. Run "dvc login sso" to authenticate with SSO\n' + + ' 2. Or set environment variables:\n' + + ' - DEVCYCLE_CLIENT_ID="your-client-id"\n' + + ' - DEVCYCLE_CLIENT_SECRET="your-client-secret"', ) } } @@ -150,7 +198,10 @@ export class DevCycleAuth { requireProject(): void { if (!this._projectKey) { throw new Error( - 'Project key required. Please configure a DevCycle project.', + 'Project configuration required. Please either:\n' + + ' 1. Run "dvc projects select" to choose a project\n' + + ' 2. Or set environment variable: DEVCYCLE_PROJECT_KEY="your-project-key"\n' + + ' 3. Or add project to .devcycle/config.yml in your repository', ) } } From 69fa14aa5b1387eef0b43bebca8fd14ba27e00e4 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Tue, 15 Jul 2025 13:46:17 -0400 Subject: [PATCH 2/6] fix: update yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 79e1e073b..7f09b7100 100644 --- a/yarn.lock +++ b/yarn.lock @@ -642,7 +642,7 @@ __metadata: zod: "npm:^3.24.2" bin: dvc: ./bin/run - dvc-mcp: ./dist/mcp/index.js + dvc-mcp: ./bin/mcp languageName: unknown linkType: soft From a9e4e6401dc6bb4dbde7fadfc753b3093d41f40a Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Tue, 15 Jul 2025 16:24:05 -0400 Subject: [PATCH 3/6] fix: error handling --- src/api/zodClient.ts | 6 +++--- src/mcp/server.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/api/zodClient.ts b/src/api/zodClient.ts index 2711cded4..d814b843d 100644 --- a/src/api/zodClient.ts +++ b/src/api/zodClient.ts @@ -314,12 +314,12 @@ const UpdateAudienceDto = z }) .partial() const VariableValidationEntity = z.object({ - schemaType: z.object({}).partial(), - enumValues: z.object({}).partial().optional(), + schemaType: z.string(), + enumValues: z.array(z.string()).optional(), regexPattern: z.string().optional(), jsonSchema: z.string().optional(), description: z.string(), - exampleValue: z.object({}).partial(), + exampleValue: z.any(), }) const CreateVariableDto = z.object({ name: z.string().max(100).optional(), diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 8b5217961..20249b750 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -121,7 +121,21 @@ export class DevCycleMCPServer { errorMessage = error.message // Categorize common error types + // Check for Zodios schema validation errors first if ( + error.message.includes( + 'Zodios: Invalid response', + ) || + error.message.includes('invalid_type') || + error.message.includes('Expected object, received') + ) { + errorType = 'SCHEMA_VALIDATION_ERROR' + suggestions = [ + 'The API response format has changed or is unexpected', + 'This may be a temporary API issue - try again in a moment', + 'Contact DevCycle support if the issue persists', + ] + } else if ( error.message.includes('401') || error.message.includes('Unauthorized') ) { From 024eb743845fb2103bcdf759095e4939dc47a58c Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Tue, 15 Jul 2025 16:27:14 -0400 Subject: [PATCH 4/6] fix: switch to path.resolve() --- bin/mcp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/mcp b/bin/mcp index 08faa650b..978b9f426 100755 --- a/bin/mcp +++ b/bin/mcp @@ -3,4 +3,4 @@ const path = require('path') // Run the MCP server directly -require(path.join(__dirname, '..', 'dist', 'mcp', 'index.js')) \ No newline at end of file +require(path.resolve(__dirname, '..', 'dist', 'mcp', 'index.js')) \ No newline at end of file From 53f340d146e91d7612d6fc32bbd7b3fd84c4d832 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Tue, 15 Jul 2025 16:30:55 -0400 Subject: [PATCH 5/6] chore: refactor and cleanup error handling code --- src/mcp/server.ts | 260 +++++++++++++++++++++++++--------------------- 1 file changed, 141 insertions(+), 119 deletions(-) diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 20249b750..9f39a81ad 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -84,6 +84,146 @@ export class DevCycleMCPServer { } } + private handleToolError(error: unknown, toolName: string) { + console.error(`Error in tool handler ${toolName}:`, error) + + let errorMessage = 'Unknown error' + let errorType = 'UNKNOWN_ERROR' + let suggestions: string[] = [] + + if (error instanceof Error) { + errorMessage = error.message + errorType = this.categorizeError(error.message) + suggestions = this.getErrorSuggestions(errorType) + } else if (error && typeof error === 'string') { + errorMessage = error + } else if (error && typeof error === 'object') { + errorMessage = JSON.stringify(error) + } + + const errorResponse = { + error: true, + type: errorType, + message: errorMessage, + tool: toolName, + suggestions, + timestamp: new Date().toISOString(), + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify(errorResponse, null, 2), + }, + ], + } + } + + private categorizeError(errorMessage: string): string { + const lowerMessage = errorMessage.toLowerCase() + + switch (true) { + case lowerMessage.includes('zodios: invalid response') || + lowerMessage.includes('invalid_type') || + lowerMessage.includes('expected object, received'): + return 'SCHEMA_VALIDATION_ERROR' + + case lowerMessage.includes('401') || + lowerMessage.includes('unauthorized'): + return 'AUTHENTICATION_ERROR' + + case lowerMessage.includes('403') || + lowerMessage.includes('forbidden'): + return 'PERMISSION_ERROR' + + case lowerMessage.includes('404') || + lowerMessage.includes('not found'): + return 'RESOURCE_NOT_FOUND' + + case lowerMessage.includes('400') || + lowerMessage.includes('bad request'): + return 'VALIDATION_ERROR' + + case lowerMessage.includes('429') || + lowerMessage.includes('rate limit'): + return 'RATE_LIMIT_ERROR' + + case lowerMessage.includes('enotfound') || + lowerMessage.includes('network'): + return 'NETWORK_ERROR' + + case lowerMessage.includes('project') && + lowerMessage.includes('not found'): + return 'PROJECT_ERROR' + + default: + return 'UNKNOWN_ERROR' + } + } + + private getErrorSuggestions(errorType: string): string[] { + switch (errorType) { + case 'SCHEMA_VALIDATION_ERROR': + return [ + 'The API response format has changed or is unexpected', + 'This may be a temporary API issue - try again in a moment', + 'Contact DevCycle support if the issue persists', + ] + + case 'AUTHENTICATION_ERROR': + return [ + 'Run "dvc login sso" to re-authenticate the devcycle cli', + 'Verify your API credentials are correct', + 'Check if your token has expired', + ] + + case 'PERMISSION_ERROR': + return [ + 'Verify your account has permissions for this operation', + 'Check if you have access to the selected project', + 'Contact your DevCycle admin for permissions', + ] + + case 'RESOURCE_NOT_FOUND': + return [ + 'Verify the resource key/ID is correct', + 'Check if the resource exists in the selected project', + "Ensure you're in the correct environment", + ] + + case 'VALIDATION_ERROR': + return [ + 'Check the provided parameters are valid', + 'Verify required fields are not missing', + 'Review parameter format and constraints', + ] + + case 'RATE_LIMIT_ERROR': + return [ + 'Wait a moment before trying again', + 'Consider reducing the frequency of requests', + ] + + case 'NETWORK_ERROR': + return [ + 'Check your internet connection', + 'Verify firewall settings allow DevCycle API access', + 'Try again in a few moments', + ] + + case 'PROJECT_ERROR': + return [ + 'Run "dvc projects select" to choose a valid project', + 'Verify the project key is correct', + 'Check if you have access to this project', + ] + + default: + return [] + } + } + private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: allToolDefinitions, @@ -110,125 +250,7 @@ export class DevCycleMCPServer { ], } } catch (error) { - console.error(`Error in tool handler ${name}:`, error) - - // Enhanced error categorization and messages - let errorMessage = 'Unknown error' - let errorType = 'UNKNOWN_ERROR' - let suggestions: string[] = [] - - if (error instanceof Error) { - errorMessage = error.message - - // Categorize common error types - // Check for Zodios schema validation errors first - if ( - error.message.includes( - 'Zodios: Invalid response', - ) || - error.message.includes('invalid_type') || - error.message.includes('Expected object, received') - ) { - errorType = 'SCHEMA_VALIDATION_ERROR' - suggestions = [ - 'The API response format has changed or is unexpected', - 'This may be a temporary API issue - try again in a moment', - 'Contact DevCycle support if the issue persists', - ] - } else if ( - error.message.includes('401') || - error.message.includes('Unauthorized') - ) { - errorType = 'AUTHENTICATION_ERROR' - suggestions = [ - 'Run "dvc login sso" to re-authenticate the devcycle cli', - 'Verify your API credentials are correct', - 'Check if your token has expired', - ] - } else if ( - error.message.includes('403') || - error.message.includes('Forbidden') - ) { - errorType = 'PERMISSION_ERROR' - suggestions = [ - 'Verify your account has permissions for this operation', - 'Check if you have access to the selected project', - 'Contact your DevCycle admin for permissions', - ] - } else if ( - error.message.includes('404') || - error.message.includes('Not Found') - ) { - errorType = 'RESOURCE_NOT_FOUND' - suggestions = [ - 'Verify the resource key/ID is correct', - 'Check if the resource exists in the selected project', - "Ensure you're in the correct environment", - ] - } else if ( - error.message.includes('400') || - error.message.includes('Bad Request') - ) { - errorType = 'VALIDATION_ERROR' - suggestions = [ - 'Check the provided parameters are valid', - 'Verify required fields are not missing', - 'Review parameter format and constraints', - ] - } else if ( - error.message.includes('429') || - error.message.includes('rate limit') - ) { - errorType = 'RATE_LIMIT_ERROR' - suggestions = [ - 'Wait a moment before trying again', - 'Consider reducing the frequency of requests', - ] - } else if ( - error.message.includes('ENOTFOUND') || - error.message.includes('network') - ) { - errorType = 'NETWORK_ERROR' - suggestions = [ - 'Check your internet connection', - 'Verify firewall settings allow DevCycle API access', - 'Try again in a few moments', - ] - } else if ( - error.message.includes('project') && - error.message.includes('not found') - ) { - errorType = 'PROJECT_ERROR' - suggestions = [ - 'Run "dvc projects select" to choose a valid project', - 'Verify the project key is correct', - 'Check if you have access to this project', - ] - } - } else if (error && typeof error === 'string') { - errorMessage = error - } else if (error && typeof error === 'object') { - errorMessage = JSON.stringify(error) - } - - // Return enhanced error response - const errorResponse = { - error: true, - type: errorType, - message: errorMessage, - tool: name, - suggestions, - timestamp: new Date().toISOString(), - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify(errorResponse, null, 2), - }, - ], - } + return this.handleToolError(error, name) } }, ) From cc7e1afde6c8df6f6ec0a01e961ec7b72cbca585 Mon Sep 17 00:00:00 2001 From: Jonathan Norris Date: Tue, 15 Jul 2025 16:34:51 -0400 Subject: [PATCH 6/6] chore: address PR comments --- src/mcp/index.ts | 29 +++++++++++------------------ src/mcp/utils/auth.ts | 2 +- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/mcp/index.ts b/src/mcp/index.ts index 0470ec99d..f21ac9a26 100644 --- a/src/mcp/index.ts +++ b/src/mcp/index.ts @@ -6,20 +6,24 @@ import { DevCycleMCPServer } from './server' import { readFileSync } from 'fs' import { join } from 'path' -// Handle command line arguments -const args = process.argv.slice(2) -if (args.includes('--version') || args.includes('-v')) { +// Get version for MCP server +function getVersion(): string { try { const packagePath = join(__dirname, '..', '..', 'package.json') const packageJson = JSON.parse(readFileSync(packagePath, 'utf8')) - console.log(packageJson.version) - process.exit(0) + return packageJson.version } catch (error) { - console.error('Unable to read version') - process.exit(1) + return 'unknown version' } } +// Handle command line arguments +const args = process.argv.slice(2) +if (args.includes('--version') || args.includes('-v')) { + console.log(getVersion()) + process.exit(0) +} + if (args.includes('--help') || args.includes('-h')) { console.log('DevCycle MCP Server') console.log('') @@ -41,17 +45,6 @@ if (args.includes('--help') || args.includes('-h')) { process.exit(0) } -// Get version for MCP server -function getVersion(): string { - try { - const packagePath = join(__dirname, '..', '..', 'package.json') - const packageJson = JSON.parse(readFileSync(packagePath, 'utf8')) - return packageJson.version - } catch (error) { - return '0.0.1' - } -} - async function main() { const server = new Server( { diff --git a/src/mcp/utils/auth.ts b/src/mcp/utils/auth.ts index 1a57fd81f..bc0a50875 100644 --- a/src/mcp/utils/auth.ts +++ b/src/mcp/utils/auth.ts @@ -70,7 +70,7 @@ export class DevCycleAuth { if (hasProjectEnv) { throw new Error( - `Invalid project key "${this._projectKey}" in environment variable. ` + + `Invalid project key "${hasProjectEnv}" in environment variable. ` + 'Please verify DEVCYCLE_PROJECT_KEY is correct, or run "dvc projects select" to configure a project.', ) } else {