@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,77 @@
1
+ /**
2
+ * Global Exception Filter
3
+ *
4
+ * Catches all exceptions and formats them consistently.
5
+ */
6
+
7
+ import {
8
+ ArgumentsHost,
9
+ Catch,
10
+ ExceptionFilter,
11
+ HttpException,
12
+ HttpStatus,
13
+ Logger,
14
+ } from '@nestjs/common';
15
+ import { Request, Response } from 'express';
16
+
17
+ interface ErrorResponse {
18
+ statusCode: number;
19
+ message: string;
20
+ error: string;
21
+ timestamp: string;
22
+ path: string;
23
+ requestId?: string;
24
+ }
25
+
26
+ @Catch()
27
+ export class AllExceptionsFilter implements ExceptionFilter {
28
+ private readonly logger = new Logger(AllExceptionsFilter.name);
29
+
30
+ catch(exception: unknown, host: ArgumentsHost): void {
31
+ const ctx = host.switchToHttp();
32
+ const response = ctx.getResponse<Response>();
33
+ const request = ctx.getRequest<Request>();
34
+
35
+ let status = HttpStatus.INTERNAL_SERVER_ERROR;
36
+ let message = 'Internal server error';
37
+ let error = 'Internal Server Error';
38
+
39
+ if (exception instanceof HttpException) {
40
+ status = exception.getStatus();
41
+ const exceptionResponse = exception.getResponse();
42
+
43
+ if (typeof exceptionResponse === 'string') {
44
+ message = exceptionResponse;
45
+ } else if (typeof exceptionResponse === 'object') {
46
+ const responseObj = exceptionResponse as Record<string, unknown>;
47
+ message = (responseObj.message as string) || message;
48
+ error = (responseObj.error as string) || exception.name;
49
+ }
50
+ } else if (exception instanceof Error) {
51
+ message = exception.message;
52
+ error = exception.name;
53
+ }
54
+
55
+ // Log error
56
+ this.logger.error(`${request.method} ${request.url} - ${status} - ${message}`, {
57
+ exception: exception instanceof Error ? exception.stack : String(exception),
58
+ requestId: request.headers['x-request-id'],
59
+ });
60
+
61
+ const errorResponse: ErrorResponse = {
62
+ statusCode: status,
63
+ message,
64
+ error,
65
+ timestamp: new Date().toISOString(),
66
+ path: request.url,
67
+ };
68
+
69
+ // Add request ID if present
70
+ const requestId = request.headers['x-request-id'];
71
+ if (typeof requestId === 'string') {
72
+ errorResponse.requestId = requestId;
73
+ }
74
+
75
+ response.status(status).json(errorResponse);
76
+ }
77
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Logging Interceptor
3
+ *
4
+ * Logs incoming requests and outgoing responses with timing.
5
+ */
6
+
7
+ import { randomUUID } from 'node:crypto';
8
+ import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
9
+ import { Request, Response } from 'express';
10
+ import { Observable } from 'rxjs';
11
+ import { tap } from 'rxjs/operators';
12
+
13
+ @Injectable()
14
+ export class LoggingInterceptor implements NestInterceptor {
15
+ private readonly logger = new Logger('HTTP');
16
+
17
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
18
+ const ctx = context.switchToHttp();
19
+ const request = ctx.getRequest<Request>();
20
+ const response = ctx.getResponse<Response>();
21
+
22
+ // Add request ID if not present
23
+ if (!request.headers['x-request-id']) {
24
+ request.headers['x-request-id'] = randomUUID();
25
+ }
26
+ const requestId = request.headers['x-request-id'];
27
+ response.setHeader('x-request-id', requestId);
28
+
29
+ const { method, url } = request;
30
+ const startTime = Date.now();
31
+
32
+ return next.handle().pipe(
33
+ tap({
34
+ next: () => {
35
+ const duration = Date.now() - startTime;
36
+ this.logger.log(`${method} ${url} ${response.statusCode} - ${duration}ms`);
37
+ },
38
+ error: () => {
39
+ const duration = Date.now() - startTime;
40
+ this.logger.warn(`${method} ${url} ERROR - ${duration}ms`);
41
+ },
42
+ })
43
+ );
44
+ }
45
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Zod Validation Pipe
3
+ *
4
+ * Custom validation pipe that uses Zod schemas.
5
+ */
6
+
7
+ import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
8
+ import { z } from 'zod';
9
+
10
+ @Injectable()
11
+ export class ZodValidationPipe implements PipeTransform {
12
+ constructor(private schema: z.ZodSchema) {}
13
+
14
+ transform(value: unknown) {
15
+ const result = this.schema.safeParse(value);
16
+
17
+ if (!result.success) {
18
+ const errors = result.error.issues.map((issue) => ({
19
+ path: issue.path.join('.'),
20
+ message: issue.message,
21
+ }));
22
+
23
+ throw new BadRequestException({
24
+ message: 'Validation failed',
25
+ errors,
26
+ });
27
+ }
28
+
29
+ return result.data;
30
+ }
31
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Application configuration
3
+ */
4
+
5
+ import { registerAs } from '@nestjs/config';
6
+
7
+ export default registerAs('app', () => ({
8
+ nodeEnv: process.env.NODE_ENV || 'development',
9
+ port: Number.parseInt(process.env.PORT || '3001', 10),
10
+ corsOrigins: process.env.CORS_ORIGINS?.split(',') || [
11
+ 'http://localhost:5173',
12
+ 'http://localhost:3000',
13
+ ],
14
+ logLevel: process.env.LOG_LEVEL || 'info',
15
+ }));
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Database configuration
3
+ */
4
+
5
+ import { homedir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { registerAs } from '@nestjs/config';
8
+
9
+ export default registerAs('database', () => {
10
+ // Parse DATABASE_URL for path
11
+ const databaseUrl = process.env.DATABASE_URL;
12
+
13
+ // Default path if not set
14
+ const defaultPath = join(homedir(), '.local-mcp-gateway-data', 'local-mcp-gateway.db');
15
+
16
+ // Extract path from file: URL
17
+ let path = defaultPath;
18
+ if (databaseUrl?.startsWith('file:')) {
19
+ const filePath = databaseUrl.replace('file:', '');
20
+ if (!filePath.startsWith(':memory:')) {
21
+ // Handle relative paths (./dev.db)
22
+ if (filePath.startsWith('./')) {
23
+ path = join(process.cwd(), filePath.slice(2));
24
+ } else {
25
+ path = filePath;
26
+ }
27
+ }
28
+ }
29
+
30
+ return {
31
+ url: databaseUrl || `file:${defaultPath}`,
32
+ path,
33
+ };
34
+ });
package/src/main.ts ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * NestJS Application Bootstrap
3
+ *
4
+ * Entry point for the Local MCP Gateway backend.
5
+ * No authentication required - immediate access to all features.
6
+ */
7
+
8
+ import { Logger, ValidationPipe } from '@nestjs/common';
9
+ import { ConfigService } from '@nestjs/config';
10
+ import { NestFactory } from '@nestjs/core';
11
+ import compression from 'compression';
12
+ import helmet from 'helmet';
13
+ import { AppModule } from './app.module.js';
14
+ import { AllExceptionsFilter } from './common/filters/all-exceptions.filter.js';
15
+ import { LoggingInterceptor } from './common/interceptors/logging.interceptor.js';
16
+
17
+ async function bootstrap() {
18
+ const logger = new Logger('Bootstrap');
19
+ const app = await NestFactory.create(AppModule, {
20
+ logger: ['error', 'warn', 'log', 'debug', 'verbose'],
21
+ });
22
+
23
+ const configService = app.get(ConfigService);
24
+
25
+ // Security
26
+ app.use(helmet());
27
+ app.use(compression());
28
+
29
+ // CORS
30
+ const corsOrigins = configService.get<string>('CORS_ORIGINS')?.split(',') || [
31
+ 'http://localhost:5173',
32
+ 'http://localhost:3000',
33
+ ];
34
+ app.enableCors({
35
+ origin: corsOrigins,
36
+ credentials: true,
37
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
38
+ });
39
+
40
+ // Global prefix
41
+ app.setGlobalPrefix('api');
42
+
43
+ // Global pipes
44
+ app.useGlobalPipes(
45
+ new ValidationPipe({
46
+ whitelist: true,
47
+ forbidNonWhitelisted: true,
48
+ transform: true,
49
+ transformOptions: { enableImplicitConversion: true },
50
+ })
51
+ );
52
+
53
+ // Global filters
54
+ app.useGlobalFilters(new AllExceptionsFilter());
55
+
56
+ // Global interceptors
57
+ app.useGlobalInterceptors(new LoggingInterceptor());
58
+
59
+ const port = configService.get<number>('PORT') || 3001;
60
+ await app.listen(port);
61
+
62
+ logger.log(`Application is running on: http://localhost:${port}`);
63
+ logger.log(`API available at: http://localhost:${port}/api`);
64
+ }
65
+
66
+ bootstrap();
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Database Module
3
+ *
4
+ * Global module providing Prisma client for database operations.
5
+ */
6
+
7
+ import { Global, Module } from '@nestjs/common';
8
+ import { PrismaService } from './prisma.service.js';
9
+
10
+ @Global()
11
+ @Module({
12
+ providers: [PrismaService],
13
+ exports: [PrismaService],
14
+ })
15
+ export class DatabaseModule {}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Prisma Service
3
+ *
4
+ * NestJS service wrapping Prisma Client with lifecycle management.
5
+ */
6
+
7
+ import { createPrismaAdapter } from '@dxheroes/local-mcp-database';
8
+ import { PrismaClient } from '@dxheroes/local-mcp-database/generated/prisma';
9
+ import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
10
+
11
+ @Injectable()
12
+ export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
13
+ private readonly logger = new Logger(PrismaService.name);
14
+
15
+ constructor() {
16
+ super({
17
+ adapter: createPrismaAdapter(),
18
+ log:
19
+ process.env.NODE_ENV === 'development'
20
+ ? [
21
+ { emit: 'event', level: 'query' },
22
+ { emit: 'stdout', level: 'info' },
23
+ { emit: 'stdout', level: 'warn' },
24
+ { emit: 'stdout', level: 'error' },
25
+ ]
26
+ : [{ emit: 'stdout', level: 'error' }],
27
+ });
28
+ }
29
+
30
+ async onModuleInit() {
31
+ this.logger.log('Connecting to database...');
32
+ await this.$connect();
33
+ this.logger.log('Database connected');
34
+
35
+ // Seed default data on first run
36
+ await this.seedDefaultData();
37
+ }
38
+
39
+ /**
40
+ * Seed default data if database is empty (first run only)
41
+ * Only seeds if no profiles exist - respects user data
42
+ */
43
+ private async seedDefaultData() {
44
+ // Check if any profiles exist - if so, user has data, don't seed
45
+ const profileCount = await this.profile.count();
46
+ if (profileCount > 0) {
47
+ this.logger.debug('Database has existing data, skipping seed');
48
+ return;
49
+ }
50
+
51
+ this.logger.log('First run detected, seeding default data...');
52
+
53
+ // Create default profile
54
+ const defaultProfile = await this.profile.create({
55
+ data: {
56
+ name: 'default',
57
+ description: 'Default MCP profile for general use',
58
+ },
59
+ });
60
+ this.logger.log(`Created default profile: ${defaultProfile.id}`);
61
+
62
+ // Create Context7 MCP server
63
+ const context7 = await this.mcpServer.create({
64
+ data: {
65
+ name: 'Context7',
66
+ type: 'remote_http',
67
+ config: JSON.stringify({ url: 'https://mcp.context7.com/mcp' }),
68
+ },
69
+ });
70
+ this.logger.log(`Created Context7 MCP server: ${context7.id}`);
71
+
72
+ // Link Context7 to default profile
73
+ await this.profileMcpServer.create({
74
+ data: {
75
+ profileId: defaultProfile.id,
76
+ mcpServerId: context7.id,
77
+ order: 0,
78
+ },
79
+ });
80
+ this.logger.log('Linked Context7 to default profile');
81
+
82
+ this.logger.log('Default data seeding complete');
83
+ }
84
+
85
+ async onModuleDestroy() {
86
+ this.logger.log('Disconnecting from database...');
87
+ await this.$disconnect();
88
+ this.logger.log('Database disconnected');
89
+ }
90
+
91
+ /**
92
+ * Clean database (for testing only)
93
+ */
94
+ async cleanDatabase() {
95
+ if (process.env.NODE_ENV !== 'test') {
96
+ throw new Error('cleanDatabase can only be called in test environment');
97
+ }
98
+
99
+ const tablenames = await this.$queryRaw<Array<{ name: string }>>`
100
+ SELECT name FROM sqlite_master
101
+ WHERE type='table'
102
+ AND name NOT LIKE '_prisma%'
103
+ AND name NOT LIKE 'sqlite%'
104
+ `;
105
+
106
+ for (const { name } of tablenames) {
107
+ await this.$executeRawUnsafe(`DELETE FROM "${name}"`);
108
+ }
109
+ }
110
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Debug Controller
3
+ *
4
+ * REST API endpoints for debug log management.
5
+ */
6
+
7
+ import { Controller, Delete, Get, HttpCode, HttpStatus, Query } from '@nestjs/common';
8
+ import { DebugService } from './debug.service.js';
9
+
10
+ @Controller('debug')
11
+ export class DebugController {
12
+ constructor(private readonly debugService: DebugService) {}
13
+
14
+ /**
15
+ * Get debug logs with optional filters
16
+ */
17
+ @Get('logs')
18
+ async getLogs(
19
+ @Query('profileId') profileId?: string,
20
+ @Query('mcpServerId') mcpServerId?: string,
21
+ @Query('status') status?: 'pending' | 'success' | 'error',
22
+ @Query('since') since?: string,
23
+ @Query('until') until?: string,
24
+ @Query('limit') limit?: string,
25
+ @Query('offset') offset?: string
26
+ ) {
27
+ const filter = {
28
+ profileId,
29
+ mcpServerId,
30
+ status,
31
+ since: since ? new Date(since) : undefined,
32
+ until: until ? new Date(until) : undefined,
33
+ };
34
+
35
+ return this.debugService.getLogs(
36
+ filter,
37
+ limit ? Number.parseInt(limit, 10) : 100,
38
+ offset ? Number.parseInt(offset, 10) : 0
39
+ );
40
+ }
41
+
42
+ /**
43
+ * Clear debug logs
44
+ */
45
+ @Delete('logs')
46
+ @HttpCode(HttpStatus.NO_CONTENT)
47
+ async clearLogs(
48
+ @Query('profileId') profileId?: string,
49
+ @Query('mcpServerId') mcpServerId?: string
50
+ ) {
51
+ await this.debugService.clearLogs({ profileId, mcpServerId });
52
+ }
53
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Debug Module
3
+ *
4
+ * Debug logging endpoints for MCP traffic inspection.
5
+ */
6
+
7
+ import { Module } from '@nestjs/common';
8
+ import { DebugController } from './debug.controller.js';
9
+ import { DebugService } from './debug.service.js';
10
+
11
+ @Module({
12
+ controllers: [DebugController],
13
+ providers: [DebugService],
14
+ exports: [DebugService],
15
+ })
16
+ export class DebugModule {}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Debug Service
3
+ *
4
+ * Manages debug logs for MCP traffic inspection.
5
+ * The DebugLog model tracks MCP requests/responses, not general log levels.
6
+ */
7
+
8
+ import { Injectable } from '@nestjs/common';
9
+ import { PrismaService } from '../database/prisma.service.js';
10
+
11
+ interface CreateLogDto {
12
+ profileId?: string | null;
13
+ mcpServerId?: string | null;
14
+ requestType: string;
15
+ requestPayload: string;
16
+ responsePayload?: string | null;
17
+ status: 'pending' | 'success' | 'error';
18
+ errorMessage?: string | null;
19
+ durationMs?: number | null;
20
+ }
21
+
22
+ interface LogFilter {
23
+ profileId?: string;
24
+ mcpServerId?: string;
25
+ status?: 'pending' | 'success' | 'error';
26
+ since?: Date;
27
+ until?: Date;
28
+ }
29
+
30
+ @Injectable()
31
+ export class DebugService {
32
+ constructor(private readonly prisma: PrismaService) {}
33
+
34
+ /**
35
+ * Create a debug log entry for an MCP request
36
+ */
37
+ async createLog(dto: CreateLogDto) {
38
+ return this.prisma.debugLog.create({
39
+ data: {
40
+ profileId: dto.profileId,
41
+ mcpServerId: dto.mcpServerId,
42
+ requestType: dto.requestType,
43
+ requestPayload: dto.requestPayload,
44
+ responsePayload: dto.responsePayload,
45
+ status: dto.status,
46
+ errorMessage: dto.errorMessage,
47
+ durationMs: dto.durationMs,
48
+ },
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Update a debug log entry with response data
54
+ */
55
+ async updateLog(
56
+ id: string,
57
+ data: {
58
+ responsePayload?: string;
59
+ status?: 'pending' | 'success' | 'error';
60
+ errorMessage?: string;
61
+ durationMs?: number;
62
+ mcpServerId?: string;
63
+ }
64
+ ) {
65
+ return this.prisma.debugLog.update({
66
+ where: { id },
67
+ data,
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Get debug logs with optional filters
73
+ */
74
+ async getLogs(filter?: LogFilter, limit = 100, offset = 0) {
75
+ const where: Record<string, unknown> = {};
76
+
77
+ if (filter?.profileId) {
78
+ where.profileId = filter.profileId;
79
+ }
80
+
81
+ if (filter?.mcpServerId) {
82
+ where.mcpServerId = filter.mcpServerId;
83
+ }
84
+
85
+ if (filter?.status) {
86
+ where.status = filter.status;
87
+ }
88
+
89
+ if (filter?.since || filter?.until) {
90
+ where.createdAt = {};
91
+ if (filter.since) {
92
+ (where.createdAt as Record<string, Date>).gte = filter.since;
93
+ }
94
+ if (filter.until) {
95
+ (where.createdAt as Record<string, Date>).lte = filter.until;
96
+ }
97
+ }
98
+
99
+ const [logs, total] = await Promise.all([
100
+ this.prisma.debugLog.findMany({
101
+ where,
102
+ orderBy: { createdAt: 'desc' },
103
+ take: limit,
104
+ skip: offset,
105
+ include: {
106
+ profile: {
107
+ select: { id: true, name: true },
108
+ },
109
+ mcpServer: {
110
+ select: { id: true, name: true },
111
+ },
112
+ },
113
+ }),
114
+ this.prisma.debugLog.count({ where }),
115
+ ]);
116
+
117
+ return {
118
+ logs,
119
+ total,
120
+ limit,
121
+ offset,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Clear debug logs
127
+ */
128
+ async clearLogs(options?: { profileId?: string; mcpServerId?: string }) {
129
+ const where: Record<string, string> = {};
130
+
131
+ if (options?.profileId) {
132
+ where.profileId = options.profileId;
133
+ }
134
+
135
+ if (options?.mcpServerId) {
136
+ where.mcpServerId = options.mcpServerId;
137
+ }
138
+
139
+ await this.prisma.debugLog.deleteMany({
140
+ where: Object.keys(where).length > 0 ? where : undefined,
141
+ });
142
+ }
143
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Health Controller
3
+ *
4
+ * Health check endpoints for monitoring and load balancers.
5
+ */
6
+
7
+ import { Controller, Get } from '@nestjs/common';
8
+ import { PrismaService } from '../database/prisma.service.js';
9
+
10
+ @Controller('health')
11
+ export class HealthController {
12
+ constructor(private readonly prisma: PrismaService) {}
13
+
14
+ /**
15
+ * Basic liveness probe
16
+ */
17
+ @Get()
18
+ async getHealth() {
19
+ return {
20
+ status: 'ok',
21
+ timestamp: new Date().toISOString(),
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Readiness probe with database check
27
+ */
28
+ @Get('ready')
29
+ async getReadiness() {
30
+ try {
31
+ // Test database connectivity
32
+ await this.prisma.$queryRaw`SELECT 1`;
33
+
34
+ return {
35
+ status: 'ok',
36
+ timestamp: new Date().toISOString(),
37
+ database: 'connected',
38
+ };
39
+ } catch (error) {
40
+ return {
41
+ status: 'error',
42
+ timestamp: new Date().toISOString(),
43
+ database: 'disconnected',
44
+ error: error instanceof Error ? error.message : 'Unknown error',
45
+ };
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Health Module
3
+ *
4
+ * Health check endpoints for monitoring.
5
+ */
6
+
7
+ import { Module } from '@nestjs/common';
8
+ import { HealthController } from './health.controller.js';
9
+
10
+ @Module({
11
+ controllers: [HealthController],
12
+ })
13
+ export class HealthModule {}