Bladeren bron

Revert "feat: add sse mcp support"

Matt Rubens 9 maanden geleden
bovenliggende
commit
b29f94ae65

+ 0 - 8
esbuild.js

@@ -72,14 +72,6 @@ const extensionConfig = {
 		copyWasmFiles,
 		/* 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") }
-				})
-			},
-		},
 	],
 	entryPoints: ["src/extension.ts"],
 	format: "cjs",

File diff suppressed because it is too large
+ 32 - 619
package-lock.json


+ 1 - 3
package.json

@@ -317,7 +317,7 @@
 		"@google-cloud/vertexai": "^1.9.3",
 		"@google/generative-ai": "^0.18.0",
 		"@mistralai/mistralai": "^1.3.6",
-		"@modelcontextprotocol/sdk": "^1.7.0",
+		"@modelcontextprotocol/sdk": "^1.0.1",
 		"@types/clone-deep": "^4.0.4",
 		"@types/pdf-parse": "^1.1.4",
 		"@types/tmp": "^0.2.6",
@@ -344,12 +344,10 @@
 		"os-name": "^6.0.0",
 		"p-wait-for": "^5.0.2",
 		"pdf-parse": "^1.1.1",
-		"pkce-challenge": "^4.1.0",
 		"posthog-node": "^4.7.0",
 		"pretty-bytes": "^6.1.1",
 		"puppeteer-chromium-resolver": "^23.0.0",
 		"puppeteer-core": "^23.4.0",
-		"reconnecting-eventsource": "^1.6.4",
 		"serialize-error": "^11.0.3",
 		"simple-git": "^3.27.0",
 		"sound-play": "^1.1.0",

+ 0 - 14
src/__mocks__/@modelcontextprotocol/sdk/client/sse.js

@@ -1,14 +0,0 @@
-class SSEClientTransport {
-	constructor(url, options = {}) {
-		this.url = url
-		this.options = options
-		this.onerror = null
-		this.connect = jest.fn().mockResolvedValue()
-		this.close = jest.fn().mockResolvedValue()
-		this.start = jest.fn().mockResolvedValue()
-	}
-}
-
-module.exports = {
-	SSEClientTransport,
-}

File diff suppressed because it is too large
+ 667 - 374
src/core/prompts/__tests__/__snapshots__/system.test.ts.snap


+ 4 - 73
src/core/prompts/__tests__/system.test.ts

@@ -250,51 +250,6 @@ describe("SYSTEM_PROMPT", () => {
 			true, // enableMcpServerCreation
 		)
 
-		// Verify basic MCP server info is included
-		expect(prompt).toContain("MCP SERVERS")
-		expect(prompt).toContain("The Model Context Protocol (MCP) enables communication")
-
-		// Verify server types are described
-		expect(prompt).toContain("Local (Stdio-based) servers")
-		expect(prompt).toContain("Remote (SSE-based) servers")
-
-		// Verify connected servers section exists
-		expect(prompt).toContain("Connected MCP Servers")
-		expect(prompt).toContain("When a server is connected")
-
-		// Verify server creation info is included
-		expect(prompt).toContain("Creating an MCP Server")
-		expect(prompt).toContain("MCP Server Types and Configuration")
-
-		// Verify configuration examples are included
-		expect(prompt).toContain("Local (Stdio) Server Configuration")
-		expect(prompt).toContain("Remote (SSE) Server Configuration")
-		expect(prompt).toContain("Common configuration options for both types")
-
-		// Verify example server info is included
-		expect(prompt).toContain("Example Local MCP Server")
-		expect(prompt).toContain("create-typescript-server")
-	})
-
-	it("should include MCP server creation info when enabled", async () => {
-		const mockMcpHub = createMockMcpHub()
-
-		const prompt = await SYSTEM_PROMPT(
-			mockContext,
-			"/test/path",
-			false,
-			mockMcpHub,
-			undefined,
-			undefined,
-			defaultModeSlug,
-			undefined,
-			undefined,
-			undefined,
-			undefined,
-			experiments,
-			true,
-		)
-
 		expect(prompt).toMatchSnapshot()
 	})
 
@@ -315,10 +270,7 @@ describe("SYSTEM_PROMPT", () => {
 			true, // enableMcpServerCreation
 		)
 
-		// Should not contain any MCP server related content since mcpHub is undefined
-		expect(prompt).not.toContain("MCP SERVERS")
-		expect(prompt).not.toContain("Connected MCP Servers")
-		expect(prompt).not.toContain("Creating an MCP Server")
+		expect(prompt).toMatchSnapshot()
 	})
 
 	it("should handle different browser viewport sizes", async () => {
@@ -781,18 +733,8 @@ describe("addCustomInstructions", () => {
 			true, // enableMcpServerCreation
 		)
 
-		// Verify server creation info is included
 		expect(prompt).toContain("Creating an MCP Server")
-		expect(prompt).toContain("MCP Server Types and Configuration")
-
-		// Verify configuration examples are included
-		expect(prompt).toContain("Local (Stdio) Server Configuration")
-		expect(prompt).toContain("Remote (SSE) Server Configuration")
-		expect(prompt).toContain("Common configuration options for both types")
-
-		// Verify example server info is included
-		expect(prompt).toContain("Example Local MCP Server")
-		expect(prompt).toContain("create-typescript-server")
+		expect(prompt).toMatchSnapshot()
 	})
 
 	it("should exclude MCP server creation info when disabled", async () => {
@@ -815,23 +757,12 @@ describe("addCustomInstructions", () => {
 		)
 
 		expect(prompt).not.toContain("Creating an MCP Server")
-		expect(prompt).not.toContain("MCP Server Types and Configuration")
-		expect(prompt).toContain("MCP SERVERS")
-		expect(prompt).toContain("Connected MCP Servers")
+		expect(prompt).toMatchSnapshot()
 	})
 
 	it("should prioritize mode-specific rules for code mode", async () => {
 		const instructions = await addCustomInstructions("", "", "/test/path", defaultModeSlug)
-		const rulesSection = instructions.split("\n\n").find((section) => section.startsWith("Rules:"))
-		expect(rulesSection).toBeDefined()
-
-		// Get the rules as separate lines and filter for header lines only
-		const ruleLines = rulesSection?.split("\n").filter((line) => line.startsWith("#")) ?? []
-
-		// Verify we have both rule headers
-		expect(ruleLines).toHaveLength(2)
-		expect(ruleLines[0]).toContain(`Rules from .clinerules-${defaultModeSlug}`)
-		expect(ruleLines[1]).toContain("Rules from .clinerules")
+		expect(instructions).toMatchSnapshot()
 	})
 
 	it("should prioritize mode-specific rules for ask mode", async () => {

+ 4 - 45
src/core/prompts/sections/mcp-servers.ts

@@ -49,10 +49,7 @@ export async function getMcpServersSection(
 
 	const baseSection = `MCP SERVERS
 
-The Model Context Protocol (MCP) enables communication between the system and MCP servers that provide additional tools and resources to extend your capabilities. MCP servers can be one of two types:
-
-1. Local (Stdio-based) servers: These run locally on the user's machine and communicate via standard input/output
-2. Remote (SSE-based) servers: These run on remote machines and communicate via Server-Sent Events (SSE) over HTTP/HTTPS
+The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.
 
 # Connected MCP Servers
 
@@ -74,51 +71,13 @@ The user may ask you something along the lines of "add a tool" that does some fu
 
 When creating MCP servers, it's important to understand that they operate in a non-interactive environment. The server cannot initiate OAuth flows, open browser windows, or prompt for user input during runtime. All credentials and authentication tokens must be provided upfront through environment variables in the MCP settings configuration. For example, Spotify's API uses OAuth to get a refresh token for the user, but the MCP server cannot initiate this flow. While you can walk the user through obtaining an application client ID and secret, you may have to create a separate one-time setup script (like get-refresh-token.js) that captures and logs the final piece of the puzzle: the user's refresh token (i.e. you might run the script using execute_command which would open a browser for authentication, and then log the refresh token so that you can see it in the command output for you to use in the MCP settings configuration).
 
-Unless the user specifies otherwise, new local MCP servers should be created in: ${await mcpHub.getMcpServersPath()}
-
-### MCP Server Types and Configuration
-
-MCP servers can be configured in two ways in the MCP settings file:
-
-1. Local (Stdio) Server Configuration:
-\`\`\`json
-{
-  "mcpServers": {
-    "local-weather": {
-      "command": "node",
-      "args": ["/path/to/weather-server/build/index.js"],
-      "env": {
-        "OPENWEATHER_API_KEY": "your-api-key"
-      }
-    }
-  }
-}
-\`\`\`
-
-2. Remote (SSE) Server Configuration:
-\`\`\`json
-{
-  "mcpServers": {
-    "remote-weather": {
-      "url": "https://api.example.com/mcp",
-      "headers": {
-        "Authorization": "Bearer your-api-key"
-      }
-    }
-  }
-}
-\`\`\`
-
-Common configuration options for both types:
-- \`disabled\`: (optional) Set to true to temporarily disable the server
-- \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60)
-- \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation
+Unless the user specifies otherwise, new MCP servers should be created in: ${await mcpHub.getMcpServersPath()}
 
-### Example Local MCP Server
+### Example MCP Server
 
 For example, if the user wanted to give you the ability to retrieve weather information, you could create an MCP server that uses the OpenWeather API to get weather information, add it to the MCP settings configuration file, and then notice that you now have access to new tools and resources in the system prompt that you might use to show the user your new capabilities.
 
-The following example demonstrates how to build a local MCP server that provides weather data functionality using the Stdio transport. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS)
+The following example demonstrates how to build an MCP server that provides weather data functionality. While this example shows how to implement resources, resource templates, and tools, in practice you should prefer using tools since they are more flexible and can handle dynamic parameters. The resource and resource template implementations are included here mainly for demonstration purposes of the different MCP capabilities, but a real weather server would likely just expose tools for fetching weather data. (The following steps are for macOS)
 
 1. Use the \`create-typescript-server\` tool to bootstrap a new project in the default MCP servers directory:
 

+ 79 - 112
src/services/mcp/McpHub.ts

@@ -1,7 +1,5 @@
 import { Client } from "@modelcontextprotocol/sdk/client/index.js"
 import { StdioClientTransport, StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js"
-import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
-import ReconnectingEventSource from "reconnecting-eventsource"
 import {
 	CallToolResultSchema,
 	ListResourcesResultSchema,
@@ -33,40 +31,23 @@ import { arePathsEqual } from "../../utils/path"
 export type McpConnection = {
 	server: McpServer
 	client: Client
-	transport: StdioClientTransport | SSEClientTransport
+	transport: StdioClientTransport
 }
 
-// Base configuration schema for common settings
-const BaseConfigSchema = z.object({
+// StdioServerParameters
+const AlwaysAllowSchema = z.array(z.string()).default([])
+
+export const StdioConfigSchema = z.object({
+	command: z.string(),
+	args: z.array(z.string()).optional(),
+	env: z.record(z.string()).optional(),
+	alwaysAllow: AlwaysAllowSchema.optional(),
 	disabled: z.boolean().optional(),
 	timeout: z.number().min(1).max(3600).optional().default(60),
-	alwaysAllow: z.array(z.string()).default([]),
 })
 
-// Server configuration schema with automatic type inference
-export const ServerConfigSchema = z.union([
-	// Stdio config (has command field)
-	BaseConfigSchema.extend({
-		command: z.string(),
-		args: z.array(z.string()).optional(),
-		env: z.record(z.string()).optional(),
-	}).transform((data) => ({
-		...data,
-		type: "stdio" as const,
-	})),
-	// SSE config (has url field)
-	BaseConfigSchema.extend({
-		url: z.string().url(),
-		headers: z.record(z.string()).optional(),
-	}).transform((data) => ({
-		...data,
-		type: "sse" as const,
-	})),
-])
-
-// Settings schema
 const McpSettingsSchema = z.object({
-	mcpServers: z.record(ServerConfigSchema),
+	mcpServers: z.record(StdioConfigSchema),
 })
 
 export class McpHub {
@@ -74,7 +55,6 @@ export class McpHub {
 	private disposables: vscode.Disposable[] = []
 	private settingsWatcher?: vscode.FileSystemWatcher
 	private fileWatchers: Map<string, FSWatcher> = new Map()
-	private isDisposed: boolean = false
 	connections: McpConnection[] = []
 	isConnecting: boolean = false
 
@@ -167,11 +147,12 @@ export class McpHub {
 		}
 	}
 
-	private async connectToServer(name: string, config: z.infer<typeof ServerConfigSchema>): Promise<void> {
-		// Remove existing connection if it exists
-		await this.deleteConnection(name)
+	private async connectToServer(name: string, config: StdioServerParameters): 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)
 
 		try {
+			// Each MCP server requires its own transport connection and has unique capabilities, configurations, and error handling. Having separate clients also allows proper scoping of resources/tools and independent server management like reconnection.
 			const client = new Client(
 				{
 					name: "Roo Code",
@@ -182,103 +163,90 @@ export class McpHub {
 				},
 			)
 
-			let transport: StdioClientTransport | SSEClientTransport
-
-			if (config.type === "stdio") {
-				transport = new StdioClientTransport({
-					command: config.command,
-					args: config.args,
-					env: {
-						...config.env,
-						...(process.env.PATH ? { PATH: process.env.PATH } : {}),
-					},
-					stderr: "pipe",
-				})
+			const transport = new StdioClientTransport({
+				command: config.command,
+				args: config.args,
+				env: {
+					...config.env,
+					...(process.env.PATH ? { PATH: process.env.PATH } : {}),
+					// ...(process.env.NODE_PATH ? { NODE_PATH: process.env.NODE_PATH } : {}),
+				},
+				stderr: "pipe", // necessary for stderr to be available
+			})
 
-				// Set up stdio specific error handling
-				transport.onerror = async (error) => {
-					console.error(`Transport error for "${name}":`, error)
-					const connection = this.connections.find((conn) => conn.server.name === name)
-					if (connection) {
-						connection.server.status = "disconnected"
-						this.appendErrorMessage(connection, error.message)
-					}
-					await this.notifyWebviewOfServerChanges()
+			transport.onerror = async (error) => {
+				console.error(`Transport error for "${name}":`, error)
+				const connection = this.connections.find((conn) => conn.server.name === name)
+				if (connection) {
+					connection.server.status = "disconnected"
+					this.appendErrorMessage(connection, error.message)
 				}
+				await this.notifyWebviewOfServerChanges()
+			}
 
-				transport.onclose = async () => {
-					const connection = this.connections.find((conn) => conn.server.name === name)
-					if (connection) {
-						connection.server.status = "disconnected"
-					}
-					await this.notifyWebviewOfServerChanges()
+			transport.onclose = async () => {
+				const connection = this.connections.find((conn) => conn.server.name === name)
+				if (connection) {
+					connection.server.status = "disconnected"
 				}
+				await this.notifyWebviewOfServerChanges()
+			}
 
-				// transport.stderr is only available after the process has been started. However we can't start it separately from the .connect() call because it also starts the transport. And we can't place this after the connect call since we need to capture the stderr stream before the connection is established, in order to capture errors during the connection process.
-				// As a workaround, we start the transport ourselves, and then monkey-patch the start method to no-op so that .connect() doesn't try to start it again.
-				await transport.start()
-				const stderrStream = transport.stderr
-				if (stderrStream) {
-					stderrStream.on("data", async (data: Buffer) => {
-						const errorOutput = data.toString()
-						console.error(`Server "${name}" stderr:`, errorOutput)
-						const connection = this.connections.find((conn) => conn.server.name === name)
-						if (connection) {
-							// NOTE: we do not set server status to "disconnected" because stderr logs do not necessarily mean the server crashed or disconnected, it could just be informational. In fact when the server first starts up, it immediately logs "<name> server running on stdio" to stderr.
-							this.appendErrorMessage(connection, errorOutput)
-							// Only need to update webview right away if it's already disconnected
-							if (connection.server.status === "disconnected") {
-								await this.notifyWebviewOfServerChanges()
-							}
-						}
-					})
-				} else {
-					console.error(`No stderr stream for ${name}`)
-				}
-				transport.start = async () => {} // No-op now, .connect() won't fail
-			} else {
-				// SSE connection
-				const sseOptions = {
-					requestInit: {
-						headers: config.headers,
+			// If the config is invalid, show an error
+			if (!StdioConfigSchema.safeParse(config).success) {
+				console.error(`Invalid config for "${name}": missing or invalid parameters`)
+				const connection: McpConnection = {
+					server: {
+						name,
+						config: JSON.stringify(config),
+						status: "disconnected",
+						error: "Invalid config: missing or invalid parameters",
 					},
+					client,
+					transport,
 				}
-				// Configure ReconnectingEventSource options
-				const reconnectingEventSourceOptions = {
-					max_retry_time: 5000, // Maximum retry time in milliseconds
-					withCredentials: config.headers?.["Authorization"] ? true : false, // Enable credentials if Authorization header exists
-				}
-				global.EventSource = ReconnectingEventSource
-				transport = new SSEClientTransport(new URL(config.url), {
-					...sseOptions,
-					eventSourceInit: reconnectingEventSourceOptions,
-				})
-
-				// Set up SSE specific error handling
-				transport.onerror = async (error) => {
-					console.error(`Transport error for "${name}":`, error)
-					const connection = this.connections.find((conn) => conn.server.name === name)
-					if (connection) {
-						connection.server.status = "disconnected"
-						this.appendErrorMessage(connection, error.message)
-					}
-					await this.notifyWebviewOfServerChanges()
-				}
+				this.connections.push(connection)
+				return
 			}
 
+			// valid schema
+			const parsedConfig = StdioConfigSchema.parse(config)
 			const connection: McpConnection = {
 				server: {
 					name,
 					config: JSON.stringify(config),
 					status: "connecting",
-					disabled: config.disabled,
+					disabled: parsedConfig.disabled,
 				},
 				client,
 				transport,
 			}
 			this.connections.push(connection)
 
-			// Connect (this will automatically start the transport)
+			// transport.stderr is only available after the process has been started. However we can't start it separately from the .connect() call because it also starts the transport. And we can't place this after the connect call since we need to capture the stderr stream before the connection is established, in order to capture errors during the connection process.
+			// As a workaround, we start the transport ourselves, and then monkey-patch the start method to no-op so that .connect() doesn't try to start it again.
+			await transport.start()
+			const stderrStream = transport.stderr
+			if (stderrStream) {
+				stderrStream.on("data", async (data: Buffer) => {
+					const errorOutput = data.toString()
+					console.error(`Server "${name}" stderr:`, errorOutput)
+					const connection = this.connections.find((conn) => conn.server.name === name)
+					if (connection) {
+						// NOTE: we do not set server status to "disconnected" because stderr logs do not necessarily mean the server crashed or disconnected, it could just be informational. In fact when the server first starts up, it immediately logs "<name> server running on stdio" to stderr.
+						this.appendErrorMessage(connection, errorOutput)
+						// Only need to update webview right away if it's already disconnected
+						if (connection.server.status === "disconnected") {
+							await this.notifyWebviewOfServerChanges()
+						}
+					}
+				})
+			} else {
+				console.error(`No stderr stream for ${name}`)
+			}
+			transport.start = async () => {} // No-op now, .connect() won't fail
+
+			// Connect
 			await client.connect(transport)
 			connection.server.status = "connected"
 			connection.server.error = ""
@@ -699,7 +667,7 @@ export class McpHub {
 
 		let timeout: number
 		try {
-			const parsedConfig = ServerConfigSchema.parse(JSON.parse(connection.server.config))
+			const parsedConfig = StdioConfigSchema.parse(JSON.parse(connection.server.config))
 			timeout = (parsedConfig.timeout ?? 60) * 1000
 		} catch (error) {
 			console.error("Failed to parse server config for timeout:", error)
@@ -761,7 +729,6 @@ export class McpHub {
 	}
 
 	async dispose(): Promise<void> {
-		this.isDisposed = true
 		this.removeAllFileWatchers()
 		for (const connection of this.connections) {
 			try {

+ 8 - 19
src/services/mcp/__tests__/McpHub.test.ts

@@ -2,7 +2,7 @@ import type { McpHub as McpHubType } from "../McpHub"
 import type { ClineProvider } from "../../../core/webview/ClineProvider"
 import type { ExtensionContext, Uri } from "vscode"
 import type { McpConnection } from "../McpHub"
-import { ServerConfigSchema } from "../McpHub"
+import { StdioConfigSchema } from "../McpHub"
 
 const fs = require("fs/promises")
 const { McpHub } = require("../McpHub")
@@ -71,7 +71,6 @@ describe("McpHub", () => {
 			JSON.stringify({
 				mcpServers: {
 					"test-server": {
-						type: "stdio",
 						command: "node",
 						args: ["test.js"],
 						alwaysAllow: ["allowed-tool"],
@@ -88,7 +87,6 @@ describe("McpHub", () => {
 			const mockConfig = {
 				mcpServers: {
 					"test-server": {
-						type: "stdio",
 						command: "node",
 						args: ["test.js"],
 						alwaysAllow: [],
@@ -111,7 +109,6 @@ describe("McpHub", () => {
 			const mockConfig = {
 				mcpServers: {
 					"test-server": {
-						type: "stdio",
 						command: "node",
 						args: ["test.js"],
 						alwaysAllow: ["existing-tool"],
@@ -134,7 +131,6 @@ describe("McpHub", () => {
 			const mockConfig = {
 				mcpServers: {
 					"test-server": {
-						type: "stdio",
 						command: "node",
 						args: ["test.js"],
 					},
@@ -159,7 +155,6 @@ describe("McpHub", () => {
 			const mockConfig = {
 				mcpServers: {
 					"test-server": {
-						type: "stdio",
 						command: "node",
 						args: ["test.js"],
 						disabled: false,
@@ -299,21 +294,20 @@ describe("McpHub", () => {
 			it("should validate timeout values", () => {
 				// Test valid timeout values
 				const validConfig = {
-					type: "stdio",
 					command: "test",
 					timeout: 60,
 				}
-				expect(() => ServerConfigSchema.parse(validConfig)).not.toThrow()
+				expect(() => StdioConfigSchema.parse(validConfig)).not.toThrow()
 
 				// Test invalid timeout values
 				const invalidConfigs = [
-					{ type: "stdio", command: "test", timeout: 0 }, // Too low
-					{ type: "stdio", command: "test", timeout: 3601 }, // Too high
-					{ type: "stdio", command: "test", timeout: -1 }, // Negative
+					{ command: "test", timeout: 0 }, // Too low
+					{ command: "test", timeout: 3601 }, // Too high
+					{ command: "test", timeout: -1 }, // Negative
 				]
 
 				invalidConfigs.forEach((config) => {
-					expect(() => ServerConfigSchema.parse(config)).toThrow()
+					expect(() => StdioConfigSchema.parse(config)).toThrow()
 				})
 			})
 
@@ -321,7 +315,7 @@ describe("McpHub", () => {
 				const mockConnection: McpConnection = {
 					server: {
 						name: "test-server",
-						config: JSON.stringify({ type: "stdio", command: "test" }), // No timeout specified
+						config: JSON.stringify({ command: "test" }), // No timeout specified
 						status: "connected",
 					},
 					client: {
@@ -344,7 +338,7 @@ describe("McpHub", () => {
 				const mockConnection: McpConnection = {
 					server: {
 						name: "test-server",
-						config: JSON.stringify({ type: "stdio", command: "test", timeout: 120 }), // 2 minutes
+						config: JSON.stringify({ command: "test", timeout: 120 }), // 2 minutes
 						status: "connected",
 					},
 					client: {
@@ -369,7 +363,6 @@ describe("McpHub", () => {
 				const mockConfig = {
 					mcpServers: {
 						"test-server": {
-							type: "stdio",
 							command: "node",
 							args: ["test.js"],
 							timeout: 60,
@@ -392,7 +385,6 @@ describe("McpHub", () => {
 				const mockConfig = {
 					mcpServers: {
 						"test-server": {
-							type: "stdio",
 							command: "node",
 							args: ["test.js"],
 							timeout: 60,
@@ -414,7 +406,6 @@ describe("McpHub", () => {
 					server: {
 						name: "test-server",
 						config: JSON.stringify({
-							type: "stdio",
 							command: "node",
 							args: ["test.js"],
 							timeout: 3601, // Invalid timeout
@@ -444,7 +435,6 @@ describe("McpHub", () => {
 				const mockConfig = {
 					mcpServers: {
 						"test-server": {
-							type: "stdio",
 							command: "node",
 							args: ["test.js"],
 							timeout: 60,
@@ -468,7 +458,6 @@ describe("McpHub", () => {
 				const mockConfig = {
 					mcpServers: {
 						"test-server": {
-							type: "stdio",
 							command: "node",
 							args: ["test.js"],
 							timeout: 60,

Some files were not shown because too many files changed in this diff