@dxheroes/local-mcp-backend 0.4.0 → 0.5.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/AGENTS.md +4 -0
- package/CHANGELOG.md +32 -0
- package/dist/app.module.js +5 -0
- package/dist/app.module.js.map +1 -1
- package/dist/modules/profiles/profiles.service.js +15 -0
- package/dist/modules/profiles/profiles.service.js.map +1 -1
- package/dist/modules/proxy/proxy.controller.js +258 -9
- package/dist/modules/proxy/proxy.controller.js.map +1 -1
- package/dist/modules/proxy/proxy.module.js +4 -1
- package/dist/modules/proxy/proxy.module.js.map +1 -1
- package/dist/modules/settings/settings.constants.js +18 -0
- package/dist/modules/settings/settings.constants.js.map +1 -0
- package/dist/modules/settings/settings.controller.js +89 -0
- package/dist/modules/settings/settings.controller.js.map +1 -0
- package/dist/modules/settings/settings.module.js +30 -0
- package/dist/modules/settings/settings.module.js.map +1 -0
- package/dist/modules/settings/settings.service.js +110 -0
- package/dist/modules/settings/settings.service.js.map +1 -0
- package/package.json +6 -5
- package/src/AGENTS.md +23 -0
- package/src/app.module.ts +6 -0
- package/src/common/AGENTS.md +19 -0
- package/src/config/AGENTS.md +17 -0
- package/src/modules/AGENTS.md +31 -0
- package/src/modules/database/AGENTS.md +30 -0
- package/src/modules/debug/AGENTS.md +30 -0
- package/src/modules/health/AGENTS.md +22 -0
- package/src/modules/mcp/AGENTS.md +38 -0
- package/src/modules/oauth/AGENTS.md +32 -0
- package/src/modules/profiles/AGENTS.md +33 -0
- package/src/modules/profiles/profiles.service.ts +19 -0
- package/src/modules/proxy/AGENTS.md +34 -0
- package/src/modules/proxy/proxy.controller.ts +249 -7
- package/src/modules/proxy/proxy.module.ts +3 -1
- package/src/modules/settings/AGENTS.md +31 -0
- package/src/modules/settings/settings.constants.ts +20 -0
- package/src/modules/settings/settings.controller.ts +47 -0
- package/src/modules/settings/settings.module.ts +16 -0
- package/src/modules/settings/settings.service.ts +99 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @dxheroes/local-mcp-backend@0.
|
|
2
|
+
> @dxheroes/local-mcp-backend@0.5.0 build /home/runner/work/local-mcp-gateway/local-mcp-gateway/apps/backend
|
|
3
3
|
> nest build
|
|
4
4
|
|
|
5
5
|
- [46m[1m TSC [22m[49m[36m Initializing type checker...[39m
|
|
6
6
|
✔ [46m[1m TSC [22m[49m[36m Initializing type checker...[39m
|
|
7
7
|
[32m> [39m[42m[1m TSC [22m[49m[32m Found 0 issues.[39m
|
|
8
8
|
[36m> [39m[46m[1m SWC [22m[49m [36mRunning...[39m
|
|
9
|
-
Successfully compiled:
|
|
9
|
+
Successfully compiled: 36 files with swc (79.38ms)
|
package/AGENTS.md
CHANGED
|
@@ -351,6 +351,10 @@ pnpm --filter backend test:watch
|
|
|
351
351
|
pnpm --filter backend test:e2e
|
|
352
352
|
```
|
|
353
353
|
|
|
354
|
+
## Child Directories
|
|
355
|
+
|
|
356
|
+
- **[src/AGENTS.md](src/AGENTS.md)** - Source code documentation
|
|
357
|
+
|
|
354
358
|
## Important Notes
|
|
355
359
|
|
|
356
360
|
- **No user authentication**: All endpoints are public
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0](https://github.com/DXHeroes/local-mcp-gateway/compare/backend-v0.4.1...backend-v0.5.0) (2026-01-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add docs page, AGENTS.md files, and UI improvements ([d24105b](https://github.com/DXHeroes/local-mcp-gateway/commit/d24105bf5face12348b5617f0deb6fba52778ca1))
|
|
9
|
+
* add gateway settings and upgrade to Streamable HTTP transport ([ba22c9f](https://github.com/DXHeroes/local-mcp-gateway/commit/ba22c9f033071e43a50462280233a0bb1ad34ed7))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Dependencies
|
|
13
|
+
|
|
14
|
+
* The following workspace dependencies were updated
|
|
15
|
+
* dependencies
|
|
16
|
+
* @dxheroes/local-mcp-core bumped to 0.4.0
|
|
17
|
+
* @dxheroes/local-mcp-database bumped to 0.4.0
|
|
18
|
+
* @dxheroes/mcp-gemini-deep-research bumped to 0.4.0
|
|
19
|
+
* devDependencies
|
|
20
|
+
* @dxheroes/local-mcp-config bumped to 0.4.0
|
|
21
|
+
|
|
22
|
+
## [0.4.1](https://github.com/DXHeroes/local-mcp-gateway/compare/backend-v0.4.0...backend-v0.4.1) (2026-01-14)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Dependencies
|
|
26
|
+
|
|
27
|
+
* The following workspace dependencies were updated
|
|
28
|
+
* dependencies
|
|
29
|
+
* @dxheroes/local-mcp-core bumped to 0.3.4
|
|
30
|
+
* @dxheroes/local-mcp-database bumped to 0.3.4
|
|
31
|
+
* @dxheroes/mcp-gemini-deep-research bumped to 0.3.4
|
|
32
|
+
* devDependencies
|
|
33
|
+
* @dxheroes/local-mcp-config bumped to 0.3.4
|
|
34
|
+
|
|
3
35
|
## [0.4.0](https://github.com/DXHeroes/local-mcp-gateway/compare/backend-v0.3.2...backend-v0.4.0) (2026-01-14)
|
|
4
36
|
|
|
5
37
|
|
package/dist/app.module.js
CHANGED
|
@@ -11,6 +11,7 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
11
11
|
* No authentication module - immediate access to all features.
|
|
12
12
|
*/ import { Module } from "@nestjs/common";
|
|
13
13
|
import { ConfigModule } from "@nestjs/config";
|
|
14
|
+
import { EventEmitterModule } from "@nestjs/event-emitter";
|
|
14
15
|
import { ThrottlerModule } from "@nestjs/throttler";
|
|
15
16
|
import appConfig from "./config/app.config.js";
|
|
16
17
|
import databaseConfig from "./config/database.config.js";
|
|
@@ -21,6 +22,7 @@ import { McpModule } from "./modules/mcp/mcp.module.js";
|
|
|
21
22
|
import { OAuthModule } from "./modules/oauth/oauth.module.js";
|
|
22
23
|
import { ProfilesModule } from "./modules/profiles/profiles.module.js";
|
|
23
24
|
import { ProxyModule } from "./modules/proxy/proxy.module.js";
|
|
25
|
+
import { SettingsModule } from "./modules/settings/settings.module.js";
|
|
24
26
|
export class AppModule {
|
|
25
27
|
}
|
|
26
28
|
AppModule = _ts_decorate([
|
|
@@ -39,6 +41,8 @@ AppModule = _ts_decorate([
|
|
|
39
41
|
'.env'
|
|
40
42
|
]
|
|
41
43
|
}),
|
|
44
|
+
// Event emitter for SSE notifications
|
|
45
|
+
EventEmitterModule.forRoot(),
|
|
42
46
|
// Rate limiting
|
|
43
47
|
ThrottlerModule.forRoot([
|
|
44
48
|
{
|
|
@@ -62,6 +66,7 @@ AppModule = _ts_decorate([
|
|
|
62
66
|
McpModule,
|
|
63
67
|
ProfilesModule,
|
|
64
68
|
OAuthModule,
|
|
69
|
+
SettingsModule,
|
|
65
70
|
ProxyModule,
|
|
66
71
|
HealthModule,
|
|
67
72
|
DebugModule
|
package/dist/app.module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/app.module.ts"],"sourcesContent":["/**\n * Root Application Module\n *\n * Configures all NestJS modules for the Local MCP Gateway.\n * No authentication module - immediate access to all features.\n */\n\nimport { Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { ThrottlerModule } from '@nestjs/throttler';\nimport appConfig from './config/app.config.js';\nimport databaseConfig from './config/database.config.js';\nimport { DatabaseModule } from './modules/database/database.module.js';\nimport { DebugModule } from './modules/debug/debug.module.js';\nimport { HealthModule } from './modules/health/health.module.js';\nimport { McpModule } from './modules/mcp/mcp.module.js';\nimport { OAuthModule } from './modules/oauth/oauth.module.js';\nimport { ProfilesModule } from './modules/profiles/profiles.module.js';\nimport { ProxyModule } from './modules/proxy/proxy.module.js';\n\n@Module({\n imports: [\n // Configuration\n ConfigModule.forRoot({\n isGlobal: true,\n load: [appConfig, databaseConfig],\n envFilePath: ['../../.env', '.env.local', '.env'],\n }),\n\n // Rate limiting\n ThrottlerModule.forRoot([\n {\n name: 'short',\n ttl: 1000,\n limit: 10,\n },\n {\n name: 'medium',\n ttl: 10000,\n limit: 50,\n },\n {\n name: 'long',\n ttl: 60000,\n limit: 200,\n },\n ]),\n\n // Core modules (no authentication - immediate access)\n DatabaseModule,\n McpModule,\n ProfilesModule,\n OAuthModule, // For OAuth MCP servers, not user authentication\n ProxyModule,\n HealthModule,\n DebugModule,\n ],\n})\nexport class AppModule {}\n"],"names":["Module","ConfigModule","ThrottlerModule","appConfig","databaseConfig","DatabaseModule","DebugModule","HealthModule","McpModule","OAuthModule","ProfilesModule","ProxyModule","AppModule","imports","forRoot","isGlobal","load","envFilePath","name","ttl","limit"],"mappings":";;;;;;AAAA;;;;;CAKC,GAED,SAASA,MAAM,QAAQ,iBAAiB;AACxC,SAASC,YAAY,QAAQ,iBAAiB;AAC9C,SAASC,eAAe,QAAQ,oBAAoB;AACpD,OAAOC,eAAe,yBAAyB;AAC/C,OAAOC,oBAAoB,8BAA8B;AACzD,SAASC,cAAc,QAAQ,wCAAwC;AACvE,SAASC,WAAW,QAAQ,kCAAkC;AAC9D,SAASC,YAAY,QAAQ,oCAAoC;AACjE,SAASC,SAAS,QAAQ,8BAA8B;AACxD,SAASC,WAAW,QAAQ,kCAAkC;AAC9D,SAASC,cAAc,QAAQ,wCAAwC;AACvE,SAASC,WAAW,QAAQ,kCAAkC;
|
|
1
|
+
{"version":3,"sources":["../src/app.module.ts"],"sourcesContent":["/**\n * Root Application Module\n *\n * Configures all NestJS modules for the Local MCP Gateway.\n * No authentication module - immediate access to all features.\n */\n\nimport { Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { EventEmitterModule } from '@nestjs/event-emitter';\nimport { ThrottlerModule } from '@nestjs/throttler';\nimport appConfig from './config/app.config.js';\nimport databaseConfig from './config/database.config.js';\nimport { DatabaseModule } from './modules/database/database.module.js';\nimport { DebugModule } from './modules/debug/debug.module.js';\nimport { HealthModule } from './modules/health/health.module.js';\nimport { McpModule } from './modules/mcp/mcp.module.js';\nimport { OAuthModule } from './modules/oauth/oauth.module.js';\nimport { ProfilesModule } from './modules/profiles/profiles.module.js';\nimport { ProxyModule } from './modules/proxy/proxy.module.js';\nimport { SettingsModule } from './modules/settings/settings.module.js';\n\n@Module({\n imports: [\n // Configuration\n ConfigModule.forRoot({\n isGlobal: true,\n load: [appConfig, databaseConfig],\n envFilePath: ['../../.env', '.env.local', '.env'],\n }),\n\n // Event emitter for SSE notifications\n EventEmitterModule.forRoot(),\n\n // Rate limiting\n ThrottlerModule.forRoot([\n {\n name: 'short',\n ttl: 1000,\n limit: 10,\n },\n {\n name: 'medium',\n ttl: 10000,\n limit: 50,\n },\n {\n name: 'long',\n ttl: 60000,\n limit: 200,\n },\n ]),\n\n // Core modules (no authentication - immediate access)\n DatabaseModule,\n McpModule,\n ProfilesModule,\n OAuthModule, // For OAuth MCP servers, not user authentication\n SettingsModule, // Gateway settings\n ProxyModule,\n HealthModule,\n DebugModule,\n ],\n})\nexport class AppModule {}\n"],"names":["Module","ConfigModule","EventEmitterModule","ThrottlerModule","appConfig","databaseConfig","DatabaseModule","DebugModule","HealthModule","McpModule","OAuthModule","ProfilesModule","ProxyModule","SettingsModule","AppModule","imports","forRoot","isGlobal","load","envFilePath","name","ttl","limit"],"mappings":";;;;;;AAAA;;;;;CAKC,GAED,SAASA,MAAM,QAAQ,iBAAiB;AACxC,SAASC,YAAY,QAAQ,iBAAiB;AAC9C,SAASC,kBAAkB,QAAQ,wBAAwB;AAC3D,SAASC,eAAe,QAAQ,oBAAoB;AACpD,OAAOC,eAAe,yBAAyB;AAC/C,OAAOC,oBAAoB,8BAA8B;AACzD,SAASC,cAAc,QAAQ,wCAAwC;AACvE,SAASC,WAAW,QAAQ,kCAAkC;AAC9D,SAASC,YAAY,QAAQ,oCAAoC;AACjE,SAASC,SAAS,QAAQ,8BAA8B;AACxD,SAASC,WAAW,QAAQ,kCAAkC;AAC9D,SAASC,cAAc,QAAQ,wCAAwC;AACvE,SAASC,WAAW,QAAQ,kCAAkC;AAC9D,SAASC,cAAc,QAAQ,wCAAwC;AA4CvE,OAAO,MAAMC;AAAW;;;QAzCtBC,SAAS;YACP,gBAAgB;YAChBd,aAAae,OAAO,CAAC;gBACnBC,UAAU;gBACVC,MAAM;oBAACd;oBAAWC;iBAAe;gBACjCc,aAAa;oBAAC;oBAAc;oBAAc;iBAAO;YACnD;YAEA,sCAAsC;YACtCjB,mBAAmBc,OAAO;YAE1B,gBAAgB;YAChBb,gBAAgBa,OAAO,CAAC;gBACtB;oBACEI,MAAM;oBACNC,KAAK;oBACLC,OAAO;gBACT;gBACA;oBACEF,MAAM;oBACNC,KAAK;oBACLC,OAAO;gBACT;gBACA;oBACEF,MAAM;oBACNC,KAAK;oBACLC,OAAO;gBACT;aACD;YAED,sDAAsD;YACtDhB;YACAG;YACAE;YACAD;YACAG;YACAD;YACAJ;YACAD;SACD"}
|
|
@@ -19,12 +19,21 @@ function _ts_param(paramIndex, decorator) {
|
|
|
19
19
|
*/ import { ConflictException, forwardRef, Inject, Injectable, NotFoundException } from "@nestjs/common";
|
|
20
20
|
import { PrismaService } from "../database/prisma.service.js";
|
|
21
21
|
import { ProxyService } from "../proxy/proxy.service.js";
|
|
22
|
+
import { RESERVED_PROFILE_NAMES } from "../settings/settings.constants.js";
|
|
22
23
|
export class ProfilesService {
|
|
23
24
|
constructor(prisma, proxyService){
|
|
24
25
|
this.prisma = prisma;
|
|
25
26
|
this.proxyService = proxyService;
|
|
26
27
|
}
|
|
27
28
|
/**
|
|
29
|
+
* Validate profile name against reserved names
|
|
30
|
+
*/ validateProfileName(name) {
|
|
31
|
+
const lowerName = name.toLowerCase();
|
|
32
|
+
if (RESERVED_PROFILE_NAMES.some((reserved)=>reserved.toLowerCase() === lowerName)) {
|
|
33
|
+
throw new ConflictException(`Profile name "${name}" is reserved for system use`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
28
37
|
* Get all profiles
|
|
29
38
|
*/ async findAll() {
|
|
30
39
|
return this.prisma.profile.findMany({
|
|
@@ -94,6 +103,8 @@ export class ProfilesService {
|
|
|
94
103
|
/**
|
|
95
104
|
* Create a new profile
|
|
96
105
|
*/ async create(dto) {
|
|
106
|
+
// Validate against reserved names
|
|
107
|
+
this.validateProfileName(dto.name);
|
|
97
108
|
// Check for unique name
|
|
98
109
|
const existing = await this.prisma.profile.findUnique({
|
|
99
110
|
where: {
|
|
@@ -121,6 +132,10 @@ export class ProfilesService {
|
|
|
121
132
|
if (!profile) {
|
|
122
133
|
throw new NotFoundException(`Profile ${id} not found`);
|
|
123
134
|
}
|
|
135
|
+
// Validate new name against reserved names
|
|
136
|
+
if (dto.name) {
|
|
137
|
+
this.validateProfileName(dto.name);
|
|
138
|
+
}
|
|
124
139
|
// Check for unique name if changing
|
|
125
140
|
if (dto.name && dto.name !== profile.name) {
|
|
126
141
|
const existing = await this.prisma.profile.findUnique({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/modules/profiles/profiles.service.ts"],"sourcesContent":["/**\n * Profiles Service\n *\n * Business logic for profile management.\n */\n\nimport {\n ConflictException,\n forwardRef,\n Inject,\n Injectable,\n NotFoundException,\n} from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { ProxyService } from '../proxy/proxy.service.js';\n\ninterface CreateProfileDto {\n name: string;\n description?: string | null;\n}\n\ninterface UpdateProfileDto {\n name?: string;\n description?: string | null;\n}\n\ninterface AddServerToProfileDto {\n mcpServerId: string;\n order?: number;\n isActive?: boolean;\n}\n\ninterface UpdateServerInProfileDto {\n order?: number;\n isActive?: boolean;\n}\n\ninterface UpdateToolDto {\n toolName: string;\n isEnabled: boolean;\n customName?: string;\n customDescription?: string;\n customInputSchema?: unknown;\n}\n\n@Injectable()\nexport class ProfilesService {\n constructor(\n private readonly prisma: PrismaService,\n @Inject(forwardRef(() => ProxyService))\n private readonly proxyService: ProxyService\n ) {}\n\n /**\n * Get all profiles\n */\n async findAll() {\n return this.prisma.profile.findMany({\n include: {\n mcpServers: {\n include: {\n mcpServer: true,\n },\n orderBy: {\n order: 'asc',\n },\n },\n },\n orderBy: {\n name: 'asc',\n },\n });\n }\n\n /**\n * Get a specific profile by ID\n */\n async findById(id: string) {\n const profile = await this.prisma.profile.findUnique({\n where: { id },\n include: {\n mcpServers: {\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: {\n order: 'asc',\n },\n },\n },\n });\n\n if (!profile) {\n throw new NotFoundException(`Profile ${id} not found`);\n }\n\n return profile;\n }\n\n /**\n * Get a specific profile by name\n */\n async findByName(name: string) {\n const profile = await this.prisma.profile.findUnique({\n where: { name },\n include: {\n mcpServers: {\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: {\n order: 'asc',\n },\n },\n },\n });\n\n if (!profile) {\n throw new NotFoundException(`Profile with name \"${name}\" not found`);\n }\n\n return profile;\n }\n\n /**\n * Create a new profile\n */\n async create(dto: CreateProfileDto) {\n // Check for unique name\n const existing = await this.prisma.profile.findUnique({\n where: { name: dto.name },\n });\n\n if (existing) {\n throw new ConflictException(`Profile with name \"${dto.name}\" already exists`);\n }\n\n return this.prisma.profile.create({\n data: {\n name: dto.name,\n description: dto.description,\n },\n });\n }\n\n /**\n * Update a profile\n */\n async update(id: string, dto: UpdateProfileDto) {\n const profile = await this.prisma.profile.findUnique({ where: { id } });\n\n if (!profile) {\n throw new NotFoundException(`Profile ${id} not found`);\n }\n\n // Check for unique name if changing\n if (dto.name && dto.name !== profile.name) {\n const existing = await this.prisma.profile.findUnique({\n where: { name: dto.name },\n });\n\n if (existing) {\n throw new ConflictException(`Profile with name \"${dto.name}\" already exists`);\n }\n }\n\n return this.prisma.profile.update({\n where: { id },\n data: dto,\n });\n }\n\n /**\n * Delete a profile\n */\n async delete(id: string) {\n const profile = await this.prisma.profile.findUnique({ where: { id } });\n\n if (!profile) {\n throw new NotFoundException(`Profile ${id} not found`);\n }\n\n // Prevent deleting default profile\n if (profile.name === 'default') {\n throw new ConflictException('Cannot delete the default profile');\n }\n\n await this.prisma.profile.delete({ where: { id } });\n }\n\n /**\n * Get servers for a profile\n */\n async getServers(profileId: string) {\n const profile = await this.prisma.profile.findUnique({\n where: { id: profileId },\n });\n\n if (!profile) {\n throw new NotFoundException(`Profile ${profileId} not found`);\n }\n\n return this.prisma.profileMcpServer.findMany({\n where: { profileId },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: {\n order: 'asc',\n },\n });\n }\n\n /**\n * Add an MCP server to a profile\n */\n async addServer(profileId: string, dto: AddServerToProfileDto) {\n // Check profile exists\n const profile = await this.prisma.profile.findUnique({ where: { id: profileId } });\n if (!profile) {\n throw new NotFoundException(`Profile ${profileId} not found`);\n }\n\n // Check server exists\n const server = await this.prisma.mcpServer.findUnique({ where: { id: dto.mcpServerId } });\n if (!server) {\n throw new NotFoundException(`MCP server ${dto.mcpServerId} not found`);\n }\n\n // Check if already linked\n const existing = await this.prisma.profileMcpServer.findUnique({\n where: {\n profileId_mcpServerId: {\n profileId,\n mcpServerId: dto.mcpServerId,\n },\n },\n });\n\n if (existing) {\n throw new ConflictException('Server is already in this profile');\n }\n\n return this.prisma.profileMcpServer.create({\n data: {\n profileId,\n mcpServerId: dto.mcpServerId,\n order: dto.order ?? 0,\n isActive: dto.isActive ?? true,\n },\n include: {\n mcpServer: true,\n },\n });\n }\n\n /**\n * Update a server in a profile\n */\n async updateServer(profileId: string, serverId: string, dto: UpdateServerInProfileDto) {\n const link = await this.prisma.profileMcpServer.findUnique({\n where: {\n profileId_mcpServerId: {\n profileId,\n mcpServerId: serverId,\n },\n },\n });\n\n if (!link) {\n throw new NotFoundException('Server is not in this profile');\n }\n\n return this.prisma.profileMcpServer.update({\n where: { id: link.id },\n data: dto,\n include: {\n mcpServer: true,\n },\n });\n }\n\n /**\n * Remove a server from a profile\n */\n async removeServer(profileId: string, serverId: string) {\n const link = await this.prisma.profileMcpServer.findUnique({\n where: {\n profileId_mcpServerId: {\n profileId,\n mcpServerId: serverId,\n },\n },\n });\n\n if (!link) {\n throw new NotFoundException('Server is not in this profile');\n }\n\n await this.prisma.profileMcpServer.delete({\n where: { id: link.id },\n });\n }\n\n /**\n * Get tools for a server in a profile with customizations\n */\n async getServerTools(profileId: string, serverId: string, _refresh = false) {\n // Check profile exists\n const profile = await this.prisma.profile.findUnique({ where: { id: profileId } });\n if (!profile) {\n throw new NotFoundException(`Profile ${profileId} not found`);\n }\n\n // Check server link exists\n const link = await this.prisma.profileMcpServer.findUnique({\n where: {\n profileId_mcpServerId: {\n profileId,\n mcpServerId: serverId,\n },\n },\n include: {\n tools: true,\n },\n });\n\n if (!link) {\n throw new NotFoundException('Server is not in this profile');\n }\n\n // Get tools from the MCP server\n const serverTools = await this.proxyService.getToolsForServer(serverId);\n\n // Apply customizations\n const tools = serverTools.map((tool) => {\n const customization = link.tools.find((t) => t.toolName === tool.name);\n\n return {\n name: tool.name,\n original: {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n },\n customized: customization\n ? {\n name: customization.customName || tool.name,\n description: customization.customDescription || tool.description,\n inputSchema: tool.inputSchema,\n }\n : null,\n isEnabled: customization?.isEnabled ?? true,\n hasChanges: !!customization,\n changeType: customization ? 'modified' : 'unchanged',\n };\n });\n\n return { tools };\n }\n\n /**\n * Update tool customizations for a server in a profile\n */\n async updateServerTools(profileId: string, serverId: string, tools: UpdateToolDto[]) {\n // Check profile exists\n const profile = await this.prisma.profile.findUnique({ where: { id: profileId } });\n if (!profile) {\n throw new NotFoundException(`Profile ${profileId} not found`);\n }\n\n // Check server link exists\n const link = await this.prisma.profileMcpServer.findUnique({\n where: {\n profileId_mcpServerId: {\n profileId,\n mcpServerId: serverId,\n },\n },\n });\n\n if (!link) {\n throw new NotFoundException('Server is not in this profile');\n }\n\n // Use transaction to update tool customizations\n await this.prisma.$transaction(async (tx) => {\n // Delete existing tool customizations for this profile-server link\n await tx.profileMcpServerTool.deleteMany({\n where: { profileMcpServerId: link.id },\n });\n\n // Create new tool customizations (only for tools that have changes)\n const toolsToCreate = tools.filter(\n (tool) =>\n !tool.isEnabled || tool.customName || tool.customDescription || tool.customInputSchema\n );\n\n if (toolsToCreate.length > 0) {\n await tx.profileMcpServerTool.createMany({\n data: toolsToCreate.map((tool) => ({\n profileMcpServerId: link.id,\n toolName: tool.toolName,\n isEnabled: tool.isEnabled,\n customName: tool.customName || null,\n customDescription: tool.customDescription || null,\n customInputSchema: tool.customInputSchema\n ? JSON.stringify(tool.customInputSchema)\n : null,\n })),\n });\n }\n });\n\n // Return updated tools by calling getServerTools\n return this.getServerTools(profileId, serverId);\n }\n}\n"],"names":["ConflictException","forwardRef","Inject","Injectable","NotFoundException","PrismaService","ProxyService","ProfilesService","prisma","proxyService","findAll","profile","findMany","include","mcpServers","mcpServer","orderBy","order","name","findById","id","findUnique","where","tools","findByName","create","dto","existing","data","description","update","delete","getServers","profileId","profileMcpServer","addServer","server","mcpServerId","profileId_mcpServerId","isActive","updateServer","serverId","link","removeServer","getServerTools","_refresh","serverTools","getToolsForServer","map","tool","customization","find","t","toolName","original","inputSchema","customized","customName","customDescription","isEnabled","hasChanges","changeType","updateServerTools","$transaction","tx","profileMcpServerTool","deleteMany","profileMcpServerId","toolsToCreate","filter","customInputSchema","length","createMany","JSON","stringify"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/modules/profiles/profiles.service.ts"],"sourcesContent":["/**\n * Profiles Service\n *\n * Business logic for profile management.\n */\n\nimport {\n ConflictException,\n forwardRef,\n Inject,\n Injectable,\n NotFoundException,\n} from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { ProxyService } from '../proxy/proxy.service.js';\nimport { RESERVED_PROFILE_NAMES } from '../settings/settings.constants.js';\n\ninterface CreateProfileDto {\n name: string;\n description?: string | null;\n}\n\ninterface UpdateProfileDto {\n name?: string;\n description?: string | null;\n}\n\ninterface AddServerToProfileDto {\n mcpServerId: string;\n order?: number;\n isActive?: boolean;\n}\n\ninterface UpdateServerInProfileDto {\n order?: number;\n isActive?: boolean;\n}\n\ninterface UpdateToolDto {\n toolName: string;\n isEnabled: boolean;\n customName?: string;\n customDescription?: string;\n customInputSchema?: unknown;\n}\n\n@Injectable()\nexport class ProfilesService {\n constructor(\n private readonly prisma: PrismaService,\n @Inject(forwardRef(() => ProxyService))\n private readonly proxyService: ProxyService\n ) {}\n\n /**\n * Validate profile name against reserved names\n */\n private validateProfileName(name: string): void {\n const lowerName = name.toLowerCase();\n if (RESERVED_PROFILE_NAMES.some((reserved) => reserved.toLowerCase() === lowerName)) {\n throw new ConflictException(`Profile name \"${name}\" is reserved for system use`);\n }\n }\n\n /**\n * Get all profiles\n */\n async findAll() {\n return this.prisma.profile.findMany({\n include: {\n mcpServers: {\n include: {\n mcpServer: true,\n },\n orderBy: {\n order: 'asc',\n },\n },\n },\n orderBy: {\n name: 'asc',\n },\n });\n }\n\n /**\n * Get a specific profile by ID\n */\n async findById(id: string) {\n const profile = await this.prisma.profile.findUnique({\n where: { id },\n include: {\n mcpServers: {\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: {\n order: 'asc',\n },\n },\n },\n });\n\n if (!profile) {\n throw new NotFoundException(`Profile ${id} not found`);\n }\n\n return profile;\n }\n\n /**\n * Get a specific profile by name\n */\n async findByName(name: string) {\n const profile = await this.prisma.profile.findUnique({\n where: { name },\n include: {\n mcpServers: {\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: {\n order: 'asc',\n },\n },\n },\n });\n\n if (!profile) {\n throw new NotFoundException(`Profile with name \"${name}\" not found`);\n }\n\n return profile;\n }\n\n /**\n * Create a new profile\n */\n async create(dto: CreateProfileDto) {\n // Validate against reserved names\n this.validateProfileName(dto.name);\n\n // Check for unique name\n const existing = await this.prisma.profile.findUnique({\n where: { name: dto.name },\n });\n\n if (existing) {\n throw new ConflictException(`Profile with name \"${dto.name}\" already exists`);\n }\n\n return this.prisma.profile.create({\n data: {\n name: dto.name,\n description: dto.description,\n },\n });\n }\n\n /**\n * Update a profile\n */\n async update(id: string, dto: UpdateProfileDto) {\n const profile = await this.prisma.profile.findUnique({ where: { id } });\n\n if (!profile) {\n throw new NotFoundException(`Profile ${id} not found`);\n }\n\n // Validate new name against reserved names\n if (dto.name) {\n this.validateProfileName(dto.name);\n }\n\n // Check for unique name if changing\n if (dto.name && dto.name !== profile.name) {\n const existing = await this.prisma.profile.findUnique({\n where: { name: dto.name },\n });\n\n if (existing) {\n throw new ConflictException(`Profile with name \"${dto.name}\" already exists`);\n }\n }\n\n return this.prisma.profile.update({\n where: { id },\n data: dto,\n });\n }\n\n /**\n * Delete a profile\n */\n async delete(id: string) {\n const profile = await this.prisma.profile.findUnique({ where: { id } });\n\n if (!profile) {\n throw new NotFoundException(`Profile ${id} not found`);\n }\n\n // Prevent deleting default profile\n if (profile.name === 'default') {\n throw new ConflictException('Cannot delete the default profile');\n }\n\n await this.prisma.profile.delete({ where: { id } });\n }\n\n /**\n * Get servers for a profile\n */\n async getServers(profileId: string) {\n const profile = await this.prisma.profile.findUnique({\n where: { id: profileId },\n });\n\n if (!profile) {\n throw new NotFoundException(`Profile ${profileId} not found`);\n }\n\n return this.prisma.profileMcpServer.findMany({\n where: { profileId },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: {\n order: 'asc',\n },\n });\n }\n\n /**\n * Add an MCP server to a profile\n */\n async addServer(profileId: string, dto: AddServerToProfileDto) {\n // Check profile exists\n const profile = await this.prisma.profile.findUnique({ where: { id: profileId } });\n if (!profile) {\n throw new NotFoundException(`Profile ${profileId} not found`);\n }\n\n // Check server exists\n const server = await this.prisma.mcpServer.findUnique({ where: { id: dto.mcpServerId } });\n if (!server) {\n throw new NotFoundException(`MCP server ${dto.mcpServerId} not found`);\n }\n\n // Check if already linked\n const existing = await this.prisma.profileMcpServer.findUnique({\n where: {\n profileId_mcpServerId: {\n profileId,\n mcpServerId: dto.mcpServerId,\n },\n },\n });\n\n if (existing) {\n throw new ConflictException('Server is already in this profile');\n }\n\n return this.prisma.profileMcpServer.create({\n data: {\n profileId,\n mcpServerId: dto.mcpServerId,\n order: dto.order ?? 0,\n isActive: dto.isActive ?? true,\n },\n include: {\n mcpServer: true,\n },\n });\n }\n\n /**\n * Update a server in a profile\n */\n async updateServer(profileId: string, serverId: string, dto: UpdateServerInProfileDto) {\n const link = await this.prisma.profileMcpServer.findUnique({\n where: {\n profileId_mcpServerId: {\n profileId,\n mcpServerId: serverId,\n },\n },\n });\n\n if (!link) {\n throw new NotFoundException('Server is not in this profile');\n }\n\n return this.prisma.profileMcpServer.update({\n where: { id: link.id },\n data: dto,\n include: {\n mcpServer: true,\n },\n });\n }\n\n /**\n * Remove a server from a profile\n */\n async removeServer(profileId: string, serverId: string) {\n const link = await this.prisma.profileMcpServer.findUnique({\n where: {\n profileId_mcpServerId: {\n profileId,\n mcpServerId: serverId,\n },\n },\n });\n\n if (!link) {\n throw new NotFoundException('Server is not in this profile');\n }\n\n await this.prisma.profileMcpServer.delete({\n where: { id: link.id },\n });\n }\n\n /**\n * Get tools for a server in a profile with customizations\n */\n async getServerTools(profileId: string, serverId: string, _refresh = false) {\n // Check profile exists\n const profile = await this.prisma.profile.findUnique({ where: { id: profileId } });\n if (!profile) {\n throw new NotFoundException(`Profile ${profileId} not found`);\n }\n\n // Check server link exists\n const link = await this.prisma.profileMcpServer.findUnique({\n where: {\n profileId_mcpServerId: {\n profileId,\n mcpServerId: serverId,\n },\n },\n include: {\n tools: true,\n },\n });\n\n if (!link) {\n throw new NotFoundException('Server is not in this profile');\n }\n\n // Get tools from the MCP server\n const serverTools = await this.proxyService.getToolsForServer(serverId);\n\n // Apply customizations\n const tools = serverTools.map((tool) => {\n const customization = link.tools.find((t) => t.toolName === tool.name);\n\n return {\n name: tool.name,\n original: {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n },\n customized: customization\n ? {\n name: customization.customName || tool.name,\n description: customization.customDescription || tool.description,\n inputSchema: tool.inputSchema,\n }\n : null,\n isEnabled: customization?.isEnabled ?? true,\n hasChanges: !!customization,\n changeType: customization ? 'modified' : 'unchanged',\n };\n });\n\n return { tools };\n }\n\n /**\n * Update tool customizations for a server in a profile\n */\n async updateServerTools(profileId: string, serverId: string, tools: UpdateToolDto[]) {\n // Check profile exists\n const profile = await this.prisma.profile.findUnique({ where: { id: profileId } });\n if (!profile) {\n throw new NotFoundException(`Profile ${profileId} not found`);\n }\n\n // Check server link exists\n const link = await this.prisma.profileMcpServer.findUnique({\n where: {\n profileId_mcpServerId: {\n profileId,\n mcpServerId: serverId,\n },\n },\n });\n\n if (!link) {\n throw new NotFoundException('Server is not in this profile');\n }\n\n // Use transaction to update tool customizations\n await this.prisma.$transaction(async (tx) => {\n // Delete existing tool customizations for this profile-server link\n await tx.profileMcpServerTool.deleteMany({\n where: { profileMcpServerId: link.id },\n });\n\n // Create new tool customizations (only for tools that have changes)\n const toolsToCreate = tools.filter(\n (tool) =>\n !tool.isEnabled || tool.customName || tool.customDescription || tool.customInputSchema\n );\n\n if (toolsToCreate.length > 0) {\n await tx.profileMcpServerTool.createMany({\n data: toolsToCreate.map((tool) => ({\n profileMcpServerId: link.id,\n toolName: tool.toolName,\n isEnabled: tool.isEnabled,\n customName: tool.customName || null,\n customDescription: tool.customDescription || null,\n customInputSchema: tool.customInputSchema\n ? JSON.stringify(tool.customInputSchema)\n : null,\n })),\n });\n }\n });\n\n // Return updated tools by calling getServerTools\n return this.getServerTools(profileId, serverId);\n }\n}\n"],"names":["ConflictException","forwardRef","Inject","Injectable","NotFoundException","PrismaService","ProxyService","RESERVED_PROFILE_NAMES","ProfilesService","prisma","proxyService","validateProfileName","name","lowerName","toLowerCase","some","reserved","findAll","profile","findMany","include","mcpServers","mcpServer","orderBy","order","findById","id","findUnique","where","tools","findByName","create","dto","existing","data","description","update","delete","getServers","profileId","profileMcpServer","addServer","server","mcpServerId","profileId_mcpServerId","isActive","updateServer","serverId","link","removeServer","getServerTools","_refresh","serverTools","getToolsForServer","map","tool","customization","find","t","toolName","original","inputSchema","customized","customName","customDescription","isEnabled","hasChanges","changeType","updateServerTools","$transaction","tx","profileMcpServerTool","deleteMany","profileMcpServerId","toolsToCreate","filter","customInputSchema","length","createMany","JSON","stringify"],"mappings":"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"}
|
|
@@ -15,12 +15,165 @@ function _ts_param(paramIndex, decorator) {
|
|
|
15
15
|
/**
|
|
16
16
|
* Proxy Controller
|
|
17
17
|
*
|
|
18
|
-
* MCP proxy endpoints for profiles.
|
|
19
|
-
|
|
18
|
+
* MCP proxy endpoints for profiles and gateway.
|
|
19
|
+
* Supports MCP Streamable HTTP transport (2025-11-25 spec) with SSE notifications.
|
|
20
|
+
*
|
|
21
|
+
* @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http
|
|
22
|
+
*/ import { Body, Controller, Get, HttpCode, HttpStatus, NotFoundException, Param, Post, Req, Res } from "@nestjs/common";
|
|
23
|
+
import { EventEmitter2 } from "@nestjs/event-emitter";
|
|
24
|
+
import { fromEvent, map } from "rxjs";
|
|
25
|
+
import { GATEWAY_PROFILE_CHANGED, SettingsService } from "../settings/settings.service.js";
|
|
20
26
|
import { ProxyService } from "./proxy.service.js";
|
|
21
27
|
export class ProxyController {
|
|
22
|
-
constructor(proxyService){
|
|
28
|
+
constructor(proxyService, settingsService, eventEmitter){
|
|
23
29
|
this.proxyService = proxyService;
|
|
30
|
+
this.settingsService = settingsService;
|
|
31
|
+
this.eventEmitter = eventEmitter;
|
|
32
|
+
}
|
|
33
|
+
// =========================================
|
|
34
|
+
// Gateway Endpoints (must come BEFORE :profileName routes)
|
|
35
|
+
// =========================================
|
|
36
|
+
/**
|
|
37
|
+
* SSE endpoint for gateway notifications (dedicated URL)
|
|
38
|
+
* Sends notifications/tools/list_changed when the active profile changes.
|
|
39
|
+
*/ streamGatewaySse(req, res) {
|
|
40
|
+
return this.streamGatewayEvents(req, res);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* POST handler for gateway SSE - handles JSON-RPC requests via SSE transport
|
|
44
|
+
*/ async handleGatewaySseRequest(request) {
|
|
45
|
+
return this.handleGatewayRequest(request);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get gateway info - proxies to default profile info
|
|
49
|
+
*/ async getGatewayInfo() {
|
|
50
|
+
const profileName = await this.settingsService.getDefaultGatewayProfile();
|
|
51
|
+
try {
|
|
52
|
+
const info = await this.proxyService.getProfileInfo(profileName);
|
|
53
|
+
return {
|
|
54
|
+
...info,
|
|
55
|
+
gateway: {
|
|
56
|
+
activeProfile: profileName
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error instanceof NotFoundException) {
|
|
61
|
+
throw new NotFoundException(`Default gateway profile "${profileName}" not found. Please select a valid profile in settings.`);
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* GET handler for gateway endpoint
|
|
68
|
+
*
|
|
69
|
+
* Streamable HTTP content negotiation:
|
|
70
|
+
* - Accept: text/event-stream -> Returns SSE stream for notifications
|
|
71
|
+
* - Otherwise -> Returns JSON usage instructions
|
|
72
|
+
*
|
|
73
|
+
* @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http
|
|
74
|
+
*/ async getGatewayEndpoint(req, res) {
|
|
75
|
+
// Streamable HTTP: check Accept header for content negotiation
|
|
76
|
+
const acceptHeader = req.headers.accept || '';
|
|
77
|
+
if (acceptHeader.includes('text/event-stream')) {
|
|
78
|
+
return this.streamGatewayEvents(req, res);
|
|
79
|
+
}
|
|
80
|
+
// Return JSON usage info
|
|
81
|
+
const profileName = await this.settingsService.getDefaultGatewayProfile();
|
|
82
|
+
try {
|
|
83
|
+
const info = await this.proxyService.getProfileInfo(profileName);
|
|
84
|
+
return res.json({
|
|
85
|
+
message: 'This is the MCP Gateway endpoint. Use POST for JSON-RPC requests.',
|
|
86
|
+
usage: {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
contentType: 'application/json',
|
|
89
|
+
body: {
|
|
90
|
+
jsonrpc: '2.0',
|
|
91
|
+
method: 'tools/list',
|
|
92
|
+
id: 1
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
endpoints: {
|
|
96
|
+
sse: '/api/mcp/gateway/sse',
|
|
97
|
+
http: '/api/mcp/gateway'
|
|
98
|
+
},
|
|
99
|
+
gateway: {
|
|
100
|
+
activeProfile: profileName,
|
|
101
|
+
settingsEndpoint: '/api/settings/default-gateway-profile'
|
|
102
|
+
},
|
|
103
|
+
profile: {
|
|
104
|
+
name: profileName,
|
|
105
|
+
toolCount: info.tools.length,
|
|
106
|
+
serverCount: info.serverStatus.total,
|
|
107
|
+
connectedServers: info.serverStatus.connected
|
|
108
|
+
},
|
|
109
|
+
infoEndpoint: '/api/mcp/gateway/info'
|
|
110
|
+
});
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error instanceof NotFoundException) {
|
|
113
|
+
throw new NotFoundException(`Default gateway profile "${profileName}" not found. Please select a valid profile in settings.`);
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Stream SSE events for gateway notifications.
|
|
120
|
+
* Sends notifications/tools/list_changed when the active profile changes.
|
|
121
|
+
*
|
|
122
|
+
* Follows MCP Streamable HTTP transport (2025-11-25):
|
|
123
|
+
* - No endpoint event needed (unified endpoint)
|
|
124
|
+
* - Simple data: format without event: prefix
|
|
125
|
+
*
|
|
126
|
+
* @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http
|
|
127
|
+
*/ streamGatewayEvents(req, res) {
|
|
128
|
+
// Set SSE headers
|
|
129
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
130
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
131
|
+
res.setHeader('Connection', 'keep-alive');
|
|
132
|
+
res.setHeader('X-Accel-Buffering', 'no'); // For nginx
|
|
133
|
+
// Streamable HTTP - no endpoint event needed
|
|
134
|
+
// Just send a comment to establish connection
|
|
135
|
+
res.write(': connected\n\n');
|
|
136
|
+
// Subscribe to profile change events
|
|
137
|
+
const subscription = fromEvent(this.eventEmitter, GATEWAY_PROFILE_CHANGED).pipe(map(()=>({
|
|
138
|
+
jsonrpc: '2.0',
|
|
139
|
+
method: 'notifications/tools/list_changed'
|
|
140
|
+
}))).subscribe((notification)=>{
|
|
141
|
+
// Streamable HTTP - simple data format, no event prefix
|
|
142
|
+
res.write(`data: ${JSON.stringify(notification)}\n\n`);
|
|
143
|
+
});
|
|
144
|
+
// Cleanup on client disconnect
|
|
145
|
+
req.on('close', ()=>{
|
|
146
|
+
subscription.unsubscribe();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* MCP JSON-RPC endpoint for the gateway - proxies to default profile
|
|
151
|
+
*/ async handleGatewayRequest(request) {
|
|
152
|
+
const profileName = await this.settingsService.getDefaultGatewayProfile();
|
|
153
|
+
try {
|
|
154
|
+
return await this.proxyService.handleRequest(profileName, request);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error instanceof NotFoundException) {
|
|
157
|
+
throw new NotFoundException(`Default gateway profile "${profileName}" not found. Please select a valid profile in settings.`);
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// =========================================
|
|
163
|
+
// Profile-specific Endpoints (existing)
|
|
164
|
+
// =========================================
|
|
165
|
+
/**
|
|
166
|
+
* SSE endpoint for profile notifications (dedicated URL)
|
|
167
|
+
* Sends notifications/tools/list_changed when the active gateway profile changes.
|
|
168
|
+
*/ async streamProfileSse(profileName, req, res) {
|
|
169
|
+
// Validate profile exists
|
|
170
|
+
await this.proxyService.getProfileInfo(profileName);
|
|
171
|
+
return this.streamGatewayEvents(req, res);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* POST handler for profile SSE - handles JSON-RPC requests via SSE transport
|
|
175
|
+
*/ async handleProfileSseRequest(profileName, request) {
|
|
176
|
+
return this.handleMcpRequest(profileName, request);
|
|
24
177
|
}
|
|
25
178
|
/**
|
|
26
179
|
* Get profile info with aggregated tools and server status
|
|
@@ -28,10 +181,23 @@ export class ProxyController {
|
|
|
28
181
|
return this.proxyService.getProfileInfo(profileName);
|
|
29
182
|
}
|
|
30
183
|
/**
|
|
31
|
-
* GET handler for MCP endpoint
|
|
32
|
-
|
|
184
|
+
* GET handler for MCP endpoint
|
|
185
|
+
*
|
|
186
|
+
* Streamable HTTP content negotiation:
|
|
187
|
+
* - Accept: text/event-stream -> Returns SSE stream for notifications
|
|
188
|
+
* - Otherwise -> Returns JSON usage instructions
|
|
189
|
+
*
|
|
190
|
+
* @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http
|
|
191
|
+
*/ async getMcpEndpoint(profileName, req, res) {
|
|
192
|
+
// Validate profile exists first
|
|
33
193
|
const info = await this.proxyService.getProfileInfo(profileName);
|
|
34
|
-
|
|
194
|
+
// Streamable HTTP: check Accept header for content negotiation
|
|
195
|
+
const acceptHeader = req.headers.accept || '';
|
|
196
|
+
if (acceptHeader.includes('text/event-stream')) {
|
|
197
|
+
return this.streamGatewayEvents(req, res);
|
|
198
|
+
}
|
|
199
|
+
// Return JSON usage info
|
|
200
|
+
return res.json({
|
|
35
201
|
message: 'This is an MCP (Model Context Protocol) endpoint. Use POST for JSON-RPC requests.',
|
|
36
202
|
usage: {
|
|
37
203
|
method: 'POST',
|
|
@@ -42,6 +208,10 @@ export class ProxyController {
|
|
|
42
208
|
id: 1
|
|
43
209
|
}
|
|
44
210
|
},
|
|
211
|
+
endpoints: {
|
|
212
|
+
sse: `/api/mcp/${profileName}/sse`,
|
|
213
|
+
http: `/api/mcp/${profileName}`
|
|
214
|
+
},
|
|
45
215
|
profile: {
|
|
46
216
|
name: profileName,
|
|
47
217
|
toolCount: info.tools.length,
|
|
@@ -49,7 +219,7 @@ export class ProxyController {
|
|
|
49
219
|
connectedServers: info.serverStatus.connected
|
|
50
220
|
},
|
|
51
221
|
infoEndpoint: `/api/mcp/${profileName}/info`
|
|
52
|
-
};
|
|
222
|
+
});
|
|
53
223
|
}
|
|
54
224
|
/**
|
|
55
225
|
* MCP JSON-RPC endpoint for a profile
|
|
@@ -57,6 +227,79 @@ export class ProxyController {
|
|
|
57
227
|
return this.proxyService.handleRequest(profileName, request);
|
|
58
228
|
}
|
|
59
229
|
}
|
|
230
|
+
_ts_decorate([
|
|
231
|
+
Get('gateway/sse'),
|
|
232
|
+
_ts_param(0, Req()),
|
|
233
|
+
_ts_param(1, Res()),
|
|
234
|
+
_ts_metadata("design:type", Function),
|
|
235
|
+
_ts_metadata("design:paramtypes", [
|
|
236
|
+
typeof Request === "undefined" ? Object : Request,
|
|
237
|
+
typeof Response === "undefined" ? Object : Response
|
|
238
|
+
]),
|
|
239
|
+
_ts_metadata("design:returntype", void 0)
|
|
240
|
+
], ProxyController.prototype, "streamGatewaySse", null);
|
|
241
|
+
_ts_decorate([
|
|
242
|
+
Post('gateway/sse'),
|
|
243
|
+
HttpCode(HttpStatus.OK),
|
|
244
|
+
_ts_param(0, Body()),
|
|
245
|
+
_ts_metadata("design:type", Function),
|
|
246
|
+
_ts_metadata("design:paramtypes", [
|
|
247
|
+
typeof McpRequest === "undefined" ? Object : McpRequest
|
|
248
|
+
]),
|
|
249
|
+
_ts_metadata("design:returntype", Promise)
|
|
250
|
+
], ProxyController.prototype, "handleGatewaySseRequest", null);
|
|
251
|
+
_ts_decorate([
|
|
252
|
+
Get('gateway/info'),
|
|
253
|
+
_ts_metadata("design:type", Function),
|
|
254
|
+
_ts_metadata("design:paramtypes", []),
|
|
255
|
+
_ts_metadata("design:returntype", Promise)
|
|
256
|
+
], ProxyController.prototype, "getGatewayInfo", null);
|
|
257
|
+
_ts_decorate([
|
|
258
|
+
Get('gateway'),
|
|
259
|
+
_ts_param(0, Req()),
|
|
260
|
+
_ts_param(1, Res()),
|
|
261
|
+
_ts_metadata("design:type", Function),
|
|
262
|
+
_ts_metadata("design:paramtypes", [
|
|
263
|
+
typeof Request === "undefined" ? Object : Request,
|
|
264
|
+
typeof Response === "undefined" ? Object : Response
|
|
265
|
+
]),
|
|
266
|
+
_ts_metadata("design:returntype", Promise)
|
|
267
|
+
], ProxyController.prototype, "getGatewayEndpoint", null);
|
|
268
|
+
_ts_decorate([
|
|
269
|
+
Post('gateway'),
|
|
270
|
+
HttpCode(HttpStatus.OK),
|
|
271
|
+
_ts_param(0, Body()),
|
|
272
|
+
_ts_metadata("design:type", Function),
|
|
273
|
+
_ts_metadata("design:paramtypes", [
|
|
274
|
+
typeof McpRequest === "undefined" ? Object : McpRequest
|
|
275
|
+
]),
|
|
276
|
+
_ts_metadata("design:returntype", Promise)
|
|
277
|
+
], ProxyController.prototype, "handleGatewayRequest", null);
|
|
278
|
+
_ts_decorate([
|
|
279
|
+
Get(':profileName/sse'),
|
|
280
|
+
_ts_param(0, Param('profileName')),
|
|
281
|
+
_ts_param(1, Req()),
|
|
282
|
+
_ts_param(2, Res()),
|
|
283
|
+
_ts_metadata("design:type", Function),
|
|
284
|
+
_ts_metadata("design:paramtypes", [
|
|
285
|
+
String,
|
|
286
|
+
typeof Request === "undefined" ? Object : Request,
|
|
287
|
+
typeof Response === "undefined" ? Object : Response
|
|
288
|
+
]),
|
|
289
|
+
_ts_metadata("design:returntype", Promise)
|
|
290
|
+
], ProxyController.prototype, "streamProfileSse", null);
|
|
291
|
+
_ts_decorate([
|
|
292
|
+
Post(':profileName/sse'),
|
|
293
|
+
HttpCode(HttpStatus.OK),
|
|
294
|
+
_ts_param(0, Param('profileName')),
|
|
295
|
+
_ts_param(1, Body()),
|
|
296
|
+
_ts_metadata("design:type", Function),
|
|
297
|
+
_ts_metadata("design:paramtypes", [
|
|
298
|
+
String,
|
|
299
|
+
typeof McpRequest === "undefined" ? Object : McpRequest
|
|
300
|
+
]),
|
|
301
|
+
_ts_metadata("design:returntype", Promise)
|
|
302
|
+
], ProxyController.prototype, "handleProfileSseRequest", null);
|
|
60
303
|
_ts_decorate([
|
|
61
304
|
Get(':profileName/info'),
|
|
62
305
|
_ts_param(0, Param('profileName')),
|
|
@@ -69,9 +312,13 @@ _ts_decorate([
|
|
|
69
312
|
_ts_decorate([
|
|
70
313
|
Get(':profileName'),
|
|
71
314
|
_ts_param(0, Param('profileName')),
|
|
315
|
+
_ts_param(1, Req()),
|
|
316
|
+
_ts_param(2, Res()),
|
|
72
317
|
_ts_metadata("design:type", Function),
|
|
73
318
|
_ts_metadata("design:paramtypes", [
|
|
74
|
-
String
|
|
319
|
+
String,
|
|
320
|
+
typeof Request === "undefined" ? Object : Request,
|
|
321
|
+
typeof Response === "undefined" ? Object : Response
|
|
75
322
|
]),
|
|
76
323
|
_ts_metadata("design:returntype", Promise)
|
|
77
324
|
], ProxyController.prototype, "getMcpEndpoint", null);
|
|
@@ -91,7 +338,9 @@ ProxyController = _ts_decorate([
|
|
|
91
338
|
Controller('mcp'),
|
|
92
339
|
_ts_metadata("design:type", Function),
|
|
93
340
|
_ts_metadata("design:paramtypes", [
|
|
94
|
-
typeof ProxyService === "undefined" ? Object : ProxyService
|
|
341
|
+
typeof ProxyService === "undefined" ? Object : ProxyService,
|
|
342
|
+
typeof SettingsService === "undefined" ? Object : SettingsService,
|
|
343
|
+
typeof EventEmitter2 === "undefined" ? Object : EventEmitter2
|
|
95
344
|
])
|
|
96
345
|
], ProxyController);
|
|
97
346
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/modules/proxy/proxy.controller.ts"],"sourcesContent":["/**\n * Proxy Controller\n *\n * MCP proxy endpoints for profiles.\n */\n\nimport { Body, Controller, Get, HttpCode, HttpStatus, Param, Post } from '@nestjs/common';\nimport type { McpRequest, McpResponse } from './proxy.service.js';\nimport { ProxyService } from './proxy.service.js';\n\n@Controller('mcp')\nexport class ProxyController {\n constructor(private readonly proxyService: ProxyService) {}\n\n /**\n * Get profile info with aggregated tools and server status\n */\n @Get(':profileName/info')\n async getProfileInfo(@Param('profileName') profileName: string) {\n return this.proxyService.getProfileInfo(profileName);\n }\n\n /**\n * GET handler for MCP endpoint - returns usage instructions instead of 404\n */\n @Get(':profileName')\n async getMcpEndpoint(@Param('profileName') profileName: string) {\n const info = await this.proxyService.getProfileInfo(profileName);\n return {\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: {\n jsonrpc: '2.0',\n method: 'tools/list',\n id: 1,\n },\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/${profileName}/info`,\n };\n }\n\n /**\n * MCP JSON-RPC endpoint for a profile\n */\n @Post(':profileName')\n @HttpCode(HttpStatus.OK)\n async handleMcpRequest(\n @Param('profileName') profileName: string,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n return this.proxyService.handleRequest(profileName, request);\n }\n}\n"],"names":["Body","Controller","Get","HttpCode","HttpStatus","Param","Post","ProxyService","ProxyController","proxyService","getProfileInfo","profileName","getMcpEndpoint","info","message","usage","method","contentType","body","jsonrpc","id","profile","name","toolCount","tools","length","serverCount","serverStatus","total","connectedServers","connected","infoEndpoint","handleMcpRequest","request","handleRequest","OK"],"mappings":";;;;;;;;;;;;;;AAAA;;;;CAIC,GAED,SAASA,IAAI,EAAEC,UAAU,EAAEC,GAAG,EAAEC,QAAQ,EAAEC,UAAU,EAAEC,KAAK,EAAEC,IAAI,QAAQ,iBAAiB;AAE1F,SAASC,YAAY,QAAQ,qBAAqB;AAGlD,OAAO,MAAMC;IACX,YAAY,AAAiBC,YAA0B,CAAE;aAA5BA,eAAAA;IAA6B;IAE1D;;GAEC,GACD,MACMC,eAAe,AAAsBC,WAAmB,EAAE;QAC9D,OAAO,IAAI,CAACF,YAAY,CAACC,cAAc,CAACC;IAC1C;IAEA;;GAEC,GACD,MACMC,eAAe,AAAsBD,WAAmB,EAAE;QAC9D,MAAME,OAAO,MAAM,IAAI,CAACJ,YAAY,CAACC,cAAc,CAACC;QACpD,OAAO;YACLG,SAAS;YACTC,OAAO;gBACLC,QAAQ;gBACRC,aAAa;gBACbC,MAAM;oBACJC,SAAS;oBACTH,QAAQ;oBACRI,IAAI;gBACN;YACF;YACAC,SAAS;gBACPC,MAAMX;gBACNY,WAAWV,KAAKW,KAAK,CAACC,MAAM;gBAC5BC,aAAab,KAAKc,YAAY,CAACC,KAAK;gBACpCC,kBAAkBhB,KAAKc,YAAY,CAACG,SAAS;YAC/C;YACAC,cAAc,CAAC,SAAS,EAAEpB,YAAY,KAAK,CAAC;QAC9C;IACF;IAEA;;GAEC,GACD,MAEMqB,iBACJ,AAAsBrB,WAAmB,EACzC,AAAQsB,OAAmB,EACL;QACtB,OAAO,IAAI,CAACxB,YAAY,CAACyB,aAAa,CAACvB,aAAasB;IACtD;AACF;;;;;;;;;;;;;;;;;;;;;wBAPuBE"}
|
|
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 *\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} from '@nestjs/common';\nimport { EventEmitter2 } from '@nestjs/event-emitter';\nimport type { Request, Response } from 'express';\nimport { fromEvent, map } from 'rxjs';\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@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 // Gateway Endpoints (must come BEFORE :profileName routes)\n // =========================================\n\n /**\n * SSE endpoint for gateway notifications (dedicated URL)\n * Sends notifications/tools/list_changed when the active profile changes.\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 - handles JSON-RPC requests via SSE transport\n */\n @Post('gateway/sse')\n @HttpCode(HttpStatus.OK)\n async handleGatewaySseRequest(@Body() request: McpRequest): Promise<McpResponse> {\n return this.handleGatewayRequest(request);\n }\n\n /**\n * Get gateway info - proxies to default profile info\n */\n @Get('gateway/info')\n async getGatewayInfo() {\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n\n try {\n const info = await this.proxyService.getProfileInfo(profileName);\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 * Streamable HTTP content negotiation:\n * - Accept: text/event-stream -> Returns SSE stream for notifications\n * - Otherwise -> Returns JSON usage instructions\n *\n * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http\n */\n @Get('gateway')\n async getGatewayEndpoint(@Req() req: Request, @Res() res: Response) {\n // Streamable HTTP: check Accept header for content negotiation\n const acceptHeader = req.headers.accept || '';\n if (acceptHeader.includes('text/event-stream')) {\n return this.streamGatewayEvents(req, res);\n }\n\n // Return JSON usage info\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n\n try {\n const info = await this.proxyService.getProfileInfo(profileName);\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: {\n jsonrpc: '2.0',\n method: 'tools/list',\n id: 1,\n },\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 gateway notifications.\n * Sends notifications/tools/list_changed when the active profile changes.\n *\n * Follows MCP Streamable HTTP transport (2025-11-25):\n * - No endpoint event needed (unified endpoint)\n * - Simple data: format without event: prefix\n *\n * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http\n */\n private streamGatewayEvents(req: Request, res: Response) {\n // Set SSE headers\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'); // For nginx\n\n // Streamable HTTP - no endpoint event needed\n // Just send a comment to establish connection\n res.write(': connected\\n\\n');\n\n // Subscribe to profile change events\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 // Streamable HTTP - simple data format, no event prefix\n res.write(`data: ${JSON.stringify(notification)}\\n\\n`);\n });\n\n // Cleanup on client disconnect\n req.on('close', () => {\n subscription.unsubscribe();\n });\n }\n\n /**\n * MCP JSON-RPC endpoint for the gateway - proxies to default profile\n */\n @Post('gateway')\n @HttpCode(HttpStatus.OK)\n async handleGatewayRequest(@Body() request: McpRequest): Promise<McpResponse> {\n const profileName = await this.settingsService.getDefaultGatewayProfile();\n\n try {\n return await this.proxyService.handleRequest(profileName, request);\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 // Profile-specific Endpoints (existing)\n // =========================================\n\n /**\n * SSE endpoint for profile notifications (dedicated URL)\n * Sends notifications/tools/list_changed when the active gateway profile changes.\n */\n @Get(':profileName/sse')\n async streamProfileSse(\n @Param('profileName') profileName: string,\n @Req() req: Request,\n @Res() res: Response\n ) {\n // Validate profile exists\n await this.proxyService.getProfileInfo(profileName);\n return this.streamGatewayEvents(req, res);\n }\n\n /**\n * POST handler for profile SSE - handles JSON-RPC requests via SSE transport\n */\n @Post(':profileName/sse')\n @HttpCode(HttpStatus.OK)\n async handleProfileSseRequest(\n @Param('profileName') profileName: string,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n return this.handleMcpRequest(profileName, request);\n }\n\n /**\n * Get profile info with aggregated tools and server status\n */\n @Get(':profileName/info')\n async getProfileInfo(@Param('profileName') profileName: string) {\n return this.proxyService.getProfileInfo(profileName);\n }\n\n /**\n * GET handler for MCP endpoint\n *\n * Streamable HTTP content negotiation:\n * - Accept: text/event-stream -> Returns SSE stream for notifications\n * - Otherwise -> Returns JSON usage instructions\n *\n * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http\n */\n @Get(':profileName')\n async getMcpEndpoint(\n @Param('profileName') profileName: string,\n @Req() req: Request,\n @Res() res: Response\n ) {\n // Validate profile exists first\n const info = await this.proxyService.getProfileInfo(profileName);\n\n // Streamable HTTP: check Accept header for content negotiation\n const acceptHeader = req.headers.accept || '';\n if (acceptHeader.includes('text/event-stream')) {\n return this.streamGatewayEvents(req, res);\n }\n\n // Return JSON usage info\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: {\n jsonrpc: '2.0',\n method: 'tools/list',\n id: 1,\n },\n },\n endpoints: {\n sse: `/api/mcp/${profileName}/sse`,\n http: `/api/mcp/${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/${profileName}/info`,\n });\n }\n\n /**\n * MCP JSON-RPC endpoint for a profile\n */\n @Post(':profileName')\n @HttpCode(HttpStatus.OK)\n async handleMcpRequest(\n @Param('profileName') profileName: string,\n @Body() request: McpRequest\n ): Promise<McpResponse> {\n return this.proxyService.handleRequest(profileName, request);\n }\n}\n"],"names":["Body","Controller","Get","HttpCode","HttpStatus","NotFoundException","Param","Post","Req","Res","EventEmitter2","fromEvent","map","GATEWAY_PROFILE_CHANGED","SettingsService","ProxyService","ProxyController","proxyService","settingsService","eventEmitter","streamGatewaySse","req","res","streamGatewayEvents","handleGatewaySseRequest","request","handleGatewayRequest","getGatewayInfo","profileName","getDefaultGatewayProfile","info","getProfileInfo","gateway","activeProfile","error","getGatewayEndpoint","acceptHeader","headers","accept","includes","json","message","usage","method","contentType","body","jsonrpc","id","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","streamProfileSse","handleProfileSseRequest","handleMcpRequest","getMcpEndpoint","OK"],"mappings":"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"}
|
|
@@ -8,9 +8,11 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
8
8
|
* Proxy Module
|
|
9
9
|
*
|
|
10
10
|
* Handles MCP proxy endpoints for profiles.
|
|
11
|
+
* Supports SSE notifications for MCP Streamable HTTP transport.
|
|
11
12
|
*/ import { Module } from "@nestjs/common";
|
|
12
13
|
import { DebugModule } from "../debug/debug.module.js";
|
|
13
14
|
import { McpModule } from "../mcp/mcp.module.js";
|
|
15
|
+
import { SettingsModule } from "../settings/settings.module.js";
|
|
14
16
|
import { ProxyController } from "./proxy.controller.js";
|
|
15
17
|
import { ProxyService } from "./proxy.service.js";
|
|
16
18
|
export class ProxyModule {
|
|
@@ -19,7 +21,8 @@ ProxyModule = _ts_decorate([
|
|
|
19
21
|
Module({
|
|
20
22
|
imports: [
|
|
21
23
|
McpModule,
|
|
22
|
-
DebugModule
|
|
24
|
+
DebugModule,
|
|
25
|
+
SettingsModule
|
|
23
26
|
],
|
|
24
27
|
controllers: [
|
|
25
28
|
ProxyController
|