@dxheroes/local-mcp-backend 0.9.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/CHANGELOG.md +52 -0
  3. package/dist/__tests__/integration/mcp-proxy-auth-http.test.js +283 -0
  4. package/dist/__tests__/integration/mcp-proxy-auth-http.test.js.map +1 -0
  5. package/dist/__tests__/integration/oauth-authorize-callback.test.js +122 -0
  6. package/dist/__tests__/integration/oauth-authorize-callback.test.js.map +1 -0
  7. package/dist/__tests__/integration/proxy-auth.test.js +171 -110
  8. package/dist/__tests__/integration/proxy-auth.test.js.map +1 -1
  9. package/dist/__tests__/unit/auth.guard.test.js +23 -2
  10. package/dist/__tests__/unit/auth.guard.test.js.map +1 -1
  11. package/dist/common/filters/all-exceptions.filter.js +6 -0
  12. package/dist/common/filters/all-exceptions.filter.js.map +1 -1
  13. package/dist/main.js +63 -2
  14. package/dist/main.js.map +1 -1
  15. package/dist/modules/auth/auth.config.js +10 -5
  16. package/dist/modules/auth/auth.config.js.map +1 -1
  17. package/dist/modules/auth/auth.module.js +5 -2
  18. package/dist/modules/auth/auth.module.js.map +1 -1
  19. package/dist/modules/auth/auth.service.js +2 -2
  20. package/dist/modules/auth/auth.service.js.map +1 -1
  21. package/dist/modules/auth/mcp-oauth.guard.js +95 -0
  22. package/dist/modules/auth/mcp-oauth.guard.js.map +1 -0
  23. package/dist/modules/auth/mcp-oauth.utils.js +75 -0
  24. package/dist/modules/auth/mcp-oauth.utils.js.map +1 -0
  25. package/dist/modules/health/health.controller.js +1 -1
  26. package/dist/modules/health/health.controller.js.map +1 -1
  27. package/dist/modules/mcp/mcp.service.js +48 -8
  28. package/dist/modules/mcp/mcp.service.js.map +1 -1
  29. package/dist/modules/oauth/oauth.controller.js +78 -1
  30. package/dist/modules/oauth/oauth.controller.js.map +1 -1
  31. package/dist/modules/oauth/oauth.service.js +197 -1
  32. package/dist/modules/oauth/oauth.service.js.map +1 -1
  33. package/dist/modules/proxy/proxy.controller.js +152 -27
  34. package/dist/modules/proxy/proxy.controller.js.map +1 -1
  35. package/dist/modules/proxy/proxy.service.js +28 -4
  36. package/dist/modules/proxy/proxy.service.js.map +1 -1
  37. package/docker-entrypoint.sh +15 -2
  38. package/package.json +9 -7
  39. package/src/__tests__/integration/mcp-proxy-auth-http.test.ts +311 -0
  40. package/src/__tests__/integration/oauth-authorize-callback.test.ts +155 -0
  41. package/src/__tests__/integration/proxy-auth.test.ts +151 -168
  42. package/src/__tests__/unit/auth.guard.test.ts +12 -2
  43. package/src/common/filters/all-exceptions.filter.ts +11 -0
  44. package/src/main.ts +56 -2
  45. package/src/modules/auth/auth.config.ts +9 -4
  46. package/src/modules/auth/auth.module.ts +3 -2
  47. package/src/modules/auth/auth.service.ts +2 -2
  48. package/src/modules/auth/mcp-oauth.guard.ts +102 -0
  49. package/src/modules/auth/mcp-oauth.utils.ts +80 -0
  50. package/src/modules/health/health.controller.ts +1 -1
  51. package/src/modules/mcp/mcp.service.ts +54 -12
  52. package/src/modules/oauth/oauth.controller.ts +84 -1
  53. package/src/modules/oauth/oauth.service.ts +218 -1
  54. package/src/modules/proxy/proxy.controller.ts +120 -25
  55. package/src/modules/proxy/proxy.service.ts +26 -4
  56. package/vitest.config.ts +2 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/modules/proxy/proxy.service.ts"],"sourcesContent":["/**\n * Proxy Service\n *\n * Handles MCP protocol proxying for profiles.\n */\n\nimport type { ApiKeyConfig as CoreApiKeyConfig, McpServer } from '@dxheroes/local-mcp-core';\nimport {\n ExternalMcpServer,\n RemoteHttpMcpServer,\n RemoteSseMcpServer,\n} from '@dxheroes/local-mcp-core';\nimport { Injectable, Logger, NotFoundException } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { DebugService } from '../debug/debug.service.js';\nimport { McpRegistry } from '../mcp/mcp-registry.js';\n\ninterface McpToolCall {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\nexport interface McpRequest {\n jsonrpc: '2.0';\n id: string | number;\n method: string;\n params?: unknown;\n}\n\nexport interface McpResponse {\n jsonrpc: '2.0';\n id: string | number;\n result?: unknown;\n error?: {\n code: number;\n message: string;\n data?: unknown;\n };\n}\n\ninterface ServerConfig {\n builtinId?: string;\n url?: string;\n // External server config\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n workingDirectory?: string;\n autoRestart?: boolean;\n maxRestartAttempts?: number;\n startupTimeout?: number;\n shutdownTimeout?: number;\n}\n\ninterface StoredApiKeyConfig {\n apiKey: string;\n headerName?: string;\n headerValueTemplate?: string;\n}\n\n@Injectable()\nexport class ProxyService {\n private readonly logger = new Logger(ProxyService.name);\n private readonly serverInstances = new Map<string, McpServer>();\n\n constructor(\n private readonly prisma: PrismaService,\n private readonly registry: McpRegistry,\n private readonly debugService: DebugService\n ) {}\n\n /**\n * Handle MCP JSON-RPC request for a profile\n */\n async handleRequest(\n profileName: string,\n request: McpRequest,\n userId?: string\n ): Promise<McpResponse> {\n const requestId = request.id;\n const startTime = Date.now();\n\n // Get profile with servers\n const profile = await this.findProfileByName(profileName, userId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n // Skip logging for initialize (just handshaking)\n const shouldLog = request.method !== 'initialize';\n\n // Create debug log entry\n let logId: string | null = null;\n if (shouldLog) {\n try {\n const log = await this.debugService.createLog({\n profileId: profile.id,\n requestType: request.method,\n requestPayload: JSON.stringify(request),\n status: 'pending',\n });\n logId = log.id;\n } catch (logError) {\n this.logger.warn(`Failed to create debug log: ${logError}`);\n }\n }\n\n try {\n // Handle different MCP methods\n let response: McpResponse;\n\n switch (request.method) {\n case 'initialize':\n response = await this.handleInitialize(requestId, profile);\n break;\n\n case 'tools/list':\n response = await this.handleToolsList(requestId, profile);\n break;\n\n case 'tools/call':\n response = await this.handleToolsCall(\n requestId,\n profile,\n request.params as McpToolCall,\n logId\n );\n break;\n\n case 'resources/list':\n response = await this.handleResourcesList(requestId, profile);\n break;\n\n case 'resources/read':\n response = await this.handleResourcesRead(\n requestId,\n profile,\n request.params as { uri: string }\n );\n break;\n\n default:\n response = {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32601,\n message: `Method not found: ${request.method}`,\n },\n };\n }\n\n // Update debug log with success\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n responsePayload: JSON.stringify(response),\n status: response.error ? 'error' : 'success',\n errorMessage: response.error?.message,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return response;\n } catch (error) {\n this.logger.error(`MCP request error: ${error}`);\n\n const errorMessage = error instanceof Error ? error.message : 'Internal error';\n\n // Update debug log with error\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n status: 'error',\n errorMessage,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32603,\n message: errorMessage,\n },\n };\n }\n }\n\n private async handleInitialize(\n requestId: string | number,\n _profile: { name: string }\n ): Promise<McpResponse> {\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n protocolVersion: '2024-11-05',\n capabilities: {\n tools: {},\n resources: {},\n },\n serverInfo: {\n name: 'Local MCP Gateway',\n version: '0.1.0',\n },\n },\n };\n }\n\n private async handleToolsList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n customDescription: string | null;\n }>;\n }>;\n }\n ): Promise<McpResponse> {\n const allTools: Array<{\n name: string;\n description: string;\n inputSchema: unknown;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Apply tool customizations\n for (const tool of tools) {\n const customization = profileServer.tools.find((t) => t.toolName === tool.name);\n\n if (!customization || customization.isEnabled) {\n allTools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n inputSchema: tool.inputSchema,\n });\n }\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n tools: allTools,\n },\n };\n }\n\n private async handleToolsCall(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n }>;\n }>;\n },\n params: McpToolCall,\n logId: string | null\n ): Promise<McpResponse> {\n // Find which server has this tool\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Check if this server has the requested tool (by name or custom name)\n const customization = profileServer.tools.find(\n (t) => t.customName === params.name || t.toolName === params.name\n );\n\n // Skip if tool is disabled\n if (customization && !customization.isEnabled) {\n continue;\n }\n\n const toolName = customization?.toolName || params.name;\n const hasTool = tools.some((t) => t.name === toolName);\n\n if (hasTool) {\n // Update log with the server that handled this tool call\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n mcpServerId: server.id,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log with server info: ${logError}`);\n }\n }\n\n const result = await instance.callTool(toolName, params.arguments || {});\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result,\n };\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Tool not found: ${params.name}`,\n },\n };\n }\n\n private async handleResourcesList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n }\n ): Promise<McpResponse> {\n const allResources: Array<{\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const resources = await instance.listResources();\n allResources.push(...resources);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n resources: allResources,\n },\n };\n }\n\n private async handleResourcesRead(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n },\n params: { uri: string }\n ): Promise<McpResponse> {\n // Try each server until we find one that has this resource\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n try {\n const content = await instance.readResource(params.uri);\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: content,\n };\n } catch {\n // Resource not found on this server, try next\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Resource not found: ${params.uri}`,\n },\n };\n }\n\n /**\n * Get tools for a specific server by ID\n */\n async getToolsForServer(serverId: string) {\n const server = await this.prisma.mcpServer.findUnique({\n where: { id: serverId },\n });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${serverId} not found`);\n }\n\n const instance = await this.getServerInstance(server);\n if (!instance) {\n return [];\n }\n\n return instance.listTools();\n }\n\n /**\n * Get or create a server instance\n */\n private async getServerInstance(server: {\n id: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n }): Promise<McpServer | null> {\n // Check cache\n const cached = this.serverInstances.get(server.id);\n if (cached) {\n return cached;\n }\n\n // Parse config\n const config = this.parseJson<ServerConfig>(server.config);\n const builtinId = config?.builtinId;\n\n // Get API key config and convert to CoreApiKeyConfig format\n const storedConfig = this.parseJson<StoredApiKeyConfig>(server.apiKeyConfig);\n const apiKeyConfig = this.convertApiKeyConfig(storedConfig);\n\n // For builtin servers, get from registry\n if (builtinId && this.registry.has(builtinId)) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n const instance = pkg.createServer(apiKeyConfig);\n await instance.initialize();\n this.serverInstances.set(server.id, instance);\n return instance;\n }\n }\n\n // For remote_http servers, create RemoteHttpMcpServer\n if (server.type === 'remote_http' && config?.url) {\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For remote_sse servers, create RemoteSseMcpServer\n if (server.type === 'remote_sse' && config?.url) {\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For external servers (NPX/stdio), create ExternalMcpServer\n if (server.type === 'external' && config?.command) {\n const externalServer = new ExternalMcpServer({\n command: config.command,\n args: config.args,\n env: config.env,\n workingDirectory: config.workingDirectory,\n autoRestart: config.autoRestart,\n maxRestartAttempts: config.maxRestartAttempts,\n startupTimeout: config.startupTimeout,\n shutdownTimeout: config.shutdownTimeout,\n });\n await externalServer.initialize();\n this.serverInstances.set(server.id, externalServer);\n return externalServer;\n }\n\n return null;\n }\n\n /**\n * Convert stored API key config to CoreApiKeyConfig format\n */\n private convertApiKeyConfig(stored: StoredApiKeyConfig | null): CoreApiKeyConfig | null {\n if (!stored?.apiKey) return null;\n\n const headerName = stored.headerName || 'Authorization';\n const template = stored.headerValueTemplate || 'Bearer {apiKey}';\n const headerValue = template.replace('{apiKey}', stored.apiKey);\n\n return {\n apiKey: stored.apiKey,\n headerName,\n headerValue,\n };\n }\n\n private parseJson<T>(value: unknown): T | null {\n if (typeof value === 'string') {\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n }\n return value as T | null;\n }\n\n /**\n * Resolve an org slug to an organization ID.\n */\n private async resolveOrgBySlug(orgSlug: string): Promise<string> {\n const org = await this.prisma.organization.findUnique({\n where: { slug: orgSlug },\n select: { id: true },\n });\n if (!org) {\n throw new NotFoundException(`Organization \"${orgSlug}\" not found`);\n }\n return org.id;\n }\n\n /**\n * Find a profile by name within an organization (or system profiles).\n */\n private async findProfileByNameAndOrg(profileName: string, orgId: string) {\n const include = {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' as const },\n },\n };\n\n // Try org-scoped first\n let profile = await this.prisma.profile.findUnique({\n where: { organizationId_name: { organizationId: orgId, name: profileName } },\n include,\n });\n\n // Fallback to system profile (organizationId=null)\n if (!profile) {\n profile = await this.prisma.profile.findFirst({\n where: { name: profileName, organizationId: null },\n include,\n });\n }\n\n return profile;\n }\n\n /**\n * Find a profile by name, scoped to user if userId is provided.\n * Anonymous users see all profiles. Authenticated users see own + system profiles.\n */\n private async findProfileByName(profileName: string, userId?: string) {\n const include = {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' as const },\n },\n };\n\n // Try finding by unique (org, name) — for system profiles use null org\n const profile = await this.prisma.profile.findFirst({\n where: { name: profileName },\n include,\n });\n\n if (!profile) return null;\n\n // If no userId or unauthenticated, allow access to all profiles\n if (!userId || userId === '__unauthenticated__') return profile;\n\n // System records — accessible to all\n if (!profile.organizationId) return profile;\n\n // Allow access if user is a member of the profile's org\n const membership = await this.prisma.member.findFirst({\n where: { userId, organizationId: profile.organizationId },\n });\n if (membership) return profile;\n\n // Otherwise deny\n return null;\n }\n\n /**\n * Handle MCP request using org slug to resolve the profile.\n */\n async handleRequestByOrgSlug(\n profileName: string,\n orgSlug: string,\n request: McpRequest,\n userId?: string\n ): Promise<McpResponse> {\n const orgId = await this.resolveOrgBySlug(orgSlug);\n const profile = await this.findProfileByNameAndOrg(profileName, orgId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found in organization \"${orgSlug}\"`);\n }\n\n const requestId = request.id;\n const startTime = Date.now();\n\n const shouldLog = request.method !== 'initialize';\n let logId: string | null = null;\n if (shouldLog) {\n try {\n const log = await this.debugService.createLog({\n profileId: profile.id,\n requestType: request.method,\n requestPayload: JSON.stringify(request),\n status: 'pending',\n });\n logId = log.id;\n } catch (logError) {\n this.logger.warn(`Failed to create debug log: ${logError}`);\n }\n }\n\n try {\n let response: McpResponse;\n\n switch (request.method) {\n case 'initialize':\n response = await this.handleInitialize(requestId, profile);\n break;\n case 'tools/list':\n response = await this.handleToolsList(requestId, profile);\n break;\n case 'tools/call':\n response = await this.handleToolsCall(\n requestId,\n profile,\n request.params as McpToolCall,\n logId\n );\n break;\n case 'resources/list':\n response = await this.handleResourcesList(requestId, profile);\n break;\n case 'resources/read':\n response = await this.handleResourcesRead(\n requestId,\n profile,\n request.params as { uri: string }\n );\n break;\n default:\n response = {\n jsonrpc: '2.0',\n id: requestId,\n error: { code: -32601, message: `Method not found: ${request.method}` },\n };\n }\n\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n responsePayload: JSON.stringify(response),\n status: response.error ? 'error' : 'success',\n errorMessage: response.error?.message,\n durationMs: Date.now() - startTime,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return response;\n } catch (error) {\n this.logger.error(`MCP request error: ${error}`);\n const errorMessage = error instanceof Error ? error.message : 'Internal error';\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n status: 'error',\n errorMessage,\n durationMs: Date.now() - startTime,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: { code: -32603, message: errorMessage },\n };\n }\n }\n\n /**\n * Get profile info using org slug.\n */\n async getProfileInfoByOrgSlug(profileName: string, orgSlug: string) {\n const orgId = await this.resolveOrgBySlug(orgSlug);\n const profile = await this.findProfileByNameAndOrg(profileName, orgId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found in organization \"${orgSlug}\"`);\n }\n\n return this.aggregateProfileInfo(profile);\n }\n\n /**\n * Get profile info with aggregated tools and server status\n */\n async getProfileInfo(profileName: string, userId?: string) {\n const profile = await this.findProfileByName(profileName, userId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n return this.aggregateProfileInfo(profile);\n }\n\n /**\n * Aggregate tools and server status from a profile.\n */\n private async aggregateProfileInfo(profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n customDescription: string | null;\n }>;\n }>;\n }) {\n const tools: Array<{ name: string; description: string }> = [];\n const serverStatus: Record<string, { connected: boolean; toolCount: number }> = {};\n\n for (const ps of profile.mcpServers) {\n const server = ps.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const serverTools = await instance.listTools();\n serverStatus[server.id] = { connected: true, toolCount: serverTools.length };\n\n for (const tool of serverTools) {\n const customization = ps.tools.find((t) => t.toolName === tool.name);\n if (!customization || customization.isEnabled) {\n tools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n });\n }\n }\n } else {\n serverStatus[server.id] = { connected: false, toolCount: 0 };\n }\n }\n\n return {\n tools,\n serverStatus: {\n total: profile.mcpServers.length,\n connected: Object.values(serverStatus).filter((s) => s.connected).length,\n servers: serverStatus,\n },\n };\n }\n}\n"],"names":["ExternalMcpServer","RemoteHttpMcpServer","RemoteSseMcpServer","Injectable","Logger","NotFoundException","PrismaService","DebugService","McpRegistry","ProxyService","prisma","registry","debugService","logger","name","serverInstances","Map","handleRequest","profileName","request","userId","requestId","id","startTime","Date","now","profile","findProfileByName","shouldLog","method","logId","log","createLog","profileId","requestType","requestPayload","JSON","stringify","status","logError","warn","response","handleInitialize","handleToolsList","handleToolsCall","params","handleResourcesList","handleResourcesRead","jsonrpc","error","code","message","durationMs","updateLog","responsePayload","errorMessage","Error","_profile","result","protocolVersion","capabilities","tools","resources","serverInfo","version","allTools","profileServer","mcpServers","server","mcpServer","instance","getServerInstance","listTools","tool","customization","find","t","toolName","isEnabled","push","customName","description","customDescription","inputSchema","hasTool","some","mcpServerId","callTool","arguments","allResources","listResources","content","readResource","uri","getToolsForServer","serverId","findUnique","where","cached","get","config","parseJson","builtinId","storedConfig","apiKeyConfig","convertApiKeyConfig","has","pkg","createServer","initialize","set","type","url","remoteServer","transport","command","externalServer","args","env","workingDirectory","autoRestart","maxRestartAttempts","startupTimeout","shutdownTimeout","stored","apiKey","headerName","template","headerValueTemplate","headerValue","replace","value","parse","resolveOrgBySlug","orgSlug","org","organization","slug","select","findProfileByNameAndOrg","orgId","include","isActive","orderBy","order","organizationId_name","organizationId","findFirst","membership","member","handleRequestByOrgSlug","getProfileInfoByOrgSlug","aggregateProfileInfo","getProfileInfo","serverStatus","ps","serverTools","connected","toolCount","length","total","Object","values","filter","s","servers"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/modules/proxy/proxy.service.ts"],"sourcesContent":["/**\n * Proxy Service\n *\n * Handles MCP protocol proxying for profiles.\n */\n\nimport type { ApiKeyConfig as CoreApiKeyConfig, McpServer } from '@dxheroes/local-mcp-core';\nimport {\n ExternalMcpServer,\n RemoteHttpMcpServer,\n RemoteSseMcpServer,\n} from '@dxheroes/local-mcp-core';\nimport { Injectable, Logger, NotFoundException } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { DebugService } from '../debug/debug.service.js';\nimport { McpRegistry } from '../mcp/mcp-registry.js';\n\ninterface McpToolCall {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\nexport interface McpRequest {\n jsonrpc: '2.0';\n id: string | number;\n method: string;\n params?: unknown;\n}\n\nexport interface McpResponse {\n jsonrpc: '2.0';\n id: string | number;\n result?: unknown;\n error?: {\n code: number;\n message: string;\n data?: unknown;\n };\n}\n\ninterface ServerConfig {\n builtinId?: string;\n url?: string;\n headers?: Record<string, string>;\n // External server config\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n workingDirectory?: string;\n autoRestart?: boolean;\n maxRestartAttempts?: number;\n startupTimeout?: number;\n shutdownTimeout?: number;\n}\n\ninterface StoredApiKeyConfig {\n apiKey: string;\n headerName?: string;\n headerValueTemplate?: string;\n}\n\n@Injectable()\nexport class ProxyService {\n private readonly logger = new Logger(ProxyService.name);\n private readonly serverInstances = new Map<string, McpServer>();\n\n constructor(\n private readonly prisma: PrismaService,\n private readonly registry: McpRegistry,\n private readonly debugService: DebugService\n ) {}\n\n /**\n * Handle MCP JSON-RPC request for a profile\n */\n async handleRequest(\n profileName: string,\n request: McpRequest,\n userId?: string\n ): Promise<McpResponse> {\n const requestId = request.id;\n const startTime = Date.now();\n\n // Get profile with servers\n const profile = await this.findProfileByName(profileName, userId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n // Skip logging for initialize (just handshaking)\n const shouldLog = request.method !== 'initialize';\n\n // Create debug log entry\n let logId: string | null = null;\n if (shouldLog) {\n try {\n const log = await this.debugService.createLog({\n profileId: profile.id,\n requestType: request.method,\n requestPayload: JSON.stringify(request),\n status: 'pending',\n });\n logId = log.id;\n } catch (logError) {\n this.logger.warn(`Failed to create debug log: ${logError}`);\n }\n }\n\n try {\n // Handle different MCP methods\n let response: McpResponse;\n\n switch (request.method) {\n case 'initialize':\n response = await this.handleInitialize(requestId, profile);\n break;\n\n case 'tools/list':\n response = await this.handleToolsList(requestId, profile);\n break;\n\n case 'tools/call':\n response = await this.handleToolsCall(\n requestId,\n profile,\n request.params as McpToolCall,\n logId\n );\n break;\n\n case 'resources/list':\n response = await this.handleResourcesList(requestId, profile);\n break;\n\n case 'resources/read':\n response = await this.handleResourcesRead(\n requestId,\n profile,\n request.params as { uri: string }\n );\n break;\n\n default:\n response = {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32601,\n message: `Method not found: ${request.method}`,\n },\n };\n }\n\n // Update debug log with success\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n responsePayload: JSON.stringify(response),\n status: response.error ? 'error' : 'success',\n errorMessage: response.error?.message,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return response;\n } catch (error) {\n this.logger.error(`MCP request error: ${error}`);\n\n const errorMessage = error instanceof Error ? error.message : 'Internal error';\n\n // Update debug log with error\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n status: 'error',\n errorMessage,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32603,\n message: errorMessage,\n },\n };\n }\n }\n\n private async handleInitialize(\n requestId: string | number,\n _profile: { name: string }\n ): Promise<McpResponse> {\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n protocolVersion: '2024-11-05',\n capabilities: {\n tools: {},\n resources: {},\n },\n serverInfo: {\n name: 'Local MCP Gateway',\n version: '0.1.0',\n },\n },\n };\n }\n\n private async handleToolsList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n customDescription: string | null;\n }>;\n }>;\n }\n ): Promise<McpResponse> {\n const allTools: Array<{\n name: string;\n description: string;\n inputSchema: unknown;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Apply tool customizations\n for (const tool of tools) {\n const customization = profileServer.tools.find((t) => t.toolName === tool.name);\n\n if (!customization || customization.isEnabled) {\n allTools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n inputSchema: tool.inputSchema,\n });\n }\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n tools: allTools,\n },\n };\n }\n\n private async handleToolsCall(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n }>;\n }>;\n },\n params: McpToolCall,\n logId: string | null\n ): Promise<McpResponse> {\n // Find which server has this tool\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Check if this server has the requested tool (by name or custom name)\n const customization = profileServer.tools.find(\n (t) => t.customName === params.name || t.toolName === params.name\n );\n\n // Skip if tool is disabled\n if (customization && !customization.isEnabled) {\n continue;\n }\n\n const toolName = customization?.toolName || params.name;\n const hasTool = tools.some((t) => t.name === toolName);\n\n if (hasTool) {\n // Update log with the server that handled this tool call\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n mcpServerId: server.id,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log with server info: ${logError}`);\n }\n }\n\n const result = await instance.callTool(toolName, params.arguments || {});\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result,\n };\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Tool not found: ${params.name}`,\n },\n };\n }\n\n private async handleResourcesList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n }\n ): Promise<McpResponse> {\n const allResources: Array<{\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const resources = await instance.listResources();\n allResources.push(...resources);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n resources: allResources,\n },\n };\n }\n\n private async handleResourcesRead(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n },\n params: { uri: string }\n ): Promise<McpResponse> {\n // Try each server until we find one that has this resource\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n try {\n const content = await instance.readResource(params.uri);\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: content,\n };\n } catch {\n // Resource not found on this server, try next\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Resource not found: ${params.uri}`,\n },\n };\n }\n\n /**\n * Get tools for a specific server by ID\n */\n async getToolsForServer(serverId: string) {\n const server = await this.prisma.mcpServer.findUnique({\n where: { id: serverId },\n });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${serverId} not found`);\n }\n\n const instance = await this.getServerInstance(server);\n if (!instance) {\n return [];\n }\n\n return instance.listTools();\n }\n\n /**\n * Get or create a server instance\n */\n private async getServerInstance(server: {\n id: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n }): Promise<McpServer | null> {\n // Check cache\n const cached = this.serverInstances.get(server.id);\n if (cached) {\n return cached;\n }\n\n // Parse config\n const config = this.parseJson<ServerConfig>(server.config);\n const builtinId = config?.builtinId;\n\n // Get API key config and convert to CoreApiKeyConfig format\n const storedConfig = this.parseJson<StoredApiKeyConfig>(server.apiKeyConfig);\n const apiKeyConfig = this.convertApiKeyConfig(storedConfig);\n\n // For builtin servers, get from registry\n if (builtinId && this.registry.has(builtinId)) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n const instance = pkg.createServer(apiKeyConfig);\n await instance.initialize();\n this.serverInstances.set(server.id, instance);\n return instance;\n }\n }\n\n // For remote_http and remote_sse servers, look up OAuth token\n let oauthToken = null;\n if (server.type === 'remote_http' || server.type === 'remote_sse') {\n const tokenRecord = await this.prisma.oAuthToken.findUnique({\n where: { mcpServerId: server.id },\n });\n if (tokenRecord) {\n oauthToken = {\n id: tokenRecord.id,\n mcpServerId: tokenRecord.mcpServerId,\n accessToken: tokenRecord.accessToken,\n tokenType: tokenRecord.tokenType,\n refreshToken: tokenRecord.refreshToken ?? undefined,\n scope: tokenRecord.scope ?? undefined,\n expiresAt: tokenRecord.expiresAt?.getTime(),\n createdAt: tokenRecord.createdAt.getTime(),\n updatedAt: tokenRecord.updatedAt.getTime(),\n };\n }\n }\n\n // For remote_http servers, create RemoteHttpMcpServer\n if (server.type === 'remote_http' && config?.url) {\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http', headers: config.headers as Record<string, string> },\n oauthToken,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For remote_sse servers, create RemoteSseMcpServer\n if (server.type === 'remote_sse' && config?.url) {\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse', headers: config.headers as Record<string, string> },\n oauthToken,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For external servers (NPX/stdio), create ExternalMcpServer\n if (server.type === 'external' && config?.command) {\n const externalServer = new ExternalMcpServer({\n command: config.command,\n args: config.args,\n env: config.env,\n workingDirectory: config.workingDirectory,\n autoRestart: config.autoRestart,\n maxRestartAttempts: config.maxRestartAttempts,\n startupTimeout: config.startupTimeout,\n shutdownTimeout: config.shutdownTimeout,\n });\n await externalServer.initialize();\n this.serverInstances.set(server.id, externalServer);\n return externalServer;\n }\n\n return null;\n }\n\n /**\n * Convert stored API key config to CoreApiKeyConfig format\n */\n private convertApiKeyConfig(stored: StoredApiKeyConfig | null): CoreApiKeyConfig | null {\n if (!stored?.apiKey) return null;\n\n const headerName = stored.headerName || 'Authorization';\n const template = stored.headerValueTemplate || 'Bearer {apiKey}';\n const headerValue = template.replace('{apiKey}', stored.apiKey);\n\n return {\n apiKey: stored.apiKey,\n headerName,\n headerValue,\n };\n }\n\n private parseJson<T>(value: unknown): T | null {\n if (typeof value === 'string') {\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n }\n return value as T | null;\n }\n\n /**\n * Resolve an org slug to an organization ID.\n */\n private async resolveOrgBySlug(orgSlug: string): Promise<string> {\n const org = await this.prisma.organization.findUnique({\n where: { slug: orgSlug },\n select: { id: true },\n });\n if (!org) {\n throw new NotFoundException(`Organization \"${orgSlug}\" not found`);\n }\n return org.id;\n }\n\n /**\n * Find a profile by name within an organization (or system profiles).\n */\n private async findProfileByNameAndOrg(profileName: string, orgId: string) {\n const include = {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' as const },\n },\n };\n\n // Try org-scoped first\n let profile = await this.prisma.profile.findUnique({\n where: { organizationId_name: { organizationId: orgId, name: profileName } },\n include,\n });\n\n // Fallback to system profile (organizationId=null)\n if (!profile) {\n profile = await this.prisma.profile.findFirst({\n where: { name: profileName, organizationId: null },\n include,\n });\n }\n\n return profile;\n }\n\n /**\n * Find a profile by name, scoped to user if userId is provided.\n * Anonymous users see all profiles. Authenticated users see own + system profiles.\n */\n private async findProfileByName(profileName: string, userId?: string) {\n const include = {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' as const },\n },\n };\n\n // Try finding by unique (org, name) — for system profiles use null org\n const profile = await this.prisma.profile.findFirst({\n where: { name: profileName },\n include,\n });\n\n if (!profile) return null;\n\n // If no userId or unauthenticated, allow access to all profiles\n if (!userId || userId === '__unauthenticated__') return profile;\n\n // System records — accessible to all\n if (!profile.organizationId) return profile;\n\n // Allow access if user is a member of the profile's org\n const membership = await this.prisma.member.findFirst({\n where: { userId, organizationId: profile.organizationId },\n });\n if (membership) return profile;\n\n // Otherwise deny\n return null;\n }\n\n /**\n * Handle MCP request using org slug to resolve the profile.\n */\n async handleRequestByOrgSlug(\n profileName: string,\n orgSlug: string,\n request: McpRequest,\n userId?: string\n ): Promise<McpResponse> {\n const orgId = await this.resolveOrgBySlug(orgSlug);\n const profile = await this.findProfileByNameAndOrg(profileName, orgId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found in organization \"${orgSlug}\"`);\n }\n\n const requestId = request.id;\n const startTime = Date.now();\n\n const shouldLog = request.method !== 'initialize';\n let logId: string | null = null;\n if (shouldLog) {\n try {\n const log = await this.debugService.createLog({\n profileId: profile.id,\n requestType: request.method,\n requestPayload: JSON.stringify(request),\n status: 'pending',\n });\n logId = log.id;\n } catch (logError) {\n this.logger.warn(`Failed to create debug log: ${logError}`);\n }\n }\n\n try {\n let response: McpResponse;\n\n switch (request.method) {\n case 'initialize':\n response = await this.handleInitialize(requestId, profile);\n break;\n case 'tools/list':\n response = await this.handleToolsList(requestId, profile);\n break;\n case 'tools/call':\n response = await this.handleToolsCall(\n requestId,\n profile,\n request.params as McpToolCall,\n logId\n );\n break;\n case 'resources/list':\n response = await this.handleResourcesList(requestId, profile);\n break;\n case 'resources/read':\n response = await this.handleResourcesRead(\n requestId,\n profile,\n request.params as { uri: string }\n );\n break;\n default:\n response = {\n jsonrpc: '2.0',\n id: requestId,\n error: { code: -32601, message: `Method not found: ${request.method}` },\n };\n }\n\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n responsePayload: JSON.stringify(response),\n status: response.error ? 'error' : 'success',\n errorMessage: response.error?.message,\n durationMs: Date.now() - startTime,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return response;\n } catch (error) {\n this.logger.error(`MCP request error: ${error}`);\n const errorMessage = error instanceof Error ? error.message : 'Internal error';\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n status: 'error',\n errorMessage,\n durationMs: Date.now() - startTime,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: { code: -32603, message: errorMessage },\n };\n }\n }\n\n /**\n * Get profile info using org slug.\n */\n async getProfileInfoByOrgSlug(profileName: string, orgSlug: string) {\n const orgId = await this.resolveOrgBySlug(orgSlug);\n const profile = await this.findProfileByNameAndOrg(profileName, orgId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found in organization \"${orgSlug}\"`);\n }\n\n return this.aggregateProfileInfo(profile);\n }\n\n /**\n * Get profile info with aggregated tools and server status\n */\n async getProfileInfo(profileName: string, userId?: string) {\n const profile = await this.findProfileByName(profileName, userId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n return this.aggregateProfileInfo(profile);\n }\n\n /**\n * Aggregate tools and server status from a profile.\n */\n private async aggregateProfileInfo(profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n customDescription: string | null;\n }>;\n }>;\n }) {\n const tools: Array<{ name: string; description: string }> = [];\n const serverStatus: Record<string, { connected: boolean; toolCount: number }> = {};\n\n for (const ps of profile.mcpServers) {\n const server = ps.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const serverTools = await instance.listTools();\n serverStatus[server.id] = { connected: true, toolCount: serverTools.length };\n\n for (const tool of serverTools) {\n const customization = ps.tools.find((t) => t.toolName === tool.name);\n if (!customization || customization.isEnabled) {\n tools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n });\n }\n }\n } else {\n serverStatus[server.id] = { connected: false, toolCount: 0 };\n }\n }\n\n return {\n tools,\n serverStatus: {\n total: profile.mcpServers.length,\n connected: Object.values(serverStatus).filter((s) => s.connected).length,\n servers: serverStatus,\n },\n };\n }\n}\n"],"names":["ExternalMcpServer","RemoteHttpMcpServer","RemoteSseMcpServer","Injectable","Logger","NotFoundException","PrismaService","DebugService","McpRegistry","ProxyService","prisma","registry","debugService","logger","name","serverInstances","Map","handleRequest","profileName","request","userId","requestId","id","startTime","Date","now","profile","findProfileByName","shouldLog","method","logId","log","createLog","profileId","requestType","requestPayload","JSON","stringify","status","logError","warn","response","handleInitialize","handleToolsList","handleToolsCall","params","handleResourcesList","handleResourcesRead","jsonrpc","error","code","message","durationMs","updateLog","responsePayload","errorMessage","Error","_profile","result","protocolVersion","capabilities","tools","resources","serverInfo","version","allTools","profileServer","mcpServers","server","mcpServer","instance","getServerInstance","listTools","tool","customization","find","t","toolName","isEnabled","push","customName","description","customDescription","inputSchema","hasTool","some","mcpServerId","callTool","arguments","allResources","listResources","content","readResource","uri","getToolsForServer","serverId","findUnique","where","cached","get","config","parseJson","builtinId","storedConfig","apiKeyConfig","convertApiKeyConfig","has","pkg","createServer","initialize","set","oauthToken","type","tokenRecord","oAuthToken","accessToken","tokenType","refreshToken","undefined","scope","expiresAt","getTime","createdAt","updatedAt","url","remoteServer","transport","headers","command","externalServer","args","env","workingDirectory","autoRestart","maxRestartAttempts","startupTimeout","shutdownTimeout","stored","apiKey","headerName","template","headerValueTemplate","headerValue","replace","value","parse","resolveOrgBySlug","orgSlug","org","organization","slug","select","findProfileByNameAndOrg","orgId","include","isActive","orderBy","order","organizationId_name","organizationId","findFirst","membership","member","handleRequestByOrgSlug","getProfileInfoByOrgSlug","aggregateProfileInfo","getProfileInfo","serverStatus","ps","serverTools","connected","toolCount","length","total","Object","values","filter","s","servers"],"mappings":"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"}
@@ -1,9 +1,22 @@
1
1
  #!/bin/sh
2
2
  set -e
3
3
 
4
- echo "Pushing database schema..."
4
+ echo "Running database migrations..."
5
+ MAX_RETRIES=15
6
+ RETRY_INTERVAL=2
5
7
  cd /app/packages/database
6
- pnpm exec prisma db push --config ./dist/prisma.config.js
8
+ for i in $(seq 1 $MAX_RETRIES); do
9
+ if pnpm exec prisma migrate deploy --config ./dist/prisma.config.js; then
10
+ echo "Migrations applied successfully."
11
+ break
12
+ fi
13
+ if [ "$i" -eq "$MAX_RETRIES" ]; then
14
+ echo "Failed to apply migrations after $MAX_RETRIES attempts."
15
+ exit 1
16
+ fi
17
+ echo "Retrying in ${RETRY_INTERVAL}s... (attempt $i/$MAX_RETRIES)"
18
+ sleep $RETRY_INTERVAL
19
+ done
7
20
 
8
21
  echo "Starting application..."
9
22
  cd /app/apps/backend
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxheroes/local-mcp-backend",
3
- "version": "0.9.2",
3
+ "version": "0.11.0",
4
4
  "description": "NestJS API server providing MCP proxy, server aggregation, OAuth 2.1, and profile management",
5
5
  "license": "Elastic-2.0",
6
6
  "type": "module",
@@ -23,11 +23,13 @@
23
23
  "reflect-metadata": "^0.2.2",
24
24
  "rxjs": "^7.8.2",
25
25
  "zod": "^4.3.5",
26
- "@dxheroes/local-mcp-core": "0.7.2",
27
- "@dxheroes/local-mcp-database": "0.5.2",
28
- "@dxheroes/mcp-gemini-deep-research": "0.5.5",
29
- "@dxheroes/mcp-toggl": "0.3.5",
30
- "@dxheroes/mcp-merk": "0.3.5"
26
+ "@dxheroes/local-mcp-database": "0.5.4",
27
+ "@dxheroes/mcp-abra-flexi": "0.3.3",
28
+ "@dxheroes/local-mcp-core": "0.8.1",
29
+ "@dxheroes/mcp-gemini-deep-research": "0.5.7",
30
+ "@dxheroes/mcp-fakturoid": "0.3.3",
31
+ "@dxheroes/mcp-toggl": "0.3.7",
32
+ "@dxheroes/mcp-merk": "0.3.7"
31
33
  },
32
34
  "devDependencies": {
33
35
  "@nestjs/cli": "^11.0.14",
@@ -41,7 +43,7 @@
41
43
  "@types/pg": "^8.18.0",
42
44
  "typescript": "^5.9.3",
43
45
  "vitest": "^4.0.17",
44
- "@dxheroes/local-mcp-config": "0.4.10"
46
+ "@dxheroes/local-mcp-config": "0.4.12"
45
47
  },
46
48
  "scripts": {
47
49
  "build": "nest build",
@@ -0,0 +1,311 @@
1
+ import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
2
+ import type { ConfigService } from '@nestjs/config';
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import { AllExceptionsFilter } from '../../common/filters/all-exceptions.filter.js';
5
+ import { AuthService, type AuthUser } from '../../modules/auth/auth.service.js';
6
+ import { McpOAuthGuard } from '../../modules/auth/mcp-oauth.guard.js';
7
+ import {
8
+ createMcpProtectedResourceMetadata,
9
+ resolvePublicAuthBaseUrl,
10
+ resolvePublicBackendOrigin,
11
+ } from '../../modules/auth/mcp-oauth.utils.js';
12
+
13
+ type MockAuthService = {
14
+ validateMcpToken: ReturnType<typeof vi.fn>;
15
+ getSession: ReturnType<typeof vi.fn>;
16
+ };
17
+
18
+ type MockConfigService = {
19
+ get: ReturnType<typeof vi.fn>;
20
+ };
21
+
22
+ function createConfigService(values: Record<string, unknown>): MockConfigService {
23
+ return {
24
+ get: vi.fn((key: string) => values[key]),
25
+ };
26
+ }
27
+
28
+ function createAuthServiceMock(): MockAuthService {
29
+ return {
30
+ validateMcpToken: vi.fn().mockResolvedValue(null),
31
+ getSession: vi.fn().mockResolvedValue(null),
32
+ };
33
+ }
34
+
35
+ async function startTestApp(options?: {
36
+ backendUrl?: string;
37
+ }): Promise<{
38
+ close: () => Promise<void>;
39
+ authService: MockAuthService;
40
+ baseUrl: string;
41
+ }> {
42
+ const authService = createAuthServiceMock();
43
+ const configService = createConfigService({
44
+ 'app.port': 3001,
45
+ BETTER_AUTH_URL: options?.backendUrl ?? 'http://localhost:3001',
46
+ });
47
+ const guard = new McpOAuthGuard(authService as unknown as AuthService, configService as never);
48
+ const filter = new AllExceptionsFilter();
49
+ const backendOrigin = resolvePublicBackendOrigin(configService as unknown as ConfigService);
50
+ const authBaseUrl = resolvePublicAuthBaseUrl(configService as unknown as ConfigService);
51
+
52
+ const createRequest = (req: IncomingMessage) => {
53
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);
54
+ return {
55
+ method: req.method ?? 'GET',
56
+ url: `${url.pathname}${url.search}`,
57
+ headers: req.headers,
58
+ query: Object.fromEntries(url.searchParams.entries()),
59
+ user: undefined,
60
+ };
61
+ };
62
+
63
+ const createResponse = (res: ServerResponse) => {
64
+ const response = {
65
+ setHeader(name: string, value: string) {
66
+ res.setHeader(name, value);
67
+ return response;
68
+ },
69
+ status(statusCode: number) {
70
+ res.statusCode = statusCode;
71
+ return response;
72
+ },
73
+ json(body: unknown) {
74
+ res.setHeader('content-type', 'application/json');
75
+ res.end(JSON.stringify(body));
76
+ },
77
+ };
78
+
79
+ return response;
80
+ };
81
+
82
+ const handleGuardedRoute = async (
83
+ nodeRequest: IncomingMessage,
84
+ nodeResponse: ServerResponse,
85
+ responseBody: (request: ReturnType<typeof createRequest>) => Record<string, unknown>
86
+ ) => {
87
+ const request = createRequest(nodeRequest);
88
+ const response = createResponse(nodeResponse);
89
+ const context = {
90
+ switchToHttp: () => ({
91
+ getRequest: () => request,
92
+ getResponse: () => response,
93
+ }),
94
+ } as never;
95
+
96
+ const host = {
97
+ switchToHttp: () => ({
98
+ getRequest: () => request,
99
+ getResponse: () => response,
100
+ }),
101
+ } as never;
102
+
103
+ try {
104
+ await guard.canActivate(context);
105
+ response.json(responseBody(request));
106
+ } catch (error) {
107
+ filter.catch(error, host);
108
+ }
109
+ };
110
+
111
+ const server = await new Promise<import('node:http').Server>((resolve) => {
112
+ const listeningServer = createServer(async (req, res) => {
113
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);
114
+
115
+ if (req.method === 'GET' && url.pathname === '/api/mcp/test') {
116
+ await handleGuardedRoute(req, res, (request) => ({
117
+ ok: true,
118
+ userId: (request as { user?: AuthUser }).user?.id ?? null,
119
+ }));
120
+ return;
121
+ }
122
+
123
+ if (req.method === 'GET' && url.pathname === '/api/mcp/sse') {
124
+ await handleGuardedRoute(req, res, (request) => ({
125
+ ok: true,
126
+ token: request.query.access_token ?? null,
127
+ userId: (request as { user?: AuthUser }).user?.id ?? null,
128
+ }));
129
+ return;
130
+ }
131
+
132
+ if (req.method === 'GET' && url.pathname === '/.well-known/oauth-protected-resource') {
133
+ res.setHeader('content-type', 'application/json');
134
+ res.end(JSON.stringify(createMcpProtectedResourceMetadata(configService as unknown as ConfigService)));
135
+ return;
136
+ }
137
+
138
+ if (req.method === 'GET' && url.pathname === '/.well-known/oauth-authorization-server') {
139
+ res.setHeader('content-type', 'application/json');
140
+ res.end(
141
+ JSON.stringify({
142
+ issuer: backendOrigin,
143
+ authorization_endpoint: `${authBaseUrl}/mcp/authorize`,
144
+ token_endpoint: `${authBaseUrl}/mcp/token`,
145
+ registration_endpoint: `${authBaseUrl}/mcp/register`,
146
+ jwks_uri: `${authBaseUrl}/mcp/jwks`,
147
+ response_types_supported: ['code'],
148
+ grant_types_supported: ['authorization_code', 'refresh_token'],
149
+ code_challenge_methods_supported: ['S256'],
150
+ })
151
+ );
152
+ return;
153
+ }
154
+
155
+ if (req.method === 'POST' && url.pathname === '/api/auth/mcp/register') {
156
+ res.statusCode = 201;
157
+ res.setHeader('content-type', 'application/json');
158
+ res.end(
159
+ JSON.stringify({
160
+ client_id: 'cursor-client',
161
+ client_secret: 'cursor-secret',
162
+ redirect_uris: ['https://cursor.sh/callback'],
163
+ })
164
+ );
165
+ return;
166
+ }
167
+
168
+ res.statusCode = 404;
169
+ res.end();
170
+ }).listen(0, () => resolve(listeningServer));
171
+ });
172
+ const address = server.address();
173
+ if (!address || typeof address === 'string') {
174
+ throw new Error('Failed to resolve test server address');
175
+ }
176
+
177
+ return {
178
+ close: () =>
179
+ new Promise<void>((resolve, reject) => {
180
+ server.close((error) => {
181
+ if (error) {
182
+ reject(error);
183
+ return;
184
+ }
185
+ resolve();
186
+ });
187
+ }),
188
+ authService,
189
+ baseUrl: `http://127.0.0.1:${address.port}`,
190
+ };
191
+ }
192
+
193
+ describe('MCP proxy auth HTTP contract', () => {
194
+ let close: (() => Promise<void>) | undefined;
195
+ let authService: MockAuthService;
196
+ let baseUrl: string;
197
+
198
+ beforeEach(async () => {
199
+ const result = await startTestApp({
200
+ backendUrl: 'http://localhost:9631',
201
+ });
202
+ close = result.close;
203
+ authService = result.authService;
204
+ baseUrl = result.baseUrl;
205
+ });
206
+
207
+ afterEach(async () => {
208
+ if (close) {
209
+ await close();
210
+ }
211
+ });
212
+
213
+ it('returns 401 with MCP discovery headers when token is missing', async () => {
214
+ const response = await fetch(`${baseUrl}/api/mcp/test`);
215
+ const body = (await response.json()) as Record<string, unknown>;
216
+
217
+ expect(response.status).toBe(401);
218
+ expect(response.headers.get('access-control-expose-headers')).toBe('WWW-Authenticate');
219
+ expect(response.headers.get('www-authenticate')).toContain('resource_metadata=');
220
+ expect(response.headers.get('www-authenticate')).toContain('resource_metadata_uri=');
221
+ expect(body.message).toBe('Bearer token required');
222
+ });
223
+
224
+ it('serves protected resource metadata with Better Auth-backed URLs', async () => {
225
+ const response = await fetch(`${baseUrl}/.well-known/oauth-protected-resource`);
226
+ const body = (await response.json()) as Record<string, unknown>;
227
+
228
+ expect(response.status).toBe(200);
229
+ expect(body).toEqual({
230
+ resource: 'http://localhost:9631/api/mcp',
231
+ authorization_servers: ['http://localhost:9631'],
232
+ bearer_methods_supported: ['header'],
233
+ scopes_supported: ['openid', 'profile', 'email', 'offline_access'],
234
+ jwks_uri: 'http://localhost:9631/api/auth/mcp/jwks',
235
+ resource_signing_alg_values_supported: ['RS256', 'none'],
236
+ });
237
+ });
238
+
239
+ it('serves authorization server metadata with MCP registration endpoint', async () => {
240
+ const response = await fetch(`${baseUrl}/.well-known/oauth-authorization-server`);
241
+ const body = (await response.json()) as Record<string, unknown>;
242
+
243
+ expect(response.status).toBe(200);
244
+ expect(body.registration_endpoint).toBe('http://localhost:9631/api/auth/mcp/register');
245
+ expect(body.authorization_endpoint).toBe('http://localhost:9631/api/auth/mcp/authorize');
246
+ expect(body.token_endpoint).toBe('http://localhost:9631/api/auth/mcp/token');
247
+ });
248
+
249
+ it('exposes the advertised registration endpoint', async () => {
250
+ const response = await fetch(`${baseUrl}/api/auth/mcp/register`, {
251
+ method: 'POST',
252
+ headers: {
253
+ 'content-type': 'application/json',
254
+ },
255
+ body: JSON.stringify({
256
+ redirect_uris: ['https://cursor.sh/callback'],
257
+ }),
258
+ });
259
+ const body = (await response.json()) as Record<string, unknown>;
260
+
261
+ expect(response.status).toBe(201);
262
+ expect(body.client_id).toBe('cursor-client');
263
+ });
264
+
265
+ it('accepts access_token query params for SSE fallback', async () => {
266
+ authService.validateMcpToken.mockResolvedValue({
267
+ id: 'user-sse',
268
+ name: 'SSE User',
269
+ email: 'sse@example.com',
270
+ });
271
+
272
+ const response = await fetch(`${baseUrl}/api/mcp/sse?access_token=sse-token`);
273
+ const body = (await response.json()) as Record<string, unknown>;
274
+
275
+ expect(response.status).toBe(200);
276
+ expect(body).toEqual({
277
+ ok: true,
278
+ token: 'sse-token',
279
+ userId: 'user-sse',
280
+ });
281
+ expect(authService.validateMcpToken).toHaveBeenCalledWith('sse-token');
282
+ });
283
+
284
+ it('accepts session cookie when no Bearer token is present', async () => {
285
+ authService.getSession.mockResolvedValue({
286
+ user: { id: 'cookie-user', name: 'Cookie User', email: 'cookie@example.com' },
287
+ session: { id: 'sess-1', userId: 'cookie-user' },
288
+ });
289
+
290
+ const response = await fetch(`${baseUrl}/api/mcp/test`, {
291
+ headers: { cookie: 'better-auth.session_token=valid-session' },
292
+ });
293
+ const body = (await response.json()) as Record<string, unknown>;
294
+
295
+ expect(response.status).toBe(200);
296
+ expect(body).toEqual({ ok: true, userId: 'cookie-user' });
297
+ expect(authService.getSession).toHaveBeenCalled();
298
+ });
299
+
300
+ it('returns 401 when session cookie is invalid and no Bearer token', async () => {
301
+ authService.getSession.mockResolvedValue(null);
302
+
303
+ const response = await fetch(`${baseUrl}/api/mcp/test`, {
304
+ headers: { cookie: 'better-auth.session_token=expired' },
305
+ });
306
+ const body = (await response.json()) as Record<string, unknown>;
307
+
308
+ expect(response.status).toBe(401);
309
+ expect(body.message).toBe('Bearer token required');
310
+ });
311
+ });