@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
@@ -20,38 +20,24 @@ function _ts_param(paramIndex, decorator) {
20
20
  * Profile endpoints use org slug: /api/mcp/:orgSlug/:profileName
21
21
  *
22
22
  * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http
23
- */ import { Body, Controller, Get, HttpCode, HttpStatus, NotFoundException, Param, Post, Req, Res, UnauthorizedException } from "@nestjs/common";
23
+ */ import { Body, Controller, Get, HttpCode, HttpStatus, NotFoundException, Param, Post, Req, Res, UseGuards } from "@nestjs/common";
24
24
  import { EventEmitter2 } from "@nestjs/event-emitter";
25
25
  import { fromEvent, map } from "rxjs";
26
- import { AuthService } from "../auth/auth.service.js";
27
26
  import { Public } from "../auth/decorators/public.decorator.js";
27
+ import { McpOAuthGuard } from "../auth/mcp-oauth.guard.js";
28
28
  import { GATEWAY_PROFILE_CHANGED, SettingsService } from "../settings/settings.service.js";
29
29
  import { ProxyService } from "./proxy.service.js";
30
30
  export class ProxyController {
31
- constructor(proxyService, settingsService, eventEmitter, authService){
31
+ constructor(proxyService, settingsService, eventEmitter){
32
32
  this.proxyService = proxyService;
33
33
  this.settingsService = settingsService;
34
34
  this.eventEmitter = eventEmitter;
35
- this.authService = authService;
36
35
  }
37
36
  /**
38
- * Resolve user from Bearer token (MCP OAuth).
39
- * When no token is provided, returns unauthenticated sentinel so
40
- * system profiles (organizationId=null) still work for unauthenticated MCP clients.
41
- */ async resolveUser(req) {
42
- const authHeader = req.headers.authorization;
43
- if (!authHeader?.startsWith('Bearer ')) {
44
- // No token — unauthenticated MCP client, can only access system profiles
45
- return {
46
- id: '__unauthenticated__'
47
- };
48
- }
49
- const token = authHeader.slice(7);
50
- const user = await this.authService.validateMcpToken(token);
51
- if (!user) {
52
- throw new UnauthorizedException('Invalid or expired MCP OAuth token');
53
- }
54
- return user;
37
+ * Resolve user from request.
38
+ * McpOAuthGuard always validates and attaches user before this is called.
39
+ */ resolveUser(req) {
40
+ return req.user;
55
41
  }
56
42
  // =========================================
57
43
  // Gateway Endpoints (must come BEFORE parameterized routes)
@@ -69,7 +55,7 @@ export class ProxyController {
69
55
  /**
70
56
  * Get gateway info
71
57
  */ async getGatewayInfo(req) {
72
- const user = await this.resolveUser(req);
58
+ const user = this.resolveUser(req);
73
59
  const profileName = await this.settingsService.getDefaultGatewayProfile();
74
60
  try {
75
61
  const info = await this.proxyService.getProfileInfo(profileName, user.id);
@@ -93,7 +79,7 @@ export class ProxyController {
93
79
  if (acceptHeader.includes('text/event-stream')) {
94
80
  return this.streamGatewayEvents(req, res);
95
81
  }
96
- const user = await this.resolveUser(req);
82
+ const user = this.resolveUser(req);
97
83
  const profileName = await this.settingsService.getDefaultGatewayProfile();
98
84
  try {
99
85
  const info = await this.proxyService.getProfileInfo(profileName, user.id);
@@ -152,7 +138,7 @@ export class ProxyController {
152
138
  /**
153
139
  * MCP JSON-RPC endpoint for the gateway
154
140
  */ async handleGatewayRequest(req, request) {
155
- const user = await this.resolveUser(req);
141
+ const user = this.resolveUser(req);
156
142
  const profileName = await this.settingsService.getDefaultGatewayProfile();
157
143
  try {
158
144
  return await this.proxyService.handleRequest(profileName, request, user.id);
@@ -164,6 +150,82 @@ export class ProxyController {
164
150
  }
165
151
  }
166
152
  // =========================================
153
+ // Org-scoped Gateway Endpoints: /api/mcp/:orgSlug/gateway
154
+ // (must come BEFORE :orgSlug/:profileName to avoid "gateway" matching :profileName)
155
+ // =========================================
156
+ /**
157
+ * SSE endpoint for org-scoped gateway
158
+ */ async streamOrgGatewaySse(orgSlug, req, res) {
159
+ const profileName = await this.settingsService.getDefaultGatewayProfile();
160
+ await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);
161
+ return this.streamGatewayEvents(req, res);
162
+ }
163
+ /**
164
+ * POST handler for org-scoped gateway SSE
165
+ */ async handleOrgGatewaySseRequest(req, orgSlug, request) {
166
+ const user = this.resolveUser(req);
167
+ const profileName = await this.settingsService.getDefaultGatewayProfile();
168
+ return this.proxyService.handleRequestByOrgSlug(profileName, orgSlug, request, user.id);
169
+ }
170
+ /**
171
+ * Get org-scoped gateway info
172
+ */ async getOrgGatewayInfo(orgSlug) {
173
+ const profileName = await this.settingsService.getDefaultGatewayProfile();
174
+ const info = await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);
175
+ return {
176
+ ...info,
177
+ gateway: {
178
+ activeProfile: profileName
179
+ }
180
+ };
181
+ }
182
+ /**
183
+ * GET handler for org-scoped gateway endpoint
184
+ */ async getOrgGatewayEndpoint(orgSlug, req, res) {
185
+ const acceptHeader = req.headers.accept || '';
186
+ if (acceptHeader.includes('text/event-stream')) {
187
+ const profileName = await this.settingsService.getDefaultGatewayProfile();
188
+ await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);
189
+ return this.streamGatewayEvents(req, res);
190
+ }
191
+ const profileName = await this.settingsService.getDefaultGatewayProfile();
192
+ const info = await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);
193
+ return res.json({
194
+ message: 'This is the MCP Gateway endpoint. Use POST for JSON-RPC requests.',
195
+ usage: {
196
+ method: 'POST',
197
+ contentType: 'application/json',
198
+ body: {
199
+ jsonrpc: '2.0',
200
+ method: 'tools/list',
201
+ id: 1
202
+ }
203
+ },
204
+ endpoints: {
205
+ sse: `/api/mcp/${orgSlug}/gateway/sse`,
206
+ http: `/api/mcp/${orgSlug}/gateway`
207
+ },
208
+ gateway: {
209
+ activeProfile: profileName,
210
+ settingsEndpoint: '/api/settings/default-gateway-profile'
211
+ },
212
+ profile: {
213
+ name: profileName,
214
+ toolCount: info.tools.length,
215
+ serverCount: info.serverStatus.total,
216
+ connectedServers: info.serverStatus.connected
217
+ },
218
+ infoEndpoint: `/api/mcp/${orgSlug}/gateway/info`
219
+ });
220
+ }
221
+ /**
222
+ * MCP JSON-RPC endpoint for org-scoped gateway
223
+ */ async handleOrgGatewayRequest(req, orgSlug, request) {
224
+ const user = this.resolveUser(req);
225
+ const profileName = await this.settingsService.getDefaultGatewayProfile();
226
+ return this.proxyService.handleRequestByOrgSlug(profileName, orgSlug, request, user.id);
227
+ }
228
+ // =========================================
167
229
  // Org-scoped Profile Endpoints: /api/mcp/:orgSlug/:profileName
168
230
  // =========================================
169
231
  /**
@@ -217,7 +279,7 @@ export class ProxyController {
217
279
  /**
218
280
  * MCP JSON-RPC endpoint for an org-scoped profile
219
281
  */ async handleOrgMcpRequest(req, orgSlug, profileName, request) {
220
- const user = await this.resolveUser(req);
282
+ const user = this.resolveUser(req);
221
283
  return this.proxyService.handleRequestByOrgSlug(profileName, orgSlug, request, user.id);
222
284
  }
223
285
  }
@@ -276,6 +338,69 @@ _ts_decorate([
276
338
  ]),
277
339
  _ts_metadata("design:returntype", Promise)
278
340
  ], ProxyController.prototype, "handleGatewayRequest", null);
341
+ _ts_decorate([
342
+ Get(':orgSlug/gateway/sse'),
343
+ _ts_param(0, Param('orgSlug')),
344
+ _ts_param(1, Req()),
345
+ _ts_param(2, Res()),
346
+ _ts_metadata("design:type", Function),
347
+ _ts_metadata("design:paramtypes", [
348
+ String,
349
+ typeof Request === "undefined" ? Object : Request,
350
+ typeof Response === "undefined" ? Object : Response
351
+ ]),
352
+ _ts_metadata("design:returntype", Promise)
353
+ ], ProxyController.prototype, "streamOrgGatewaySse", null);
354
+ _ts_decorate([
355
+ Post(':orgSlug/gateway/sse'),
356
+ HttpCode(HttpStatus.OK),
357
+ _ts_param(0, Req()),
358
+ _ts_param(1, Param('orgSlug')),
359
+ _ts_param(2, Body()),
360
+ _ts_metadata("design:type", Function),
361
+ _ts_metadata("design:paramtypes", [
362
+ typeof Request === "undefined" ? Object : Request,
363
+ String,
364
+ typeof McpRequest === "undefined" ? Object : McpRequest
365
+ ]),
366
+ _ts_metadata("design:returntype", Promise)
367
+ ], ProxyController.prototype, "handleOrgGatewaySseRequest", null);
368
+ _ts_decorate([
369
+ Get(':orgSlug/gateway/info'),
370
+ _ts_param(0, Param('orgSlug')),
371
+ _ts_metadata("design:type", Function),
372
+ _ts_metadata("design:paramtypes", [
373
+ String
374
+ ]),
375
+ _ts_metadata("design:returntype", Promise)
376
+ ], ProxyController.prototype, "getOrgGatewayInfo", null);
377
+ _ts_decorate([
378
+ Get(':orgSlug/gateway'),
379
+ _ts_param(0, Param('orgSlug')),
380
+ _ts_param(1, Req()),
381
+ _ts_param(2, Res()),
382
+ _ts_metadata("design:type", Function),
383
+ _ts_metadata("design:paramtypes", [
384
+ String,
385
+ typeof Request === "undefined" ? Object : Request,
386
+ typeof Response === "undefined" ? Object : Response
387
+ ]),
388
+ _ts_metadata("design:returntype", Promise)
389
+ ], ProxyController.prototype, "getOrgGatewayEndpoint", null);
390
+ _ts_decorate([
391
+ Post(':orgSlug/gateway'),
392
+ HttpCode(HttpStatus.OK),
393
+ _ts_param(0, Req()),
394
+ _ts_param(1, Param('orgSlug')),
395
+ _ts_param(2, Body()),
396
+ _ts_metadata("design:type", Function),
397
+ _ts_metadata("design:paramtypes", [
398
+ typeof Request === "undefined" ? Object : Request,
399
+ String,
400
+ typeof McpRequest === "undefined" ? Object : McpRequest
401
+ ]),
402
+ _ts_metadata("design:returntype", Promise)
403
+ ], ProxyController.prototype, "handleOrgGatewayRequest", null);
279
404
  _ts_decorate([
280
405
  Get(':orgSlug/:profileName/sse'),
281
406
  _ts_param(0, Param('orgSlug')),
@@ -351,13 +476,13 @@ _ts_decorate([
351
476
  ], ProxyController.prototype, "handleOrgMcpRequest", null);
352
477
  ProxyController = _ts_decorate([
353
478
  Public(),
479
+ UseGuards(McpOAuthGuard),
354
480
  Controller('mcp'),
355
481
  _ts_metadata("design:type", Function),
356
482
  _ts_metadata("design:paramtypes", [
357
483
  typeof ProxyService === "undefined" ? Object : ProxyService,
358
484
  typeof SettingsService === "undefined" ? Object : SettingsService,
359
- typeof EventEmitter2 === "undefined" ? Object : EventEmitter2,
360
- typeof AuthService === "undefined" ? Object : AuthService
485
+ typeof EventEmitter2 === "undefined" ? Object : EventEmitter2
361
486
  ])
362
487
  ], ProxyController);
363
488
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/modules/proxy/proxy.controller.ts"],"sourcesContent":["/**\n * Proxy Controller\n *\n * MCP proxy endpoints for profiles and gateway.\n * Supports MCP Streamable HTTP transport (2025-11-25 spec) with SSE notifications.\n * Profile endpoints use org slug: /api/mcp/:orgSlug/:profileName\n *\n * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http\n */\n\nimport {\n Body,\n Controller,\n Get,\n HttpCode,\n HttpStatus,\n NotFoundException,\n Param,\n Post,\n Req,\n Res,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { EventEmitter2 } from '@nestjs/event-emitter';\nimport type { Request, Response } from 'express';\nimport { fromEvent, map } from 'rxjs';\nimport { AuthService } from '../auth/auth.service.js';\nimport { Public } from '../auth/decorators/public.decorator.js';\nimport { GATEWAY_PROFILE_CHANGED, SettingsService } from '../settings/settings.service.js';\nimport type { McpRequest, McpResponse } from './proxy.service.js';\nimport { ProxyService } from './proxy.service.js';\n\n@Public()\n@Controller('mcp')\nexport class ProxyController {\n constructor(\n private readonly proxyService: ProxyService,\n private readonly settingsService: SettingsService,\n private readonly eventEmitter: EventEmitter2,\n private readonly authService: AuthService\n ) {}\n\n /**\n * Resolve user from Bearer token (MCP OAuth).\n * When no token is provided, returns unauthenticated sentinel so\n * system profiles (organizationId=null) still work for unauthenticated MCP clients.\n */\n private async resolveUser(req: Request): Promise<{ id: string }> {\n const authHeader = req.headers.authorization;\n if (!authHeader?.startsWith('Bearer ')) {\n // No token — unauthenticated MCP client, can only access system profiles\n return { id: '__unauthenticated__' };\n }\n\n const token = authHeader.slice(7);\n const user = await this.authService.validateMcpToken(token);\n if (!user) {\n throw new UnauthorizedException('Invalid or expired MCP OAuth token');\n }\n\n return user;\n }\n\n // =========================================\n // Gateway Endpoints (must come BEFORE parameterized routes)\n // =========================================\n\n /**\n * SSE endpoint for gateway notifications (dedicated URL)\n */\n @Get('gateway/sse')\n streamGatewaySse(@Req() req: Request, @Res() res: Response) {\n return this.streamGatewayEvents(req, res);\n }\n\n /**\n * POST handler for gateway SSE\n */\n @Post('gateway/sse')\n @HttpCode(HttpStatus.OK)\n async handleGatewaySseRequest(\n @Req() req: Request,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n return this.handleGatewayRequest(req, request);\n }\n\n /**\n * Get gateway info\n */\n @Get('gateway/info')\n async getGatewayInfo(@Req() req: Request) {\n const user = await this.resolveUser(req);\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n\n try {\n const info = await this.proxyService.getProfileInfo(profileName, user.id);\n return {\n ...info,\n gateway: {\n activeProfile: profileName,\n },\n };\n } catch (error) {\n if (error instanceof NotFoundException) {\n throw new NotFoundException(\n `Default gateway profile \"${profileName}\" not found. Please select a valid profile in settings.`\n );\n }\n throw error;\n }\n }\n\n /**\n * GET handler for gateway endpoint\n */\n @Get('gateway')\n async getGatewayEndpoint(@Req() req: Request, @Res() res: Response) {\n const acceptHeader = req.headers.accept || '';\n if (acceptHeader.includes('text/event-stream')) {\n return this.streamGatewayEvents(req, res);\n }\n\n const user = await this.resolveUser(req);\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n\n try {\n const info = await this.proxyService.getProfileInfo(profileName, user.id);\n return res.json({\n message: 'This is the MCP Gateway endpoint. Use POST for JSON-RPC requests.',\n usage: {\n method: 'POST',\n contentType: 'application/json',\n body: { jsonrpc: '2.0', method: 'tools/list', id: 1 },\n },\n endpoints: {\n sse: '/api/mcp/gateway/sse',\n http: '/api/mcp/gateway',\n },\n gateway: {\n activeProfile: profileName,\n settingsEndpoint: '/api/settings/default-gateway-profile',\n },\n profile: {\n name: profileName,\n toolCount: info.tools.length,\n serverCount: info.serverStatus.total,\n connectedServers: info.serverStatus.connected,\n },\n infoEndpoint: '/api/mcp/gateway/info',\n });\n } catch (error) {\n if (error instanceof NotFoundException) {\n throw new NotFoundException(\n `Default gateway profile \"${profileName}\" not found. Please select a valid profile in settings.`\n );\n }\n throw error;\n }\n }\n\n /**\n * Stream SSE events for notifications.\n */\n private streamGatewayEvents(req: Request, res: Response) {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n\n res.write(': connected\\n\\n');\n\n const subscription = fromEvent(this.eventEmitter, GATEWAY_PROFILE_CHANGED)\n .pipe(\n map(() => ({\n jsonrpc: '2.0',\n method: 'notifications/tools/list_changed',\n }))\n )\n .subscribe((notification) => {\n res.write(`data: ${JSON.stringify(notification)}\\n\\n`);\n });\n\n req.on('close', () => {\n subscription.unsubscribe();\n });\n }\n\n /**\n * MCP JSON-RPC endpoint for the gateway\n */\n @Post('gateway')\n @HttpCode(HttpStatus.OK)\n async handleGatewayRequest(\n @Req() req: Request,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n const user = await this.resolveUser(req);\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n\n try {\n return await this.proxyService.handleRequest(profileName, request, user.id);\n } catch (error) {\n if (error instanceof NotFoundException) {\n throw new NotFoundException(\n `Default gateway profile \"${profileName}\" not found. Please select a valid profile in settings.`\n );\n }\n throw error;\n }\n }\n\n // =========================================\n // Org-scoped Profile Endpoints: /api/mcp/:orgSlug/:profileName\n // =========================================\n\n /**\n * SSE endpoint for org-scoped profile\n */\n @Get(':orgSlug/:profileName/sse')\n async streamOrgProfileSse(\n @Param('orgSlug') orgSlug: string,\n @Param('profileName') profileName: string,\n @Req() req: Request,\n @Res() res: Response\n ) {\n await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);\n return this.streamGatewayEvents(req, res);\n }\n\n /**\n * POST handler for org-scoped profile SSE\n */\n @Post(':orgSlug/:profileName/sse')\n @HttpCode(HttpStatus.OK)\n async handleOrgProfileSseRequest(\n @Req() req: Request,\n @Param('orgSlug') orgSlug: string,\n @Param('profileName') profileName: string,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n return this.handleOrgMcpRequest(req, orgSlug, profileName, request);\n }\n\n /**\n * Get org-scoped profile info\n */\n @Get(':orgSlug/:profileName/info')\n async getOrgProfileInfo(\n @Param('orgSlug') orgSlug: string,\n @Param('profileName') profileName: string\n ) {\n return this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);\n }\n\n /**\n * GET handler for org-scoped MCP endpoint\n */\n @Get(':orgSlug/:profileName')\n async getOrgMcpEndpoint(\n @Param('orgSlug') orgSlug: string,\n @Param('profileName') profileName: string,\n @Req() req: Request,\n @Res() res: Response\n ) {\n const info = await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);\n\n const acceptHeader = req.headers.accept || '';\n if (acceptHeader.includes('text/event-stream')) {\n return this.streamGatewayEvents(req, res);\n }\n\n return res.json({\n message: 'This is an MCP (Model Context Protocol) endpoint. Use POST for JSON-RPC requests.',\n usage: {\n method: 'POST',\n contentType: 'application/json',\n body: { jsonrpc: '2.0', method: 'tools/list', id: 1 },\n },\n endpoints: {\n sse: `/api/mcp/${orgSlug}/${profileName}/sse`,\n http: `/api/mcp/${orgSlug}/${profileName}`,\n },\n profile: {\n name: profileName,\n toolCount: info.tools.length,\n serverCount: info.serverStatus.total,\n connectedServers: info.serverStatus.connected,\n },\n infoEndpoint: `/api/mcp/${orgSlug}/${profileName}/info`,\n });\n }\n\n /**\n * MCP JSON-RPC endpoint for an org-scoped profile\n */\n @Post(':orgSlug/:profileName')\n @HttpCode(HttpStatus.OK)\n async handleOrgMcpRequest(\n @Req() req: Request,\n @Param('orgSlug') orgSlug: string,\n @Param('profileName') profileName: string,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n const user = await this.resolveUser(req);\n return this.proxyService.handleRequestByOrgSlug(profileName, orgSlug, request, user.id);\n }\n}\n"],"names":["Body","Controller","Get","HttpCode","HttpStatus","NotFoundException","Param","Post","Req","Res","UnauthorizedException","EventEmitter2","fromEvent","map","AuthService","Public","GATEWAY_PROFILE_CHANGED","SettingsService","ProxyService","ProxyController","proxyService","settingsService","eventEmitter","authService","resolveUser","req","authHeader","headers","authorization","startsWith","id","token","slice","user","validateMcpToken","streamGatewaySse","res","streamGatewayEvents","handleGatewaySseRequest","request","handleGatewayRequest","getGatewayInfo","profileName","getDefaultGatewayProfile","info","getProfileInfo","gateway","activeProfile","error","getGatewayEndpoint","acceptHeader","accept","includes","json","message","usage","method","contentType","body","jsonrpc","endpoints","sse","http","settingsEndpoint","profile","name","toolCount","tools","length","serverCount","serverStatus","total","connectedServers","connected","infoEndpoint","setHeader","write","subscription","pipe","subscribe","notification","JSON","stringify","on","unsubscribe","handleRequest","streamOrgProfileSse","orgSlug","getProfileInfoByOrgSlug","handleOrgProfileSseRequest","handleOrgMcpRequest","getOrgProfileInfo","getOrgMcpEndpoint","handleRequestByOrgSlug","OK"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/modules/proxy/proxy.controller.ts"],"sourcesContent":["/**\n * Proxy Controller\n *\n * MCP proxy endpoints for profiles and gateway.\n * Supports MCP Streamable HTTP transport (2025-11-25 spec) with SSE notifications.\n * Profile endpoints use org slug: /api/mcp/:orgSlug/:profileName\n *\n * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http\n */\n\nimport {\n Body,\n Controller,\n Get,\n HttpCode,\n HttpStatus,\n NotFoundException,\n Param,\n Post,\n Req,\n Res,\n UseGuards,\n} from '@nestjs/common';\nimport { EventEmitter2 } from '@nestjs/event-emitter';\nimport type { Request, Response } from 'express';\nimport { fromEvent, map } from 'rxjs';\nimport { Public } from '../auth/decorators/public.decorator.js';\nimport { McpOAuthGuard } from '../auth/mcp-oauth.guard.js';\nimport { GATEWAY_PROFILE_CHANGED, SettingsService } from '../settings/settings.service.js';\nimport type { McpRequest, McpResponse } from './proxy.service.js';\nimport { ProxyService } from './proxy.service.js';\n\n@Public()\n@UseGuards(McpOAuthGuard)\n@Controller('mcp')\nexport class ProxyController {\n constructor(\n private readonly proxyService: ProxyService,\n private readonly settingsService: SettingsService,\n private readonly eventEmitter: EventEmitter2\n ) {}\n\n /**\n * Resolve user from request.\n * McpOAuthGuard always validates and attaches user before this is called.\n */\n private resolveUser(req: Request): { id: string } {\n return req.user!;\n }\n\n // =========================================\n // Gateway Endpoints (must come BEFORE parameterized routes)\n // =========================================\n\n /**\n * SSE endpoint for gateway notifications (dedicated URL)\n */\n @Get('gateway/sse')\n streamGatewaySse(@Req() req: Request, @Res() res: Response) {\n return this.streamGatewayEvents(req, res);\n }\n\n /**\n * POST handler for gateway SSE\n */\n @Post('gateway/sse')\n @HttpCode(HttpStatus.OK)\n async handleGatewaySseRequest(\n @Req() req: Request,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n return this.handleGatewayRequest(req, request);\n }\n\n /**\n * Get gateway info\n */\n @Get('gateway/info')\n async getGatewayInfo(@Req() req: Request) {\n const user = this.resolveUser(req);\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n\n try {\n const info = await this.proxyService.getProfileInfo(profileName, user.id);\n return {\n ...info,\n gateway: {\n activeProfile: profileName,\n },\n };\n } catch (error) {\n if (error instanceof NotFoundException) {\n throw new NotFoundException(\n `Default gateway profile \"${profileName}\" not found. Please select a valid profile in settings.`\n );\n }\n throw error;\n }\n }\n\n /**\n * GET handler for gateway endpoint\n */\n @Get('gateway')\n async getGatewayEndpoint(@Req() req: Request, @Res() res: Response) {\n const acceptHeader = req.headers.accept || '';\n if (acceptHeader.includes('text/event-stream')) {\n return this.streamGatewayEvents(req, res);\n }\n\n const user = this.resolveUser(req);\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n\n try {\n const info = await this.proxyService.getProfileInfo(profileName, user.id);\n return res.json({\n message: 'This is the MCP Gateway endpoint. Use POST for JSON-RPC requests.',\n usage: {\n method: 'POST',\n contentType: 'application/json',\n body: { jsonrpc: '2.0', method: 'tools/list', id: 1 },\n },\n endpoints: {\n sse: '/api/mcp/gateway/sse',\n http: '/api/mcp/gateway',\n },\n gateway: {\n activeProfile: profileName,\n settingsEndpoint: '/api/settings/default-gateway-profile',\n },\n profile: {\n name: profileName,\n toolCount: info.tools.length,\n serverCount: info.serverStatus.total,\n connectedServers: info.serverStatus.connected,\n },\n infoEndpoint: '/api/mcp/gateway/info',\n });\n } catch (error) {\n if (error instanceof NotFoundException) {\n throw new NotFoundException(\n `Default gateway profile \"${profileName}\" not found. Please select a valid profile in settings.`\n );\n }\n throw error;\n }\n }\n\n /**\n * Stream SSE events for notifications.\n */\n private streamGatewayEvents(req: Request, res: Response) {\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n\n res.write(': connected\\n\\n');\n\n const subscription = fromEvent(this.eventEmitter, GATEWAY_PROFILE_CHANGED)\n .pipe(\n map(() => ({\n jsonrpc: '2.0',\n method: 'notifications/tools/list_changed',\n }))\n )\n .subscribe((notification) => {\n res.write(`data: ${JSON.stringify(notification)}\\n\\n`);\n });\n\n req.on('close', () => {\n subscription.unsubscribe();\n });\n }\n\n /**\n * MCP JSON-RPC endpoint for the gateway\n */\n @Post('gateway')\n @HttpCode(HttpStatus.OK)\n async handleGatewayRequest(\n @Req() req: Request,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n const user = this.resolveUser(req);\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n\n try {\n return await this.proxyService.handleRequest(profileName, request, user.id);\n } catch (error) {\n if (error instanceof NotFoundException) {\n throw new NotFoundException(\n `Default gateway profile \"${profileName}\" not found. Please select a valid profile in settings.`\n );\n }\n throw error;\n }\n }\n\n // =========================================\n // Org-scoped Gateway Endpoints: /api/mcp/:orgSlug/gateway\n // (must come BEFORE :orgSlug/:profileName to avoid \"gateway\" matching :profileName)\n // =========================================\n\n /**\n * SSE endpoint for org-scoped gateway\n */\n @Get(':orgSlug/gateway/sse')\n async streamOrgGatewaySse(\n @Param('orgSlug') orgSlug: string,\n @Req() req: Request,\n @Res() res: Response\n ) {\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);\n return this.streamGatewayEvents(req, res);\n }\n\n /**\n * POST handler for org-scoped gateway SSE\n */\n @Post(':orgSlug/gateway/sse')\n @HttpCode(HttpStatus.OK)\n async handleOrgGatewaySseRequest(\n @Req() req: Request,\n @Param('orgSlug') orgSlug: string,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n const user = this.resolveUser(req);\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n return this.proxyService.handleRequestByOrgSlug(profileName, orgSlug, request, user.id);\n }\n\n /**\n * Get org-scoped gateway info\n */\n @Get(':orgSlug/gateway/info')\n async getOrgGatewayInfo(@Param('orgSlug') orgSlug: string) {\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n const info = await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);\n return {\n ...info,\n gateway: {\n activeProfile: profileName,\n },\n };\n }\n\n /**\n * GET handler for org-scoped gateway endpoint\n */\n @Get(':orgSlug/gateway')\n async getOrgGatewayEndpoint(\n @Param('orgSlug') orgSlug: string,\n @Req() req: Request,\n @Res() res: Response\n ) {\n const acceptHeader = req.headers.accept || '';\n if (acceptHeader.includes('text/event-stream')) {\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);\n return this.streamGatewayEvents(req, res);\n }\n\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n const info = await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);\n\n return res.json({\n message: 'This is the MCP Gateway endpoint. Use POST for JSON-RPC requests.',\n usage: {\n method: 'POST',\n contentType: 'application/json',\n body: { jsonrpc: '2.0', method: 'tools/list', id: 1 },\n },\n endpoints: {\n sse: `/api/mcp/${orgSlug}/gateway/sse`,\n http: `/api/mcp/${orgSlug}/gateway`,\n },\n gateway: {\n activeProfile: profileName,\n settingsEndpoint: '/api/settings/default-gateway-profile',\n },\n profile: {\n name: profileName,\n toolCount: info.tools.length,\n serverCount: info.serverStatus.total,\n connectedServers: info.serverStatus.connected,\n },\n infoEndpoint: `/api/mcp/${orgSlug}/gateway/info`,\n });\n }\n\n /**\n * MCP JSON-RPC endpoint for org-scoped gateway\n */\n @Post(':orgSlug/gateway')\n @HttpCode(HttpStatus.OK)\n async handleOrgGatewayRequest(\n @Req() req: Request,\n @Param('orgSlug') orgSlug: string,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n const user = this.resolveUser(req);\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n return this.proxyService.handleRequestByOrgSlug(profileName, orgSlug, request, user.id);\n }\n\n // =========================================\n // Org-scoped Profile Endpoints: /api/mcp/:orgSlug/:profileName\n // =========================================\n\n /**\n * SSE endpoint for org-scoped profile\n */\n @Get(':orgSlug/:profileName/sse')\n async streamOrgProfileSse(\n @Param('orgSlug') orgSlug: string,\n @Param('profileName') profileName: string,\n @Req() req: Request,\n @Res() res: Response\n ) {\n await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);\n return this.streamGatewayEvents(req, res);\n }\n\n /**\n * POST handler for org-scoped profile SSE\n */\n @Post(':orgSlug/:profileName/sse')\n @HttpCode(HttpStatus.OK)\n async handleOrgProfileSseRequest(\n @Req() req: Request,\n @Param('orgSlug') orgSlug: string,\n @Param('profileName') profileName: string,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n return this.handleOrgMcpRequest(req, orgSlug, profileName, request);\n }\n\n /**\n * Get org-scoped profile info\n */\n @Get(':orgSlug/:profileName/info')\n async getOrgProfileInfo(\n @Param('orgSlug') orgSlug: string,\n @Param('profileName') profileName: string\n ) {\n return this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);\n }\n\n /**\n * GET handler for org-scoped MCP endpoint\n */\n @Get(':orgSlug/:profileName')\n async getOrgMcpEndpoint(\n @Param('orgSlug') orgSlug: string,\n @Param('profileName') profileName: string,\n @Req() req: Request,\n @Res() res: Response\n ) {\n const info = await this.proxyService.getProfileInfoByOrgSlug(profileName, orgSlug);\n\n const acceptHeader = req.headers.accept || '';\n if (acceptHeader.includes('text/event-stream')) {\n return this.streamGatewayEvents(req, res);\n }\n\n return res.json({\n message: 'This is an MCP (Model Context Protocol) endpoint. Use POST for JSON-RPC requests.',\n usage: {\n method: 'POST',\n contentType: 'application/json',\n body: { jsonrpc: '2.0', method: 'tools/list', id: 1 },\n },\n endpoints: {\n sse: `/api/mcp/${orgSlug}/${profileName}/sse`,\n http: `/api/mcp/${orgSlug}/${profileName}`,\n },\n profile: {\n name: profileName,\n toolCount: info.tools.length,\n serverCount: info.serverStatus.total,\n connectedServers: info.serverStatus.connected,\n },\n infoEndpoint: `/api/mcp/${orgSlug}/${profileName}/info`,\n });\n }\n\n /**\n * MCP JSON-RPC endpoint for an org-scoped profile\n */\n @Post(':orgSlug/:profileName')\n @HttpCode(HttpStatus.OK)\n async handleOrgMcpRequest(\n @Req() req: Request,\n @Param('orgSlug') orgSlug: string,\n @Param('profileName') profileName: string,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n const user = this.resolveUser(req);\n return this.proxyService.handleRequestByOrgSlug(profileName, orgSlug, request, user.id);\n }\n}\n"],"names":["Body","Controller","Get","HttpCode","HttpStatus","NotFoundException","Param","Post","Req","Res","UseGuards","EventEmitter2","fromEvent","map","Public","McpOAuthGuard","GATEWAY_PROFILE_CHANGED","SettingsService","ProxyService","ProxyController","proxyService","settingsService","eventEmitter","resolveUser","req","user","streamGatewaySse","res","streamGatewayEvents","handleGatewaySseRequest","request","handleGatewayRequest","getGatewayInfo","profileName","getDefaultGatewayProfile","info","getProfileInfo","id","gateway","activeProfile","error","getGatewayEndpoint","acceptHeader","headers","accept","includes","json","message","usage","method","contentType","body","jsonrpc","endpoints","sse","http","settingsEndpoint","profile","name","toolCount","tools","length","serverCount","serverStatus","total","connectedServers","connected","infoEndpoint","setHeader","write","subscription","pipe","subscribe","notification","JSON","stringify","on","unsubscribe","handleRequest","streamOrgGatewaySse","orgSlug","getProfileInfoByOrgSlug","handleOrgGatewaySseRequest","handleRequestByOrgSlug","getOrgGatewayInfo","getOrgGatewayEndpoint","handleOrgGatewayRequest","streamOrgProfileSse","handleOrgProfileSseRequest","handleOrgMcpRequest","getOrgProfileInfo","getOrgMcpEndpoint","OK"],"mappings":"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"}
@@ -296,12 +296,35 @@ export class ProxyService {
296
296
  return instance;
297
297
  }
298
298
  }
299
+ // For remote_http and remote_sse servers, look up OAuth token
300
+ let oauthToken = null;
301
+ if (server.type === 'remote_http' || server.type === 'remote_sse') {
302
+ const tokenRecord = await this.prisma.oAuthToken.findUnique({
303
+ where: {
304
+ mcpServerId: server.id
305
+ }
306
+ });
307
+ if (tokenRecord) {
308
+ oauthToken = {
309
+ id: tokenRecord.id,
310
+ mcpServerId: tokenRecord.mcpServerId,
311
+ accessToken: tokenRecord.accessToken,
312
+ tokenType: tokenRecord.tokenType,
313
+ refreshToken: tokenRecord.refreshToken ?? undefined,
314
+ scope: tokenRecord.scope ?? undefined,
315
+ expiresAt: tokenRecord.expiresAt?.getTime(),
316
+ createdAt: tokenRecord.createdAt.getTime(),
317
+ updatedAt: tokenRecord.updatedAt.getTime()
318
+ };
319
+ }
320
+ }
299
321
  // For remote_http servers, create RemoteHttpMcpServer
300
322
  if (server.type === 'remote_http' && config?.url) {
301
323
  const remoteServer = new RemoteHttpMcpServer({
302
324
  url: config.url,
303
- transport: 'http'
304
- }, null, apiKeyConfig);
325
+ transport: 'http',
326
+ headers: config.headers
327
+ }, oauthToken, apiKeyConfig);
305
328
  await remoteServer.initialize();
306
329
  this.serverInstances.set(server.id, remoteServer);
307
330
  return remoteServer;
@@ -310,8 +333,9 @@ export class ProxyService {
310
333
  if (server.type === 'remote_sse' && config?.url) {
311
334
  const remoteServer = new RemoteSseMcpServer({
312
335
  url: config.url,
313
- transport: 'sse'
314
- }, null, apiKeyConfig);
336
+ transport: 'sse',
337
+ headers: config.headers
338
+ }, oauthToken, apiKeyConfig);
315
339
  await remoteServer.initialize();
316
340
  this.serverInstances.set(server.id, remoteServer);
317
341
  return remoteServer;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/modules/proxy/proxy.service.ts"],"sourcesContent":["/**\n * Proxy Service\n *\n * Handles MCP protocol proxying for profiles.\n */\n\nimport type { ApiKeyConfig as CoreApiKeyConfig, McpServer } from '@dxheroes/local-mcp-core';\nimport {\n ExternalMcpServer,\n RemoteHttpMcpServer,\n RemoteSseMcpServer,\n} from '@dxheroes/local-mcp-core';\nimport { Injectable, Logger, NotFoundException } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { DebugService } from '../debug/debug.service.js';\nimport { McpRegistry } from '../mcp/mcp-registry.js';\n\ninterface McpToolCall {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\nexport interface McpRequest {\n jsonrpc: '2.0';\n id: string | number;\n method: string;\n params?: unknown;\n}\n\nexport interface McpResponse {\n jsonrpc: '2.0';\n id: string | number;\n result?: unknown;\n error?: {\n code: number;\n message: string;\n data?: unknown;\n };\n}\n\ninterface ServerConfig {\n builtinId?: string;\n url?: string;\n // External server config\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n workingDirectory?: string;\n autoRestart?: boolean;\n maxRestartAttempts?: number;\n startupTimeout?: number;\n shutdownTimeout?: number;\n}\n\ninterface StoredApiKeyConfig {\n apiKey: string;\n headerName?: string;\n headerValueTemplate?: string;\n}\n\n@Injectable()\nexport class ProxyService {\n private readonly logger = new Logger(ProxyService.name);\n private readonly serverInstances = new Map<string, McpServer>();\n\n constructor(\n private readonly prisma: PrismaService,\n private readonly registry: McpRegistry,\n private readonly debugService: DebugService\n ) {}\n\n /**\n * Handle MCP JSON-RPC request for a profile\n */\n async handleRequest(\n profileName: string,\n request: McpRequest,\n userId?: string\n ): Promise<McpResponse> {\n const requestId = request.id;\n const startTime = Date.now();\n\n // Get profile with servers\n const profile = await this.findProfileByName(profileName, userId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n // Skip logging for initialize (just handshaking)\n const shouldLog = request.method !== 'initialize';\n\n // Create debug log entry\n let logId: string | null = null;\n if (shouldLog) {\n try {\n const log = await this.debugService.createLog({\n profileId: profile.id,\n requestType: request.method,\n requestPayload: JSON.stringify(request),\n status: 'pending',\n });\n logId = log.id;\n } catch (logError) {\n this.logger.warn(`Failed to create debug log: ${logError}`);\n }\n }\n\n try {\n // Handle different MCP methods\n let response: McpResponse;\n\n switch (request.method) {\n case 'initialize':\n response = await this.handleInitialize(requestId, profile);\n break;\n\n case 'tools/list':\n response = await this.handleToolsList(requestId, profile);\n break;\n\n case 'tools/call':\n response = await this.handleToolsCall(\n requestId,\n profile,\n request.params as McpToolCall,\n logId\n );\n break;\n\n case 'resources/list':\n response = await this.handleResourcesList(requestId, profile);\n break;\n\n case 'resources/read':\n response = await this.handleResourcesRead(\n requestId,\n profile,\n request.params as { uri: string }\n );\n break;\n\n default:\n response = {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32601,\n message: `Method not found: ${request.method}`,\n },\n };\n }\n\n // Update debug log with success\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n responsePayload: JSON.stringify(response),\n status: response.error ? 'error' : 'success',\n errorMessage: response.error?.message,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return response;\n } catch (error) {\n this.logger.error(`MCP request error: ${error}`);\n\n const errorMessage = error instanceof Error ? error.message : 'Internal error';\n\n // Update debug log with error\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n status: 'error',\n errorMessage,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32603,\n message: errorMessage,\n },\n };\n }\n }\n\n private async handleInitialize(\n requestId: string | number,\n _profile: { name: string }\n ): Promise<McpResponse> {\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n protocolVersion: '2024-11-05',\n capabilities: {\n tools: {},\n resources: {},\n },\n serverInfo: {\n name: 'Local MCP Gateway',\n version: '0.1.0',\n },\n },\n };\n }\n\n private async handleToolsList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n customDescription: string | null;\n }>;\n }>;\n }\n ): Promise<McpResponse> {\n const allTools: Array<{\n name: string;\n description: string;\n inputSchema: unknown;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Apply tool customizations\n for (const tool of tools) {\n const customization = profileServer.tools.find((t) => t.toolName === tool.name);\n\n if (!customization || customization.isEnabled) {\n allTools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n inputSchema: tool.inputSchema,\n });\n }\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n tools: allTools,\n },\n };\n }\n\n private async handleToolsCall(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n }>;\n }>;\n },\n params: McpToolCall,\n logId: string | null\n ): Promise<McpResponse> {\n // Find which server has this tool\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Check if this server has the requested tool (by name or custom name)\n const customization = profileServer.tools.find(\n (t) => t.customName === params.name || t.toolName === params.name\n );\n\n // Skip if tool is disabled\n if (customization && !customization.isEnabled) {\n continue;\n }\n\n const toolName = customization?.toolName || params.name;\n const hasTool = tools.some((t) => t.name === toolName);\n\n if (hasTool) {\n // Update log with the server that handled this tool call\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n mcpServerId: server.id,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log with server info: ${logError}`);\n }\n }\n\n const result = await instance.callTool(toolName, params.arguments || {});\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result,\n };\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Tool not found: ${params.name}`,\n },\n };\n }\n\n private async handleResourcesList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n }\n ): Promise<McpResponse> {\n const allResources: Array<{\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const resources = await instance.listResources();\n allResources.push(...resources);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n resources: allResources,\n },\n };\n }\n\n private async handleResourcesRead(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n },\n params: { uri: string }\n ): Promise<McpResponse> {\n // Try each server until we find one that has this resource\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n try {\n const content = await instance.readResource(params.uri);\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: content,\n };\n } catch {\n // Resource not found on this server, try next\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Resource not found: ${params.uri}`,\n },\n };\n }\n\n /**\n * Get tools for a specific server by ID\n */\n async getToolsForServer(serverId: string) {\n const server = await this.prisma.mcpServer.findUnique({\n where: { id: serverId },\n });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${serverId} not found`);\n }\n\n const instance = await this.getServerInstance(server);\n if (!instance) {\n return [];\n }\n\n return instance.listTools();\n }\n\n /**\n * Get or create a server instance\n */\n private async getServerInstance(server: {\n id: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n }): Promise<McpServer | null> {\n // Check cache\n const cached = this.serverInstances.get(server.id);\n if (cached) {\n return cached;\n }\n\n // Parse config\n const config = this.parseJson<ServerConfig>(server.config);\n const builtinId = config?.builtinId;\n\n // Get API key config and convert to CoreApiKeyConfig format\n const storedConfig = this.parseJson<StoredApiKeyConfig>(server.apiKeyConfig);\n const apiKeyConfig = this.convertApiKeyConfig(storedConfig);\n\n // For builtin servers, get from registry\n if (builtinId && this.registry.has(builtinId)) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n const instance = pkg.createServer(apiKeyConfig);\n await instance.initialize();\n this.serverInstances.set(server.id, instance);\n return instance;\n }\n }\n\n // For remote_http servers, create RemoteHttpMcpServer\n if (server.type === 'remote_http' && config?.url) {\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For remote_sse servers, create RemoteSseMcpServer\n if (server.type === 'remote_sse' && config?.url) {\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For external servers (NPX/stdio), create ExternalMcpServer\n if (server.type === 'external' && config?.command) {\n const externalServer = new ExternalMcpServer({\n command: config.command,\n args: config.args,\n env: config.env,\n workingDirectory: config.workingDirectory,\n autoRestart: config.autoRestart,\n maxRestartAttempts: config.maxRestartAttempts,\n startupTimeout: config.startupTimeout,\n shutdownTimeout: config.shutdownTimeout,\n });\n await externalServer.initialize();\n this.serverInstances.set(server.id, externalServer);\n return externalServer;\n }\n\n return null;\n }\n\n /**\n * Convert stored API key config to CoreApiKeyConfig format\n */\n private convertApiKeyConfig(stored: StoredApiKeyConfig | null): CoreApiKeyConfig | null {\n if (!stored?.apiKey) return null;\n\n const headerName = stored.headerName || 'Authorization';\n const template = stored.headerValueTemplate || 'Bearer {apiKey}';\n const headerValue = template.replace('{apiKey}', stored.apiKey);\n\n return {\n apiKey: stored.apiKey,\n headerName,\n headerValue,\n };\n }\n\n private parseJson<T>(value: unknown): T | null {\n if (typeof value === 'string') {\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n }\n return value as T | null;\n }\n\n /**\n * Resolve an org slug to an organization ID.\n */\n private async resolveOrgBySlug(orgSlug: string): Promise<string> {\n const org = await this.prisma.organization.findUnique({\n where: { slug: orgSlug },\n select: { id: true },\n });\n if (!org) {\n throw new NotFoundException(`Organization \"${orgSlug}\" not found`);\n }\n return org.id;\n }\n\n /**\n * Find a profile by name within an organization (or system profiles).\n */\n private async findProfileByNameAndOrg(profileName: string, orgId: string) {\n const include = {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' as const },\n },\n };\n\n // Try org-scoped first\n let profile = await this.prisma.profile.findUnique({\n where: { organizationId_name: { organizationId: orgId, name: profileName } },\n include,\n });\n\n // Fallback to system profile (organizationId=null)\n if (!profile) {\n profile = await this.prisma.profile.findFirst({\n where: { name: profileName, organizationId: null },\n include,\n });\n }\n\n return profile;\n }\n\n /**\n * Find a profile by name, scoped to user if userId is provided.\n * Anonymous users see all profiles. Authenticated users see own + system profiles.\n */\n private async findProfileByName(profileName: string, userId?: string) {\n const include = {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' as const },\n },\n };\n\n // Try finding by unique (org, name) — for system profiles use null org\n const profile = await this.prisma.profile.findFirst({\n where: { name: profileName },\n include,\n });\n\n if (!profile) return null;\n\n // If no userId or unauthenticated, allow access to all profiles\n if (!userId || userId === '__unauthenticated__') return profile;\n\n // System records — accessible to all\n if (!profile.organizationId) return profile;\n\n // Allow access if user is a member of the profile's org\n const membership = await this.prisma.member.findFirst({\n where: { userId, organizationId: profile.organizationId },\n });\n if (membership) return profile;\n\n // Otherwise deny\n return null;\n }\n\n /**\n * Handle MCP request using org slug to resolve the profile.\n */\n async handleRequestByOrgSlug(\n profileName: string,\n orgSlug: string,\n request: McpRequest,\n userId?: string\n ): Promise<McpResponse> {\n const orgId = await this.resolveOrgBySlug(orgSlug);\n const profile = await this.findProfileByNameAndOrg(profileName, orgId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found in organization \"${orgSlug}\"`);\n }\n\n const requestId = request.id;\n const startTime = Date.now();\n\n const shouldLog = request.method !== 'initialize';\n let logId: string | null = null;\n if (shouldLog) {\n try {\n const log = await this.debugService.createLog({\n profileId: profile.id,\n requestType: request.method,\n requestPayload: JSON.stringify(request),\n status: 'pending',\n });\n logId = log.id;\n } catch (logError) {\n this.logger.warn(`Failed to create debug log: ${logError}`);\n }\n }\n\n try {\n let response: McpResponse;\n\n switch (request.method) {\n case 'initialize':\n response = await this.handleInitialize(requestId, profile);\n break;\n case 'tools/list':\n response = await this.handleToolsList(requestId, profile);\n break;\n case 'tools/call':\n response = await this.handleToolsCall(\n requestId,\n profile,\n request.params as McpToolCall,\n logId\n );\n break;\n case 'resources/list':\n response = await this.handleResourcesList(requestId, profile);\n break;\n case 'resources/read':\n response = await this.handleResourcesRead(\n requestId,\n profile,\n request.params as { uri: string }\n );\n break;\n default:\n response = {\n jsonrpc: '2.0',\n id: requestId,\n error: { code: -32601, message: `Method not found: ${request.method}` },\n };\n }\n\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n responsePayload: JSON.stringify(response),\n status: response.error ? 'error' : 'success',\n errorMessage: response.error?.message,\n durationMs: Date.now() - startTime,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return response;\n } catch (error) {\n this.logger.error(`MCP request error: ${error}`);\n const errorMessage = error instanceof Error ? error.message : 'Internal error';\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n status: 'error',\n errorMessage,\n durationMs: Date.now() - startTime,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: { code: -32603, message: errorMessage },\n };\n }\n }\n\n /**\n * Get profile info using org slug.\n */\n async getProfileInfoByOrgSlug(profileName: string, orgSlug: string) {\n const orgId = await this.resolveOrgBySlug(orgSlug);\n const profile = await this.findProfileByNameAndOrg(profileName, orgId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found in organization \"${orgSlug}\"`);\n }\n\n return this.aggregateProfileInfo(profile);\n }\n\n /**\n * Get profile info with aggregated tools and server status\n */\n async getProfileInfo(profileName: string, userId?: string) {\n const profile = await this.findProfileByName(profileName, userId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n return this.aggregateProfileInfo(profile);\n }\n\n /**\n * Aggregate tools and server status from a profile.\n */\n private async aggregateProfileInfo(profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n customDescription: string | null;\n }>;\n }>;\n }) {\n const tools: Array<{ name: string; description: string }> = [];\n const serverStatus: Record<string, { connected: boolean; toolCount: number }> = {};\n\n for (const ps of profile.mcpServers) {\n const server = ps.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const serverTools = await instance.listTools();\n serverStatus[server.id] = { connected: true, toolCount: serverTools.length };\n\n for (const tool of serverTools) {\n const customization = ps.tools.find((t) => t.toolName === tool.name);\n if (!customization || customization.isEnabled) {\n tools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n });\n }\n }\n } else {\n serverStatus[server.id] = { connected: false, toolCount: 0 };\n }\n }\n\n return {\n tools,\n serverStatus: {\n total: profile.mcpServers.length,\n connected: Object.values(serverStatus).filter((s) => s.connected).length,\n servers: serverStatus,\n },\n };\n }\n}\n"],"names":["ExternalMcpServer","RemoteHttpMcpServer","RemoteSseMcpServer","Injectable","Logger","NotFoundException","PrismaService","DebugService","McpRegistry","ProxyService","prisma","registry","debugService","logger","name","serverInstances","Map","handleRequest","profileName","request","userId","requestId","id","startTime","Date","now","profile","findProfileByName","shouldLog","method","logId","log","createLog","profileId","requestType","requestPayload","JSON","stringify","status","logError","warn","response","handleInitialize","handleToolsList","handleToolsCall","params","handleResourcesList","handleResourcesRead","jsonrpc","error","code","message","durationMs","updateLog","responsePayload","errorMessage","Error","_profile","result","protocolVersion","capabilities","tools","resources","serverInfo","version","allTools","profileServer","mcpServers","server","mcpServer","instance","getServerInstance","listTools","tool","customization","find","t","toolName","isEnabled","push","customName","description","customDescription","inputSchema","hasTool","some","mcpServerId","callTool","arguments","allResources","listResources","content","readResource","uri","getToolsForServer","serverId","findUnique","where","cached","get","config","parseJson","builtinId","storedConfig","apiKeyConfig","convertApiKeyConfig","has","pkg","createServer","initialize","set","type","url","remoteServer","transport","command","externalServer","args","env","workingDirectory","autoRestart","maxRestartAttempts","startupTimeout","shutdownTimeout","stored","apiKey","headerName","template","headerValueTemplate","headerValue","replace","value","parse","resolveOrgBySlug","orgSlug","org","organization","slug","select","findProfileByNameAndOrg","orgId","include","isActive","orderBy","order","organizationId_name","organizationId","findFirst","membership","member","handleRequestByOrgSlug","getProfileInfoByOrgSlug","aggregateProfileInfo","getProfileInfo","serverStatus","ps","serverTools","connected","toolCount","length","total","Object","values","filter","s","servers"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/modules/proxy/proxy.service.ts"],"sourcesContent":["/**\n * Proxy Service\n *\n * Handles MCP protocol proxying for profiles.\n */\n\nimport type { ApiKeyConfig as CoreApiKeyConfig, McpServer } from '@dxheroes/local-mcp-core';\nimport {\n ExternalMcpServer,\n RemoteHttpMcpServer,\n RemoteSseMcpServer,\n} from '@dxheroes/local-mcp-core';\nimport { Injectable, Logger, NotFoundException } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { DebugService } from '../debug/debug.service.js';\nimport { McpRegistry } from '../mcp/mcp-registry.js';\n\ninterface McpToolCall {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\nexport interface McpRequest {\n jsonrpc: '2.0';\n id: string | number;\n method: string;\n params?: unknown;\n}\n\nexport interface McpResponse {\n jsonrpc: '2.0';\n id: string | number;\n result?: unknown;\n error?: {\n code: number;\n message: string;\n data?: unknown;\n };\n}\n\ninterface ServerConfig {\n builtinId?: string;\n url?: string;\n headers?: Record<string, string>;\n // External server config\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n workingDirectory?: string;\n autoRestart?: boolean;\n maxRestartAttempts?: number;\n startupTimeout?: number;\n shutdownTimeout?: number;\n}\n\ninterface StoredApiKeyConfig {\n apiKey: string;\n headerName?: string;\n headerValueTemplate?: string;\n}\n\n@Injectable()\nexport class ProxyService {\n private readonly logger = new Logger(ProxyService.name);\n private readonly serverInstances = new Map<string, McpServer>();\n\n constructor(\n private readonly prisma: PrismaService,\n private readonly registry: McpRegistry,\n private readonly debugService: DebugService\n ) {}\n\n /**\n * Handle MCP JSON-RPC request for a profile\n */\n async handleRequest(\n profileName: string,\n request: McpRequest,\n userId?: string\n ): Promise<McpResponse> {\n const requestId = request.id;\n const startTime = Date.now();\n\n // Get profile with servers\n const profile = await this.findProfileByName(profileName, userId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n // Skip logging for initialize (just handshaking)\n const shouldLog = request.method !== 'initialize';\n\n // Create debug log entry\n let logId: string | null = null;\n if (shouldLog) {\n try {\n const log = await this.debugService.createLog({\n profileId: profile.id,\n requestType: request.method,\n requestPayload: JSON.stringify(request),\n status: 'pending',\n });\n logId = log.id;\n } catch (logError) {\n this.logger.warn(`Failed to create debug log: ${logError}`);\n }\n }\n\n try {\n // Handle different MCP methods\n let response: McpResponse;\n\n switch (request.method) {\n case 'initialize':\n response = await this.handleInitialize(requestId, profile);\n break;\n\n case 'tools/list':\n response = await this.handleToolsList(requestId, profile);\n break;\n\n case 'tools/call':\n response = await this.handleToolsCall(\n requestId,\n profile,\n request.params as McpToolCall,\n logId\n );\n break;\n\n case 'resources/list':\n response = await this.handleResourcesList(requestId, profile);\n break;\n\n case 'resources/read':\n response = await this.handleResourcesRead(\n requestId,\n profile,\n request.params as { uri: string }\n );\n break;\n\n default:\n response = {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32601,\n message: `Method not found: ${request.method}`,\n },\n };\n }\n\n // Update debug log with success\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n responsePayload: JSON.stringify(response),\n status: response.error ? 'error' : 'success',\n errorMessage: response.error?.message,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return response;\n } catch (error) {\n this.logger.error(`MCP request error: ${error}`);\n\n const errorMessage = error instanceof Error ? error.message : 'Internal error';\n\n // Update debug log with error\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n status: 'error',\n errorMessage,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32603,\n message: errorMessage,\n },\n };\n }\n }\n\n private async handleInitialize(\n requestId: string | number,\n _profile: { name: string }\n ): Promise<McpResponse> {\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n protocolVersion: '2024-11-05',\n capabilities: {\n tools: {},\n resources: {},\n },\n serverInfo: {\n name: 'Local MCP Gateway',\n version: '0.1.0',\n },\n },\n };\n }\n\n private async handleToolsList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n customDescription: string | null;\n }>;\n }>;\n }\n ): Promise<McpResponse> {\n const allTools: Array<{\n name: string;\n description: string;\n inputSchema: unknown;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Apply tool customizations\n for (const tool of tools) {\n const customization = profileServer.tools.find((t) => t.toolName === tool.name);\n\n if (!customization || customization.isEnabled) {\n allTools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n inputSchema: tool.inputSchema,\n });\n }\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n tools: allTools,\n },\n };\n }\n\n private async handleToolsCall(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n }>;\n }>;\n },\n params: McpToolCall,\n logId: string | null\n ): Promise<McpResponse> {\n // Find which server has this tool\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Check if this server has the requested tool (by name or custom name)\n const customization = profileServer.tools.find(\n (t) => t.customName === params.name || t.toolName === params.name\n );\n\n // Skip if tool is disabled\n if (customization && !customization.isEnabled) {\n continue;\n }\n\n const toolName = customization?.toolName || params.name;\n const hasTool = tools.some((t) => t.name === toolName);\n\n if (hasTool) {\n // Update log with the server that handled this tool call\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n mcpServerId: server.id,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log with server info: ${logError}`);\n }\n }\n\n const result = await instance.callTool(toolName, params.arguments || {});\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result,\n };\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Tool not found: ${params.name}`,\n },\n };\n }\n\n private async handleResourcesList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n }\n ): Promise<McpResponse> {\n const allResources: Array<{\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const resources = await instance.listResources();\n allResources.push(...resources);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n resources: allResources,\n },\n };\n }\n\n private async handleResourcesRead(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n },\n params: { uri: string }\n ): Promise<McpResponse> {\n // Try each server until we find one that has this resource\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n try {\n const content = await instance.readResource(params.uri);\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: content,\n };\n } catch {\n // Resource not found on this server, try next\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Resource not found: ${params.uri}`,\n },\n };\n }\n\n /**\n * Get tools for a specific server by ID\n */\n async getToolsForServer(serverId: string) {\n const server = await this.prisma.mcpServer.findUnique({\n where: { id: serverId },\n });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${serverId} not found`);\n }\n\n const instance = await this.getServerInstance(server);\n if (!instance) {\n return [];\n }\n\n return instance.listTools();\n }\n\n /**\n * Get or create a server instance\n */\n private async getServerInstance(server: {\n id: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n }): Promise<McpServer | null> {\n // Check cache\n const cached = this.serverInstances.get(server.id);\n if (cached) {\n return cached;\n }\n\n // Parse config\n const config = this.parseJson<ServerConfig>(server.config);\n const builtinId = config?.builtinId;\n\n // Get API key config and convert to CoreApiKeyConfig format\n const storedConfig = this.parseJson<StoredApiKeyConfig>(server.apiKeyConfig);\n const apiKeyConfig = this.convertApiKeyConfig(storedConfig);\n\n // For builtin servers, get from registry\n if (builtinId && this.registry.has(builtinId)) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n const instance = pkg.createServer(apiKeyConfig);\n await instance.initialize();\n this.serverInstances.set(server.id, instance);\n return instance;\n }\n }\n\n // For remote_http and remote_sse servers, look up OAuth token\n let oauthToken = null;\n if (server.type === 'remote_http' || server.type === 'remote_sse') {\n const tokenRecord = await this.prisma.oAuthToken.findUnique({\n where: { mcpServerId: server.id },\n });\n if (tokenRecord) {\n oauthToken = {\n id: tokenRecord.id,\n mcpServerId: tokenRecord.mcpServerId,\n accessToken: tokenRecord.accessToken,\n tokenType: tokenRecord.tokenType,\n refreshToken: tokenRecord.refreshToken ?? undefined,\n scope: tokenRecord.scope ?? undefined,\n expiresAt: tokenRecord.expiresAt?.getTime(),\n createdAt: tokenRecord.createdAt.getTime(),\n updatedAt: tokenRecord.updatedAt.getTime(),\n };\n }\n }\n\n // For remote_http servers, create RemoteHttpMcpServer\n if (server.type === 'remote_http' && config?.url) {\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http', headers: config.headers as Record<string, string> },\n oauthToken,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For remote_sse servers, create RemoteSseMcpServer\n if (server.type === 'remote_sse' && config?.url) {\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse', headers: config.headers as Record<string, string> },\n oauthToken,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For external servers (NPX/stdio), create ExternalMcpServer\n if (server.type === 'external' && config?.command) {\n const externalServer = new ExternalMcpServer({\n command: config.command,\n args: config.args,\n env: config.env,\n workingDirectory: config.workingDirectory,\n autoRestart: config.autoRestart,\n maxRestartAttempts: config.maxRestartAttempts,\n startupTimeout: config.startupTimeout,\n shutdownTimeout: config.shutdownTimeout,\n });\n await externalServer.initialize();\n this.serverInstances.set(server.id, externalServer);\n return externalServer;\n }\n\n return null;\n }\n\n /**\n * Convert stored API key config to CoreApiKeyConfig format\n */\n private convertApiKeyConfig(stored: StoredApiKeyConfig | null): CoreApiKeyConfig | null {\n if (!stored?.apiKey) return null;\n\n const headerName = stored.headerName || 'Authorization';\n const template = stored.headerValueTemplate || 'Bearer {apiKey}';\n const headerValue = template.replace('{apiKey}', stored.apiKey);\n\n return {\n apiKey: stored.apiKey,\n headerName,\n headerValue,\n };\n }\n\n private parseJson<T>(value: unknown): T | null {\n if (typeof value === 'string') {\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n }\n return value as T | null;\n }\n\n /**\n * Resolve an org slug to an organization ID.\n */\n private async resolveOrgBySlug(orgSlug: string): Promise<string> {\n const org = await this.prisma.organization.findUnique({\n where: { slug: orgSlug },\n select: { id: true },\n });\n if (!org) {\n throw new NotFoundException(`Organization \"${orgSlug}\" not found`);\n }\n return org.id;\n }\n\n /**\n * Find a profile by name within an organization (or system profiles).\n */\n private async findProfileByNameAndOrg(profileName: string, orgId: string) {\n const include = {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' as const },\n },\n };\n\n // Try org-scoped first\n let profile = await this.prisma.profile.findUnique({\n where: { organizationId_name: { organizationId: orgId, name: profileName } },\n include,\n });\n\n // Fallback to system profile (organizationId=null)\n if (!profile) {\n profile = await this.prisma.profile.findFirst({\n where: { name: profileName, organizationId: null },\n include,\n });\n }\n\n return profile;\n }\n\n /**\n * Find a profile by name, scoped to user if userId is provided.\n * Anonymous users see all profiles. Authenticated users see own + system profiles.\n */\n private async findProfileByName(profileName: string, userId?: string) {\n const include = {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' as const },\n },\n };\n\n // Try finding by unique (org, name) — for system profiles use null org\n const profile = await this.prisma.profile.findFirst({\n where: { name: profileName },\n include,\n });\n\n if (!profile) return null;\n\n // If no userId or unauthenticated, allow access to all profiles\n if (!userId || userId === '__unauthenticated__') return profile;\n\n // System records — accessible to all\n if (!profile.organizationId) return profile;\n\n // Allow access if user is a member of the profile's org\n const membership = await this.prisma.member.findFirst({\n where: { userId, organizationId: profile.organizationId },\n });\n if (membership) return profile;\n\n // Otherwise deny\n return null;\n }\n\n /**\n * Handle MCP request using org slug to resolve the profile.\n */\n async handleRequestByOrgSlug(\n profileName: string,\n orgSlug: string,\n request: McpRequest,\n userId?: string\n ): Promise<McpResponse> {\n const orgId = await this.resolveOrgBySlug(orgSlug);\n const profile = await this.findProfileByNameAndOrg(profileName, orgId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found in organization \"${orgSlug}\"`);\n }\n\n const requestId = request.id;\n const startTime = Date.now();\n\n const shouldLog = request.method !== 'initialize';\n let logId: string | null = null;\n if (shouldLog) {\n try {\n const log = await this.debugService.createLog({\n profileId: profile.id,\n requestType: request.method,\n requestPayload: JSON.stringify(request),\n status: 'pending',\n });\n logId = log.id;\n } catch (logError) {\n this.logger.warn(`Failed to create debug log: ${logError}`);\n }\n }\n\n try {\n let response: McpResponse;\n\n switch (request.method) {\n case 'initialize':\n response = await this.handleInitialize(requestId, profile);\n break;\n case 'tools/list':\n response = await this.handleToolsList(requestId, profile);\n break;\n case 'tools/call':\n response = await this.handleToolsCall(\n requestId,\n profile,\n request.params as McpToolCall,\n logId\n );\n break;\n case 'resources/list':\n response = await this.handleResourcesList(requestId, profile);\n break;\n case 'resources/read':\n response = await this.handleResourcesRead(\n requestId,\n profile,\n request.params as { uri: string }\n );\n break;\n default:\n response = {\n jsonrpc: '2.0',\n id: requestId,\n error: { code: -32601, message: `Method not found: ${request.method}` },\n };\n }\n\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n responsePayload: JSON.stringify(response),\n status: response.error ? 'error' : 'success',\n errorMessage: response.error?.message,\n durationMs: Date.now() - startTime,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return response;\n } catch (error) {\n this.logger.error(`MCP request error: ${error}`);\n const errorMessage = error instanceof Error ? error.message : 'Internal error';\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n status: 'error',\n errorMessage,\n durationMs: Date.now() - startTime,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: { code: -32603, message: errorMessage },\n };\n }\n }\n\n /**\n * Get profile info using org slug.\n */\n async getProfileInfoByOrgSlug(profileName: string, orgSlug: string) {\n const orgId = await this.resolveOrgBySlug(orgSlug);\n const profile = await this.findProfileByNameAndOrg(profileName, orgId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found in organization \"${orgSlug}\"`);\n }\n\n return this.aggregateProfileInfo(profile);\n }\n\n /**\n * Get profile info with aggregated tools and server status\n */\n async getProfileInfo(profileName: string, userId?: string) {\n const profile = await this.findProfileByName(profileName, userId);\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n return this.aggregateProfileInfo(profile);\n }\n\n /**\n * Aggregate tools and server status from a profile.\n */\n private async aggregateProfileInfo(profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n customDescription: string | null;\n }>;\n }>;\n }) {\n const tools: Array<{ name: string; description: string }> = [];\n const serverStatus: Record<string, { connected: boolean; toolCount: number }> = {};\n\n for (const ps of profile.mcpServers) {\n const server = ps.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const serverTools = await instance.listTools();\n serverStatus[server.id] = { connected: true, toolCount: serverTools.length };\n\n for (const tool of serverTools) {\n const customization = ps.tools.find((t) => t.toolName === tool.name);\n if (!customization || customization.isEnabled) {\n tools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n });\n }\n }\n } else {\n serverStatus[server.id] = { connected: false, toolCount: 0 };\n }\n }\n\n return {\n tools,\n serverStatus: {\n total: profile.mcpServers.length,\n connected: Object.values(serverStatus).filter((s) => s.connected).length,\n servers: serverStatus,\n },\n };\n }\n}\n"],"names":["ExternalMcpServer","RemoteHttpMcpServer","RemoteSseMcpServer","Injectable","Logger","NotFoundException","PrismaService","DebugService","McpRegistry","ProxyService","prisma","registry","debugService","logger","name","serverInstances","Map","handleRequest","profileName","request","userId","requestId","id","startTime","Date","now","profile","findProfileByName","shouldLog","method","logId","log","createLog","profileId","requestType","requestPayload","JSON","stringify","status","logError","warn","response","handleInitialize","handleToolsList","handleToolsCall","params","handleResourcesList","handleResourcesRead","jsonrpc","error","code","message","durationMs","updateLog","responsePayload","errorMessage","Error","_profile","result","protocolVersion","capabilities","tools","resources","serverInfo","version","allTools","profileServer","mcpServers","server","mcpServer","instance","getServerInstance","listTools","tool","customization","find","t","toolName","isEnabled","push","customName","description","customDescription","inputSchema","hasTool","some","mcpServerId","callTool","arguments","allResources","listResources","content","readResource","uri","getToolsForServer","serverId","findUnique","where","cached","get","config","parseJson","builtinId","storedConfig","apiKeyConfig","convertApiKeyConfig","has","pkg","createServer","initialize","set","oauthToken","type","tokenRecord","oAuthToken","accessToken","tokenType","refreshToken","undefined","scope","expiresAt","getTime","createdAt","updatedAt","url","remoteServer","transport","headers","command","externalServer","args","env","workingDirectory","autoRestart","maxRestartAttempts","startupTimeout","shutdownTimeout","stored","apiKey","headerName","template","headerValueTemplate","headerValue","replace","value","parse","resolveOrgBySlug","orgSlug","org","organization","slug","select","findProfileByNameAndOrg","orgId","include","isActive","orderBy","order","organizationId_name","organizationId","findFirst","membership","member","handleRequestByOrgSlug","getProfileInfoByOrgSlug","aggregateProfileInfo","getProfileInfo","serverStatus","ps","serverTools","connected","toolCount","length","total","Object","values","filter","s","servers"],"mappings":"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"}
@@ -1,9 +1,22 @@
1
1
  #!/bin/sh
2
2
  set -e
3
3
 
4
- echo "Pushing database schema..."
4
+ echo "Running database migrations..."
5
+ MAX_RETRIES=15
6
+ RETRY_INTERVAL=2
5
7
  cd /app/packages/database
6
- pnpm exec prisma db push --config ./dist/prisma.config.js
8
+ for i in $(seq 1 $MAX_RETRIES); do
9
+ if pnpm exec prisma migrate deploy --config ./dist/prisma.config.js; then
10
+ echo "Migrations applied successfully."
11
+ break
12
+ fi
13
+ if [ "$i" -eq "$MAX_RETRIES" ]; then
14
+ echo "Failed to apply migrations after $MAX_RETRIES attempts."
15
+ exit 1
16
+ fi
17
+ echo "Retrying in ${RETRY_INTERVAL}s... (attempt $i/$MAX_RETRIES)"
18
+ sleep $RETRY_INTERVAL
19
+ done
7
20
 
8
21
  echo "Starting application..."
9
22
  cd /app/apps/backend