Skip to content

feat(dep-2): Hyper (Bun) HTTP+MCP service + live per-token ownership#2

Merged
zkSoju merged 3 commits into
mainfrom
feat/dep-2-hyper-service
May 24, 2026
Merged

feat(dep-2): Hyper (Bun) HTTP+MCP service + live per-token ownership#2
zkSoju merged 3 commits into
mainfrom
feat/dep-2-hyper-service

Conversation

@zkSoju
Copy link
Copy Markdown
Contributor

@zkSoju zkSoju commented May 24, 2026

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

  • Part 1 (live activation, unchanged from first pass): liveOwnerTokenIds (sonar Token index) + liveCandiesBalances (CandiesHolderBalance); live getHoldings now returns real tokenIds; getNftsForOwner backed by live ownership; fixed a latent fail-soft crash.
  • Part 2 (Hyper service): vendored src/hyper/ (source-distributed); src/routes.ts declares 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.ts is the Bun entrypoint.
  • Package reshape: Node-pure library → private Bun service (dropped main/types/exports; Bun scripts; Bun-native tsconfig). Domain fns remain importable internally.

Verified: tsc --noEmit clean; 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).

⚠️ Review notes (informed-merge)

  1. Hyper is 0.1.0 — needed 2 in-place patches to vendored components (openapi-zod, openapi/generate), a DOM-lib fix for an @types/bun skew, and a workaround for a destructive hyper openapi <path> CLI footgun. Source-distributed = we own + maintain the 5,274 vendored lines + patches.
  2. Live unverifiable — sonar belt not yet deployed/reindexed; ownership paths are hermetic-only.
  3. Candies holder_id filter unconfirmed against live schema — isolated to one constant.

Follow-ups (post-merge)

  • DEP-3: deploy the service + register the gateway tenant.
  • Surface Candies in getHoldings/getNftsForOwner (needs collectionKey registration).
  • Live-smoke once sonar deploys (reconcile tokenIds vs TrackedHolder.tokenCount; confirm Candies filter).

🤖 Authored by a Claude Code agent dispatched via the cycle-006 cross-repo coordinator. Review-gated.

zkSoju and others added 3 commits May 24, 2026 12:58
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>
@zkSoju zkSoju merged commit 355446b into main May 24, 2026
3 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +183 to +185
...(r.params ? { params: { type: "object" } } : {}),
...(r.query ? { query: { type: "object" } } : {}),
...(r.body ? { body: { type: "object" } } : {}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment thread src/routes.ts
Comment on lines +124 to +127
options.chains = query.chains
.split(",")
.map((s) => Number(s.trim()))
.filter((n) => Number.isFinite(n));
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 Badge 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 👍 / 👎.

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.

1 participant