fix(opencode): preserve local mcp fallback

This commit is contained in:
777genius 2026-05-16 22:30:11 +03:00
parent 429dfe5528
commit 2c393bc78f
3 changed files with 94 additions and 13 deletions

View file

@ -68,6 +68,12 @@ import { ChangeExtractorService } from '@main/services/team/ChangeExtractorServi
import { CrossTeamService } from '@main/services/team/CrossTeamService';
import { FileContentResolver } from '@main/services/team/FileContentResolver';
import { GitDiffFallback } from '@main/services/team/GitDiffFallback';
import {
clearOpenCodeLocalMcpLaunchEnv,
copyOpenCodeLocalMcpLaunchEnv,
hasOpenCodeLocalMcpLaunchEnv,
isOpenCodeMcpHttpBridgeEnabled,
} from '@main/services/team/opencode/bridge/OpenCodeMcpBridgeEnv';
import { ReviewApplierService } from '@main/services/team/ReviewApplierService';
import { TeamBackupService } from '@main/services/team/TeamBackupService';
import { TeamConfigReader } from '@main/services/team/TeamConfigReader';
@ -145,10 +151,6 @@ import {
OpenCodeBridgeCommandHandshakePort,
} from './services/team/opencode/bridge/OpenCodeBridgeHandshakeClient';
import { cleanupManagedOpenCodeServeProcesses } from './services/team/opencode/bridge/OpenCodeManagedHostProcessCleanup';
import {
clearOpenCodeLocalMcpLaunchEnv,
isOpenCodeMcpHttpBridgeEnabled,
} from './services/team/opencode/bridge/OpenCodeMcpBridgeEnv';
import { OpenCodeStateChangingBridgeCommandService } from './services/team/opencode/bridge/OpenCodeStateChangingBridgeCommandService';
import { OpenCodeRuntimeManifestEvidenceReader } from './services/team/opencode/store/OpenCodeRuntimeManifestEvidenceReader';
import {
@ -361,6 +363,7 @@ async function createOpenCodeRuntimeAdapterRegistry(
if (!useHttpMcpBridge) {
delete bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL;
} else {
delete bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL;
clearOpenCodeLocalMcpLaunchEnv(bridgeEnv);
}
const applyMcpLaunchSpecEnv = async (
@ -390,6 +393,20 @@ async function createOpenCodeRuntimeAdapterRegistry(
);
}
};
const ensureOpenCodeLocalMcpLaunchEnv = async (
targetEnv: NodeJS.ProcessEnv,
options: { emitProgress?: boolean } = {}
): Promise<void> => {
if (hasOpenCodeLocalMcpLaunchEnv(bridgeEnv)) {
copyOpenCodeLocalMcpLaunchEnv(bridgeEnv, targetEnv);
return;
}
await applyMcpLaunchSpecEnv(targetEnv, options);
if (hasOpenCodeLocalMcpLaunchEnv(targetEnv)) {
copyOpenCodeLocalMcpLaunchEnv(targetEnv, bridgeEnv);
}
};
try {
const appManagedOpenCodeBinary = await resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath();
if (appManagedOpenCodeBinary && !bridgeEnv.CLAUDE_MULTIMODEL_OPENCODE_BIN_PATH) {
@ -433,8 +450,8 @@ async function createOpenCodeRuntimeAdapterRegistry(
);
}
}
if (!useHttpMcpBridge && !bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL) {
await applyMcpLaunchSpecEnv(bridgeEnv, { emitProgress: true });
if (!bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL) {
await ensureOpenCodeLocalMcpLaunchEnv(bridgeEnv, { emitProgress: true });
}
reportProgress('runtime-bridge', 'Preparing OpenCode bridge...');
@ -453,6 +470,7 @@ async function createOpenCodeRuntimeAdapterRegistry(
delete bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL;
delete nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL;
clearOpenCodeLocalMcpLaunchEnv(nextEnv);
await ensureOpenCodeLocalMcpLaunchEnv(nextEnv);
logger.warn(
`[OpenCode] Runtime adapter bridge MCP HTTP server refresh failed: ${
error instanceof Error ? error.message : String(error)

View file

@ -6,18 +6,32 @@ const LOCAL_MCP_LAUNCH_ENV_KEYS = [
'CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON',
] as const;
export interface OpenCodeMcpHttpBridgeEnv {
CLAUDE_TEAM_OPENCODE_MCP_HTTP?: string;
}
export type OpenCodeMcpBridgeEnv = Record<string, string | undefined>;
export function isOpenCodeMcpHttpBridgeEnabled(
env: OpenCodeMcpHttpBridgeEnv = process.env
): boolean {
export function isOpenCodeMcpHttpBridgeEnabled(env: OpenCodeMcpBridgeEnv = process.env): boolean {
const rawValue = env.CLAUDE_TEAM_OPENCODE_MCP_HTTP?.trim().toLowerCase();
return rawValue ? !DISABLED_HTTP_MCP_VALUES.has(rawValue) : true;
}
export function clearOpenCodeLocalMcpLaunchEnv(env: NodeJS.ProcessEnv): void {
export function hasOpenCodeLocalMcpLaunchEnv(env: OpenCodeMcpBridgeEnv): boolean {
return LOCAL_MCP_LAUNCH_ENV_KEYS.every((key) => Boolean(env[key]?.trim()));
}
export function copyOpenCodeLocalMcpLaunchEnv(
sourceEnv: OpenCodeMcpBridgeEnv,
targetEnv: OpenCodeMcpBridgeEnv
): void {
for (const key of LOCAL_MCP_LAUNCH_ENV_KEYS) {
const value = sourceEnv[key]?.trim();
if (value) {
targetEnv[key] = value;
} else {
delete targetEnv[key];
}
}
}
export function clearOpenCodeLocalMcpLaunchEnv(env: OpenCodeMcpBridgeEnv): void {
for (const key of LOCAL_MCP_LAUNCH_ENV_KEYS) {
delete env[key];
}

View file

@ -2,6 +2,8 @@ import { describe, expect, it } from 'vitest';
import {
clearOpenCodeLocalMcpLaunchEnv,
copyOpenCodeLocalMcpLaunchEnv,
hasOpenCodeLocalMcpLaunchEnv,
isOpenCodeMcpHttpBridgeEnabled,
} from '@main/services/team/opencode/bridge/OpenCodeMcpBridgeEnv';
@ -20,6 +22,53 @@ describe('OpenCodeMcpBridgeEnv', () => {
expect(isOpenCodeMcpHttpBridgeEnabled({ CLAUDE_TEAM_OPENCODE_MCP_HTTP: 'off' })).toBe(false);
});
it('accepts process-style env objects', () => {
const env: NodeJS.ProcessEnv = {
PATH: '/usr/bin',
CLAUDE_TEAM_OPENCODE_MCP_HTTP: 'no',
};
expect(isOpenCodeMcpHttpBridgeEnabled(env)).toBe(false);
});
it('detects complete local MCP launch env', () => {
expect(
hasOpenCodeLocalMcpLaunchEnv({
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND: 'node',
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY: 'mcp-server/dist/index.js',
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON: '["mcp-server/dist/index.js"]',
})
).toBe(true);
expect(
hasOpenCodeLocalMcpLaunchEnv({
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND: 'node',
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY: '',
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON: '["mcp-server/dist/index.js"]',
})
).toBe(false);
});
it('copies local MCP launch env for HTTP fallback without copying the HTTP URL', () => {
const target: NodeJS.ProcessEnv = {
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL: 'http://127.0.0.1:41001/mcp',
};
copyOpenCodeLocalMcpLaunchEnv(
{
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND: 'node',
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY: 'mcp-server/dist/index.js',
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON: '["mcp-server/dist/index.js"]',
},
target
);
expect(target.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND).toBe('node');
expect(target.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY).toBe('mcp-server/dist/index.js');
expect(target.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON).toBe('["mcp-server/dist/index.js"]');
expect(target.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL).toBe('http://127.0.0.1:41001/mcp');
});
it('removes local MCP launch env when HTTP MCP is active', () => {
const env: NodeJS.ProcessEnv = {
CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND: 'node',