Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions cli/src/__tests__/integration/local-agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import {
loadAgentDefinitions,
loadLocalAgents,
initializeAgentRegistry,
reloadLocalAgentRegistry,
findAgentsDirectory,
getLoadedAgentsData,
getLoadedAgentsMessage,
getLoadedMCPServers,
announceLoadedAgents,
__resetLocalAgentRegistryForTests,
} from '../../utils/local-agent-registry'
Expand Down Expand Up @@ -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',
})
})
})
10 changes: 9 additions & 1 deletion cli/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -356,6 +359,11 @@ async function main(): Promise<void> {
})
// 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
Expand Down
15 changes: 15 additions & 0 deletions cli/src/utils/local-agent-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ export async function initializeAgentRegistry(): Promise<void> {
}
}

/**
* 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<void> {
cachedAgentsByMode.clear()
cachedAgentsDir = null
await initializeAgentRegistry()
}

/**
* Get default agent directories to scan.
* Matches the SDK's getDefaultAgentDirs() to ensure consistency.
Expand Down