@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.
- package/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +27 -0
- package/dist/__tests__/integration/mcp-proxy-auth-http.test.js +246 -0
- package/dist/__tests__/integration/mcp-proxy-auth-http.test.js.map +1 -0
- package/dist/__tests__/integration/oauth-authorize-callback.test.js +122 -0
- package/dist/__tests__/integration/oauth-authorize-callback.test.js.map +1 -0
- package/dist/__tests__/integration/proxy-auth.test.js +121 -111
- package/dist/__tests__/integration/proxy-auth.test.js.map +1 -1
- package/dist/__tests__/unit/auth.guard.test.js +23 -2
- package/dist/__tests__/unit/auth.guard.test.js.map +1 -1
- package/dist/common/filters/all-exceptions.filter.js +6 -0
- package/dist/common/filters/all-exceptions.filter.js.map +1 -1
- package/dist/main.js +37 -0
- package/dist/main.js.map +1 -1
- package/dist/modules/auth/auth.config.js +5 -2
- package/dist/modules/auth/auth.config.js.map +1 -1
- package/dist/modules/auth/auth.module.js +5 -2
- package/dist/modules/auth/auth.module.js.map +1 -1
- package/dist/modules/auth/auth.service.js +2 -2
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/mcp-oauth.guard.js +70 -0
- package/dist/modules/auth/mcp-oauth.guard.js.map +1 -0
- package/dist/modules/auth/mcp-oauth.utils.js +75 -0
- package/dist/modules/auth/mcp-oauth.utils.js.map +1 -0
- package/dist/modules/mcp/mcp.service.js +48 -8
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/oauth/oauth.controller.js +78 -1
- package/dist/modules/oauth/oauth.controller.js.map +1 -1
- package/dist/modules/oauth/oauth.service.js +197 -1
- package/dist/modules/oauth/oauth.service.js.map +1 -1
- package/dist/modules/proxy/proxy.controller.js +152 -27
- package/dist/modules/proxy/proxy.controller.js.map +1 -1
- package/dist/modules/proxy/proxy.service.js +28 -4
- package/dist/modules/proxy/proxy.service.js.map +1 -1
- package/docker-entrypoint.sh +15 -2
- package/package.json +7 -7
- package/src/__tests__/integration/mcp-proxy-auth-http.test.ts +281 -0
- package/src/__tests__/integration/oauth-authorize-callback.test.ts +155 -0
- package/src/__tests__/integration/proxy-auth.test.ts +119 -168
- package/src/__tests__/unit/auth.guard.test.ts +12 -2
- package/src/common/filters/all-exceptions.filter.ts +11 -0
- package/src/main.ts +32 -1
- package/src/modules/auth/auth.config.ts +4 -1
- package/src/modules/auth/auth.module.ts +3 -2
- package/src/modules/auth/auth.service.ts +2 -2
- package/src/modules/auth/mcp-oauth.guard.ts +75 -0
- package/src/modules/auth/mcp-oauth.utils.ts +80 -0
- package/src/modules/mcp/mcp.service.ts +54 -12
- package/src/modules/oauth/oauth.controller.ts +84 -1
- package/src/modules/oauth/oauth.service.ts +218 -1
- package/src/modules/proxy/proxy.controller.ts +120 -25
- package/src/modules/proxy/proxy.service.ts +26 -4
- 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,
|
|
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
|
|
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
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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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"}
|
package/docker-entrypoint.sh
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
set -e
|
|
3
3
|
|
|
4
|
-
echo "
|
|
4
|
+
echo "Running database migrations..."
|
|
5
|
+
MAX_RETRIES=15
|
|
6
|
+
RETRY_INTERVAL=2
|
|
5
7
|
cd /app/packages/database
|
|
6
|
-
|
|
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
|