mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: auto-infer streamable-http type for MCP servers with URL configuration
- Modified validateServerConfig to auto-infer streamable-http when URL is present without explicit type - Updated ServerConfigSchema to prioritize streamable-http over SSE for URL configs - SSE now requires explicit type specification - Added comprehensive test coverage for URL auto-inference - Maintains full backward compatibility with existing configs Fixes #9118
This commit is contained in:
parent
e98f4b9057
commit
8630a9601b
2 changed files with 106 additions and 21 deletions
|
|
@ -77,7 +77,7 @@ const streamableHttpFieldsErrorMessage =
|
|||
const mixedFieldsErrorMessage =
|
||||
"Cannot mix 'stdio' and ('sse' or 'streamable-http') fields. For 'stdio' use 'command', 'args', and 'env'. For 'sse'/'streamable-http' use 'url' and 'headers'"
|
||||
const missingFieldsErrorMessage =
|
||||
"Server configuration must include either 'command' (for stdio) or 'url' (for sse/streamable-http) and a corresponding 'type' if 'url' is used."
|
||||
"Server configuration must include either 'command' (for stdio) or 'url' (for sse/streamable-http). When 'url' is present without 'type', 'streamable-http' will be inferred."
|
||||
|
||||
// Helper function to create a refined schema with better error messages
|
||||
const createServerTypeSchema = () => {
|
||||
|
|
@ -98,22 +98,7 @@ const createServerTypeSchema = () => {
|
|||
type: "stdio" as const,
|
||||
}))
|
||||
.refine((data) => data.type === undefined || data.type === "stdio", { message: typeErrorMessage }),
|
||||
// SSE config (has url field)
|
||||
BaseConfigSchema.extend({
|
||||
type: z.enum(["sse"]).optional(),
|
||||
url: z.string().url("URL must be a valid URL format"),
|
||||
headers: z.record(z.string()).optional(),
|
||||
// Ensure no stdio fields are present
|
||||
command: z.undefined().optional(),
|
||||
args: z.undefined().optional(),
|
||||
env: z.undefined().optional(),
|
||||
})
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
type: "sse" as const,
|
||||
}))
|
||||
.refine((data) => data.type === undefined || data.type === "sse", { message: typeErrorMessage }),
|
||||
// StreamableHTTP config (has url field)
|
||||
// StreamableHTTP config (has url field, default for URL-based configs)
|
||||
BaseConfigSchema.extend({
|
||||
type: z.enum(["streamable-http"]).optional(),
|
||||
url: z.string().url("URL must be a valid URL format"),
|
||||
|
|
@ -130,6 +115,16 @@ const createServerTypeSchema = () => {
|
|||
.refine((data) => data.type === undefined || data.type === "streamable-http", {
|
||||
message: typeErrorMessage,
|
||||
}),
|
||||
// SSE config (requires explicit type="sse")
|
||||
BaseConfigSchema.extend({
|
||||
type: z.enum(["sse"]),
|
||||
url: z.string().url("URL must be a valid URL format"),
|
||||
headers: z.record(z.string()).optional(),
|
||||
// Ensure no stdio fields are present
|
||||
command: z.undefined().optional(),
|
||||
args: z.undefined().optional(),
|
||||
env: z.undefined().optional(),
|
||||
}).refine((data) => data.type === "sse", { message: typeErrorMessage }),
|
||||
])
|
||||
}
|
||||
|
||||
|
|
@ -209,9 +204,9 @@ export class McpHub {
|
|||
config.type = "stdio"
|
||||
}
|
||||
|
||||
// For url-based configs, type must be provided by the user
|
||||
// For url-based configs, auto-infer type as 'streamable-http' if not provided
|
||||
if (hasUrlFields && !config.type) {
|
||||
throw new Error("Configuration with 'url' must explicitly specify 'type' as 'sse' or 'streamable-http'.")
|
||||
config.type = "streamable-http"
|
||||
}
|
||||
|
||||
// Validate type if provided
|
||||
|
|
@ -1378,8 +1373,8 @@ export class McpHub {
|
|||
await this.deleteConnection(serverName, serverSource)
|
||||
// Re-add as a disabled connection
|
||||
// Re-read config from file to get updated disabled state
|
||||
const updatedConfig = await this.readServerConfigFromFile(serverName, serverSource)
|
||||
await this.connectToServer(serverName, updatedConfig, serverSource)
|
||||
const updatedConfig = await this.readServerConfigFromFile(serverName, serverSource)
|
||||
await this.connectToServer(serverName, updatedConfig, serverSource)
|
||||
} else if (!disabled && connection.server.status === "disconnected") {
|
||||
// If enabling a disabled server, connect it
|
||||
// Re-read config from file to get updated disabled state
|
||||
|
|
|
|||
|
|
@ -2147,4 +2147,94 @@ describe("McpHub", () => {
|
|||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("URL type auto-inference", () => {
|
||||
it("should auto-infer 'streamable-http' type when URL is present without explicit type", () => {
|
||||
const config = {
|
||||
url: "https://api.example.com/mcp",
|
||||
headers: {
|
||||
Authorization: "Bearer token",
|
||||
},
|
||||
}
|
||||
|
||||
const result = ServerConfigSchema.parse(config)
|
||||
expect(result.type).toBe("streamable-http")
|
||||
})
|
||||
|
||||
it("should preserve explicit 'sse' type when specified with URL", () => {
|
||||
const config = {
|
||||
type: "sse",
|
||||
url: "https://api.example.com/mcp",
|
||||
headers: {
|
||||
Authorization: "Bearer token",
|
||||
},
|
||||
}
|
||||
|
||||
const result = ServerConfigSchema.parse(config)
|
||||
expect(result.type).toBe("sse")
|
||||
})
|
||||
|
||||
it("should preserve explicit 'streamable-http' type when specified", () => {
|
||||
const config = {
|
||||
type: "streamable-http",
|
||||
url: "https://api.example.com/mcp",
|
||||
}
|
||||
|
||||
const result = ServerConfigSchema.parse(config)
|
||||
expect(result.type).toBe("streamable-http")
|
||||
})
|
||||
|
||||
it("should maintain backward compatibility for stdio type", () => {
|
||||
const config = {
|
||||
command: "node",
|
||||
args: ["server.js"],
|
||||
}
|
||||
|
||||
const result = ServerConfigSchema.parse(config)
|
||||
expect(result.type).toBe("stdio")
|
||||
})
|
||||
|
||||
it("should work with minimal URL config", () => {
|
||||
const config = {
|
||||
url: "https://docs.langchain.com/mcp",
|
||||
}
|
||||
|
||||
const result = ServerConfigSchema.parse(config)
|
||||
expect(result.type).toBe("streamable-http")
|
||||
})
|
||||
|
||||
it("should auto-infer for GitHub Copilot-like config", () => {
|
||||
const config = {
|
||||
url: "https://api.githubcopilot.com/mcp",
|
||||
headers: {
|
||||
Authorization: "Bearer GITHUB_PAT",
|
||||
},
|
||||
alwaysAllow: ["SearchDocsByLangChain"],
|
||||
}
|
||||
|
||||
const result = ServerConfigSchema.parse(config)
|
||||
expect(result.type).toBe("streamable-http")
|
||||
})
|
||||
|
||||
it("should reject invalid URL", () => {
|
||||
const config = {
|
||||
url: "not-a-url",
|
||||
}
|
||||
|
||||
expect(() => ServerConfigSchema.parse(config)).toThrow("URL must be a valid URL format")
|
||||
})
|
||||
|
||||
it("should reject mixed stdio and URL fields", () => {
|
||||
const config = {
|
||||
command: "node",
|
||||
url: "https://api.example.com/mcp",
|
||||
}
|
||||
|
||||
// This should be caught by the validateServerConfig function
|
||||
const mcpHub = new McpHub(mockProvider as ClineProvider)
|
||||
expect(() => mcpHub["validateServerConfig"](config)).toThrow(
|
||||
"Cannot mix 'stdio' and ('sse' or 'streamable-http') fields",
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue