Skip to content
Draft
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: 5 additions & 1 deletion mcp-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"deploy": "wrangler deploy",
"build": "tsc",
"type-check": "tsc --noEmit",
"cf-typegen": "wrangler types"
"cf-typegen": "wrangler types",
"test": "vitest run --watch=false"
},
"dependencies": {
"@cloudflare/workers-oauth-provider": "^0.0.5",
Expand All @@ -18,6 +19,9 @@
"oauth4webapi": "^3.6.1"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.8.62",
"miniflare": "^3.20241127.1",
"vitest": "^3.2.4",
"wrangler": "^4.28.0"
},
"packageManager": "[email protected]"
Expand Down
187 changes: 187 additions & 0 deletions mcp-worker/src/test-index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/**
* Test-specific worker entry point that mocks MCP SDK
* This avoids the AJV compatibility issues during testing
*/

// Mock MCP Server for testing
class MockMcpServer {
constructor(config: any) {
// Mock MCP Server initialized silently for testing
}

registerTool(name: string, config: any, handler: any) {
// Tool registration handled silently in mock
}
}

// Mock McpAgent for testing
class MockMcpAgent {
server = new MockMcpServer({
name: 'DevCycle MCP Test Server',
version: '1.0.0',
})

static serveSSE(path: string) {
return async (request: Request) => {
return new Response('Mock SSE endpoint', { status: 200 })
}
}

static serve(path: string) {
return async (request: Request) => {
const url = new URL(request.url)

// Mock MCP protocol responses
if (request.method === 'POST' && url.pathname === path) {
// Check for Authorization header in MCP requests
const authHeader = request.headers.get('Authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response(
JSON.stringify({
jsonrpc: '2.0',
id: 1,
error: {
code: 401,
message: 'Unauthorized - Bearer token required',
},
}),
{
status: 401,
headers: { 'Content-Type': 'application/json' },
},
)
}

try {
const raw = await request.json()

const isRecord = (
v: unknown,
): v is Record<string, unknown> =>
v !== null && typeof v === 'object'

const method =
isRecord(raw) && typeof raw.method === 'string'
? raw.method
: undefined
const id =
isRecord(raw) && 'id' in raw
? (raw as Record<string, unknown>).id
: undefined

// Mock tools/list response
if (method === 'tools/list') {
return new Response(
JSON.stringify({
jsonrpc: '2.0',
id: id ?? 1,
result: {
tools: [
{
name: 'mockTool',
description:
'A mock tool for testing',
inputSchema: { type: 'object' },
},
],
},
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
}

// Mock initialize response
if (method === 'initialize') {
return new Response(
JSON.stringify({
jsonrpc: '2.0',
id: id ?? 1,
result: {
protocolVersion: '2024-11-05',
capabilities: { tools: {} },
serverInfo: {
name: 'DevCycle MCP Test Server',
version: '1.0.0',
},
},
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
}

// Mock notifications/initialized (should succeed silently)
if (method === 'notifications/initialized') {
return new Response(
JSON.stringify({
jsonrpc: '2.0',
id: id ?? 1,
result: {},
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
}

// Default response for unknown methods
return new Response(
JSON.stringify({
jsonrpc: '2.0',
id: id ?? 1,
error: {
code: -32601,
message: 'Method not found',
},
}),
{
headers: { 'Content-Type': 'application/json' },
},
)
} catch (error) {
return new Response('Invalid JSON', { status: 400 })
}
}

return new Response('Not found', { status: 404 })
}
}
}

// Create a simple test app that mimics the OAuth provider structure
function createTestApp() {
return {
async fetch(request: Request, env: any, ctx: any): Promise<Response> {
const url = new URL(request.url)

// Handle different endpoints
switch (url.pathname) {
case '/':
return new Response('DevCycle MCP Worker Test', {
status: 200,
})

case '/health':
return new Response('OK', { status: 200 })

case '/oauth/authorize':
return new Response('Mock OAuth authorize', { status: 200 })

case '/sse':
return MockMcpAgent.serveSSE('/sse')(request)

case '/mcp':
return MockMcpAgent.serve('/mcp')(request)

default:
return new Response('Not Found', { status: 404 })
}
},
}
}

// Export the test worker
export default createTestApp()
7 changes: 7 additions & 0 deletions mcp-worker/test-types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Type declarations for Cloudflare Workers test modules
*/

declare module 'cloudflare:test' {
export const SELF: Fetcher
}
Loading