Skip to content

Commit 56564d1

Browse files
authored
feat(nx-mcp): add --tools flag and vscode option to filter mcp tools (#2908)
1 parent a572eeb commit 56564d1

File tree

16 files changed

+1194
-733
lines changed

16 files changed

+1194
-733
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import {
2+
cleanupNxWorkspace,
3+
createInvokeMCPInspectorCLI,
4+
defaultVersion,
5+
e2eCwd,
6+
newWorkspace,
7+
simpleReactWorkspaceOptions,
8+
uniq,
9+
} from '@nx-console/shared-e2e-utils';
10+
import { rmSync } from 'node:fs';
11+
import { join } from 'node:path';
12+
13+
describe('tool-filter', () => {
14+
let invokeMCPInspectorCLI: Awaited<
15+
ReturnType<typeof createInvokeMCPInspectorCLI>
16+
>;
17+
const workspaceName = uniq('nx-mcp-tool-filter');
18+
const testWorkspacePath = join(e2eCwd, workspaceName);
19+
20+
beforeAll(async () => {
21+
newWorkspace({
22+
name: workspaceName,
23+
options: simpleReactWorkspaceOptions,
24+
});
25+
invokeMCPInspectorCLI = await createInvokeMCPInspectorCLI(
26+
e2eCwd,
27+
workspaceName,
28+
);
29+
});
30+
31+
afterAll(async () => {
32+
await cleanupNxWorkspace(testWorkspacePath, defaultVersion);
33+
rmSync(testWorkspacePath, { recursive: true, force: true });
34+
});
35+
36+
it('should filter to a single tool when --tools specifies one tool', () => {
37+
const result = invokeMCPInspectorCLI(
38+
testWorkspacePath,
39+
'--tools',
40+
'nx_docs',
41+
'--method',
42+
'tools/list',
43+
);
44+
const toolNames = result.tools.map((tool: any) => tool.name);
45+
expect(toolNames).toEqual(['nx_docs']);
46+
});
47+
48+
it('should allow multiple tools with multiple --tools args', () => {
49+
const result = invokeMCPInspectorCLI(
50+
testWorkspacePath,
51+
'--tools',
52+
'nx_docs',
53+
'nx_workspace',
54+
'--method',
55+
'tools/list',
56+
);
57+
const toolNames = result.tools.map((tool: any) => tool.name);
58+
expect(toolNames).toEqual(['nx_docs', 'nx_workspace']);
59+
});
60+
61+
it('should support glob patterns for tool filtering', () => {
62+
const result = invokeMCPInspectorCLI(
63+
testWorkspacePath,
64+
'--tools',
65+
'nx_*',
66+
'--method',
67+
'tools/list',
68+
);
69+
const toolNames = result.tools.map((tool: any) => tool.name);
70+
expect(toolNames).toEqual([
71+
'nx_docs',
72+
'nx_available_plugins',
73+
'nx_workspace',
74+
'nx_workspace_path',
75+
'nx_project_details',
76+
'nx_generators',
77+
'nx_generator_schema',
78+
]);
79+
});
80+
81+
it('should exclude tools matching negation patterns', () => {
82+
const result = invokeMCPInspectorCLI(
83+
testWorkspacePath,
84+
'--tools',
85+
'nx_*',
86+
'!nx_docs',
87+
'--method',
88+
'tools/list',
89+
);
90+
const toolNames = result.tools.map((tool: any) => tool.name);
91+
expect(toolNames).not.toContain('nx_docs');
92+
expect(toolNames).toContain('nx_workspace');
93+
expect(toolNames).toContain('nx_generators');
94+
});
95+
96+
it('should combine positive and negative patterns correctly', () => {
97+
const result = invokeMCPInspectorCLI(
98+
testWorkspacePath,
99+
'--tools',
100+
'nx_*',
101+
'!nx_generators',
102+
'!nx_generator_schema',
103+
'--method',
104+
'tools/list',
105+
);
106+
const toolNames = result.tools.map((tool: any) => tool.name);
107+
expect(toolNames).toEqual([
108+
'nx_docs',
109+
'nx_available_plugins',
110+
'nx_workspace',
111+
'nx_workspace_path',
112+
'nx_project_details',
113+
]);
114+
});
115+
116+
it('should support negative glob patterns', () => {
117+
const result = invokeMCPInspectorCLI(
118+
testWorkspacePath,
119+
'--tools',
120+
'nx_*',
121+
'!nx_generator*',
122+
'--method',
123+
'tools/list',
124+
);
125+
const toolNames = result.tools.map((tool: any) => tool.name);
126+
expect(toolNames).toEqual([
127+
'nx_docs',
128+
'nx_available_plugins',
129+
'nx_workspace',
130+
'nx_workspace_path',
131+
'nx_project_details',
132+
]);
133+
});
134+
});

apps/nx-mcp/src/main.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ async function main() {
9696

9797
logger.log('Starting Nx MCP server');
9898

99+
// Normalize tools filter to always be an array or undefined
100+
const toolsFilter: string[] | undefined = argv.tools
101+
? Array.isArray(argv.tools)
102+
? argv.tools
103+
: [argv.tools]
104+
: undefined;
105+
106+
if (toolsFilter && toolsFilter.length > 0) {
107+
logger.log(`Tools filter: ${toolsFilter.join(', ')}`);
108+
}
109+
99110
const providedPath: string = resolve(
100111
argv.workspacePath || (argv._[0] as string) || process.cwd(),
101112
) as string;
@@ -226,6 +237,7 @@ async function main() {
226237
ideProvider,
227238
telemetryLogger,
228239
logger,
240+
toolsFilter,
229241
);
230242

231243
// disposables for shutting down
@@ -355,6 +367,7 @@ async function main() {
355367
ideProvider,
356368
telemetryLogger,
357369
logger,
370+
toolsFilter,
358371
);
359372

360373
await connectionServer.getMcpServer().connect(transport);

apps/nx-mcp/src/yargs-config.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,41 @@ describe('createYargsConfig', () => {
157157
}).toThrow();
158158
});
159159
});
160+
161+
describe('tools option', () => {
162+
it('should parse single --tools flag', () => {
163+
const argv = createYargsConfig(['--tools', 'nx_docs']).parseSync();
164+
expect(argv.tools).toEqual(['nx_docs']);
165+
});
166+
167+
it('should parse multiple --tools flags', () => {
168+
const argv = createYargsConfig([
169+
'--tools',
170+
'nx_docs',
171+
'--tools',
172+
'nx_workspace',
173+
]).parseSync();
174+
expect(argv.tools).toEqual(['nx_docs', 'nx_workspace']);
175+
});
176+
177+
it('should parse -t alias', () => {
178+
const argv = createYargsConfig(['-t', 'nx_docs']).parseSync();
179+
expect(argv.tools).toEqual(['nx_docs']);
180+
});
181+
182+
it('should handle negation patterns', () => {
183+
const argv = createYargsConfig(['--tools', '!nx_docs']).parseSync();
184+
expect(argv.tools).toEqual(['!nx_docs']);
185+
});
186+
187+
it('should handle glob patterns', () => {
188+
const argv = createYargsConfig(['--tools', 'nx_*']).parseSync();
189+
expect(argv.tools).toEqual(['nx_*']);
190+
});
191+
192+
it('should default to undefined when not provided', () => {
193+
const argv = createYargsConfig([]).parseSync();
194+
expect(argv.tools).toBeUndefined();
195+
});
196+
});
160197
});

apps/nx-mcp/src/yargs-config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface ArgvType {
99
disableTelemetry: boolean;
1010
keepAliveInterval: number;
1111
debugLogs: boolean;
12+
tools?: string | string[];
1213
_: (string | number)[];
1314
$0: string;
1415
[x: string]: unknown;
@@ -65,6 +66,13 @@ export function createYargsConfig(args: string[]): Argv<any> {
6566
type: 'boolean',
6667
default: false,
6768
})
69+
.option('tools', {
70+
alias: 't',
71+
describe:
72+
'Filter which tools are enabled. Accepts glob patterns including negation (e.g., "*", "!nx_docs", "cloud_*")',
73+
type: 'array',
74+
string: true,
75+
})
6876
.check((argv) => {
6977
// Check for conflicting options
7078
if (argv.sse && argv.transport === 'http') {

apps/vscode/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,14 @@
833833
"scope": "resource",
834834
"description": "Fixed port for the Nx MCP server. If set to 0, a random available port will be used."
835835
},
836+
"nxConsole.mcpToolsFilter": {
837+
"type": "array",
838+
"items": {
839+
"type": "string"
840+
},
841+
"scope": "resource",
842+
"description": "Filter which MCP tools are enabled. Accepts glob patterns including negation (e.g., '*', '!nx_docs', 'cloud_*'). If empty, all tools are enabled."
843+
},
836844
"nxConsole.enableDebugLogging": {
837845
"type": "boolean",
838846
"default": false,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './lib/nx-mcp-server-wrapper';
22
export * from './lib/ide-provider';
3+
export * from './lib/tool-filter';

libs/nx-mcp/nx-mcp-server/src/lib/nx-mcp-server-wrapper.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ export interface NxWorkspaceInfoProvider {
5757
export class NxMcpServerWrapper {
5858
private logger: Logger;
5959
private _nxWorkspacePath?: string;
60-
private ideProvider?: IdeProvider;
6160
private periodicMonitoringTimer?: NodeJS.Timeout;
6261
private periodicMonitoringCount = 0;
6362
private readonly PERIODIC_MONITORING_INTERVAL = 10000; // 10 seconds
@@ -77,9 +76,10 @@ export class NxMcpServerWrapper {
7776
initialWorkspacePath: string | undefined,
7877
private nxWorkspaceInfoProvider: NxWorkspaceInfoProvider,
7978
private server: McpServer,
80-
ideProvider?: IdeProvider,
79+
private ideProvider?: IdeProvider,
8180
private telemetry?: NxConsoleTelemetryLogger,
8281
logger?: Logger,
82+
private toolsFilter?: string[],
8383
) {
8484
this._nxWorkspacePath = initialWorkspacePath;
8585
this.ideProvider = ideProvider;
@@ -121,6 +121,7 @@ export class NxMcpServerWrapper {
121121
ideProvider?: IdeProvider,
122122
telemetry?: NxConsoleTelemetryLogger,
123123
logger?: Logger,
124+
toolsFilter?: string[],
124125
): Promise<NxMcpServerWrapper> {
125126
const server = new NxMcpServerWrapper(
126127
initialWorkspacePath,
@@ -129,6 +130,7 @@ export class NxMcpServerWrapper {
129130
ideProvider,
130131
telemetry,
131132
logger,
133+
toolsFilter,
132134
);
133135
logger?.debug?.('Registering all Nx MCP tools');
134136

@@ -145,6 +147,7 @@ export class NxMcpServerWrapper {
145147
server.nxWorkspaceInfoProvider,
146148
server.telemetry,
147149
server._nxWorkspacePath,
150+
server.toolsFilter,
148151
);
149152
server.toolRegistrationState.nxCore = true;
150153

@@ -262,6 +265,7 @@ export class NxMcpServerWrapper {
262265
this.server,
263266
this.logger,
264267
this.telemetry,
268+
this.toolsFilter,
265269
);
266270

267271
// Register CIPE resources
@@ -294,6 +298,7 @@ export class NxMcpServerWrapper {
294298
this.logger,
295299
this.nxWorkspaceInfoProvider,
296300
this.telemetry,
301+
this.toolsFilter,
297302
);
298303
this.toolRegistrationState.nxWorkspace = true;
299304
}
@@ -313,6 +318,7 @@ export class NxMcpServerWrapper {
313318
this.ideProvider,
314319
this.logger,
315320
this.telemetry,
321+
this.toolsFilter,
316322
);
317323
this.toolRegistrationState.nxTasks = true;
318324
}
@@ -329,6 +335,7 @@ export class NxMcpServerWrapper {
329335
this.logger,
330336
this.ideProvider,
331337
this.telemetry,
338+
this.toolsFilter,
332339
);
333340
this.toolRegistrationState.nxIde = true;
334341
}

0 commit comments

Comments
 (0)