feat(dep-2): Hyper (Bun) HTTP+MCP service + live per-token ownership#2
Conversation
Wire the sonar belt-factory per-token owner index into live mode: - liveOwnerTokenIds(address, contractLower) queries the new Token index (collection + owner + isBurned=false) - liveCandiesBalances(address) for the ERC-1155 Candies case (CandiesHolderBalance, holder_id + amount>0) - live getHoldings now populates real tokenIds (was tokenIds: []) - getNftsForOwner backed by liveOwnerTokenIds in live mode, joining codex metadata, fail-soft to fixtures when the index is unreachable Also hardens the live getHoldings fail-soft: a fully-unreachable endpoint now degrades to fixture holdings + a degraded envelope instead of throwing (README contract). Hermetic coverage in tests/live-ownership.test.ts stubs fetch with the known belt schema shapes — the belt-factory branch is not yet deployed/reindexed so there is no live endpoint to verify against. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…art 2) Add a service transport exposing getHoldings / getNftsForOwner / getNftMetadata over HTTP, with an OpenAPI 3.1 spec + MCP tool manifest derived from a single ROUTES table (src/server/). The library stays the core — the server only calls it; index.ts/types.ts exports are unchanged. - src/server/routes.ts: single source-of-truth route table - src/server/openapi.ts: OpenAPI 3.1 doc with component schemas mirroring types.ts - src/server/mcp.ts: MCP tool manifest (Hyper-shaped) - src/server/server.ts: Bun.serve runtime + exported `handle` fetch handler - src/server/emit-openapi.ts: writes openapi.json (consumer drift-CI anchor) - openapi.json: emitted 3.1 spec, committed for drift-CI - tests/server-transport.test.ts: hermetic (calls handle() directly, no port/Bun) Full Hyper (hyperjs.ai) adoption deferred: it installs cleanly in isolation but vendoring ~22 Bun-coupled framework files + a @hyper alias would dominate the diff and break this repo's Node-pure library contract. The minimal server follows Hyper's route/OpenAPI/MCP conventions so a later swap is low-friction. Rationale in src/server/README.md. The library tsconfig excludes src/server so `npm run build`/`typecheck` stay Node-pure; the server has its own tsconfig.server.json + `npm run typecheck:server`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nimal server) Node-pure publishable-library constraint released — inventory-api is a building consumed over HTTP + MCP, not an npm package. Replace the minimal Bun.serve fallback with a genuine Hyper (hyperjs.ai) service: one route declaration per endpoint generates the runtime + OpenAPI 3.1 + MCP from a single source. - Vendor Hyper source-distributed components (bun create hyper + hyper add openapi openapi-zod mcp) under src/hyper/ (hyper.config.json + hyper.lock.json) - src/routes.ts: 3 routes (getHoldings, getNftsForOwner, getNftMetadata) with zod query schemas, meta.mcp tool descriptions, throws() + examples - src/app.ts: serves HTTP + /openapi.json + /docs (Swagger) + /.well-known/mcp.json + POST /mcp (JSON-RPC); business graph drives OpenAPI/MCP generation - src/emit-openapi.ts / src/emit-mcp.ts: write openapi.json + mcp.json (drift-CI anchors) - tests/service.test.ts: HTTP + OpenAPI + MCP coverage via app.fetch (offline) - Two local fixes to vendored components (source-distributed = editable): openapi-zod array-def cast, openapi/generate unknown-spread guard - package shape: Node-pure library -> private Bun service (dropped main/types/ exports/files; bun.lock replaces package-lock.json); tsconfig is Bun-native (@types/bun, @hyper/* alias, DOM lib for Hyper's Request/HeadersInit globals); vitest.config maps @hyper/* so tests resolve the vendored components - index.ts reframed as the internal domain barrel (route handlers + tests import it) Part 1 (live-sonar liveOwnerTokenIds/liveCandiesBalances + inventory activation) is byte-for-byte unchanged. All 119 tests pass offline; tsc clean; Bun server smoke verified (all 3 routes + /openapi.json + /docs + MCP manifest + JSON-RPC tools/call). Live sonar still not deployed — ownership paths covered hermetically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1d0f3a0174
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ...(r.params ? { params: { type: "object" } } : {}), | ||
| ...(r.query ? { query: { type: "object" } } : {}), | ||
| ...(r.body ? { body: { type: "object" } } : {}), |
There was a problem hiding this comment.
Expose path params in MCP tool schemas
Build the MCP inputSchema from the route path, not only from r.params schema declarations. In this commit all three tools use path params (e.g. :address, :contract, :tokenId) but none declare a params schema, so the generated manifest omits params. MCP clients that follow the schema will call tools without path arguments, and app.invoke() then fails with invoke: missing path param, making these tools effectively unusable through manifest-driven clients.
Useful? React with 👍 / 👎.
| options.chains = query.chains | ||
| .split(",") | ||
| .map((s) => Number(s.trim())) | ||
| .filter((n) => Number.isFinite(n)); |
There was a problem hiding this comment.
Reject invalid chain filters instead of dropping them
Do not silently filter out non-numeric chains values. This parser currently converts each token with Number(...) and then drops invalid entries with Number.isFinite, so a request like ?chains=abc degrades to chains=[] and returns unfiltered/default holdings instead of a 400. That hides client input errors and changes query semantics compared with the domain-level validation in getHoldings.
Useful? React with 👍 / 👎.
DEP-2 — inventory-api becomes a Hyper (Bun) HTTP+MCP service (cross-repo cycle-006)
Activates per-token ownership against the new sonar index, and converts the library into a Hyper-on-Bun service consumed over HTTP + MCP (the no-npm decision: honeyroad reads this over the wire, never as a package). Unblocks honeyroad's Stash.
Part of #1 (master task DEP-2). Intentionally does NOT
Closes #1— that coord issue also tracks DEP-3 (deploy + gateway), which remains open. DEP-2 completion is recorded against the coordinator bead.What changed
liveOwnerTokenIds(sonarTokenindex) +liveCandiesBalances(CandiesHolderBalance); livegetHoldingsnow returns realtokenIds;getNftsForOwnerbacked by live ownership; fixed a latent fail-soft crash.src/hyper/(source-distributed);src/routes.tsdeclares 3 routes (getHoldings/getNftsForOwner/getNftMetadata) — one decl → HTTP + OpenAPI 3.1 (/openapi.json,openapi.json) + MCP (/.well-known/mcp.json,POST /mcp) + typed client.src/app.tsis the Bun entrypoint.main/types/exports; Bun scripts; Bun-native tsconfig). Domain fns remain importable internally.Verified:
tsc --noEmitclean; 119 tests pass (incl. 16 new service tests: routes, error-mapping, OpenAPI, MCP JSON-RPC); Bun server smoke (3 routes + /openapi + /docs + MCP) green; injection-safe (JSON.stringify-escaped GraphQL).openapi-zod,openapi/generate), a DOM-lib fix for an@types/bunskew, and a workaround for a destructivehyper openapi <path>CLI footgun. Source-distributed = we own + maintain the 5,274 vendored lines + patches.holder_idfilter unconfirmed against live schema — isolated to one constant.Follow-ups (post-merge)
🤖 Authored by a Claude Code agent dispatched via the cycle-006 cross-repo coordinator. Review-gated.