@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
@@ -253,7 +253,8 @@ export class McpService {
253
253
  const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig) : null;
254
254
  const remoteServer = new RemoteHttpMcpServer({
255
255
  url: config.url,
256
- transport: 'http'
256
+ transport: 'http',
257
+ headers: config.headers
257
258
  }, null, apiKeyConfig);
258
259
  await remoteServer.initialize();
259
260
  const tools = await remoteServer.listTools();
@@ -267,7 +268,8 @@ export class McpService {
267
268
  const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig) : null;
268
269
  const remoteServer = new RemoteSseMcpServer({
269
270
  url: config.url,
270
- transport: 'sse'
271
+ transport: 'sse',
272
+ headers: config.headers
271
273
  }, null, apiKeyConfig);
272
274
  await remoteServer.initialize();
273
275
  const tools = await remoteServer.listTools();
@@ -382,14 +384,28 @@ export class McpService {
382
384
  validationDetails = 'Server ready (no API key required)';
383
385
  }
384
386
  // For remote_http servers, validate by connecting
387
+ let oauthRequired = false;
385
388
  if (server.type === 'remote_http' && status === 'unknown') {
386
389
  const config = this.parseConfig(server.config);
387
390
  const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig) : null;
391
+ // Get OAuth token if available, mapping Prisma types to core OAuthToken
392
+ const oauthToken = server.oauthToken ? {
393
+ id: server.oauthToken.id,
394
+ mcpServerId: server.oauthToken.mcpServerId,
395
+ accessToken: server.oauthToken.accessToken,
396
+ tokenType: server.oauthToken.tokenType,
397
+ refreshToken: server.oauthToken.refreshToken ?? undefined,
398
+ scope: server.oauthToken.scope ?? undefined,
399
+ expiresAt: server.oauthToken.expiresAt?.getTime(),
400
+ createdAt: server.oauthToken.createdAt.getTime(),
401
+ updatedAt: server.oauthToken.updatedAt.getTime()
402
+ } : null;
388
403
  try {
389
404
  const remoteServer = new RemoteHttpMcpServer({
390
405
  url: config.url,
391
- transport: 'http'
392
- }, null, apiKeyConfig);
406
+ transport: 'http',
407
+ headers: config.headers
408
+ }, oauthToken, apiKeyConfig);
393
409
  await remoteServer.initialize();
394
410
  const tools = await remoteServer.listTools();
395
411
  status = 'connected';
@@ -397,18 +413,36 @@ export class McpService {
397
413
  } catch (error) {
398
414
  status = 'error';
399
415
  validationError = error instanceof Error ? error.message : 'Unknown error';
400
- validationDetails = `Connection failed: ${validationError}`;
416
+ if (validationError.includes('OAUTH_REQUIRED')) {
417
+ oauthRequired = true;
418
+ validationDetails = 'OAuth authentication required. Click "Login with OAuth" to authorize.';
419
+ } else {
420
+ validationDetails = `Connection failed: ${validationError}`;
421
+ }
401
422
  }
402
423
  }
403
424
  // For remote_sse servers, validate by connecting
404
425
  if (server.type === 'remote_sse' && status === 'unknown') {
405
426
  const config = this.parseConfig(server.config);
406
427
  const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig) : null;
428
+ // Get OAuth token if available, mapping Prisma types to core OAuthToken
429
+ const oauthToken = server.oauthToken ? {
430
+ id: server.oauthToken.id,
431
+ mcpServerId: server.oauthToken.mcpServerId,
432
+ accessToken: server.oauthToken.accessToken,
433
+ tokenType: server.oauthToken.tokenType,
434
+ refreshToken: server.oauthToken.refreshToken ?? undefined,
435
+ scope: server.oauthToken.scope ?? undefined,
436
+ expiresAt: server.oauthToken.expiresAt?.getTime(),
437
+ createdAt: server.oauthToken.createdAt.getTime(),
438
+ updatedAt: server.oauthToken.updatedAt.getTime()
439
+ } : null;
407
440
  try {
408
441
  const remoteServer = new RemoteSseMcpServer({
409
442
  url: config.url,
410
- transport: 'sse'
411
- }, null, apiKeyConfig);
443
+ transport: 'sse',
444
+ headers: config.headers
445
+ }, oauthToken, apiKeyConfig);
412
446
  await remoteServer.initialize();
413
447
  const tools = await remoteServer.listTools();
414
448
  status = 'connected';
@@ -416,7 +450,12 @@ export class McpService {
416
450
  } catch (error) {
417
451
  status = 'error';
418
452
  validationError = error instanceof Error ? error.message : 'Unknown error';
419
- validationDetails = `Connection failed: ${validationError}`;
453
+ if (validationError.includes('OAUTH_REQUIRED')) {
454
+ oauthRequired = true;
455
+ validationDetails = 'OAuth authentication required. Click "Login with OAuth" to authorize.';
456
+ } else {
457
+ validationDetails = `Connection failed: ${validationError}`;
458
+ }
420
459
  }
421
460
  }
422
461
  // For external (NPX/stdio) servers, validate by spawning and checking
@@ -451,6 +490,7 @@ export class McpService {
451
490
  hasOAuth,
452
491
  requiresApiKey,
453
492
  requiresOAuth,
493
+ oauthRequired,
454
494
  isReady,
455
495
  status,
456
496
  error: validationError,
@@ -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);