diff --git a/aider/args.py b/aider/args.py index a74a29d29c6..1834c59fc09 100644 --- a/aider/args.py +++ b/aider/args.py @@ -804,6 +804,13 @@ def get_parser(default_config_files, git_root): help="Specify a file path with MCP server configurations", default=None, ) + group.add_argument( + "--mcp-transport", + metavar="MCP_TRANSPORT", + help="Specify the transport for MCP servers (default: stdio)", + default="stdio", + choices=["stdio", "http"], + ) group.add_argument( "-c", "--config", diff --git a/aider/main.py b/aider/main.py index c40b92a764e..6a9ea7175a6 100644 --- a/aider/main.py +++ b/aider/main.py @@ -972,7 +972,9 @@ def get_io(pretty): try: # Load MCP servers from config string or file - mcp_servers = load_mcp_servers(args.mcp_servers, args.mcp_servers_file, io, args.verbose) + mcp_servers = load_mcp_servers( + args.mcp_servers, args.mcp_servers_file, io, args.verbose, args.mcp_transport + ) if not mcp_servers: mcp_servers = [] diff --git a/aider/mcp/__init__.py b/aider/mcp/__init__.py index 636f8daf7b0..0fcf3f003d3 100644 --- a/aider/mcp/__init__.py +++ b/aider/mcp/__init__.py @@ -1,9 +1,9 @@ import json -from aider.mcp.server import McpServer +from aider.mcp.server import McpServer, HttpStreamingServer -def _parse_mcp_servers_from_json_string(json_string, io, verbose=False): +def _parse_mcp_servers_from_json_string(json_string, io, verbose=False, mcp_transport="stdio"): """Parse MCP servers from a JSON string.""" servers = [] @@ -19,7 +19,11 @@ def _parse_mcp_servers_from_json_string(json_string, io, verbose=False): # Create a server config with name included server_config["name"] = name - servers.append(McpServer(server_config)) + transport = server_config.get("transport", mcp_transport) + if transport == "stdio": + servers.append(McpServer(server_config)) + elif transport == "http": + servers.append(HttpStreamingServer(server_config)) if verbose: io.tool_output(f"Loaded {len(servers)} MCP servers from JSON string") @@ -34,7 +38,7 @@ def _parse_mcp_servers_from_json_string(json_string, io, verbose=False): return servers -def _parse_mcp_servers_from_file(file_path, io, verbose=False): +def _parse_mcp_servers_from_file(file_path, io, verbose=False, mcp_transport="stdio"): """Parse MCP servers from a JSON file.""" servers = [] @@ -52,7 +56,11 @@ def _parse_mcp_servers_from_file(file_path, io, verbose=False): # Create a server config with name included server_config["name"] = name - servers.append(McpServer(server_config)) + transport = server_config.get("transport", mcp_transport) + if transport == "stdio": + servers.append(McpServer(server_config)) + elif transport == "http": + servers.append(HttpStreamingServer(server_config)) if verbose: io.tool_output(f"Loaded {len(servers)} MCP servers from {file_path}") @@ -69,18 +77,18 @@ def _parse_mcp_servers_from_file(file_path, io, verbose=False): return servers -def load_mcp_servers(mcp_servers, mcp_servers_file, io, verbose=False): +def load_mcp_servers(mcp_servers, mcp_servers_file, io, verbose=False, mcp_transport="stdio"): """Load MCP servers from a JSON string or file.""" servers = [] # First try to load from the JSON string (preferred) if mcp_servers: - servers = _parse_mcp_servers_from_json_string(mcp_servers, io, verbose) + servers = _parse_mcp_servers_from_json_string(mcp_servers, io, verbose, mcp_transport) if servers: return servers # If JSON string failed or wasn't provided, try the file if mcp_servers_file: - servers = _parse_mcp_servers_from_file(mcp_servers_file, io, verbose) + servers = _parse_mcp_servers_from_file(mcp_servers_file, io, verbose, mcp_transport) return servers diff --git a/aider/mcp/server.py b/aider/mcp/server.py index 9f21453f29d..a3e868756d1 100644 --- a/aider/mcp/server.py +++ b/aider/mcp/server.py @@ -5,6 +5,7 @@ from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client +from mcp.client.streamable_http import streamablehttp_client class McpServer: @@ -74,3 +75,25 @@ async def disconnect(self): self.stdio_context = None except Exception as e: logging.error(f"Error during cleanup of server {self.name}: {e}") + +class HttpStreamingServer(McpServer): + async def connect(self): + if self.session is not None: + logging.info(f"Using existing session for MCP server: {self.name}") + return self.session + + logging.info(f"Establishing new connection to MCP server: {self.name}") + try: + url = self.config["url"] + http_transport = await self.exit_stack.enter_async_context(streamablehttp_client(url)) + read, write, _ = http_transport + + session = await self.exit_stack.enter_async_context(ClientSession(read, write)) + await session.initialize() + self.session = session + return session + except Exception as e: + logging.error(f"Error initializing server {self.name}: {e}") + await self.disconnect() + raise + diff --git a/aider/website/docs/config/mcp.md b/aider/website/docs/config/mcp.md index 2d49227bbe4..a6d698c294b 100644 --- a/aider/website/docs/config/mcp.md +++ b/aider/website/docs/config/mcp.md @@ -17,7 +17,8 @@ for more information. You have two ways of sharing your MCP server configuration with Aider. {: .note } -Today, Aider only supports connecting to MCP servers using the stdio transport + +> Today, Aider supports connecting to MCP servers using stdio and http transports. ### Config Files @@ -28,8 +29,8 @@ mcp-servers: | { "mcpServers": { "git": { - "command": "uvx", - "args": ["mcp-server-git"] + "transport": "http", + "url": "http://localhost:8000" } } } @@ -50,7 +51,7 @@ You can specify MCP servers directly on the command line using the `--mcp-server #### Using a JSON String ```bash -aider --mcp-servers '{"mcpServers":{"git":{"command":"uvx","args":["mcp-server-git"]}}}' +aider --mcp-servers '{"mcpServers":{"git":{"transport":"http","url":"http://localhost:8000"}}}' ``` #### Using a configuration file @@ -61,6 +62,14 @@ Alternatively, you can store your MCP server configurations in a JSON file and r aider --mcp-servers-file mcp.json ``` +#### Specifying the transport + +You can use the `--mcp-transport` flag to specify the transport for all configured MCP servers that do not have a transport specified. + +```bash +aider --mcp-transport http +``` + ### Environment Variables You can also configure MCP servers using environment variables in your `.env` file: diff --git a/requirements.txt b/requirements.txt index 4eede2ee315..fa8d5f87dcd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -239,7 +239,7 @@ mccabe==0.7.0 # via # -c requirements/common-constraints.txt # flake8 -mcp==1.6.0 +mcp==1.12.3 # via # -c requirements/common-constraints.txt # -r requirements/requirements.in diff --git a/requirements/common-constraints.txt b/requirements/common-constraints.txt index 65f56ba0066..fac2ceebf71 100644 --- a/requirements/common-constraints.txt +++ b/requirements/common-constraints.txt @@ -262,7 +262,7 @@ matplotlib==3.10.3 # via -r requirements/requirements-dev.in mccabe==0.7.0 # via flake8 -mcp==1.6.0 +mcp==1.12.3 # via -r requirements/requirements.in mdurl==0.1.2 # via markdown-it-py diff --git a/requirements/requirements.in b/requirements/requirements.in index c83edc31d57..9e0a6234721 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -30,7 +30,7 @@ pillow shtab oslex google-generativeai -mcp>=1.0.0 +mcp==1.12.3 # The proper dependency is networkx[default], but this brings # in matplotlib and a bunch of other deps