@dxheroes/local-mcp-backend 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +20 -0
- package/dist/modules/mcp/dto/create-mcp-server.dto.js.map +1 -1
- package/dist/modules/mcp/dto/update-mcp-server.dto.js.map +1 -1
- package/dist/modules/mcp/mcp-seed.service.js +113 -2
- package/dist/modules/mcp/mcp-seed.service.js.map +1 -1
- package/dist/modules/mcp/mcp.service.js +41 -1
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/proxy/proxy.service.js +17 -1
- package/dist/modules/proxy/proxy.service.js.map +1 -1
- package/package.json +7 -5
- package/src/modules/mcp/dto/create-mcp-server.dto.ts +1 -1
- package/src/modules/mcp/dto/update-mcp-server.dto.ts +1 -1
- package/src/modules/mcp/mcp-seed.service.ts +117 -3
- package/src/modules/mcp/mcp.service.ts +61 -1
- package/src/modules/proxy/proxy.service.ts +31 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @dxheroes/local-mcp-backend@0.
|
|
2
|
+
> @dxheroes/local-mcp-backend@0.7.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: 36 files with swc (
|
|
9
|
+
Successfully compiled: 36 files with swc (78.04ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0](https://github.com/DXHeroes/local-mcp-gateway/compare/backend-v0.6.2...backend-v0.7.0) (2026-03-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **mcp:** introduce external MCP server support and enhance seeding functionality ([9f99183](https://github.com/DXHeroes/local-mcp-gateway/commit/9f99183d02c42a572bc82c7c599822d4feb4a4b8))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @dxheroes/local-mcp-core bumped to 0.6.0
|
|
16
|
+
* @dxheroes/local-mcp-database bumped to 0.4.5
|
|
17
|
+
* @dxheroes/mcp-gemini-deep-research bumped to 0.5.0
|
|
18
|
+
* @dxheroes/mcp-merk bumped to 0.3.0
|
|
19
|
+
* @dxheroes/mcp-toggl bumped to 0.3.0
|
|
20
|
+
* devDependencies
|
|
21
|
+
* @dxheroes/local-mcp-config bumped to 0.4.5
|
|
22
|
+
|
|
3
23
|
## [0.6.2](https://github.com/DXHeroes/local-mcp-gateway/compare/backend-v0.6.1...backend-v0.6.2) (2026-02-02)
|
|
4
24
|
|
|
5
25
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/mcp/dto/create-mcp-server.dto.ts"],"sourcesContent":["/**\n * Create MCP Server DTO\n */\n\nimport { Type } from 'class-transformer';\nimport { IsArray, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';\n\nclass ApiKeyConfigDto {\n @IsString()\n apiKey!: string;\n\n @IsString()\n @IsOptional()\n headerName?: string;\n\n @IsString()\n @IsOptional()\n headerValue?: string;\n}\n\nclass OAuthConfigDto {\n @IsArray()\n @IsString({ each: true })\n @IsOptional()\n scopes?: string[];\n\n @IsString()\n @IsOptional()\n authorizationServerUrl?: string;\n}\n\nexport class CreateMcpServerDto {\n @IsString()\n @IsNotEmpty()\n name!: string;\n\n @IsString()\n @IsNotEmpty()\n type!: 'builtin' | '
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/mcp/dto/create-mcp-server.dto.ts"],"sourcesContent":["/**\n * Create MCP Server DTO\n */\n\nimport { Type } from 'class-transformer';\nimport { IsArray, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';\n\nclass ApiKeyConfigDto {\n @IsString()\n apiKey!: string;\n\n @IsString()\n @IsOptional()\n headerName?: string;\n\n @IsString()\n @IsOptional()\n headerValue?: string;\n}\n\nclass OAuthConfigDto {\n @IsArray()\n @IsString({ each: true })\n @IsOptional()\n scopes?: string[];\n\n @IsString()\n @IsOptional()\n authorizationServerUrl?: string;\n}\n\nexport class CreateMcpServerDto {\n @IsString()\n @IsNotEmpty()\n name!: string;\n\n @IsString()\n @IsNotEmpty()\n type!: 'builtin' | 'external' | 'remote_http' | 'remote_sse';\n\n @IsOptional()\n config?: Record<string, unknown> | string;\n\n @ValidateNested()\n @Type(() => ApiKeyConfigDto)\n @IsOptional()\n apiKeyConfig?: ApiKeyConfigDto | null;\n\n @ValidateNested()\n @Type(() => OAuthConfigDto)\n @IsOptional()\n oauthConfig?: OAuthConfigDto | null;\n}\n"],"names":["Type","IsArray","IsNotEmpty","IsOptional","IsString","ValidateNested","ApiKeyConfigDto","OAuthConfigDto","each","CreateMcpServerDto"],"mappings":";;;;;;;;;AAAA;;CAEC,GAED,SAASA,IAAI,QAAQ,oBAAoB;AACzC,SAASC,OAAO,EAAEC,UAAU,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,cAAc,QAAQ,kBAAkB;AAE5F,IAAA,AAAMC,kBAAN,MAAMA;AAWN;;;;;;;;;;;;;;;AAEA,IAAA,AAAMC,iBAAN,MAAMA;AASN;;;;QAPcC,MAAM;;;;;;;;;;AASpB,OAAO,MAAMC;AAqBb;;;;;;;;;;;;;;;;;aARcH;;;;;;aAKAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/mcp/dto/update-mcp-server.dto.ts"],"sourcesContent":["/**\n * Update MCP Server DTO\n */\n\nimport { Type } from 'class-transformer';\nimport { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator';\n\nclass ApiKeyConfigDto {\n @IsString()\n apiKey!: string;\n\n @IsString()\n @IsOptional()\n headerName?: string;\n\n @IsString()\n @IsOptional()\n headerValue?: string;\n}\n\nclass OAuthConfigDto {\n @IsArray()\n @IsString({ each: true })\n @IsOptional()\n scopes?: string[];\n\n @IsString()\n @IsOptional()\n authorizationServerUrl?: string;\n}\n\nexport class UpdateMcpServerDto {\n @IsString()\n @IsOptional()\n name?: string;\n\n @IsString()\n @IsOptional()\n type?: 'builtin' | '
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/mcp/dto/update-mcp-server.dto.ts"],"sourcesContent":["/**\n * Update MCP Server DTO\n */\n\nimport { Type } from 'class-transformer';\nimport { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator';\n\nclass ApiKeyConfigDto {\n @IsString()\n apiKey!: string;\n\n @IsString()\n @IsOptional()\n headerName?: string;\n\n @IsString()\n @IsOptional()\n headerValue?: string;\n}\n\nclass OAuthConfigDto {\n @IsArray()\n @IsString({ each: true })\n @IsOptional()\n scopes?: string[];\n\n @IsString()\n @IsOptional()\n authorizationServerUrl?: string;\n}\n\nexport class UpdateMcpServerDto {\n @IsString()\n @IsOptional()\n name?: string;\n\n @IsString()\n @IsOptional()\n type?: 'builtin' | 'external' | 'remote_http' | 'remote_sse';\n\n @IsOptional()\n config?: Record<string, unknown> | string;\n\n @ValidateNested()\n @Type(() => ApiKeyConfigDto)\n @IsOptional()\n apiKeyConfig?: ApiKeyConfigDto | null;\n\n @ValidateNested()\n @Type(() => OAuthConfigDto)\n @IsOptional()\n oauthConfig?: OAuthConfigDto | null;\n}\n"],"names":["Type","IsArray","IsOptional","IsString","ValidateNested","ApiKeyConfigDto","OAuthConfigDto","each","UpdateMcpServerDto"],"mappings":";;;;;;;;;AAAA;;CAEC,GAED,SAASA,IAAI,QAAQ,oBAAoB;AACzC,SAASC,OAAO,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,cAAc,QAAQ,kBAAkB;AAEhF,IAAA,AAAMC,kBAAN,MAAMA;AAWN;;;;;;;;;;;;;;;AAEA,IAAA,AAAMC,iBAAN,MAAMA;AASN;;;;QAPcC,MAAM;;;;;;;;;;AASpB,OAAO,MAAMC;AAqBb;;;;;;;;;;;;;;;;;aARcH;;;;;;aAKAC"}
|
|
@@ -10,24 +10,100 @@ function _ts_metadata(k, v) {
|
|
|
10
10
|
/**
|
|
11
11
|
* MCP Seed Service
|
|
12
12
|
*
|
|
13
|
-
* Seeds MCP server records from discovered packages.
|
|
13
|
+
* Seeds MCP server records from discovered packages and external presets.
|
|
14
14
|
*/ import { randomUUID } from "node:crypto";
|
|
15
15
|
import { Injectable, Logger } from "@nestjs/common";
|
|
16
16
|
import { PrismaService } from "../database/prisma.service.js";
|
|
17
17
|
import { SETTING_KEYS } from "../settings/settings.constants.js";
|
|
18
|
+
const EXTERNAL_MCP_PRESETS = [
|
|
19
|
+
{
|
|
20
|
+
name: 'Playwright MCP',
|
|
21
|
+
description: 'Browser automation with Playwright - page interactions, screenshots, PDF generation',
|
|
22
|
+
config: {
|
|
23
|
+
command: 'npx',
|
|
24
|
+
args: [
|
|
25
|
+
'-y',
|
|
26
|
+
'@playwright/mcp@latest'
|
|
27
|
+
],
|
|
28
|
+
autoRestart: true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'Sequential Thinking',
|
|
33
|
+
description: 'Dynamic problem-solving through structured thoughts - analysis, planning, revision',
|
|
34
|
+
config: {
|
|
35
|
+
command: 'npx',
|
|
36
|
+
args: [
|
|
37
|
+
'-y',
|
|
38
|
+
'@anthropic/mcp-server-sequential-thinking'
|
|
39
|
+
],
|
|
40
|
+
autoRestart: true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'Filesystem MCP',
|
|
45
|
+
description: 'File system access - read, write, search, and manage files',
|
|
46
|
+
config: {
|
|
47
|
+
command: 'npx',
|
|
48
|
+
args: [
|
|
49
|
+
'-y',
|
|
50
|
+
'@modelcontextprotocol/server-filesystem',
|
|
51
|
+
'/tmp'
|
|
52
|
+
],
|
|
53
|
+
autoRestart: true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'Memory MCP',
|
|
58
|
+
description: 'Knowledge graph-based persistent memory - store and retrieve entities and relations',
|
|
59
|
+
config: {
|
|
60
|
+
command: 'npx',
|
|
61
|
+
args: [
|
|
62
|
+
'-y',
|
|
63
|
+
'@modelcontextprotocol/server-memory'
|
|
64
|
+
],
|
|
65
|
+
autoRestart: true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'GitHub MCP',
|
|
70
|
+
description: 'GitHub API access - repositories, issues, pull requests, and more',
|
|
71
|
+
config: {
|
|
72
|
+
command: 'npx',
|
|
73
|
+
args: [
|
|
74
|
+
'-y',
|
|
75
|
+
'@modelcontextprotocol/server-github'
|
|
76
|
+
],
|
|
77
|
+
autoRestart: true
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'Example: Fetch MCP',
|
|
82
|
+
description: 'Example MCP server calling public APIs - use as a template for your own external MCP scripts',
|
|
83
|
+
config: {
|
|
84
|
+
command: 'npx',
|
|
85
|
+
args: [
|
|
86
|
+
'-y',
|
|
87
|
+
'@anthropic/mcp-server-fetch'
|
|
88
|
+
],
|
|
89
|
+
autoRestart: true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
];
|
|
18
93
|
export class McpSeedService {
|
|
19
94
|
constructor(prisma){
|
|
20
95
|
this.prisma = prisma;
|
|
21
96
|
this.logger = new Logger(McpSeedService.name);
|
|
22
97
|
}
|
|
23
98
|
/**
|
|
24
|
-
* Run seed data for all discovered MCP packages
|
|
99
|
+
* Run seed data for all discovered MCP packages and external presets
|
|
25
100
|
*
|
|
26
101
|
* Seeding is idempotent - only creates records that don't exist
|
|
27
102
|
*/ async runSeeds(packages) {
|
|
28
103
|
this.logger.log(`Running seeds for ${packages.length} MCP packages`);
|
|
29
104
|
// Ensure default profile exists
|
|
30
105
|
await this.ensureDefaultProfile();
|
|
106
|
+
// Seed builtin packages
|
|
31
107
|
for (const { package: pkg, packageName } of packages){
|
|
32
108
|
try {
|
|
33
109
|
await this.seedPackage(pkg, packageName);
|
|
@@ -35,8 +111,43 @@ export class McpSeedService {
|
|
|
35
111
|
this.logger.error(`Failed to seed ${packageName}: ${error}`);
|
|
36
112
|
}
|
|
37
113
|
}
|
|
114
|
+
// Seed external presets
|
|
115
|
+
await this.seedExternalPresets();
|
|
38
116
|
this.logger.log('MCP seeding complete');
|
|
39
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Seed external MCP server presets
|
|
120
|
+
* These are NOT assigned to any profile - users manually add them
|
|
121
|
+
*/ async seedExternalPresets() {
|
|
122
|
+
this.logger.log(`Seeding ${EXTERNAL_MCP_PRESETS.length} external MCP presets`);
|
|
123
|
+
for (const preset of EXTERNAL_MCP_PRESETS){
|
|
124
|
+
try {
|
|
125
|
+
// Check if this external server already exists (by name)
|
|
126
|
+
const existingServer = await this.prisma.mcpServer.findFirst({
|
|
127
|
+
where: {
|
|
128
|
+
name: preset.name,
|
|
129
|
+
type: 'external'
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
if (existingServer) {
|
|
133
|
+
this.logger.debug(`External preset ${preset.name} already exists, skipping`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// Create the external MCP server record
|
|
137
|
+
await this.prisma.mcpServer.create({
|
|
138
|
+
data: {
|
|
139
|
+
id: randomUUID(),
|
|
140
|
+
name: preset.name,
|
|
141
|
+
type: 'external',
|
|
142
|
+
config: JSON.stringify(preset.config)
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
this.logger.log(`Created external MCP preset: ${preset.name}`);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
this.logger.error(`Failed to seed external preset ${preset.name}: ${error}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
40
151
|
async ensureDefaultProfile() {
|
|
41
152
|
// Check if user intentionally deleted the default profile
|
|
42
153
|
const deletedSetting = await this.prisma.gatewaySetting.findUnique({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/modules/mcp/mcp-seed.service.ts"],"sourcesContent":["/**\n * MCP Seed Service\n *\n * Seeds MCP server records from discovered packages.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { DiscoveredMcpPackage } from '@dxheroes/local-mcp-core';\nimport { Injectable, Logger } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { SETTING_KEYS } from '../settings/settings.constants.js';\n\ninterface McpServerConfig {\n builtinId?: string;\n}\n\n@Injectable()\nexport class McpSeedService {\n private readonly logger = new Logger(McpSeedService.name);\n\n constructor(private readonly prisma: PrismaService) {}\n\n /**\n * Run seed data for all discovered MCP packages\n *\n * Seeding is idempotent - only creates records that don't exist\n */\n async runSeeds(packages: DiscoveredMcpPackage[]): Promise<void> {\n this.logger.log(`Running seeds for ${packages.length} MCP packages`);\n\n // Ensure default profile exists\n await this.ensureDefaultProfile();\n\n for (const { package: pkg, packageName } of packages) {\n try {\n await this.seedPackage(pkg, packageName);\n } catch (error) {\n this.logger.error(`Failed to seed ${packageName}: ${error}`);\n }\n }\n\n this.logger.log('MCP seeding complete');\n }\n\n private async ensureDefaultProfile(): Promise<void> {\n // Check if user intentionally deleted the default profile\n const deletedSetting = await this.prisma.gatewaySetting.findUnique({\n where: { key: SETTING_KEYS.DEFAULT_PROFILE_DELETED },\n });\n\n if (deletedSetting?.value === 'true') {\n this.logger.log('Default profile was deleted by user, skipping creation');\n return;\n }\n\n const defaultProfile = await this.prisma.profile.findUnique({\n where: { name: 'default' },\n });\n\n if (!defaultProfile) {\n await this.prisma.profile.create({\n data: {\n id: randomUUID(),\n name: 'default',\n description: 'Default MCP profile',\n },\n });\n this.logger.log('Created default profile');\n }\n }\n\n private async seedPackage(\n pkg: DiscoveredMcpPackage['package'],\n _packageName: string\n ): Promise<void> {\n const { metadata, seed } = pkg;\n\n // Check if MCP server already exists (by builtinId in config)\n const existingServer = await this.findServerByBuiltinId(metadata.id);\n\n if (existingServer) {\n this.logger.debug(`MCP server ${metadata.id} already exists, skipping seed`);\n return;\n }\n\n // Create the MCP server record\n const serverId = randomUUID();\n\n await this.prisma.mcpServer.create({\n data: {\n id: serverId,\n name: metadata.name,\n type: 'builtin',\n config: JSON.stringify({ builtinId: metadata.id }),\n },\n });\n\n this.logger.log(`Created MCP server: ${metadata.name} (${metadata.id})`);\n\n // Link to profile if seed config specifies\n if (seed?.defaultProfile) {\n const profile = await this.prisma.profile.findUnique({\n where: { name: seed.defaultProfile },\n });\n\n if (profile) {\n await this.prisma.profileMcpServer.create({\n data: {\n id: randomUUID(),\n profileId: profile.id,\n mcpServerId: serverId,\n order: seed.defaultOrder ?? 0,\n isActive: seed.defaultActive ?? true,\n },\n });\n\n this.logger.log(`Linked ${metadata.name} to profile: ${seed.defaultProfile}`);\n }\n }\n }\n\n private async findServerByBuiltinId(builtinId: string): Promise<boolean> {\n const servers = await this.prisma.mcpServer.findMany({\n where: { type: 'builtin' },\n });\n\n return servers.some((s) => {\n try {\n const config = JSON.parse(s.config as string) as McpServerConfig;\n return config.builtinId === builtinId;\n } catch {\n return false;\n }\n });\n }\n}\n"],"names":["randomUUID","Injectable","Logger","PrismaService","SETTING_KEYS","McpSeedService","prisma","logger","name","runSeeds","packages","log","length","ensureDefaultProfile","package","pkg","packageName","seedPackage","error","deletedSetting","gatewaySetting","findUnique","where","key","DEFAULT_PROFILE_DELETED","value","defaultProfile","profile","create","data","id","description","_packageName","metadata","seed","existingServer","findServerByBuiltinId","debug","serverId","mcpServer","type","config","JSON","stringify","builtinId","profileMcpServer","profileId","mcpServerId","order","defaultOrder","isActive","defaultActive","servers","findMany","some","s","parse"],"mappings":";;;;;;;;;AAAA;;;;CAIC,GAED,SAASA,UAAU,QAAQ,cAAc;AAEzC,SAASC,UAAU,EAAEC,MAAM,QAAQ,iBAAiB;AACpD,SAASC,aAAa,QAAQ,gCAAgC;AAC9D,SAASC,YAAY,QAAQ,oCAAoC;AAOjE,OAAO,MAAMC;IAGX,YAAY,AAAiBC,MAAqB,CAAE;aAAvBA,SAAAA;aAFZC,SAAS,IAAIL,OAAOG,eAAeG,IAAI;IAEH;IAErD;;;;GAIC,GACD,MAAMC,SAASC,QAAgC,EAAiB;QAC9D,IAAI,CAACH,MAAM,CAACI,GAAG,CAAC,CAAC,kBAAkB,EAAED,SAASE,MAAM,CAAC,aAAa,CAAC;QAEnE,gCAAgC;QAChC,MAAM,IAAI,CAACC,oBAAoB;QAE/B,KAAK,MAAM,EAAEC,SAASC,GAAG,EAAEC,WAAW,EAAE,IAAIN,SAAU;YACpD,IAAI;gBACF,MAAM,IAAI,CAACO,WAAW,CAACF,KAAKC;YAC9B,EAAE,OAAOE,OAAO;gBACd,IAAI,CAACX,MAAM,CAACW,KAAK,CAAC,CAAC,eAAe,EAAEF,YAAY,EAAE,EAAEE,OAAO;YAC7D;QACF;QAEA,IAAI,CAACX,MAAM,CAACI,GAAG,CAAC;IAClB;IAEA,MAAcE,uBAAsC;QAClD,0DAA0D;QAC1D,MAAMM,iBAAiB,MAAM,IAAI,CAACb,MAAM,CAACc,cAAc,CAACC,UAAU,CAAC;YACjEC,OAAO;gBAAEC,KAAKnB,aAAaoB,uBAAuB;YAAC;QACrD;QAEA,IAAIL,gBAAgBM,UAAU,QAAQ;YACpC,IAAI,CAAClB,MAAM,CAACI,GAAG,CAAC;YAChB;QACF;QAEA,MAAMe,iBAAiB,MAAM,IAAI,CAACpB,MAAM,CAACqB,OAAO,CAACN,UAAU,CAAC;YAC1DC,OAAO;gBAAEd,MAAM;YAAU;QAC3B;QAEA,IAAI,CAACkB,gBAAgB;YACnB,MAAM,IAAI,CAACpB,MAAM,CAACqB,OAAO,CAACC,MAAM,CAAC;gBAC/BC,MAAM;oBACJC,IAAI9B;oBACJQ,MAAM;oBACNuB,aAAa;gBACf;YACF;YACA,IAAI,CAACxB,MAAM,CAACI,GAAG,CAAC;QAClB;IACF;IAEA,MAAcM,YACZF,GAAoC,EACpCiB,YAAoB,EACL;QACf,MAAM,EAAEC,QAAQ,EAAEC,IAAI,EAAE,GAAGnB;QAE3B,8DAA8D;QAC9D,MAAMoB,iBAAiB,MAAM,IAAI,CAACC,qBAAqB,CAACH,SAASH,EAAE;QAEnE,IAAIK,gBAAgB;YAClB,IAAI,CAAC5B,MAAM,CAAC8B,KAAK,CAAC,CAAC,WAAW,EAAEJ,SAASH,EAAE,CAAC,8BAA8B,CAAC;YAC3E;QACF;QAEA,+BAA+B;QAC/B,MAAMQ,WAAWtC;QAEjB,MAAM,IAAI,CAACM,MAAM,CAACiC,SAAS,CAACX,MAAM,CAAC;YACjCC,MAAM;gBACJC,IAAIQ;gBACJ9B,MAAMyB,SAASzB,IAAI;gBACnBgC,MAAM;gBACNC,QAAQC,KAAKC,SAAS,CAAC;oBAAEC,WAAWX,SAASH,EAAE;gBAAC;YAClD;QACF;QAEA,IAAI,CAACvB,MAAM,CAACI,GAAG,CAAC,CAAC,oBAAoB,EAAEsB,SAASzB,IAAI,CAAC,EAAE,EAAEyB,SAASH,EAAE,CAAC,CAAC,CAAC;QAEvE,2CAA2C;QAC3C,IAAII,MAAMR,gBAAgB;YACxB,MAAMC,UAAU,MAAM,IAAI,CAACrB,MAAM,CAACqB,OAAO,CAACN,UAAU,CAAC;gBACnDC,OAAO;oBAAEd,MAAM0B,KAAKR,cAAc;gBAAC;YACrC;YAEA,IAAIC,SAAS;gBACX,MAAM,IAAI,CAACrB,MAAM,CAACuC,gBAAgB,CAACjB,MAAM,CAAC;oBACxCC,MAAM;wBACJC,IAAI9B;wBACJ8C,WAAWnB,QAAQG,EAAE;wBACrBiB,aAAaT;wBACbU,OAAOd,KAAKe,YAAY,IAAI;wBAC5BC,UAAUhB,KAAKiB,aAAa,IAAI;oBAClC;gBACF;gBAEA,IAAI,CAAC5C,MAAM,CAACI,GAAG,CAAC,CAAC,OAAO,EAAEsB,SAASzB,IAAI,CAAC,aAAa,EAAE0B,KAAKR,cAAc,EAAE;YAC9E;QACF;IACF;IAEA,MAAcU,sBAAsBQ,SAAiB,EAAoB;QACvE,MAAMQ,UAAU,MAAM,IAAI,CAAC9C,MAAM,CAACiC,SAAS,CAACc,QAAQ,CAAC;YACnD/B,OAAO;gBAAEkB,MAAM;YAAU;QAC3B;QAEA,OAAOY,QAAQE,IAAI,CAAC,CAACC;YACnB,IAAI;gBACF,MAAMd,SAASC,KAAKc,KAAK,CAACD,EAAEd,MAAM;gBAClC,OAAOA,OAAOG,SAAS,KAAKA;YAC9B,EAAE,OAAM;gBACN,OAAO;YACT;QACF;IACF;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../../src/modules/mcp/mcp-seed.service.ts"],"sourcesContent":["/**\n * MCP Seed Service\n *\n * Seeds MCP server records from discovered packages and external presets.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { DiscoveredMcpPackage, ExternalMcpConfig } from '@dxheroes/local-mcp-core';\nimport { Injectable, Logger } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { SETTING_KEYS } from '../settings/settings.constants.js';\n\ninterface McpServerConfig {\n builtinId?: string;\n}\n\n/**\n * External MCP server presets - popular NPX-based MCP servers\n * These are seeded but NOT assigned to any profile\n */\ninterface ExternalMcpPreset {\n name: string;\n description: string;\n config: ExternalMcpConfig;\n}\n\nconst EXTERNAL_MCP_PRESETS: ExternalMcpPreset[] = [\n {\n name: 'Playwright MCP',\n description:\n 'Browser automation with Playwright - page interactions, screenshots, PDF generation',\n config: {\n command: 'npx',\n args: ['-y', '@playwright/mcp@latest'],\n autoRestart: true,\n },\n },\n {\n name: 'Sequential Thinking',\n description:\n 'Dynamic problem-solving through structured thoughts - analysis, planning, revision',\n config: {\n command: 'npx',\n args: ['-y', '@anthropic/mcp-server-sequential-thinking'],\n autoRestart: true,\n },\n },\n {\n name: 'Filesystem MCP',\n description: 'File system access - read, write, search, and manage files',\n config: {\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp'],\n autoRestart: true,\n },\n },\n {\n name: 'Memory MCP',\n description:\n 'Knowledge graph-based persistent memory - store and retrieve entities and relations',\n config: {\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-memory'],\n autoRestart: true,\n },\n },\n {\n name: 'GitHub MCP',\n description: 'GitHub API access - repositories, issues, pull requests, and more',\n config: {\n command: 'npx',\n args: ['-y', '@modelcontextprotocol/server-github'],\n autoRestart: true,\n },\n },\n {\n name: 'Example: Fetch MCP',\n description:\n 'Example MCP server calling public APIs - use as a template for your own external MCP scripts',\n config: {\n command: 'npx',\n args: ['-y', '@anthropic/mcp-server-fetch'],\n autoRestart: true,\n },\n },\n];\n\n@Injectable()\nexport class McpSeedService {\n private readonly logger = new Logger(McpSeedService.name);\n\n constructor(private readonly prisma: PrismaService) {}\n\n /**\n * Run seed data for all discovered MCP packages and external presets\n *\n * Seeding is idempotent - only creates records that don't exist\n */\n async runSeeds(packages: DiscoveredMcpPackage[]): Promise<void> {\n this.logger.log(`Running seeds for ${packages.length} MCP packages`);\n\n // Ensure default profile exists\n await this.ensureDefaultProfile();\n\n // Seed builtin packages\n for (const { package: pkg, packageName } of packages) {\n try {\n await this.seedPackage(pkg, packageName);\n } catch (error) {\n this.logger.error(`Failed to seed ${packageName}: ${error}`);\n }\n }\n\n // Seed external presets\n await this.seedExternalPresets();\n\n this.logger.log('MCP seeding complete');\n }\n\n /**\n * Seed external MCP server presets\n * These are NOT assigned to any profile - users manually add them\n */\n private async seedExternalPresets(): Promise<void> {\n this.logger.log(`Seeding ${EXTERNAL_MCP_PRESETS.length} external MCP presets`);\n\n for (const preset of EXTERNAL_MCP_PRESETS) {\n try {\n // Check if this external server already exists (by name)\n const existingServer = await this.prisma.mcpServer.findFirst({\n where: {\n name: preset.name,\n type: 'external',\n },\n });\n\n if (existingServer) {\n this.logger.debug(`External preset ${preset.name} already exists, skipping`);\n continue;\n }\n\n // Create the external MCP server record\n await this.prisma.mcpServer.create({\n data: {\n id: randomUUID(),\n name: preset.name,\n type: 'external',\n config: JSON.stringify(preset.config),\n },\n });\n\n this.logger.log(`Created external MCP preset: ${preset.name}`);\n } catch (error) {\n this.logger.error(`Failed to seed external preset ${preset.name}: ${error}`);\n }\n }\n }\n\n private async ensureDefaultProfile(): Promise<void> {\n // Check if user intentionally deleted the default profile\n const deletedSetting = await this.prisma.gatewaySetting.findUnique({\n where: { key: SETTING_KEYS.DEFAULT_PROFILE_DELETED },\n });\n\n if (deletedSetting?.value === 'true') {\n this.logger.log('Default profile was deleted by user, skipping creation');\n return;\n }\n\n const defaultProfile = await this.prisma.profile.findUnique({\n where: { name: 'default' },\n });\n\n if (!defaultProfile) {\n await this.prisma.profile.create({\n data: {\n id: randomUUID(),\n name: 'default',\n description: 'Default MCP profile',\n },\n });\n this.logger.log('Created default profile');\n }\n }\n\n private async seedPackage(\n pkg: DiscoveredMcpPackage['package'],\n _packageName: string\n ): Promise<void> {\n const { metadata, seed } = pkg;\n\n // Check if MCP server already exists (by builtinId in config)\n const existingServer = await this.findServerByBuiltinId(metadata.id);\n\n if (existingServer) {\n this.logger.debug(`MCP server ${metadata.id} already exists, skipping seed`);\n return;\n }\n\n // Create the MCP server record\n const serverId = randomUUID();\n\n await this.prisma.mcpServer.create({\n data: {\n id: serverId,\n name: metadata.name,\n type: 'builtin',\n config: JSON.stringify({ builtinId: metadata.id }),\n },\n });\n\n this.logger.log(`Created MCP server: ${metadata.name} (${metadata.id})`);\n\n // Link to profile if seed config specifies\n if (seed?.defaultProfile) {\n const profile = await this.prisma.profile.findUnique({\n where: { name: seed.defaultProfile },\n });\n\n if (profile) {\n await this.prisma.profileMcpServer.create({\n data: {\n id: randomUUID(),\n profileId: profile.id,\n mcpServerId: serverId,\n order: seed.defaultOrder ?? 0,\n isActive: seed.defaultActive ?? true,\n },\n });\n\n this.logger.log(`Linked ${metadata.name} to profile: ${seed.defaultProfile}`);\n }\n }\n }\n\n private async findServerByBuiltinId(builtinId: string): Promise<boolean> {\n const servers = await this.prisma.mcpServer.findMany({\n where: { type: 'builtin' },\n });\n\n return servers.some((s) => {\n try {\n const config = JSON.parse(s.config as string) as McpServerConfig;\n return config.builtinId === builtinId;\n } catch {\n return false;\n }\n });\n }\n}\n"],"names":["randomUUID","Injectable","Logger","PrismaService","SETTING_KEYS","EXTERNAL_MCP_PRESETS","name","description","config","command","args","autoRestart","McpSeedService","prisma","logger","runSeeds","packages","log","length","ensureDefaultProfile","package","pkg","packageName","seedPackage","error","seedExternalPresets","preset","existingServer","mcpServer","findFirst","where","type","debug","create","data","id","JSON","stringify","deletedSetting","gatewaySetting","findUnique","key","DEFAULT_PROFILE_DELETED","value","defaultProfile","profile","_packageName","metadata","seed","findServerByBuiltinId","serverId","builtinId","profileMcpServer","profileId","mcpServerId","order","defaultOrder","isActive","defaultActive","servers","findMany","some","s","parse"],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ function _ts_metadata(k, v) {
|
|
|
11
11
|
* MCP Service
|
|
12
12
|
*
|
|
13
13
|
* Business logic for MCP server management.
|
|
14
|
-
*/ import { RemoteHttpMcpServer, RemoteSseMcpServer } from "@dxheroes/local-mcp-core";
|
|
14
|
+
*/ import { ExternalMcpServer, RemoteHttpMcpServer, RemoteSseMcpServer } from "@dxheroes/local-mcp-core";
|
|
15
15
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
|
16
16
|
import { PrismaService } from "../database/prisma.service.js";
|
|
17
17
|
import { DebugService } from "../debug/debug.service.js";
|
|
@@ -217,6 +217,21 @@ export class McpService {
|
|
|
217
217
|
tools
|
|
218
218
|
};
|
|
219
219
|
}
|
|
220
|
+
// For external (NPX/stdio) servers, spawn and fetch tools
|
|
221
|
+
if (server.type === 'external') {
|
|
222
|
+
const config = this.parseConfig(server.config);
|
|
223
|
+
const externalServer = new ExternalMcpServer(config);
|
|
224
|
+
try {
|
|
225
|
+
await externalServer.initialize();
|
|
226
|
+
const tools = await externalServer.listTools();
|
|
227
|
+
return {
|
|
228
|
+
tools
|
|
229
|
+
};
|
|
230
|
+
} finally{
|
|
231
|
+
// Shutdown after fetching tools
|
|
232
|
+
await externalServer.shutdown();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
220
235
|
// For cached tools from external servers (fallback)
|
|
221
236
|
const tools = await this.prisma.mcpServerToolsCache.findMany({
|
|
222
237
|
where: {
|
|
@@ -350,6 +365,31 @@ export class McpService {
|
|
|
350
365
|
console.error(`[McpService] Remote SSE validation error for ${server.name}:`, error);
|
|
351
366
|
}
|
|
352
367
|
}
|
|
368
|
+
// For external (NPX/stdio) servers, validate by spawning and checking
|
|
369
|
+
if (server.type === 'external' && status === 'unknown') {
|
|
370
|
+
const config = this.parseConfig(server.config);
|
|
371
|
+
if (!config.command) {
|
|
372
|
+
status = 'error';
|
|
373
|
+
validationError = 'Command is required for external MCP servers';
|
|
374
|
+
validationDetails = 'Missing command configuration';
|
|
375
|
+
} else {
|
|
376
|
+
try {
|
|
377
|
+
const externalServer = new ExternalMcpServer(config);
|
|
378
|
+
await externalServer.initialize();
|
|
379
|
+
const tools = await externalServer.listTools();
|
|
380
|
+
status = 'connected';
|
|
381
|
+
validationDetails = `Connected successfully (stdio). ${tools.length} tools available.`;
|
|
382
|
+
console.log(`[McpService] External validation for ${server.name}: ${tools.length} tools`);
|
|
383
|
+
// Shutdown after validation
|
|
384
|
+
await externalServer.shutdown();
|
|
385
|
+
} catch (error) {
|
|
386
|
+
status = 'error';
|
|
387
|
+
validationError = error instanceof Error ? error.message : 'Unknown error';
|
|
388
|
+
validationDetails = `Connection failed: ${validationError}`;
|
|
389
|
+
console.error(`[McpService] External validation error for ${server.name}:`, error);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
353
393
|
const isReady = status === 'connected';
|
|
354
394
|
return {
|
|
355
395
|
id: server.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/modules/mcp/mcp.service.ts"],"sourcesContent":["/**\n * MCP Service\n *\n * Business logic for MCP server management.\n */\n\nimport { RemoteHttpMcpServer, RemoteSseMcpServer } from '@dxheroes/local-mcp-core';\nimport { Injectable, NotFoundException } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { DebugService } from '../debug/debug.service.js';\nimport type { CreateMcpServerDto } from './dto/create-mcp-server.dto.js';\nimport type { UpdateMcpServerDto } from './dto/update-mcp-server.dto.js';\nimport { McpRegistry } from './mcp-registry.js';\n\n@Injectable()\nexport class McpService {\n constructor(\n private readonly prisma: PrismaService,\n private readonly registry: McpRegistry,\n private readonly debugService: DebugService\n ) {}\n\n /**\n * Get all MCP servers\n */\n async findAll() {\n const servers = await this.prisma.mcpServer.findMany({\n include: {\n profiles: {\n include: {\n profile: true,\n },\n },\n },\n orderBy: { name: 'asc' },\n });\n\n // Enrich with metadata from registry for builtin servers\n return servers.map((server) => {\n const builtinId = this.getBuiltinId(server.config);\n\n if (builtinId && this.registry.has(builtinId)) {\n const metadata = this.registry.get(builtinId)?.metadata;\n return {\n ...server,\n metadata,\n };\n }\n\n return server;\n });\n }\n\n /**\n * Get a specific MCP server\n */\n async findById(id: string) {\n const server = await this.prisma.mcpServer.findUnique({\n where: { id },\n include: {\n profiles: {\n include: {\n profile: true,\n tools: true,\n },\n },\n oauthToken: true,\n toolsCache: true,\n },\n });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n // Enrich with metadata from registry for builtin servers\n const builtinId = this.getBuiltinId(server.config);\n\n if (builtinId && this.registry.has(builtinId)) {\n const metadata = this.registry.get(builtinId)?.metadata;\n return {\n ...server,\n metadata,\n };\n }\n\n return server;\n }\n\n /**\n * Create a new MCP server\n */\n async create(dto: CreateMcpServerDto) {\n return this.prisma.mcpServer.create({\n data: {\n name: dto.name,\n type: dto.type,\n config: JSON.stringify(dto.config || {}),\n apiKeyConfig: dto.apiKeyConfig ? JSON.stringify(dto.apiKeyConfig) : null,\n oauthConfig: dto.oauthConfig ? JSON.stringify(dto.oauthConfig) : null,\n },\n });\n }\n\n /**\n * Update an MCP server\n */\n async update(id: string, dto: UpdateMcpServerDto) {\n const server = await this.prisma.mcpServer.findUnique({ where: { id } });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n return this.prisma.mcpServer.update({\n where: { id },\n data: {\n name: dto.name,\n type: dto.type,\n config:\n dto.config !== undefined\n ? typeof dto.config === 'string'\n ? dto.config\n : JSON.stringify(dto.config)\n : undefined,\n apiKeyConfig: dto.apiKeyConfig !== undefined ? JSON.stringify(dto.apiKeyConfig) : undefined,\n oauthConfig: dto.oauthConfig !== undefined ? JSON.stringify(dto.oauthConfig) : undefined,\n },\n });\n }\n\n /**\n * Delete an MCP server\n */\n async delete(id: string) {\n const server = await this.prisma.mcpServer.findUnique({ where: { id } });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n await this.prisma.mcpServer.delete({ where: { id } });\n }\n\n /**\n * Get tools from an MCP server\n */\n async getTools(id: string) {\n const startTime = Date.now();\n\n // Create debug log entry\n const log = await this.debugService.createLog({\n mcpServerId: id,\n requestType: 'tools/list',\n requestPayload: JSON.stringify({ method: 'tools/list', serverId: id }),\n status: 'pending',\n });\n\n try {\n const result = await this.getToolsInternal(id);\n\n // Update debug log with success\n await this.debugService.updateLog(log.id, {\n responsePayload: JSON.stringify(result),\n status: 'success',\n durationMs: Date.now() - startTime,\n });\n\n return result;\n } catch (error) {\n // Update debug log with error\n await this.debugService.updateLog(log.id, {\n status: 'error',\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n durationMs: Date.now() - startTime,\n });\n throw error;\n }\n }\n\n /**\n * Internal method to get tools from an MCP server\n */\n private async getToolsInternal(id: string) {\n const server = await this.findById(id);\n const builtinId = this.getBuiltinId(server.config);\n\n // For builtin servers, get tools from the registry\n if (builtinId && this.registry.has(builtinId)) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n // Get API key config if set\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n // Create server instance and list tools\n const instance = pkg.createServer(apiKeyConfig);\n await instance.initialize();\n const tools = await instance.listTools();\n return { tools };\n }\n }\n\n // For remote_http servers, connect and fetch tools\n if (server.type === 'remote_http') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n return { tools };\n }\n\n // For remote_sse servers, connect and fetch tools\n if (server.type === 'remote_sse') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n return { tools };\n }\n\n // For cached tools from external servers (fallback)\n const tools = await this.prisma.mcpServerToolsCache.findMany({\n where: { mcpServerId: id },\n });\n return { tools };\n }\n\n /**\n * Get MCP server status with real validation\n */\n async getStatus(id: string) {\n const startTime = Date.now();\n\n // Create debug log entry\n const log = await this.debugService.createLog({\n mcpServerId: id,\n requestType: 'status/check',\n requestPayload: JSON.stringify({ method: 'status/check', serverId: id }),\n status: 'pending',\n });\n\n try {\n const result = await this.getStatusInternal(id);\n\n // Update debug log with success or error based on status\n await this.debugService.updateLog(log.id, {\n responsePayload: JSON.stringify(result),\n status: result.status === 'error' ? 'error' : 'success',\n errorMessage: result.error,\n durationMs: Date.now() - startTime,\n });\n\n return result;\n } catch (error) {\n // Update debug log with error\n await this.debugService.updateLog(log.id, {\n status: 'error',\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n durationMs: Date.now() - startTime,\n });\n throw error;\n }\n }\n\n /**\n * Internal method to get MCP server status with real validation\n */\n private async getStatusInternal(id: string) {\n const server = await this.findById(id);\n const builtinId = this.getBuiltinId(server.config);\n\n // Check if it's a builtin server\n const isBuiltin = builtinId && this.registry.has(builtinId);\n\n // Check API key config\n const hasApiKey = !!server.apiKeyConfig;\n\n // Check OAuth token\n const hasOAuth = !!server.oauthToken;\n\n // Get metadata\n const metadata = isBuiltin ? this.registry.get(builtinId)?.metadata : null;\n const requiresApiKey = metadata?.requiresApiKey ?? false;\n const requiresOAuth = metadata?.requiresOAuth ?? false;\n\n // Default status\n let status: 'connected' | 'error' | 'unknown' = 'unknown';\n let validationError: string | undefined;\n let validationDetails: string | undefined;\n\n // For builtin servers with API key, actually validate\n if (isBuiltin && hasApiKey) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n try {\n const instance = pkg.createServer(apiKeyConfig);\n // Call validate() method\n const validation = await instance.validate();\n status = validation.valid ? 'connected' : 'error';\n validationError = validation.error;\n validationDetails = validation.valid\n ? 'API key validated successfully'\n : `Validation failed: ${validation.error}`;\n console.log(`[McpService] Validation result for ${server.name}:`, validation);\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection test failed: ${validationError}`;\n console.error(`[McpService] Validation error for ${server.name}:`, error);\n }\n }\n } else if (!hasApiKey && requiresApiKey) {\n status = 'error';\n validationError = 'API key required';\n validationDetails = 'This server requires an API key to function';\n } else if (isBuiltin && !requiresApiKey) {\n status = 'connected';\n validationDetails = 'Server ready (no API key required)';\n }\n\n // For remote_http servers, validate by connecting\n if (server.type === 'remote_http' && status === 'unknown') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n try {\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully. ${tools.length} tools available.`;\n console.log(\n `[McpService] Remote HTTP validation for ${server.name}: ${tools.length} tools`\n );\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection failed: ${validationError}`;\n console.error(`[McpService] Remote HTTP validation error for ${server.name}:`, error);\n }\n }\n\n // For remote_sse servers, validate by connecting\n if (server.type === 'remote_sse' && status === 'unknown') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n try {\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully (SSE). ${tools.length} tools available.`;\n console.log(`[McpService] Remote SSE validation for ${server.name}: ${tools.length} tools`);\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection failed: ${validationError}`;\n console.error(`[McpService] Remote SSE validation error for ${server.name}:`, error);\n }\n }\n\n const isReady = status === 'connected';\n\n return {\n id: server.id,\n name: server.name,\n type: server.type,\n isBuiltin,\n hasApiKey,\n hasOAuth,\n requiresApiKey,\n requiresOAuth,\n isReady,\n status,\n error: validationError,\n details: validationDetails,\n validatedAt: new Date().toISOString(),\n };\n }\n\n /**\n * Extract builtinId from config (handles JSON string parsing)\n */\n private getBuiltinId(config: unknown): string | null {\n const parsed = this.parseConfig(config);\n const builtinId = parsed?.builtinId;\n\n if (typeof builtinId === 'string') {\n return builtinId;\n }\n return null;\n }\n\n private parseConfig(config: unknown): Record<string, unknown> | null {\n if (typeof config === 'string') {\n try {\n return JSON.parse(config);\n } catch {\n return null;\n }\n }\n return config as Record<string, unknown> | null;\n }\n}\n"],"names":["RemoteHttpMcpServer","RemoteSseMcpServer","Injectable","NotFoundException","PrismaService","DebugService","McpRegistry","McpService","prisma","registry","debugService","findAll","servers","mcpServer","findMany","include","profiles","profile","orderBy","name","map","server","builtinId","getBuiltinId","config","has","metadata","get","findById","id","findUnique","where","tools","oauthToken","toolsCache","create","dto","data","type","JSON","stringify","apiKeyConfig","oauthConfig","update","undefined","delete","getTools","startTime","Date","now","log","createLog","mcpServerId","requestType","requestPayload","method","serverId","status","result","getToolsInternal","updateLog","responsePayload","durationMs","error","errorMessage","Error","message","pkg","parse","instance","createServer","initialize","listTools","parseConfig","remoteServer","url","transport","mcpServerToolsCache","getStatus","getStatusInternal","isBuiltin","hasApiKey","hasOAuth","requiresApiKey","requiresOAuth","validationError","validationDetails","validation","validate","valid","console","length","isReady","details","validatedAt","toISOString","parsed"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/modules/mcp/mcp.service.ts"],"sourcesContent":["/**\n * MCP Service\n *\n * Business logic for MCP server management.\n */\n\nimport {\n ExternalMcpServer,\n RemoteHttpMcpServer,\n RemoteSseMcpServer,\n} from '@dxheroes/local-mcp-core';\nimport { Injectable, NotFoundException } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { DebugService } from '../debug/debug.service.js';\nimport type { CreateMcpServerDto } from './dto/create-mcp-server.dto.js';\nimport type { UpdateMcpServerDto } from './dto/update-mcp-server.dto.js';\nimport { McpRegistry } from './mcp-registry.js';\n\n@Injectable()\nexport class McpService {\n constructor(\n private readonly prisma: PrismaService,\n private readonly registry: McpRegistry,\n private readonly debugService: DebugService\n ) {}\n\n /**\n * Get all MCP servers\n */\n async findAll() {\n const servers = await this.prisma.mcpServer.findMany({\n include: {\n profiles: {\n include: {\n profile: true,\n },\n },\n },\n orderBy: { name: 'asc' },\n });\n\n // Enrich with metadata from registry for builtin servers\n return servers.map((server) => {\n const builtinId = this.getBuiltinId(server.config);\n\n if (builtinId && this.registry.has(builtinId)) {\n const metadata = this.registry.get(builtinId)?.metadata;\n return {\n ...server,\n metadata,\n };\n }\n\n return server;\n });\n }\n\n /**\n * Get a specific MCP server\n */\n async findById(id: string) {\n const server = await this.prisma.mcpServer.findUnique({\n where: { id },\n include: {\n profiles: {\n include: {\n profile: true,\n tools: true,\n },\n },\n oauthToken: true,\n toolsCache: true,\n },\n });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n // Enrich with metadata from registry for builtin servers\n const builtinId = this.getBuiltinId(server.config);\n\n if (builtinId && this.registry.has(builtinId)) {\n const metadata = this.registry.get(builtinId)?.metadata;\n return {\n ...server,\n metadata,\n };\n }\n\n return server;\n }\n\n /**\n * Create a new MCP server\n */\n async create(dto: CreateMcpServerDto) {\n return this.prisma.mcpServer.create({\n data: {\n name: dto.name,\n type: dto.type,\n config: JSON.stringify(dto.config || {}),\n apiKeyConfig: dto.apiKeyConfig ? JSON.stringify(dto.apiKeyConfig) : null,\n oauthConfig: dto.oauthConfig ? JSON.stringify(dto.oauthConfig) : null,\n },\n });\n }\n\n /**\n * Update an MCP server\n */\n async update(id: string, dto: UpdateMcpServerDto) {\n const server = await this.prisma.mcpServer.findUnique({ where: { id } });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n return this.prisma.mcpServer.update({\n where: { id },\n data: {\n name: dto.name,\n type: dto.type,\n config:\n dto.config !== undefined\n ? typeof dto.config === 'string'\n ? dto.config\n : JSON.stringify(dto.config)\n : undefined,\n apiKeyConfig: dto.apiKeyConfig !== undefined ? JSON.stringify(dto.apiKeyConfig) : undefined,\n oauthConfig: dto.oauthConfig !== undefined ? JSON.stringify(dto.oauthConfig) : undefined,\n },\n });\n }\n\n /**\n * Delete an MCP server\n */\n async delete(id: string) {\n const server = await this.prisma.mcpServer.findUnique({ where: { id } });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${id} not found`);\n }\n\n await this.prisma.mcpServer.delete({ where: { id } });\n }\n\n /**\n * Get tools from an MCP server\n */\n async getTools(id: string) {\n const startTime = Date.now();\n\n // Create debug log entry\n const log = await this.debugService.createLog({\n mcpServerId: id,\n requestType: 'tools/list',\n requestPayload: JSON.stringify({ method: 'tools/list', serverId: id }),\n status: 'pending',\n });\n\n try {\n const result = await this.getToolsInternal(id);\n\n // Update debug log with success\n await this.debugService.updateLog(log.id, {\n responsePayload: JSON.stringify(result),\n status: 'success',\n durationMs: Date.now() - startTime,\n });\n\n return result;\n } catch (error) {\n // Update debug log with error\n await this.debugService.updateLog(log.id, {\n status: 'error',\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n durationMs: Date.now() - startTime,\n });\n throw error;\n }\n }\n\n /**\n * Internal method to get tools from an MCP server\n */\n private async getToolsInternal(id: string) {\n const server = await this.findById(id);\n const builtinId = this.getBuiltinId(server.config);\n\n // For builtin servers, get tools from the registry\n if (builtinId && this.registry.has(builtinId)) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n // Get API key config if set\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n // Create server instance and list tools\n const instance = pkg.createServer(apiKeyConfig);\n await instance.initialize();\n const tools = await instance.listTools();\n return { tools };\n }\n }\n\n // For remote_http servers, connect and fetch tools\n if (server.type === 'remote_http') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n return { tools };\n }\n\n // For remote_sse servers, connect and fetch tools\n if (server.type === 'remote_sse') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n return { tools };\n }\n\n // For external (NPX/stdio) servers, spawn and fetch tools\n if (server.type === 'external') {\n const config = this.parseConfig(server.config) as {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n workingDirectory?: string;\n autoRestart?: boolean;\n maxRestartAttempts?: number;\n startupTimeout?: number;\n shutdownTimeout?: number;\n };\n\n const externalServer = new ExternalMcpServer(config);\n try {\n await externalServer.initialize();\n const tools = await externalServer.listTools();\n return { tools };\n } finally {\n // Shutdown after fetching tools\n await externalServer.shutdown();\n }\n }\n\n // For cached tools from external servers (fallback)\n const tools = await this.prisma.mcpServerToolsCache.findMany({\n where: { mcpServerId: id },\n });\n return { tools };\n }\n\n /**\n * Get MCP server status with real validation\n */\n async getStatus(id: string) {\n const startTime = Date.now();\n\n // Create debug log entry\n const log = await this.debugService.createLog({\n mcpServerId: id,\n requestType: 'status/check',\n requestPayload: JSON.stringify({ method: 'status/check', serverId: id }),\n status: 'pending',\n });\n\n try {\n const result = await this.getStatusInternal(id);\n\n // Update debug log with success or error based on status\n await this.debugService.updateLog(log.id, {\n responsePayload: JSON.stringify(result),\n status: result.status === 'error' ? 'error' : 'success',\n errorMessage: result.error,\n durationMs: Date.now() - startTime,\n });\n\n return result;\n } catch (error) {\n // Update debug log with error\n await this.debugService.updateLog(log.id, {\n status: 'error',\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n durationMs: Date.now() - startTime,\n });\n throw error;\n }\n }\n\n /**\n * Internal method to get MCP server status with real validation\n */\n private async getStatusInternal(id: string) {\n const server = await this.findById(id);\n const builtinId = this.getBuiltinId(server.config);\n\n // Check if it's a builtin server\n const isBuiltin = builtinId && this.registry.has(builtinId);\n\n // Check API key config\n const hasApiKey = !!server.apiKeyConfig;\n\n // Check OAuth token\n const hasOAuth = !!server.oauthToken;\n\n // Get metadata\n const metadata = isBuiltin ? this.registry.get(builtinId)?.metadata : null;\n const requiresApiKey = metadata?.requiresApiKey ?? false;\n const requiresOAuth = metadata?.requiresOAuth ?? false;\n\n // Default status\n let status: 'connected' | 'error' | 'unknown' = 'unknown';\n let validationError: string | undefined;\n let validationDetails: string | undefined;\n\n // For builtin servers with API key, actually validate\n if (isBuiltin && hasApiKey) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n try {\n const instance = pkg.createServer(apiKeyConfig);\n // Call validate() method\n const validation = await instance.validate();\n status = validation.valid ? 'connected' : 'error';\n validationError = validation.error;\n validationDetails = validation.valid\n ? 'API key validated successfully'\n : `Validation failed: ${validation.error}`;\n console.log(`[McpService] Validation result for ${server.name}:`, validation);\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection test failed: ${validationError}`;\n console.error(`[McpService] Validation error for ${server.name}:`, error);\n }\n }\n } else if (!hasApiKey && requiresApiKey) {\n status = 'error';\n validationError = 'API key required';\n validationDetails = 'This server requires an API key to function';\n } else if (isBuiltin && !requiresApiKey) {\n status = 'connected';\n validationDetails = 'Server ready (no API key required)';\n }\n\n // For remote_http servers, validate by connecting\n if (server.type === 'remote_http' && status === 'unknown') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n try {\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully. ${tools.length} tools available.`;\n console.log(\n `[McpService] Remote HTTP validation for ${server.name}: ${tools.length} tools`\n );\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection failed: ${validationError}`;\n console.error(`[McpService] Remote HTTP validation error for ${server.name}:`, error);\n }\n }\n\n // For remote_sse servers, validate by connecting\n if (server.type === 'remote_sse' && status === 'unknown') {\n const config = this.parseConfig(server.config) as { url: string };\n const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;\n\n try {\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n const tools = await remoteServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully (SSE). ${tools.length} tools available.`;\n console.log(`[McpService] Remote SSE validation for ${server.name}: ${tools.length} tools`);\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection failed: ${validationError}`;\n console.error(`[McpService] Remote SSE validation error for ${server.name}:`, error);\n }\n }\n\n // For external (NPX/stdio) servers, validate by spawning and checking\n if (server.type === 'external' && status === 'unknown') {\n const config = this.parseConfig(server.config) as {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n workingDirectory?: string;\n };\n\n if (!config.command) {\n status = 'error';\n validationError = 'Command is required for external MCP servers';\n validationDetails = 'Missing command configuration';\n } else {\n try {\n const externalServer = new ExternalMcpServer(config);\n await externalServer.initialize();\n const tools = await externalServer.listTools();\n status = 'connected';\n validationDetails = `Connected successfully (stdio). ${tools.length} tools available.`;\n console.log(`[McpService] External validation for ${server.name}: ${tools.length} tools`);\n // Shutdown after validation\n await externalServer.shutdown();\n } catch (error) {\n status = 'error';\n validationError = error instanceof Error ? error.message : 'Unknown error';\n validationDetails = `Connection failed: ${validationError}`;\n console.error(`[McpService] External validation error for ${server.name}:`, error);\n }\n }\n }\n\n const isReady = status === 'connected';\n\n return {\n id: server.id,\n name: server.name,\n type: server.type,\n isBuiltin,\n hasApiKey,\n hasOAuth,\n requiresApiKey,\n requiresOAuth,\n isReady,\n status,\n error: validationError,\n details: validationDetails,\n validatedAt: new Date().toISOString(),\n };\n }\n\n /**\n * Extract builtinId from config (handles JSON string parsing)\n */\n private getBuiltinId(config: unknown): string | null {\n const parsed = this.parseConfig(config);\n const builtinId = parsed?.builtinId;\n\n if (typeof builtinId === 'string') {\n return builtinId;\n }\n return null;\n }\n\n private parseConfig(config: unknown): Record<string, unknown> | null {\n if (typeof config === 'string') {\n try {\n return JSON.parse(config);\n } catch {\n return null;\n }\n }\n return config as Record<string, unknown> | null;\n }\n}\n"],"names":["ExternalMcpServer","RemoteHttpMcpServer","RemoteSseMcpServer","Injectable","NotFoundException","PrismaService","DebugService","McpRegistry","McpService","prisma","registry","debugService","findAll","servers","mcpServer","findMany","include","profiles","profile","orderBy","name","map","server","builtinId","getBuiltinId","config","has","metadata","get","findById","id","findUnique","where","tools","oauthToken","toolsCache","create","dto","data","type","JSON","stringify","apiKeyConfig","oauthConfig","update","undefined","delete","getTools","startTime","Date","now","log","createLog","mcpServerId","requestType","requestPayload","method","serverId","status","result","getToolsInternal","updateLog","responsePayload","durationMs","error","errorMessage","Error","message","pkg","parse","instance","createServer","initialize","listTools","parseConfig","remoteServer","url","transport","externalServer","shutdown","mcpServerToolsCache","getStatus","getStatusInternal","isBuiltin","hasApiKey","hasOAuth","requiresApiKey","requiresOAuth","validationError","validationDetails","validation","validate","valid","console","length","command","isReady","details","validatedAt","toISOString","parsed"],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
function _ts_metadata(k, v) {
|
|
12
12
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
13
13
|
}
|
|
14
|
-
import { RemoteHttpMcpServer, RemoteSseMcpServer } from "@dxheroes/local-mcp-core";
|
|
14
|
+
import { ExternalMcpServer, RemoteHttpMcpServer, RemoteSseMcpServer } from "@dxheroes/local-mcp-core";
|
|
15
15
|
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
|
|
16
16
|
import { PrismaService } from "../database/prisma.service.js";
|
|
17
17
|
import { DebugService } from "../debug/debug.service.js";
|
|
@@ -334,6 +334,22 @@ export class ProxyService {
|
|
|
334
334
|
this.serverInstances.set(server.id, remoteServer);
|
|
335
335
|
return remoteServer;
|
|
336
336
|
}
|
|
337
|
+
// For external servers (NPX/stdio), create ExternalMcpServer
|
|
338
|
+
if (server.type === 'external' && config?.command) {
|
|
339
|
+
const externalServer = new ExternalMcpServer({
|
|
340
|
+
command: config.command,
|
|
341
|
+
args: config.args,
|
|
342
|
+
env: config.env,
|
|
343
|
+
workingDirectory: config.workingDirectory,
|
|
344
|
+
autoRestart: config.autoRestart,
|
|
345
|
+
maxRestartAttempts: config.maxRestartAttempts,
|
|
346
|
+
startupTimeout: config.startupTimeout,
|
|
347
|
+
shutdownTimeout: config.shutdownTimeout
|
|
348
|
+
});
|
|
349
|
+
await externalServer.initialize();
|
|
350
|
+
this.serverInstances.set(server.id, externalServer);
|
|
351
|
+
return externalServer;
|
|
352
|
+
}
|
|
337
353
|
return null;
|
|
338
354
|
}
|
|
339
355
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/modules/proxy/proxy.service.ts"],"sourcesContent":["/**\n * Proxy Service\n *\n * Handles MCP protocol proxying for profiles.\n */\n\nimport type { ApiKeyConfig as CoreApiKeyConfig, McpServer } from '@dxheroes/local-mcp-core';\nimport { RemoteHttpMcpServer, RemoteSseMcpServer } from '@dxheroes/local-mcp-core';\nimport { Injectable, Logger, NotFoundException } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { DebugService } from '../debug/debug.service.js';\nimport { McpRegistry } from '../mcp/mcp-registry.js';\n\ninterface McpToolCall {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\nexport interface McpRequest {\n jsonrpc: '2.0';\n id: string | number;\n method: string;\n params?: unknown;\n}\n\nexport interface McpResponse {\n jsonrpc: '2.0';\n id: string | number;\n result?: unknown;\n error?: {\n code: number;\n message: string;\n data?: unknown;\n };\n}\n\ninterface ServerConfig {\n builtinId?: string;\n url?: string;\n}\n\ninterface StoredApiKeyConfig {\n apiKey: string;\n headerName?: string;\n headerValueTemplate?: string;\n}\n\n@Injectable()\nexport class ProxyService {\n private readonly logger = new Logger(ProxyService.name);\n private readonly serverInstances = new Map<string, McpServer>();\n\n constructor(\n private readonly prisma: PrismaService,\n private readonly registry: McpRegistry,\n private readonly debugService: DebugService\n ) {}\n\n /**\n * Handle MCP JSON-RPC request for a profile\n */\n async handleRequest(profileName: string, request: McpRequest): Promise<McpResponse> {\n const requestId = request.id;\n const startTime = Date.now();\n\n // Get profile with servers\n const profile = await this.prisma.profile.findUnique({\n where: { name: profileName },\n include: {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' },\n },\n },\n });\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n // Skip logging for initialize (just handshaking)\n const shouldLog = request.method !== 'initialize';\n\n // Create debug log entry\n let logId: string | null = null;\n if (shouldLog) {\n try {\n const log = await this.debugService.createLog({\n profileId: profile.id,\n requestType: request.method,\n requestPayload: JSON.stringify(request),\n status: 'pending',\n });\n logId = log.id;\n } catch (logError) {\n this.logger.warn(`Failed to create debug log: ${logError}`);\n }\n }\n\n try {\n // Handle different MCP methods\n let response: McpResponse;\n\n switch (request.method) {\n case 'initialize':\n response = await this.handleInitialize(requestId, profile);\n break;\n\n case 'tools/list':\n response = await this.handleToolsList(requestId, profile);\n break;\n\n case 'tools/call':\n response = await this.handleToolsCall(\n requestId,\n profile,\n request.params as McpToolCall,\n logId\n );\n break;\n\n case 'resources/list':\n response = await this.handleResourcesList(requestId, profile);\n break;\n\n case 'resources/read':\n response = await this.handleResourcesRead(\n requestId,\n profile,\n request.params as { uri: string }\n );\n break;\n\n default:\n response = {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32601,\n message: `Method not found: ${request.method}`,\n },\n };\n }\n\n // Update debug log with success\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n responsePayload: JSON.stringify(response),\n status: response.error ? 'error' : 'success',\n errorMessage: response.error?.message,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return response;\n } catch (error) {\n this.logger.error(`MCP request error: ${error}`);\n\n const errorMessage = error instanceof Error ? error.message : 'Internal error';\n\n // Update debug log with error\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n status: 'error',\n errorMessage,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32603,\n message: errorMessage,\n },\n };\n }\n }\n\n private async handleInitialize(\n requestId: string | number,\n _profile: { name: string }\n ): Promise<McpResponse> {\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n protocolVersion: '2024-11-05',\n capabilities: {\n tools: {},\n resources: {},\n },\n serverInfo: {\n name: 'Local MCP Gateway',\n version: '0.1.0',\n },\n },\n };\n }\n\n private async handleToolsList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n customDescription: string | null;\n }>;\n }>;\n }\n ): Promise<McpResponse> {\n const allTools: Array<{\n name: string;\n description: string;\n inputSchema: unknown;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Apply tool customizations\n for (const tool of tools) {\n const customization = profileServer.tools.find((t) => t.toolName === tool.name);\n\n if (!customization || customization.isEnabled) {\n allTools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n inputSchema: tool.inputSchema,\n });\n }\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n tools: allTools,\n },\n };\n }\n\n private async handleToolsCall(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n }>;\n }>;\n },\n params: McpToolCall,\n logId: string | null\n ): Promise<McpResponse> {\n // Find which server has this tool\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Check if this server has the requested tool (by name or custom name)\n const customization = profileServer.tools.find(\n (t) => t.customName === params.name || t.toolName === params.name\n );\n\n // Skip if tool is disabled\n if (customization && !customization.isEnabled) {\n continue;\n }\n\n const toolName = customization?.toolName || params.name;\n const hasTool = tools.some((t) => t.name === toolName);\n\n if (hasTool) {\n // Update log with the server that handled this tool call\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n mcpServerId: server.id,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log with server info: ${logError}`);\n }\n }\n\n const result = await instance.callTool(toolName, params.arguments || {});\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result,\n };\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Tool not found: ${params.name}`,\n },\n };\n }\n\n private async handleResourcesList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n }\n ): Promise<McpResponse> {\n const allResources: Array<{\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const resources = await instance.listResources();\n allResources.push(...resources);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n resources: allResources,\n },\n };\n }\n\n private async handleResourcesRead(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n },\n params: { uri: string }\n ): Promise<McpResponse> {\n // Try each server until we find one that has this resource\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n try {\n const content = await instance.readResource(params.uri);\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: content,\n };\n } catch {\n // Resource not found on this server, try next\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Resource not found: ${params.uri}`,\n },\n };\n }\n\n /**\n * Get tools for a specific server by ID\n */\n async getToolsForServer(serverId: string) {\n const server = await this.prisma.mcpServer.findUnique({\n where: { id: serverId },\n });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${serverId} not found`);\n }\n\n const instance = await this.getServerInstance(server);\n if (!instance) {\n return [];\n }\n\n return instance.listTools();\n }\n\n /**\n * Get or create a server instance\n */\n private async getServerInstance(server: {\n id: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n }): Promise<McpServer | null> {\n // Check cache\n const cached = this.serverInstances.get(server.id);\n if (cached) {\n return cached;\n }\n\n // Parse config\n const config = this.parseJson<ServerConfig>(server.config);\n const builtinId = config?.builtinId;\n\n // Get API key config and convert to CoreApiKeyConfig format\n const storedConfig = this.parseJson<StoredApiKeyConfig>(server.apiKeyConfig);\n const apiKeyConfig = this.convertApiKeyConfig(storedConfig);\n\n // For builtin servers, get from registry\n if (builtinId && this.registry.has(builtinId)) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n const instance = pkg.createServer(apiKeyConfig);\n await instance.initialize();\n this.serverInstances.set(server.id, instance);\n return instance;\n }\n }\n\n // For remote_http servers, create RemoteHttpMcpServer\n if (server.type === 'remote_http' && config?.url) {\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For remote_sse servers, create RemoteSseMcpServer\n if (server.type === 'remote_sse' && config?.url) {\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n return null;\n }\n\n /**\n * Convert stored API key config to CoreApiKeyConfig format\n */\n private convertApiKeyConfig(stored: StoredApiKeyConfig | null): CoreApiKeyConfig | null {\n if (!stored?.apiKey) return null;\n\n const headerName = stored.headerName || 'Authorization';\n const template = stored.headerValueTemplate || 'Bearer {apiKey}';\n const headerValue = template.replace('{apiKey}', stored.apiKey);\n\n return {\n apiKey: stored.apiKey,\n headerName,\n headerValue,\n };\n }\n\n private parseJson<T>(value: unknown): T | null {\n if (typeof value === 'string') {\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n }\n return value as T | null;\n }\n\n /**\n * Get profile info with aggregated tools and server status\n */\n async getProfileInfo(profileName: string) {\n const profile = await this.prisma.profile.findUnique({\n where: { name: profileName },\n include: {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' },\n },\n },\n });\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n // Aggregate tools from all servers\n const tools: Array<{ name: string; description: string }> = [];\n const serverStatus: Record<string, { connected: boolean; toolCount: number }> = {};\n\n for (const ps of profile.mcpServers) {\n const server = ps.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const serverTools = await instance.listTools();\n serverStatus[server.id] = { connected: true, toolCount: serverTools.length };\n\n for (const tool of serverTools) {\n const customization = ps.tools.find((t) => t.toolName === tool.name);\n if (!customization || customization.isEnabled) {\n tools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n });\n }\n }\n } else {\n serverStatus[server.id] = { connected: false, toolCount: 0 };\n }\n }\n\n return {\n tools,\n serverStatus: {\n total: profile.mcpServers.length,\n connected: Object.values(serverStatus).filter((s) => s.connected).length,\n servers: serverStatus,\n },\n };\n }\n}\n"],"names":["RemoteHttpMcpServer","RemoteSseMcpServer","Injectable","Logger","NotFoundException","PrismaService","DebugService","McpRegistry","ProxyService","prisma","registry","debugService","logger","name","serverInstances","Map","handleRequest","profileName","request","requestId","id","startTime","Date","now","profile","findUnique","where","include","mcpServers","isActive","mcpServer","tools","orderBy","order","shouldLog","method","logId","log","createLog","profileId","requestType","requestPayload","JSON","stringify","status","logError","warn","response","handleInitialize","handleToolsList","handleToolsCall","params","handleResourcesList","handleResourcesRead","jsonrpc","error","code","message","durationMs","updateLog","responsePayload","errorMessage","Error","_profile","result","protocolVersion","capabilities","resources","serverInfo","version","allTools","profileServer","server","instance","getServerInstance","listTools","tool","customization","find","t","toolName","isEnabled","push","customName","description","customDescription","inputSchema","hasTool","some","mcpServerId","callTool","arguments","allResources","listResources","content","readResource","uri","getToolsForServer","serverId","cached","get","config","parseJson","builtinId","storedConfig","apiKeyConfig","convertApiKeyConfig","has","pkg","createServer","initialize","set","type","url","remoteServer","transport","stored","apiKey","headerName","template","headerValueTemplate","headerValue","replace","value","parse","getProfileInfo","serverStatus","ps","serverTools","connected","toolCount","length","total","Object","values","filter","s","servers"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/modules/proxy/proxy.service.ts"],"sourcesContent":["/**\n * Proxy Service\n *\n * Handles MCP protocol proxying for profiles.\n */\n\nimport type { ApiKeyConfig as CoreApiKeyConfig, McpServer } from '@dxheroes/local-mcp-core';\nimport {\n ExternalMcpServer,\n RemoteHttpMcpServer,\n RemoteSseMcpServer,\n} from '@dxheroes/local-mcp-core';\nimport { Injectable, Logger, NotFoundException } from '@nestjs/common';\nimport { PrismaService } from '../database/prisma.service.js';\nimport { DebugService } from '../debug/debug.service.js';\nimport { McpRegistry } from '../mcp/mcp-registry.js';\n\ninterface McpToolCall {\n name: string;\n arguments?: Record<string, unknown>;\n}\n\nexport interface McpRequest {\n jsonrpc: '2.0';\n id: string | number;\n method: string;\n params?: unknown;\n}\n\nexport interface McpResponse {\n jsonrpc: '2.0';\n id: string | number;\n result?: unknown;\n error?: {\n code: number;\n message: string;\n data?: unknown;\n };\n}\n\ninterface ServerConfig {\n builtinId?: string;\n url?: string;\n // External server config\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n workingDirectory?: string;\n autoRestart?: boolean;\n maxRestartAttempts?: number;\n startupTimeout?: number;\n shutdownTimeout?: number;\n}\n\ninterface StoredApiKeyConfig {\n apiKey: string;\n headerName?: string;\n headerValueTemplate?: string;\n}\n\n@Injectable()\nexport class ProxyService {\n private readonly logger = new Logger(ProxyService.name);\n private readonly serverInstances = new Map<string, McpServer>();\n\n constructor(\n private readonly prisma: PrismaService,\n private readonly registry: McpRegistry,\n private readonly debugService: DebugService\n ) {}\n\n /**\n * Handle MCP JSON-RPC request for a profile\n */\n async handleRequest(profileName: string, request: McpRequest): Promise<McpResponse> {\n const requestId = request.id;\n const startTime = Date.now();\n\n // Get profile with servers\n const profile = await this.prisma.profile.findUnique({\n where: { name: profileName },\n include: {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' },\n },\n },\n });\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n // Skip logging for initialize (just handshaking)\n const shouldLog = request.method !== 'initialize';\n\n // Create debug log entry\n let logId: string | null = null;\n if (shouldLog) {\n try {\n const log = await this.debugService.createLog({\n profileId: profile.id,\n requestType: request.method,\n requestPayload: JSON.stringify(request),\n status: 'pending',\n });\n logId = log.id;\n } catch (logError) {\n this.logger.warn(`Failed to create debug log: ${logError}`);\n }\n }\n\n try {\n // Handle different MCP methods\n let response: McpResponse;\n\n switch (request.method) {\n case 'initialize':\n response = await this.handleInitialize(requestId, profile);\n break;\n\n case 'tools/list':\n response = await this.handleToolsList(requestId, profile);\n break;\n\n case 'tools/call':\n response = await this.handleToolsCall(\n requestId,\n profile,\n request.params as McpToolCall,\n logId\n );\n break;\n\n case 'resources/list':\n response = await this.handleResourcesList(requestId, profile);\n break;\n\n case 'resources/read':\n response = await this.handleResourcesRead(\n requestId,\n profile,\n request.params as { uri: string }\n );\n break;\n\n default:\n response = {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32601,\n message: `Method not found: ${request.method}`,\n },\n };\n }\n\n // Update debug log with success\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n responsePayload: JSON.stringify(response),\n status: response.error ? 'error' : 'success',\n errorMessage: response.error?.message,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return response;\n } catch (error) {\n this.logger.error(`MCP request error: ${error}`);\n\n const errorMessage = error instanceof Error ? error.message : 'Internal error';\n\n // Update debug log with error\n if (logId) {\n const durationMs = Date.now() - startTime;\n try {\n await this.debugService.updateLog(logId, {\n status: 'error',\n errorMessage,\n durationMs,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log: ${logError}`);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32603,\n message: errorMessage,\n },\n };\n }\n }\n\n private async handleInitialize(\n requestId: string | number,\n _profile: { name: string }\n ): Promise<McpResponse> {\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n protocolVersion: '2024-11-05',\n capabilities: {\n tools: {},\n resources: {},\n },\n serverInfo: {\n name: 'Local MCP Gateway',\n version: '0.1.0',\n },\n },\n };\n }\n\n private async handleToolsList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n customDescription: string | null;\n }>;\n }>;\n }\n ): Promise<McpResponse> {\n const allTools: Array<{\n name: string;\n description: string;\n inputSchema: unknown;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Apply tool customizations\n for (const tool of tools) {\n const customization = profileServer.tools.find((t) => t.toolName === tool.name);\n\n if (!customization || customization.isEnabled) {\n allTools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n inputSchema: tool.inputSchema,\n });\n }\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n tools: allTools,\n },\n };\n }\n\n private async handleToolsCall(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n tools: Array<{\n toolName: string;\n isEnabled: boolean;\n customName: string | null;\n }>;\n }>;\n },\n params: McpToolCall,\n logId: string | null\n ): Promise<McpResponse> {\n // Find which server has this tool\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const tools = await instance.listTools();\n\n // Check if this server has the requested tool (by name or custom name)\n const customization = profileServer.tools.find(\n (t) => t.customName === params.name || t.toolName === params.name\n );\n\n // Skip if tool is disabled\n if (customization && !customization.isEnabled) {\n continue;\n }\n\n const toolName = customization?.toolName || params.name;\n const hasTool = tools.some((t) => t.name === toolName);\n\n if (hasTool) {\n // Update log with the server that handled this tool call\n if (logId) {\n try {\n await this.debugService.updateLog(logId, {\n mcpServerId: server.id,\n });\n } catch (logError) {\n this.logger.warn(`Failed to update debug log with server info: ${logError}`);\n }\n }\n\n const result = await instance.callTool(toolName, params.arguments || {});\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result,\n };\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Tool not found: ${params.name}`,\n },\n };\n }\n\n private async handleResourcesList(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n }\n ): Promise<McpResponse> {\n const allResources: Array<{\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n }> = [];\n\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const resources = await instance.listResources();\n allResources.push(...resources);\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: {\n resources: allResources,\n },\n };\n }\n\n private async handleResourcesRead(\n requestId: string | number,\n profile: {\n mcpServers: Array<{\n mcpServer: {\n id: string;\n name: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n };\n }>;\n },\n params: { uri: string }\n ): Promise<McpResponse> {\n // Try each server until we find one that has this resource\n for (const profileServer of profile.mcpServers) {\n const server = profileServer.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n try {\n const content = await instance.readResource(params.uri);\n return {\n jsonrpc: '2.0',\n id: requestId,\n result: content,\n };\n } catch {\n // Resource not found on this server, try next\n }\n }\n }\n\n return {\n jsonrpc: '2.0',\n id: requestId,\n error: {\n code: -32602,\n message: `Resource not found: ${params.uri}`,\n },\n };\n }\n\n /**\n * Get tools for a specific server by ID\n */\n async getToolsForServer(serverId: string) {\n const server = await this.prisma.mcpServer.findUnique({\n where: { id: serverId },\n });\n\n if (!server) {\n throw new NotFoundException(`MCP server ${serverId} not found`);\n }\n\n const instance = await this.getServerInstance(server);\n if (!instance) {\n return [];\n }\n\n return instance.listTools();\n }\n\n /**\n * Get or create a server instance\n */\n private async getServerInstance(server: {\n id: string;\n type: string;\n config: unknown;\n apiKeyConfig: unknown;\n }): Promise<McpServer | null> {\n // Check cache\n const cached = this.serverInstances.get(server.id);\n if (cached) {\n return cached;\n }\n\n // Parse config\n const config = this.parseJson<ServerConfig>(server.config);\n const builtinId = config?.builtinId;\n\n // Get API key config and convert to CoreApiKeyConfig format\n const storedConfig = this.parseJson<StoredApiKeyConfig>(server.apiKeyConfig);\n const apiKeyConfig = this.convertApiKeyConfig(storedConfig);\n\n // For builtin servers, get from registry\n if (builtinId && this.registry.has(builtinId)) {\n const pkg = this.registry.get(builtinId);\n if (pkg) {\n const instance = pkg.createServer(apiKeyConfig);\n await instance.initialize();\n this.serverInstances.set(server.id, instance);\n return instance;\n }\n }\n\n // For remote_http servers, create RemoteHttpMcpServer\n if (server.type === 'remote_http' && config?.url) {\n const remoteServer = new RemoteHttpMcpServer(\n { url: config.url, transport: 'http' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For remote_sse servers, create RemoteSseMcpServer\n if (server.type === 'remote_sse' && config?.url) {\n const remoteServer = new RemoteSseMcpServer(\n { url: config.url, transport: 'sse' },\n null,\n apiKeyConfig\n );\n await remoteServer.initialize();\n this.serverInstances.set(server.id, remoteServer);\n return remoteServer;\n }\n\n // For external servers (NPX/stdio), create ExternalMcpServer\n if (server.type === 'external' && config?.command) {\n const externalServer = new ExternalMcpServer({\n command: config.command,\n args: config.args,\n env: config.env,\n workingDirectory: config.workingDirectory,\n autoRestart: config.autoRestart,\n maxRestartAttempts: config.maxRestartAttempts,\n startupTimeout: config.startupTimeout,\n shutdownTimeout: config.shutdownTimeout,\n });\n await externalServer.initialize();\n this.serverInstances.set(server.id, externalServer);\n return externalServer;\n }\n\n return null;\n }\n\n /**\n * Convert stored API key config to CoreApiKeyConfig format\n */\n private convertApiKeyConfig(stored: StoredApiKeyConfig | null): CoreApiKeyConfig | null {\n if (!stored?.apiKey) return null;\n\n const headerName = stored.headerName || 'Authorization';\n const template = stored.headerValueTemplate || 'Bearer {apiKey}';\n const headerValue = template.replace('{apiKey}', stored.apiKey);\n\n return {\n apiKey: stored.apiKey,\n headerName,\n headerValue,\n };\n }\n\n private parseJson<T>(value: unknown): T | null {\n if (typeof value === 'string') {\n try {\n return JSON.parse(value) as T;\n } catch {\n return null;\n }\n }\n return value as T | null;\n }\n\n /**\n * Get profile info with aggregated tools and server status\n */\n async getProfileInfo(profileName: string) {\n const profile = await this.prisma.profile.findUnique({\n where: { name: profileName },\n include: {\n mcpServers: {\n where: { isActive: true },\n include: {\n mcpServer: true,\n tools: true,\n },\n orderBy: { order: 'asc' },\n },\n },\n });\n\n if (!profile) {\n throw new NotFoundException(`Profile \"${profileName}\" not found`);\n }\n\n // Aggregate tools from all servers\n const tools: Array<{ name: string; description: string }> = [];\n const serverStatus: Record<string, { connected: boolean; toolCount: number }> = {};\n\n for (const ps of profile.mcpServers) {\n const server = ps.mcpServer;\n const instance = await this.getServerInstance(server);\n\n if (instance) {\n const serverTools = await instance.listTools();\n serverStatus[server.id] = { connected: true, toolCount: serverTools.length };\n\n for (const tool of serverTools) {\n const customization = ps.tools.find((t) => t.toolName === tool.name);\n if (!customization || customization.isEnabled) {\n tools.push({\n name: customization?.customName || tool.name,\n description: customization?.customDescription || tool.description,\n });\n }\n }\n } else {\n serverStatus[server.id] = { connected: false, toolCount: 0 };\n }\n }\n\n return {\n tools,\n serverStatus: {\n total: profile.mcpServers.length,\n connected: Object.values(serverStatus).filter((s) => s.connected).length,\n servers: serverStatus,\n },\n };\n }\n}\n"],"names":["ExternalMcpServer","RemoteHttpMcpServer","RemoteSseMcpServer","Injectable","Logger","NotFoundException","PrismaService","DebugService","McpRegistry","ProxyService","prisma","registry","debugService","logger","name","serverInstances","Map","handleRequest","profileName","request","requestId","id","startTime","Date","now","profile","findUnique","where","include","mcpServers","isActive","mcpServer","tools","orderBy","order","shouldLog","method","logId","log","createLog","profileId","requestType","requestPayload","JSON","stringify","status","logError","warn","response","handleInitialize","handleToolsList","handleToolsCall","params","handleResourcesList","handleResourcesRead","jsonrpc","error","code","message","durationMs","updateLog","responsePayload","errorMessage","Error","_profile","result","protocolVersion","capabilities","resources","serverInfo","version","allTools","profileServer","server","instance","getServerInstance","listTools","tool","customization","find","t","toolName","isEnabled","push","customName","description","customDescription","inputSchema","hasTool","some","mcpServerId","callTool","arguments","allResources","listResources","content","readResource","uri","getToolsForServer","serverId","cached","get","config","parseJson","builtinId","storedConfig","apiKeyConfig","convertApiKeyConfig","has","pkg","createServer","initialize","set","type","url","remoteServer","transport","command","externalServer","args","env","workingDirectory","autoRestart","maxRestartAttempts","startupTimeout","shutdownTimeout","stored","apiKey","headerName","template","headerValueTemplate","headerValue","replace","value","parse","getProfileInfo","serverStatus","ps","serverTools","connected","toolCount","length","total","Object","values","filter","s","servers"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxheroes/local-mcp-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "NestJS API server providing MCP proxy, server aggregation, OAuth 2.1, and profile management",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -20,9 +20,11 @@
|
|
|
20
20
|
"reflect-metadata": "^0.2.2",
|
|
21
21
|
"rxjs": "^7.8.2",
|
|
22
22
|
"zod": "^4.3.5",
|
|
23
|
-
"@dxheroes/local-mcp-
|
|
24
|
-
"@dxheroes/local-mcp-
|
|
25
|
-
"@dxheroes/mcp-
|
|
23
|
+
"@dxheroes/local-mcp-database": "0.4.5",
|
|
24
|
+
"@dxheroes/local-mcp-core": "0.6.0",
|
|
25
|
+
"@dxheroes/mcp-merk": "0.3.0",
|
|
26
|
+
"@dxheroes/mcp-toggl": "0.3.0",
|
|
27
|
+
"@dxheroes/mcp-gemini-deep-research": "0.5.0"
|
|
26
28
|
},
|
|
27
29
|
"devDependencies": {
|
|
28
30
|
"@nestjs/cli": "^11.0.14",
|
|
@@ -35,7 +37,7 @@
|
|
|
35
37
|
"@types/node": "^25.0.6",
|
|
36
38
|
"typescript": "^5.9.3",
|
|
37
39
|
"vitest": "^4.0.17",
|
|
38
|
-
"@dxheroes/local-mcp-config": "0.4.
|
|
40
|
+
"@dxheroes/local-mcp-config": "0.4.5"
|
|
39
41
|
},
|
|
40
42
|
"scripts": {
|
|
41
43
|
"build": "nest build",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Seed Service
|
|
3
3
|
*
|
|
4
|
-
* Seeds MCP server records from discovered packages.
|
|
4
|
+
* Seeds MCP server records from discovered packages and external presets.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
|
-
import type { DiscoveredMcpPackage } from '@dxheroes/local-mcp-core';
|
|
8
|
+
import type { DiscoveredMcpPackage, ExternalMcpConfig } from '@dxheroes/local-mcp-core';
|
|
9
9
|
import { Injectable, Logger } from '@nestjs/common';
|
|
10
10
|
import { PrismaService } from '../database/prisma.service.js';
|
|
11
11
|
import { SETTING_KEYS } from '../settings/settings.constants.js';
|
|
@@ -14,6 +14,77 @@ interface McpServerConfig {
|
|
|
14
14
|
builtinId?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* External MCP server presets - popular NPX-based MCP servers
|
|
19
|
+
* These are seeded but NOT assigned to any profile
|
|
20
|
+
*/
|
|
21
|
+
interface ExternalMcpPreset {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
config: ExternalMcpConfig;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const EXTERNAL_MCP_PRESETS: ExternalMcpPreset[] = [
|
|
28
|
+
{
|
|
29
|
+
name: 'Playwright MCP',
|
|
30
|
+
description:
|
|
31
|
+
'Browser automation with Playwright - page interactions, screenshots, PDF generation',
|
|
32
|
+
config: {
|
|
33
|
+
command: 'npx',
|
|
34
|
+
args: ['-y', '@playwright/mcp@latest'],
|
|
35
|
+
autoRestart: true,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Sequential Thinking',
|
|
40
|
+
description:
|
|
41
|
+
'Dynamic problem-solving through structured thoughts - analysis, planning, revision',
|
|
42
|
+
config: {
|
|
43
|
+
command: 'npx',
|
|
44
|
+
args: ['-y', '@anthropic/mcp-server-sequential-thinking'],
|
|
45
|
+
autoRestart: true,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Filesystem MCP',
|
|
50
|
+
description: 'File system access - read, write, search, and manage files',
|
|
51
|
+
config: {
|
|
52
|
+
command: 'npx',
|
|
53
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp'],
|
|
54
|
+
autoRestart: true,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Memory MCP',
|
|
59
|
+
description:
|
|
60
|
+
'Knowledge graph-based persistent memory - store and retrieve entities and relations',
|
|
61
|
+
config: {
|
|
62
|
+
command: 'npx',
|
|
63
|
+
args: ['-y', '@modelcontextprotocol/server-memory'],
|
|
64
|
+
autoRestart: true,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'GitHub MCP',
|
|
69
|
+
description: 'GitHub API access - repositories, issues, pull requests, and more',
|
|
70
|
+
config: {
|
|
71
|
+
command: 'npx',
|
|
72
|
+
args: ['-y', '@modelcontextprotocol/server-github'],
|
|
73
|
+
autoRestart: true,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'Example: Fetch MCP',
|
|
78
|
+
description:
|
|
79
|
+
'Example MCP server calling public APIs - use as a template for your own external MCP scripts',
|
|
80
|
+
config: {
|
|
81
|
+
command: 'npx',
|
|
82
|
+
args: ['-y', '@anthropic/mcp-server-fetch'],
|
|
83
|
+
autoRestart: true,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
17
88
|
@Injectable()
|
|
18
89
|
export class McpSeedService {
|
|
19
90
|
private readonly logger = new Logger(McpSeedService.name);
|
|
@@ -21,7 +92,7 @@ export class McpSeedService {
|
|
|
21
92
|
constructor(private readonly prisma: PrismaService) {}
|
|
22
93
|
|
|
23
94
|
/**
|
|
24
|
-
* Run seed data for all discovered MCP packages
|
|
95
|
+
* Run seed data for all discovered MCP packages and external presets
|
|
25
96
|
*
|
|
26
97
|
* Seeding is idempotent - only creates records that don't exist
|
|
27
98
|
*/
|
|
@@ -31,6 +102,7 @@ export class McpSeedService {
|
|
|
31
102
|
// Ensure default profile exists
|
|
32
103
|
await this.ensureDefaultProfile();
|
|
33
104
|
|
|
105
|
+
// Seed builtin packages
|
|
34
106
|
for (const { package: pkg, packageName } of packages) {
|
|
35
107
|
try {
|
|
36
108
|
await this.seedPackage(pkg, packageName);
|
|
@@ -39,9 +111,51 @@ export class McpSeedService {
|
|
|
39
111
|
}
|
|
40
112
|
}
|
|
41
113
|
|
|
114
|
+
// Seed external presets
|
|
115
|
+
await this.seedExternalPresets();
|
|
116
|
+
|
|
42
117
|
this.logger.log('MCP seeding complete');
|
|
43
118
|
}
|
|
44
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Seed external MCP server presets
|
|
122
|
+
* These are NOT assigned to any profile - users manually add them
|
|
123
|
+
*/
|
|
124
|
+
private async seedExternalPresets(): Promise<void> {
|
|
125
|
+
this.logger.log(`Seeding ${EXTERNAL_MCP_PRESETS.length} external MCP presets`);
|
|
126
|
+
|
|
127
|
+
for (const preset of EXTERNAL_MCP_PRESETS) {
|
|
128
|
+
try {
|
|
129
|
+
// Check if this external server already exists (by name)
|
|
130
|
+
const existingServer = await this.prisma.mcpServer.findFirst({
|
|
131
|
+
where: {
|
|
132
|
+
name: preset.name,
|
|
133
|
+
type: 'external',
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (existingServer) {
|
|
138
|
+
this.logger.debug(`External preset ${preset.name} already exists, skipping`);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Create the external MCP server record
|
|
143
|
+
await this.prisma.mcpServer.create({
|
|
144
|
+
data: {
|
|
145
|
+
id: randomUUID(),
|
|
146
|
+
name: preset.name,
|
|
147
|
+
type: 'external',
|
|
148
|
+
config: JSON.stringify(preset.config),
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
this.logger.log(`Created external MCP preset: ${preset.name}`);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
this.logger.error(`Failed to seed external preset ${preset.name}: ${error}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
45
159
|
private async ensureDefaultProfile(): Promise<void> {
|
|
46
160
|
// Check if user intentionally deleted the default profile
|
|
47
161
|
const deletedSetting = await this.prisma.gatewaySetting.findUnique({
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* Business logic for MCP server management.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
ExternalMcpServer,
|
|
9
|
+
RemoteHttpMcpServer,
|
|
10
|
+
RemoteSseMcpServer,
|
|
11
|
+
} from '@dxheroes/local-mcp-core';
|
|
8
12
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
9
13
|
import { PrismaService } from '../database/prisma.service.js';
|
|
10
14
|
import { DebugService } from '../debug/debug.service.js';
|
|
@@ -230,6 +234,30 @@ export class McpService {
|
|
|
230
234
|
return { tools };
|
|
231
235
|
}
|
|
232
236
|
|
|
237
|
+
// For external (NPX/stdio) servers, spawn and fetch tools
|
|
238
|
+
if (server.type === 'external') {
|
|
239
|
+
const config = this.parseConfig(server.config) as {
|
|
240
|
+
command: string;
|
|
241
|
+
args?: string[];
|
|
242
|
+
env?: Record<string, string>;
|
|
243
|
+
workingDirectory?: string;
|
|
244
|
+
autoRestart?: boolean;
|
|
245
|
+
maxRestartAttempts?: number;
|
|
246
|
+
startupTimeout?: number;
|
|
247
|
+
shutdownTimeout?: number;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const externalServer = new ExternalMcpServer(config);
|
|
251
|
+
try {
|
|
252
|
+
await externalServer.initialize();
|
|
253
|
+
const tools = await externalServer.listTools();
|
|
254
|
+
return { tools };
|
|
255
|
+
} finally {
|
|
256
|
+
// Shutdown after fetching tools
|
|
257
|
+
await externalServer.shutdown();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
233
261
|
// For cached tools from external servers (fallback)
|
|
234
262
|
const tools = await this.prisma.mcpServerToolsCache.findMany({
|
|
235
263
|
where: { mcpServerId: id },
|
|
@@ -382,6 +410,38 @@ export class McpService {
|
|
|
382
410
|
}
|
|
383
411
|
}
|
|
384
412
|
|
|
413
|
+
// For external (NPX/stdio) servers, validate by spawning and checking
|
|
414
|
+
if (server.type === 'external' && status === 'unknown') {
|
|
415
|
+
const config = this.parseConfig(server.config) as {
|
|
416
|
+
command: string;
|
|
417
|
+
args?: string[];
|
|
418
|
+
env?: Record<string, string>;
|
|
419
|
+
workingDirectory?: string;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
if (!config.command) {
|
|
423
|
+
status = 'error';
|
|
424
|
+
validationError = 'Command is required for external MCP servers';
|
|
425
|
+
validationDetails = 'Missing command configuration';
|
|
426
|
+
} else {
|
|
427
|
+
try {
|
|
428
|
+
const externalServer = new ExternalMcpServer(config);
|
|
429
|
+
await externalServer.initialize();
|
|
430
|
+
const tools = await externalServer.listTools();
|
|
431
|
+
status = 'connected';
|
|
432
|
+
validationDetails = `Connected successfully (stdio). ${tools.length} tools available.`;
|
|
433
|
+
console.log(`[McpService] External validation for ${server.name}: ${tools.length} tools`);
|
|
434
|
+
// Shutdown after validation
|
|
435
|
+
await externalServer.shutdown();
|
|
436
|
+
} catch (error) {
|
|
437
|
+
status = 'error';
|
|
438
|
+
validationError = error instanceof Error ? error.message : 'Unknown error';
|
|
439
|
+
validationDetails = `Connection failed: ${validationError}`;
|
|
440
|
+
console.error(`[McpService] External validation error for ${server.name}:`, error);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
385
445
|
const isReady = status === 'connected';
|
|
386
446
|
|
|
387
447
|
return {
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ApiKeyConfig as CoreApiKeyConfig, McpServer } from '@dxheroes/local-mcp-core';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
ExternalMcpServer,
|
|
10
|
+
RemoteHttpMcpServer,
|
|
11
|
+
RemoteSseMcpServer,
|
|
12
|
+
} from '@dxheroes/local-mcp-core';
|
|
9
13
|
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
|
10
14
|
import { PrismaService } from '../database/prisma.service.js';
|
|
11
15
|
import { DebugService } from '../debug/debug.service.js';
|
|
@@ -37,6 +41,15 @@ export interface McpResponse {
|
|
|
37
41
|
interface ServerConfig {
|
|
38
42
|
builtinId?: string;
|
|
39
43
|
url?: string;
|
|
44
|
+
// External server config
|
|
45
|
+
command?: string;
|
|
46
|
+
args?: string[];
|
|
47
|
+
env?: Record<string, string>;
|
|
48
|
+
workingDirectory?: string;
|
|
49
|
+
autoRestart?: boolean;
|
|
50
|
+
maxRestartAttempts?: number;
|
|
51
|
+
startupTimeout?: number;
|
|
52
|
+
shutdownTimeout?: number;
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
interface StoredApiKeyConfig {
|
|
@@ -507,6 +520,23 @@ export class ProxyService {
|
|
|
507
520
|
return remoteServer;
|
|
508
521
|
}
|
|
509
522
|
|
|
523
|
+
// For external servers (NPX/stdio), create ExternalMcpServer
|
|
524
|
+
if (server.type === 'external' && config?.command) {
|
|
525
|
+
const externalServer = new ExternalMcpServer({
|
|
526
|
+
command: config.command,
|
|
527
|
+
args: config.args,
|
|
528
|
+
env: config.env,
|
|
529
|
+
workingDirectory: config.workingDirectory,
|
|
530
|
+
autoRestart: config.autoRestart,
|
|
531
|
+
maxRestartAttempts: config.maxRestartAttempts,
|
|
532
|
+
startupTimeout: config.startupTimeout,
|
|
533
|
+
shutdownTimeout: config.shutdownTimeout,
|
|
534
|
+
});
|
|
535
|
+
await externalServer.initialize();
|
|
536
|
+
this.serverInstances.set(server.id, externalServer);
|
|
537
|
+
return externalServer;
|
|
538
|
+
}
|
|
539
|
+
|
|
510
540
|
return null;
|
|
511
541
|
}
|
|
512
542
|
|