Render list results as a table instead of pretty-printed JSON - #35
Merged
Merged
Conversation
Every tool returned JSON.stringify(data, null, 2). For the list endpoints that is a bad trade: a 100-row get_transactions response is ~2,100 lines and ~15k tokens, and roughly 60% of that is the same nineteen key names repeated a hundred times. The rows are what the model needs; the repetition is what it pays for. A pipe table states each key once, in the header — 60,302 chars down to 22,145, which test/format.mjs prints on every run so the figure stays honest as the shape changes. No columns are dropped. A curated column set per tool would compress further, but a field the model cannot see is a field the user cannot ask about, and the tools are generated from an evolving OpenAPI spec, so a hand-maintained column list would rot silently. Rows carrying populated nested collections keep their old JSON output exactly: get_signals returns each signal with its transactions[] attached, and those are the substance of the answer. Numbers below 1 keep four decimals. Returns and hit rates are stored as fractions — the generated spec says so on every scored endpoint — so 2dp would read 0.0234 as +2%, print a -0.30% return as "-0.00", and collapse the leaderboard's ranking column into ties. Money and prices sit above 1 and read fine at two. structuredContent is deliberately not used: it cannot hold a bare array, and a tool that sets it should also serialize the same payload as text, so it would duplicate the data rather than replace it.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Owner
Author
|
Review before merge — the one caveat in the description is now measured rather than open.
The 41 are trust and pension-scheme entities filing as reporting owners, where 80 characters is still more than enough to identify the filer. Also re-verified after rebasing onto current main: build clean, 130 tests pass, and this does not conflict with #36 (they touch different lines of |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every tool returned
JSON.stringify(data, null, 2). For the list endpoints that is a bad trade: a 100-rowget_transactionsresponse is ~2,100 lines and ~15k tokens, and roughly 60% of that is the same nineteen key names repeated a hundred times. The rows are what the model needs; the repetition is what it pays for.A pipe table states each key once, in the header:
2.7x.
test/format.mjsprints that figure on every run, so it stays honest as the response shape changes.That saving is spent from the caller's context window rather than ours, and MCP is this product's highest-reach channel (~2.7x SDK downloads), so it is worth being careful about in both directions.
Two deliberate limits
No columns are dropped. A curated column set per tool would compress further, but a field the model cannot see is a field the user cannot ask about, and the tools are generated from an evolving OpenAPI spec — a hand-maintained column list would rot silently.
Rows carrying populated nested collections keep their old JSON output exactly.
get_signalsreturns each signal with itstransactions[]attached, and those transactions are the substance of the answer; flattening them into a cell would either explode the table or lose them.Numbers below 1 keep four decimals
Returns and hit rates are stored as fractions — the generated spec says so on every scored endpoint ("all return fields are stored as FRACTIONS — 0.05 means +5%"). Rounding those to 2dp is wrong in a way that is easy to miss, because the reasoning that money reads fine at 2dp does not transfer:
avgReturn3m0.11avgReturn3m0.02avgReturn3m-0.00/v1/insiders/leaderboardreturns an array of flat rows carrying exactly these fields, so it takes the table path — the whole ranking column would have collapsed into ties. Money and prices sit above 1 and read fine at two.Not used: structuredContent
It cannot hold a bare array (the spec types it as an object), and a tool that sets it SHOULD also serialize the same payload into a text block — so it duplicates the data rather than replacing it, which is the opposite of the point. Declaring the matching
outputSchemawould also make the SDK hard-fail any tool whose response drifts from it, and these shapes come from a live API.Verification
wrapResultis the single choke point for every tool, so this applies uniformly. 130 tests pass (mcp-test65,client-errors27,format35,generated-paths3);tsc --noEmitclean; build clean.Cell truncation at 80 characters is the one remaining lossy edge — no current field is close to it, but it is silent when it does fire.