Skip to content

Pyth template completion, MCP token-bloat fix, honest 404s for /v1 - #1

Draft
92Infinitus92 wants to merge 5 commits into
developfrom
feat/pyth-price-feed-fields
Draft

Pyth template completion, MCP token-bloat fix, honest 404s for /v1#1
92Infinitus92 wants to merge 5 commits into
developfrom
feat/pyth-price-feed-fields

Conversation

@92Infinitus92

@92Infinitus92 92Infinitus92 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Four changes, one commit each:

  • Pyth template: 2 → 9 fields. conf, exponent, publish times, EMA values
    and posted_slot are now overridable, so scenarios can express staleness and
    confidence cases. Unit-tested against a real PriceUpdateV2 account.
  • MCP: 85x smaller template payloads. get_override_templates no longer
    inlines hundreds of constant options (1.86 MB → 22 KB; it overflowed LLM
    context and TPM limits). The new search_constant_options tool resolves
    values on demand. First tests in the mcp crate. The /v1/scenarios/templates
    HTTP endpoint (studio dropdowns) intentionally keeps full data.
  • Unknown /v1/ paths return JSON 404* instead of the SPA's index.html with
    HTTP 200, which masked client/API mismatches as JSON parse errors. Route
    registration is shared between server and test via configure_api.
  • surfnet_timeTravel docs: absoluteTimestamp is milliseconds and jumps are
    forward-only — neither was documented.

Testing: cli 19/19, mcp 5/5, core pyth 3/3; fmt and clippy clean; live
e2e against a running surfnet.

Open questions

  • Search page size is 20 (MAX_SEARCH_RESULTS): keep, change, or make it a
    tool parameter?
  • Add a real GET /v1/scenarios/{id}? It grows the public API and old embedded
    UIs must not depend on it.
  • Small cleanup PR for three pre-existing nits (legacy error convention in
    older MCP tools, dead Arc app_data in cli/http, HEAD 404s on GET routes)?

Greptile Summary

This PR bundles four focused improvements: the Pyth PriceUpdateV2 override template grows from 2 to 9 overridable fields (with comprehensive unit tests); the MCP get_override_templates tool stops inlining all constant option lists and instead summarises them by count, while a new search_constant_options tool resolves values on demand (dropping payload size from ~1.86 MB to ~22 KB); unknown /v1/* paths now return a JSON 404 instead of silently falling through to the SPA's index.html; and the surfnet_timeTravel docs are corrected to document millisecond units and forward-only movement.

  • MCP payload reduction: compact_template_json replaces full constant lists with {label, description, optionsCount} summaries; the new search_constant_options tool provides paginated, case-insensitive search with truncated/totalMatches signalling, and both the tool and str:///override_templates resource now share the compact serialiser.
  • JSON 404 for unknown API paths: configure_api centralises route registration and appends a /v1 scope default_service that returns a structured JSON error, backed by an actix-web integration test that confirms known routes still return 200.
  • Pyth template expansion: seven new field overrides (conf, exponent, publish_time, prev_publish_time, ema_price, ema_conf, posted_slot) are exercised by a byte-level fixture test that reads each override back through the Borsh-decoded account.

Confidence Score: 4/5

Safe to merge; all four changes are well-scoped, tested, and do not touch critical paths like auth or data persistence.

The MCP token-reduction work is the most complex piece: the compact serialiser and search tool are correct and well-tested. The one open question is whether the verbatim metadata field in search results could partially offset the token savings for option-rich templates, but this is a tuning concern rather than a functional defect. The /v1 JSON-404 fix uses actix-web routing correctly and the accompanying test confirms both the catch-all and the known-route case.

Files Needing Attention: crates/mcp/src/surfpool/mod.rs — the metadata field in search results is worth a second look if options carry large blobs.

Important Files Changed

Filename Overview
crates/mcp/src/surfpool/mod.rs Adds compact_template_json helper and search_constant_options MCP tool; get_override_templates and the resource handler both adopt the compact serialiser. metadata field is included verbatim in search results, which may partially undercut the token-reduction goal for option-rich templates.
crates/cli/src/http/mod.rs Extracts configure_api to share route registration between server and tests; appends a /v1 scope default_service that returns JSON 404. Test covers both the catch-all and confirms a known route still serves 200.
crates/core/src/scenarios/protocols/pyth/v2/overrides.yaml Extends the pyth-price-feed-v2 template from 2 to 9 overridable fields and adds comprehensive llm_context guidance including units warnings for absoluteTimestamp and the staleness-simulation recipe.
crates/core/src/surfnet/locker.rs Extracts Pyth fixture bytes into a shared helper and adds a thorough round-trip test covering all 8 new template fields via Borsh encode/decode.
crates/core/src/rpc/surfnet_cheatcodes.rs Doc-only update: corrects surfnet_timeTravel to document millisecond units for absoluteTimestamp and forward-only constraint.
crates/mcp/Cargo.toml Adds tokio as a dev-dependency with macros and rt features to support the new async MCP tests.
Cargo.lock Records tokio addition to the mcp crate dev-dependencies; no unexpected new transitive dependencies.

Sequence Diagram

sequenceDiagram
    participant LLM as LLM Agent
    participant MCP as MCP Server
    participant Reg as TemplateRegistry

    LLM->>MCP: get_override_templates()
    MCP->>Reg: registry.all()
    Reg-->>MCP: "Vec<OverrideTemplate>"
    MCP->>MCP: "compact_template_json(t)<br/>(constants → {label, description, optionsCount})"
    MCP-->>LLM: "compact JSON (~22 KB)<br/>constants have no inlined options"

    LLM->>MCP: search_constant_options(templateId, constant?, query)
    MCP->>Reg: find template by id
    Reg-->>MCP: OverrideTemplate
    MCP->>MCP: "filter options (case-insensitive)<br/>collect up to MAX_SEARCH_RESULTS=20"
    MCP-->>LLM: "{results, totalMatches, truncated}"

    LLM->>MCP: "create_scenario(templateId, values{feed_id: value})"
    MCP-->>LLM: scenario URL
Loading

Comments Outside Diff (1)

  1. crates/mcp/src/surfpool/mod.rs, line 584-591 (link)

    P2 metadata included verbatim may undercut the token savings

    Each search result includes "metadata": opt.metadata without any summarisation. For Pyth price-feed options this field likely contains extra chain/market data; at MAX_SEARCH_RESULTS = 20 results the aggregate metadata blob could be non-trivial. Since the value field is the only part create_scenario actually needs, it's worth deciding whether metadata should be omitted or summarised (e.g. its key names only), similar to how constant options are now summarised in compact_template_json.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: crates/mcp/src/surfpool/mod.rs
    Line: 584-591
    
    Comment:
    **`metadata` included verbatim may undercut the token savings**
    
    Each search result includes `"metadata": opt.metadata` without any summarisation. For Pyth price-feed options this field likely contains extra chain/market data; at `MAX_SEARCH_RESULTS = 20` results the aggregate metadata blob could be non-trivial. Since the `value` field is the only part `create_scenario` actually needs, it's worth deciding whether metadata should be omitted or summarised (e.g. its key names only), similar to how constant options are now summarised in `compact_template_json`.
    
    ---
    
    For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Claude Code Fix in Codex Fix in Cursor

Fix All in Claude Code Fix All in Codex Fix All in Cursor

Prompt To Fix All With AI
### Issue 1
crates/mcp/src/surfpool/mod.rs:584-591
**`metadata` included verbatim may undercut the token savings**

Each search result includes `"metadata": opt.metadata` without any summarisation. For Pyth price-feed options this field likely contains extra chain/market data; at `MAX_SEARCH_RESULTS = 20` results the aggregate metadata blob could be non-trivial. Since the `value` field is the only part `create_scenario` actually needs, it's worth deciding whether metadata should be omitted or summarised (e.g. its key names only), similar to how constant options are now summarised in `compact_template_json`.

### Issue 2
crates/cli/src/http/mod.rs:290-319
**Test only covers GET; POST/DELETE method mismatches on unknown paths are untested**

The catch-all uses `web::route()` (no method filter), so all HTTP methods on unknown `/v1/*` paths correctly return a JSON 404. The test only exercises GET requests, which means the case where a client sends POST or DELETE to a mistyped URL is not covered. Adding at least one non-GET assertion would solidify the guarantee that the scope's `default_service` is truly method-agnostic.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(cli): return JSON 404 for unknown /v..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

The pyth-price-feed-v2 template exposed only feed_id and price, so scenarios
could not express staleness or confidence-band cases that lending/PMM logic
actually reads. Adds the remaining PriceFeedMessage fields (conf, exponent,
publish_time, prev_publish_time, ema_price, ema_conf) and posted_slot as
template properties, and extends llm_context with field semantics, a staleness
recipe, and the Surfnet clock rules (timeTravel takes milliseconds, the
simulated clock only moves forward).

Covered by a forging unit test against a real 134-byte PriceUpdateV2 account:
all eight writable fields change to values that differ from the fixture's,
bytes before price are asserted untouched, trailing padding is preserved.
absoluteTimestamp is consumed in milliseconds while the Clock sysvar exposes
seconds, and jumps are forward-only; the doc comment said neither, which cost
a debugging session to rediscover empirically.
get_override_templates inlined every constant option (hundreds of price feeds,
duplicated across templates), producing a ~1.86 MB payload that overflowed LLM
context windows and TPM limits. Constants are now summarized as
{label, description, optionsCount} — 85x smaller measured — and the new
search_constant_options tool resolves concrete values via case-insensitive
search with stable sorted paging (MAX_SEARCH_RESULTS) and self-correcting
errors for unknown template ids or constants. The str:///override_templates
resource shares the same serialization through compact_template_json, so both
MCP surfaces stay compact.

Adds the first tests of the mcp crate: tool error paths, case-insensitive
matching, truncation reporting, and a guarantee that no template leaks inlined
options.
…llback

The studio SPA catch-all answered any unknown GET — including API misses like
GET /v1/scenarios/{id}, which has never existed — with index.html and HTTP 200.
Clients then failed deep in JSON parsing ("Unexpected token '<'") instead of
seeing a clean 404, which is how a tag-wiping frontend bug shipped unnoticed.
Unknown /v1/* paths now get {"error":"not found"} with 404; page routes and
static assets keep the SPA fallback.

Route registration moved into configure_api, shared between the server and the
new HTTP test, so the load-bearing registration order (real endpoints before
the /v1 catch-all scope) is asserted on the exact code production runs.
@92Infinitus92 92Infinitus92 self-assigned this Aug 4, 2026
@failfmi failfmi closed this Aug 4, 2026
@failfmi failfmi reopened this Aug 4, 2026
@failfmi

failfmi commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

@greptileai

Comment on lines +290 to +319
#[actix_web::test]
async fn unknown_v1_paths_return_json_404_instead_of_spa_fallback() {
let loaded_scenarios = Data::new(RwLock::new(LoadedScenarios::new()));
let app = test::init_service(
App::new()
.app_data(loaded_scenarios)
.configure(configure_api)
.service(surfpool_studio_ui::serve_studio_static_files),
)
.await;

for path in ["/v1/scenarios/some-id", "/v1/nonexistent"] {
let request = test::TestRequest::get().uri(path).to_request();
let response = test::call_service(&app, request).await;
assert_eq!(response.status(), 404, "expected 404 for {path}");
assert_eq!(
response.headers().get("content-type").unwrap(),
"application/json",
"expected JSON body for {path}"
);
}

let request = test::TestRequest::get().uri("/v1/scenarios").to_request();
let response = test::call_service(&app, request).await;
assert_eq!(
response.status(),
200,
"registered endpoints must keep working"
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test only covers GET; POST/DELETE method mismatches on unknown paths are untested

The catch-all uses web::route() (no method filter), so all HTTP methods on unknown /v1/* paths correctly return a JSON 404. The test only exercises GET requests, which means the case where a client sends POST or DELETE to a mistyped URL is not covered. Adding at least one non-GET assertion would solidify the guarantee that the scope's default_service is truly method-agnostic.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/cli/src/http/mod.rs
Line: 290-319

Comment:
**Test only covers GET; POST/DELETE method mismatches on unknown paths are untested**

The catch-all uses `web::route()` (no method filter), so all HTTP methods on unknown `/v1/*` paths correctly return a JSON 404. The test only exercises GET requests, which means the case where a client sends POST or DELETE to a mistyped URL is not covered. Adding at least one non-GET assertion would solidify the guarantee that the scope's `default_service` is truly method-agnostic.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — added a POST assertion in c2c4e65.

Review feedback: the guard's default_service is method-agnostic, but the test
only exercised GET. A POST to an unknown /v1 path now asserts the JSON 404 too.
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