From 2cded9cbf1714a345d96b1470897483023b61fd9 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Sun, 3 Aug 2025 07:12:12 +0000 Subject: [PATCH 1/3] LLM Generate Implementation --- aider/args.py | 7 +++++ aider/main.py | 4 ++- aider/mcp/__init__.py | 24 ++++++++++------ aider/mcp/server.py | 49 ++++++++++++++++++++++++++++++++ aider/website/docs/config/mcp.md | 17 ++++++++--- 5 files changed, 88 insertions(+), 13 deletions(-) diff --git a/aider/args.py b/aider/args.py index a74a29d29c6..d93b5d34117 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-streaming"], + ) 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..e8589263b68 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-streaming": + 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-streaming": + 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..ef971eae99f 100644 --- a/aider/mcp/server.py +++ b/aider/mcp/server.py @@ -3,6 +3,7 @@ import os from contextlib import AsyncExitStack +import aiohttp from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client @@ -74,3 +75,51 @@ 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): + def __init__(self, server_config): + super().__init__(server_config) + self.aiohttp_session = None + + 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}") + + self.aiohttp_session = aiohttp.ClientSession() + await self.exit_stack.enter_async_context(self.aiohttp_session) + + try: + url = self.config["url"] + response = await self.aiohttp_session.post(url, data=b"") + response.raise_for_status() + + async def read_gen(): + async for chunk in response.content.iter_any(): + yield chunk + + read = read_gen() + + async def write(data): + await self.aiohttp_session.post(url, data=data) + + self.session = await self.exit_stack.enter_async_context(ClientSession(read, write)) + await self.session.initialize() + return self.session + except Exception as e: + logging.error(f"Error initializing server {self.name}: {e}") + await self.disconnect() + raise + + async def disconnect(self): + """Disconnect from the MCP server and clean up resources.""" + async with self._cleanup_lock: + try: + if self.aiohttp_session: + await self.aiohttp_session.close() + self.aiohttp_session = None + await super().disconnect() + except Exception as e: + logging.error(f"Error during cleanup of server {self.name}: {e}") diff --git a/aider/website/docs/config/mcp.md b/aider/website/docs/config/mcp.md index 2d49227bbe4..7e0a35d55f5 100644 --- a/aider/website/docs/config/mcp.md +++ b/aider/website/docs/config/mcp.md @@ -17,7 +17,7 @@ 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-streaming transports. ### Config Files @@ -28,8 +28,8 @@ mcp-servers: | { "mcpServers": { "git": { - "command": "uvx", - "args": ["mcp-server-git"] + "transport": "http-streaming", + "url": "http://localhost:8000" } } } @@ -50,7 +50,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-streaming","url":"http://localhost:8000"}}}' ``` #### Using a configuration file @@ -61,6 +61,15 @@ 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-streaming +``` + + ### Environment Variables You can also configure MCP servers using environment variables in your `.env` file: From ff22bb01134ca90140434d130380b78948956935 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Sun, 3 Aug 2025 13:58:06 +0000 Subject: [PATCH 2/3] Remove unnecessary disconnect in http transpaort --- aider/mcp/server.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/aider/mcp/server.py b/aider/mcp/server.py index ef971eae99f..a76f04734b5 100644 --- a/aider/mcp/server.py +++ b/aider/mcp/server.py @@ -113,13 +113,3 @@ async def write(data): await self.disconnect() raise - async def disconnect(self): - """Disconnect from the MCP server and clean up resources.""" - async with self._cleanup_lock: - try: - if self.aiohttp_session: - await self.aiohttp_session.close() - self.aiohttp_session = None - await super().disconnect() - except Exception as e: - logging.error(f"Error during cleanup of server {self.name}: {e}") From a91ee1c03627a31093364fd2a09e654781b1b879 Mon Sep 17 00:00:00 2001 From: Dustin Washington Date: Sun, 3 Aug 2025 15:10:22 +0000 Subject: [PATCH 3/3] Get http transport working by updating the version of the mcp depedency and reading their guidance on how to set up a client --- aider/args.py | 2 +- aider/mcp/__init__.py | 4 ++-- aider/mcp/server.py | 32 ++++++++--------------------- aider/website/docs/config/mcp.md | 10 ++++----- requirements.txt | 2 +- requirements/common-constraints.txt | 2 +- requirements/requirements.in | 2 +- 7 files changed, 19 insertions(+), 35 deletions(-) diff --git a/aider/args.py b/aider/args.py index d93b5d34117..1834c59fc09 100644 --- a/aider/args.py +++ b/aider/args.py @@ -809,7 +809,7 @@ def get_parser(default_config_files, git_root): metavar="MCP_TRANSPORT", help="Specify the transport for MCP servers (default: stdio)", default="stdio", - choices=["stdio", "http-streaming"], + choices=["stdio", "http"], ) group.add_argument( "-c", diff --git a/aider/mcp/__init__.py b/aider/mcp/__init__.py index e8589263b68..0fcf3f003d3 100644 --- a/aider/mcp/__init__.py +++ b/aider/mcp/__init__.py @@ -22,7 +22,7 @@ def _parse_mcp_servers_from_json_string(json_string, io, verbose=False, mcp_tran transport = server_config.get("transport", mcp_transport) if transport == "stdio": servers.append(McpServer(server_config)) - elif transport == "http-streaming": + elif transport == "http": servers.append(HttpStreamingServer(server_config)) if verbose: @@ -59,7 +59,7 @@ def _parse_mcp_servers_from_file(file_path, io, verbose=False, mcp_transport="st transport = server_config.get("transport", mcp_transport) if transport == "stdio": servers.append(McpServer(server_config)) - elif transport == "http-streaming": + elif transport == "http": servers.append(HttpStreamingServer(server_config)) if verbose: diff --git a/aider/mcp/server.py b/aider/mcp/server.py index a76f04734b5..a3e868756d1 100644 --- a/aider/mcp/server.py +++ b/aider/mcp/server.py @@ -3,9 +3,9 @@ import os from contextlib import AsyncExitStack -import aiohttp from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client +from mcp.client.streamable_http import streamablehttp_client class McpServer: @@ -77,37 +77,21 @@ async def disconnect(self): logging.error(f"Error during cleanup of server {self.name}: {e}") class HttpStreamingServer(McpServer): - def __init__(self, server_config): - super().__init__(server_config) - self.aiohttp_session = None - 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}") - - self.aiohttp_session = aiohttp.ClientSession() - await self.exit_stack.enter_async_context(self.aiohttp_session) - try: url = self.config["url"] - response = await self.aiohttp_session.post(url, data=b"") - response.raise_for_status() - - async def read_gen(): - async for chunk in response.content.iter_any(): - yield chunk - - read = read_gen() - - async def write(data): - await self.aiohttp_session.post(url, data=data) - - self.session = await self.exit_stack.enter_async_context(ClientSession(read, write)) - await self.session.initialize() - return self.session + 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() diff --git a/aider/website/docs/config/mcp.md b/aider/website/docs/config/mcp.md index 7e0a35d55f5..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 supports connecting to MCP servers using stdio and http-streaming transports. + +> Today, Aider supports connecting to MCP servers using stdio and http transports. ### Config Files @@ -28,7 +29,7 @@ mcp-servers: | { "mcpServers": { "git": { - "transport": "http-streaming", + "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":{"transport":"http-streaming","url":"http://localhost:8000"}}}' +aider --mcp-servers '{"mcpServers":{"git":{"transport":"http","url":"http://localhost:8000"}}}' ``` #### Using a configuration file @@ -66,10 +67,9 @@ aider --mcp-servers-file mcp.json 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-streaming +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