@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.
Files changed (40) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/AGENTS.md +4 -0
  3. package/CHANGELOG.md +32 -0
  4. package/dist/app.module.js +5 -0
  5. package/dist/app.module.js.map +1 -1
  6. package/dist/modules/profiles/profiles.service.js +15 -0
  7. package/dist/modules/profiles/profiles.service.js.map +1 -1
  8. package/dist/modules/proxy/proxy.controller.js +258 -9
  9. package/dist/modules/proxy/proxy.controller.js.map +1 -1
  10. package/dist/modules/proxy/proxy.module.js +4 -1
  11. package/dist/modules/proxy/proxy.module.js.map +1 -1
  12. package/dist/modules/settings/settings.constants.js +18 -0
  13. package/dist/modules/settings/settings.constants.js.map +1 -0
  14. package/dist/modules/settings/settings.controller.js +89 -0
  15. package/dist/modules/settings/settings.controller.js.map +1 -0
  16. package/dist/modules/settings/settings.module.js +30 -0
  17. package/dist/modules/settings/settings.module.js.map +1 -0
  18. package/dist/modules/settings/settings.service.js +110 -0
  19. package/dist/modules/settings/settings.service.js.map +1 -0
  20. package/package.json +6 -5
  21. package/src/AGENTS.md +23 -0
  22. package/src/app.module.ts +6 -0
  23. package/src/common/AGENTS.md +19 -0
  24. package/src/config/AGENTS.md +17 -0
  25. package/src/modules/AGENTS.md +31 -0
  26. package/src/modules/database/AGENTS.md +30 -0
  27. package/src/modules/debug/AGENTS.md +30 -0
  28. package/src/modules/health/AGENTS.md +22 -0
  29. package/src/modules/mcp/AGENTS.md +38 -0
  30. package/src/modules/oauth/AGENTS.md +32 -0
  31. package/src/modules/profiles/AGENTS.md +33 -0
  32. package/src/modules/profiles/profiles.service.ts +19 -0
  33. package/src/modules/proxy/AGENTS.md +34 -0
  34. package/src/modules/proxy/proxy.controller.ts +249 -7
  35. package/src/modules/proxy/proxy.module.ts +3 -1
  36. package/src/modules/settings/AGENTS.md +31 -0
  37. package/src/modules/settings/settings.constants.ts +20 -0
  38. package/src/modules/settings/settings.controller.ts +47 -0
  39. package/src/modules/settings/settings.module.ts +16 -0
  40. package/src/modules/settings/settings.service.ts +99 -0
@@ -1,9 +1,9 @@
1
1
 
2
- > @dxheroes/local-mcp-backend@0.4.0 build /home/runner/work/local-mcp-gateway/local-mcp-gateway/apps/backend
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
  -  TSC  Initializing type checker...
6
6
  ✔  TSC  Initializing type checker...
7
7
  >  TSC  Found 0 issues.
8
8
  >  SWC  Running...
9
- Successfully compiled: 32 files with swc (72.5ms)
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
 
@@ -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
@@ -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;AAwC9D,OAAO,MAAMC;AAAW;;;QArCtBC,SAAS;YACP,gBAAgB;YAChBZ,aAAaa,OAAO,CAAC;gBACnBC,UAAU;gBACVC,MAAM;oBAACb;oBAAWC;iBAAe;gBACjCa,aAAa;oBAAC;oBAAc;oBAAc;iBAAO;YACnD;YAEA,gBAAgB;YAChBf,gBAAgBY,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;YACtDf;YACAG;YACAE;YACAD;YACAE;YACAJ;YACAD;SACD"}
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
- */ import { Body, Controller, Get, HttpCode, HttpStatus, Param, Post } from "@nestjs/common";
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 - returns usage instructions instead of 404
32
- */ async getMcpEndpoint(profileName) {
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
- return {
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