Files
goodgo-platform/docs/audits/MCP_MODULE_EXPLORATION_2.md
Ho Ngoc Hai 25b22ea9bd docs: move additional exploration docs to docs/audits/
Move 6 recently generated inquiry and MCP exploration documents
to the centralized audit directory.

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

7.0 KiB

MCP Module Exploration - GoodGo Platform

1. MODULE STRUCTURE & SOURCE FILES

Directory Structure

apps/api/src/modules/mcp/
├── index.ts
├── mcp.module.ts
└── presentation/
    ├── mcp-transport.controller.ts
    └── __tests__/
        └── mcp-transport.controller.spec.ts

All Source Files (4 files)

1. apps/api/src/modules/mcp/index.ts

  • Type: Module entry point (exports)
  • Lines: 1
  • Purpose: Exports the McpIntegrationModule
  • Exports: { McpIntegrationModule }

2. apps/api/src/modules/mcp/mcp.module.ts

  • Type: NestJS Module configuration (22 lines)
  • Key Class: McpIntegrationModule implements OnModuleInit
  • Responsibility:
    • Sets up MCP core module with configuration
    • Initializes TypesenseClient for MCP registry
    • Logs initialized server names on module init
  • Dependencies Injected:
    • TypesenseClientService (from SearchModule)
    • McpRegistryService (from @goodgo/mcp-servers)
    • LoggerService (from SharedModule)
  • Imports: SearchModule, AuthModule, McpCoreModule.forRoot()
  • Controllers: McpTransportController
  • Lifecycle: Implements onModuleInit()

3. apps/api/src/modules/mcp/presentation/mcp-transport.controller.ts

  • Type: NestJS Controller (102 lines)
  • Key Class: McpTransportController
  • Responsibility: HTTP transport layer for MCP SSE connections
  • Decorators: @ApiTags, @ApiBearerAuth, @Controller, @UseGuards(JwtAuthGuard)
  • Properties: transports: Map<string, SSEServerTransport> (session management)
  • Injected: registry: McpRegistryService

Endpoints:

  1. GET /mcp/servers - List available MCP servers (Throttle: 30/60s)
  2. GET /mcp/:serverName/sse - Open SSE connection (Throttle: 5/60s, stricter)
  3. POST /mcp/:serverName/messages - Send message to session (Throttle: 30/60s)

4. apps/api/src/modules/mcp/presentation/tests/mcp-transport.controller.spec.ts

  • Type: Unit Test Suite (174 lines)
  • Framework: Vitest
  • Tests: 11 total in 4 describe blocks
  • Coverage: Security decorators, listServers, handleSse, handleMessage

2. TEST FILES SUMMARY

Total: 1 test file (174 lines)

File: mcp-transport.controller.spec.ts

  • Suites: 4 describe blocks
  • Total Tests: 11
  • Status: Well tested
  • Coverage: Controller endpoints, security, error handling

3. DDD LAYER STRUCTURE

Current Status: SIMPLIFIED (Presentation-only)

What EXISTS:

  • Presentation Layer
    • presentation/mcp-transport.controller.ts (102 lines)
    • presentation/__tests__/mcp-transport.controller.spec.ts (174 lines)

What DOES NOT EXIST:

  • Domain Layer - No entities, value objects, events
  • Application Layer - No handlers, commands, queries
  • Infrastructure Layer - No repositories, adapters

Architecture Notes:

  • Acts as integration wrapper for @goodgo/mcp-servers
  • Simple HTTP-to-SSE bridge
  • In-memory session tracking via Map
  • No business logic or persistence

4. KEY CLASSES/HANDLERS

McpIntegrationModule

  • Status: ⚠️ Partially tested
  • Methods: constructor, onModuleInit()
  • Tests Needed: Module initialization, service integration

McpTransportController

  • Status: Well tested
  • Methods: listServers(), handleSse(), handleMessage()
  • Tests: 11 comprehensive tests covering all methods

5. TESTING PATTERNS FROM OTHER MODULES

Pattern 1: Auth Module (Simple Handler)

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);
  });
});

Pattern 2: Payments Module (Complex Handler)

describe('CreatePaymentHandler', () => {
  let handler: CreatePaymentHandler;
  let mockPaymentRepo: { [K in keyof IPaymentRepository]: ReturnType<typeof vi.fn> };
  let mockGatewayFactory: { getGateway: ReturnType<typeof vi.fn> };
  
  beforeEach(() => {
    mockPaymentRepo = {
      findById: vi.fn(),
      save: vi.fn().mockResolvedValue(undefined),
    };
    handler = new CreatePaymentHandler(mockPaymentRepo as any, mockGatewayFactory as any);
  });
  
  it('creates payment successfully', async () => {
    const result = await handler.execute(command);
    expect(result.paymentId).toBeDefined();
    expect(mockPaymentRepo.save).toHaveBeenCalledTimes(1);
  });
});

Pattern 3: Domain Entity 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);
  });
});

Pattern 4: Infrastructure 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);
  });
});

6. TESTING CONVENTIONS SUMMARY

Framework & Setup

  • Framework: Vitest
  • Environment: Node.js
  • Globals: Enabled
  • File Pattern: *.spec.ts
  • Test Organization: __tests__/ subdirectory

Mocking Patterns

  1. Service Mocks: mockService = { method: vi.fn().mockResolvedValue(value) }
  2. Module Mocks: vi.mock('@goodgo/mcp-servers', () => ({ ... }))
  3. Configuration Mocks: get: vi.fn((key) => env[key])

Test Structure

describe('ClassName', () => {
  let instance: ClassName;
  let mockDep1: Mock;
  
  beforeEach(() => {
    mockDep1 = { method: vi.fn() };
    instance = new ClassName(mockDep1 as any);
  });
  
  describe('methodName', () => {
    it('happy path', async () => {
      mockDep1.method.mockResolvedValue(expected);
      const result = await instance.method();
      expect(result).toEqual(expected);
    });
  });
});

Test Commands

# Run MCP tests
pnpm test -- src/modules/mcp

# Watch mode
pnpm test -- --watch src/modules/mcp

# With coverage
pnpm test -- --coverage src/modules/mcp

7. RECOMMENDATIONS

Immediate

  • Maintain controller test coverage
  • 📝 Add tests for McpIntegrationModule.onModuleInit()
  • 📝 Test module dependency injection

Future

  • 🏗️ Add integration tests for full SSE lifecycle
  • 🏗️ If domain logic added, follow payments module pattern
  • 🏗️ If handlers added, use CQRS patterns from auth/payments modules