Skip to content

Commit 5066511

Browse files
authored
fix(core): make sure that mcp args aren't overridden when running configure-ai-agents (#34381)
## Current Behavior right now if users modify their mcp params like `--minimal`, we will override them on `configure-ai-agents` ## Expected Behavior We want to bring users up to latest without overriding their valid configurations
1 parent 0b6961b commit 5066511

File tree

2 files changed

+261
-0
lines changed

2 files changed

+261
-0
lines changed

packages/nx/src/ai/set-up-ai-agents/set-up-ai-agents.spec.ts

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,218 @@ describe('setup-ai-agents generator', () => {
609609
});
610610
});
611611

612+
describe('MCP config extra args preservation', () => {
613+
it('should preserve extra args in gemini MCP config', async () => {
614+
const options: SetupAiAgentsGeneratorSchema = {
615+
directory: '.',
616+
agents: ['gemini'],
617+
};
618+
619+
tree.write(
620+
'.gemini/settings.json',
621+
JSON.stringify({
622+
mcpServers: {
623+
'nx-mcp': {
624+
type: 'stdio',
625+
command: 'npx',
626+
args: ['nx', 'mcp', '--transport', 'http'],
627+
},
628+
},
629+
})
630+
);
631+
632+
await setupAiAgentsGenerator(tree, options);
633+
634+
const config = JSON.parse(
635+
tree.read('.gemini/settings.json')?.toString() ?? '{}'
636+
);
637+
expect(config.mcpServers['nx-mcp'].args).toEqual([
638+
'nx',
639+
'mcp',
640+
'--transport',
641+
'http',
642+
]);
643+
});
644+
645+
it('should preserve multiple extra args in gemini MCP config', async () => {
646+
const options: SetupAiAgentsGeneratorSchema = {
647+
directory: '.',
648+
agents: ['gemini'],
649+
};
650+
651+
tree.write(
652+
'.gemini/settings.json',
653+
JSON.stringify({
654+
mcpServers: {
655+
'nx-mcp': {
656+
type: 'stdio',
657+
command: 'npx',
658+
args: [
659+
'nx',
660+
'mcp',
661+
'--experimental-polygraph',
662+
'--transport',
663+
'http',
664+
],
665+
},
666+
},
667+
})
668+
);
669+
670+
await setupAiAgentsGenerator(tree, options);
671+
672+
const config = JSON.parse(
673+
tree.read('.gemini/settings.json')?.toString() ?? '{}'
674+
);
675+
expect(config.mcpServers['nx-mcp'].args).toEqual([
676+
'nx',
677+
'mcp',
678+
'--experimental-polygraph',
679+
'--transport',
680+
'http',
681+
]);
682+
});
683+
684+
it('should preserve extra args when upgrading from Nx 21 to 22 (gemini)', async () => {
685+
readModulePackageJsonSpy.mockReturnValue({
686+
packageJson: { name: 'nx', version: '22.0.0' },
687+
path: '/fake/path/package.json',
688+
});
689+
690+
const options: SetupAiAgentsGeneratorSchema = {
691+
directory: '.',
692+
agents: ['gemini'],
693+
};
694+
695+
// Simulate old Nx 21 config with extra args
696+
tree.write(
697+
'.gemini/settings.json',
698+
JSON.stringify({
699+
mcpServers: {
700+
'nx-mcp': {
701+
type: 'stdio',
702+
command: 'npx',
703+
args: ['nx-mcp', '--minimal', '--transport', 'http'],
704+
},
705+
},
706+
})
707+
);
708+
709+
await setupAiAgentsGenerator(tree, options);
710+
711+
const config = JSON.parse(
712+
tree.read('.gemini/settings.json')?.toString() ?? '{}'
713+
);
714+
// Should update base args to v22 format but preserve extras
715+
expect(config.mcpServers['nx-mcp'].args).toEqual([
716+
'nx',
717+
'mcp',
718+
'--minimal',
719+
'--transport',
720+
'http',
721+
]);
722+
});
723+
724+
it('should preserve extra args from versioned nx-mcp base command (gemini)', async () => {
725+
const options: SetupAiAgentsGeneratorSchema = {
726+
directory: '.',
727+
agents: ['gemini'],
728+
};
729+
730+
tree.write(
731+
'.gemini/settings.json',
732+
JSON.stringify({
733+
mcpServers: {
734+
'nx-mcp': {
735+
type: 'stdio',
736+
command: 'npx',
737+
args: ['nx-mcp@latest', '--experimental-polygraph'],
738+
},
739+
},
740+
})
741+
);
742+
743+
await setupAiAgentsGenerator(tree, options);
744+
745+
const config = JSON.parse(
746+
tree.read('.gemini/settings.json')?.toString() ?? '{}'
747+
);
748+
expect(config.mcpServers['nx-mcp'].args).toEqual([
749+
'nx',
750+
'mcp',
751+
'--experimental-polygraph',
752+
]);
753+
});
754+
755+
it('should preserve extra args in opencode MCP command', async () => {
756+
const options: SetupAiAgentsGeneratorSchema = {
757+
directory: '.',
758+
agents: ['opencode'],
759+
};
760+
761+
tree.write(
762+
'opencode.json',
763+
JSON.stringify({
764+
mcp: {
765+
'nx-mcp': {
766+
type: 'local',
767+
command: [
768+
'npx',
769+
'nx',
770+
'mcp',
771+
'--experimental-polygraph',
772+
'--transport',
773+
'http',
774+
],
775+
enabled: true,
776+
},
777+
},
778+
})
779+
);
780+
781+
await setupAiAgentsGenerator(tree, options);
782+
783+
const config = JSON.parse(
784+
tree.read('opencode.json')?.toString() ?? '{}'
785+
);
786+
expect(config.mcp['nx-mcp'].command).toEqual([
787+
'npx',
788+
'nx',
789+
'mcp',
790+
'--experimental-polygraph',
791+
'--transport',
792+
'http',
793+
]);
794+
});
795+
796+
it('should not add extra args when none exist in existing config', async () => {
797+
const options: SetupAiAgentsGeneratorSchema = {
798+
directory: '.',
799+
agents: ['gemini'],
800+
};
801+
802+
tree.write(
803+
'.gemini/settings.json',
804+
JSON.stringify({
805+
mcpServers: {
806+
'nx-mcp': {
807+
type: 'stdio',
808+
command: 'npx',
809+
args: ['nx', 'mcp'],
810+
},
811+
},
812+
})
813+
);
814+
815+
await setupAiAgentsGenerator(tree, options);
816+
817+
const config = JSON.parse(
818+
tree.read('.gemini/settings.json')?.toString() ?? '{}'
819+
);
820+
expect(config.mcpServers['nx-mcp'].args).toEqual(['nx', 'mcp']);
821+
});
822+
});
823+
612824
describe('Nx version-specific MCP configuration', () => {
613825
it('should use "nx mcp" for Nx 22+ (gemini)', async () => {
614826
readModulePackageJsonSpy.mockReturnValue({

packages/nx/src/ai/set-up-ai-agents/set-up-ai-agents.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,10 +435,52 @@ function writeAgentRules(tree: Tree, path: string, writeNxCloudRules: boolean) {
435435
}
436436
}
437437

438+
/**
439+
* Extract user-added extra args/flags from an existing MCP config args array
440+
* by stripping the known base command prefix.
441+
*
442+
* Known base patterns (matched in order, first wins):
443+
* ['nx', 'mcp'] or ['nx-mcp'] (possibly with @version suffix like nx-mcp@latest)
444+
* For opencode the caller prepends 'npx' to these patterns.
445+
*/
446+
function getExtraMcpArgs(
447+
existingArgs: string[] | undefined,
448+
knownBasePatterns: string[][]
449+
): string[] {
450+
if (!Array.isArray(existingArgs) || existingArgs.length === 0) return [];
451+
452+
for (const pattern of knownBasePatterns) {
453+
if (existingArgs.length < pattern.length) continue;
454+
455+
const matches = pattern.every((baseArg, i) => {
456+
if (baseArg === 'nx-mcp') {
457+
// Also match versioned variants like nx-mcp@latest
458+
return (
459+
existingArgs[i] === 'nx-mcp' || existingArgs[i].startsWith('nx-mcp@')
460+
);
461+
}
462+
return existingArgs[i] === baseArg;
463+
});
464+
465+
if (matches) {
466+
return existingArgs.slice(pattern.length);
467+
}
468+
}
469+
470+
return [];
471+
}
472+
438473
function mcpConfigUpdater(existing: any, nxVersion: string): any {
439474
const majorVersion = major(nxVersion);
440475
const mcpArgs = majorVersion >= 22 ? ['nx', 'mcp'] : ['nx-mcp'];
441476

477+
// Preserve any extra args (e.g. --experimental-polygraph, --transport http) from existing config
478+
const extraArgs = getExtraMcpArgs(existing.mcpServers?.['nx-mcp']?.args, [
479+
['nx', 'mcp'],
480+
['nx-mcp'],
481+
]);
482+
mcpArgs.push(...extraArgs);
483+
442484
if (existing.mcpServers) {
443485
existing.mcpServers['nx-mcp'] = {
444486
type: 'stdio',
@@ -462,6 +504,13 @@ function opencodeMcpConfigUpdater(existing: any, nxVersion: string): any {
462504
const mcpCommand =
463505
majorVersion >= 22 ? ['npx', 'nx', 'mcp'] : ['npx', 'nx-mcp'];
464506

507+
// Preserve any extra args (e.g. --experimental-polygraph, --transport http) from existing config
508+
const extraArgs = getExtraMcpArgs(existing.mcp?.['nx-mcp']?.command, [
509+
['npx', 'nx', 'mcp'],
510+
['npx', 'nx-mcp'],
511+
]);
512+
mcpCommand.push(...extraArgs);
513+
465514
if (existing.mcp) {
466515
existing.mcp['nx-mcp'] = {
467516
type: 'local',

0 commit comments

Comments
 (0)