diff --git a/server.json b/server.json index 918cbce5..adaa5797 100644 --- a/server.json +++ b/server.json @@ -1,21 +1,21 @@ { - "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", - "name": "com.devcycle/mcp", - "description": "DevCycle MCP server for feature flag management", - "version": "6.1.1", - "repository": { - "url": "https://github.com/DevCycleHQ/cli", - "source": "github" - }, - "website_url": "https://docs.devcycle.com/cli-mcp/mcp-getting-started", - "remotes": [ - { - "type": "streamable-http", - "url": "https://mcp.devcycle.com/mcp" + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", + "name": "com.devcycle/mcp", + "description": "DevCycle MCP server for feature flag management", + "version": "6.1.1", + "repository": { + "url": "https://github.com/DevCycleHQ/cli", + "source": "github" }, - { - "type": "sse", - "url": "https://mcp.devcycle.com/sse" - } - ] + "website_url": "https://docs.devcycle.com/cli-mcp/mcp-getting-started", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.devcycle.com/mcp" + }, + { + "type": "sse", + "url": "https://mcp.devcycle.com/sse" + } + ] } diff --git a/src/mcp/tools/customPropertiesTools.ts b/src/mcp/tools/customPropertiesTools.ts deleted file mode 100644 index c49fe094..00000000 --- a/src/mcp/tools/customPropertiesTools.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { z } from 'zod' -import { handleZodiosValidationErrors } from '../utils/api' -import { - fetchCustomProperties, - createCustomProperty, - updateCustomProperty, - deleteCustomProperty, -} from '../../api/customProperties' -import { - ListCustomPropertiesArgsSchema, - UpsertCustomPropertyArgsSchema, - UpdateCustomPropertyArgsSchema, - DeleteCustomPropertyArgsSchema, -} from '../types' -import { IDevCycleApiClient } from '../api/interface' -import { DevCycleMCPServerInstance } from '../server' -import { dashboardLinks } from '../utils/dashboardLinks' - -// Individual handler functions -export async function listCustomPropertiesHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - return await apiClient.executeWithDashboardLink( - 'listCustomProperties', - args, - async (authToken: string, projectKey: string | undefined) => { - if (!projectKey) { - throw new Error( - 'Project key is required for listing custom properties. Please select a project first.', - ) - } - return await handleZodiosValidationErrors( - () => fetchCustomProperties(authToken, projectKey), - 'fetchCustomProperties', - ) - }, - dashboardLinks.customProperties.list, - ) -} - -export async function createCustomPropertyHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - return await apiClient.executeWithDashboardLink( - 'createCustomProperty', - args, - async (authToken: string, projectKey: string | undefined) => { - if (!projectKey) { - throw new Error( - 'Project key is required for creating custom properties. Please select a project first.', - ) - } - return await handleZodiosValidationErrors( - () => createCustomProperty(authToken, projectKey, args), - 'createCustomProperty', - ) - }, - dashboardLinks.customProperties.list, - ) -} - -export async function updateCustomPropertyHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - return await apiClient.executeWithDashboardLink( - 'updateCustomProperty', - args, - async (authToken: string, projectKey: string | undefined) => { - if (!projectKey) { - throw new Error( - 'Project key is required for updating custom properties. Please select a project first.', - ) - } - const { key, ...updateData } = args - - return await handleZodiosValidationErrors( - () => - updateCustomProperty( - authToken, - projectKey, - key, - updateData, - ), - 'updateCustomProperty', - ) - }, - dashboardLinks.customProperties.list, - ) -} - -export async function deleteCustomPropertyHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - return await apiClient.executeWithDashboardLink( - 'deleteCustomProperty', - args, - async (authToken: string, projectKey: string | undefined) => { - if (!projectKey) { - throw new Error( - 'Project key is required for deleting custom properties. Please select a project first.', - ) - } - await handleZodiosValidationErrors( - () => deleteCustomProperty(authToken, projectKey, args.key), - 'deleteCustomProperty', - ) - return { - message: `Custom property '${args.key}' deleted successfully`, - } - }, - dashboardLinks.customProperties.list, - ) -} - -/** - * Register custom properties tools with the MCP server using the new direct registration pattern - */ -export function registerCustomPropertiesTools( - serverInstance: DevCycleMCPServerInstance, - apiClient: IDevCycleApiClient, -): void { - serverInstance.registerToolWithErrorHandling( - 'list_custom_properties', - { - description: [ - 'List custom properties in the current project.', - 'Include dashboard link in the response.', - ].join('\n'), - annotations: { - title: 'List Custom Properties', - readOnlyHint: true, - }, - inputSchema: ListCustomPropertiesArgsSchema.shape, - }, - async (args: any) => { - const validatedArgs = ListCustomPropertiesArgsSchema.parse(args) - return await listCustomPropertiesHandler(validatedArgs, apiClient) - }, - ) - - serverInstance.registerToolWithErrorHandling( - 'create_custom_property', - { - description: [ - 'Create a new custom property.', - 'Custom properties are used in feature targeting audiences as custom user-data definitions.', - 'Include dashboard link in the response.', - ].join('\n'), - annotations: { - title: 'Create Custom Property', - }, - inputSchema: UpsertCustomPropertyArgsSchema.shape, - }, - async (args: any) => { - const validatedArgs = UpsertCustomPropertyArgsSchema.parse(args) - return await createCustomPropertyHandler(validatedArgs, apiClient) - }, - ) - - serverInstance.registerToolWithErrorHandling( - 'update_custom_property', - { - description: [ - 'Update an existing custom property.', - '⚠️ IMPORTANT: Custom property changes can affect feature flags in production environments.', - 'Always confirm with the user before updating custom properties for features that are active in production.', - 'Include dashboard link in the response.', - ].join('\n'), - annotations: { - title: 'Update Custom Property', - destructiveHint: true, - }, - inputSchema: UpdateCustomPropertyArgsSchema.shape, - }, - async (args: any) => { - const validatedArgs = UpdateCustomPropertyArgsSchema.parse(args) - return await updateCustomPropertyHandler(validatedArgs, apiClient) - }, - ) - - serverInstance.registerToolWithErrorHandling( - 'delete_custom_property', - { - description: [ - 'Delete a custom property.', - '⚠️ CRITICAL: Deleting a custom property will remove it from ALL environments including production.', - 'ALWAYS confirm with the user before deleting any custom property.', - 'Include dashboard link in the response.', - ].join('\n'), - annotations: { - title: 'Delete Custom Property', - destructiveHint: true, - }, - inputSchema: DeleteCustomPropertyArgsSchema.shape, - }, - async (args: any) => { - const validatedArgs = DeleteCustomPropertyArgsSchema.parse(args) - return await deleteCustomPropertyHandler(validatedArgs, apiClient) - }, - ) -} diff --git a/src/mcp/tools/environmentTools.ts b/src/mcp/tools/environmentTools.ts deleted file mode 100644 index 97c8e63a..00000000 --- a/src/mcp/tools/environmentTools.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { z } from 'zod' -import { handleZodiosValidationErrors } from '../utils/api' -import { - fetchEnvironments, - fetchEnvironmentByKey, - createEnvironment, - updateEnvironment, -} from '../../api/environments' -import { - GetSdkKeysArgsSchema, - ListEnvironmentsArgsSchema, - CreateEnvironmentArgsSchema, - UpdateEnvironmentArgsSchema, -} from '../types' -import { IDevCycleApiClient } from '../api/interface' -import { DevCycleMCPServerInstance } from '../server' -import { dashboardLinks } from '../utils/dashboardLinks' - -// Individual handler functions -export async function listEnvironmentsHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - return await apiClient.executeWithDashboardLink( - 'listEnvironments', - args, - async (authToken: string, projectKey: string | undefined) => { - if (!projectKey) { - throw new Error( - 'Project key is required for listing environments. Please select a project using the select_project tool first.', - ) - } - return await handleZodiosValidationErrors( - () => fetchEnvironments(authToken, projectKey), - 'listEnvironments', - ) - }, - dashboardLinks.environment.settings, - ) -} - -export async function getSdkKeysHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - return await apiClient.executeWithDashboardLink( - 'getSdkKeys', - args, - async (authToken: string, projectKey: string | undefined) => { - if (!projectKey) { - throw new Error( - 'Project key is required for getting SDK keys. Please select a project using the select_project tool first.', - ) - } - const environment = await handleZodiosValidationErrors( - () => - fetchEnvironmentByKey( - authToken, - projectKey, - args.environmentKey, - ), - 'fetchEnvironmentByKey', - ) - - const sdkKeys = environment.sdkKeys - - if (args.keyType) { - return { - [args.keyType]: sdkKeys[args.keyType], - } - } else { - return { - mobile: sdkKeys.mobile, - server: sdkKeys.server, - client: sdkKeys.client, - } - } - }, - dashboardLinks.environment.settings, - ) -} - -export async function createEnvironmentHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - return await apiClient.executeWithDashboardLink( - 'createEnvironment', - args, - async (authToken: string, projectKey: string | undefined) => { - if (!projectKey) { - throw new Error( - 'Project key is required for creating environments. Please select a project using the select_project tool first.', - ) - } - return await handleZodiosValidationErrors( - () => createEnvironment(authToken, projectKey, args), - 'createEnvironment', - ) - }, - dashboardLinks.environment.settings, - ) -} - -export async function updateEnvironmentHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - const { key, ...updateParams } = args - - return await apiClient.executeWithDashboardLink( - 'updateEnvironment', - args, - async (authToken: string, projectKey: string | undefined) => { - if (!projectKey) { - throw new Error( - 'Project key is required for updating environments. Please select a project using the select_project tool first.', - ) - } - return await handleZodiosValidationErrors( - () => - updateEnvironment(authToken, projectKey, key, updateParams), - 'updateEnvironment', - ) - }, - dashboardLinks.environment.settings, - ) -} - -/** - * Register environment tools with the MCP server using the new direct registration pattern - */ -export function registerEnvironmentTools( - _serverInstance: DevCycleMCPServerInstance, - _apiClient: IDevCycleApiClient, -): void { - void _serverInstance - void _apiClient - // DISABLED: list_environments tool (data available from select_project) - // serverInstance.registerToolWithErrorHandling( - // 'list_environments', - // { - // description: [ - // 'List environments in the current project.', - // 'Include dashboard link in the response.', - // ].join('\n'), - // annotations: { - // title: 'List Environments', - // readOnlyHint: true, - // }, - // inputSchema: ListEnvironmentsArgsSchema.shape, - // }, - // async (args: any) => { - // const validatedArgs = ListEnvironmentsArgsSchema.parse(args) - // return await listEnvironmentsHandler(validatedArgs, apiClient) - // }, - // ) - - // DISABLED: get_sdk_keys tool (data available from select_project) - // serverInstance.registerToolWithErrorHandling( - // 'get_sdk_keys', - // { - // description: [ - // 'Get SDK keys for an environment.', - // 'Include dashboard link in the response.', - // ].join('\n'), - // annotations: { - // title: 'Get SDK Keys', - // readOnlyHint: true, - // }, - // inputSchema: GetSdkKeysArgsSchema.shape, - // }, - // async (args: any) => { - // const validatedArgs = GetSdkKeysArgsSchema.parse(args) - // return await getSdkKeysHandler(validatedArgs, apiClient) - // }, - // ) - - // DISABLED: Environment creation/update tools - // serverInstance.registerToolWithErrorHandling( - // 'create_environment', - // { - // description: - // 'Create a new environment. Include dashboard link in the response.', - // annotations: { - // title: 'Create Environment', - // }, - // inputSchema: CreateEnvironmentArgsSchema.shape, - // }, - // async (args: any) => { - // const validatedArgs = CreateEnvironmentArgsSchema.parse(args) - // return await createEnvironmentHandler(validatedArgs, apiClient) - // }, - // ) - - // serverInstance.registerToolWithErrorHandling( - // 'update_environment', - // { - // description: - // 'Update an existing environment. Include dashboard link in the response.', - // annotations: { - // title: 'Update Environment', - // }, - // inputSchema: UpdateEnvironmentArgsSchema.shape, - // }, - // async (args: any) => { - // const validatedArgs = UpdateEnvironmentArgsSchema.parse(args) - // return await updateEnvironmentHandler(validatedArgs, apiClient) - // }, - // ) -} diff --git a/src/mcp/tools/index.ts b/src/mcp/tools/index.ts index 823c617a..c110edd5 100644 --- a/src/mcp/tools/index.ts +++ b/src/mcp/tools/index.ts @@ -4,7 +4,6 @@ import { IDevCycleApiClient } from '../api/interface' import { DevCycleMCPServerInstance } from '../server' import { registerProjectTools } from './projectTools' -import { registerEnvironmentTools } from './environmentTools' import { registerFeatureTools } from './featureTools' import { registerResultsTools } from './resultsTools' import { registerSelfTargetingTools } from './selfTargetingTools' @@ -29,8 +28,6 @@ export function registerAllToolsWithServer( registerInstallTools(serverInstance) registerProjectTools(serverInstance, apiClient) - // registerCustomPropertiesTools(serverInstance, apiClient) // DISABLED: Custom properties tools - registerEnvironmentTools(serverInstance, apiClient) registerFeatureTools(serverInstance, apiClient) registerResultsTools(serverInstance, apiClient) registerSelfTargetingTools(serverInstance, apiClient) diff --git a/src/mcp/tools/projectTools.ts b/src/mcp/tools/projectTools.ts index 764bab4e..0a575a63 100644 --- a/src/mcp/tools/projectTools.ts +++ b/src/mcp/tools/projectTools.ts @@ -1,41 +1,11 @@ -import { z } from 'zod' import { handleZodiosValidationErrors } from '../utils/api' -import { - fetchProjects, - fetchProject, - createProject, - updateProject, -} from '../../api/projects' +import { fetchProject } from '../../api/projects' import { fetchEnvironments } from '../../api/environments' -import { - ListProjectsArgsSchema, - CreateProjectArgsSchema, - UpdateProjectArgsSchema, -} from '../types' import { IDevCycleApiClient } from '../api/interface' import { DevCycleMCPServerInstance } from '../server' import { formatProjectWithEnvironments } from '../utils/projectFormatting' import { dashboardLinks } from '../utils/dashboardLinks' -// Individual handler functions -export async function listProjectsHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - return await apiClient.executeWithDashboardLink( - 'listProjects', - args, - async (authToken: string) => { - return await handleZodiosValidationErrors( - () => fetchProjects(authToken, args), - 'fetchProjects', - ) - }, - dashboardLinks.organization.settings, - false, // Don't require project for listing projects - ) -} - export async function getCurrentProjectHandler(apiClient: IDevCycleApiClient) { return await apiClient.executeWithDashboardLink( 'getCurrentProject', @@ -70,76 +40,16 @@ export async function getCurrentProjectHandler(apiClient: IDevCycleApiClient) { ) } -export async function createProjectHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - return await apiClient.executeWithDashboardLink( - 'createProject', - args, - async (authToken: string) => { - return await handleZodiosValidationErrors( - () => createProject(authToken, args), - 'createProject', - ) - }, - dashboardLinks.project.dashboard, - ) -} - -export async function updateProjectHandler( - args: z.infer, - apiClient: IDevCycleApiClient, -) { - const { key, ...updateParams } = args - - return await apiClient.executeWithDashboardLink( - 'updateProject', - args, - async (authToken: string) => { - return await handleZodiosValidationErrors( - () => updateProject(authToken, key, updateParams), - 'updateProject', - ) - }, - dashboardLinks.project.edit, - ) -} - -/** - * Register project tools with the MCP server using the new direct registration pattern - */ export function registerProjectTools( serverInstance: DevCycleMCPServerInstance, apiClient: IDevCycleApiClient, ): void { - // DISABLED: list_projects tool (data available from select_project) - // serverInstance.registerToolWithErrorHandling( - // 'list_projects', - // { - // description: [ - // 'List all projects in the current organization.', - // 'Can be called before "select_project"', - // 'Include dashboard link in the response.', - // ].join('\n'), - // annotations: { - // title: 'List Projects', - // readOnlyHint: true, - // }, - // inputSchema: ListProjectsArgsSchema.shape, - // }, - // async (args: unknown) => { - // const validatedArgs = ListProjectsArgsSchema.parse(args) - // - // return await listProjectsHandler(validatedArgs, apiClient) - // }, - // ) - serverInstance.registerToolWithErrorHandling( 'get_current_project', { description: [ 'Get the currently selected project.', + 'Only call this tool if you have already selected a project using the select_project tool.', 'Include dashboard link in the response.', 'Returns the current project, its environments, and SDK keys.', ].join('\n'), @@ -153,39 +63,4 @@ export function registerProjectTools( return await getCurrentProjectHandler(apiClient) }, ) - - // DISABLED: Project creation/update tools - // serverInstance.registerToolWithErrorHandling( - // 'create_project', - // { - // description: - // 'Create a new project. Include dashboard link in the response.', - // annotations: { - // title: 'Create Project', - // }, - // inputSchema: CreateProjectArgsSchema.shape, - // }, - // async (args: any) => { - // const validatedArgs = CreateProjectArgsSchema.parse(args) - - // return await createProjectHandler(validatedArgs, apiClient) - // }, - // ) - - // serverInstance.registerToolWithErrorHandling( - // 'update_project', - // { - // description: - // 'Update an existing project. Include dashboard link in the response.', - // annotations: { - // title: 'Update Project', - // }, - // inputSchema: UpdateProjectArgsSchema.shape, - // }, - // async (args: any) => { - // const validatedArgs = UpdateProjectArgsSchema.parse(args) - - // return await updateProjectHandler(validatedArgs, apiClient) - // }, - // ) } diff --git a/src/mcp/tools/selfTargetingTools.ts b/src/mcp/tools/selfTargetingTools.ts index cf69ed08..66aa49a8 100644 --- a/src/mcp/tools/selfTargetingTools.ts +++ b/src/mcp/tools/selfTargetingTools.ts @@ -5,7 +5,6 @@ import { fetchProjectOverridesForUser, updateOverride, deleteFeatureOverrides, - deleteAllProjectOverrides, } from '../../api/overrides' import { UpdateSelfTargetingIdentityArgsSchema, @@ -146,28 +145,6 @@ export async function clearFeatureSelfTargetingOverridesHandler( ) } -export async function clearAllSelfTargetingOverridesHandler( - apiClient: IDevCycleApiClient, -) { - return await apiClient.executeWithDashboardLink( - 'clearAllSelfTargetingOverrides', - null, - async (authToken: string, projectKey: string | undefined) => { - if (!projectKey) { - throw new Error( - 'Project key is required for this operation. Please select a project using the select_project tool first.', - ) - } - await handleZodiosValidationErrors( - () => deleteAllProjectOverrides(authToken, projectKey), - 'deleteAllProjectOverrides', - ) - return { message: 'Cleared all overrides for the project' } - }, - dashboardLinks.organization.profileOverrides, - ) -} - /** * Register self-targeting tools with the MCP server using the new direct registration pattern */ @@ -207,7 +184,7 @@ export function registerSelfTargetingTools( }, inputSchema: UpdateSelfTargetingIdentityArgsSchema.shape, }, - async (args: any) => { + async (args: unknown) => { const validatedArgs = UpdateSelfTargetingIdentityArgsSchema.parse(args) return await updateSelfTargetingIdentityHandler( @@ -249,7 +226,7 @@ export function registerSelfTargetingTools( }, inputSchema: SetSelfTargetingOverrideArgsSchema.shape, }, - async (args: any) => { + async (args: unknown) => { const validatedArgs = SetSelfTargetingOverrideArgsSchema.parse(args) return await setSelfTargetingOverrideHandler( validatedArgs, @@ -272,7 +249,7 @@ export function registerSelfTargetingTools( }, inputSchema: ClearSelfTargetingOverridesArgsSchema.shape, }, - async (args: any) => { + async (args: unknown) => { const validatedArgs = ClearSelfTargetingOverridesArgsSchema.parse(args) return await clearFeatureSelfTargetingOverridesHandler( @@ -281,21 +258,4 @@ export function registerSelfTargetingTools( ) }, ) - - // DISABLED: Clear all self-targeting overrides tool - // serverInstance.registerToolWithErrorHandling( - // 'clear_all_self_targeting_overrides', - // { - // description: - // 'Clear all self-targeting overrides for the current project. ⚠️ IMPORTANT: Always confirm with the user before clearing all overrides as it can clear production environments (environments where type = "production"). Include dashboard link in the response.', - // annotations: { - // title: 'Clear All Self-Targeting Overrides', - // destructiveHint: true, - // }, - // inputSchema: {}, // No parameters needed - // }, - // async () => { - // return await clearAllSelfTargetingOverridesHandler(apiClient) - // }, - // ) }