Move MCP module exploration, quick reference, and inquiries exploration documents to the centralized audit directory. Co-Authored-By: Paperclip <noreply@paperclip.ing>
11 KiB
11 KiB
MCP Module - Quick Reference & Testing Guide
📋 Module at a Glance
| Aspect | Details |
|---|---|
| Location | apps/api/src/modules/mcp/ |
| Total Files | 4 source files (2 TypeScript + 1 module config + 1 export) |
| Test Files | 1 test file (174 lines) |
| Architecture | Presentation layer only (simplified) |
| Testing Framework | Vitest with Globals enabled |
| Test Coverage | Controller well tested, Module/Infrastructure untested |
📁 File Listing (Complete)
✅ TESTED ✅ SOURCE ❌ NOT TESTED
─────────────────────────────────────────────────────────────
MCP module/ mcp/
├── mcp.module.ts [Module config - 22 lines]
├── index.ts [Re-export - 1 line]
└── presentation/
├── mcp-transport.controller.ts [Controller - 102 lines]
└── __tests__/
└── mcp-transport.controller.spec.ts ✅ [174 lines]
File Details
| File | Type | Lines | Status | Purpose |
|---|---|---|---|---|
index.ts |
Export | 1 | ✅ | Module entry point |
mcp.module.ts |
Config | 22 | ⚠️ | NestJS module setup |
mcp-transport.controller.ts |
Controller | 102 | ✅ | HTTP/SSE transport |
mcp-transport.controller.spec.ts |
Test | 174 | ✅ | Controller tests |
🏗️ Architecture Diagram
Current DDD Structure (Simplified)
┌─────────────────────────────────────────────────┐
│ Presentation Layer (Only) ✅ │
├─────────────────────────────────────────────────┤
│ • McpTransportController │
│ - GET /mcp/servers (list) │
│ - GET /mcp/:serverName/sse (connect) │
│ - POST /mcp/:serverName/messages (send) │
├─────────────────────────────────────────────────┤
│ Dependencies: │
│ • McpRegistryService (external) │
│ • JwtAuthGuard (auth layer) │
│ • SSEServerTransport (external) │
└─────────────────────────────────────────────────┘
Missing Layers (Not Implemented)
❌ Domain Layer
- No entities, value objects, events
- No business logic abstractions
❌ Application Layer
- No CQRS handlers
- No command/query objects
- No use case orchestration
❌ Infrastructure Layer
- No repositories
- No external service adapters
- No persistence logic
🧪 Testing Overview
Test File Stats
File: mcp-transport.controller.spec.ts
├── Total Tests: 11
├── Test Suites: 4 describe blocks
├── Total Lines: 174
└── Coverage Areas:
├── Security Decorators: 4 tests
├── listServers(): 2 tests
├── handleSse(): 3 tests
└── handleMessage(): 2 tests
Test Breakdown by Suite
1. Security Decorators (4 tests)
├── JwtAuthGuard applied
├── listServers throttle (30 req/60s)
├── handleSse throttle (5 req/60s) ⚡ stricter
└── handleMessage throttle (30 req/60s)
2. listServers (2 tests)
├── Returns server list
└── Handles empty list
3. handleSse (3 tests)
├── Throws NOT_FOUND for missing server
├── Creates transport & connects
└── Cleans up on connection close
4. handleMessage (2 tests)
├── Throws BAD_REQUEST for missing sessionId
└── Throws NOT_FOUND for expired session
🎯 Key Classes & Methods
McpIntegrationModule
class McpIntegrationModule implements OnModuleInit {
constructor(
typesenseClient: TypesenseClientService,
mcpRegistry: McpRegistryService,
logger: LoggerService,
) {}
async onModuleInit(): Promise<void> {
// 1. Set typesense client on registry
// 2. Re-initialize servers
// 3. Log initialized servers
}
}
McpTransportController
@Controller('mcp')
@UseGuards(JwtAuthGuard)
class McpTransportController {
private transports: Map<string, SSEServerTransport>
constructor(registry: McpRegistryService) {}
@Get('servers')
@Throttle(30/60s)
listServers(): { servers: string[] }
@Get(':serverName/sse')
@Throttle(5/60s) // Stricter limit for SSE
async handleSse(serverName, user, req, res): Promise<void>
@Post(':serverName/messages')
@Throttle(30/60s)
async handleMessage(serverName, user, req, res): Promise<void>
}
🔧 Testing Patterns Used
1. Mock Pattern (Vitest)
// Mocking external module classes
vi.mock('@goodgo/mcp-servers', () => ({
SSEServerTransport: class MockSSEServerTransport {
sessionId = 'mock-session-id';
handlePostMessage = vi.fn().mockResolvedValue(undefined);
constructor(path: string, res: unknown) {}
},
}));
2. Service Mock Pattern
const mockRegistry = {
getServerNames: vi.fn(),
getServer: vi.fn(),
};
3. Decorator Verification Pattern
// Using Reflect API for NestJS decorators
const guards = Reflect.getMetadata('__guards__', McpTransportController);
const throttleLimit = Reflect.getMetadata(
'THROTTLER:LIMITdefault',
McpTransportController.prototype.listServers,
);
4. Error Testing Pattern
// Testing HTTP errors
await expect(
controller.handleSse('nonexistent', user, req, res)
).rejects.toThrow(HttpException);
// Checking status code
try {
await controller.handleSse(...);
} catch (error) {
expect((error as HttpException).getStatus()).toBe(HttpStatus.NOT_FOUND);
}
📚 Testing Patterns from Other Modules
Auth Module - Simple Handler Pattern
// Minimal dependencies, focused testing
describe('LoginUserHandler', () => {
let handler: LoginUserHandler;
let mockTokenService = { generateTokenPair: vi.fn() };
beforeEach(() => {
handler = new LoginUserHandler(mockTokenService as any);
});
it('generates token pair', async () => {
mockTokenService.generateTokenPair.mockResolvedValue(tokenPair);
const result = await handler.execute(command);
expect(result).toEqual(tokenPair);
});
});
Payments Module - Complex Handler Pattern
// Multiple dependencies, rich testing scenarios
describe('CreatePaymentHandler', () => {
let handler: CreatePaymentHandler;
let mockPaymentRepo: { [K in keyof IPaymentRepository]: ReturnType<typeof vi.fn> };
let mockGatewayFactory: { getGateway: ReturnType<typeof vi.fn> };
let mockEventBus: { publish: ReturnType<typeof vi.fn> };
beforeEach(() => {
mockPaymentRepo = {
findById: vi.fn(),
save: vi.fn().mockResolvedValue(undefined),
// ... more methods
};
handler = new CreatePaymentHandler(mockPaymentRepo as any, ...);
});
it('creates payment successfully', async () => {
// Rich test scenario with multiple assertions
expect(result.paymentId).toBeDefined();
expect(mockPaymentRepo.save).toHaveBeenCalledTimes(1);
expect(mockEventBus.publish).toHaveBeenCalled();
});
});
Payments Domain - DDD Pattern
// Explicit imports, entity behavior testing
import { describe, it, expect } from 'vitest';
describe('PaymentEntity', () => {
it('should create payment with events', () => {
const payment = PaymentEntity.createNew(...);
expect(payment.status).toBe('PENDING');
expect(payment.domainEvents).toHaveLength(1);
expect(payment.domainEvents[0]).toBeInstanceOf(PaymentCreatedEvent);
});
it('should not refund non-completed payment', () => {
const payment = createPayment(); // helper
const result = payment.markRefunded();
expect(result.isErr).toBe(true);
});
});
Infrastructure Service - Crypto Pattern
// Complex external service testing
describe('ZalopayService', () => {
let service: ZalopayService;
beforeEach(() => {
const mockConfig = {
get: vi.fn((key) => env[key]),
getOrThrow: vi.fn((key) => env[key] || throw),
};
service = new ZalopayService(mockConfig as any);
});
it('should verify valid callback', () => {
const mac = crypto.createHmac('sha256', key2).update(dataStr).digest('hex');
const result = service.verifyCallback({ data: dataStr, mac });
expect(result.isValid).toBe(true);
expect(result.orderId).toBe('expected-order-id');
});
});
🚀 Running Tests
Test Commands
# Run all MCP tests
pnpm test -- src/modules/mcp
# Run with watch mode
pnpm test -- --watch src/modules/mcp
# Run with coverage
pnpm test -- --coverage src/modules/mcp
# Run specific test file
pnpm test -- src/modules/mcp/presentation/__tests__/mcp-transport.controller.spec.ts
Vitest Configuration
// apps/api/vitest.config.ts
{
test: {
globals: true, // vi, describe, it, expect available globally
environment: 'node',
include: ['src/**/*.spec.ts'], // Matches *.spec.ts pattern
exclude: ['**/*.integration.spec.ts', 'node_modules'],
}
}
💡 Recommendations
Immediate (High Priority)
- ✅ Controller is well tested - maintain this
- 📝 Add tests for
McpIntegrationModule.onModuleInit() - 📝 Test module dependency injection
Future (Lower Priority)
- 🏗️ If domain logic is added, create domain tests
- 🏗️ If application handlers are added, follow payments module pattern
- 🏗️ Add integration tests for full SSE lifecycle
Best Practices to Follow
- Use globals pattern for simple tests (like existing controller tests)
- Use explicit imports for complex domain tests
- Use helper factories for complex entity setup
- Use Reflect API for decorator verification
- Test both happy path AND error cases
- Use regex matching for error message assertions
- Verify service calls with
toHaveBeenCalledWith()
📖 File Location Reference
GoodGo Platform Root
├── apps/api/src/modules/mcp/ ← MCP Module
│ ├── index.ts
│ ├── mcp.module.ts
│ └── presentation/
│ ├── mcp-transport.controller.ts
│ └── __tests__/
│ └── mcp-transport.controller.spec.ts
│
├── apps/api/vitest.config.ts ← Test config
├── apps/api/package.json ← Test scripts
│
└── MCP_MODULE_EXPLORATION.md ← Full documentation
Generated: April 11, 2026