Repository navigation
MCP analytics for Rust MCP servers - #308
Conversation
63f2ae5 to
e8c8929
Compare
Commit signatures resolvedGitHub now verifies every commit signature in this PR. No further action is needed. |
There was a problem hiding this comment.
Reviewed against the cross-SDK MCP analytics spec and ran the example server end to end over stdio; the events match the spec. I pushed a few fixes on top: panics in tool handlers are now recorded as failed calls (then re-raised), $mcp_interface is never dropped by the size bound, payload redaction cost is bounded per event, a panic isn't double-reported when capture_panics is on, and get_more_tools is listed on the first tools/list page like TS and Python. Each commit says how it was tested. Thanks @wthrajat, this is a really solid port.
|
thanks @lucasheriques!! :D (looks like your commits are missing signatures) |
rmcp does not catch handler panics, so a panicking tool sent no $mcp_tool_call at all: the worst failures were invisible. The spec counts a panic as a final outcome. The wrapper now catches the unwind, sends the failed call ($mcp_error_type "Panic", the panic message) and its $exception, then resumes the panic so the server behaves exactly as it would uninstrumented. Tested: new a_panicking_handler_is_a_failed_call fails before, passes after; cargo test --features mcp --test test_mcp_connection (21 pass); cargo clippy --features mcp -D warnings; cargo +1.88.0 check --locked --lib --features mcp.
$mcp_interface is required on every MCP event, but it was not in the always-kept list, so an event over the byte bound (a listing of a few thousand tools) lost it along with the other optional properties. Tested: an_oversized_event_keeps_its_interface fails without the fix, passes with it.
Each payload string was redacted up to its own 32,768-character limit, so a result of 100 large text blocks scanned ~3.3M characters on the response path, only for the event bound to cut them to 256 afterwards. Strings in a payload now share a budget of one event's size; once it is spent, the rest are cut to 64 characters before redaction scans them. Tested: strings_past_the_event_budget_are_cut_short fails without the budget, passes with it. Release build, 99 x 40 KB blocks: ~120 ms -> ~10 ms per test run. cargo test --features mcp,tower: 484 pass.
From the pre-PR review: - Catch a panic raised while the handler builds its future, not only while it runs, by awaiting it inside an async block. - Report a panic's $exception as unhandled: rmcp never turns it into a response, unlike a returned error. - Debit the payload budget by what redaction scans, not what it keeps, so strings redacted to a short token cannot keep every scan at full cost. - Tests pin the exact cut (first string at its limit, strings past the budget at 64) and that the client gets no answer from a panic. Tested: cargo test --features mcp (481 pass); cargo clippy --features mcp -D warnings.
With the client's opt-in capture_panics hook installed, a panicking tool was reported twice: once by the hook, once by MCP analytics. The hook already sent the $exception, so the call now sends only its $mcp_tool_call. The panic test also asserts the client gets no answer within 250 ms, which pins that the panic is resumed. Tested: cargo test --features mcp,tower (484 pass); cargo check --no-default-features --features mcp. The hook branch has no test of its own: the hook is process-global and installable only through the global client.
The virtual tool was appended to the last page, so a client that reads only the first page of a paginated server never saw it. TS (#4967) and Python (#962) list it on the first page, the one every client reads; Rust now matches. Tested: the_virtual_tool_is_listed_once_on_the_first_page fails before ((0, 1)), passes after; cargo test --features mcp,tower (485 pass); cargo clippy --features mcp -D warnings.
451004d to
2db1c93
Compare
* docs(mcp-analytics): add the Rust SDK posthog-rs 0.29.0 (on crates.io) ships experimental MCP analytics for rmcp servers behind the `mcp` feature (PostHog/posthog-rs#308). Add a Rust installation page (install, instrument, McpOptionsBuilder options, identify, sessions, get_more_tools, the manual API, events with $lib posthog-rs-mcp, limits), a Rust column in the SDK feature table, the sidebar entry, and Rust tabs/mentions on start-here, events, and intent. Tested: every Rust snippet compiled with `cargo check` against posthog-rs =0.29.0 + rmcp 3.7; changed MDX parses with @mdx-js/mdx v1; prettier --check passes on src/navs/index.js. * docs(mcp-analytics): correct Rust SDK claims from the pre-PR review Two-model review (Opus, Sonnet) of the Rust page against posthog-rs 0.29.0 found overclaims and gaps. Fix: manual API sends tool-call events only; error type naming and the rmcp `validation` case; client_metadata intent source; get_more_tools collision check and placement; required llm_model, appended conversation handle, composed-schema skip; argument stripping needs Registered or list_tools; rmcp 3.7+ and Clone; panic $exception via hook; Limits (tasks, stateless client info, no properties callback, no stack trace). Mark Rust in the Go-only notes on events, identifying-users, privacy, intent, start-here, and sdk-v2. Tested: all changed MDX parses with @mdx-js/mdx v1; each claim checked against sdks/rust at tag 0.29.0. * docs(mcp-analytics): answer failed Rust manual calls, narrow client identity limit The custom dispatcher example recorded a failed call but never answered it, so a copied dispatcher left clients waiting for a timeout. The limits listed client name and version as missing on every stateless server, but posthog-rs 0.29.0 reads them from a 2026-07-28 request's _meta (RequestScope::client_identity), so only older clients without a replayed token lack them. * docs(mcp-analytics): correct the Rust panic, initialize session, and identify notes Addresses wthrajat's review: the panic hook needs capture_panics on the global client, not just the error-tracking feature; $mcp_initialize gets its own ses_ id because initialize precedes Mcp-Session-Id; Rust resolves identify once per request. Also drops a duplicated Go sentence on start-here. * docs(mcp-analytics): scope the BeforeSend note to Go * docs(mcp-analytics): make the Rust snippets compile, check events by query, add troubleshooting - Every Rust block on the Rust and start-here pages now compiles against posthog-rs 0.29.0 as written: imports added, the identify user type and the custom-dispatcher types spelled out, a compiled Streamable HTTP snippet. - The example run command reuses POSTHOG_PROJECT_TOKEN, the one variable the page asks for. - "Check your first events" is a SQL query, so an agent can run it through the PostHog MCP server's execute-sql tool. - New troubleshooting table keyed by symptom. Tested: a scratch crate compiles each ```rust block extracted from the .mdx files (9/9 compile); removing one import from a copy makes that block fail. * Correct Rust $exception properties after the master merge 0.29.0 sends no $mcp_server_build, and the Rust note contradicted the paragraph above it. State the Rust differences once, in that paragraph. Found by an independent verification pass against the 0.29.0 crate source.



💡 Motivation and Context
closes #258
This adds mcp analytics like the ruby, python, go and node sdks have.
For more context, I had a bit of discussion with @pauldambra on twitter here about this.
Also screenshots are attached here MCP analytics for Rust MCP servers #308 (comment)
mcpfeatureposthog-rs-mcpas the lib so they're easy to tell apart1.88, the rest of the crate still works on1.78💚 How did you test it?
stdioandhttp)📝 Checklist
If releasing new changes
sampo addto generate a changeset file