aider icon indicating copy to clipboard operation
aider copied to clipboard

Add MCP Support with LiteLLM

Open quinlanjager opened this issue 7 months ago • 74 comments

Overview

Related: #2525

This PR integrates Model Context Protocol (MCP) servers with Aider using LiteLLM's MCP bridge implementation. Server tools are provided to theunderlying model by Coder so all Coders will have access to them.

Configuration follows the standard MCP Server Configuration JSON schema used by Claude and Cursor. All server operations are ran on the main thread with coroutines. Coders will execute requests to multiple servers concurrently.

Configuration

MCP servers can be configured in multiple ways:

  1. Command Line: Directly specify server configurations as a JSON string:

    aider --mcp-servers '{"mcpServers":{"git":{"command":"uvx","args":["mcp-server-git"]}}}'
    
  2. Config File: Use a separate JSON configuration file:

    aider --mcp-servers-file mcp.json
    
  3. YAML Config: Add to your .aider.conf.yml:

    mcp-servers: |
      {
        "mcpServers": {
          "git": {
            "command": "uvx",
            "args": ["mcp-server-git"]
          }
        }
      }
    
    # mcp-servers-file: /path/to/mcp.json
    

Implementation Details

The integration leverages LiteLLM's experimental_mcp_client module to load tools from configured servers and provide them to OpenAI compatible models.

The McpServer class is used to manage stdio transport connections via the Python MCP SDK.

The Coder class has been extended to initialize and use MCP tools, process tool calls in streaming responses, and execute tools concurrently across multiple servers. While generating a single reply, at most 25 tool calls can be made.

Limitations

Currently stdio is the only supported server transport. This was a scoping decision. The Python MCP SDK has an SSE server transport so implementation should be possible if desired.

It would be nice if we maintained persistent server connections throughout Aider's runtime. Currently, connections only exist for the duration of each request. I've been using this quite a bit and it is reasonably fast but I admit it is not ideal. Implementing context management at the top level using the with statement would provide a more efficient approach to connection handling.

quinlanjager avatar May 03 '25 07:05 quinlanjager

CLA assistant check
All committers have signed the CLA.

CLAassistant avatar May 03 '25 07:05 CLAassistant

I've been testing this PR, and overall it seems to be working quite well. Great job on this!

However, while testing, I encountered an error message: container not running: No such process when using mcpServers added via containers, configured like this:

 "mcpServers": {
   "sequentialthinking": {
     "command": "docker",
     "args": [
       "run",
       "--rm",
       "-i",
       "mcp/sequentialthinking"
     ]
   }
 }

I'm not entirely sure if this is expected behavior for this specific configuration, or if I might have missed something in the setup?

Additionally, I was also wondering about the feasibility of running aider itself within a container, and using containerized mcpServers.

imunique-ZJ avatar May 04 '25 12:05 imunique-ZJ

@imunique-ZJ Thank you for giving this a spin! 😄 I tested your config outside of a docker container. I was able to start and connect to the server. It sounds like you're running the project in a Docker container which might be the problem.

When using the MCP SDK's STDIO transport, Aider is responsible for starting and stopping the server processes. This means Aider has to have access to the executables and dependencies needed to start the server (in this case the docker process). So if you're running Aider inside of a Docker container and want to use MCP tools started with Docker you will have to configure some "Docker-in-Docker" solution. I think this might be beyond the scope of this PR though.

quinlanjager avatar May 04 '25 14:05 quinlanjager

Thanks for the detailed explanation and for testing this out!

I also tested outside of a container environment, and I did occasionally encounter the same error. However, it didn't seem to affect the core functionality, and I was still able to get the expected results. So, it's not a blocking issue for me. 👍

Regarding the Docker-in-Docker or Podman-in-Podman approach, since it would be outside the scope of this particular PR, so I won't go into further discussion here.

Thanks again.

imunique-ZJ avatar May 05 '25 00:05 imunique-ZJ

I updated the implementation to allow partial (or total) failure when initializing MCP servers. Even if a user's configured MCP servers fail to initialize we should allow the user to continue using Aider but let them know something has gone wrong.

Partial Failure

Screenshot 2025-05-04 at 11 03 11 PM

Total Failure

Screenshot 2025-05-04 at 11 03 54 PM

Multiple Total Failures

Screenshot 2025-05-04 at 11 09 20 PM

quinlanjager avatar May 05 '25 06:05 quinlanjager

I apologize if I'm missing something obvious, but have you been able to test this using local LLMs? If so, could you describe your setup please?

I tried using Qwen3 30B-A3B (which has good agentic support) with both Llama.cpp (using --jinga in LCPP and stream=false in Aider since streaming isn't supported) and also with Ollama, wasn't having success. I quite possibly have something set up incorrectly.

With Llama.cpp, Qwen3-30B-A3B, --jinga flag, aider --verbose:

Loading MCP servers from file: /home/<user>/.aider/mcp.json
Loading MCP server: git
Loaded 1 MCP servers from /home/<user>/.aider/mcp.json
[...]
MCP servers configured:
  - git
    - git_status: Shows the working tree status
    [...]

Testing with: Are there any uncommited changes in the repo?

USER Are there any uncommited changes in the repo?
kwargs:
{
    "model": "openai/qwen3-30b-tools",    
    "stream": false,
    "tools": [
        {
            "type": "function",
            "function": {
                "name": "git_status",
                "description": "Shows the working tree status",
                [...]
ModelResponse(id='chatcmpl-88kLkZ5RRgnRYXsRHA0DaT75tvMval0J', created=1746473202, model='qwen3-30b-tools', object='chat.completion', [...] choices=[Choices(finish_reason='tool_calls', index=0, message=Message(content=None, role='assistant', tool_calls=[ChatCompletionMessageToolCall(function=Function(arguments='{"repo_path":"."}', name='git_status'), id='4BNEfmO4MmbLrLxTlEl1g4fnjWVXiJTn', type='function')], function_call=None, provider_specific_fields={'refusal': None}))], usage=Usage(completion_tokens=22, prompt_tokens=2640, total_tokens=2662, completion_tokens_details=None, prompt_tokens_details=None), service_tier=None, timings={'prompt_n': 2633, 'prompt_ms': 1689.764, 'prompt_per_token_ms': 0.6417637675655146, 'prompt_per_second': 1558.2057612779063, 'predicted_n': 22, 'predicted_ms': 281.688, 'predicted_per_token_ms': 12.804, 'predicted_per_second': 78.1005935645111})

Empty response received from LLM. Check your provider account?

It looks like it's attempting to send a tool_call for git_status with arg repo_path: ".", which seems as expected. tool_calls=[ChatCompletionMessageToolCall(function=Function(arguments='{"repo_path":"."}', name='git_status')

But Aider tells me Empty response received from LLM..

For completeness, without the --jinga flag I get from Aider:

litellm.APIError: APIError: OpenAIException - tools param requires --jinja flag
Retrying in 1.0 seconds...

Using Ollama with model: "ollama_chat/qwen3:30b-a3b" looks similar:

USER Are there any uncommited changes in the repo?
kwargs:
{
    "model": "ollama_chat/qwen3:30b-a3b",
    "stream": false,
    "tools": [
        {
            "type": "function",
            "function": {
                "name": "git_status",
                "description": "Shows the working tree status",
                [...]
ModelResponse(id='chatcmpl-47db188f-b040-4bcb-b405-26da9aff3e86', created=1746475327, model='ollama_chat/qwen3:30b-a3b', object='chat.completion', [...], choices=[Choices(finish_reason='stop', index=0, message=Message(content='', role='assistant', tool_calls=[ChatCompletionMessageToolCall(function=Function(arguments='{"repo_path": "."}', name='git_status'), id='84f31a11-f671-43e3-872e-75f7297c9b1f', type='function')], function_call=None, provider_specific_fields=None))], usage=Usage(completion_tokens=280, prompt_tokens=8913, total_tokens=9193, completion_tokens_details=None, prompt_tokens_details=None))

Empty response received from LLM. Check your provider account?

I still see:

tool_calls=[ChatCompletionMessageToolCall(function=Function(arguments='{"repo_path": "."}', name='git_status')

But Empty response received from LLM. Check your provider account? from Aider.

strawberrymelonpanda avatar May 05 '25 19:05 strawberrymelonpanda

fyi @ishaan-jaff & @krrishdholakia i think you might think this is cool.

aider is a perfect test bed to put the litellm mcp bridge to work!

rawwerks avatar May 05 '25 20:05 rawwerks

Looks great, let us know if there's any way we can improve the litellm mcp bridge

ishaan-jaff avatar May 05 '25 20:05 ishaan-jaff

@strawberrymelonpanda I ran into the same problem. I looked at the LiteLLM repo and there seems to be an issue tool calling with Ollama models (https://github.com/BerriAI/litellm/issues/7570). I'm not 100% sure this is the exact problem that is happening behind the scenes, but it does look like the tools are being passed to the completion. So it is feeling like this might be related. I was also able to get tool calling working with Ollama with these settings based on this suggestion:

env OPENAI_API_BASE=<ollama-base-url>/v1 aider --model openai/<ollama-model>

This option does appear to have some limitations though so YMMV.

quinlanjager avatar May 05 '25 21:05 quinlanjager

@quinlanjager Thanks for the pointers, that indeed got it moving.

For anyone following along, I changed my .aider.conf.yaml to:

openai-api-base: "http://127.0.0.1:11434/v1"
model: "openai/qwen3:30b-a3b"

A few notes:

  1. The CTX issue is annoying, but not that big of a problem since you can change it in other ways. Ollama recently changed the default to 4096 "with plans to increase it further", but that's still way too low IMO. The bigger issue is I don't normally use Ollama, and would love to know what's going on on the Llama.CPP side here. Llama.CPP normally works fine with Aider.

@ishaan-jaff, since you're in the thread, any ideas what's happening here? Is the Llama.CPP tool-use --jinga flag incompatible with the LiteLLM MCP Bridge?

  1. Back to Ollama, it does indeed work now but it's not a great user experience as is with the messaging. I'm still seeing Empty response received from LLM. Check your provider account? in yellow even when it proceeds to work just fine:
architect> Are there any uncommited changes?

Empty response received from LLM. Check your provider account?

Tokens: 288 sent, 0 received.
Running MCP tool: git_status from server git
Tool arguments: {"repo_path":"."}

There are no uncommitted changes in the repository. The working tree is clean, and all modifications are either staged or already committed. Let me know if you need further assistance!

Tokens: 283 sent, 112 received.
MCP servers configured:
  - git

It's probably worth trying to find a way to suppress this message.

  1. I've noticed there's no opportunity for user approval.
> Add uncommited changes to git and commit them in single-task commits.

Empty response received from LLM. Check your provider account?

Tokens: 295 sent, 0 received.
Running MCP tool: git_status from server git
Tool arguments: {"repo_path":"."}

Running MCP tool: git_diff_unstaged from server git
Tool arguments: {"repo_path":"."}

Empty response received from LLM. Check your provider account?

Tokens: 619 sent, 0 received.
Running MCP tool: git_add from server git
Tool arguments: {"files":["test"],"repo_path":"."}

Running MCP tool: git_commit from server git
Tool arguments: {"message":"Stage changes to test","repo_path":"."}

Everything worked and there's a new commit as expected, but as a user without always-yes flags set, I'd really want the opportunity to review the MCP tool calls before they're executed.

MCP is a must-have for Aider so thanks for this!

strawberrymelonpanda avatar May 05 '25 22:05 strawberrymelonpanda

Some other thoughts: I'll also mention that I'd love some new / commands specific to MCP if it's at all possible.
MCP-CLI has some good examples, but I think some of the most important:

/tools - Probably the same list a user gets at startup with the --verbose flag set:

MCP servers configured:
  - git
    - git_status: Shows the working tree status
    - git_diff_unstaged: Shows changes in the working directory that are not yet staged
    [...]

/servers - Add or disable MCP servers on the fly once Aider is started?

Finally, granular MCP endpoint support at a config level, as some MCP clients have. E.g. for mcp-server-git I maybe want to enable git_status but not git_commit.

Mostly just nice-to-haves and food for thought.

strawberrymelonpanda avatar May 05 '25 22:05 strawberrymelonpanda

@strawberrymelonpanda Thanks for the feedback. I think you're right that respecting the --always-yes config is necessary. I can include that in this PR to round out the feature. I'll start with asking for confirmation before using any tools (like running a shell command).

As for the warning message, this is happening because some models return "None" content with a tool call (rather than a string like "I'll use tools to find this out for you"). It makes sense to skip this warning if there are tool calls. I'll update my PR to include this.

I feel additional Aider commands are beyond the scope of this PR (I think these would be good features to include in a follow up though). I want to focus this PR on the fundamental configuration and execution of MCP tools. Anything building on top of this platform, I'd prefer to leave for follow ups as they benefit from their own discussions. Though these are great suggestions. I actually really like granular tool config.

quinlanjager avatar May 05 '25 22:05 quinlanjager

@strawberrymelonpanda I pushed confirmation support and removed that warning message if there are tool calls. Here's a screenshot with both features.

Screenshot 2025-05-06 at 8 22 04 AM

quinlanjager avatar May 06 '25 15:05 quinlanjager

The warning is gone and the confirmation is nice, however I wonder if it has somehow decreased performance? I noticed in your screenshot you say "Use your tools." I too had to say "use tools" otherwise it would not, where as it did automatically previously.

I tried a sample prompt I used yesterday, "Review changes and make single-task commits." First it just told me it needed access to the files. Then with "use tools" added to the prompt, after asking for git status permission, it stopped with a long thinking that included:

"the user's current request is to make single-task commits. So perhaps I should suggest adding the modified files, committing them, and handling the untracked file. However, the user's instructions say to stop and wait for approval after suggesting files."

It was on the right track but now seems to hesitate to continue, where as yesterday it scanned the repo, added the file, and made the commit in one go. Ideally, it'd work the same unless I give it a "no" response to a permission request.

strawberrymelonpanda avatar May 06 '25 17:05 strawberrymelonpanda

Perhaps the changes in this recent commit have caused the behavior changes.

strawberrymelonpanda avatar May 06 '25 17:05 strawberrymelonpanda

@strawberrymelonpanda Good find... I removed final_reminders and the LLM seems to more keen to use tools. I'll look at tweaking that prompt to encourage tool use.

quinlanjager avatar May 06 '25 18:05 quinlanjager

@quinlanjager Yep, seems improved I think. A quick test still needed a hint to use tools, which is fine, and then it followed up by using multiple tools as needed. I think it used a few in a strange order, but that's probably down to local model performance issues. The core concept seemed correct.

For what it's worth just so it doesn't get lost in the feedback, I really appreciate these changes and think Aider can strongly benefit. I would absolutely recommend this be merged in ASAP so more people can test and use it, and I think this approach makes sense to outsource much of the complexity to LiteLLM.

@paul-gauthier What are your thoughts on this approach?

strawberrymelonpanda avatar May 06 '25 19:05 strawberrymelonpanda

I've tried this PR and it works quite well. Hope this will be merged soon.

twinity1 avatar May 20 '25 17:05 twinity1

Hey aider admins, can we get a merge on this?

AidolonSystems avatar May 20 '25 20:05 AidolonSystems

@paul-gauthier

morandalex avatar May 25 '25 09:05 morandalex

Patiently waiting for this. In our codebase, we use code generators and keep a local MD files with our docs. Unfortunately, given the size and non-standard nature of our targets, llms often fall short of simple tasks that require new packages because the context needed to get it right is too large. The ability to rag our docs would also be a game changer in enabling junior devs to traverse the codebase with aider better. MCP support will soon become a deal-breaker for using aider, especially given cursor, etc already support it for quite some time. Please don't let open source stay behind! @paul-gauthier

cloudpresser avatar May 25 '25 14:05 cloudpresser

I pulled the latest commit and got the following error. It might be the merge conflicts with 'main' branch? image

imunique-ZJ avatar May 27 '25 07:05 imunique-ZJ

I think this change causes the error

https://github.com/Aider-AI/aider/pull/3937/commits/67595d2e8e811a789508053846d8359d81918d54#diff-9f52ef5aacb0c6fd78d0deeb48adfca1ba2d6c9ac09183b314536dfa9b085f1eR897

derui avatar May 27 '25 09:05 derui

MCP support will soon become a deal-breaker for using aider

Sadly, it already has for me. I've been using Roocode more often, but I'm watching this PR with interest. The number of unmerged PRs with good, useful changes is also concerning.

strawberrymelonpanda avatar May 27 '25 15:05 strawberrymelonpanda

I think this change causes the error

67595d2#diff-9f52ef5aacb0c6fd78d0deeb48adfca1ba2d6c9ac09183b314536dfa9b085f1eR897

Yes, this merge into the pull request removes the tools=None from def send_completion(self, messages, functions, stream, temperature=None, tools=None): https://github.com/Aider-AI/aider/pull/3937/commits/67595d2e8e811a789508053846d8359d81918d54#diff-9f52ef5aacb0c6fd78d0deeb48adfca1ba2d6c9ac09183b314536dfa9b085f1e

gyathaar avatar May 28 '25 07:05 gyathaar

Thx @quinlanjager for this.

It makes sense to keep this a bit minimal when aiming for a merge but some behaviors are a bit clunky for daily use.

I attempted to improve some aspects in that regard but I'm not a python dev, so aider wrote almost everything. No doubt some of that is hot garbage but it seems to do the job anyway. No big changes from your PR besides profile (server groups) management, less logging and per server no_confirm config.

img-2025-05-28-182453

https://github.com/arosov/aider/tree/mcp

Keeping this in my fork for now, feel free to PR or issues if necessary.

EDIT: Forgot to mention I also tweaked the prompt a bit, hopefully for the best (previously coudn't list its own tools reliably, felt weird).

Forgot to show TUI change on tool call img-2025-05-29-212622

Just pushed:

  • Enable/disable tool toggle
  • Enable a profile as default (no need to run /mcp enable on every session)

img-2025-05-29-211817

arosov avatar May 28 '25 16:05 arosov

Can we merge this? or not yet?

supastishn avatar Jun 13 '25 08:06 supastishn

wen merge?

morandalex avatar Jun 13 '25 09:06 morandalex

@paul-gauthier

morandalex avatar Jun 13 '25 09:06 morandalex

we have a lot of feddback on this, it seems having high priority image

morandalex avatar Jun 13 '25 09:06 morandalex