From ad87240fd1677b265778e3f3f57723048cb2107c Mon Sep 17 00:00:00 2001 From: Lincoln <778157949@qq.com> Date: Fri, 2 May 2025 03:48:26 +0000 Subject: [PATCH 1/4] Follow-ups to #5215 --- apps/rush-mcp-server/src/tools/docs.tool.ts | 31 ++++++++++++------- .../src/tools/migrate-project.tool.ts | 4 +-- .../src/tools/rush-command-validator.tool.ts | 2 +- .../mcp-server/main_2025-05-02-03-43.json | 10 ++++++ .../rush/nonbrowser-approved-packages.json | 4 +++ 5 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 common/changes/@rushstack/mcp-server/main_2025-05-02-03-43.json diff --git a/apps/rush-mcp-server/src/tools/docs.tool.ts b/apps/rush-mcp-server/src/tools/docs.tool.ts index ed616dd623f..47c2b0ae4a5 100644 --- a/apps/rush-mcp-server/src/tools/docs.tool.ts +++ b/apps/rush-mcp-server/src/tools/docs.tool.ts @@ -2,6 +2,8 @@ // See LICENSE in the project root for license information. import { z } from 'zod'; +import { JsonFile } from '@rushstack/node-core-library'; +import path from 'path'; import { BaseTool, type CallToolResult } from './base.tool'; @@ -20,30 +22,35 @@ export class RushDocsTool extends BaseTool { super({ name: 'rush_docs', description: - 'Search and retrieve relevant sections from Rush official documentation based on user queries.', + 'Search and retrieve relevant sections from the official Rush documentation based on user queries.', schema: { userQuery: z.string().describe('The user query to search for relevant documentation sections.') } }); } - public async executeAsync({ userQuery }: { userQuery: string }): Promise { - // An example of a knowledge base that can run, but needs to be replaced with Microsoft’s service. - const response: Response = await fetch('http://47.120.46.115/search', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ query: userQuery, topK: 10 }) - }); + // TODO: replace with Microsoft's service + private searchDocs(query: string): IDocsResult { + const startTime = Date.now(); + + const results = JsonFile.load(path.join(__dirname, '../rush-doc-fragment.mock.json')); + + return { + query, + results, + count: results.length, + searchTimeMs: Date.now() - startTime + }; + } - const result: IDocsResult = (await response.json()) as IDocsResult; + public async executeAsync({ userQuery }: { userQuery: string }): Promise { + const docSearchResult: IDocsResult = this.searchDocs(userQuery); return { content: [ { type: 'text', - text: result.results.map((item) => item.text).join('\n') + text: docSearchResult.results.map((item) => item.text).join('\n\n') } ] }; diff --git a/apps/rush-mcp-server/src/tools/migrate-project.tool.ts b/apps/rush-mcp-server/src/tools/migrate-project.tool.ts index 1bdc38d741e..88481c3125d 100644 --- a/apps/rush-mcp-server/src/tools/migrate-project.tool.ts +++ b/apps/rush-mcp-server/src/tools/migrate-project.tool.ts @@ -125,8 +125,8 @@ export class RushMigrateProjectTool extends BaseTool { type: 'text', text: `Project "${projectName}" migrated to subspace "${targetSubspaceName}" successfully. ` + - `You can ask whether user wants to run "rush update --subspace ${targetSubspaceName}" to update the project. ` + - `If user says "yes" you can run "rush update --subspace ${targetSubspaceName}" directly for them.` + `You can ask whether the user wants to run "rush update --subspace ${targetSubspaceName}" to update the project. ` + + `If the user says "yes", you can run "rush update --subspace ${targetSubspaceName}" directly for them.` } ] }; diff --git a/apps/rush-mcp-server/src/tools/rush-command-validator.tool.ts b/apps/rush-mcp-server/src/tools/rush-command-validator.tool.ts index 10d2ebee9e4..cd02cbc2cb2 100644 --- a/apps/rush-mcp-server/src/tools/rush-command-validator.tool.ts +++ b/apps/rush-mcp-server/src/tools/rush-command-validator.tool.ts @@ -88,7 +88,7 @@ export class RushCommandValidatorTool extends BaseTool { content: [ { type: 'text', - text: `The package "${packageName}" does not exist in the Rush workspace. You can retrive package name from 'package.json' file in the project folder.` + text: `The package "${packageName}" does not exist in the Rush workspace. You can retrieve the package name from the 'package.json' file in the project folder.` } ] }; diff --git a/common/changes/@rushstack/mcp-server/main_2025-05-02-03-43.json b/common/changes/@rushstack/mcp-server/main_2025-05-02-03-43.json new file mode 100644 index 00000000000..1f47237e420 --- /dev/null +++ b/common/changes/@rushstack/mcp-server/main_2025-05-02-03-43.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/mcp-server", + "comment": "Follow-ups to #5215", + "type": "patch" + } + ], + "packageName": "@rushstack/mcp-server" +} \ No newline at end of file diff --git a/common/config/rush/nonbrowser-approved-packages.json b/common/config/rush/nonbrowser-approved-packages.json index bdcbd6cf572..898cc741fcb 100644 --- a/common/config/rush/nonbrowser-approved-packages.json +++ b/common/config/rush/nonbrowser-approved-packages.json @@ -969,6 +969,10 @@ { "name": "xmldoc", "allowedCategories": [ "libraries" ] + }, + { + "name": "zod", + "allowedCategories": [ "libraries" ] } ] } From 880c65de9e72e13cb422c974688be213634f5e54 Mon Sep 17 00:00:00 2001 From: Lincoln <778157949@qq.com> Date: Fri, 2 May 2025 03:59:53 +0000 Subject: [PATCH 2/4] Follow-ups to #5215 --- .../src/utilities/command-runner.ts | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/apps/rush-mcp-server/src/utilities/command-runner.ts b/apps/rush-mcp-server/src/utilities/command-runner.ts index f67e00b190c..0bcc60b8054 100644 --- a/apps/rush-mcp-server/src/utilities/command-runner.ts +++ b/apps/rush-mcp-server/src/utilities/command-runner.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import type * as child_process from 'child_process'; +import type { ChildProcess, SpawnSyncReturns } from 'child_process'; import { Executable, type IExecutableSpawnSyncOptions } from '@rushstack/node-core-library'; interface ICommandResult { @@ -49,21 +49,38 @@ export class CommandRunner { ): Promise { const commandPath: string = this._resolveCommand(command); - const result: child_process.SpawnSyncReturns = Executable.spawnSync(commandPath, args, options); - - const status: number = result.status ?? 1; - - if (status !== 0) { - throw new CommandExecutionError(command, args, result.stderr, status); - } - - return { - status, - stdout: result.stdout, - stderr: result.stderr, - command, - args - }; + return new Promise((resolve, reject) => { + const childProcess: ChildProcess = Executable.spawn(commandPath, args, options); + let stdout = ''; + let stderr = ''; + + childProcess.stdout?.on('data', (data) => { + stdout += data.toString(); + }); + + childProcess.stderr?.on('data', (data) => { + stderr += data.toString(); + }); + + childProcess.on('close', (status) => { + if (status !== 0) { + reject(new CommandExecutionError(command, args, stderr, status ?? 1)); + return; + } + + resolve({ + status: status ?? 0, + stdout, + stderr, + command, + args + }); + }); + + childProcess.on('error', (error) => { + reject(error); + }); + }); } public static async runRushCommandAsync( From 10e2a25f64cd50f8276df9c6cf0d3c5b5a366bca Mon Sep 17 00:00:00 2001 From: Lincoln <778157949@qq.com> Date: Fri, 2 May 2025 04:03:54 +0000 Subject: [PATCH 3/4] Follow-ups to #5215 --- apps/rush-mcp-server/src/tools/docs.tool.ts | 10 ++++++---- apps/rush-mcp-server/src/utilities/command-runner.ts | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/rush-mcp-server/src/tools/docs.tool.ts b/apps/rush-mcp-server/src/tools/docs.tool.ts index 47c2b0ae4a5..7c157877b3f 100644 --- a/apps/rush-mcp-server/src/tools/docs.tool.ts +++ b/apps/rush-mcp-server/src/tools/docs.tool.ts @@ -30,10 +30,12 @@ export class RushDocsTool extends BaseTool { } // TODO: replace with Microsoft's service - private searchDocs(query: string): IDocsResult { - const startTime = Date.now(); + private _searchDocs(query: string): IDocsResult { + const startTime: number = Date.now(); - const results = JsonFile.load(path.join(__dirname, '../rush-doc-fragment.mock.json')); + const results: IDocsResult['results'] = JsonFile.load( + path.join(__dirname, '../rush-doc-fragment.mock.json') + ); return { query, @@ -44,7 +46,7 @@ export class RushDocsTool extends BaseTool { } public async executeAsync({ userQuery }: { userQuery: string }): Promise { - const docSearchResult: IDocsResult = this.searchDocs(userQuery); + const docSearchResult: IDocsResult = this._searchDocs(userQuery); return { content: [ diff --git a/apps/rush-mcp-server/src/utilities/command-runner.ts b/apps/rush-mcp-server/src/utilities/command-runner.ts index 0bcc60b8054..db51f551c7c 100644 --- a/apps/rush-mcp-server/src/utilities/command-runner.ts +++ b/apps/rush-mcp-server/src/utilities/command-runner.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import type { ChildProcess, SpawnSyncReturns } from 'child_process'; +import type { ChildProcess } from 'child_process'; import { Executable, type IExecutableSpawnSyncOptions } from '@rushstack/node-core-library'; interface ICommandResult { @@ -51,8 +51,8 @@ export class CommandRunner { return new Promise((resolve, reject) => { const childProcess: ChildProcess = Executable.spawn(commandPath, args, options); - let stdout = ''; - let stderr = ''; + let stdout: string = ''; + let stderr: string = ''; childProcess.stdout?.on('data', (data) => { stdout += data.toString(); From d374a8562a8c021bf78d026b1285ba7ae7a5fe5a Mon Sep 17 00:00:00 2001 From: Lincoln <778157949@qq.com> Date: Fri, 2 May 2025 04:12:17 +0000 Subject: [PATCH 4/4] Follow-ups to #5215 --- .../rush-doc-fragment.mock.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 apps/rush-mcp-server/rush-doc-fragment.mock.json diff --git a/apps/rush-mcp-server/rush-doc-fragment.mock.json b/apps/rush-mcp-server/rush-doc-fragment.mock.json new file mode 100644 index 00000000000..fc62927d218 --- /dev/null +++ b/apps/rush-mcp-server/rush-doc-fragment.mock.json @@ -0,0 +1,18 @@ +[ + { + "text": "---\ntitle: rush install-autoinstaller\n---\n\n```\nusage: rush install-autoinstaller [-h] --name AUTOINSTALLER_NAME\n\nUse this command to install dependencies for an autoinstaller folder.\n\nOptional arguments:\n -h, --help Show this help message and exit.\n --name AUTOINSTALLER_NAME\n The name of the autoinstaller, which must be one of\n the folders under common/autoinstallers.\n```\n\n## See also\n\n- [rush update-autoinstaller](../commands/rush_update-autoinstaller.md)\n- [rush init-autoinstaller](../commands/rush_init-autoinstaller.md)", + "score": 0.7231232 + }, + { + "text": "---\ntitle: rush install-autoinstaller\n---\n\n```\n用法:rush install-autoinstaller [-h] --name AUTOINSTALLER_NAME\n\n使用该指令给一个项目安装依赖。\n\n可选参数:\n -h, --help 展示帮助信息并退出\n --name AUTOINSTALLER_NAME\n 指定自动安装的包名,它必须是 common/autoinstallers\n 下的一个文件夹。\n```\n\n## See also\n\n- [rush update-autoinstaller](../commands/rush_update-autoinstaller.md)\n- [rush init-autoinstaller](../commands/rush_init-autoinstaller.md)", + "score": 0.7132133 + }, + { + "text": "---\ntitle: rush update-autoinstaller\n---\n\n```\nusage: rush update-autoinstaller [-h] --name AUTOINSTALLER_NAME\n\nUse this command to regenerate the shrinkwrap file for an autoinstaller\nfolder.\n\nOptional arguments:\n -h, --help Show this help message and exit.\n --name AUTOINSTALLER_NAME\n The name of the autoinstaller, which must be one of\n the folders under common/autoinstallers.\n```\n\n## See also\n\n- [rush install-autoinstaller](../commands/rush_install-autoinstaller.md)\n- [rush init-autoinstaller](../commands/rush_init-autoinstaller.md)", + "score": 0.6632131 + }, + { + "text": "---\ntitle: rush update-autoinstaller\n---\n\n```\nusage: rush update-autoinstaller [-h] --name AUTOINSTALLER_NAME\n\nUse this command to regenerate the shrinkwrap file for an autoinstaller\nfolder.\n\nOptional arguments:\n -h, --help Show this help message and exit.\n --name AUTOINSTALLER_NAME\n The name of the autoinstaller, which must be one of\n the folders under common/autoinstallers.\n```\n\n## See also\n\n- [rush install-autoinstaller](../commands/rush_install-autoinstaller.md)\n- [rush init-autoinstaller](../commands/rush_init-autoinstaller.md)", + "score": 0.6528328 + } +]