Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
nx-graph-server.ts6.45 kB
import { findNxExecutable } from '@nx-console/shared-npm'; import { httpRequest } from '@nx-console/shared-utils'; import { getNxWorkspacePath } from '@nx-console/vscode-configuration'; import { vscodeLogger } from '@nx-console/vscode-output-channels'; import { ChildProcess, spawn } from 'child_process'; import { createServer } from 'net'; import { Disposable, EventEmitter, ExtensionContext } from 'vscode'; let nxGraphServer: NxGraphServer | undefined = undefined; let nxGraphServerAffected: NxGraphServer | undefined = undefined; export function hasNxGraphServer() { return !!nxGraphServer; } export function hasNxGraphServerAffected() { return !!nxGraphServerAffected; } export function getNxGraphServer(context: ExtensionContext, affected = false) { if (affected) { if (!nxGraphServerAffected) { nxGraphServerAffected = new NxGraphServer(5567, affected); nxGraphServerAffected.start(); context.subscriptions.push(nxGraphServerAffected); } return nxGraphServerAffected; } if (!nxGraphServer) { nxGraphServer = new NxGraphServer(5566); nxGraphServer.start(); context.subscriptions.push(nxGraphServer); } return nxGraphServer; } export class NxGraphServer implements Disposable { private currentPort: number | undefined = undefined; private nxGraphProcess: ChildProcess | undefined = undefined; isStarting = false; isStarted = false; updatedEventEmitter = new EventEmitter(); constructor( private startPort: number, private affected = false, ) {} async handleWebviewRequest(request: { type: string; id: string; payload: string; }): Promise< | { type: string; id: string; payload: string; error?: string; } | undefined > { try { if (this.isCrashed) { await this.start(); } if (!this.isStarted) { await this.waitForServerReady(); } const { type, id } = request; let url = `http://localhost:${this.currentPort}/`; switch (type) { case 'requestProjectGraph': url += 'project-graph.json'; break; case 'requestTaskGraph': url += 'task-graph.json'; break; case 'requestExpandedTaskInputs': url += `task-inputs.json?taskId=${request.payload}`; break; case 'requestSourceMaps': url += 'source-maps.json'; break; default: return; } const response = await httpRequest({ url, }); const data = response.responseText; return { type: `${type}Response`, id, payload: data, }; } catch (error) { console.log('error while handling webview request', error); return { ...request, error: `${error}`, }; } } /** * starts nx graph server */ async start(): Promise<{ error: string } | undefined> { if (this.isStarting) { return; } if (this.isStarted && !this.isCrashed) { return; } this.isStarted = false; this.isStarting = true; let port = this.startPort; let isPortAvailable = false; while (!isPortAvailable) { isPortAvailable = await this.checkPort(port); if (!isPortAvailable) { port++; } } this.currentPort = port; try { await this.spawnProcess(port); console.log('successfully started nx graph at port', port); this.isStarted = true; this.isStarting = false; } catch (error) { console.error(`error while starting nx graph: ${error}`); this.isStarting = false; this.isStarted = false; return { error: `${error}` }; } } async restart() { this.dispose(); await this.start(); } private get isCrashed() { return !!this.nxGraphProcess && !!this.nxGraphProcess.exitCode; } private async spawnProcess(port: number): Promise<void> { const workspacePath = getNxWorkspacePath(); const nxExecutable = await findNxExecutable(workspacePath); return await new Promise((resolve, reject) => { vscodeLogger.log(`Starting nx graph server at port ${port}`); const nxGraphProcess = spawn( nxExecutable, [ 'graph', `--port`, `${port}`, '--open', 'false', '--watch', this.affected ? '--affected' : '', ], { cwd: workspacePath, windowsHide: true, shell: true, env: process.env, }, ); nxGraphProcess.stdout.setEncoding('utf8'); nxGraphProcess.stderr.setEncoding('utf8'); let stdErrOutput = ''; nxGraphProcess.stdout.on('data', (data) => { const text: string = data.toString().trim().toLowerCase(); if (!text) return; if (text.includes(`${port}`)) { resolve(); return; } if (text.includes('updated')) { this.updatedEventEmitter.fire(true); } }); nxGraphProcess.stderr.on('data', (data) => { stdErrOutput += data.toString(); }); nxGraphProcess.on('exit', async () => { this.isStarted = false; reject(stdErrOutput); }); this.nxGraphProcess = nxGraphProcess; }); } private checkPort(port: number): Promise<boolean> { return new Promise((resolve) => { const server = createServer(); server.listen(port, '127.0.0.1'); server.on('listening', () => { server.close(); resolve(true); }); server.on('error', () => { resolve(false); }); }); } private waitForServerReady(): Promise<void> { return new Promise((resolve, reject) => { const timeout = 10000; const checkInterval = setInterval(async () => { if (this.isStarted) { clearInterval(checkInterval); clearTimeout(timeoutId); resolve(); } if (this.isCrashed) { clearTimeout(timeoutId); clearInterval(checkInterval); reject(new Error('Server crashed during startup')); } }, 100); const timeoutId = setTimeout(() => { clearInterval(checkInterval); reject(new Error('Server did not start within 10 seconds')); }, timeout); }); } dispose() { this.nxGraphProcess?.kill('SIGINT'); this.nxGraphProcess = undefined; } }

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