Optional GCF output for the MCP server (PLANVIEWER_OUTPUT_FORMAT=gcf) - #501
Conversation
5b28eeb to
c5e1892
Compare
|
One ask before this goes in: pin 1.0.0 went up about four hours before you opened this and had zero downloads when I checked. PerformanceMonitor has been running 0.2.0 in production for a week. I'd rather Studio ship the bits that already have mileage on them. Your "behavior-identical to 0.2.1" claim does hold up, for what it's worth: identical assembly sizes across 0.2.0, 0.2.1 and 1.0.0, and the same 49-member documented public API surface. So this isn't doubt about the package, it's that "same code, more days in the wild" is free and I'll take it. 0.2.0 would also keep both repos on one version, which is worth something on its own. Happy to move to 1.0.0 once it has some time on it. Everything else here builds and tests clean on my machine. |
c5e1892 to
03d8faf
Compare
|
Done — pinned to Fair call, and thanks for actually diffing the assemblies and the public API surface to check the behavior-identical claim rather than taking it on faith. 0.2.1 keeps Studio on the same 0.2.x line PerformanceMonitor's been running, and it's the freshest patch of it (a decode fix for bracketed quoted keys — belt-and-suspenders for the record shapes here, but no reason not to have it).
|
PerformanceStudio took 0.2.1 in erikdarlingdata/PerformanceStudio#501; this puts both repos on the same build rather than leaving Monitor a patch behind on 0.2.0. 0.2.1 is a decoder fix: a quoted key containing "[" alongside an array value now round-trips. Nothing here depended on the broken case. GcfOutput verifies every wire by decoding it and comparing against the input before substituting, so a key that did not round-trip made the result fall back to JSON rather than emit a bad wire -- the bug cost compression on those shapes, never correctness. Only the two Gcf entries are touched. Regenerating the lock with --force-evaluate also pruned unrelated ProtectedData and EventLog entries; those were reverted, since restore --locked-mode (what build.yml runs) accepts the committed lock as it stands and this is a dependency bump, not a lock cleanup. Verified: dotnet restore --locked-mode passes, and Darling.Tests is green at 6901 tests, 0 failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nD83xZyWPzKWhs7Gg9Vyq
- PlanShare /api/stats: accept the token via the X-Stats-Token header only. The ?token= fallback wrote the secret into nginx access logs - the exact leak the dashboard's fragment-based bootstrap avoids, and nothing used it (the dashboard sends the header). - deploy-planshare.yml: count 401 from /api/stats as a healthy deploy. Requiring 200 meant that setting STATS_TOKEN would fail verification and auto-roll-back every subsequent good deploy. /health stays out of reach because nginx does not route it. - dependabot.yml: stop auto-bumping BlackwellSystems.Gcf. It runs against every MCP tool result when enabled, it is maintained by the contributor who introduced it (#501), and dependabot PRs skip the AI review gate - so bumps now arrive only via hand-reviewed PRs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UbaAfAZPVqXbZVPy79AL81
Summary
Adds an opt-in GCF (Graph Compact Format) encoding for the embedded MCP server's tool results, off by default. Set
PLANVIEWER_OUTPUT_FORMAT=gcfand each tool's JSON result is re-encoded as a GCF generic wire before it goes to the model; leave it unset and nothing changes.This is the same integration you already merged into PerformanceMonitor (erikdarlingdata/PerformanceMonitor#2286): a single call-tool filter registered once on the MCP host (
McpHostService), so it covers every tool with no per-tool changes. The record-heavy results here —get_query_store_top's plan list, the plan/connection/statement listings — repeat every field name on every row in JSON; GCF factors those names into one header, so a model spends fewer tokens reading them.Scope
This covers the embedded MCP server built with
AddMcpServer(...)inMcpHostService(the one exposingMcpPlanTools+McpQueryStoreTools, including the Query Store tool benchmarked below). It does not touch the CLI'splanview mcp serve: that host is generated by theRepl.Mcpframework, whoseUseMcpServeroptions expose no result-transform hook, so the filter can't attach there. If covering the CLI surface is of interest, it would need a result hook upstream inRepl.Mcp— happy to raise that separately.Measured
get_query_store_top, GPT-4o (o200k) tokenizer, versus the server's compact JSON (no whitespace credit), all round-trips verified lossless:~30% fewer tokens, scaling with N. (The tools serialize with
WriteIndented = true, so against the actual on-the-wire output the reduction is larger — ~45% — but the honest apples-to-apples number is versus compact JSON, which is what's shown above.) The per-rowquery_textis free text that no format can factor, so it dilutes the win; a metrics-only result lands nearer 36%.How it works (mirrors PerformanceMonitor)
Mcp/GcfOutput.cs—TryEncode(json)returns a GCF wire only when it parses, encodes, is smaller than the JSON (never-grow), and decodes back to the same value (verified against the input, order-insensitive). Any failure returns null and the JSON is kept. Integers are parsed tolongso large ids/counts/durations are never float-rounded, and a non-integer is kept only when it survives as an exact double (a high-precision decimal or an overflowing token declines the payload rather than emit a rounded wire).Mcp/GcfCallToolFilter.cs— oneMcpRequestFilterthat rewrites a lone JSON text block when enabled; an error result, a multi-block result, or a non-text block is passed through untouched.McpHostService.cs— one line:.WithRequestFilters(filters => filters.AddCallToolFilter(GcfCallToolFilter.Instance)).PlanViewer.App.csproj—BlackwellSystems.Gcf(zero runtime dependencies), pinned exact.So enabling it can only ever shrink a result or leave it exactly as-is; it can never grow, drop, or garble one.
Why GCF
On the dependency
BlackwellSystems.Gcfis pinned to 0.2.1 — the same 0.2.x line PerformanceMonitor already runs in production, so Studio ships bits that have mileage on them. It's zero-dep, so nothing comes in transitively. (The later 1.0.0 is behavior-identical — same assembly and public API — but there's no reason to reach for it here; happy to bump once it has some time in the wild.)Compatibility
PLANVIEWER_OUTPUT_FORMAT=gcfis set.StructuredContent, or to the HTTP/stdio transport wiring.GcfOutputTests(encoder: record-array round-trip, value-carry, never-grow, invalid JSON, and the int64 / exact-double / high-precision-decimal / non-finite number guards),GcfCallToolFilterTests(enabled, disabled, error result, multi-block), andGcfPipelineTests— an end-to-end pair that stands up the MCP server with this exact registration over an in-process transport and confirms a real clientCallToolcomes back as GCF when enabled and JSON when not.dotnet buildclean; all 17 pass; the rest of the suite is unaffected.Where GCF is in use
Already shipped in Chrome DevTools MCP, Speakeasy, the Elasticsearch MCP server, Equibles (.NET), the Cisco Support MCP server (which made GCF its default), and — the same filter pattern as this PR — your own PerformanceMonitor. Full list: https://gcformat.com