Skip to content

Commit 6957215

Browse files
authored
Merge pull request #118 from 777genius/fix/hide-cli-child-windows-dev
fix(runtime): hide cli child windows by default
2 parents d5f87a2 + 31bfd5e commit 6957215

23 files changed

Lines changed: 1386 additions & 95 deletions

mcp-server/src/index.ts

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
11
#!/usr/bin/env node
2-
import { pathToFileURL } from 'node:url';
2+
import { pathToFileURL } from 'url';
33

44
import { FastMCP } from 'fastmcp';
55

66
import { registerTools } from './tools';
77

8+
const HTTP_TRANSPORT = 'httpStream';
9+
const STDIO_TRANSPORT = 'stdio';
10+
const DEFAULT_HTTP_HOST = '127.0.0.1';
11+
const DEFAULT_HTTP_ENDPOINT = '/mcp';
12+
13+
export type AgentTeamsMcpStartOptions =
14+
| {
15+
transportType: typeof STDIO_TRANSPORT;
16+
}
17+
| {
18+
transportType: typeof HTTP_TRANSPORT;
19+
httpStream: {
20+
host: string;
21+
port: number;
22+
endpoint: `/${string}`;
23+
};
24+
};
25+
826
export function createServer() {
927
const server = new FastMCP({
1028
name: 'agent-teams-mcp',
@@ -16,9 +34,64 @@ export function createServer() {
1634
return server;
1735
}
1836

37+
function getArgValue(argv: string[], name: string): string | null {
38+
const directPrefix = `${name}=`;
39+
for (let index = 2; index < argv.length; index += 1) {
40+
const value = argv[index];
41+
if (value === name) {
42+
return argv[index + 1] ?? null;
43+
}
44+
if (value.startsWith(directPrefix)) {
45+
return value.slice(directPrefix.length);
46+
}
47+
}
48+
return null;
49+
}
50+
51+
function normalizeEndpoint(value: string | null | undefined): `/${string}` {
52+
const trimmed = value?.trim();
53+
if (!trimmed) {
54+
return DEFAULT_HTTP_ENDPOINT;
55+
}
56+
return (trimmed.startsWith('/') ? trimmed : `/${trimmed}`) as `/${string}`;
57+
}
58+
59+
function parsePort(value: string | null | undefined): number {
60+
const parsed = Number(value);
61+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
62+
throw new Error(`Invalid agent-teams MCP HTTP port: ${value ?? '<empty>'}`);
63+
}
64+
return parsed;
65+
}
66+
67+
export function resolveStartOptions(
68+
argv: string[] = process.argv,
69+
env: NodeJS.ProcessEnv = process.env
70+
): AgentTeamsMcpStartOptions {
71+
const transport =
72+
getArgValue(argv, '--transport') ??
73+
getArgValue(argv, '--transportType') ??
74+
env.AGENT_TEAMS_MCP_TRANSPORT ??
75+
STDIO_TRANSPORT;
76+
77+
if (transport !== HTTP_TRANSPORT) {
78+
return { transportType: STDIO_TRANSPORT };
79+
}
80+
81+
return {
82+
transportType: HTTP_TRANSPORT,
83+
httpStream: {
84+
host:
85+
getArgValue(argv, '--host')?.trim() ??
86+
env.AGENT_TEAMS_MCP_HTTP_HOST?.trim() ??
87+
DEFAULT_HTTP_HOST,
88+
port: parsePort(getArgValue(argv, '--port') ?? env.AGENT_TEAMS_MCP_HTTP_PORT),
89+
endpoint: normalizeEndpoint(getArgValue(argv, '--endpoint') ?? env.AGENT_TEAMS_MCP_HTTP_ENDPOINT),
90+
},
91+
};
92+
}
93+
1994
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
2095
const server = createServer();
21-
void server.start({
22-
transportType: 'stdio',
23-
});
96+
void server.start(resolveStartOptions());
2497
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { resolveStartOptions } from '../src/index';
4+
5+
describe('agent-teams MCP start options', () => {
6+
it('defaults to stdio transport', () => {
7+
expect(resolveStartOptions(['node', 'index.js'], {})).toEqual({
8+
transportType: 'stdio',
9+
});
10+
});
11+
12+
it('resolves HTTP stream transport from CLI args', () => {
13+
expect(
14+
resolveStartOptions(
15+
[
16+
'node',
17+
'index.js',
18+
'--transport',
19+
'httpStream',
20+
'--host',
21+
'127.0.0.1',
22+
'--port',
23+
'43123',
24+
'--endpoint',
25+
'mcp',
26+
],
27+
{}
28+
)
29+
).toEqual({
30+
transportType: 'httpStream',
31+
httpStream: {
32+
host: '127.0.0.1',
33+
port: 43123,
34+
endpoint: '/mcp',
35+
},
36+
});
37+
});
38+
39+
it('resolves HTTP stream transport from environment', () => {
40+
expect(
41+
resolveStartOptions(['node', 'index.js'], {
42+
AGENT_TEAMS_MCP_TRANSPORT: 'httpStream',
43+
AGENT_TEAMS_MCP_HTTP_PORT: '43124',
44+
})
45+
).toEqual({
46+
transportType: 'httpStream',
47+
httpStream: {
48+
host: '127.0.0.1',
49+
port: 43124,
50+
endpoint: '/mcp',
51+
},
52+
});
53+
});
54+
});

src/main/index.ts

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ import {
133133
import { startEventLoopLagMonitor } from './services/infrastructure/EventLoopLagMonitor';
134134
import { HttpServer } from './services/infrastructure/HttpServer';
135135
import { clearAutoResumeService } from './services/team/AutoResumeService';
136+
import { agentTeamsMcpHttpServer } from './services/team/AgentTeamsMcpHttpServer';
136137
import { LaunchIoGovernor } from './services/team/LaunchIoGovernor';
137138
import { OpenCodeBridgeCommandClient } from './services/team/opencode/bridge/OpenCodeBridgeCommandClient';
138139
import {
@@ -352,6 +353,33 @@ async function createOpenCodeRuntimeAdapterRegistry(
352353
const bridgeEnv = applyOpenCodeAutoUpdatePolicy({ ...process.env });
353354
bridgeEnv.CLAUDE_TEAM_APP_INSTANCE_ID = openCodeManagedHostInstanceId;
354355
bridgeEnv.AGENT_TEAMS_MCP_CLAUDE_DIR = getClaudeBasePath();
356+
const applyMcpLaunchSpecEnv = async (
357+
targetEnv: NodeJS.ProcessEnv,
358+
options: { emitProgress?: boolean } = {}
359+
): Promise<void> => {
360+
try {
361+
if (options.emitProgress) {
362+
reportProgress('runtime-mcp', 'Resolving Agent Teams MCP server...');
363+
}
364+
const mcpLaunchSpec = await resolveAgentTeamsMcpLaunchSpec({
365+
onProgress: options.emitProgress
366+
? ({ phase, message }) => reportProgress(`mcp-${phase}`, message)
367+
: undefined,
368+
});
369+
const mcpEntry = mcpLaunchSpec.args[0];
370+
if (mcpEntry) {
371+
targetEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND = mcpLaunchSpec.command;
372+
targetEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY = mcpEntry;
373+
targetEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON = JSON.stringify(mcpLaunchSpec.args);
374+
}
375+
} catch (error) {
376+
logger.warn(
377+
`[OpenCode] Runtime adapter bridge MCP entrypoint unresolved: ${
378+
error instanceof Error ? error.message : String(error)
379+
}`
380+
);
381+
}
382+
};
355383
try {
356384
const appManagedOpenCodeBinary = await resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath();
357385
if (appManagedOpenCodeBinary && !bridgeEnv.CLAUDE_MULTIMODEL_OPENCODE_BIN_PATH) {
@@ -381,29 +409,69 @@ async function createOpenCodeRuntimeAdapterRegistry(
381409
);
382410
}
383411
try {
384-
reportProgress('runtime-mcp', 'Resolving Agent Teams MCP server...');
385-
const mcpLaunchSpec = await resolveAgentTeamsMcpLaunchSpec({
386-
onProgress: ({ phase, message }) => reportProgress(`mcp-${phase}`, message),
387-
});
388-
const mcpEntry = mcpLaunchSpec.args[0];
389-
if (mcpEntry) {
390-
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND = mcpLaunchSpec.command;
391-
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY = mcpEntry;
392-
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON = JSON.stringify(mcpLaunchSpec.args);
393-
}
412+
reportProgress('runtime-mcp-http', 'Starting Agent Teams MCP server...');
413+
const mcpHttpServer = await agentTeamsMcpHttpServer.ensureStarted();
414+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL = mcpHttpServer.url;
415+
reportProgress('runtime-mcp-http-ready', 'Agent Teams MCP server is ready...');
394416
} catch (error) {
395417
logger.warn(
396-
`[OpenCode] Runtime adapter bridge MCP entrypoint unresolved: ${
418+
`[OpenCode] Runtime adapter bridge MCP HTTP server unavailable: ${
397419
error instanceof Error ? error.message : String(error)
398420
}`
399421
);
400422
}
423+
if (!bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL) {
424+
await applyMcpLaunchSpecEnv(bridgeEnv, { emitProgress: true });
425+
}
401426

402427
reportProgress('runtime-bridge', 'Preparing OpenCode bridge...');
428+
const resolveBridgeCommandEnv = async (): Promise<NodeJS.ProcessEnv> => {
429+
const nextEnv = { ...bridgeEnv };
430+
if (!bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL) {
431+
return nextEnv;
432+
}
433+
try {
434+
const mcpHttpServer = await agentTeamsMcpHttpServer.ensureStarted();
435+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL = mcpHttpServer.url;
436+
nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL = mcpHttpServer.url;
437+
delete nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND;
438+
delete nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY;
439+
delete nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON;
440+
} catch (error) {
441+
delete nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_URL;
442+
if (
443+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND &&
444+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY &&
445+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON
446+
) {
447+
nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND =
448+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND;
449+
nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY =
450+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY;
451+
nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON =
452+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON;
453+
} else {
454+
await applyMcpLaunchSpecEnv(nextEnv);
455+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND =
456+
nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_COMMAND;
457+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY =
458+
nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ENTRY;
459+
bridgeEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON =
460+
nextEnv.CLAUDE_MULTIMODEL_AGENT_TEAMS_MCP_ARGS_JSON;
461+
}
462+
logger.warn(
463+
`[OpenCode] Runtime adapter bridge MCP HTTP server refresh failed: ${
464+
error instanceof Error ? error.message : String(error)
465+
}`
466+
);
467+
}
468+
return nextEnv;
469+
};
403470
const bridgeClient = new OpenCodeBridgeCommandClient({
404471
binaryPath,
405472
tempDirectory: join(app.getPath('temp'), 'claude-team-opencode-bridge'),
406473
env: bridgeEnv,
474+
envProvider: resolveBridgeCommandEnv,
407475
});
408476
const bridgeControlDir = join(app.getPath('userData'), 'opencode-bridge');
409477
const clientIdentity = createOpenCodeBridgeClientIdentity({
@@ -2081,6 +2149,9 @@ async function shutdownServices(): Promise<void> {
20812149
() => cleanupOpenCodeHostsForLifecycle('shutdown'),
20822150
10_000
20832151
);
2152+
await runShutdownStep('Agent Teams MCP HTTP server cleanup', () =>
2153+
agentTeamsMcpHttpServer.stop()
2154+
);
20842155
await runShutdownStep('tracked CLI subprocess cleanup', () =>
20852156
killTrackedCliProcesses('SIGKILL')
20862157
);

0 commit comments

Comments
 (0)