Skip to content

Commit 206a65f

Browse files
committed
feat(nx-mcp): make nx_project_details more token efficient by default
1 parent 801d573 commit 206a65f

File tree

6 files changed

+394
-87
lines changed

6 files changed

+394
-87
lines changed

apps/nx-mcp-e2e/src/nx-project-details-compressed-targets.test.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,10 @@ describe('nx_project_details compressed targets', () => {
174174

175175
const targetsBlock = getCompressedTargetsBlock(result);
176176
expect(targetsBlock).toBeDefined();
177-
expect(targetsBlock).toContain(
178-
'target-nx-executor: @nx/webpack:webpack | cache: true',
177+
expect(targetsBlock).toContain('target-nx-executor: @nx/webpack:webpack');
178+
// Cache is true by default, so it should NOT be shown (token efficiency)
179+
expect(targetsBlock).not.toContain(
180+
'target-nx-executor: @nx/webpack:webpack | cache:',
179181
);
180182
});
181183

@@ -190,7 +192,7 @@ describe('nx_project_details compressed targets', () => {
190192
const targetsBlock = getCompressedTargetsBlock(result);
191193
expect(targetsBlock).toBeDefined();
192194
expect(targetsBlock).toContain(
193-
'target-custom-executor: @my-org/custom:build | cache: true',
195+
'target-custom-executor: @my-org/custom:build',
194196
);
195197
});
196198

@@ -205,7 +207,7 @@ describe('nx_project_details compressed targets', () => {
205207
const targetsBlock = getCompressedTargetsBlock(result);
206208
expect(targetsBlock).toBeDefined();
207209
expect(targetsBlock).toContain(
208-
"target-run-cmd-options: nx:run-commands - 'echo test' | cache: true",
210+
"target-run-cmd-options: nx:run-commands - 'echo test'",
209211
);
210212
});
211213

@@ -220,7 +222,7 @@ describe('nx_project_details compressed targets', () => {
220222
const targetsBlock = getCompressedTargetsBlock(result);
221223
expect(targetsBlock).toBeDefined();
222224
expect(targetsBlock).toContain(
223-
"target-run-cmd-array-single-str: nx:run-commands - 'npm build' | cache: true",
225+
"target-run-cmd-array-single-str: nx:run-commands - 'npm build'",
224226
);
225227
});
226228

@@ -235,7 +237,7 @@ describe('nx_project_details compressed targets', () => {
235237
const targetsBlock = getCompressedTargetsBlock(result);
236238
expect(targetsBlock).toBeDefined();
237239
expect(targetsBlock).toContain(
238-
"target-run-cmd-array-single-obj: nx:run-commands - 'npm test' | cache: true",
240+
"target-run-cmd-array-single-obj: nx:run-commands - 'npm test'",
239241
);
240242
});
241243

@@ -250,7 +252,7 @@ describe('nx_project_details compressed targets', () => {
250252
const targetsBlock = getCompressedTargetsBlock(result);
251253
expect(targetsBlock).toBeDefined();
252254
expect(targetsBlock).toContain(
253-
'target-run-cmd-array-multi: nx:run-commands - 3 commands | cache: true',
255+
'target-run-cmd-array-multi: nx:run-commands - 3 commands',
254256
);
255257
});
256258

@@ -264,9 +266,7 @@ describe('nx_project_details compressed targets', () => {
264266

265267
const targetsBlock = getCompressedTargetsBlock(result);
266268
expect(targetsBlock).toBeDefined();
267-
expect(targetsBlock).toContain(
268-
"deploy: nx:run-script - 'npm run deploy' | cache: true",
269-
);
269+
expect(targetsBlock).toContain("deploy: nx:run-script - 'npm run deploy'");
270270
});
271271

272272
it('should display nx:run-script target from package.json script - custom-hello', () => {
@@ -280,7 +280,7 @@ describe('nx_project_details compressed targets', () => {
280280
const targetsBlock = getCompressedTargetsBlock(result);
281281
expect(targetsBlock).toBeDefined();
282282
expect(targetsBlock).toContain(
283-
"custom-hello: nx:run-script - 'npm run custom-hello' | cache: true",
283+
"custom-hello: nx:run-script - 'npm run custom-hello'",
284284
);
285285
});
286286

@@ -295,7 +295,7 @@ describe('nx_project_details compressed targets', () => {
295295
const targetsBlock = getCompressedTargetsBlock(result);
296296
expect(targetsBlock).toBeDefined();
297297
expect(targetsBlock).toContain(
298-
'target-with-deps-string: @nx/js:tsc | depends: [build, test] | cache: true',
298+
'target-with-deps-string: @nx/js:tsc | depends: [build, test]',
299299
);
300300
});
301301

@@ -310,7 +310,7 @@ describe('nx_project_details compressed targets', () => {
310310
const targetsBlock = getCompressedTargetsBlock(result);
311311
expect(targetsBlock).toBeDefined();
312312
expect(targetsBlock).toContain(
313-
'target-with-deps-object: @nx/js:tsc | depends: [build, ^build] | cache: true',
313+
'target-with-deps-object: @nx/js:tsc | depends: [build, ^build]',
314314
);
315315
});
316316

@@ -339,8 +339,10 @@ describe('nx_project_details compressed targets', () => {
339339

340340
const targetsBlock = getCompressedTargetsBlock(result);
341341
expect(targetsBlock).toBeDefined();
342-
expect(targetsBlock).toContain(
343-
'target-with-cache: @nx/js:tsc | cache: true',
342+
// When cache is true (default), it should NOT be displayed (token efficiency)
343+
expect(targetsBlock).toContain('target-with-cache: @nx/js:tsc');
344+
expect(targetsBlock).not.toContain(
345+
'target-with-cache: @nx/js:tsc | cache:',
344346
);
345347
});
346348

@@ -405,7 +407,7 @@ describe('nx_project_details compressed targets', () => {
405407
const targetsBlock = getCompressedTargetsBlock(result);
406408
expect(targetsBlock).toBeDefined();
407409
expect(targetsBlock).toContain(
408-
'target-empty-cmds: nx:run-commands - 0 commands | cache: true',
410+
'target-empty-cmds: nx:run-commands - 0 commands',
409411
);
410412
});
411413

apps/nx-mcp-e2e/src/nx-project-details-select.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ describe('nx_project_details select', () => {
5555
expect(result.content[1]?.text).toContain(
5656
'To see full configuration for a specific target',
5757
);
58-
expect(result.content[1]?.text).toContain('cache:');
58+
// Cache should only appear when it's false (token efficiency)
59+
// So we should see "cache: false" somewhere, but not "cache: true"
60+
expect(result.content[1]?.text).not.toContain('cache: true');
5961

6062
// Third block should be External Dependencies
6163
expect(result.content[2]?.text).toContain('External Dependencies:');

libs/nx-mcp/nx-mcp-server/src/lib/tools/nx-workspace.test.ts

Lines changed: 157 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
getTokenOptimizedToolResult,
33
chunkContent,
44
registerNxWorkspaceTools,
5+
__testing__,
56
} from './nx-workspace';
67
import { NxWorkspace, NxError } from '@nx-console/shared-types';
78
import {
@@ -12,16 +13,15 @@ import {
1213
NX_PROJECT_DETAILS,
1314
} from '@nx-console/shared-llm-context';
1415

15-
jest.mock('@nx-console/shared-llm-context', () => ({
16-
getNxJsonPrompt: jest.fn(),
17-
getProjectGraphPrompt: jest.fn(),
18-
getProjectGraphErrorsPrompt: jest.fn(),
19-
NX_WORKSPACE: 'nx_workspace',
20-
NX_PROJECT_DETAILS: 'nx_project_details',
21-
NX_GENERATORS: 'nx_generators',
22-
NX_GENERATOR_SCHEMA: 'nx_generator_schema',
23-
NX_WORKSPACE_PATH: 'nx_workspace_path',
24-
}));
16+
jest.mock('@nx-console/shared-llm-context', () => {
17+
const actual = jest.requireActual('@nx-console/shared-llm-context');
18+
return {
19+
...actual,
20+
getNxJsonPrompt: jest.fn(),
21+
getProjectGraphPrompt: jest.fn(),
22+
getProjectGraphErrorsPrompt: jest.fn(),
23+
};
24+
});
2525

2626
// Mock shared-npm module - don't import to avoid lazy-load conflict
2727
jest.mock('@nx-console/shared-npm', () => ({
@@ -512,3 +512,150 @@ describe('registerNxWorkspaceTools', () => {
512512
});
513513
});
514514
});
515+
516+
describe('compressTargetForDisplay', () => {
517+
const { compressTargetForDisplay } = __testing__;
518+
519+
it('should omit cache status when true', () => {
520+
const config = {
521+
executor: 'nx:run-commands',
522+
command: 'echo test',
523+
cache: true,
524+
};
525+
526+
const result = compressTargetForDisplay('build', config);
527+
528+
expect(result).toBe("build: nx:run-commands - 'echo test'");
529+
expect(result).not.toContain('cache');
530+
});
531+
532+
it('should show cache status when false', () => {
533+
const config = {
534+
executor: 'nx:run-commands',
535+
command: 'echo test',
536+
cache: false,
537+
};
538+
539+
const result = compressTargetForDisplay('build', config);
540+
541+
expect(result).toBe("build: nx:run-commands - 'echo test' | cache: false");
542+
});
543+
544+
it('should truncate long dependency lists', () => {
545+
const config = {
546+
executor: '@nx/gradle:gradle',
547+
dependsOn: [
548+
'dep1',
549+
'dep2',
550+
'dep3',
551+
'dep4',
552+
'dep5',
553+
'dep6',
554+
'dep7',
555+
'dep8',
556+
'dep9',
557+
'dep10',
558+
'dep11',
559+
],
560+
cache: true,
561+
};
562+
563+
const result = compressTargetForDisplay('build', config);
564+
565+
expect(result).toBe(
566+
'build: @nx/gradle:gradle | depends: [dep1, dep2, dep3, +8 more]',
567+
);
568+
});
569+
570+
it('should not truncate short dependency lists', () => {
571+
const config = {
572+
executor: '@nx/gradle:gradle',
573+
dependsOn: ['dep1', 'dep2', 'dep3'],
574+
cache: true,
575+
};
576+
577+
const result = compressTargetForDisplay('build', config);
578+
579+
expect(result).toBe(
580+
'build: @nx/gradle:gradle | depends: [dep1, dep2, dep3]',
581+
);
582+
});
583+
584+
it('should show atomized targets with abbreviated names', () => {
585+
const config = {
586+
executor: 'nx:noop',
587+
dependsOn: ['test-ci--Test1', 'test-ci--Test2', 'test-ci--Test3'],
588+
cache: true,
589+
};
590+
const atomizedTargets = [
591+
'test-ci--Test1',
592+
'test-ci--Test2',
593+
'test-ci--Test3',
594+
];
595+
596+
const result = compressTargetForDisplay('test-ci', config, atomizedTargets);
597+
598+
expect(result).toBe(
599+
'test-ci: nx:noop | depends: [3 atomized targets] | atomized: [Test1, Test2, Test3]',
600+
);
601+
});
602+
603+
it('should truncate long atomized target lists', () => {
604+
const config = {
605+
executor: 'nx:noop',
606+
cache: true,
607+
};
608+
const atomizedTargets = [
609+
'test-ci--Test1',
610+
'test-ci--Test2',
611+
'test-ci--Test3',
612+
'test-ci--Test4',
613+
'test-ci--Test5',
614+
'test-ci--Test6',
615+
];
616+
617+
const result = compressTargetForDisplay('test-ci', config, atomizedTargets);
618+
619+
expect(result).toBe(
620+
'test-ci: nx:noop | atomized: [Test1, Test2, Test3, +3 more]',
621+
);
622+
});
623+
624+
it('should handle target without executor', () => {
625+
const config = {
626+
cache: true,
627+
};
628+
629+
const result = compressTargetForDisplay('build', config);
630+
631+
expect(result).toBe('build: no executor');
632+
});
633+
634+
it('should handle nx:run-commands with multiple commands', () => {
635+
const config = {
636+
executor: 'nx:run-commands',
637+
options: {
638+
commands: ['echo test1', 'echo test2', 'echo test3'],
639+
},
640+
cache: true,
641+
};
642+
643+
const result = compressTargetForDisplay('build', config);
644+
645+
expect(result).toBe('build: nx:run-commands - 3 commands');
646+
});
647+
648+
it('should handle nx:run-script executor', () => {
649+
const config = {
650+
executor: 'nx:run-script',
651+
metadata: {
652+
runCommand: 'npm run test',
653+
},
654+
cache: true,
655+
};
656+
657+
const result = compressTargetForDisplay('test', config);
658+
659+
expect(result).toBe("test: nx:run-script - 'npm run test'");
660+
});
661+
});

0 commit comments

Comments
 (0)