Repository navigation
feat(mcp): generate a Rust MCP server - #49
Draft
dimitropoulos wants to merge 1 commit into
Draft
dimitropoulos wants to merge 1 commit into
dimitropoulos wants to merge 1 commit into
Conversation
Add an `mcp` target that generates a Rust crate for an MCP server. The server exposes every method as a tool and calls the API over HTTP when a client calls one. The crate is a fixed runtime (src/main.rs) plus a generated table of tools (src/tools.rs). Each tool carries its HTTP method, path template, path and query parameters, where its JSON body comes from, a JSON Schema of its arguments, and read-only, destructive and idempotent hints derived from the HTTP method and x-forge-require-confirmation. The server speaks MCP over stdio with rmcp 3.5, sends requests with reqwest 0.13, reads the base URL from API_BASE_URL (defaulting to the document's first server), and sends API_TOKEN as a bearer token.
commit: |
Collaborator
|
I think we should start with a spec that clearly describes how OpenAPI operations are mapped to MCP protocol. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the first step toward generating MCP servers from OpenAPI documents. It adds an
mcptarget:The output is a standalone Rust crate. The server exposes every method as an MCP tool and calls the API over HTTP when a client calls one.
What gets generated
Cargo.toml[workspace]so the crate builds wherever it's generated.src/main.rsinclude_str!fromsrc/bin/forge/mcp/runtime/main.rs. ImplementsServerHandlerwithlist_toolsandcall_tool.src/tools.rsBASE_URL,INSTRUCTIONSandTOOLS: &[Tool].Each tool gets:
accounts_members_list. If two methods produce the same name, generation fails.x-forge-require-confirmationsentence are appended to the description.exploderespected).Method::request) or a singlebodyargument.$defsand are referenced with$ref, so recursive models work. Read-only fields are left out.readOnly,destructiveandidempotent, derived from the HTTP method. A method that needs confirmation is always marked destructive.openWorldis always true.Methods are skipped when:
text/*;Runtime configuration
The server speaks MCP over stdio. It reads two environment variables:
API_BASE_URL: the base URL, defaulting to the document's first server.API_TOKEN: sent as a bearer token when set.A non-2xx response is returned as a tool result with
isError: true(not a JSON-RPC error), so the model can see the error.Dependencies
Only the generated crate gets new dependencies. Forge itself gets none: it already has
serde_jsonundercli, which the schema builder uses.rmcpdefault-features = falseandserver+transport-io. Macros aren't needed because the tools come from data.reqwesttokiomacrosandrt-multi-thread.serde_jsonEdition 2024,
rust-version = "1.88"(rmcp's MSRV).I deliberately left these out:
async-trait: rmcp uses native async fns in traits.schemars: the schemas come from the OpenAPI document, not from Rust types.tera/minijinja: the generator writes plain Rust withwriteln!, like the TS target.worker/getrandom: Workers support comes later (see below).Testing
src/bin/forge/mcp.rsandmcp/schema.rs, covering:bodyargument;tests/cli.rs:mcp()checks the generated layout.mcp_builds()runscargo buildon the output. It's#[ignore]d because it downloads crates; run it withcargo test --test cli -- --ignored.openapi.forge.jsonrelease, which produced 2,486 tools and a 5.8 MBtools.rs. It builds in about 35s in debug, and every input schema parses intools/list. Over stdio I raninitialize,tools/listandtools/call, both against a local echo server (path encoding, exploded query arrays, flat and field bodies, bearer auth, error statuses) and against the live API, where an invalid token correctly comes back as anisErrorresult.cargo fmt --check, both clippy runs with-D warnings, andcargo test --locked --all-featuresall pass.Not in this PR (follow-ups)
tools/list, which is too much context for most clients. The next step is probably per-group selection, or a small search/execute tool surface.securitySchemesyet, so auth is just a bearer token from the environment.npxdistribution of prebuilt binaries.mcp-server0.1.0) until forge has a way to configure them.