diff --git a/cli/src/__tests__/integration/local-agents.test.ts b/cli/src/__tests__/integration/local-agents.test.ts index b7444a87b3..712dc9e719 100644 --- a/cli/src/__tests__/integration/local-agents.test.ts +++ b/cli/src/__tests__/integration/local-agents.test.ts @@ -21,9 +21,11 @@ import { loadAgentDefinitions, loadLocalAgents, initializeAgentRegistry, + reloadLocalAgentRegistry, findAgentsDirectory, getLoadedAgentsData, getLoadedAgentsMessage, + getLoadedMCPServers, announceLoadedAgents, __resetLocalAgentRegistryForTests, } from '../../utils/local-agent-registry' @@ -1184,4 +1186,42 @@ describe('Local Agent Integration', () => { ), ).toBe(true) }) + + test('reloads MCP servers from the selected project after a project change', async () => { + // Simulates the project picker flow: the CLI is started from an ancestor + // directory (no mcp.json in the launch cwd), then the user selects a + // project whose .agents/mcp.json defines MCP servers. + const projectDir = path.join(tempDir, 'selected-project') + const projectAgentsDir = path.join(projectDir, '.agents') + mkdirSync(projectAgentsDir, { recursive: true }) + writeFileSync( + path.join(projectAgentsDir, 'mcp.json'), + JSON.stringify({ + mcpServers: { + context7: { + type: 'http', + url: 'https://mcp.context7.com/mcp', + }, + }, + }), + 'utf8', + ) + + // Launch: registry initialized from the ancestor directory, so the + // selected project's servers are not present yet + await initializeAgentRegistry() + expect(getLoadedMCPServers()).not.toHaveProperty('context7') + + // Project change: chdir + setProjectRoot like handleProjectChange does, + // then the registry must reload so mcp.json is picked up + process.chdir(projectDir) + setProjectRoot(projectDir) + await reloadLocalAgentRegistry() + + expect(getLoadedMCPServers()).toHaveProperty('context7') + expect(getLoadedMCPServers().context7).toMatchObject({ + type: 'http', + url: 'https://mcp.context7.com/mcp', + }) + }) }) diff --git a/cli/src/index.tsx b/cli/src/index.tsx index cae4e380eb..9b4b05e740 100644 --- a/cli/src/index.tsx +++ b/cli/src/index.tsx @@ -34,7 +34,10 @@ import { getAuthToken, getAuthTokenDetails } from './utils/auth' import { resetCodebuffClient } from './utils/codebuff-client' import { setApiClientAuthToken } from './utils/codebuff-api' import { IS_FREEBUFF } from './utils/constants' -import { initializeAgentRegistry } from './utils/local-agent-registry' +import { + initializeAgentRegistry, + reloadLocalAgentRegistry, +} from './utils/local-agent-registry' import { trimOversizedChatLogs } from './utils/chat-history' import { clearLogFile, logger } from './utils/logger' import { drainClientLogs } from './utils/log-shipper' @@ -356,6 +359,11 @@ async function main(): Promise { }) // Update the project root in the module state setProjectRoot(newProjectPath) + // Reload local agents and MCP servers for the newly selected project. + // The registry is initialized once at startup from the launch cwd, so + // when the CLI was started from an ancestor directory the selected + // project's .agents/mcp.json was never loaded. + await reloadLocalAgentRegistry() // Reset client to ensure tools use the updated project root resetCodebuffClient() // Save to recent projects list diff --git a/cli/src/utils/local-agent-registry.ts b/cli/src/utils/local-agent-registry.ts index 1781e50db3..65922ed078 100644 --- a/cli/src/utils/local-agent-registry.ts +++ b/cli/src/utils/local-agent-registry.ts @@ -89,6 +89,21 @@ export async function initializeAgentRegistry(): Promise { } } +/** + * Reload the local agent registry for the current working directory. + * + * Called after the project changes at runtime (e.g. the project picker), where + * the CLI was started from an ancestor directory so .agents content was read + * from the old cwd at startup. Clears the derived caches before re-initializing + * so agent listings, the resolved .agents directory, and MCP servers from the + * newly selected project's mcp.json all reflect the new working directory. + */ +export async function reloadLocalAgentRegistry(): Promise { + cachedAgentsByMode.clear() + cachedAgentsDir = null + await initializeAgentRegistry() +} + /** * Get default agent directories to scan. * Matches the SDK's getDefaultAgentDirs() to ensure consistency.