feat: support streameable http transport (#3413)

* feat: support Stremeable Http transport

* feat: add http to rpc method
This commit is contained in:
Alejandro Peral Taboada 2025-05-21 19:17:26 +02:00 committed by GitHub
parent 0870c65fa5
commit 8356e058c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 44 additions and 41 deletions

View File

@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Support Streameable Http Transport for MCPs

View File

@ -134,14 +134,6 @@ const baseConfig = {
aliasResolverPlugin,
/* add to the end of plugins array */
esbuildProblemMatcherPlugin,
{
name: "alias-plugin",
setup(build) {
build.onResolve({ filter: /^pkce-challenge$/ }, (args) => {
return { path: require.resolve("pkce-challenge/dist/index.browser.js") }
})
},
},
],
format: "cjs",
sourcesContent: false,

34
package-lock.json generated
View File

@ -19,7 +19,7 @@
"@grpc/grpc-js": "^1.9.15",
"@grpc/reflection": "^1.0.4",
"@mistralai/mistralai": "^1.5.0",
"@modelcontextprotocol/sdk": "^1.7.0",
"@modelcontextprotocol/sdk": "^1.11.1",
"@opentelemetry/api": "^1.4.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.39.1",
"@opentelemetry/resources": "^1.30.1",
@ -7600,17 +7600,17 @@
"license": "BSD-2-Clause"
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz",
"integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==",
"license": "MIT",
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz",
"integrity": "sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==",
"dependencies": {
"content-type": "^1.0.5",
"cors": "^2.8.5",
"cross-spawn": "^7.0.3",
"eventsource": "^3.0.2",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0",
"pkce-challenge": "^4.1.0",
"pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.24.1"
@ -21465,10 +21465,9 @@
}
},
"node_modules/pkce-challenge": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz",
"integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==",
"license": "MIT",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
"integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
"engines": {
"node": ">=16.20.0"
}
@ -31509,16 +31508,17 @@
"integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="
},
"@modelcontextprotocol/sdk": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.7.0.tgz",
"integrity": "sha512-IYPe/FLpvF3IZrd/f5p5ffmWhMc3aEMuM2wGJASDqC2Ge7qatVCdbfPx3n/5xFeb19xN0j/911M2AaFuircsWA==",
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz",
"integrity": "sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==",
"requires": {
"content-type": "^1.0.5",
"cors": "^2.8.5",
"cross-spawn": "^7.0.3",
"eventsource": "^3.0.2",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0",
"pkce-challenge": "^4.1.0",
"pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.24.1"
@ -41248,9 +41248,9 @@
"dev": true
},
"pkce-challenge": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz",
"integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
"integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="
},
"pony-cause": {
"version": "1.1.1",

View File

@ -341,7 +341,7 @@
"@grpc/grpc-js": "^1.9.15",
"@grpc/reflection": "^1.0.4",
"@mistralai/mistralai": "^1.5.0",
"@modelcontextprotocol/sdk": "^1.7.0",
"@modelcontextprotocol/sdk": "^1.11.1",
"@opentelemetry/api": "^1.4.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.39.1",
"@opentelemetry/resources": "^1.30.1",

View File

@ -30,6 +30,7 @@ import { arePathsEqual } from "@utils/path"
import { secondsToMs } from "@utils/time"
import { GlobalFileNames } from "@core/storage/disk"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { ExtensionMessage } from "@shared/ExtensionMessage"
// Default timeout for internal MCP data requests in milliseconds; is not the same as the user facing timeout stored as DEFAULT_MCP_TIMEOUT_SECONDS
@ -38,10 +39,10 @@ const DEFAULT_REQUEST_TIMEOUT_MS = 5000
export type McpConnection = {
server: McpServer
client: Client
transport: StdioClientTransport | SSEClientTransport
transport: StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport
}
export type McpTransportType = "stdio" | "sse"
export type McpTransportType = "stdio" | "sse" | "http"
export type McpServerConfig = z.infer<typeof ServerConfigSchema>
@ -54,22 +55,23 @@ const BaseConfigSchema = z.object({
})
const SseConfigSchema = BaseConfigSchema.extend({
transportType: z.literal("sse"),
url: z.string().url(),
}).transform((config) => ({
...config,
transportType: "sse" as const,
}))
})
const StdioConfigSchema = BaseConfigSchema.extend({
transportType: z.literal("stdio"),
command: z.string(),
args: z.array(z.string()).optional(),
env: z.record(z.string()).optional(),
}).transform((config) => ({
...config,
transportType: "stdio" as const,
}))
})
const ServerConfigSchema = z.union([StdioConfigSchema, SseConfigSchema])
const StreamableHTTPConfigSchema = BaseConfigSchema.extend({
transportType: z.literal("http"),
url: z.string().url(),
})
const ServerConfigSchema = z.discriminatedUnion("transportType", [StdioConfigSchema, SseConfigSchema, StreamableHTTPConfigSchema])
const McpSettingsSchema = z.object({
mcpServers: z.record(ServerConfigSchema),
@ -183,7 +185,7 @@ export class McpHub {
private async connectToServerRPC(
name: string,
config: z.infer<typeof StdioConfigSchema> | z.infer<typeof SseConfigSchema>,
config: z.infer<typeof StdioConfigSchema> | z.infer<typeof SseConfigSchema> | z.infer<typeof StreamableHTTPConfigSchema>,
): Promise<void> {
// Remove existing connection if it exists (should never happen, the connection should be deleted beforehand)
this.connections = this.connections.filter((conn) => conn.server.name !== name)
@ -200,10 +202,12 @@ export class McpHub {
},
)
let transport: StdioClientTransport | SSEClientTransport
let transport: StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport
if (config.transportType === "sse") {
transport = new SSEClientTransport(new URL(config.url), {})
} else if (config.transportType === "http") {
transport = new StreamableHTTPClientTransport(new URL(config.url), {})
} else {
transport = new StdioClientTransport({
command: config.command,
@ -297,7 +301,7 @@ export class McpHub {
private async connectToServer(
name: string,
config: z.infer<typeof StdioConfigSchema> | z.infer<typeof SseConfigSchema>,
config: z.infer<typeof StdioConfigSchema> | z.infer<typeof SseConfigSchema> | z.infer<typeof StreamableHTTPConfigSchema>,
): Promise<void> {
// Remove existing connection if it exists (should never happen, the connection should be deleted beforehand)
this.connections = this.connections.filter((conn) => conn.server.name !== name)
@ -314,10 +318,12 @@ export class McpHub {
},
)
let transport: StdioClientTransport | SSEClientTransport
let transport: StdioClientTransport | SSEClientTransport | StreamableHTTPClientTransport
if (config.transportType === "sse") {
transport = new SSEClientTransport(new URL(config.url), {})
} else if (config.transportType === "http") {
transport = new StreamableHTTPClientTransport(new URL(config.url), {})
} else {
transport = new StdioClientTransport({
command: config.command,