Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
nx-mcp-server-wrapper.ts12.2 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { NxConsoleTelemetryLogger } from '@nx-console/shared-telemetry'; import { Logger } from '@nx-console/shared-utils'; import { getMcpLogger } from './mcp-logger'; import { ServerCapabilities, SetLevelRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { NxGeneratorsRequestOptions } from '@nx-console/language-server-types'; import { GeneratorCollectionInfo } from '@nx-console/shared-schema'; import { NxWorkspace, CIPEInfo, CIPEInfoError } from '@nx-console/shared-types'; import { IdeProvider } from './ide-provider'; import { registerNxCloudTools } from './tools/nx-cloud'; import { registerNxCloudCipeResources, clearRegisteredCipeResources, } from './resources/nx-cloud-cipe-resources'; import { registerNxCoreTools, setNxWorkspacePath as setNxWorkspacePathForCoreTools, } from './tools/nx-core'; import { registerNxIdeTools } from './tools/nx-ide'; import { registerNxTaskTools } from './tools/nx-tasks'; import { registerNxWorkspaceTools, setNxWorkspacePath as setNxWorkspacePathForWorkspaceTools, } from './tools/nx-workspace'; import { ToolRegistry } from './tool-registry'; export interface NxWorkspaceInfoProvider { nxWorkspace: ( workspacePath: string, logger: Logger, reset?: boolean, ) => Promise<NxWorkspace | undefined>; getGenerators: ( workspacePath: string, options?: NxGeneratorsRequestOptions, logger?: Logger, ) => Promise<GeneratorCollectionInfo[] | undefined>; getGitDiffs: ( workspacePath: string, baseSha?: string, headSha?: string, ) => Promise<{ path: string; diffContent: string }[] | null>; isNxCloudEnabled: () => Promise<boolean>; getRecentCIPEData?: ( workspacePath: string, logger: Logger, ) => Promise<{ info?: CIPEInfo[]; error?: CIPEInfoError; workspaceUrl?: string; }>; } export class NxMcpServerWrapper { private logger: Logger; private _nxWorkspacePath?: string; private periodicMonitoringTimer?: NodeJS.Timeout; private periodicMonitoringCount = 0; private readonly PERIODIC_MONITORING_INTERVAL = 10000; // 10 seconds private readonly PERIODIC_MONITORING_MAX_COUNT = 5; private ideConnectionCleanup?: () => void; private cipeRefreshInterval?: NodeJS.Timeout; private readonly CIPE_REFRESH_INTERVAL = 30000; // 30 seconds private toolRegistrationState = { nxWorkspace: false, nxCore: false, nxCloud: false, nxIde: false, nxTasks: false, }; private toolRegistry: ToolRegistry; constructor( initialWorkspacePath: string | undefined, private nxWorkspaceInfoProvider: NxWorkspaceInfoProvider, private server: McpServer, private ideProvider?: IdeProvider, private telemetry?: NxConsoleTelemetryLogger, logger?: Logger, private toolsFilter?: string[], ) { this._nxWorkspacePath = initialWorkspacePath; this.ideProvider = ideProvider; this.toolRegistry = new ToolRegistry(this.server.server); this.server.server.registerCapabilities({ logging: {}, resources: { listChanged: true, subscribe: false, }, }); this.logger = logger ?? getMcpLogger(this.server); if (this.ideProvider?.isAvailable()) { this.logger.log('IDE provider available and connected'); } else { this.logger.log('Running in standalone mode (no IDE connection)'); } // Set up IDE connection change listener if provider exists if (this.ideProvider) { this.ideConnectionCleanup = this.ideProvider.onConnectionChange( async (available) => { this.logger.log(`IDE connection status changed to: ${available}`); await this.evaluateAndAddNewTools(); }, ); } } static async create( initialWorkspacePath: string | undefined, nxWorkspaceInfoProvider: NxWorkspaceInfoProvider, mcpServer: McpServer, ideProvider?: IdeProvider, telemetry?: NxConsoleTelemetryLogger, logger?: Logger, toolsFilter?: string[], ): Promise<NxMcpServerWrapper> { const server = new NxMcpServerWrapper( initialWorkspacePath, nxWorkspaceInfoProvider, mcpServer, ideProvider, telemetry, logger, toolsFilter, ); logger?.debug?.('Registering all Nx MCP tools'); server.server.server.setRequestHandler( SetLevelRequestSchema, (req, res) => { return {}; }, ); registerNxCoreTools( server.toolRegistry, server.logger, server.nxWorkspaceInfoProvider, server.telemetry, server._nxWorkspacePath, server.toolsFilter, ); server.toolRegistrationState.nxCore = true; await server.evaluateAndAddNewTools(); server.startPeriodicMonitoring(); return server; } async setNxWorkspacePath(path: string) { this.logger.log( `Setting mcp nx workspace path from ${this._nxWorkspacePath} to ${path}`, ); // If workspace path changed, trigger dynamic evaluation if (this._nxWorkspacePath !== path) { this._nxWorkspacePath = path; this.logger.log( `Nx workspace path changed, re-evaluating tools for new path: ${path}`, ); setNxWorkspacePathForCoreTools(path); setNxWorkspacePathForWorkspaceTools(path); await this.evaluateAndAddNewTools(); } } getMcpServer(): McpServer { return this.server; } /** * Check if IDE is available and connected */ isIdeAvailable(): boolean { return this.ideProvider?.isAvailable() ?? false; } /** * Cleanup resources when server shuts down */ cleanup(): void { // Stop periodic monitoring this.stopPeriodicMonitoring(); // Stop CIPE refresh interval this.stopCipeRefreshInterval(); // Clean up IDE connection listener if (this.ideConnectionCleanup) { this.ideConnectionCleanup(); } // Dispose IDE provider if it exists this.ideProvider?.dispose(); // Clear all registered CIPE resources clearRegisteredCipeResources(); } /** * Check if Nx Cloud is enabled */ private async isNxCloudEnabled(): Promise<boolean> { try { return await this.nxWorkspaceInfoProvider.isNxCloudEnabled(); } catch (error) { this.logger.log('Error checking Nx Cloud status:', error); return false; } } /** * Check if we have a valid Nx workspace */ private async isValidNxWorkspace(): Promise<boolean> { if (!this._nxWorkspacePath) { return false; } try { const workspace = await this.nxWorkspaceInfoProvider.nxWorkspace( this._nxWorkspacePath, this.logger, ); return workspace !== undefined; } catch (error) { this.logger.log('Error checking workspace validity:', error); return false; } } /** * Check if IDE connection is available */ private isIdeConnectionAvailable(): boolean { return this.ideProvider?.isAvailable() ?? false; } /** * Evaluate conditions and add new tools if conditions have changed from false to true * This method is called periodically and on certain events to dynamically add tools */ async evaluateAndAddNewTools(): Promise<void> { try { // Check cloud tools condition const cloudEnabled = await this.isNxCloudEnabled(); if ( cloudEnabled && this._nxWorkspacePath && !this.toolRegistrationState.nxCloud ) { registerNxCloudTools( this._nxWorkspacePath, this.toolRegistry, this.logger, this.telemetry, this.toolsFilter, ); // Register CIPE resources await registerNxCloudCipeResources( this._nxWorkspacePath, this.server, this.logger, this.telemetry, this.nxWorkspaceInfoProvider, ); // Start refresh interval for CIPE resources // Disabled while we figure out a better way to handle multiple NxMcpServerWrapper // this.startCipeRefreshInterval(); this.toolRegistrationState.nxCloud = true; } // Check workspace tools condition const workspaceValid = this.ideProvider ? await this.isValidNxWorkspace() : true; if ( workspaceValid && this._nxWorkspacePath && !this.toolRegistrationState.nxWorkspace ) { await registerNxWorkspaceTools( this._nxWorkspacePath, this.toolRegistry, this.logger, this.nxWorkspaceInfoProvider, this.telemetry, this.toolsFilter, ); this.toolRegistrationState.nxWorkspace = true; } // Check IDE tools condition const ideAvailable = this.isIdeConnectionAvailable(); if ( workspaceValid && ideAvailable && this.ideProvider?.isAvailable() && this._nxWorkspacePath && !this.toolRegistrationState.nxTasks ) { registerNxTaskTools( this.toolRegistry, this.ideProvider, this.logger, this.telemetry, this.toolsFilter, ); this.toolRegistrationState.nxTasks = true; } if ( ideAvailable && this.ideProvider && this._nxWorkspacePath && !this.toolRegistrationState.nxIde && this.ideProvider.isAvailable() ) { registerNxIdeTools( this.toolRegistry, this.logger, this.ideProvider, this.telemetry, this.toolsFilter, ); this.toolRegistrationState.nxIde = true; } } catch (error) { this.logger.log('Error during tool condition evaluation:', error); } } /** * Start periodic condition monitoring * Runs 5 times with 10-second intervals after startup */ startPeriodicMonitoring(): void { // Don't start if already running if (this.periodicMonitoringTimer) { return; } this.logger.debug?.( 'Starting periodic tool condition monitoring (5 times, 10-second intervals)', ); this.periodicMonitoringCount = 0; this.periodicMonitoringTimer = setInterval(async () => { this.periodicMonitoringCount++; await this.evaluateAndAddNewTools(); // Stop after max count reached if (this.periodicMonitoringCount >= this.PERIODIC_MONITORING_MAX_COUNT) { this.stopPeriodicMonitoring(); } }, this.PERIODIC_MONITORING_INTERVAL); } /** * Stop periodic condition monitoring */ private stopPeriodicMonitoring(): void { if (this.periodicMonitoringTimer) { clearInterval(this.periodicMonitoringTimer); this.periodicMonitoringTimer = undefined; this.logger.debug?.('Stopped periodic tool condition monitoring'); } } /** * Start CIPE refresh interval * Refreshes available CIPE resources every 30 seconds */ private startCipeRefreshInterval(): void { // Don't start if already running if (this.cipeRefreshInterval) { return; } // Don't start if workspace path or provider is not available if ( !this._nxWorkspacePath || !this.nxWorkspaceInfoProvider.getRecentCIPEData ) { return; } this.logger.log('Starting CIPE refresh interval (every 30 seconds)'); this.cipeRefreshInterval = setInterval(async () => { try { await registerNxCloudCipeResources( this._nxWorkspacePath!, this.server, this.logger, this.telemetry, this.nxWorkspaceInfoProvider, ); } catch (error) { this.logger.log('Error refreshing CIPE resources:', error); } }, this.CIPE_REFRESH_INTERVAL); } /** * Stop CIPE refresh interval */ private stopCipeRefreshInterval(): void { if (this.cipeRefreshInterval) { clearInterval(this.cipeRefreshInterval); this.cipeRefreshInterval = undefined; this.logger.log('Stopped CIPE refresh interval'); } } } export const nxMcpServerCapabilities: ServerCapabilities = { tools: { listChanged: true, }, resources: { listChanged: true, subscribe: false, }, logging: {}, };

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