Skip to content

Conversation

@tiborvass
Copy link
Contributor

@tiborvass tiborvass commented Mar 21, 2025

This introduces a new dagger mcp command that starts an MCP stdio server

Usage

dagger mcp or dagger mcp -m ref, where the ref is a path to a local module or a remote one

Implementation

In this version, each dagger function corresponds to an MCP tool.

When a function is called, the list of available tools is refreshed. This allows the LLM to handle objects it didn't know previously. Such is the case for a function returning a dagger.Container.

The MCP server lives on the engine side, and the dagger mcp command simply forwards the stdio pipes to it using a new Pipe session attachable.

Integrating this command with your IDE / other MCP clients

mcp_config.json:

{
    "mcpServers": {
        "dagger": {
            "command": "/path/to/dev/dagger",
            "env": {
                "_EXPERIMENTAL_DAGGER_RUNNER_HOST":"docker-container://dagger-engine.dev",
                "OPENAI_API_KEY":"toto"
            },
            "args": [
                "mcp",
                "-m",
                "ref/to/module"
            ]
        }
    }
}

Current capabilities supported

Analysis of Client support

Client Resources Prompts Tools Sampling Roots Notes
Claude Desktop App Dynamic reload of tools not supported -- we error on the prompts / resources as we don't support it
Goose Dynamic reload of tools is supported (via notification)
Cursor Supports tools. Dynamic reload not supported atm -- UX under experimentation
Zed Prompts appear as slash commands -- No current support for tools
Windsurf Editor Need pro sub to test
-- -- -- -- -- -- --

Todo

  • Currently, OPENAI_API_KEY or equivalent needs to be set (can be just foo) because MCP is a method on the LLM object even though MCP doesn't make API calls. This requirement will go away as we separate MCP from LLM.
  • Currently -s or --progress plain needs to be specified so only stderr is used, as dagger mcp command uses both stdin and stdout for MCP communication. We could force --progress plain, or decide on a minimalist reporting when using -s.
  • SSE forwarding coming soon.
  • This doesn't yet use the multi-object API.
  • Module constructors won't work if there are arguments to the constructor. Need to figure out the UX. Should it be like dagger call ? Or should we piggyback on dagger shell by instead doing dagger -c "... | mcp" ? (Would need to think how that could work on the client side...). Or dagger mcp -c '...' or dagger -mcp -c '...' ?
  • *_id functions should not be exposed to MCP. Apparently it's still exposed despite logic attempting to prevent it.
  • Moar tests
  • Nice IDE integration examples
  • Fix bug spewing logs upon closing connections

Co-authored by @grouville

@cwlbraa
Copy link
Contributor

cwlbraa commented Mar 21, 2025

Currently -s or --progress plain needs to be specified so only stderr is used, as dagger mcp command uses both stdin and stdout for MCP communication. We could force --progress plain, or decide on a minimalist reporting when using -s.

i'd love to see the stderr output with --progress=plain– is it useful? like do we get to see the requests made by the attached LLM, or are those logged regardless of -s or --progress=plain? does --progress=plain produce logs that help users understand how the LLM is failing, or might it be better to actually hand those logs off to the LLM via the MCP interface?

},
}

// dagger -m github.com/org/repo mcp key1=val1 key2=val2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you see these keys and values being passed to? are they module-level flags?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, this is more of a TODO of how to handle module constructors. Forgot to mention it in the description.

Copy link
Contributor

@cwlbraa cwlbraa Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's possible they should retain the syntax of call, like --module-constructor-key=value

@tiborvass
Copy link
Contributor Author

@cwlbraa Just want to clarify that this mcp command is not really meant to be used by users, but by a program (generally an MCP client) that wants to start the MCP server. So it is up to that program what/they want to show for progress.

@grouville and I are finishing a barebones cli to be able to easily test this with an LLM. Will update the description.

Currently, the official mcp-go client doesn't forward os.Stderr and the upstream change may need some UX debate, but in my fork I set "cmd.Stderr = os.Stderr" in the mcp-go client so that our testing cli does show --progress plain with the dagger operations visible. It just gets in the way of the interactiveness (the prompt ">" is lost when there's stderr output). Anyway this is for the testing mcp client, but we need a general solution. dagger mcp logs ? Probably a bad idea, but open to anything that makes the UX better.

@cwlbraa
Copy link
Contributor

cwlbraa commented Mar 21, 2025

@cwlbraa Just want to clarify that this mcp command is not really meant to be used by users, but by a program (generally an MCP client) that wants to start the MCP server. So it is up to that program what/they want to show for progress.

it's client specific, but claude desktop for example has pretty detailed debugging instructions and i'm curious if ~/Library/Logs/Claude/mcp*.log includes these stderr plain logs. in the hackathon building toy MCPs, i'd leave a terminal split up with while true; do tail -f ~/Library/Logs/Claude/mcp*.log; echo "RESTARTING"; done

dagger mcp logs? Probably a bad idea

agreed this feels like kinda a bad idea, something we should leave to docker lol

@tiborvass
Copy link
Contributor Author

tiborvass commented Mar 21, 2025

@cwlbraa If they use their own official Go mcp client, then os.Stderr is not forwarded, so my best guess is that they provide logs for SSE. I'll try to make it work soon so we can test with it.

So stderr is part of the Claude logs, so if you use -s it won't show the dagger operations, but without it, it will show --progress plain output.

@marcosnils
Copy link
Contributor

@tiborvass how does the dynamic tool support actually work? IIUC I can only start dagger mcp with a single module so I don't seem to find the way to dynamically load modules in an already started dagger mcp server so they can be re-discovered by the client.

@tiborvass
Copy link
Contributor Author

@marcosnils My bad, the terminology is confusing. What we meant by dynamic is that prior to calling a function that returns a dagger.Container type, the container functions are not available, they become available or at least we try, but some clients don't handle the "tool list update" of MCP.

What you are referring to is also justified being called dynamic and I believe it would require a "load module" function exposed in the LLM Env API. But note that this also suffers from the same problem that apart from Goose, nothing handles tools list update.

@tiborvass tiborvass force-pushed the mcp-server branch 3 times, most recently from 235fdda to cd852d6 Compare March 28, 2025 21:35
@tiborvass tiborvass marked this pull request as ready for review March 28, 2025 22:15
@grouville grouville force-pushed the mcp-server branch 2 times, most recently from db39d87 to 0cf11c3 Compare March 29, 2025 00:30
@grouville grouville requested review from cwlbraa and shykes March 29, 2025 00:31
grouville and others added 11 commits March 29, 2025 00:36
Co-authored-by: Guillaume de Rouville <guillaume@dagger.io>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
So far we were using the terminal session attachable to pass data between
cli and engine for the MCP stdio server.

However, MCP clients when they execute the cli they don't provide a TTY.

The new pipe session attachable allows to send and receive data between
the cli and the engine.

Co-authored-by: Guillaume de Rouville <guillaume@dagger.io>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Co-authored-by: Guillaume de Rouville <guillaume@dagger.io>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
When a tool is called, new tools may be available for the MCP client,
so notify it about the new tools.

For example, after a call to a function returning a Container, the client
should be able to call container-specific functions.

Co-authored-by: Guillaume de Rouville <guillaume@dagger.io>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Co-authored-by: Guillaume de Rouville <guillaume@dagger.io>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
This fixes a bug where MCP clients would see unavailable tools
and would attempt to use them and receive a cryptic error.

Signed-off-by: Tibor Vass <teabee89@gmail.com>
Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
…erveStdio()

The idea is to then have: mcp.ServeStdio() potentially in the API ?

Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Copy link
Member

@grouville grouville left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a good shape ✅

Some of the todos left, but good enough for beta testing. Everything is hidden, under the hidden dagger mcp command ; AND, everything is scoped to the dagger mcp subcommand -- no impact on main branch at all

How to test it (dev mode):

OPENAI_API_KEY=toto dagger_dev --progress plain mcp -m github.com/shykes/hello

Notes

  • The options --progress plain or -s are mandatory atm to not have the stdin polluted. One of the todos is to override it when not set in the CLI
  • One of the provider key is still mandatory as our MCP method is still part of LLM

Then you can paste this on stdio:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{},"clientInfo":{"name":"goose","version":"1.0.12"},"protocolVersion":"1.0.0"}}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"arguments":{"name":"Fred"},"name":"Hello_hello"}}

@grouville grouville merged commit d49377c into dagger:main Mar 29, 2025
53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants