@dxheroes/local-mcp-backend 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.swcrc +22 -0
  2. package/.turbo/turbo-build.log +9 -0
  3. package/AGENTS.md +360 -0
  4. package/CHANGELOG.md +60 -0
  5. package/Dockerfile +71 -0
  6. package/LICENSE +94 -0
  7. package/dist/app.module.js +72 -0
  8. package/dist/app.module.js.map +1 -0
  9. package/dist/common/decorators/request-id.decorator.js +12 -0
  10. package/dist/common/decorators/request-id.decorator.js.map +1 -0
  11. package/dist/common/filters/all-exceptions.filter.js +61 -0
  12. package/dist/common/filters/all-exceptions.filter.js.map +1 -0
  13. package/dist/common/interceptors/logging.interceptor.js +46 -0
  14. package/dist/common/interceptors/logging.interceptor.js.map +1 -0
  15. package/dist/common/pipes/validation.pipe.js +43 -0
  16. package/dist/common/pipes/validation.pipe.js.map +1 -0
  17. package/dist/config/app.config.js +14 -0
  18. package/dist/config/app.config.js.map +1 -0
  19. package/dist/config/database.config.js +30 -0
  20. package/dist/config/database.config.js.map +1 -0
  21. package/dist/main.js +68 -0
  22. package/dist/main.js.map +1 -0
  23. package/dist/modules/database/database.module.js +27 -0
  24. package/dist/modules/database/database.module.js.map +1 -0
  25. package/dist/modules/database/prisma.service.js +122 -0
  26. package/dist/modules/database/prisma.service.js.map +1 -0
  27. package/dist/modules/debug/debug.controller.js +87 -0
  28. package/dist/modules/debug/debug.controller.js.map +1 -0
  29. package/dist/modules/debug/debug.module.js +30 -0
  30. package/dist/modules/debug/debug.module.js.map +1 -0
  31. package/dist/modules/debug/debug.service.js +126 -0
  32. package/dist/modules/debug/debug.service.js.map +1 -0
  33. package/dist/modules/health/health.controller.js +69 -0
  34. package/dist/modules/health/health.controller.js.map +1 -0
  35. package/dist/modules/health/health.module.js +23 -0
  36. package/dist/modules/health/health.module.js.map +1 -0
  37. package/dist/modules/mcp/dto/create-mcp-server.dto.js +74 -0
  38. package/dist/modules/mcp/dto/create-mcp-server.dto.js.map +1 -0
  39. package/dist/modules/mcp/dto/update-mcp-server.dto.js +74 -0
  40. package/dist/modules/mcp/dto/update-mcp-server.dto.js.map +1 -0
  41. package/dist/modules/mcp/mcp-discovery.service.js +176 -0
  42. package/dist/modules/mcp/mcp-discovery.service.js.map +1 -0
  43. package/dist/modules/mcp/mcp-registry.js +67 -0
  44. package/dist/modules/mcp/mcp-registry.js.map +1 -0
  45. package/dist/modules/mcp/mcp-seed.service.js +122 -0
  46. package/dist/modules/mcp/mcp-seed.service.js.map +1 -0
  47. package/dist/modules/mcp/mcp.controller.js +152 -0
  48. package/dist/modules/mcp/mcp.controller.js.map +1 -0
  49. package/dist/modules/mcp/mcp.module.js +70 -0
  50. package/dist/modules/mcp/mcp.module.js.map +1 -0
  51. package/dist/modules/mcp/mcp.service.js +401 -0
  52. package/dist/modules/mcp/mcp.service.js.map +1 -0
  53. package/dist/modules/oauth/oauth.controller.js +116 -0
  54. package/dist/modules/oauth/oauth.controller.js.map +1 -0
  55. package/dist/modules/oauth/oauth.module.js +31 -0
  56. package/dist/modules/oauth/oauth.module.js.map +1 -0
  57. package/dist/modules/oauth/oauth.service.js +183 -0
  58. package/dist/modules/oauth/oauth.service.js.map +1 -0
  59. package/dist/modules/profiles/profiles.controller.js +241 -0
  60. package/dist/modules/profiles/profiles.controller.js.map +1 -0
  61. package/dist/modules/profiles/profiles.module.js +34 -0
  62. package/dist/modules/profiles/profiles.module.js.map +1 -0
  63. package/dist/modules/profiles/profiles.service.js +390 -0
  64. package/dist/modules/profiles/profiles.service.js.map +1 -0
  65. package/dist/modules/proxy/proxy.controller.js +98 -0
  66. package/dist/modules/proxy/proxy.controller.js.map +1 -0
  67. package/dist/modules/proxy/proxy.module.js +36 -0
  68. package/dist/modules/proxy/proxy.module.js.map +1 -0
  69. package/dist/modules/proxy/proxy.service.js +439 -0
  70. package/dist/modules/proxy/proxy.service.js.map +1 -0
  71. package/docker-entrypoint.sh +10 -0
  72. package/nest-cli.json +10 -0
  73. package/package.json +51 -0
  74. package/src/app.module.ts +59 -0
  75. package/src/common/decorators/request-id.decorator.ts +16 -0
  76. package/src/common/filters/all-exceptions.filter.ts +77 -0
  77. package/src/common/interceptors/logging.interceptor.ts +45 -0
  78. package/src/common/pipes/validation.pipe.ts +31 -0
  79. package/src/config/app.config.ts +15 -0
  80. package/src/config/database.config.ts +34 -0
  81. package/src/main.ts +66 -0
  82. package/src/modules/database/database.module.ts +15 -0
  83. package/src/modules/database/prisma.service.ts +110 -0
  84. package/src/modules/debug/debug.controller.ts +53 -0
  85. package/src/modules/debug/debug.module.ts +16 -0
  86. package/src/modules/debug/debug.service.ts +143 -0
  87. package/src/modules/health/health.controller.ts +48 -0
  88. package/src/modules/health/health.module.ts +13 -0
  89. package/src/modules/mcp/dto/create-mcp-server.dto.ts +53 -0
  90. package/src/modules/mcp/dto/update-mcp-server.dto.ts +53 -0
  91. package/src/modules/mcp/mcp-discovery.service.ts +205 -0
  92. package/src/modules/mcp/mcp-registry.ts +73 -0
  93. package/src/modules/mcp/mcp-seed.service.ts +125 -0
  94. package/src/modules/mcp/mcp.controller.ts +98 -0
  95. package/src/modules/mcp/mcp.module.ts +48 -0
  96. package/src/modules/mcp/mcp.service.ts +427 -0
  97. package/src/modules/oauth/oauth.controller.ts +89 -0
  98. package/src/modules/oauth/oauth.module.ts +17 -0
  99. package/src/modules/oauth/oauth.service.ts +212 -0
  100. package/src/modules/profiles/profiles.controller.ts +177 -0
  101. package/src/modules/profiles/profiles.module.ts +18 -0
  102. package/src/modules/profiles/profiles.service.ts +421 -0
  103. package/src/modules/proxy/proxy.controller.ts +61 -0
  104. package/src/modules/proxy/proxy.module.ts +19 -0
  105. package/src/modules/proxy/proxy.service.ts +595 -0
  106. package/tsconfig.json +28 -0
@@ -0,0 +1,427 @@
1
+ /**
2
+ * MCP Service
3
+ *
4
+ * Business logic for MCP server management.
5
+ */
6
+
7
+ import { RemoteHttpMcpServer, RemoteSseMcpServer } from '@dxheroes/local-mcp-core';
8
+ import { Injectable, NotFoundException } from '@nestjs/common';
9
+ import { PrismaService } from '../database/prisma.service.js';
10
+ import { DebugService } from '../debug/debug.service.js';
11
+ import type { CreateMcpServerDto } from './dto/create-mcp-server.dto.js';
12
+ import type { UpdateMcpServerDto } from './dto/update-mcp-server.dto.js';
13
+ import { McpRegistry } from './mcp-registry.js';
14
+
15
+ @Injectable()
16
+ export class McpService {
17
+ constructor(
18
+ private readonly prisma: PrismaService,
19
+ private readonly registry: McpRegistry,
20
+ private readonly debugService: DebugService
21
+ ) {}
22
+
23
+ /**
24
+ * Get all MCP servers
25
+ */
26
+ async findAll() {
27
+ const servers = await this.prisma.mcpServer.findMany({
28
+ include: {
29
+ profiles: {
30
+ include: {
31
+ profile: true,
32
+ },
33
+ },
34
+ },
35
+ orderBy: { name: 'asc' },
36
+ });
37
+
38
+ // Enrich with metadata from registry for builtin servers
39
+ return servers.map((server) => {
40
+ const builtinId = this.getBuiltinId(server.config);
41
+
42
+ if (builtinId && this.registry.has(builtinId)) {
43
+ const metadata = this.registry.get(builtinId)?.metadata;
44
+ return {
45
+ ...server,
46
+ metadata,
47
+ };
48
+ }
49
+
50
+ return server;
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Get a specific MCP server
56
+ */
57
+ async findById(id: string) {
58
+ const server = await this.prisma.mcpServer.findUnique({
59
+ where: { id },
60
+ include: {
61
+ profiles: {
62
+ include: {
63
+ profile: true,
64
+ tools: true,
65
+ },
66
+ },
67
+ oauthToken: true,
68
+ toolsCache: true,
69
+ },
70
+ });
71
+
72
+ if (!server) {
73
+ throw new NotFoundException(`MCP server ${id} not found`);
74
+ }
75
+
76
+ // Enrich with metadata from registry for builtin servers
77
+ const builtinId = this.getBuiltinId(server.config);
78
+
79
+ if (builtinId && this.registry.has(builtinId)) {
80
+ const metadata = this.registry.get(builtinId)?.metadata;
81
+ return {
82
+ ...server,
83
+ metadata,
84
+ };
85
+ }
86
+
87
+ return server;
88
+ }
89
+
90
+ /**
91
+ * Create a new MCP server
92
+ */
93
+ async create(dto: CreateMcpServerDto) {
94
+ return this.prisma.mcpServer.create({
95
+ data: {
96
+ name: dto.name,
97
+ type: dto.type,
98
+ config: JSON.stringify(dto.config || {}),
99
+ apiKeyConfig: dto.apiKeyConfig ? JSON.stringify(dto.apiKeyConfig) : null,
100
+ oauthConfig: dto.oauthConfig ? JSON.stringify(dto.oauthConfig) : null,
101
+ },
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Update an MCP server
107
+ */
108
+ async update(id: string, dto: UpdateMcpServerDto) {
109
+ const server = await this.prisma.mcpServer.findUnique({ where: { id } });
110
+
111
+ if (!server) {
112
+ throw new NotFoundException(`MCP server ${id} not found`);
113
+ }
114
+
115
+ return this.prisma.mcpServer.update({
116
+ where: { id },
117
+ data: {
118
+ name: dto.name,
119
+ type: dto.type,
120
+ config:
121
+ dto.config !== undefined
122
+ ? typeof dto.config === 'string'
123
+ ? dto.config
124
+ : JSON.stringify(dto.config)
125
+ : undefined,
126
+ apiKeyConfig: dto.apiKeyConfig !== undefined ? JSON.stringify(dto.apiKeyConfig) : undefined,
127
+ oauthConfig: dto.oauthConfig !== undefined ? JSON.stringify(dto.oauthConfig) : undefined,
128
+ },
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Delete an MCP server
134
+ */
135
+ async delete(id: string) {
136
+ const server = await this.prisma.mcpServer.findUnique({ where: { id } });
137
+
138
+ if (!server) {
139
+ throw new NotFoundException(`MCP server ${id} not found`);
140
+ }
141
+
142
+ await this.prisma.mcpServer.delete({ where: { id } });
143
+ }
144
+
145
+ /**
146
+ * Get tools from an MCP server
147
+ */
148
+ async getTools(id: string) {
149
+ const startTime = Date.now();
150
+
151
+ // Create debug log entry
152
+ const log = await this.debugService.createLog({
153
+ mcpServerId: id,
154
+ requestType: 'tools/list',
155
+ requestPayload: JSON.stringify({ method: 'tools/list', serverId: id }),
156
+ status: 'pending',
157
+ });
158
+
159
+ try {
160
+ const result = await this.getToolsInternal(id);
161
+
162
+ // Update debug log with success
163
+ await this.debugService.updateLog(log.id, {
164
+ responsePayload: JSON.stringify(result),
165
+ status: 'success',
166
+ durationMs: Date.now() - startTime,
167
+ });
168
+
169
+ return result;
170
+ } catch (error) {
171
+ // Update debug log with error
172
+ await this.debugService.updateLog(log.id, {
173
+ status: 'error',
174
+ errorMessage: error instanceof Error ? error.message : 'Unknown error',
175
+ durationMs: Date.now() - startTime,
176
+ });
177
+ throw error;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Internal method to get tools from an MCP server
183
+ */
184
+ private async getToolsInternal(id: string) {
185
+ const server = await this.findById(id);
186
+ const builtinId = this.getBuiltinId(server.config);
187
+
188
+ // For builtin servers, get tools from the registry
189
+ if (builtinId && this.registry.has(builtinId)) {
190
+ const pkg = this.registry.get(builtinId);
191
+ if (pkg) {
192
+ // Get API key config if set
193
+ const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;
194
+
195
+ // Create server instance and list tools
196
+ const instance = pkg.createServer(apiKeyConfig);
197
+ await instance.initialize();
198
+ const tools = await instance.listTools();
199
+ return { tools };
200
+ }
201
+ }
202
+
203
+ // For remote_http servers, connect and fetch tools
204
+ if (server.type === 'remote_http') {
205
+ const config = this.parseConfig(server.config) as { url: string };
206
+ const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;
207
+
208
+ const remoteServer = new RemoteHttpMcpServer(
209
+ { url: config.url, transport: 'http' },
210
+ null,
211
+ apiKeyConfig
212
+ );
213
+ await remoteServer.initialize();
214
+ const tools = await remoteServer.listTools();
215
+ return { tools };
216
+ }
217
+
218
+ // For remote_sse servers, connect and fetch tools
219
+ if (server.type === 'remote_sse') {
220
+ const config = this.parseConfig(server.config) as { url: string };
221
+ const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;
222
+
223
+ const remoteServer = new RemoteSseMcpServer(
224
+ { url: config.url, transport: 'sse' },
225
+ null,
226
+ apiKeyConfig
227
+ );
228
+ await remoteServer.initialize();
229
+ const tools = await remoteServer.listTools();
230
+ return { tools };
231
+ }
232
+
233
+ // For cached tools from external servers (fallback)
234
+ const tools = await this.prisma.mcpServerToolsCache.findMany({
235
+ where: { mcpServerId: id },
236
+ });
237
+ return { tools };
238
+ }
239
+
240
+ /**
241
+ * Get MCP server status with real validation
242
+ */
243
+ async getStatus(id: string) {
244
+ const startTime = Date.now();
245
+
246
+ // Create debug log entry
247
+ const log = await this.debugService.createLog({
248
+ mcpServerId: id,
249
+ requestType: 'status/check',
250
+ requestPayload: JSON.stringify({ method: 'status/check', serverId: id }),
251
+ status: 'pending',
252
+ });
253
+
254
+ try {
255
+ const result = await this.getStatusInternal(id);
256
+
257
+ // Update debug log with success or error based on status
258
+ await this.debugService.updateLog(log.id, {
259
+ responsePayload: JSON.stringify(result),
260
+ status: result.status === 'error' ? 'error' : 'success',
261
+ errorMessage: result.error,
262
+ durationMs: Date.now() - startTime,
263
+ });
264
+
265
+ return result;
266
+ } catch (error) {
267
+ // Update debug log with error
268
+ await this.debugService.updateLog(log.id, {
269
+ status: 'error',
270
+ errorMessage: error instanceof Error ? error.message : 'Unknown error',
271
+ durationMs: Date.now() - startTime,
272
+ });
273
+ throw error;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Internal method to get MCP server status with real validation
279
+ */
280
+ private async getStatusInternal(id: string) {
281
+ const server = await this.findById(id);
282
+ const builtinId = this.getBuiltinId(server.config);
283
+
284
+ // Check if it's a builtin server
285
+ const isBuiltin = builtinId && this.registry.has(builtinId);
286
+
287
+ // Check API key config
288
+ const hasApiKey = !!server.apiKeyConfig;
289
+
290
+ // Check OAuth token
291
+ const hasOAuth = !!server.oauthToken;
292
+
293
+ // Get metadata
294
+ const metadata = isBuiltin ? this.registry.get(builtinId)?.metadata : null;
295
+ const requiresApiKey = metadata?.requiresApiKey ?? false;
296
+ const requiresOAuth = metadata?.requiresOAuth ?? false;
297
+
298
+ // Default status
299
+ let status: 'connected' | 'error' | 'unknown' = 'unknown';
300
+ let validationError: string | undefined;
301
+ let validationDetails: string | undefined;
302
+
303
+ // For builtin servers with API key, actually validate
304
+ if (isBuiltin && hasApiKey) {
305
+ const pkg = this.registry.get(builtinId);
306
+ if (pkg) {
307
+ const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;
308
+
309
+ try {
310
+ const instance = pkg.createServer(apiKeyConfig);
311
+ // Call validate() method
312
+ const validation = await instance.validate();
313
+ status = validation.valid ? 'connected' : 'error';
314
+ validationError = validation.error;
315
+ validationDetails = validation.valid
316
+ ? 'API key validated successfully'
317
+ : `Validation failed: ${validation.error}`;
318
+ console.log(`[McpService] Validation result for ${server.name}:`, validation);
319
+ } catch (error) {
320
+ status = 'error';
321
+ validationError = error instanceof Error ? error.message : 'Unknown error';
322
+ validationDetails = `Connection test failed: ${validationError}`;
323
+ console.error(`[McpService] Validation error for ${server.name}:`, error);
324
+ }
325
+ }
326
+ } else if (!hasApiKey && requiresApiKey) {
327
+ status = 'error';
328
+ validationError = 'API key required';
329
+ validationDetails = 'This server requires an API key to function';
330
+ } else if (isBuiltin && !requiresApiKey) {
331
+ status = 'connected';
332
+ validationDetails = 'Server ready (no API key required)';
333
+ }
334
+
335
+ // For remote_http servers, validate by connecting
336
+ if (server.type === 'remote_http' && status === 'unknown') {
337
+ const config = this.parseConfig(server.config) as { url: string };
338
+ const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;
339
+
340
+ try {
341
+ const remoteServer = new RemoteHttpMcpServer(
342
+ { url: config.url, transport: 'http' },
343
+ null,
344
+ apiKeyConfig
345
+ );
346
+ await remoteServer.initialize();
347
+ const tools = await remoteServer.listTools();
348
+ status = 'connected';
349
+ validationDetails = `Connected successfully. ${tools.length} tools available.`;
350
+ console.log(
351
+ `[McpService] Remote HTTP validation for ${server.name}: ${tools.length} tools`
352
+ );
353
+ } catch (error) {
354
+ status = 'error';
355
+ validationError = error instanceof Error ? error.message : 'Unknown error';
356
+ validationDetails = `Connection failed: ${validationError}`;
357
+ console.error(`[McpService] Remote HTTP validation error for ${server.name}:`, error);
358
+ }
359
+ }
360
+
361
+ // For remote_sse servers, validate by connecting
362
+ if (server.type === 'remote_sse' && status === 'unknown') {
363
+ const config = this.parseConfig(server.config) as { url: string };
364
+ const apiKeyConfig = server.apiKeyConfig ? JSON.parse(server.apiKeyConfig as string) : null;
365
+
366
+ try {
367
+ const remoteServer = new RemoteSseMcpServer(
368
+ { url: config.url, transport: 'sse' },
369
+ null,
370
+ apiKeyConfig
371
+ );
372
+ await remoteServer.initialize();
373
+ const tools = await remoteServer.listTools();
374
+ status = 'connected';
375
+ validationDetails = `Connected successfully (SSE). ${tools.length} tools available.`;
376
+ console.log(`[McpService] Remote SSE validation for ${server.name}: ${tools.length} tools`);
377
+ } catch (error) {
378
+ status = 'error';
379
+ validationError = error instanceof Error ? error.message : 'Unknown error';
380
+ validationDetails = `Connection failed: ${validationError}`;
381
+ console.error(`[McpService] Remote SSE validation error for ${server.name}:`, error);
382
+ }
383
+ }
384
+
385
+ const isReady = status === 'connected';
386
+
387
+ return {
388
+ id: server.id,
389
+ name: server.name,
390
+ type: server.type,
391
+ isBuiltin,
392
+ hasApiKey,
393
+ hasOAuth,
394
+ requiresApiKey,
395
+ requiresOAuth,
396
+ isReady,
397
+ status,
398
+ error: validationError,
399
+ details: validationDetails,
400
+ validatedAt: new Date().toISOString(),
401
+ };
402
+ }
403
+
404
+ /**
405
+ * Extract builtinId from config (handles JSON string parsing)
406
+ */
407
+ private getBuiltinId(config: unknown): string | null {
408
+ const parsed = this.parseConfig(config);
409
+ const builtinId = parsed?.builtinId;
410
+
411
+ if (typeof builtinId === 'string') {
412
+ return builtinId;
413
+ }
414
+ return null;
415
+ }
416
+
417
+ private parseConfig(config: unknown): Record<string, unknown> | null {
418
+ if (typeof config === 'string') {
419
+ try {
420
+ return JSON.parse(config);
421
+ } catch {
422
+ return null;
423
+ }
424
+ }
425
+ return config as Record<string, unknown> | null;
426
+ }
427
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * OAuth Controller
3
+ *
4
+ * REST API endpoints for OAuth token management.
5
+ */
6
+
7
+ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post } from '@nestjs/common';
8
+ import { OAuthService } from './oauth.service.js';
9
+
10
+ interface StartOAuthDto {
11
+ mcpServerId: string;
12
+ authorizationServerUrl: string;
13
+ clientId: string;
14
+ scopes?: string[];
15
+ redirectUri: string;
16
+ }
17
+
18
+ interface CompleteOAuthDto {
19
+ mcpServerId: string;
20
+ code: string;
21
+ codeVerifier: string;
22
+ tokenEndpoint: string;
23
+ clientId: string;
24
+ redirectUri: string;
25
+ }
26
+
27
+ interface StoreTokenDto {
28
+ accessToken: string;
29
+ refreshToken?: string | null;
30
+ tokenType?: string;
31
+ scope?: string | null;
32
+ expiresIn?: number;
33
+ }
34
+
35
+ @Controller('oauth')
36
+ export class OAuthController {
37
+ constructor(private readonly oauthService: OAuthService) {}
38
+
39
+ /**
40
+ * Start OAuth flow for an MCP server
41
+ */
42
+ @Post('start')
43
+ async startOAuthFlow(@Body() dto: StartOAuthDto) {
44
+ return this.oauthService.startOAuthFlow(dto);
45
+ }
46
+
47
+ /**
48
+ * Complete OAuth flow with authorization code
49
+ */
50
+ @Post('complete')
51
+ async completeOAuthFlow(@Body() dto: CompleteOAuthDto) {
52
+ return this.oauthService.completeOAuthFlow(dto);
53
+ }
54
+
55
+ /**
56
+ * Manually store a token for an MCP server
57
+ */
58
+ @Post('servers/:serverId/token')
59
+ @HttpCode(HttpStatus.CREATED)
60
+ async storeToken(@Param('serverId') serverId: string, @Body() dto: StoreTokenDto) {
61
+ const expiresAt = dto.expiresIn ? new Date(Date.now() + dto.expiresIn * 1000) : null;
62
+
63
+ return this.oauthService.storeToken({
64
+ mcpServerId: serverId,
65
+ accessToken: dto.accessToken,
66
+ refreshToken: dto.refreshToken,
67
+ tokenType: dto.tokenType || 'Bearer',
68
+ scope: dto.scope,
69
+ expiresAt,
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Get OAuth token for an MCP server
75
+ */
76
+ @Get('servers/:serverId/token')
77
+ async getToken(@Param('serverId') serverId: string) {
78
+ return this.oauthService.getToken(serverId);
79
+ }
80
+
81
+ /**
82
+ * Delete OAuth token for an MCP server
83
+ */
84
+ @Delete('servers/:serverId/token')
85
+ @HttpCode(HttpStatus.NO_CONTENT)
86
+ async deleteToken(@Param('serverId') serverId: string) {
87
+ await this.oauthService.deleteToken(serverId);
88
+ }
89
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * OAuth Module
3
+ *
4
+ * Handles OAuth flows for MCP servers that require OAuth authentication.
5
+ * This is NOT for user authentication - the app has no user auth.
6
+ */
7
+
8
+ import { Module } from '@nestjs/common';
9
+ import { OAuthController } from './oauth.controller.js';
10
+ import { OAuthService } from './oauth.service.js';
11
+
12
+ @Module({
13
+ controllers: [OAuthController],
14
+ providers: [OAuthService],
15
+ exports: [OAuthService],
16
+ })
17
+ export class OAuthModule {}