8000 [mcp-server] Add more tools to mcp server (#5215) · lwsinclair/rushstack@780f3d1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 780f3d1

Browse files
authored
[mcp-server] Add more tools to mcp server (microsoft#5215)
* feat: rush mcp server * rush change * chore: add description for rush docs tool
1 parent e986cc5 commit 780f3d1

20 files changed

+1239
-11
lines changed

apps/rush-mcp-server/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@
2323
"dependencies": {
2424
"@rushstack/node-core-library": "workspace:*",
2525
"@rushstack/terminal": "workspace:*",
26-
"@rushstack/ts-command-line": "workspace:*"
26+
"@rushstack/rush-sdk": "workspace:*",
27+
"@rushstack/ts-command-line": "workspace:*",
28+
"@modelcontextprotocol/sdk": "~1.10.2",
29+
"zod": "~3.24.3"
2730
},
2831
"devDependencies": {
2932
"@rushstack/heft": "workspace:*",
3033
"local-node-rig": "workspace:*",
31-
"typescript": "~5.8.2"
34+
"typescript": "~5.8.2",
35+
"@types/node": "20.17.19"
3236
}
3337
}

apps/rush-mcp-server/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
export { log } from './utilities/log';
5+
export * from './tools';

apps/rush-mcp-server/src/server.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5+
import {
6+
type BaseTool,
7+
RushConflictResolverTool,
8+
RushMigrateProjectTool,
9+
RushCommandValidatorTool,
10+
RushWorkspaceDetailsTool,
11+
RushProjectDetailsTool,
12+
RushDocsTool
13+
} from './tools';
14+
15+
export class RushMCPServer extends McpServer {
16+
private _rushWorkspacePath: string;
17+
private _tools: BaseTool[] = [];
18+
19+
public constructor(rushWorkspacePath: string) {
20+
super({
21+
name: 'rush',
22+
version: '1.0.0'
23+
});
24+
25+
this._rushWorkspacePath = rushWorkspacePath;
26+
27+
this._initializeTools();
28+
this._registerTools();
29+
}
30+
31+
private _initializeTools(): void {
32+
this._tools.push(new RushConflictResolverTool());
33+
this._tools.push(new RushMigrateProjectTool(this._rushWorkspacePath));
34+
this._tools.push(new RushCommandValidatorTool());
35+
this._tools.push(new RushWorkspaceDetailsTool());
36+
this._tools.push(new RushProjectDetailsTool());
37+
this._tools.push(new RushDocsTool());
38+
}
39+
40+
private _registerTools(): void {
41+
process.chdir(this._rushWorkspacePath);
42+
43+
for (const tool of this._tools) {
44+
tool.register(this);
45+
}
46+
}
47+
}

apps/rush-mcp-server/src/start.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,25 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
3+
4+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5+
6+
import { log } from './utilities/log';
7+
import { RushMCPServer } from './server';
8+
9+
const main = async (): Promise<void> => {
10+
const rushWorkspacePath: string | undefined = process.argv[2];
11+
if (!rushWorkspacePath) {
12+
throw new Error('Please provide workspace root path as the first argument');
13+
}
14+
15+
const server: RushMCPServer = new RushMCPServer(rushWorkspacePath);
16+
const transport: StdioServerTransport = new StdioServerTransport();
17+
await server.connect(transport);
18+
19+
log('Rush MCP Server running on stdio');
20+
};
21+
22+
main().catch((error) => {
23+
log('Fatal error running server:', error);
24+
process.exit(1);
25+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
5+
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol';
6+
import type {
7+
CallToolResultSchema,
8+
ServerNotification,
9+
ServerRequest
10+
} from '@modelcontextprotocol/sdk/types';
11+
import type { z, ZodRawShape, ZodTypeAny } from 'zod';
12+
13+
export type CallToolResult = z.infer<typeof CallToolResultSchema>;
14+
15+
type ToolCallback<Args extends undefined | ZodRawShape = undefined> = Args extends ZodRawShape
16+
? (
17+
args: z.objectOutputType<Args, ZodTypeAny>,
18+
extra: RequestHandlerExtra<ServerRequest, ServerNotification>
19+
) => CallToolResult | Promise<CallToolResult>
20+
: (
21+
extra: RequestHandlerExtra<ServerRequest, ServerNotification>
22+
) => CallToolResult | Promise<CallToolResult>;
23+
24+
export interface IBaseToolOptions<Args extends ZodRawShape = ZodRawShape> {
25+
name: string;
26+
description: string;
27+
schema: Args;
28+
}
29+
30+
export abstract class BaseTool<Args extends ZodRawShape = ZodRawShape> {
31+
private _options: IBaseToolOptions<Args>;
32+
33+
protected constructor(options: IBaseToolOptions<Args>) {
34+
this._options = options;
35+
}
36+
37+
protected abstract executeAsync(...args: Parameters<ToolCallback<Args>>): ReturnType<ToolCallback<Args>>;
38+
39+
public register(server: McpServer): void {
40+
// TODO: remove ts-ignore
41+
// @ts-ignore
42+
server.tool(this._options.name, this._options.description, this._options.schema, async (...args) => {
43+
try {
44+
const result: CallToolResult = await this.executeAsync(...(args as Parameters<ToolCallback<Args>>));
45+
return result;
46+
} catch (error: unknown) {
47+
return {
48+
isError: true,
49+
content: [
50+
{
51+
type: 'text',
52+
text: error instanceof Error ? error.message : error
53+
}
54+
]
55+
};
56+
}
57+
});
58+
}
59+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import { z } from 'zod';
5+
import type { RushConfiguration } from '@rushstack/rush-sdk';
6+
import type { IExecutableSpawnSyncOptions } from '@rushstack/node-core-library';
7+
8+
import { CommandRunner } from '../utilities/command-runner';
9+
import { getRushConfiguration } from '../utilities/common';
10+
import { BaseTool, type CallToolResult } from './base.tool';
11+
12+
export class RushConflictResolverTool extends BaseTool {
13+
public constructor() {
14+
super({
15+
name: 'rush_pnpm_lock_file_conflict_resolver',
16+
description:
17+
'If a user requests to resolve a pnpm-lock.yaml file conflict, use this tool to automatically fix the conflict directly.',
18+
schema: {
19+
lockfilePath: z.string().describe('The path to the pnpm-lock.yaml file, should pass absolute path')
20+
}
21+
});
22+
}
23+
24+
private _tryGetSubspaceNameFromLockfilePath(
25+
lockfilePath: string,
26+
rushConfiguration: RushConfiguration
27+
): string | null {
28+
for (const subspace of rushConfiguration.subspaces) {
29+
const folderPath: string = subspace.getSubspaceConfigFolderPath();
30+
if (lockfilePath.startsWith(folderPath)) {
31+
return subspace.subspaceName;
32+
}
33+
}
34+
return null;
35+
}
36+
37+
public async executeAsync({ lockfilePath }: { lockfilePath: string }): Promise<CallToolResult> {
38+
const rushConfiguration: RushConfiguration = await getRushConfiguration();
39+
const subspaceName: string | null = this._tryGetSubspaceNameFromLockfilePath(
40+
lockfilePath,
41+
rushConfiguration
42+
);
43+
if (!subspaceName) {
44+
throw new Error('subspace name not found');
45+
}
46+
47+
const options: IExecutableSpawnSyncOptions = {
48+
stdio: 'inherit',
49+
currentWorkingDirectory: rushConfiguration.rushJsonFolder
50+
};
51+
await CommandRunner.runGitCommandAsync(['checkout', '--theirs', lockfilePath], options);
52+
await CommandRunner.runRushCommandAsync(['update', '--subspace', subspaceName], options);
53+
54+
return {
55+
content: [
56+
{
57+
type: 'text',
58+
text: 'Conflict resolved successfully'
59+
}
60+
]
61+
};
62+
}
63+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import { z } from 'zod';
5+
6+
import { BaseTool, type CallToolResult } from './base.tool';
7+
8+
interface IDocsResult {
9+
query: string;
10+
results: {
11+
score: number;
12+
text: string;
13+
}[];
14+
count: number;
15+
searchTimeMs: number;
16+
}
17+
18+
export class RushDocsTool extends BaseTool {
19+
public constructor() {
20+
super({
21+
name: 'rush_docs',
22+
description:
23+
'Search and retrieve relevant sections from Rush official documentation based on user queries.',
24+
schema: {
25+
userQuery: z.string().describe('The user query to search for relevant documentation sections.')
26+
}
27+
});
28+
}
29+
30+
public async executeAsync({ userQuery }: { userQuery: string }): Promise<CallToolResult> {
31+
// An example of a knowledge base that can run, but needs to be replaced with Microsoft’s service.
32+
const response: Response = await fetch('http://47.120.46.115/search', {
33+
method: 'POST',
34+
headers: {
35+
'Content-Type': 'application/json'
36+
},
37+
body: JSON.stringify({ query: userQuery, topK: 10 })
38+
});
39+
40+
const result: IDocsResult = (await response.json()) as IDocsResult;
41+
42+
return {
43+
content: [
44+
{
45+
type: 'text',
46+
text: result.results.map((item) => item.text).join('\n')
47+
}
48+
]
49+
};
50+
}
51+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
export * from './base.tool';
5+
export * from './migrate-project.tool';
6+
export * from './project-details.tool';
7+
export * from './rush-command-validator.tool';
8+
export * from './workspace-details';
9+
export * from './conflict-resolver.tool';
10+
export * from './docs.tool';

0 commit comments

Comments
 (0)
0