mcp-multi-module.e2e.spec.ts•3.91 kB
import { INestApplication, Injectable, Module } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { Tool } from '../src';
import { McpModule } from '../src/mcp/mcp.module';
import { createStreamableClient } from './utils';
@Injectable()
class ToolsA {
  @Tool({
    name: 'toolA',
    description: 'Tool A from ModuleA',
  })
  toolA() {
    return 'Tool A result';
  }
}
@Injectable()
class ToolsB {
  @Tool({
    name: 'toolB',
    description: 'Tool B from ModuleB',
  })
  toolB() {
    return 'Tool B result';
  }
}
const mcpModuleA = McpModule.forRoot({
  name: 'server-a',
  mcpEndpoint: '/servers/a/mcp',
  sseEndpoint: '/servers/a/sse',
  messagesEndpoint: '/servers/a/messages',
  capabilities: { tools: {} },
  version: '0.0.1',
});
const mcpModuleB = McpModule.forRoot({
  name: 'server-b',
  mcpEndpoint: '/servers/b/mcp',
  sseEndpoint: '/servers/b/sse',
  messagesEndpoint: '/servers/b/messages',
  version: '0.0.1',
});
@Module({
  imports: [mcpModuleA],
  providers: [ToolsA],
  exports: [ToolsA],
})
class ModuleA {}
@Module({
  imports: [mcpModuleB],
  providers: [ToolsB],
  exports: [ToolsB],
})
class ModuleB {}
describe('E2E: Multiple MCP servers (Streamable HTTP)', () => {
  let app: INestApplication;
  let statelessApp: INestApplication;
  let statefulServerPort: number;
  let statelessServerPort: number;
  // Set timeout for all tests in this describe block to 15000ms
  jest.setTimeout(15000);
  beforeAll(async () => {
    // Create stateful server (original)
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [ModuleA, ModuleB],
    }).compile();
    app = moduleFixture.createNestApplication();
    await app.listen(0);
    const server = app.getHttpServer();
    if (!server.address()) {
      throw new Error('Server address not found after listen');
    }
    statefulServerPort = (server.address() as import('net').AddressInfo).port;
    // Create stateless server
    const statelessModuleFixture: TestingModule =
      await Test.createTestingModule({
        imports: [ModuleA, ModuleB],
      }).compile();
    statelessApp = statelessModuleFixture.createNestApplication();
    await statelessApp.listen(0);
    const statelessServer = statelessApp.getHttpServer();
    if (!statelessServer.address()) {
      throw new Error('Stateless server address not found after listen');
    }
    statelessServerPort = (
      statelessServer.address() as import('net').AddressInfo
    ).port;
  });
  afterAll(async () => {
    await app.close();
    await statelessApp.close();
  });
  const runClientTests = (stateless: boolean) => {
    describe(`${stateless ? 'stateless' : 'stateful'} client`, () => {
      let port: number;
      beforeAll(async () => {
        port = stateless ? statelessServerPort : statefulServerPort;
      });
      it('should list tools for server A', async () => {
        const client = await createStreamableClient(port, {
          endpoint: '/servers/a/mcp',
        });
        try {
          const tools = await client.listTools();
          expect(tools.tools.length).toBe(1);
          expect(tools.tools.find((t) => t.name === 'toolA')).toBeDefined();
        } finally {
          await client.close();
        }
      });
      it('should list tools for server B', async () => {
        const client = await createStreamableClient(port, {
          endpoint: '/servers/b/mcp',
        });
        try {
          const tools = await client.listTools();
          expect(tools.tools.length).toBe(1);
          expect(tools.tools.find((t) => t.name === 'toolB')).toBeDefined();
        } finally {
          await client.close();
        }
      });
    });
  };
  // Run tests using the [Stateful] Streamable HTTP MCP client
  runClientTests(false);
  // Run tests using the [Stateless] Streamable HTTP MCP client
  runClientTests(true);
});