Skip to main content
Glama

Nx MCP Server

Official
by nrwl
http-multi-client.test.ts5.77 kB
import { cleanupNxWorkspace, defaultVersion, e2eCwd, newWorkspace, simpleReactWorkspaceOptions, uniq, TestMCPClient, } from '@nx-console/shared-e2e-utils'; import { spawn, ChildProcess } from 'node:child_process'; import { rmSync } from 'node:fs'; import { join } from 'node:path'; import { workspaceRoot } from 'nx/src/devkit-exports'; describe('HTTP Multi-Client', () => { let serverProcess: ChildProcess; const serverPort = 9922; const workspaceName = uniq('nx-mcp-http-test'); const testWorkspacePath = join(e2eCwd, workspaceName); const serverPath = join(workspaceRoot, 'dist', 'apps', 'nx-mcp', 'main.js'); // Helper to wait for HTTP server to be ready by polling async function waitForServerReady( port: number, timeoutMs = 60000, ): Promise<void> { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { try { // Try to connect to the server // this is not an official supported thing in the spec but it works for checkign aliveness const response = await fetch(`http://localhost:${port}/mcp`, { method: 'OPTIONS', }); // If we get any response (even an error), the server is up console.log(`Server is ready on port ${port}`); return; } catch (error) { // Server not ready yet, wait and retry await new Promise((resolve) => setTimeout(resolve, 500)); } } throw new Error(`Timeout waiting for server to be ready on port ${port}`); } beforeAll(async () => { // Create workspace newWorkspace({ name: workspaceName, options: simpleReactWorkspaceOptions, }); // Start HTTP MCP server without workspace path // The workspace will be determined per-session based on requests serverProcess = spawn( 'node', [serverPath, '--transport=http', `--port=${serverPort}`], { stdio: 'pipe', env: { ...process.env, NX_NO_CLOUD: 'true', MCP_AUTO_OPEN_ENABLED: 'false', }, cwd: testWorkspacePath, // Set working directory to workspace }, ); // Log server output for debugging serverProcess.stdout?.on('data', (data) => { if (process.env['NX_VERBOSE_LOGGING']) { console.log(`[MCP Server] ${data.toString()}`); } }); serverProcess.stderr?.on('data', (data) => { if (process.env['NX_VERBOSE_LOGGING']) { console.error(`[MCP Server Error] ${data.toString()}`); } }); // Wait for server to be ready by polling the endpoint await waitForServerReady(serverPort); console.log(`MCP HTTP server confirmed ready on port ${serverPort}`); }); afterAll(async () => { // Kill server if (serverProcess) { serverProcess.kill('SIGTERM'); // Wait a bit for graceful shutdown await new Promise((resolve) => setTimeout(resolve, 1000)); if (!serverProcess.killed) { serverProcess.kill('SIGKILL'); } } // Clean up workspace await cleanupNxWorkspace(testWorkspacePath, defaultVersion); rmSync(testWorkspacePath, { recursive: true, force: true }); }); it('should handle two simultaneous clients listing tools', async () => { const serverUrl = `http://localhost:${serverPort}/mcp`; // Create two test clients const client1 = new TestMCPClient(serverUrl, 'test-client-1'); const client2 = new TestMCPClient(serverUrl, 'test-client-2'); // Connect both clients in parallel await Promise.all([client1.connect(), client2.connect()]); // Make two simultaneous requests const [tools1, tools2] = await Promise.all([ client1.listTools(), client2.listTools(), ]); // Verify both clients got valid responses expect(tools1).toBeDefined(); expect(tools2).toBeDefined(); const toolNames1 = tools1.map((tool: any) => tool.name); const toolNames2 = tools2.map((tool: any) => tool.name); // Both should have the same set of tools const expectedTools = [ 'nx_docs', 'nx_available_plugins', 'nx_workspace', 'nx_workspace_path', 'nx_project_details', 'nx_generators', 'nx_generator_schema', ]; expect(toolNames1).toEqual(expectedTools); expect(toolNames2).toEqual(expectedTools); console.log('Both clients successfully connected and retrieved tools'); // Disconnect both clients await Promise.all([client1.disconnect(), client2.disconnect()]); }); it('should allow both clients to invoke tools and get results', async () => { const serverUrl = `http://localhost:${serverPort}/mcp`; // Create two test clients const client1 = new TestMCPClient(serverUrl, 'test-client-3'); const client2 = new TestMCPClient(serverUrl, 'test-client-4'); // Connect both clients in parallel await Promise.all([client1.connect(), client2.connect()]); // Make two simultaneous tool calls const [result1, result2] = await Promise.all([ client1.callTool('nx_workspace_path', {}), client2.callTool('nx_workspace_path', {}), ]); // Verify both clients got valid tool results expect(result1.content).toBeDefined(); expect(result2.content).toBeDefined(); // The nx_workspace_path tool returns the workspace path as text expect(result1.content[0].type).toBe('text'); expect(result1.content[0].text).toContain(testWorkspacePath); expect(result2.content[0].type).toBe('text'); expect(result2.content[0].text).toContain(testWorkspacePath); console.log( 'Both clients successfully invoked tools and received correct results', ); // Disconnect both clients await Promise.all([client1.disconnect(), client2.disconnect()]); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nrwl/nx-console'

If you have feedback or need assistance with the MCP directory API, please join our Discord server