Use official ACP SDK and support HTTP/SSE based MCP servers (#13856)

This commit is contained in:
Steffen Deusch
2025-12-17 00:28:27 +01:00
committed by GitHub
parent cc52839f19
commit ba100642e3
11 changed files with 120 additions and 1420 deletions

View File

@@ -4,8 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type { ReadableStream } from 'node:stream/web';
import type {
Config,
GeminiChat,
@@ -33,7 +31,7 @@ import {
createWorkingStdio,
startupProfiler,
} from '@google/gemini-cli-core';
import * as acp from './acp.js';
import * as acp from '@agentclientprotocol/sdk';
import { AcpFileSystemService } from './fileSystemService.js';
import { Readable, Writable } from 'node:stream';
import type { Content, Part, FunctionCall } from '@google/genai';
@@ -53,13 +51,13 @@ export async function runZedIntegration(
argv: CliArgs,
) {
const { stdout: workingStdout } = createWorkingStdio();
const stdout = Writable.toWeb(workingStdout);
const stdout = Writable.toWeb(workingStdout) as WritableStream;
const stdin = Readable.toWeb(process.stdin) as ReadableStream<Uint8Array>;
const stream = acp.ndJsonStream(stdout, stdin);
new acp.AgentSideConnection(
(client: acp.Client) => new GeminiAgent(config, settings, argv, client),
stdout,
stdin,
(connection) => new GeminiAgent(config, settings, argv, connection),
stream,
);
}
@@ -71,7 +69,7 @@ export class GeminiAgent {
private config: Config,
private settings: LoadedSettings,
private argv: CliArgs,
private client: acp.Client,
private connection: acp.AgentSideConnection,
) {}
async initialize(
@@ -107,6 +105,10 @@ export class GeminiAgent {
audio: true,
embeddedContext: true,
},
mcpCapabilities: {
http: true,
sse: true,
},
},
};
}
@@ -156,7 +158,7 @@ export class GeminiAgent {
if (this.clientCapabilities?.fs) {
const acpFileSystemService = new AcpFileSystemService(
this.client,
this.connection,
sessionId,
this.clientCapabilities.fs,
config.getFileSystemService(),
@@ -166,7 +168,7 @@ export class GeminiAgent {
const geminiClient = config.getGeminiClient();
const chat = await geminiClient.startChat();
const session = new Session(sessionId, chat, config, this.client);
const session = new Session(sessionId, chat, config, this.connection);
this.sessions.set(sessionId, session);
return {
@@ -181,12 +183,37 @@ export class GeminiAgent {
): Promise<Config> {
const mergedMcpServers = { ...this.settings.merged.mcpServers };
for (const { command, args, env: rawEnv, name } of mcpServers) {
const env: Record<string, string> = {};
for (const { name: envName, value } of rawEnv) {
env[envName] = value;
for (const server of mcpServers) {
if (
'type' in server &&
(server.type === 'sse' || server.type === 'http')
) {
// HTTP or SSE MCP server
const headers = Object.fromEntries(
server.headers.map(({ name, value }) => [name, value]),
);
mergedMcpServers[server.name] = new MCPServerConfig(
undefined, // command
undefined, // args
undefined, // env
undefined, // cwd
server.type === 'sse' ? server.url : undefined, // url (sse)
server.type === 'http' ? server.url : undefined, // httpUrl
headers,
);
} else if ('command' in server) {
// Stdio MCP server
const env: Record<string, string> = {};
for (const { name: envName, value } of server.env) {
env[envName] = value;
}
mergedMcpServers[server.name] = new MCPServerConfig(
server.command,
server.args,
env,
cwd,
);
}
mergedMcpServers[name] = new MCPServerConfig(command, args, env, cwd);
}
const settings = { ...this.settings.merged, mcpServers: mergedMcpServers };
@@ -222,7 +249,7 @@ export class Session {
private readonly id: string,
private readonly chat: GeminiChat,
private readonly config: Config,
private readonly client: acp.Client,
private readonly connection: acp.AgentSideConnection,
) {}
async cancelPendingPrompt(): Promise<void> {
@@ -340,13 +367,15 @@ export class Session {
return { stopReason: 'end_turn' };
}
private async sendUpdate(update: acp.SessionUpdate): Promise<void> {
private async sendUpdate(
update: acp.SessionNotification['update'],
): Promise<void> {
const params: acp.SessionNotification = {
sessionId: this.id,
update,
};
await this.client.sessionUpdate(params);
await this.connection.sessionUpdate(params);
}
private async runTool(
@@ -432,7 +461,7 @@ export class Session {
},
};
const output = await this.client.requestPermission(params);
const output = await this.connection.requestPermission(params);
const outcome =
output.outcome.outcome === 'cancelled'
? ToolConfirmationOutcome.Cancel