migrate the MCP server to rmcp 3.1 - #34
Conversation
rmcp 3 renamed `Content` to `ContentBlock`; the constructors and `CallToolResult::success` are otherwise unchanged, so the visual tools' `image_result` helper is the only call site that moved. Two things the rename does not cover: `rmcp` now validates the inbound `Host` header against an allow-list that defaults to loopback, to blunt DNS rebinding against local servers. But `KERF_MCP_ADDR` exists to move the server *off* loopback, so the stock default would have made every such override reject the clients it just enabled. `allowed_hosts` derives the list from the bind address instead: a concrete address joins the loopback defaults, while a wildcard bind cannot be enumerated — the client's `Host` is whichever of this machine's addresses it arrived on — so it yields the empty "allow any" list. Tool schemas now come from schemars 1, which emits draft 2020-12 with `$defs`. The generated surface is equivalent, but nothing failed to compile when it changed, so the contract is now pinned by tests: every tool keeps a description and an object schema, `Option<T>` parameters stay out of `required` (a great many tools document a field as "omit to ..."), and an image result still serializes as a caption plus bare base64 under `data` rather than a `data:` URL. Verified against a running app: initialize, all 78 tools listed, read / mutating / error / staging round-trips, and both host-validation paths.
`ServerInfo::default()` fills `server_info` from rmcp's own build env, so every client listed the endpoint as "rmcp" 3.1.4 — wrong on 1.7 too, and invisible from the Rust side because nothing about it fails to compile.
dbeb452 to
46648aa
Compare
Tauri's manifest rides in the .res, which cargo links into bins only. The lib test binary thus ran with no activation context, bound comctl32 v5, and died with STATUS_ENTRYPOINT_NOT_FOUND on rfd's TaskDialogIndirect import before any test ran.
|
Force-pushed: dropped a temporary CI diagnostic and added a Windows fix the migration turned up.
|
46648aa to
01e2598
Compare
Supersedes #26 (dependabot's
rmcp1.7.0 → 3.1.2), which is a lockfile-only bump thecode does not survive: rmcp 3 renamed
Contentand added inboundHostvalidation.This does the migration and pins the parts of the contract that changed silently.
The rename
rmcp::model::Content→ContentBlock. The constructors andCallToolResult::successkept their signatures, so
image_resultwas the only call site. The transportconstructor is unchanged. rmcp 3.1 needs Rust 1.88 against this repo's 1.95 MSRV, so
there is no floor to raise.
KERF_MCP_ADDRwould have brokenrmcp 3 validates the inbound
Hostheader against an allow-list that defaults toloopback (a DNS-rebinding guard).
KERF_MCP_ADDRexists precisely to move the endpointoff loopback, so the stock default would have made every override 403 the clients it
just enabled.
allowed_hosts(pure, unit-tested) derives the list from the bindaddress: a concrete address joins the loopback defaults; a wildcard bind can't be
enumerated, so it yields the empty list — rmcp's "allow any".
Tool schemas switched generators without a compile error
schemars 0.8 → 1.x means draft 2020-12 with
$defs. The output is equivalent, butnothing failed to build when it changed, so the contract is now asserted: every tool
keeps a description and an object schema,
Option<T>params stay out ofrequired(a great many tools document "omit to append"), and image results still serialize as
bare base64 under
datarather than adata:URL. 8 tests, up from 1.The server was introducing itself as "rmcp"
ServerInfo::default()fillsserver_infofrom rmcp's own build env, so clients listedthe endpoint as
rmcp 3.1.4— equally wrong on 1.7, and invisible from the Rust side.It now names itself
kerf+ the crate version, with a test.Verified against a running app
Not only CI: built it, launched under WSLg, and spoke real MCP over HTTP — initialize
handshake, all 78 tools listed, read / mutating / tool-error / staging round-trips.
Re-bound to
10.255.255.254:7788and confirmed it accepts its own host (200) while aforeign
Hoston that same bind still gets 403.Then drove the GUI itself: imported a real 10 s clip through the file dialog, added it
to the timeline over MCP (the webview picked the edit up live), and pressed play —
playback stream ended playback_id=1 frames=237 first_frame_ms=72 elapsed_ms=9947,with the preview showing moving footage and the playhead tracking it.
fmt,
clippy --all-targets -D warnings, MSRV under a real 1.95 toolchain, and bothtest jobs (216 core + 9 app) pass. Lockfile churn is contained to
rmcp,sse-streamand
darling.