Skip to content

Commit f0bc32a

Browse files
authored
Merge pull request #7520 from uinstinct/cli-sse-streamable-mcp
feat(cli): support sse and streamable http for mcp
2 parents 1ff23a6 + 9a2a36d commit f0bc32a

File tree

2 files changed

+130
-22
lines changed

2 files changed

+130
-22
lines changed

extensions/cli/src/services/MCPService.test.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AssistantConfig } from "@continuedev/sdk";
2-
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33

44
import { MCPService } from "./MCPService.js";
55

@@ -21,6 +21,14 @@ vi.mock("@modelcontextprotocol/sdk/client/stdio.js", () => ({
2121
StdioClientTransport: vi.fn(),
2222
}));
2323

24+
vi.mock("@modelcontextprotocol/sdk/client/sse.js", () => ({
25+
SSEClientTransport: vi.fn(),
26+
}));
27+
28+
vi.mock("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({
29+
StreamableHTTPClientTransport: vi.fn(),
30+
}));
31+
2432
describe("MCPService", () => {
2533
let mcpService: MCPService;
2634
let mockAssistant: AssistantConfig;
@@ -152,4 +160,56 @@ describe("MCPService", () => {
152160
await expect(mcpService.cleanup()).resolves.not.toThrow();
153161
});
154162
});
163+
164+
describe("transport types", () => {
165+
it("should support SSE transport", async () => {
166+
const sseAssistant: AssistantConfig = {
167+
name: "sse-assistant",
168+
version: "1.0.0",
169+
mcpServers: [
170+
{
171+
name: "sse-server",
172+
type: "sse",
173+
url: "https://example.com/sse",
174+
},
175+
],
176+
} as AssistantConfig;
177+
178+
await expect(mcpService.initialize(sseAssistant)).resolves.not.toThrow();
179+
});
180+
181+
it("should support streamable-http transport", async () => {
182+
const httpAssistant: AssistantConfig = {
183+
name: "http-assistant",
184+
version: "1.0.0",
185+
mcpServers: [
186+
{
187+
name: "http-server",
188+
type: "streamable-http",
189+
url: "https://example.com/http",
190+
},
191+
],
192+
} as AssistantConfig;
193+
194+
await expect(mcpService.initialize(httpAssistant)).resolves.not.toThrow();
195+
});
196+
197+
it("should default to stdio transport when type is not specified", async () => {
198+
const defaultAssistant: AssistantConfig = {
199+
name: "default-assistant",
200+
version: "1.0.0",
201+
mcpServers: [
202+
{
203+
name: "default-server",
204+
command: "npx",
205+
args: ["-v"],
206+
},
207+
],
208+
} as AssistantConfig;
209+
210+
await expect(
211+
mcpService.initialize(defaultAssistant),
212+
).resolves.not.toThrow();
213+
});
214+
});
155215
});

extensions/cli/src/services/MCPService.ts

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { type AssistantConfig } from "@continuedev/sdk";
22
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
34
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
6+
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
47

58
import { getErrorString } from "../util/error.js";
69
import { logger } from "../util/logger.js";
@@ -9,9 +12,9 @@ import { BaseService, ServiceWithDependencies } from "./BaseService.js";
912
import { serviceContainer } from "./ServiceContainer.js";
1013
import {
1114
MCPConnectionInfo,
15+
MCPServerConfig,
1216
MCPServiceState,
1317
SERVICE_NAMES,
14-
MCPServerConfig,
1518
} from "./types.js";
1619

1720
interface ServerConnection extends MCPConnectionInfo {
@@ -214,31 +217,12 @@ export class MCPService
214217
this.updateState();
215218

216219
try {
217-
if (serverConfig.type && serverConfig.type !== "stdio") {
218-
throw new Error(
219-
`${serverConfig.type} MCP servers are not yet supported in the Continue CLI`,
220-
);
221-
}
222-
if (!serverConfig.command) {
223-
throw new Error("MCP server command is not specified");
224-
}
225-
226220
const client = new Client(
227221
{ name: "continue-cli-client", version: "1.0.0" },
228222
{ capabilities: {} },
229223
);
230224

231-
const env: Record<string, string> = serverConfig.env || {};
232-
if (process.env.PATH !== undefined) {
233-
env.PATH = process.env.PATH;
234-
}
235-
236-
const transport = new StdioClientTransport({
237-
command: serverConfig.command,
238-
args: serverConfig.args,
239-
env,
240-
stderr: "ignore",
241-
});
225+
const transport = await this.constructTransport(serverConfig);
242226

243227
logger.debug("Connecting to MCP server", {
244228
name: serverName,
@@ -363,4 +347,68 @@ export class MCPService
363347
logger.debug("Shutting down MCPService");
364348
await this.shutdownConnections();
365349
}
350+
351+
/**
352+
* Construct transport based on server configuration
353+
*/
354+
private async constructTransport(
355+
serverConfig: MCPServerConfig,
356+
): Promise<Transport> {
357+
const transportType = serverConfig.type || "stdio";
358+
359+
switch (transportType) {
360+
case "stdio":
361+
if (!serverConfig.command) {
362+
throw new Error(
363+
"MCP server command is not specified for stdio transport",
364+
);
365+
}
366+
367+
const env: Record<string, string> = serverConfig.env || {};
368+
if (process.env.PATH !== undefined) {
369+
env.PATH = process.env.PATH;
370+
}
371+
372+
return new StdioClientTransport({
373+
command: serverConfig.command,
374+
args: serverConfig.args || [],
375+
env,
376+
cwd: serverConfig.cwd,
377+
stderr: "ignore",
378+
});
379+
380+
case "sse":
381+
if (!serverConfig.url) {
382+
throw new Error("MCP server URL is not specified for SSE transport");
383+
}
384+
return new SSEClientTransport(new URL(serverConfig.url), {
385+
eventSourceInit: {
386+
fetch: (input, init) =>
387+
fetch(input, {
388+
...init,
389+
headers: {
390+
...init?.headers,
391+
...(serverConfig.requestOptions?.headers as
392+
| Record<string, string>
393+
| undefined),
394+
},
395+
}),
396+
},
397+
requestInit: { headers: serverConfig.requestOptions?.headers },
398+
});
399+
400+
case "streamable-http":
401+
if (!serverConfig.url) {
402+
throw new Error(
403+
"MCP server URL is not specified for streamable-http transport",
404+
);
405+
}
406+
return new StreamableHTTPClientTransport(new URL(serverConfig.url), {
407+
requestInit: { headers: serverConfig.requestOptions?.headers },
408+
});
409+
410+
default:
411+
throw new Error(`Unsupported transport type: ${transportType}`);
412+
}
413+
}
366414
}

0 commit comments

Comments
 (0)