@dxheroes/local-mcp-backend 0.9.2 → 0.10.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 (53) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/CHANGELOG.md +27 -0
  3. package/dist/__tests__/integration/mcp-proxy-auth-http.test.js +246 -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 +121 -111
  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 +37 -0
  14. package/dist/main.js.map +1 -1
  15. package/dist/modules/auth/auth.config.js +5 -2
  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 +70 -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/mcp/mcp.service.js +48 -8
  26. package/dist/modules/mcp/mcp.service.js.map +1 -1
  27. package/dist/modules/oauth/oauth.controller.js +78 -1
  28. package/dist/modules/oauth/oauth.controller.js.map +1 -1
  29. package/dist/modules/oauth/oauth.service.js +197 -1
  30. package/dist/modules/oauth/oauth.service.js.map +1 -1
  31. package/dist/modules/proxy/proxy.controller.js +152 -27
  32. package/dist/modules/proxy/proxy.controller.js.map +1 -1
  33. package/dist/modules/proxy/proxy.service.js +28 -4
  34. package/dist/modules/proxy/proxy.service.js.map +1 -1
  35. package/docker-entrypoint.sh +15 -2
  36. package/package.json +7 -7
  37. package/src/__tests__/integration/mcp-proxy-auth-http.test.ts +281 -0
  38. package/src/__tests__/integration/oauth-authorize-callback.test.ts +155 -0
  39. package/src/__tests__/integration/proxy-auth.test.ts +119 -168
  40. package/src/__tests__/unit/auth.guard.test.ts +12 -2
  41. package/src/common/filters/all-exceptions.filter.ts +11 -0
  42. package/src/main.ts +32 -1
  43. package/src/modules/auth/auth.config.ts +4 -1
  44. package/src/modules/auth/auth.module.ts +3 -2
  45. package/src/modules/auth/auth.service.ts +2 -2
  46. package/src/modules/auth/mcp-oauth.guard.ts +75 -0
  47. package/src/modules/auth/mcp-oauth.utils.ts +80 -0
  48. package/src/modules/mcp/mcp.service.ts +54 -12
  49. package/src/modules/oauth/oauth.controller.ts +84 -1
  50. package/src/modules/oauth/oauth.service.ts +218 -1
  51. package/src/modules/proxy/proxy.controller.ts +120 -25
  52. package/src/modules/proxy/proxy.service.ts +26 -4
  53. package/vitest.config.ts +2 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/modules/mcp/mcp.service.ts"],"sourcesContent":["/**\n * MCP Service\n *\n * Business logic for MCP server management.\n * All queries are scoped to the user's active organization.\n * System records (organizationId=null, including builtin) are visible to all.\n */\n\nimport {\n ExternalMcpServer,\n RemoteHttpMcpServer,\n RemoteSseMcpServer,\n} from '@dxheroes/local-mcp-core';\nimport { ConflictException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';\n\n/** Sentinel value for unauthenticated MCP access — can only see system servers */\nconst UNAUTHENTICATED_ID = '__unauthenticated__';\n\nimport { PrismaService } from '../database/prisma.service.js';\nimport { DebugService } from '../debug/debug.service.js';\nimport type { CreateMcpServerDto } from './dto/create-mcp-server.dto.js';\nimport type { UpdateMcpServerDto } from './dto/update-mcp-server.dto.js';\nimport { MCP_PRESETS } from './mcp-presets.js';\nimport { McpRegistry } from './mcp-registry.js';\n\n@Injectable()\nexport class McpService {\n constructor(\n private readonly prisma: PrismaService,\n private readonly registry: McpRegistry,\n private readonly debugService: DebugService\n ) {}\n\n private isAnonymous(userId: string): boolean {\n return userId === UNAUTHENTICATED_ID;\n }\n\n private async assertAccess(serverId: string, userId: string, orgId?: string): Promise<void> {\n if (this.isAnonymous(userId)) return;\n\n const server = await this.prisma.mcpServer.findUnique({\n where: { id: serverId },\n select: { userId: true, organizationId: true },\n });\n if (!server) throw new NotFoundException(`MCP server ${serverId} not found`);\n\n // System record (no org) — accessible to all\n if (!server.organizationId) return;\n\n // Must belong to the active org\n if (orgId && server.organizationId === orgId) return;\n\n throw new ForbiddenException('You do not have access to this MCP server');\n }\n\n private async assertOwnership(serverId: string, userId: string, orgId?: string): Promise<void> {\n if (this.isAnonymous(userId)) return;\n\n const server = await this.prisma.mcpServer.findUnique({\n where: { id: serverId },\n select: { userId: true, organizationId: true },\n });\n if (!server) throw new NotFoundException(`MCP server ${serverId} not found`);\n if (!server.organizationId) return; // system record\n\n if (orgId && server.organizationId === orgId) return;\n\n throw new ForbiddenException('You do not own this MCP server');\n }\n\n /**\n * Get all MCP servers visible in the active org (org servers + system servers)\n */\n async findAll(userId: string, orgId?: string) {\n const include = { profiles: { include: { profile: true as const } } };\n const orderBy = { name: 'asc' as const };\n\n const servers = this.isAnonymous(userId)\n ? await this.prisma.mcpServer.findMany({ include, orderBy })\n : await this.prisma.mcpServer.findMany({\n where: {\n OR: [\n { organizationId: orgId }, // org servers\n { organizationId: null }, // system servers\n ],\n },\n include,\n orderBy,\n });\n\n // Enrich with metadata from registry for builtin servers\n return servers.map((server) => {\n const builtinId = this.getBuiltinId(server.config);\n return builtinId && this.registry.has(builtinId)\n ? { ...server, metadata: this.registry.get(builtinId)?.metadata }\n : server;\n });\n }\n\n /**\n * Get a specific MCP server\n */\n async findById(id: string, userId?: string, orgId?: string) {\n if (userId) {\n await this.assertAccess(id, userId, orgId);\n }\n\n const server = await this.prisma.mcpServer.findUnique({\n where: { id },\n include: {\n profiles: {\n include: {\n profile: true,\n tools: true,\n },\n },\n oauthToken: true,\n toolsCache: true,\n },\n });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n // Enrich with metadata from registry for builtin servers\n const builtinId = this.getBuiltinId(server.config);\n\n return builtinId && this.registry.has(builtinId)\n ? { ...server, metadata: this.registry.get(builtinId)?.metadata }\n : server;\n }\n\n /**\n * Create a new MCP server in the active org\n */\n async create(dto: CreateMcpServerDto, userId: string, orgId?: string) {\n return this.prisma.mcpServer.create({\n data: {\n name: dto.name,\n type: dto.type,\n config: JSON.stringify(dto.config || {}),\n apiKeyConfig: dto.apiKeyConfig ? JSON.stringify(dto.apiKeyConfig) : null,\n oauthConfig: dto.oauthConfig ? JSON.stringify(dto.oauthConfig) : null,\n userId: this.isAnonymous(userId) ? null : userId,\n organizationId: orgId ?? null,\n },\n });\n }\n\n /**\n * Update an MCP server\n */\n async update(id: string, dto: UpdateMcpServerDto, userId: string, orgId?: string) {\n await this.assertOwnership(id, userId, orgId);\n\n const server = await this.prisma.mcpServer.findUnique({ where: { id } });\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n return this.prisma.mcpServer.update({\n where: { id },\n data: {\n name: dto.name,\n type: dto.type,\n config:\n dto.config !== undefined\n ? typeof dto.config === 'string'\n ? dto.config\n : JSON.stringify(dto.config)\n : undefined,\n apiKeyConfig: dto.apiKeyConfig !== undefined ? JSON.stringify(dto.apiKeyConfig) : undefined,\n oauthConfig: dto.oauthConfig !== undefined ? JSON.stringify(dto.oauthConfig) : undefined,\n },\n });\n }\n\n /**\n * Delete an MCP server\n */\n async delete(id: string, userId: string, orgId?: string) {\n await this.assertOwnership(id, userId, orgId);\n\n const server = await this.prisma.mcpServer.findUnique({ where: { id } });\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n await this.prisma.mcpServer.delete({ where: { id } });\n }\n\n /**\n * Get tools from an MCP server\n */\n async getTools(id: string, userId?: string, orgId?: string) {\n if (userId) {\n await this.assertAccess(id, userId, orgId);\n }\n\n const startTime = Date.now();\n\n // Create debug log entry\n const log = await this.debugService.createLog({\n mcpServerId: id,\n requestType: 'tools/list',\n requestPayload: JSON.stringify({ method: 'tools/list', serverId: id }),\n status: 'pending',\n });\n\n try {\n const result = await this.getToolsInternal(id);\n\n // Update debug log with success\n await this.debugService.updateLog(log.id, {\n responsePayload: JSON.stringify(result),\n status: 'success',\n durationMs: Date.now() - startTime,\n });\n\n return result;\n } catch (error) {\n // Update debug log with error\n await this.debugService.updateLog(log.id, {\n status: 'error',\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n durationMs: Date.now() - startTime,\n });\n throw error;\n }\n }\n\n /**\n * Internal method to get tools from an MCP server\n */\n private async getToolsInternal(id: string) {\n const server = await this.findById(id);\n const builtinId = this.getBuiltinId(server.config);\n\n // For builtin servers, get tools from the registry\n if (builtinId && this.registry.has(builtinId)) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n // Get API key config if set\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n // Create server instance and list tools\n const instance = pkg.createServer(apiKeyConfig);\n await instance.initialize();\n const tools = await instance.listTools();\n return { tools };\n }\n }\n\n // For remote_http servers, connect and fetch tools\n if (server.type === 'remote_http') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n return { tools };\n }\n\n // For remote_sse servers, connect and fetch tools\n if (server.type === 'remote_sse') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n return { tools };\n }\n\n // For external (NPX/stdio) servers, spawn and fetch tools\n if (server.type === 'external') {\n const config = this.parseConfig(server.config) as {\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\n const externalServer = new ExternalMcpServer(config);\n try {\n await externalServer.initialize();\n const tools = await externalServer.listTools();\n return { tools };\n } finally {\n // Shutdown after fetching tools\n await externalServer.shutdown();\n }\n }\n\n // For cached tools from external servers (fallback)\n const tools = await this.prisma.mcpServerToolsCache.findMany({\n where: { mcpServerId: id },\n });\n return { tools };\n }\n\n /**\n * Get MCP server status with real validation\n */\n async getStatus(id: string, userId?: string, orgId?: string) {\n if (userId) {\n await this.assertAccess(id, userId, orgId);\n }\n\n const startTime = Date.now();\n\n // Create debug log entry\n const log = await this.debugService.createLog({\n mcpServerId: id,\n requestType: 'status/check',\n requestPayload: JSON.stringify({ method: 'status/check', serverId: id }),\n status: 'pending',\n });\n\n try {\n const result = await this.getStatusInternal(id);\n\n // Update debug log with success or error based on status\n await this.debugService.updateLog(log.id, {\n responsePayload: JSON.stringify(result),\n status: result.status === 'error' ? 'error' : 'success',\n errorMessage: result.error,\n durationMs: Date.now() - startTime,\n });\n\n return result;\n } catch (error) {\n // Update debug log with error\n await this.debugService.updateLog(log.id, {\n status: 'error',\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n durationMs: Date.now() - startTime,\n });\n throw error;\n }\n }\n\n /**\n * Internal method to get MCP server status with real validation\n */\n private async getStatusInternal(id: string) {\n const server = await this.findById(id);\n const builtinId = this.getBuiltinId(server.config);\n\n // Check if it's a builtin server\n const isBuiltin = builtinId && this.registry.has(builtinId);\n\n // Check API key config\n const hasApiKey = !!server.apiKeyConfig;\n\n // Check OAuth token\n const hasOAuth = !!server.oauthToken;\n\n // Get metadata\n const metadata = isBuiltin ? this.registry.get(builtinId)?.metadata : null;\n const requiresApiKey = metadata?.requiresApiKey ?? false;\n const requiresOAuth = metadata?.requiresOAuth ?? false;\n\n // Default status\n let status: 'connected' | 'error' | 'unknown' = 'unknown';\n let validationError: string | undefined;\n let validationDetails: string | undefined;\n\n // For builtin servers with API key, actually validate\n if (isBuiltin && hasApiKey) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n try {\n const instance = pkg.createServer(apiKeyConfig);\n const validation = await instance.validate();\n status = validation.valid ? 'connected' : 'error';\n validationError = validation.error;\n validationDetails = validation.valid\n ? 'API key validated successfully'\n : `Validation failed: ${validation.error}`;\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection test failed: ${validationError}`;\n }\n }\n } else if (!hasApiKey && requiresApiKey) {\n status = 'error';\n validationError = 'API key required';\n validationDetails = 'This server requires an API key to function';\n } else if (isBuiltin && !requiresApiKey) {\n status = 'connected';\n validationDetails = 'Server ready (no API key required)';\n }\n\n // For remote_http servers, validate by connecting\n if (server.type === 'remote_http' && status === 'unknown') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n try {\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully. ${tools.length} tools available.`;\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection failed: ${validationError}`;\n }\n }\n\n // For remote_sse servers, validate by connecting\n if (server.type === 'remote_sse' && status === 'unknown') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n try {\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully (SSE). ${tools.length} tools available.`;\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection failed: ${validationError}`;\n }\n }\n\n // For external (NPX/stdio) servers, validate by spawning and checking\n if (server.type === 'external' && status === 'unknown') {\n const config = this.parseConfig(server.config) as {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n workingDirectory?: string;\n };\n\n if (!config.command) {\n status = 'error';\n validationError = 'Command is required for external MCP servers';\n validationDetails = 'Missing command configuration';\n } else {\n try {\n const externalServer = new ExternalMcpServer(config);\n await externalServer.initialize();\n const tools = await externalServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully (stdio). ${tools.length} tools available.`;\n await externalServer.shutdown();\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection failed: ${validationError}`;\n }\n }\n }\n\n const isReady = status === 'connected';\n\n return {\n id: server.id,\n name: server.name,\n type: server.type,\n isBuiltin,\n hasApiKey,\n hasOAuth,\n requiresApiKey,\n requiresOAuth,\n isReady,\n status,\n error: validationError,\n details: validationDetails,\n validatedAt: new Date().toISOString(),\n };\n }\n\n /**\n * Add a preset MCP server to the user's organization\n */\n async addPreset(presetId: string, userId: string, orgId: string) {\n const preset = MCP_PRESETS.find((p) => p.id === presetId);\n if (!preset) {\n throw new NotFoundException(`Preset \"${presetId}\" not found`);\n }\n\n // Check if a server with this name already exists in the org\n const existing = await this.prisma.mcpServer.findFirst({\n where: {\n name: preset.name,\n organizationId: orgId,\n },\n });\n\n if (existing) {\n throw new ConflictException(\n `MCP server \"${preset.name}\" already exists in your organization`\n );\n }\n\n return this.prisma.mcpServer.create({\n data: {\n name: preset.name,\n type: preset.type,\n config: JSON.stringify(preset.config),\n userId,\n organizationId: orgId,\n },\n });\n }\n\n /**\n * Extract builtinId from config (handles JSON string parsing)\n */\n private getBuiltinId(config: unknown): string | null {\n const parsed = this.parseConfig(config);\n const builtinId = parsed?.builtinId;\n\n if (typeof builtinId === 'string') {\n return builtinId;\n }\n return null;\n }\n\n private parseConfig(config: unknown): Record<string, unknown> | null {\n if (typeof config === 'string') {\n try {\n return JSON.parse(config);\n } catch {\n return null;\n }\n }\n return config as Record<string, unknown> | null;\n }\n}\n"],"names":["ExternalMcpServer","RemoteHttpMcpServer","RemoteSseMcpServer","ConflictException","ForbiddenException","Injectable","NotFoundException","UNAUTHENTICATED_ID","PrismaService","DebugService","MCP_PRESETS","McpRegistry","McpService","prisma","registry","debugService","isAnonymous","userId","assertAccess","serverId","orgId","server","mcpServer","findUnique","where","id","select","organizationId","assertOwnership","findAll","include","profiles","profile","orderBy","name","servers","findMany","OR","map","builtinId","getBuiltinId","config","has","metadata","get","findById","tools","oauthToken","toolsCache","create","dto","data","type","JSON","stringify","apiKeyConfig","oauthConfig","update","undefined","delete","getTools","startTime","Date","now","log","createLog","mcpServerId","requestType","requestPayload","method","status","result","getToolsInternal","updateLog","responsePayload","durationMs","error","errorMessage","Error","message","pkg","parse","instance","createServer","initialize","listTools","parseConfig","remoteServer","url","transport","externalServer","shutdown","mcpServerToolsCache","getStatus","getStatusInternal","isBuiltin","hasApiKey","hasOAuth","requiresApiKey","requiresOAuth","validationError","validationDetails","validation","validate","valid","length","command","isReady","details","validatedAt","toISOString","addPreset","presetId","preset","find","p","existing","findFirst","parsed"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/modules/mcp/mcp.service.ts"],"sourcesContent":["/**\n * MCP Service\n *\n * Business logic for MCP server management.\n * All queries are scoped to the user's active organization.\n * System records (organizationId=null, including builtin) are visible to all.\n */\n\nimport {\n ExternalMcpServer,\n RemoteHttpMcpServer,\n RemoteSseMcpServer,\n} from '@dxheroes/local-mcp-core';\nimport { ConflictException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';\n\n/** Sentinel value for unauthenticated MCP access — can only see system servers */\nconst UNAUTHENTICATED_ID = '__unauthenticated__';\n\nimport { PrismaService } from '../database/prisma.service.js';\nimport { DebugService } from '../debug/debug.service.js';\nimport type { CreateMcpServerDto } from './dto/create-mcp-server.dto.js';\nimport type { UpdateMcpServerDto } from './dto/update-mcp-server.dto.js';\nimport { MCP_PRESETS } from './mcp-presets.js';\nimport { McpRegistry } from './mcp-registry.js';\n\n@Injectable()\nexport class McpService {\n constructor(\n private readonly prisma: PrismaService,\n private readonly registry: McpRegistry,\n private readonly debugService: DebugService\n ) {}\n\n private isAnonymous(userId: string): boolean {\n return userId === UNAUTHENTICATED_ID;\n }\n\n private async assertAccess(serverId: string, userId: string, orgId?: string): Promise<void> {\n if (this.isAnonymous(userId)) return;\n\n const server = await this.prisma.mcpServer.findUnique({\n where: { id: serverId },\n select: { userId: true, organizationId: true },\n });\n if (!server) throw new NotFoundException(`MCP server ${serverId} not found`);\n\n // System record (no org) — accessible to all\n if (!server.organizationId) return;\n\n // Must belong to the active org\n if (orgId && server.organizationId === orgId) return;\n\n throw new ForbiddenException('You do not have access to this MCP server');\n }\n\n private async assertOwnership(serverId: string, userId: string, orgId?: string): Promise<void> {\n if (this.isAnonymous(userId)) return;\n\n const server = await this.prisma.mcpServer.findUnique({\n where: { id: serverId },\n select: { userId: true, organizationId: true },\n });\n if (!server) throw new NotFoundException(`MCP server ${serverId} not found`);\n if (!server.organizationId) return; // system record\n\n if (orgId && server.organizationId === orgId) return;\n\n throw new ForbiddenException('You do not own this MCP server');\n }\n\n /**\n * Get all MCP servers visible in the active org (org servers + system servers)\n */\n async findAll(userId: string, orgId?: string) {\n const include = { profiles: { include: { profile: true as const } } };\n const orderBy = { name: 'asc' as const };\n\n const servers = this.isAnonymous(userId)\n ? await this.prisma.mcpServer.findMany({ include, orderBy })\n : await this.prisma.mcpServer.findMany({\n where: {\n OR: [\n { organizationId: orgId }, // org servers\n { organizationId: null }, // system servers\n ],\n },\n include,\n orderBy,\n });\n\n // Enrich with metadata from registry for builtin servers\n return servers.map((server) => {\n const builtinId = this.getBuiltinId(server.config);\n return builtinId && this.registry.has(builtinId)\n ? { ...server, metadata: this.registry.get(builtinId)?.metadata }\n : server;\n });\n }\n\n /**\n * Get a specific MCP server\n */\n async findById(id: string, userId?: string, orgId?: string) {\n if (userId) {\n await this.assertAccess(id, userId, orgId);\n }\n\n const server = await this.prisma.mcpServer.findUnique({\n where: { id },\n include: {\n profiles: {\n include: {\n profile: true,\n tools: true,\n },\n },\n oauthToken: true,\n toolsCache: true,\n },\n });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n // Enrich with metadata from registry for builtin servers\n const builtinId = this.getBuiltinId(server.config);\n\n return builtinId && this.registry.has(builtinId)\n ? { ...server, metadata: this.registry.get(builtinId)?.metadata }\n : server;\n }\n\n /**\n * Create a new MCP server in the active org\n */\n async create(dto: CreateMcpServerDto, userId: string, orgId?: string) {\n return this.prisma.mcpServer.create({\n data: {\n name: dto.name,\n type: dto.type,\n config: JSON.stringify(dto.config || {}),\n apiKeyConfig: dto.apiKeyConfig ? JSON.stringify(dto.apiKeyConfig) : null,\n oauthConfig: dto.oauthConfig ? JSON.stringify(dto.oauthConfig) : null,\n userId: this.isAnonymous(userId) ? null : userId,\n organizationId: orgId ?? null,\n },\n });\n }\n\n /**\n * Update an MCP server\n */\n async update(id: string, dto: UpdateMcpServerDto, userId: string, orgId?: string) {\n await this.assertOwnership(id, userId, orgId);\n\n const server = await this.prisma.mcpServer.findUnique({ where: { id } });\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n return this.prisma.mcpServer.update({\n where: { id },\n data: {\n name: dto.name,\n type: dto.type,\n config:\n dto.config !== undefined\n ? typeof dto.config === 'string'\n ? dto.config\n : JSON.stringify(dto.config)\n : undefined,\n apiKeyConfig: dto.apiKeyConfig !== undefined ? JSON.stringify(dto.apiKeyConfig) : undefined,\n oauthConfig: dto.oauthConfig !== undefined ? JSON.stringify(dto.oauthConfig) : undefined,\n },\n });\n }\n\n /**\n * Delete an MCP server\n */\n async delete(id: string, userId: string, orgId?: string) {\n await this.assertOwnership(id, userId, orgId);\n\n const server = await this.prisma.mcpServer.findUnique({ where: { id } });\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n await this.prisma.mcpServer.delete({ where: { id } });\n }\n\n /**\n * Get tools from an MCP server\n */\n async getTools(id: string, userId?: string, orgId?: string) {\n if (userId) {\n await this.assertAccess(id, userId, orgId);\n }\n\n const startTime = Date.now();\n\n // Create debug log entry\n const log = await this.debugService.createLog({\n mcpServerId: id,\n requestType: 'tools/list',\n requestPayload: JSON.stringify({ method: 'tools/list', serverId: id }),\n status: 'pending',\n });\n\n try {\n const result = await this.getToolsInternal(id);\n\n // Update debug log with success\n await this.debugService.updateLog(log.id, {\n responsePayload: JSON.stringify(result),\n status: 'success',\n durationMs: Date.now() - startTime,\n });\n\n return result;\n } catch (error) {\n // Update debug log with error\n await this.debugService.updateLog(log.id, {\n status: 'error',\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n durationMs: Date.now() - startTime,\n });\n throw error;\n }\n }\n\n /**\n * Internal method to get tools from an MCP server\n */\n private async getToolsInternal(id: string) {\n const server = await this.findById(id);\n const builtinId = this.getBuiltinId(server.config);\n\n // For builtin servers, get tools from the registry\n if (builtinId && this.registry.has(builtinId)) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n // Get API key config if set\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n // Create server instance and list tools\n const instance = pkg.createServer(apiKeyConfig);\n await instance.initialize();\n const tools = await instance.listTools();\n return { tools };\n }\n }\n\n // For remote_http servers, connect and fetch tools\n if (server.type === 'remote_http') {\n const config = this.parseConfig(server.config) as { url: string; headers?: Record<string, string> };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http', headers: config.headers },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n return { tools };\n }\n\n // For remote_sse servers, connect and fetch tools\n if (server.type === 'remote_sse') {\n const config = this.parseConfig(server.config) as { url: string; headers?: Record<string, string> };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse', headers: config.headers },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n return { tools };\n }\n\n // For external (NPX/stdio) servers, spawn and fetch tools\n if (server.type === 'external') {\n const config = this.parseConfig(server.config) as {\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\n const externalServer = new ExternalMcpServer(config);\n try {\n await externalServer.initialize();\n const tools = await externalServer.listTools();\n return { tools };\n } finally {\n // Shutdown after fetching tools\n await externalServer.shutdown();\n }\n }\n\n // For cached tools from external servers (fallback)\n const tools = await this.prisma.mcpServerToolsCache.findMany({\n where: { mcpServerId: id },\n });\n return { tools };\n }\n\n /**\n * Get MCP server status with real validation\n */\n async getStatus(id: string, userId?: string, orgId?: string) {\n if (userId) {\n await this.assertAccess(id, userId, orgId);\n }\n\n const startTime = Date.now();\n\n // Create debug log entry\n const log = await this.debugService.createLog({\n mcpServerId: id,\n requestType: 'status/check',\n requestPayload: JSON.stringify({ method: 'status/check', serverId: id }),\n status: 'pending',\n });\n\n try {\n const result = await this.getStatusInternal(id);\n\n // Update debug log with success or error based on status\n await this.debugService.updateLog(log.id, {\n responsePayload: JSON.stringify(result),\n status: result.status === 'error' ? 'error' : 'success',\n errorMessage: result.error,\n durationMs: Date.now() - startTime,\n });\n\n return result;\n } catch (error) {\n // Update debug log with error\n await this.debugService.updateLog(log.id, {\n status: 'error',\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n durationMs: Date.now() - startTime,\n });\n throw error;\n }\n }\n\n /**\n * Internal method to get MCP server status with real validation\n */\n private async getStatusInternal(id: string) {\n const server = await this.findById(id);\n const builtinId = this.getBuiltinId(server.config);\n\n // Check if it's a builtin server\n const isBuiltin = builtinId && this.registry.has(builtinId);\n\n // Check API key config\n const hasApiKey = !!server.apiKeyConfig;\n\n // Check OAuth token\n const hasOAuth = !!server.oauthToken;\n\n // Get metadata\n const metadata = isBuiltin ? this.registry.get(builtinId)?.metadata : null;\n const requiresApiKey = metadata?.requiresApiKey ?? false;\n const requiresOAuth = metadata?.requiresOAuth ?? false;\n\n // Default status\n let status: 'connected' | 'error' | 'unknown' = 'unknown';\n let validationError: string | undefined;\n let validationDetails: string | undefined;\n\n // For builtin servers with API key, actually validate\n if (isBuiltin && hasApiKey) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n try {\n const instance = pkg.createServer(apiKeyConfig);\n const validation = await instance.validate();\n status = validation.valid ? 'connected' : 'error';\n validationError = validation.error;\n validationDetails = validation.valid\n ? 'API key validated successfully'\n : `Validation failed: ${validation.error}`;\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection test failed: ${validationError}`;\n }\n }\n } else if (!hasApiKey && requiresApiKey) {\n status = 'error';\n validationError = 'API key required';\n validationDetails = 'This server requires an API key to function';\n } else if (isBuiltin && !requiresApiKey) {\n status = 'connected';\n validationDetails = 'Server ready (no API key required)';\n }\n\n // For remote_http servers, validate by connecting\n let oauthRequired = false;\n if (server.type === 'remote_http' && status === 'unknown') {\n const config = this.parseConfig(server.config) as { url: string; headers?: Record<string, string> };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n // Get OAuth token if available, mapping Prisma types to core OAuthToken\n const oauthToken = server.oauthToken\n ? {\n id: server.oauthToken.id,\n mcpServerId: server.oauthToken.mcpServerId,\n accessToken: server.oauthToken.accessToken,\n tokenType: server.oauthToken.tokenType,\n refreshToken: server.oauthToken.refreshToken ?? undefined,\n scope: server.oauthToken.scope ?? undefined,\n expiresAt: server.oauthToken.expiresAt?.getTime(),\n createdAt: server.oauthToken.createdAt.getTime(),\n updatedAt: server.oauthToken.updatedAt.getTime(),\n }\n : null;\n\n try {\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http', headers: config.headers },\n oauthToken,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully. ${tools.length} tools available.`;\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n if (validationError.includes('OAUTH_REQUIRED')) {\n oauthRequired = true;\n validationDetails = 'OAuth authentication required. Click \"Login with OAuth\" to authorize.';\n } else {\n validationDetails = `Connection failed: ${validationError}`;\n }\n }\n }\n\n // For remote_sse servers, validate by connecting\n if (server.type === 'remote_sse' && status === 'unknown') {\n const config = this.parseConfig(server.config) as { url: string; headers?: Record<string, string> };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n // Get OAuth token if available, mapping Prisma types to core OAuthToken\n const oauthToken = server.oauthToken\n ? {\n id: server.oauthToken.id,\n mcpServerId: server.oauthToken.mcpServerId,\n accessToken: server.oauthToken.accessToken,\n tokenType: server.oauthToken.tokenType,\n refreshToken: server.oauthToken.refreshToken ?? undefined,\n scope: server.oauthToken.scope ?? undefined,\n expiresAt: server.oauthToken.expiresAt?.getTime(),\n createdAt: server.oauthToken.createdAt.getTime(),\n updatedAt: server.oauthToken.updatedAt.getTime(),\n }\n : null;\n\n try {\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse', headers: config.headers },\n oauthToken,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully (SSE). ${tools.length} tools available.`;\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n if (validationError.includes('OAUTH_REQUIRED')) {\n oauthRequired = true;\n validationDetails = 'OAuth authentication required. Click \"Login with OAuth\" to authorize.';\n } else {\n validationDetails = `Connection failed: ${validationError}`;\n }\n }\n }\n\n // For external (NPX/stdio) servers, validate by spawning and checking\n if (server.type === 'external' && status === 'unknown') {\n const config = this.parseConfig(server.config) as {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n workingDirectory?: string;\n };\n\n if (!config.command) {\n status = 'error';\n validationError = 'Command is required for external MCP servers';\n validationDetails = 'Missing command configuration';\n } else {\n try {\n const externalServer = new ExternalMcpServer(config);\n await externalServer.initialize();\n const tools = await externalServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully (stdio). ${tools.length} tools available.`;\n await externalServer.shutdown();\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection failed: ${validationError}`;\n }\n }\n }\n\n const isReady = status === 'connected';\n\n return {\n id: server.id,\n name: server.name,\n type: server.type,\n isBuiltin,\n hasApiKey,\n hasOAuth,\n requiresApiKey,\n requiresOAuth,\n oauthRequired,\n isReady,\n status,\n error: validationError,\n details: validationDetails,\n validatedAt: new Date().toISOString(),\n };\n }\n\n /**\n * Add a preset MCP server to the user's organization\n */\n async addPreset(presetId: string, userId: string, orgId: string) {\n const preset = MCP_PRESETS.find((p) => p.id === presetId);\n if (!preset) {\n throw new NotFoundException(`Preset \"${presetId}\" not found`);\n }\n\n // Check if a server with this name already exists in the org\n const existing = await this.prisma.mcpServer.findFirst({\n where: {\n name: preset.name,\n organizationId: orgId,\n },\n });\n\n if (existing) {\n throw new ConflictException(\n `MCP server \"${preset.name}\" already exists in your organization`\n );\n }\n\n return this.prisma.mcpServer.create({\n data: {\n name: preset.name,\n type: preset.type,\n config: JSON.stringify(preset.config),\n userId,\n organizationId: orgId,\n },\n });\n }\n\n /**\n * Extract builtinId from config (handles JSON string parsing)\n */\n private getBuiltinId(config: unknown): string | null {\n const parsed = this.parseConfig(config);\n const builtinId = parsed?.builtinId;\n\n if (typeof builtinId === 'string') {\n return builtinId;\n }\n return null;\n }\n\n private parseConfig(config: unknown): Record<string, unknown> | null {\n if (typeof config === 'string') {\n try {\n return JSON.parse(config);\n } catch {\n return null;\n }\n }\n return config as Record<string, unknown> | null;\n }\n}\n"],"names":["ExternalMcpServer","RemoteHttpMcpServer","RemoteSseMcpServer","ConflictException","ForbiddenException","Injectable","NotFoundException","UNAUTHENTICATED_ID","PrismaService","DebugService","MCP_PRESETS","McpRegistry","McpService","prisma","registry","debugService","isAnonymous","userId","assertAccess","serverId","orgId","server","mcpServer","findUnique","where","id","select","organizationId","assertOwnership","findAll","include","profiles","profile","orderBy","name","servers","findMany","OR","map","builtinId","getBuiltinId","config","has","metadata","get","findById","tools","oauthToken","toolsCache","create","dto","data","type","JSON","stringify","apiKeyConfig","oauthConfig","update","undefined","delete","getTools","startTime","Date","now","log","createLog","mcpServerId","requestType","requestPayload","method","status","result","getToolsInternal","updateLog","responsePayload","durationMs","error","errorMessage","Error","message","pkg","parse","instance","createServer","initialize","listTools","parseConfig","remoteServer","url","transport","headers","externalServer","shutdown","mcpServerToolsCache","getStatus","getStatusInternal","isBuiltin","hasApiKey","hasOAuth","requiresApiKey","requiresOAuth","validationError","validationDetails","validation","validate","valid","oauthRequired","accessToken","tokenType","refreshToken","scope","expiresAt","getTime","createdAt","updatedAt","length","includes","command","isReady","details","validatedAt","toISOString","addPreset","presetId","preset","find","p","existing","findFirst","parsed"],"mappings":"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"}
@@ -16,12 +16,61 @@ function _ts_param(paramIndex, decorator) {
16
16
  * OAuth Controller
17
17
  *
18
18
  * REST API endpoints for OAuth token management.
19
- */ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post } from "@nestjs/common";
19
+ */ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Logger, Param, Post, Query, Req, Res } from "@nestjs/common";
20
20
  import { SkipOrgCheck } from "../auth/decorators/skip-org-check.decorator.js";
21
21
  import { OAuthService } from "./oauth.service.js";
22
22
  export class OAuthController {
23
23
  constructor(oauthService){
24
24
  this.oauthService = oauthService;
25
+ this.logger = new Logger(OAuthController.name);
26
+ }
27
+ /**
28
+ * Start OAuth auto-discovery flow for an MCP server.
29
+ * Discovers OAuth endpoints via RFC 9728/8414, performs DCR if needed,
30
+ * and redirects the browser to the authorization server.
31
+ */ async authorize(serverId, req, res) {
32
+ try {
33
+ const callbackUrl = `${req.protocol}://${req.get('host')}/api/oauth/callback`;
34
+ const authorizationUrl = await this.oauthService.discoverAndAuthorize(serverId, callbackUrl);
35
+ res.redirect(authorizationUrl);
36
+ } catch (error) {
37
+ const message = error instanceof Error ? error.message : 'Unknown error';
38
+ this.logger.error(`OAuth authorize failed for ${serverId}: ${message}`);
39
+ res.status(400).send(this.renderCallbackPage(false, message));
40
+ }
41
+ }
42
+ /**
43
+ * OAuth callback endpoint. Receives authorization code, exchanges for tokens,
44
+ * and returns an HTML page that notifies the opener window.
45
+ */ async callback(code, state, req, res) {
46
+ if (!code || !state) {
47
+ res.status(400).send(this.renderCallbackPage(false, 'Missing code or state parameter'));
48
+ return;
49
+ }
50
+ const serverId = state;
51
+ const callbackUrl = `${req.protocol}://${req.get('host')}/api/oauth/callback`;
52
+ const result = await this.oauthService.handleCallback(serverId, code, callbackUrl);
53
+ res.status(200).send(this.renderCallbackPage(result.success, result.error));
54
+ }
55
+ /**
56
+ * Render an HTML page that posts a message to the opener window and closes itself.
57
+ */ renderCallbackPage(success, error) {
58
+ const message = JSON.stringify({
59
+ type: 'oauth-callback',
60
+ success,
61
+ error: error || null
62
+ });
63
+ return `<!DOCTYPE html>
64
+ <html><head><title>OAuth ${success ? 'Success' : 'Error'}</title></head>
65
+ <body>
66
+ <p>${success ? 'Authorization successful! This window will close.' : `Error: ${error}`}</p>
67
+ <script>
68
+ if (window.opener) {
69
+ window.opener.postMessage(${message}, '*');
70
+ }
71
+ setTimeout(function() { window.close(); }, 2000);
72
+ </script>
73
+ </body></html>`;
25
74
  }
26
75
  /**
27
76
  * Start OAuth flow for an MCP server
@@ -57,6 +106,34 @@ export class OAuthController {
57
106
  await this.oauthService.deleteToken(serverId);
58
107
  }
59
108
  }
109
+ _ts_decorate([
110
+ Get('authorize/:serverId'),
111
+ _ts_param(0, Param('serverId')),
112
+ _ts_param(1, Req()),
113
+ _ts_param(2, Res()),
114
+ _ts_metadata("design:type", Function),
115
+ _ts_metadata("design:paramtypes", [
116
+ String,
117
+ typeof Request === "undefined" ? Object : Request,
118
+ typeof Response === "undefined" ? Object : Response
119
+ ]),
120
+ _ts_metadata("design:returntype", Promise)
121
+ ], OAuthController.prototype, "authorize", null);
122
+ _ts_decorate([
123
+ Get('callback'),
124
+ _ts_param(0, Query('code')),
125
+ _ts_param(1, Query('state')),
126
+ _ts_param(2, Req()),
127
+ _ts_param(3, Res()),
128
+ _ts_metadata("design:type", Function),
129
+ _ts_metadata("design:paramtypes", [
130
+ String,
131
+ String,
132
+ typeof Request === "undefined" ? Object : Request,
133
+ typeof Response === "undefined" ? Object : Response
134
+ ]),
135
+ _ts_metadata("design:returntype", Promise)
136
+ ], OAuthController.prototype, "callback", null);
60
137
  _ts_decorate([
61
138
  Post('start'),
62
139
  _ts_param(0, Body()),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/modules/oauth/oauth.controller.ts"],"sourcesContent":["/**\n * OAuth Controller\n *\n * REST API endpoints for OAuth token management.\n */\n\nimport { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post } from '@nestjs/common';\nimport { SkipOrgCheck } from '../auth/decorators/skip-org-check.decorator.js';\nimport { OAuthService } from './oauth.service.js';\n\ninterface StartOAuthDto {\n mcpServerId: string;\n authorizationServerUrl: string;\n clientId: string;\n scopes?: string[];\n redirectUri: string;\n}\n\ninterface CompleteOAuthDto {\n mcpServerId: string;\n code: string;\n codeVerifier: string;\n tokenEndpoint: string;\n clientId: string;\n redirectUri: string;\n}\n\ninterface StoreTokenDto {\n accessToken: string;\n refreshToken?: string | null;\n tokenType?: string;\n scope?: string | null;\n expiresIn?: number;\n}\n\n@SkipOrgCheck()\n@Controller('oauth')\nexport class OAuthController {\n constructor(private readonly oauthService: OAuthService) {}\n\n /**\n * Start OAuth flow for an MCP server\n */\n @Post('start')\n async startOAuthFlow(@Body() dto: StartOAuthDto) {\n return this.oauthService.startOAuthFlow(dto);\n }\n\n /**\n * Complete OAuth flow with authorization code\n */\n @Post('complete')\n async completeOAuthFlow(@Body() dto: CompleteOAuthDto) {\n return this.oauthService.completeOAuthFlow(dto);\n }\n\n /**\n * Manually store a token for an MCP server\n */\n @Post('servers/:serverId/token')\n @HttpCode(HttpStatus.CREATED)\n async storeToken(@Param('serverId') serverId: string, @Body() dto: StoreTokenDto) {\n const expiresAt = dto.expiresIn ? new Date(Date.now() + dto.expiresIn * 1000) : null;\n\n return this.oauthService.storeToken({\n mcpServerId: serverId,\n accessToken: dto.accessToken,\n refreshToken: dto.refreshToken,\n tokenType: dto.tokenType || 'Bearer',\n scope: dto.scope,\n expiresAt,\n });\n }\n\n /**\n * Get OAuth token for an MCP server\n */\n @Get('servers/:serverId/token')\n async getToken(@Param('serverId') serverId: string) {\n return this.oauthService.getToken(serverId);\n }\n\n /**\n * Delete OAuth token for an MCP server\n */\n @Delete('servers/:serverId/token')\n @HttpCode(HttpStatus.NO_CONTENT)\n async deleteToken(@Param('serverId') serverId: string) {\n await this.oauthService.deleteToken(serverId);\n }\n}\n"],"names":["Body","Controller","Delete","Get","HttpCode","HttpStatus","Param","Post","SkipOrgCheck","OAuthService","OAuthController","oauthService","startOAuthFlow","dto","completeOAuthFlow","storeToken","serverId","expiresAt","expiresIn","Date","now","mcpServerId","accessToken","refreshToken","tokenType","scope","getToken","deleteToken","CREATED","NO_CONTENT"],"mappings":";;;;;;;;;;;;;;AAAA;;;;CAIC,GAED,SAASA,IAAI,EAAEC,UAAU,EAAEC,MAAM,EAAEC,GAAG,EAAEC,QAAQ,EAAEC,UAAU,EAAEC,KAAK,EAAEC,IAAI,QAAQ,iBAAiB;AAClG,SAASC,YAAY,QAAQ,iDAAiD;AAC9E,SAASC,YAAY,QAAQ,qBAAqB;AA6BlD,OAAO,MAAMC;IACX,YAAY,AAAiBC,YAA0B,CAAE;aAA5BA,eAAAA;IAA6B;IAE1D;;GAEC,GACD,MACMC,eAAe,AAAQC,GAAkB,EAAE;QAC/C,OAAO,IAAI,CAACF,YAAY,CAACC,cAAc,CAACC;IAC1C;IAEA;;GAEC,GACD,MACMC,kBAAkB,AAAQD,GAAqB,EAAE;QACrD,OAAO,IAAI,CAACF,YAAY,CAACG,iBAAiB,CAACD;IAC7C;IAEA;;GAEC,GACD,MAEME,WAAW,AAAmBC,QAAgB,EAAE,AAAQH,GAAkB,EAAE;QAChF,MAAMI,YAAYJ,IAAIK,SAAS,GAAG,IAAIC,KAAKA,KAAKC,GAAG,KAAKP,IAAIK,SAAS,GAAG,QAAQ;QAEhF,OAAO,IAAI,CAACP,YAAY,CAACI,UAAU,CAAC;YAClCM,aAAaL;YACbM,aAAaT,IAAIS,WAAW;YAC5BC,cAAcV,IAAIU,YAAY;YAC9BC,WAAWX,IAAIW,SAAS,IAAI;YAC5BC,OAAOZ,IAAIY,KAAK;YAChBR;QACF;IACF;IAEA;;GAEC,GACD,MACMS,SAAS,AAAmBV,QAAgB,EAAE;QAClD,OAAO,IAAI,CAACL,YAAY,CAACe,QAAQ,CAACV;IACpC;IAEA;;GAEC,GACD,MAEMW,YAAY,AAAmBX,QAAgB,EAAE;QACrD,MAAM,IAAI,CAACL,YAAY,CAACgB,WAAW,CAACX;IACtC;AACF;;;;;;;;;;;;;;;;;;;;;wBA9BuBY;;;;;;;;;;;;;;;;;;;;;wBA0BAC"}
1
+ {"version":3,"sources":["../../../src/modules/oauth/oauth.controller.ts"],"sourcesContent":["/**\n * OAuth Controller\n *\n * REST API endpoints for OAuth token management.\n */\n\nimport {\n Body,\n Controller,\n Delete,\n Get,\n HttpCode,\n HttpStatus,\n Logger,\n Param,\n Post,\n Query,\n Req,\n Res,\n} from '@nestjs/common';\nimport type { Request, Response } from 'express';\nimport { SkipOrgCheck } from '../auth/decorators/skip-org-check.decorator.js';\nimport { OAuthService } from './oauth.service.js';\n\ninterface StartOAuthDto {\n mcpServerId: string;\n authorizationServerUrl: string;\n clientId: string;\n scopes?: string[];\n redirectUri: string;\n}\n\ninterface CompleteOAuthDto {\n mcpServerId: string;\n code: string;\n codeVerifier: string;\n tokenEndpoint: string;\n clientId: string;\n redirectUri: string;\n}\n\ninterface StoreTokenDto {\n accessToken: string;\n refreshToken?: string | null;\n tokenType?: string;\n scope?: string | null;\n expiresIn?: number;\n}\n\n@SkipOrgCheck()\n@Controller('oauth')\nexport class OAuthController {\n private readonly logger = new Logger(OAuthController.name);\n\n constructor(private readonly oauthService: OAuthService) {}\n\n /**\n * Start OAuth auto-discovery flow for an MCP server.\n * Discovers OAuth endpoints via RFC 9728/8414, performs DCR if needed,\n * and redirects the browser to the authorization server.\n */\n @Get('authorize/:serverId')\n async authorize(\n @Param('serverId') serverId: string,\n @Req() req: Request,\n @Res() res: Response\n ) {\n try {\n const callbackUrl = `${req.protocol}://${req.get('host')}/api/oauth/callback`;\n const authorizationUrl = await this.oauthService.discoverAndAuthorize(serverId, callbackUrl);\n res.redirect(authorizationUrl);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n this.logger.error(`OAuth authorize failed for ${serverId}: ${message}`);\n res.status(400).send(this.renderCallbackPage(false, message));\n }\n }\n\n /**\n * OAuth callback endpoint. Receives authorization code, exchanges for tokens,\n * and returns an HTML page that notifies the opener window.\n */\n @Get('callback')\n async callback(\n @Query('code') code: string,\n @Query('state') state: string,\n @Req() req: Request,\n @Res() res: Response\n ) {\n if (!code || !state) {\n res.status(400).send(this.renderCallbackPage(false, 'Missing code or state parameter'));\n return;\n }\n\n const serverId = state;\n const callbackUrl = `${req.protocol}://${req.get('host')}/api/oauth/callback`;\n\n const result = await this.oauthService.handleCallback(serverId, code, callbackUrl);\n res.status(200).send(this.renderCallbackPage(result.success, result.error));\n }\n\n /**\n * Render an HTML page that posts a message to the opener window and closes itself.\n */\n private renderCallbackPage(success: boolean, error?: string): string {\n const message = JSON.stringify({\n type: 'oauth-callback',\n success,\n error: error || null,\n });\n return `<!DOCTYPE html>\n<html><head><title>OAuth ${success ? 'Success' : 'Error'}</title></head>\n<body>\n<p>${success ? 'Authorization successful! This window will close.' : `Error: ${error}`}</p>\n<script>\n if (window.opener) {\n window.opener.postMessage(${message}, '*');\n }\n setTimeout(function() { window.close(); }, 2000);\n</script>\n</body></html>`;\n }\n\n /**\n * Start OAuth flow for an MCP server\n */\n @Post('start')\n async startOAuthFlow(@Body() dto: StartOAuthDto) {\n return this.oauthService.startOAuthFlow(dto);\n }\n\n /**\n * Complete OAuth flow with authorization code\n */\n @Post('complete')\n async completeOAuthFlow(@Body() dto: CompleteOAuthDto) {\n return this.oauthService.completeOAuthFlow(dto);\n }\n\n /**\n * Manually store a token for an MCP server\n */\n @Post('servers/:serverId/token')\n @HttpCode(HttpStatus.CREATED)\n async storeToken(@Param('serverId') serverId: string, @Body() dto: StoreTokenDto) {\n const expiresAt = dto.expiresIn ? new Date(Date.now() + dto.expiresIn * 1000) : null;\n\n return this.oauthService.storeToken({\n mcpServerId: serverId,\n accessToken: dto.accessToken,\n refreshToken: dto.refreshToken,\n tokenType: dto.tokenType || 'Bearer',\n scope: dto.scope,\n expiresAt,\n });\n }\n\n /**\n * Get OAuth token for an MCP server\n */\n @Get('servers/:serverId/token')\n async getToken(@Param('serverId') serverId: string) {\n return this.oauthService.getToken(serverId);\n }\n\n /**\n * Delete OAuth token for an MCP server\n */\n @Delete('servers/:serverId/token')\n @HttpCode(HttpStatus.NO_CONTENT)\n async deleteToken(@Param('serverId') serverId: string) {\n await this.oauthService.deleteToken(serverId);\n }\n}\n"],"names":["Body","Controller","Delete","Get","HttpCode","HttpStatus","Logger","Param","Post","Query","Req","Res","SkipOrgCheck","OAuthService","OAuthController","oauthService","logger","name","authorize","serverId","req","res","callbackUrl","protocol","get","authorizationUrl","discoverAndAuthorize","redirect","error","message","Error","status","send","renderCallbackPage","callback","code","state","result","handleCallback","success","JSON","stringify","type","startOAuthFlow","dto","completeOAuthFlow","storeToken","expiresAt","expiresIn","Date","now","mcpServerId","accessToken","refreshToken","tokenType","scope","getToken","deleteToken","CREATED","NO_CONTENT"],"mappings":"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"}
@@ -11,13 +11,16 @@ function _ts_metadata(k, v) {
11
11
  * OAuth Service
12
12
  *
13
13
  * Manages OAuth tokens for MCP servers.
14
- */ import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
14
+ */ import { OAuthDiscoveryService } from "@dxheroes/local-mcp-core";
15
+ import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
15
16
  import { PrismaService } from "../database/prisma.service.js";
16
17
  export class OAuthService {
17
18
  constructor(prisma){
18
19
  this.prisma = prisma;
20
+ this.logger = new Logger(OAuthService.name);
19
21
  // In-memory storage for PKCE verifiers (in production, use Redis or similar)
20
22
  this.pkceVerifiers = new Map();
23
+ this.discoveryService = new OAuthDiscoveryService();
21
24
  }
22
25
  /**
23
26
  * Generate PKCE challenge and store verifier
@@ -151,6 +154,199 @@ export class OAuthService {
151
154
  });
152
155
  }
153
156
  /**
157
+ * Discover OAuth configuration and build an authorization URL for a server.
158
+ * Uses RFC 9728 / 8414 / 7591 auto-discovery and optional DCR.
159
+ * Returns an authorization URL the browser should be redirected to.
160
+ */ async discoverAndAuthorize(serverId, callbackUrl) {
161
+ const server = await this.prisma.mcpServer.findUnique({
162
+ where: {
163
+ id: serverId
164
+ }
165
+ });
166
+ if (!server) {
167
+ throw new NotFoundException(`MCP server ${serverId} not found`);
168
+ }
169
+ // Parse server URL from config
170
+ const config = typeof server.config === 'string' ? JSON.parse(server.config) : server.config;
171
+ const serverUrl = config?.url;
172
+ if (!serverUrl) {
173
+ throw new BadRequestException('Server has no URL configured');
174
+ }
175
+ // Parse existing oauthConfig if any (may have manual clientId)
176
+ const existingOAuthConfig = server.oauthConfig ? typeof server.oauthConfig === 'string' ? JSON.parse(server.oauthConfig) : server.oauthConfig : null;
177
+ // Step 1: Discover OAuth endpoints via RFC 9728 + 8414
178
+ this.logger.log(`Discovering OAuth for server ${serverId} at ${serverUrl}`);
179
+ const discovery = await this.discoveryService.discoverFromServerUrl(serverUrl);
180
+ // Step 2: Get or register client
181
+ let clientId;
182
+ if (existingOAuthConfig?.clientId) {
183
+ // Use manually configured clientId
184
+ clientId = existingOAuthConfig.clientId;
185
+ } else {
186
+ // Check for existing DCR registration
187
+ const existingReg = await this.prisma.oAuthClientRegistration.findUnique({
188
+ where: {
189
+ mcpServerId_authorizationServerUrl: {
190
+ mcpServerId: serverId,
191
+ authorizationServerUrl: discovery.authorizationServerUrl
192
+ }
193
+ }
194
+ });
195
+ if (existingReg) {
196
+ // Delete stale registration — redirect_uri may have changed
197
+ await this.prisma.oAuthClientRegistration.delete({
198
+ where: {
199
+ id: existingReg.id
200
+ }
201
+ });
202
+ this.logger.log(`Deleted stale DCR registration for ${serverId}, will re-register`);
203
+ }
204
+ if (discovery.registrationEndpoint) {
205
+ // Perform Dynamic Client Registration (RFC 7591)
206
+ this.logger.log(`Performing DCR at ${discovery.registrationEndpoint}`);
207
+ const registration = await this.discoveryService.registerClient(discovery.registrationEndpoint, callbackUrl, discovery.scopes);
208
+ clientId = registration.clientId;
209
+ // Store registration
210
+ await this.prisma.oAuthClientRegistration.create({
211
+ data: {
212
+ mcpServerId: serverId,
213
+ authorizationServerUrl: discovery.authorizationServerUrl,
214
+ clientId: registration.clientId,
215
+ clientSecret: registration.clientSecret,
216
+ registrationAccessToken: registration.registrationAccessToken
217
+ }
218
+ });
219
+ } else {
220
+ throw new BadRequestException('No clientId configured and server does not support Dynamic Client Registration. ' + 'Please configure a clientId manually in the OAuth settings.');
221
+ }
222
+ }
223
+ // Step 3: Store discovery result in oauthConfig on the server
224
+ await this.prisma.mcpServer.update({
225
+ where: {
226
+ id: serverId
227
+ },
228
+ data: {
229
+ oauthConfig: JSON.stringify({
230
+ authorizationServerUrl: discovery.authorizationServerUrl,
231
+ authorizationEndpoint: discovery.authorizationEndpoint,
232
+ tokenEndpoint: discovery.tokenEndpoint,
233
+ registrationEndpoint: discovery.registrationEndpoint,
234
+ resource: discovery.resource,
235
+ scopes: discovery.scopes,
236
+ clientId,
237
+ requiresOAuth: true
238
+ })
239
+ }
240
+ });
241
+ // Step 4: Generate PKCE
242
+ const codeVerifier = this.generateCodeVerifier();
243
+ const codeChallenge = await this.generateCodeChallenge(codeVerifier);
244
+ // Store verifier (expires in 10 minutes)
245
+ this.pkceVerifiers.set(serverId, {
246
+ verifier: codeVerifier,
247
+ expiresAt: Date.now() + 10 * 60 * 1000
248
+ });
249
+ // Step 5: Build authorization URL
250
+ const params = new URLSearchParams({
251
+ response_type: 'code',
252
+ client_id: clientId,
253
+ redirect_uri: callbackUrl,
254
+ code_challenge: codeChallenge,
255
+ code_challenge_method: 'S256',
256
+ state: serverId
257
+ });
258
+ if (discovery.scopes.length > 0) {
259
+ params.set('scope', discovery.scopes.join(' '));
260
+ }
261
+ if (discovery.resource) {
262
+ params.set('resource', discovery.resource);
263
+ }
264
+ return `${discovery.authorizationEndpoint}?${params.toString()}`;
265
+ }
266
+ /**
267
+ * Handle OAuth callback: exchange authorization code for tokens.
268
+ */ async handleCallback(serverId, code, callbackUrl) {
269
+ const server = await this.prisma.mcpServer.findUnique({
270
+ where: {
271
+ id: serverId
272
+ }
273
+ });
274
+ if (!server) {
275
+ return {
276
+ success: false,
277
+ error: `MCP server ${serverId} not found`
278
+ };
279
+ }
280
+ // Retrieve PKCE verifier
281
+ const pkceEntry = this.pkceVerifiers.get(serverId);
282
+ if (!pkceEntry) {
283
+ return {
284
+ success: false,
285
+ error: 'PKCE verifier not found or expired'
286
+ };
287
+ }
288
+ if (pkceEntry.expiresAt < Date.now()) {
289
+ this.pkceVerifiers.delete(serverId);
290
+ return {
291
+ success: false,
292
+ error: 'PKCE verifier expired'
293
+ };
294
+ }
295
+ const codeVerifier = pkceEntry.verifier;
296
+ this.pkceVerifiers.delete(serverId);
297
+ // Get oauthConfig for token endpoint and clientId
298
+ const oauthConfig = server.oauthConfig ? typeof server.oauthConfig === 'string' ? JSON.parse(server.oauthConfig) : server.oauthConfig : null;
299
+ if (!oauthConfig?.tokenEndpoint || !oauthConfig?.clientId) {
300
+ return {
301
+ success: false,
302
+ error: 'OAuth configuration incomplete (missing tokenEndpoint or clientId)'
303
+ };
304
+ }
305
+ try {
306
+ // Exchange code for tokens
307
+ const response = await fetch(oauthConfig.tokenEndpoint, {
308
+ method: 'POST',
309
+ headers: {
310
+ 'Content-Type': 'application/x-www-form-urlencoded'
311
+ },
312
+ body: new URLSearchParams({
313
+ grant_type: 'authorization_code',
314
+ code,
315
+ redirect_uri: callbackUrl,
316
+ client_id: oauthConfig.clientId,
317
+ code_verifier: codeVerifier
318
+ })
319
+ });
320
+ if (!response.ok) {
321
+ const errorText = await response.text();
322
+ return {
323
+ success: false,
324
+ error: `Token exchange failed: ${errorText}`
325
+ };
326
+ }
327
+ const tokenData = await response.json();
328
+ const expiresAt = tokenData.expires_in ? new Date(Date.now() + tokenData.expires_in * 1000) : null;
329
+ // Store token
330
+ await this.storeToken({
331
+ mcpServerId: serverId,
332
+ accessToken: tokenData.access_token,
333
+ refreshToken: tokenData.refresh_token,
334
+ tokenType: tokenData.token_type || 'Bearer',
335
+ scope: tokenData.scope,
336
+ expiresAt
337
+ });
338
+ return {
339
+ success: true
340
+ };
341
+ } catch (error) {
342
+ const message = error instanceof Error ? error.message : 'Unknown error';
343
+ return {
344
+ success: false,
345
+ error: message
346
+ };
347
+ }
348
+ }
349
+ /**
154
350
  * Generate a random code verifier for PKCE
155
351
  */ generateCodeVerifier() {
156
352
  const array = new Uint8Array(32);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/modules/oauth/oauth.service.ts"],"sourcesContent":["/**\n * OAuth Service\n *\n * Manages OAuth tokens for MCP servers.\n */\n\nimport { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\n\ninterface StartOAuthFlowDto {\n mcpServerId: string;\n authorizationServerUrl: string;\n clientId: string;\n scopes?: string[];\n redirectUri: string;\n}\n\ninterface CompleteOAuthFlowDto {\n mcpServerId: string;\n code: string;\n codeVerifier: string;\n tokenEndpoint: string;\n clientId: string;\n redirectUri: string;\n}\n\ninterface StoreTokenDto {\n mcpServerId: string;\n accessToken: string;\n refreshToken?: string | null;\n tokenType?: string;\n scope?: string | null;\n expiresAt?: Date | null;\n}\n\n@Injectable()\nexport class OAuthService {\n // In-memory storage for PKCE verifiers (in production, use Redis or similar)\n private pkceVerifiers = new Map<string, { verifier: string; expiresAt: number }>();\n\n constructor(private readonly prisma: PrismaService) {}\n\n /**\n * Generate PKCE challenge and store verifier\n */\n async startOAuthFlow(dto: StartOAuthFlowDto) {\n // Check server exists\n const server = await this.prisma.mcpServer.findUnique({ where: { id: dto.mcpServerId } });\n if (!server) {\n throw new NotFoundException(`MCP server ${dto.mcpServerId} not found`);\n }\n\n // Generate PKCE code verifier and challenge\n const codeVerifier = this.generateCodeVerifier();\n const codeChallenge = await this.generateCodeChallenge(codeVerifier);\n\n // Store verifier temporarily (expires in 10 minutes)\n const expiresAt = Date.now() + 10 * 60 * 1000;\n this.pkceVerifiers.set(dto.mcpServerId, { verifier: codeVerifier, expiresAt });\n\n // Build authorization URL\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: dto.clientId,\n redirect_uri: dto.redirectUri,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n });\n\n if (dto.scopes?.length) {\n params.set('scope', dto.scopes.join(' '));\n }\n\n const authorizationUrl = `${dto.authorizationServerUrl}?${params.toString()}`;\n\n return {\n authorizationUrl,\n codeVerifier, // Return for client-side storage if needed\n };\n }\n\n /**\n * Exchange authorization code for tokens\n */\n async completeOAuthFlow(dto: CompleteOAuthFlowDto) {\n // Check server exists\n const server = await this.prisma.mcpServer.findUnique({ where: { id: dto.mcpServerId } });\n if (!server) {\n throw new NotFoundException(`MCP server ${dto.mcpServerId} not found`);\n }\n\n // Exchange code for token\n const response = await fetch(dto.tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: dto.code,\n redirect_uri: dto.redirectUri,\n client_id: dto.clientId,\n code_verifier: dto.codeVerifier,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new BadRequestException(`Token exchange failed: ${error}`);\n }\n\n const tokenData = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n token_type?: string;\n scope?: string;\n expires_in?: number;\n };\n\n // Calculate expiry\n const expiresAt = tokenData.expires_in\n ? new Date(Date.now() + tokenData.expires_in * 1000)\n : null;\n\n // Store token\n return this.storeToken({\n mcpServerId: dto.mcpServerId,\n accessToken: tokenData.access_token,\n refreshToken: tokenData.refresh_token,\n tokenType: tokenData.token_type || 'Bearer',\n scope: tokenData.scope,\n expiresAt,\n });\n }\n\n /**\n * Store OAuth token for an MCP server\n */\n async storeToken(dto: StoreTokenDto) {\n return this.prisma.oAuthToken.upsert({\n where: { mcpServerId: dto.mcpServerId },\n update: {\n accessToken: dto.accessToken,\n refreshToken: dto.refreshToken,\n tokenType: dto.tokenType || 'Bearer',\n scope: dto.scope,\n expiresAt: dto.expiresAt,\n },\n create: {\n mcpServerId: dto.mcpServerId,\n accessToken: dto.accessToken,\n refreshToken: dto.refreshToken,\n tokenType: dto.tokenType || 'Bearer',\n scope: dto.scope,\n expiresAt: dto.expiresAt,\n },\n });\n }\n\n /**\n * Get OAuth token for an MCP server\n */\n async getToken(mcpServerId: string) {\n return this.prisma.oAuthToken.findUnique({\n where: { mcpServerId },\n });\n }\n\n /**\n * Delete OAuth token for an MCP server\n */\n async deleteToken(mcpServerId: string) {\n const token = await this.prisma.oAuthToken.findUnique({\n where: { mcpServerId },\n });\n\n if (!token) {\n throw new NotFoundException('OAuth token not found');\n }\n\n await this.prisma.oAuthToken.delete({\n where: { mcpServerId },\n });\n }\n\n /**\n * Generate a random code verifier for PKCE\n */\n private generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return this.base64UrlEncode(array);\n }\n\n /**\n * Generate code challenge from verifier using SHA-256\n */\n private async generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return this.base64UrlEncode(new Uint8Array(hash));\n }\n\n /**\n * Base64 URL encode\n */\n private base64UrlEncode(buffer: Uint8Array): string {\n const base64 = Buffer.from(buffer).toString('base64');\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n }\n}\n"],"names":["BadRequestException","Injectable","NotFoundException","PrismaService","OAuthService","prisma","pkceVerifiers","Map","startOAuthFlow","dto","server","mcpServer","findUnique","where","id","mcpServerId","codeVerifier","generateCodeVerifier","codeChallenge","generateCodeChallenge","expiresAt","Date","now","set","verifier","params","URLSearchParams","response_type","client_id","clientId","redirect_uri","redirectUri","code_challenge","code_challenge_method","scopes","length","join","authorizationUrl","authorizationServerUrl","toString","completeOAuthFlow","response","fetch","tokenEndpoint","method","headers","body","grant_type","code","code_verifier","ok","error","text","tokenData","json","expires_in","storeToken","accessToken","access_token","refreshToken","refresh_token","tokenType","token_type","scope","oAuthToken","upsert","update","create","getToken","deleteToken","token","delete","array","Uint8Array","crypto","getRandomValues","base64UrlEncode","encoder","TextEncoder","data","encode","hash","subtle","digest","buffer","base64","Buffer","from","replace"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/modules/oauth/oauth.service.ts"],"sourcesContent":["/**\n * OAuth Service\n *\n * Manages OAuth tokens for MCP servers.\n */\n\nimport { OAuthDiscoveryService } from '@dxheroes/local-mcp-core';\nimport { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\n\ninterface StartOAuthFlowDto {\n mcpServerId: string;\n authorizationServerUrl: string;\n clientId: string;\n scopes?: string[];\n redirectUri: string;\n}\n\ninterface CompleteOAuthFlowDto {\n mcpServerId: string;\n code: string;\n codeVerifier: string;\n tokenEndpoint: string;\n clientId: string;\n redirectUri: string;\n}\n\ninterface StoreTokenDto {\n mcpServerId: string;\n accessToken: string;\n refreshToken?: string | null;\n tokenType?: string;\n scope?: string | null;\n expiresAt?: Date | null;\n}\n\n@Injectable()\nexport class OAuthService {\n private readonly logger = new Logger(OAuthService.name);\n // In-memory storage for PKCE verifiers (in production, use Redis or similar)\n private pkceVerifiers = new Map<string, { verifier: string; expiresAt: number }>();\n private readonly discoveryService = new OAuthDiscoveryService();\n\n constructor(private readonly prisma: PrismaService) {}\n\n /**\n * Generate PKCE challenge and store verifier\n */\n async startOAuthFlow(dto: StartOAuthFlowDto) {\n // Check server exists\n const server = await this.prisma.mcpServer.findUnique({ where: { id: dto.mcpServerId } });\n if (!server) {\n throw new NotFoundException(`MCP server ${dto.mcpServerId} not found`);\n }\n\n // Generate PKCE code verifier and challenge\n const codeVerifier = this.generateCodeVerifier();\n const codeChallenge = await this.generateCodeChallenge(codeVerifier);\n\n // Store verifier temporarily (expires in 10 minutes)\n const expiresAt = Date.now() + 10 * 60 * 1000;\n this.pkceVerifiers.set(dto.mcpServerId, { verifier: codeVerifier, expiresAt });\n\n // Build authorization URL\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: dto.clientId,\n redirect_uri: dto.redirectUri,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n });\n\n if (dto.scopes?.length) {\n params.set('scope', dto.scopes.join(' '));\n }\n\n const authorizationUrl = `${dto.authorizationServerUrl}?${params.toString()}`;\n\n return {\n authorizationUrl,\n codeVerifier, // Return for client-side storage if needed\n };\n }\n\n /**\n * Exchange authorization code for tokens\n */\n async completeOAuthFlow(dto: CompleteOAuthFlowDto) {\n // Check server exists\n const server = await this.prisma.mcpServer.findUnique({ where: { id: dto.mcpServerId } });\n if (!server) {\n throw new NotFoundException(`MCP server ${dto.mcpServerId} not found`);\n }\n\n // Exchange code for token\n const response = await fetch(dto.tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: dto.code,\n redirect_uri: dto.redirectUri,\n client_id: dto.clientId,\n code_verifier: dto.codeVerifier,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new BadRequestException(`Token exchange failed: ${error}`);\n }\n\n const tokenData = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n token_type?: string;\n scope?: string;\n expires_in?: number;\n };\n\n // Calculate expiry\n const expiresAt = tokenData.expires_in\n ? new Date(Date.now() + tokenData.expires_in * 1000)\n : null;\n\n // Store token\n return this.storeToken({\n mcpServerId: dto.mcpServerId,\n accessToken: tokenData.access_token,\n refreshToken: tokenData.refresh_token,\n tokenType: tokenData.token_type || 'Bearer',\n scope: tokenData.scope,\n expiresAt,\n });\n }\n\n /**\n * Store OAuth token for an MCP server\n */\n async storeToken(dto: StoreTokenDto) {\n return this.prisma.oAuthToken.upsert({\n where: { mcpServerId: dto.mcpServerId },\n update: {\n accessToken: dto.accessToken,\n refreshToken: dto.refreshToken,\n tokenType: dto.tokenType || 'Bearer',\n scope: dto.scope,\n expiresAt: dto.expiresAt,\n },\n create: {\n mcpServerId: dto.mcpServerId,\n accessToken: dto.accessToken,\n refreshToken: dto.refreshToken,\n tokenType: dto.tokenType || 'Bearer',\n scope: dto.scope,\n expiresAt: dto.expiresAt,\n },\n });\n }\n\n /**\n * Get OAuth token for an MCP server\n */\n async getToken(mcpServerId: string) {\n return this.prisma.oAuthToken.findUnique({\n where: { mcpServerId },\n });\n }\n\n /**\n * Delete OAuth token for an MCP server\n */\n async deleteToken(mcpServerId: string) {\n const token = await this.prisma.oAuthToken.findUnique({\n where: { mcpServerId },\n });\n\n if (!token) {\n throw new NotFoundException('OAuth token not found');\n }\n\n await this.prisma.oAuthToken.delete({\n where: { mcpServerId },\n });\n }\n\n /**\n * Discover OAuth configuration and build an authorization URL for a server.\n * Uses RFC 9728 / 8414 / 7591 auto-discovery and optional DCR.\n * Returns an authorization URL the browser should be redirected to.\n */\n async discoverAndAuthorize(serverId: string, callbackUrl: string): Promise<string> {\n const server = await this.prisma.mcpServer.findUnique({ where: { id: serverId } });\n if (!server) {\n throw new NotFoundException(`MCP server ${serverId} not found`);\n }\n\n // Parse server URL from config\n const config = typeof server.config === 'string' ? JSON.parse(server.config) : server.config;\n const serverUrl = config?.url as string | undefined;\n if (!serverUrl) {\n throw new BadRequestException('Server has no URL configured');\n }\n\n // Parse existing oauthConfig if any (may have manual clientId)\n const existingOAuthConfig = server.oauthConfig\n ? typeof server.oauthConfig === 'string'\n ? JSON.parse(server.oauthConfig)\n : server.oauthConfig\n : null;\n\n // Step 1: Discover OAuth endpoints via RFC 9728 + 8414\n this.logger.log(`Discovering OAuth for server ${serverId} at ${serverUrl}`);\n const discovery = await this.discoveryService.discoverFromServerUrl(serverUrl);\n\n // Step 2: Get or register client\n let clientId: string;\n\n if (existingOAuthConfig?.clientId) {\n // Use manually configured clientId\n clientId = existingOAuthConfig.clientId;\n } else {\n // Check for existing DCR registration\n const existingReg = await this.prisma.oAuthClientRegistration.findUnique({\n where: {\n mcpServerId_authorizationServerUrl: {\n mcpServerId: serverId,\n authorizationServerUrl: discovery.authorizationServerUrl,\n },\n },\n });\n\n if (existingReg) {\n // Delete stale registration — redirect_uri may have changed\n await this.prisma.oAuthClientRegistration.delete({\n where: { id: existingReg.id },\n });\n this.logger.log(`Deleted stale DCR registration for ${serverId}, will re-register`);\n }\n\n if (discovery.registrationEndpoint) {\n // Perform Dynamic Client Registration (RFC 7591)\n this.logger.log(`Performing DCR at ${discovery.registrationEndpoint}`);\n const registration = await this.discoveryService.registerClient(\n discovery.registrationEndpoint,\n callbackUrl,\n discovery.scopes\n );\n clientId = registration.clientId;\n\n // Store registration\n await this.prisma.oAuthClientRegistration.create({\n data: {\n mcpServerId: serverId,\n authorizationServerUrl: discovery.authorizationServerUrl,\n clientId: registration.clientId,\n clientSecret: registration.clientSecret,\n registrationAccessToken: registration.registrationAccessToken,\n },\n });\n } else {\n throw new BadRequestException(\n 'No clientId configured and server does not support Dynamic Client Registration. ' +\n 'Please configure a clientId manually in the OAuth settings.'\n );\n }\n }\n\n // Step 3: Store discovery result in oauthConfig on the server\n await this.prisma.mcpServer.update({\n where: { id: serverId },\n data: {\n oauthConfig: JSON.stringify({\n authorizationServerUrl: discovery.authorizationServerUrl,\n authorizationEndpoint: discovery.authorizationEndpoint,\n tokenEndpoint: discovery.tokenEndpoint,\n registrationEndpoint: discovery.registrationEndpoint,\n resource: discovery.resource,\n scopes: discovery.scopes,\n clientId,\n requiresOAuth: true,\n }),\n },\n });\n\n // Step 4: Generate PKCE\n const codeVerifier = this.generateCodeVerifier();\n const codeChallenge = await this.generateCodeChallenge(codeVerifier);\n\n // Store verifier (expires in 10 minutes)\n this.pkceVerifiers.set(serverId, {\n verifier: codeVerifier,\n expiresAt: Date.now() + 10 * 60 * 1000,\n });\n\n // Step 5: Build authorization URL\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: clientId,\n redirect_uri: callbackUrl,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n state: serverId, // Use serverId as state for callback routing\n });\n\n if (discovery.scopes.length > 0) {\n params.set('scope', discovery.scopes.join(' '));\n }\n\n if (discovery.resource) {\n params.set('resource', discovery.resource);\n }\n\n return `${discovery.authorizationEndpoint}?${params.toString()}`;\n }\n\n /**\n * Handle OAuth callback: exchange authorization code for tokens.\n */\n async handleCallback(\n serverId: string,\n code: string,\n callbackUrl: string\n ): Promise<{ success: boolean; error?: string }> {\n const server = await this.prisma.mcpServer.findUnique({ where: { id: serverId } });\n if (!server) {\n return { success: false, error: `MCP server ${serverId} not found` };\n }\n\n // Retrieve PKCE verifier\n const pkceEntry = this.pkceVerifiers.get(serverId);\n if (!pkceEntry) {\n return { success: false, error: 'PKCE verifier not found or expired' };\n }\n if (pkceEntry.expiresAt < Date.now()) {\n this.pkceVerifiers.delete(serverId);\n return { success: false, error: 'PKCE verifier expired' };\n }\n const codeVerifier = pkceEntry.verifier;\n this.pkceVerifiers.delete(serverId);\n\n // Get oauthConfig for token endpoint and clientId\n const oauthConfig = server.oauthConfig\n ? typeof server.oauthConfig === 'string'\n ? JSON.parse(server.oauthConfig)\n : server.oauthConfig\n : null;\n\n if (!oauthConfig?.tokenEndpoint || !oauthConfig?.clientId) {\n return { success: false, error: 'OAuth configuration incomplete (missing tokenEndpoint or clientId)' };\n }\n\n try {\n // Exchange code for tokens\n const response = await fetch(oauthConfig.tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: callbackUrl,\n client_id: oauthConfig.clientId,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n return { success: false, error: `Token exchange failed: ${errorText}` };\n }\n\n const tokenData = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n token_type?: string;\n scope?: string;\n expires_in?: number;\n };\n\n const expiresAt = tokenData.expires_in\n ? new Date(Date.now() + tokenData.expires_in * 1000)\n : null;\n\n // Store token\n await this.storeToken({\n mcpServerId: serverId,\n accessToken: tokenData.access_token,\n refreshToken: tokenData.refresh_token,\n tokenType: tokenData.token_type || 'Bearer',\n scope: tokenData.scope,\n expiresAt,\n });\n\n return { success: true };\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n return { success: false, error: message };\n }\n }\n\n /**\n * Generate a random code verifier for PKCE\n */\n private generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return this.base64UrlEncode(array);\n }\n\n /**\n * Generate code challenge from verifier using SHA-256\n */\n private async generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return this.base64UrlEncode(new Uint8Array(hash));\n }\n\n /**\n * Base64 URL encode\n */\n private base64UrlEncode(buffer: Uint8Array): string {\n const base64 = Buffer.from(buffer).toString('base64');\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n }\n}\n"],"names":["OAuthDiscoveryService","BadRequestException","Injectable","Logger","NotFoundException","PrismaService","OAuthService","prisma","logger","name","pkceVerifiers","Map","discoveryService","startOAuthFlow","dto","server","mcpServer","findUnique","where","id","mcpServerId","codeVerifier","generateCodeVerifier","codeChallenge","generateCodeChallenge","expiresAt","Date","now","set","verifier","params","URLSearchParams","response_type","client_id","clientId","redirect_uri","redirectUri","code_challenge","code_challenge_method","scopes","length","join","authorizationUrl","authorizationServerUrl","toString","completeOAuthFlow","response","fetch","tokenEndpoint","method","headers","body","grant_type","code","code_verifier","ok","error","text","tokenData","json","expires_in","storeToken","accessToken","access_token","refreshToken","refresh_token","tokenType","token_type","scope","oAuthToken","upsert","update","create","getToken","deleteToken","token","delete","discoverAndAuthorize","serverId","callbackUrl","config","JSON","parse","serverUrl","url","existingOAuthConfig","oauthConfig","log","discovery","discoverFromServerUrl","existingReg","oAuthClientRegistration","mcpServerId_authorizationServerUrl","registrationEndpoint","registration","registerClient","data","clientSecret","registrationAccessToken","stringify","authorizationEndpoint","resource","requiresOAuth","state","handleCallback","success","pkceEntry","get","errorText","message","Error","array","Uint8Array","crypto","getRandomValues","base64UrlEncode","encoder","TextEncoder","encode","hash","subtle","digest","buffer","base64","Buffer","from","replace"],"mappings":"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"}