diff --git a/src/main/services/runtime/agentTeamsMcpLaunchEnv.ts b/src/main/services/runtime/agentTeamsMcpLaunchEnv.ts new file mode 100644 index 00000000..1efebad6 --- /dev/null +++ b/src/main/services/runtime/agentTeamsMcpLaunchEnv.ts @@ -0,0 +1,46 @@ +import { resolveAgentTeamsMcpLaunchSpec } from '@main/services/team/TeamMcpConfigBuilder'; +import { createLogger } from '@shared/utils/logger'; + +import type { McpLaunchSpec } from '@main/services/team/TeamMcpConfigBuilder'; + +const logger = createLogger('Runtime:AgentTeamsMcpLaunchEnv'); + +const MCP_COMMAND_ENV = 'CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND'; +const MCP_ENTRY_ENV = 'CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY'; +const MCP_ARGS_JSON_ENV = 'CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON'; + +export type AgentTeamsMcpLaunchEnv = Record; + +export function hasAgentTeamsMcpLocalLaunchEnv(env: AgentTeamsMcpLaunchEnv): boolean { + return Boolean( + env[MCP_COMMAND_ENV]?.trim() && env[MCP_ENTRY_ENV]?.trim() && env[MCP_ARGS_JSON_ENV]?.trim() + ); +} + +export async function ensureAgentTeamsMcpLocalLaunchEnv( + env: AgentTeamsMcpLaunchEnv, + resolveLaunchSpec: () => Promise = resolveAgentTeamsMcpLaunchSpec +): Promise { + if (hasAgentTeamsMcpLocalLaunchEnv(env)) { + return; + } + + try { + const launchSpec = await resolveLaunchSpec(); + const entry = launchSpec.args[0]?.trim(); + const command = launchSpec.command.trim(); + if (!command || !entry) { + return; + } + + env[MCP_COMMAND_ENV] = command; + env[MCP_ENTRY_ENV] = entry; + env[MCP_ARGS_JSON_ENV] = JSON.stringify(launchSpec.args); + } catch (error) { + logger.warn( + `Unable to resolve Agent Teams MCP local launch env: ${ + error instanceof Error ? error.message : String(error) + }` + ); + } +} diff --git a/src/main/services/runtime/providerAwareCliEnv.ts b/src/main/services/runtime/providerAwareCliEnv.ts index 506f0b65..d5f7b0bf 100644 --- a/src/main/services/runtime/providerAwareCliEnv.ts +++ b/src/main/services/runtime/providerAwareCliEnv.ts @@ -3,6 +3,7 @@ import { getCachedShellEnv } from '@main/utils/shellEnv'; import { resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath } from '../infrastructure/OpenCodeRuntimeInstallerService'; +import { ensureAgentTeamsMcpLocalLaunchEnv } from './agentTeamsMcpLaunchEnv'; import { buildRuntimeBaseEnv } from './buildRuntimeBaseEnv'; import { providerConnectionService } from './ProviderConnectionService'; @@ -58,6 +59,9 @@ export async function buildProviderAwareCliEnv( ) { env.CODEX_CLI_PATH = appManagedCodexBinary; } + if (!resolvedProviderId || resolvedProviderId === 'opencode') { + await ensureAgentTeamsMcpLocalLaunchEnv(env); + } if (options.providerId) { if (!resolvedProviderId) { diff --git a/test/main/services/runtime/providerAwareCliEnv.test.ts b/test/main/services/runtime/providerAwareCliEnv.test.ts index 58ea080d..60b12711 100644 --- a/test/main/services/runtime/providerAwareCliEnv.test.ts +++ b/test/main/services/runtime/providerAwareCliEnv.test.ts @@ -12,6 +12,7 @@ const getConfiguredConnectionIssuesMock = vi.fn(); const getConfiguredConnectionLaunchArgsMock = vi.fn(); const resolveVerifiedAppManagedOpenCodeRuntimeBinaryPathMock = vi.fn(); const resolveVerifiedAppManagedCodexRuntimeBinaryPathMock = vi.fn(); +const resolveAgentTeamsMcpLaunchSpecMock = vi.fn(); vi.mock('@main/utils/cliEnv', () => ({ buildEnrichedEnv: (...args: Parameters) => @@ -68,6 +69,10 @@ vi.mock('@features/codex-runtime-installer/main', () => ({ resolveVerifiedAppManagedCodexRuntimeBinaryPathMock(), })); +vi.mock('@main/services/team/TeamMcpConfigBuilder', () => ({ + resolveAgentTeamsMcpLaunchSpec: () => resolveAgentTeamsMcpLaunchSpecMock(), +})); + describe('buildProviderAwareCliEnv', () => { beforeEach(() => { vi.resetModules(); @@ -95,6 +100,10 @@ describe('buildProviderAwareCliEnv', () => { getConfiguredConnectionIssuesMock.mockResolvedValue({}); resolveVerifiedAppManagedOpenCodeRuntimeBinaryPathMock.mockResolvedValue(null); resolveVerifiedAppManagedCodexRuntimeBinaryPathMock.mockResolvedValue(null); + resolveAgentTeamsMcpLaunchSpecMock.mockResolvedValue({ + command: 'node', + args: ['/app/mcp-server/index.js'], + }); }); it('builds provider-pinned CLI env and returns provider-specific issues', async () => { @@ -168,6 +177,44 @@ describe('buildProviderAwareCliEnv', () => { expect(result.connectionIssues).toEqual({}); expect(result.providerArgs).toEqual([]); expect(result.env.OPENCODE_DISABLE_AUTOUPDATE).toBe('1'); + expect(result.env.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND).toBe('node'); + expect(result.env.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY).toBe('/app/mcp-server/index.js'); + expect(result.env.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON).toBe( + '["/app/mcp-server/index.js"]' + ); + }); + + it('adds local Agent Teams MCP launch env for OpenCode provider runtime commands', async () => { + const { buildProviderAwareCliEnv } = + await import('../../../../src/main/services/runtime/providerAwareCliEnv'); + const result = await buildProviderAwareCliEnv({ + providerId: 'opencode', + }); + + expect(resolveAgentTeamsMcpLaunchSpecMock).toHaveBeenCalledTimes(1); + expect(result.env.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND).toBe('node'); + expect(result.env.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY).toBe('/app/mcp-server/index.js'); + expect(result.env.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON).toBe( + '["/app/mcp-server/index.js"]' + ); + }); + + it('preserves explicit local Agent Teams MCP launch env for OpenCode provider commands', async () => { + const { buildProviderAwareCliEnv } = + await import('../../../../src/main/services/runtime/providerAwareCliEnv'); + const result = await buildProviderAwareCliEnv({ + providerId: 'opencode', + env: { + CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND: 'custom-node', + CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY: '/custom/mcp.js', + CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON: '["/custom/mcp.js"]', + }, + }); + + expect(resolveAgentTeamsMcpLaunchSpecMock).not.toHaveBeenCalled(); + expect(result.env.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND).toBe('custom-node'); + expect(result.env.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY).toBe('/custom/mcp.js'); + expect(result.env.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON).toBe('["/custom/mcp.js"]'); }); it('allows OpenCode auto-update only behind an explicit app override', async () => {