Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions mcp-worker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ export class DevCycleMCP extends McpAgent<Env, DevCycleMCPState, UserProps> {
},
}

// Register worker-specific project selection tools first so it appears at the front
registerProjectSelectionTools(serverAdapter, this.apiClient)

// Register all CLI tools using the centralized registration function
registerAllToolsWithServer(serverAdapter, this.apiClient)

// Register worker-specific project selection tools using the modern pattern
registerProjectSelectionTools(serverAdapter, this.apiClient)

console.log('✅ DevCycle MCP Worker initialization completed')
}

Expand Down
21 changes: 15 additions & 6 deletions src/mcp/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@ describe('DevCycleMCPServer', () => {
// Check that we have tools from all categories
expect(registeredToolNames).to.include('list_features')
expect(registeredToolNames).to.include('list_variables')
expect(registeredToolNames).to.include('list_environments')
expect(registeredToolNames).to.include('list_projects')
// expect(registeredToolNames).to.include('list_environments')
// expect(registeredToolNames).to.include('list_projects')
// list_environments and list_projects are disabled; using select_project instead
expect(registeredToolNames).to.include('select_project')
expect(registeredToolNames).to.include('get_current_project')
expect(registeredToolNames).to.include(
'get_self_targeting_identity',
Expand All @@ -144,13 +146,20 @@ describe('DevCycleMCPServer', () => {
it('should register tools with input schemas', () => {
const registerToolStub = server.registerTool as sinon.SinonStub

// Old expectation (tool disabled):
// const listProjectsCall = registerToolStub
// .getCalls()
// .find((call) => call.args[0] === 'list_projects')
// expect(listProjectsCall).to.exist
// expect(listProjectsCall!.args[1]).to.have.property('inputSchema')

// Find a tool that should have an input schema
const listProjectsCall = registerToolStub
const listFeaturesCall = registerToolStub
.getCalls()
.find((call) => call.args[0] === 'list_projects')
.find((call) => call.args[0] === 'list_features')

expect(listProjectsCall).to.exist
expect(listProjectsCall!.args[1]).to.have.property('inputSchema')
expect(listFeaturesCall).to.exist
expect(listFeaturesCall!.args[1]).to.have.property('inputSchema')
})
})

Expand Down
80 changes: 42 additions & 38 deletions src/mcp/tools/environmentTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,46 +131,50 @@ export async function updateEnvironmentHandler(
* Register environment tools with the MCP server using the new direct registration pattern
*/
export function registerEnvironmentTools(
serverInstance: DevCycleMCPServerInstance,
apiClient: IDevCycleApiClient,
_serverInstance: DevCycleMCPServerInstance,
_apiClient: IDevCycleApiClient,
): void {
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)
},
)
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)
// },
// )

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: 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(
Expand Down
16 changes: 8 additions & 8 deletions src/mcp/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ export function registerAllToolsWithServer(
serverInstance: DevCycleMCPServerInstance,
apiClient: IDevCycleApiClient,
): void {
// Register local project selection tools first for local MCP
// We detect local MCP by checking if the apiClient is an instance of DevCycleApiClient
if (apiClient instanceof DevCycleApiClient) {
const auth = apiClient.getAuth()
registerLocalProjectTools(serverInstance, apiClient, auth)
}

registerInstallTools(serverInstance)
registerProjectTools(serverInstance, apiClient)
// registerCustomPropertiesTools(serverInstance, apiClient) // DISABLED: Custom properties tools
registerEnvironmentTools(serverInstance, apiClient)
registerFeatureTools(serverInstance, apiClient)
registerResultsTools(serverInstance, apiClient)
registerSelfTargetingTools(serverInstance, apiClient)
registerVariableTools(serverInstance, apiClient)
registerInstallTools(serverInstance)

// Register local project selection tools only for local MCP (not worker)
// We detect local MCP by checking if the apiClient is an instance of DevCycleApiClient
if (apiClient instanceof DevCycleApiClient) {
const auth = apiClient.getAuth()
registerLocalProjectTools(serverInstance, apiClient, auth)
}
}

export type { IDevCycleApiClient } from '../api/interface'
Expand Down
40 changes: 21 additions & 19 deletions src/mcp/tools/projectTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,25 +113,27 @@ export function registerProjectTools(
serverInstance: DevCycleMCPServerInstance,
apiClient: IDevCycleApiClient,
): void {
serverInstance.registerToolWithErrorHandling(
'list_projects',
{
description: [
'List all projects in the current organization.',
'Can be called before "select_project"',
].join('\n'),
annotations: {
title: 'List Projects',
readOnlyHint: true,
},
inputSchema: ListProjectsArgsSchema.shape,
},
async (args: unknown) => {
const validatedArgs = ListProjectsArgsSchema.parse(args)

return await listProjectsHandler(validatedArgs, apiClient)
},
)
// 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',
Expand Down