Configurable mcp servers

This commit is contained in:
Quinlan Jager 2025-05-02 17:14:26 -07:00
parent 2c24084cb0
commit 16c4e622e3
6 changed files with 254 additions and 10 deletions

View file

@ -723,6 +723,18 @@ def get_parser(default_config_files, git_root):
default="platform",
help="Line endings to use when writing files (default: platform)",
)
group.add_argument(
"--mcp-servers",
metavar="MCP_CONFIG_JSON",
help="Specify MCP server configurations as a JSON string",
default=None,
)
group.add_argument(
"--mcp-servers-file",
metavar="MCP_CONFIG_FILE",
help="Specify a file path with MCP server configurations",
default=None,
)
group.add_argument(
"-c",
"--config",

View file

@ -198,6 +198,7 @@ class GUI:
)
for fname in fnames:
print(fname)
if fname not in self.coder.get_inchat_relative_files():
self.coder.add_rel_fname(fname)
self.info(f"Added {fname} to the chat")

View file

@ -29,7 +29,7 @@ from aider.format_settings import format_settings, scrub_sensitive_info
from aider.history import ChatSummary
from aider.io import InputOutput
from aider.llm import litellm # noqa: F401; properly init litellm on launch
from aider.mcp.server import McpServer
from aider.mcp import load_mcp_servers
from aider.models import ModelSettings
from aider.onboarding import offer_openrouter_oauth, select_default_model
from aider.repo import ANY_GIT_ERROR, GitRepo
@ -957,14 +957,11 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
analytics.event("auto_commits", enabled=bool(args.auto_commits))
try:
git_server = McpServer({"name": "git", "command": "uvx", "args": ["mcp-server-git"]})
context_seven_server = McpServer(
{
"name": "context7",
"command": "deno",
"args": ["run", "--allow-net", "npm:@upstash/context7-mcp"],
}
)
# Load MCP servers from config string or file
mcp_servers = load_mcp_servers(args.mcp_servers, args.mcp_servers_file, io, args.verbose)
if not mcp_servers:
mcp_servers = []
coder = Coder.create(
main_model=main_model,
@ -998,7 +995,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F
detect_urls=args.detect_urls,
auto_copy_context=args.copy_paste,
auto_accept_architect=args.auto_accept_architect,
mcp_servers=[context_seven_server, git_server],
mcp_servers=mcp_servers,
)
except UnknownEditFormat as err:
io.tool_error(str(err))

View file

@ -0,0 +1,86 @@
import json
from aider.mcp.server import McpServer
def _parse_mcp_servers_from_json_string(json_string, io, verbose=False):
"""Parse MCP servers from a JSON string."""
servers = []
try:
config = json.loads(json_string)
if verbose:
io.tool_output("Loading MCP servers from provided JSON string")
if "mcpServers" in config:
for name, server_config in config["mcpServers"].items():
if verbose:
io.tool_output(f"Loading MCP server: {name}")
# Create a server config with name included
server_config["name"] = name
servers.append(McpServer(server_config))
if verbose:
io.tool_output(f"Loaded {len(servers)} MCP servers from JSON string")
return servers
else:
io.tool_warning("No 'mcpServers' key found in MCP config JSON string")
except json.JSONDecodeError:
io.tool_error("Invalid JSON in MCP config string")
except Exception as e:
io.tool_error(f"Error loading MCP config from string: {e}")
return servers
def _parse_mcp_servers_from_file(file_path, io, verbose=False):
"""Parse MCP servers from a JSON file."""
servers = []
try:
with open(file_path, "r") as f:
config = json.load(f)
if verbose:
io.tool_output(f"Loading MCP servers from file: {file_path}")
if "mcpServers" in config:
for name, server_config in config["mcpServers"].items():
if verbose:
io.tool_output(f"Loading MCP server: {name}")
# Create a server config with name included
server_config["name"] = name
servers.append(McpServer(server_config))
if verbose:
io.tool_output(f"Loaded {len(servers)} MCP servers from {file_path}")
return servers
else:
io.tool_warning(f"No 'mcpServers' key found in MCP config file: {file_path}")
except FileNotFoundError:
io.tool_warning(f"MCP config file not found: {file_path}")
except json.JSONDecodeError:
io.tool_error(f"Invalid JSON in MCP config file: {file_path}")
except Exception as e:
io.tool_error(f"Error loading MCP config from file: {e}")
return servers
def load_mcp_servers(mcp_servers, mcp_servers_file, io, verbose=False):
"""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)
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)
return servers

View file

@ -0,0 +1,94 @@
---
parent: Configuration
nav_order: 120
description: Configure Model Control Protocol (MCP) servers for enhanced AI capabilities.
---
# Model Control Protocol (MCP)
Model Control Protocol (MCP) servers extend aider's capabilities by providing additional tools and functionality to the AI models. MCP servers can add features like git operations, context retrieval, and other specialized tools.
## Configuring MCP Servers
Aider supports configuring MCP servers using the MCP Server Configuration schema. Please
see the [Model Context Protocol documentation](https://modelcontextprotocol.io/introduction)
for more information.
You have two ways of sharing your MCP server configuration with Aider.
### 1. Using a JSON string
You can specify MCP servers directly on the command line using the `--mcp-servers` option with a JSON string:
```bash
aider --mcp-servers '{"mcpServers":{"git":{"command":"uvx","args":["mcp-server-git"]}}}'
```
### 2. Using a configuration file
Alternatively, you can store your MCP server configurations in a JSON file and reference it with the `--mcp-servers-file` option:
```bash
aider --mcp-servers-file mcp.json
```
Example `mcp.json` file:
```json
{
"mcpServers": {
"git": {
"command": "uvx",
"args": ["mcp-server-git"]
}
}
}
```
## YAML Configuration
You can also configure MCP servers in your `.aider.conf.yml` file:
```yaml
mcp-servers: |
{
"mcpServers": {
"git": {
"command": "uvx",
"args": ["mcp-server-git"]
}
}
}
```
Or specify a configuration file:
```yaml
mcp-servers-file: /path/to/mcp.json
```
These options are configurable in any of Aider's config file formats.
## Environment Variables
You can also configure MCP servers using environment variables in your `.env` file:
```
AIDER_MCP_SERVERS={"mcpServers":{"git":{"command":"uvx","args":["mcp-server-git"]}}}
```
Or specify a configuration file:
```
AIDER_MCP_SERVERS_FILE=/path/to/mcp.json
```
## Troubleshooting
If you encounter issues with MCP servers:
1. Use the `--verbose` flag to see detailed information about MCP server loading
2. Check that the specified executables are installed and available in your PATH
3. Verify that your JSON configuration is valid
For more information about specific MCP servers and their capabilities, refer to their respective documentation.

View file

@ -1345,3 +1345,57 @@ class TestMain(TestCase):
)
for call in mock_io_instance.tool_warning.call_args_list:
self.assertNotIn("Cost estimates may be inaccurate", call[0][0])
@patch("aider.coders.Coder.create")
def test_mcp_servers_parsing(self, mock_coder_create):
# Setup mock coder
mock_coder_instance = MagicMock()
mock_coder_create.return_value = mock_coder_instance
# Test with --mcp-servers option
with GitTemporaryDirectory():
main(
[
"--mcp-servers",
'{"mcpServers":{"git":{"command":"uvx","args":["mcp-server-git"]}}}',
"--exit",
"--yes",
],
input=DummyInput(),
output=DummyOutput(),
)
# Verify that Coder.create was called with mcp_servers parameter
mock_coder_create.assert_called_once()
_, kwargs = mock_coder_create.call_args
self.assertIn("mcp_servers", kwargs)
self.assertIsNotNone(kwargs["mcp_servers"])
# At least one server should be in the list
self.assertTrue(len(kwargs["mcp_servers"]) > 0)
# First server should have a name attribute
self.assertTrue(hasattr(kwargs["mcp_servers"][0], "name"))
# Test with --mcp-servers-file option
mock_coder_create.reset_mock()
with GitTemporaryDirectory():
# Create a temporary MCP servers file
mcp_file = Path("mcp_servers.json")
mcp_content = {"mcpServers": {"git": {"command": "uvx", "args": ["mcp-server-git"]}}}
mcp_file.write_text(json.dumps(mcp_content))
main(
["--mcp-servers-file", str(mcp_file), "--exit", "--yes"],
input=DummyInput(),
output=DummyOutput(),
)
# Verify that Coder.create was called with mcp_servers parameter
mock_coder_create.assert_called_once()
_, kwargs = mock_coder_create.call_args
self.assertIn("mcp_servers", kwargs)
self.assertIsNotNone(kwargs["mcp_servers"])
# At least one server should be in the list
self.assertTrue(len(kwargs["mcp_servers"]) > 0)
# First server should have a name attribute
self.assertTrue(hasattr(kwargs["mcp_servers"][0], "name"))