Files
goodgo-platform/docs/audits/MCP_QUICK_REFERENCE.md
Ho Ngoc Hai 642b593884 docs: move remaining analysis docs to docs/audits/
Move MCP module exploration, quick reference, and inquiries exploration
documents to the centralized audit directory.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-11 01:38:14 +07:00

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

  1. Use globals pattern for simple tests (like existing controller tests)
  2. Use explicit imports for complex domain tests
  3. Use helper factories for complex entity setup
  4. Use Reflect API for decorator verification
  5. Test both happy path AND error cases
  6. Use regex matching for error message assertions
  7. 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