Repository navigation
feat: declare the resources capability and expose pairs, venues, OpenAPI spec, and status - #494
Conversation
bazaar_search passed any caller-supplied URL straight to fetch after a trailing-slash trim, letting the agent reach metadata endpoints or private services from the operator's machine. Parse the URL and reject non-http(s); resolve the host and refuse if any address is private, loopback, link-local or metadata; follow redirects manually and re-validate each hop. Hosts can be narrowed further via a shared OUTBOUND_ALLOWED_HOSTS list that never re-opens a blocked address. Blocked requests return ERR_URL_BLOCKED.
A tool result is written straight into the agent's context window, so an unbounded one is self-inflicted denial of service whose failure gets blamed on the model. bazaar_search and paid_call now serialise compactly (no indentation), page collections with a total and a notice explaining how to ask for more, and read the response body through a capped stream reader that cancels the stream instead of buffering a hostile body in full. Truncation is always stated in the result; an oversized discovery body returns ERR_RESPONSE_TOO_LARGE. Bounds are operator-configurable via MCP_MAX_RESULT_BYTES, MCP_MAX_UPSTREAM_BYTES, MCP_DEFAULT_PAGE_LIMIT and MCP_MAX_PAGE_LIMIT.
…bs#418) Lens facts that barely change — watched pairs, venues, the OpenAPI document, service status — forced an agent to call a tool every turn to keep them in context. Expose them as MCP resources instead, each with a stable URI, an accurate MIME type, and honest freshness: a content-hash version plus a lastModified per source, so a client can tell whether what it cached is still current. Status is regenerated per read and so is deliberately never cacheable. Default sources read only config and the on-disk OpenAPI document, keeping the stdio server free of a database dependency; a host with richer runtime state can inject its own descriptors.
server.ts now imports ./limits.js, so the resources change set cannot be based on main on its own. Note the merge order and the retarget so a reviewer knows why the branch starts where it does.
|
@Jonathan-ai9 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Coordination note — ten PRs are editing one 179-line fileIf your MCP PR has just gone red with a conflict, this is why, and it is my fault rather than yours. I published 22 MCP issues (#404–#425) as if they were independent. They are not: almost all of them land in Already merged:
Order I intend to take the rest in, so you can rebase once rather than repeatedly:
Please do not all rebase at once. If you are below the one currently in flight, wait until the one above you lands — otherwise you rebase, I merge someone else, and you rebase again. I will comment on your PR when it is your turn. One thing to settle between you: Nothing here reflects on the quality of the work. Several of these are among the best PRs this repo has had. |
|
#487 has landed — the I resolved #487's conflicts myself because nine PRs were stuck behind it. Three things on 1. Two refusal codes, deliberately separate. ERR_URL_BLOCKED // the target resolves somewhere we will never fetch (#488)
ERR_HOST_NOT_ALLOWED // reachable, but not on the paid_call allow-list (#487)If your PR adds a refusal, extend this vocabulary rather than starting a third. "I cannot reach there" and "I will not pay there" are different answers; whatever you add should be a third distinct answer, not a synonym for one of these. 2. The constructor takes 3. Push the rebase and I will review it. If it conflicts in a way that is not obvious, say so and I will resolve it rather than leave you stuck. |
Both merge commits on this branch ("Merge branch 'main' into
feat/mcp-resources", 4a553a6 and its first parent baf94a8) were resolved in
GitHub's browser conflict editor, which kept both sides' lines in place of
choosing one: `src/mcp/server.ts` imported the same symbols twice, left
`export interface McpLimits {` unclosed in front of main's
`readPackageVersion` import block, and put class fields inside the
constructor body. Nine TS1xxx syntax errors, and no conflict markers left to
point at the seam.
The tip tree is now the merge git's own three-way machinery produces: main
plus Divinity-Labs#416's result bounds plus this PR's resources, each side's gates intact.
History is untouched — no rebase, no force-push.
|
CI on this branch was red from Both merges on this branch were resolved in GitHub's browser editor, and each Fixed in 6920034 by redoing the merge with git rather than hand-patching: main + Local: tsc clean, build clean, 964 passed / 1 skipped, dropped-tests guard One decision this surfaced: #418 now exists on two branches. |
tsc --noEmit clean vitest 97 files, 1127 passing (up from 93 / 1068) dropped-tests none #325 GET /movers for market gainers and losers #327 GET /correlation for basket construction #485 net arbitrage spread against fees #494 declare the resources capability and expose pages over MCP Two fixes on merge. #325 shipped the route file and the OpenAPI entry but never registered it. The repo's own guard caught it: AssertionError: Documented but unregistered: ["GET /movers"] So the endpoint was documented, generated into clients, and did not exist. registerMoversRoutes already followed the house signature, so wiring it was an import and a call next to registerScreenerRoutes. That guard is worth its weight -- a documented route that 404s is worse than an undocumented one, because a client is generated against it. #327 added a console.error to src/routes/correlation.ts, which the logging hygiene test forbids. Now createLogger, the same change #388 and #318 needed in the previous batch. Three PRs in two batches have tripped that test, which suggests the rule is not discoverable enough from the outside -- worth a line in CONTRIBUTING rather than catching it per-PR. NOT merged, each failing on its own branch rather than from merge order: #460 Phoenix AMM adapter -- 5 failures alone, including its own networkVenueConfig and venues/registry tests #353 1 tsc error #354 1 tsc error #482 4 tsc errors #393 #431 #434 #472 conflict Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB
Closes #418
Summaryfeat(mcp): declare the resources capability and expose pairs, venues, OpenAPI spec, and status
Related issue
## SummaryLens advertised only
tools, so agents had to call a function every time they wanted data that rarely changes. This PR declares the MCPresourcescapability and implementsresources/listandresources/readfor the stable, cacheable content: watched pairs, venues, the OpenAPI document, and service status. Each resource has a stable URI, an accurate MIME type, and honest freshness information.Why
Exposing this data as resources lets an agent attach it to its context once instead of calling a tool repeatedly. That is cheaper for the agent (fewer tool calls, less context churn) and for the service (fewer upstream requests). Freshness metadata lets the agent tell whether what it cached is still current.
Changes
Declare
capabilities: { tools: {}, resources: {} }on the serverImplement
resources/listandresources/readhandlersExpose these resources with stable URIs:
lens://pairsapplication/jsonlens://venuesapplication/jsonlens://openapiapplication/json(orapplication/yamlif served as YAML)lens://statusapplication/jsonEach resource has a
name,description, andmimeTypein the listingFreshness: every read result includes a version and last-modified value derived from the real data (content hash for pairs/venues/OpenAPI, upstream timestamp where one exists, generation time for status), not the time of the request
lens://statusis marked as volatile in its description so agents do not cache it for long; the others document their expected change frequencyUnknown URIs return a proper MCP "resource not found" error, with no internal details leaked
Resource payloads go through the same size cap as tool results (see Tool results are unbounded #416), with truncation stated explicitly
Document the URIs, MIME types, and freshness semantics in the README and in each resource description
Tests
resourcescapabilityresources/listreturns all four resources with URI, name, and MIME typeresources/readforlens://pairsreturns valid JSON with the expected MIME typeresources/readforlens://openapireturns the OpenAPI documentNotes
resources/subscribeandlistChangednotifications. Freshness metadata covers the cache-validity need for now, and subscriptions can follow if wanted.Type of change
Checklist
npx tsc --noEmitpassesnpm run buildpasses