Skip to content

Optional GCF output for the MCP server (PLANVIEWER_OUTPUT_FORMAT=gcf) - #501

Merged
erikdarlingdata merged 1 commit into
erikdarlingdata:devfrom
blackwell-systems:feat/gcf-mcp-output
Sep 4, 2026
Merged

erikdarlingdata merged 1 commit into
erikdarlingdata:devfrom
blackwell-systems:feat/gcf-mcp-output

Conversation

@blackwell-systems

@blackwell-systems blackwell-systems commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

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=gcf and 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(...) in McpHostService (the one exposing McpPlanTools + McpQueryStoreTools, including the Query Store tool benchmarked below). It does not touch the CLI's planview mcp serve: that host is generated by the Repl.Mcp framework, whose UseMcpServer options 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 in Repl.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:

top-N compact JSON GCF reduction
10 2,294 1,629 29.0%
25 5,755 3,984 30.8%
50 11,473 7,858 31.5%

~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-row query_text is 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 to long so 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 — one McpRequestFilter that 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

  • Comprehension is measured, not assumed. A compact format is only useful if the model still reads it correctly — GCF has a published study reporting 100% comprehension across frontier models: https://gcformat.com/guide/benchmarks
  • Lossless by construction, verified per-result at runtime and by a cross-SDK conformance suite + differential fuzz.
  • Zero runtime dependencies, deterministic, and portable across the language SDKs.
  • Grounded in tokenizer/attention research (DOIs in the project README), currently under review.

On the dependency

BlackwellSystems.Gcf is 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

  • Off by default; JSON is unchanged unless PLANVIEWER_OUTPUT_FORMAT=gcf is set.
  • No change to any tool, to StructuredContent, or to the HTTP/stdio transport wiring.
  • Tests (17): 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), and GcfPipelineTests — an end-to-end pair that stands up the MCP server with this exact registration over an in-process transport and confirms a real client CallTool comes back as GCF when enabled and JSON when not. dotnet build clean; 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

@blackwell-systems
blackwell-systems changed the base branch from main to dev September 4, 2026 12:40
@erikdarlingdata

Copy link
Copy Markdown
Owner

One ask before this goes in: pin BlackwellSystems.Gcf to 0.2.1 (or 0.2.0) rather than 1.0.0.

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.

@blackwell-systems

Copy link
Copy Markdown
Contributor Author

Done — pinned to 0.2.1 and pushed.

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).

build-and-test is green on the updated branch.

@erikdarlingdata
erikdarlingdata merged commit 7e7d18f into erikdarlingdata:dev Sep 4, 2026
4 checks passed
pull Bot pushed a commit to ehtick/PerformanceMonitor that referenced this pull request Sep 10, 2026
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
erikdarlingdata added a commit that referenced this pull request Sep 12, 2026
- 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
This was referenced Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants