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
13 changes: 9 additions & 4 deletions mcp-worker/src/apiClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UserProps, DevCycleJWTClaims } from './types'
import { IDevCycleApiClient } from '../../src/mcp/api/interface'
import { getErrorMessage, ensureError } from '../../src/mcp/utils/api'
import { setDVCReferrer } from '../../src/api/apiClient'
import { setMCPHeaders, setMCPToolCommand } from '../../src/mcp/utils/headers'

/**
* Interface for state management - allows McpAgent or other state managers
Expand All @@ -20,9 +20,15 @@ export class WorkerApiClient implements IDevCycleApiClient {
private props: UserProps,
private env: Env,
private stateManager?: IStateManager,
private version: string = 'unknown',
) {}

/**
* Initialize MCP headers with version - should be called once at server startup
*/
static initializeMCPHeaders(version: string): void {
setMCPHeaders(version)
}

/**
* Execute an API operation with OAuth token authentication and consistent logging
*/
Expand All @@ -45,8 +51,7 @@ export class WorkerApiClient implements IDevCycleApiClient {
}

// Set MCP analytics headers for this specific tool operation
// Caller is 'mcp' to distinguish from CLI and other callers; command is the tool name
setDVCReferrer(operationName, this.version, 'mcp')
setMCPToolCommand(operationName)

console.log(`Worker MCP ${operationName}:`, {
args,
Expand Down
52 changes: 40 additions & 12 deletions mcp-worker/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import OAuthProvider from '@cloudflare/workers-oauth-provider'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'
import type { ZodRawShape } from 'zod'
import workerVersion from './version'
import { McpAgent } from 'agents/mcp'
import { createAuthApp, createTokenExchangeCallback } from './auth'
Expand All @@ -9,7 +11,7 @@ import { WorkerApiClient } from './apiClient'
import { registerAllToolsWithServer } from '../../src/mcp/tools/index'

// Import types
import { DevCycleMCPServerInstance } from '../../src/mcp/server'
import type { DevCycleMCPServerInstance } from '../../src/mcp/server'
import { handleToolError } from '../../src/mcp/utils/errorHandling'

import { registerProjectSelectionTools } from './projectSelectionTools'
Expand Down Expand Up @@ -48,12 +50,14 @@ export class DevCycleMCP extends McpAgent<Env, DevCycleMCPState, UserProps> {
* Initialize the MCP server with tools and handlers
*/
async init() {
// Initialize MCP headers with version once at startup
WorkerApiClient.initializeMCPHeaders(this.version)

// Initialize the Worker-specific API client with OAuth tokens and state management
this.apiClient = new WorkerApiClient(
this.props,
this.env,
this, // Pass the McpAgent instance for state management
this.version,
)

console.log('Initializing DevCycle MCP Worker', {
Expand All @@ -62,26 +66,50 @@ export class DevCycleMCP extends McpAgent<Env, DevCycleMCPState, UserProps> {
hasProject: await this.apiClient.hasProjectKey(),
})

// Check environment variable for output schema support
const enableOutputSchemas =
(this.env.ENABLE_OUTPUT_SCHEMAS as string) === 'true'
if (enableOutputSchemas) {
console.log('DevCycle MCP Worker - Output Schemas: ENABLED')
}

// Create an adapter to make the worker's McpServer compatible with the CLI registration pattern
const serverAdapter: DevCycleMCPServerInstance = {
registerToolWithErrorHandling: (
name: string,
config: {
description: string
annotations?: any
inputSchema?: any
outputSchema?: any
annotations?: ToolAnnotations
inputSchema?: ZodRawShape
outputSchema?: ZodRawShape
},
handler: (args: any) => Promise<any>,
handler: (args: unknown) => Promise<unknown>,
) => {
// Conditionally include output schema based on environment variable
const toolConfig: {
description: string
annotations?: ToolAnnotations
inputSchema?: ZodRawShape
outputSchema?: ZodRawShape
} = {
description: config.description,
annotations: config.annotations,
inputSchema: config.inputSchema,
}

if (enableOutputSchemas && config.outputSchema) {
toolConfig.outputSchema = config.outputSchema
}

this.server.registerTool(
name,
{
description: config.description,
annotations: config.annotations,
inputSchema: config.inputSchema || {},
},
async (args: any) => {
// TypeScript workaround: The MCP SDK's registerTool has complex generic constraints
// that cause "Type instantiation is excessively deep" errors when used with our
// adapter pattern. The types are correct at runtime, but TS can't verify them.
toolConfig as Parameters<
typeof this.server.registerTool
>[1],
async (args: unknown) => {
try {
const result = await handler(args)
return {
Expand Down
1 change: 1 addition & 0 deletions mcp-worker/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"compilerOptions": {
"outDir": "./dist",
Copy link

Copilot AI Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding noEmit: true to a TypeScript configuration that has an outDir specified is contradictory. The noEmit flag prevents TypeScript from generating output files, making the outDir setting meaningless. Consider removing either noEmit: true or the outDir setting based on whether you want TypeScript to emit files or just perform type checking.

Suggested change
"outDir": "./dist",

Copilot uses AI. Check for mistakes.
"rootDir": "../",
"noEmit": true,
"types": [
"node"
],
Expand Down
Loading
Loading