Skip to content

docs: README parity with protoc-gen-go-mcp (#13) - #30

Merged
erikist merged 1 commit into
mainfrom
factory/13-docs-readme-parity-with-protoc-gen
Oct 10, 2026
Merged

erikist merged 1 commit into
mainfrom
factory/13-docs-readme-parity-with-protoc-gen

Conversation

@erikist

@erikist erikist commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Closes #13

Summary

Brings README.md to parity with Go's reference README at
stablekernel/protoc-gen-go-mcp@9e072f9,
filling in every [TODO]:

  • Prerequisites: the Rust toolchain, protoc 29.3, and the pinned
    protoc-gen-prost/protoc-gen-tonic/protoc-gen-prost-serde versions
    with an install command, plus a table of the generated code's own
    runtime crates (prost, tonic, pbjson/pbjson-types 0.8, rmcp,
    serde_json, jsonschema, futures) and why each is needed.
  • Running the plugin: the full protoc --rust-mcp_out=...
    invocation alongside the prost/tonic/serde plugins, make generate,
    and how the output is include!d.
  • Debugging the plugin: a new protoc-gen-debug
    script (executable) that saves the CodeGeneratorRequest to
    request.bin; replay it directly, under rust-lldb/rust-gdb, or in
    VS Code with CodeLLDB and a new .vscode/launch.json.
    Go's protoc-gen-debug is a one-liner that also starts dlv --headless; Rust debuggers have no direct equivalent of that single
    step, so this is a save-then-replay two-step instead. The README is
    explicit that neither lldb nor gdb itself is installed in the
    factory image (only rustup's wrapper scripts are) — CodeLLDB bundles
    its own LLDB, so that path needs no separate install.
  • Testing the example: unchanged (was already filled in).
  • What the plugin generates: stdio and Streamable HTTP wiring
    snippets, kept compiling by a new
    examples/tests/wiring.rs (the
    counterpart of Go's Example_wiring). The Streamable HTTP snippet
    needed new hyper/hyper-util dev-dependencies and rmcp's
    transport-streamable-http-server feature in examples/Cargo.toml
    (checked against rmcp 3.5.1/hyper 1.12.0/hyper-util 0.1.21, the
    versions Cargo.lock already pinned via tonic's own dependency on
    axum/hyper, by compiling the exact feature set standalone before
    wiring it into the workspace — see that file's doc comment).
  • How tool arguments map to the request message: every rule from
    Go's section, restated for pbjson (field names, 64-bit integers,
    enums, bytes, non-finite floats, zero-value omission, gRPC error
    text) and cross-checked against this repo's own schema.rs/server.rs
    doc comments and examples/tests/handler.rs's assertions (exact error
    text, exact rpc error: code = ... format).
  • A new "Differences from protoc-gen-go-mcp" section, per feat: protojson-compatible google.protobuf.FieldMask in tool args, results and schemas #20's PR
    review feedback on this issue: Any's pbjson shape vs. protojson's,
    non-finite float results as JSON null (input still accepts all
    three strings either way), no editions support, and that
    google.protobuf.FieldMask is made to match Go (not a divergence).
  • Philosophical Notes: the Rust version of Go's three composable
    parts (tool, handler, registration), using this repo's own generated
    identifier names (VIBE_SERVICE_SET_VIBE_INPUT_SCHEMA,
    call_set_vibe, etc.) instead of Go's.
  • Topeka and Maintainers sections: kept as-is, per the issue.

Decisions made along the way

  • Go's protoc-gen-debug starts a debugger (dlv --headless) directly;
    there's no Rust tool that does the equivalent single step, so I split
    it into "save the request" (the new script) + "replay it under
    whatever debugger you have" (documented for rust-lldb/rust-gdb/VS
    Code CodeLLDB). I called out explicitly that the factory image has
    neither lldb nor gdb installed (only rustup's wrapper scripts,
    which need one of those) so nobody is misled into thinking the
    rust-lldb command works out of the box here; CodeLLDB (which bundles
    its own LLDB) is the one documented path that is usable without any
    further local install.
  • The wiring snippets are compiled via a new examples/tests/*.rs file
    (not a doctest), matching how Go's Example_wiring lives in a
    _test.go file compiled-but-never-run by go vet/go test: both
    functions in wiring.rs would block forever if called, so neither is
    an actual #[test].

Test evidence

All commands below were run in this factory image (rustc 1.99.0,
cargo 1.99.0, protoc 29.3/protoc-gen-prost 0.5.0/protoc-gen-tonic
0.5.0/protoc-gen-prost-serde 0.4.0 already on PATH).

Repo checks (AGENTS.md):

$ cargo fmt --all --check
(no output — clean)

$ cargo clippy --workspace --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.81s
(no warnings)

$ cargo build --workspace
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.99s

$ cargo test --workspace
...
test result: ok. 61 passed; 0 failed  (protoc-gen-rust-mcp unit tests)
test result: ok. 1 passed; 0 failed   (example_proto_descriptions)
test result: ok. 1 passed; 0 failed   (golden)
test result: ok. 17 passed; 0 failed  (server_integration)
test result: ok. 1 passed; 0 failed   (version)
Running tests/wiring.rs (target/debug/deps/wiring-...)
test result: ok. 0 passed; 0 failed   (wiring.rs has no #[test]s — it only
                                       needs to compile, which this run
                                       confirms)
... (examples' own handler/e2e/mcp_tool_parity/mcp_vibe_binary tests:
     all passing, unaffected by this change)

README commands, each run directly in a scratch directory (not
committed):

$ protoc --proto_path=examples/protos \
    --prost_out=<scratch> --tonic_out=<scratch> --prost-serde_out=<scratch> \
    --plugin=protoc-gen-rust-mcp=target/debug/protoc-gen-rust-mcp \
    --rust-mcp_out=<scratch> examples/protos/example.proto
exit 0; produced examples.v1.{rs,serde.rs,tonic.rs,mcp.rs}

$ make generate
Building protoc-gen-rust-mcp... Built successfully!
Generating new files... Generated successfully!
(diff against the already-committed examples/src/gen: empty — byte-identical)

$ cargo install --path examples --bin mcp-vibe --root <scratch>
Installed package `examples v0.1.0` (executable `mcp-vibe`)

$ protoc --proto_path=examples/protos \
    --plugin=protoc-gen-debug=./protoc-gen-debug \
    --debug_out=<scratch> examples/protos/example.proto
(protoc reports its usual "hasn't been updated to support ... proto3
optional" error afterwards, same as Go's own protoc-gen-debug would —
request.bin is saved by then)
$ ls -la <scratch>/request.bin
-rw-r--r-- ... request.bin   (15886 bytes)

$ cargo build -p protoc-gen-rust-mcp
$ ./target/debug/protoc-gen-rust-mcp < <scratch>/request.bin > /dev/null
exit 0

protoc-gen-prost@0.5.0/protoc-gen-tonic@0.5.0/protoc-gen-prost-serde@0.4.0
were already installed in this image (per Dockerfile.factory); I
confirmed their versions (protoc-gen-prost --version etc. → 0.5.0,
0.5.0, 0.4.0) rather than reinstalling.

Notes for reviewers

  • examples/Cargo.toml's new dev-dependencies (hyper, hyper-util,
    and rmcp's added transport-streamable-http-server feature) only
    affect test builds, not the published runtime API; Cargo.lock's diff
    also picks up a second base64 version (0.22.1 and 0.23.1) as
    transitive fallout from sse-stream (pulled in by
    transport-streamable-http-server) wanting a newer base64 than the
    rest of the dependency graph — normal, not hand-edited.

  • I did not re-run rust-lldb/rust-gdb end to end since neither lldb
    nor gdb itself is installed in this image; the README says so
    explicitly rather than claiming otherwise. protoc-gen-debug and the
    replay-without-a-debugger path (cargo build + run the binary
    directly against the saved request) are both verified above.

  • Self-reviewed via the reviewer subagent against this issue's
    acceptance criteria; it found no blocking issues (one nit about
    calling out the duplicate base64 version in Cargo.lock, addressed
    in this description).

  • Factory-authored

Brings README.md to parity with Go's reference README at
stablekernel/protoc-gen-go-mcp@9e072f9, filling in its [TODO]s:

- Prerequisites: Rust toolchain, protoc 29.3, and the pinned
  protoc-gen-{prost,tonic,prost-serde} versions with install commands,
  plus a table of the generated code's own runtime crates and why each
  is needed.
- Running the plugin: the full protoc --rust-mcp_out=... invocation
  alongside the prost/tonic/serde plugins, make generate, and how the
  output is include!d.
- Debugging the plugin: protoc-gen-debug (new script) saves the
  CodeGeneratorRequest to request.bin; replay it under rust-lldb/
  rust-gdb or VS Code CodeLLDB (new .vscode/launch.json). Rust has no
  direct counterpart of Go's dlv --headless step, so this is
  two-step instead of one script that also launches a debugger.
- Testing the example: unchanged (was already filled in).
- What the plugin generates: stdio and Streamable HTTP wiring
  snippets, kept compiling by a new examples/tests/wiring.rs (the
  counterpart of Go's Example_wiring), needing new hyper/hyper-util
  dev-dependencies and rmcp's transport-streamable-http-server
  feature in examples/Cargo.toml.
- How tool arguments map to the request message: every rule from Go's
  section, restated for pbjson (field names, 64-bit integers, enums,
  bytes, non-finite floats, zero-value omission, gRPC error text) and
  cross-checked against this repo's own schema.rs/server.rs doc
  comments and handler.rs assertions.
- A new Differences from protoc-gen-go-mcp section, per #20's PR
  review feedback on this issue: Any's pbjson shape, non-finite float
  results as JSON null, no editions support, and FieldMask's
  protojson-matching (not pbjson-matching) treatment.
- Philosophical Notes: the Rust version of Go's three composable
  parts (tool, handler, registration), using this repo's own
  generated identifier names.
- Topeka and Maintainers sections: kept as-is.

Every command in the README (prerequisite installs, the direct protoc
invocation, make generate, cargo install --path examples --bin
mcp-vibe, the protoc-gen-debug replay) was run in the factory image;
see the PR for the transcript. The wiring snippets are compiled by
cargo test via examples/tests/wiring.rs.
@erikist

erikist commented Oct 10, 2026

Copy link
Copy Markdown
Contributor Author

🔍 Review: LGTM. It's thorough and matches the Go README section for section, with a precise Differences section. I ran the debugging workflow in the factory image: protoc … --plugin=protoc-gen-debug=./protoc-gen-debug --debug_out=. saves a 15.9 KB request.bin (protoc then errors, as the README says), and ./target/debug/protoc-gen-rust-mcp < request.bin exits 0 with a 33 KB response. The wiring snippets are compiled via examples/tests/wiring.rs, and no TODOs are left.

@erikist
erikist merged commit 6860dce into main Oct 10, 2026
6 checks passed
@erikist
erikist deleted the factory/13-docs-readme-parity-with-protoc-gen branch October 10, 2026 04:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: README parity with protoc-gen-go-mcp

1 participant