diff --git a/apps/rush-mcp-server/src/pluginFramework/RushMcpPluginLoader.ts b/apps/rush-mcp-server/src/pluginFramework/RushMcpPluginLoader.ts index 58fc212603f..f0715d4d0fe 100644 --- a/apps/rush-mcp-server/src/pluginFramework/RushMcpPluginLoader.ts +++ b/apps/rush-mcp-server/src/pluginFramework/RushMcpPluginLoader.ts @@ -39,11 +39,6 @@ export interface IJsonRushMcpPlugin { * @rushstack/mcp-server will ensure this folder is installed before loading the plugin. */ autoinstaller: string; - - /** - * The name of the plugin. This is used to identify the plugin in the MCP server. - */ - pluginName: string; } /** @@ -85,6 +80,10 @@ export class RushMcpPluginLoader { this._mcpServer = mcpServer; } + private static _formatError(e: Error): string { + return e.stack ?? RushMcpPluginLoader._formatError(e); + } + public async loadAsync(): Promise { const rushMcpFilePath: string = path.join( this._rushWorkspacePath, @@ -151,7 +150,7 @@ export class RushMcpPluginLoader { const mcpPluginSchema: JsonSchema = await JsonSchema.fromFile(mcpPluginSchemaFilePath); const rushMcpPluginOptionsFilePath: string = path.resolve( this._rushWorkspacePath, - `common/config/rush-mcp/${jsonMcpPlugin.pluginName}.json` + `common/config/rush-mcp/${jsonManifest.pluginName}.json` ); // Example: /path/to/my-repo/common/config/rush-mcp/rush-mcp-example-plugin.json rushMcpPluginOptions = await JsonFile.loadAndValidateAsync( @@ -169,7 +168,10 @@ export class RushMcpPluginLoader { } pluginFactory = entryPointModule.default; } catch (e) { - throw new Error(`Unable to load plugin entry point at ${fullEntryPointPath}: ` + e.toString()); + throw new Error( + `Unable to load plugin entry point at ${fullEntryPointPath}:\n` + + RushMcpPluginLoader._formatError(e) + ); } const session: RushMcpPluginSessionInternal = new RushMcpPluginSessionInternal(this._mcpServer); @@ -178,14 +180,18 @@ export class RushMcpPluginLoader { try { plugin = pluginFactory(session, rushMcpPluginOptions); } catch (e) { - throw new Error(`Error invoking entry point for plugin ${jsonManifest.pluginName}:` + e.toString()); + throw new Error( + `Error invoking entry point for plugin ${jsonManifest.pluginName}:\n` + + RushMcpPluginLoader._formatError(e) + ); } try { await plugin.onInitializeAsync(); } catch (e) { throw new Error( - `Error occurred in onInitializeAsync() for plugin ${jsonManifest.pluginName}:` + e.toString() + `Error occurred in onInitializeAsync() for plugin ${jsonManifest.pluginName}:\n` + + RushMcpPluginLoader._formatError(e) ); } } diff --git a/apps/rush-mcp-server/src/schemas/rush-mcp.schema.json b/apps/rush-mcp-server/src/schemas/rush-mcp.schema.json index 14904b86cc3..0dd9a9546fe 100644 --- a/apps/rush-mcp-server/src/schemas/rush-mcp.schema.json +++ b/apps/rush-mcp-server/src/schemas/rush-mcp.schema.json @@ -16,10 +16,6 @@ "autoinstaller": { "type": "string", "description": "The name of a Rush autoinstaller with this package as its dependency." - }, - "pluginName": { - "type": "string", - "description": "The name of the plugin. This is used to identify the plugin in the MCP server." } }, "required": ["packageName", "autoinstaller"], diff --git a/apps/rush-mcp-server/src/start.ts b/apps/rush-mcp-server/src/start.ts index ac6441963b5..fa74da180ea 100644 --- a/apps/rush-mcp-server/src/start.ts +++ b/apps/rush-mcp-server/src/start.ts @@ -1,10 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import * as path from 'path'; +import * as process from 'process'; +import { FileSystem } from '@rushstack/node-core-library'; +import { RushSdkLoader } from '@rushstack/rush-sdk/loader'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { log } from './utilities/log'; -import { RushMCPServer } from './server'; +import type { RushMCPServer } from './server'; const main = async (): Promise => { const rushWorkspacePath: string | undefined = process.argv[2]; @@ -12,7 +16,22 @@ const main = async (): Promise => { throw new Error('Please provide workspace root path as the first argument'); } - const server: RushMCPServer = new RushMCPServer(rushWorkspacePath); + const rushWorkspaceFullPath: string = path.resolve(rushWorkspacePath); + + if (!(await FileSystem.existsAsync(rushWorkspaceFullPath))) { + throw new Error( + 'The specified workspace root path does not exist:\n ' + JSON.stringify(rushWorkspacePath) + ); + } + + // Load rush-sdk from the specified repository + await RushSdkLoader.loadAsync({ + rushJsonSearchFolder: rushWorkspaceFullPath + }); + + const RushMCPServerClass: typeof RushMCPServer = (await import('./server')).RushMCPServer; + + const server: RushMCPServer = new RushMCPServerClass(rushWorkspaceFullPath); await server.startAsync(); const transport: StdioServerTransport = new StdioServerTransport(); await server.connect(transport); diff --git a/build-tests/rush-mcp-example-plugin/src/rush-mcp-example-plugin.schema.json b/build-tests/rush-mcp-example-plugin/src/rush-mcp-example-plugin.schema.json index 534b3291d31..55da2c56a0e 100644 --- a/build-tests/rush-mcp-example-plugin/src/rush-mcp-example-plugin.schema.json +++ b/build-tests/rush-mcp-example-plugin/src/rush-mcp-example-plugin.schema.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", + "$schema": "http://json-schema.org/draft-07/schema#", "title": "State Capital Map", "type": "object", "required": ["capitalsByState"], diff --git a/common/changes/@rushstack/mcp-server/octogonz-mcp-fixes_2025-06-06-07-20.json b/common/changes/@rushstack/mcp-server/octogonz-mcp-fixes_2025-06-06-07-20.json new file mode 100644 index 00000000000..57999305204 --- /dev/null +++ b/common/changes/@rushstack/mcp-server/octogonz-mcp-fixes_2025-06-06-07-20.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/mcp-server", + "comment": "Fix some errors when loading plugins", + "type": "patch" + } + ], + "packageName": "@rushstack/mcp-server" +} \ No newline at end of file