Skip to content

feat(tools): expose voice, transcribe, captions, dispatch routing and payment rails (#72) - #63

Open
yakimoto wants to merge 3 commits into
mainfrom
ce/fleet-tools
Open

feat(tools): expose voice, transcribe, captions, dispatch routing and payment rails (#72)#63
yakimoto wants to merge 3 commits into
mainfrom
ce/fleet-tools

Conversation

@yakimoto

@yakimoto yakimoto commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Part of wave-av/wave-context#48. Addresses the mcp-server half of wave-av/wave-context#72.

The MCP server exposed 18 tools across Analytics / Billing / Streams / Studio — and none for the shipped fleet products. This adds 7, taking the registry to 25.

Tool Calls Notes
wave_speak POST /v1/voice returns a receipt, not audio — see below
wave_transcribe POST /v1/transcribe JSON transcript
wave_caption POST /v1/captions the caption file itself (VTT / SRT / JSON cues)
wave_route dispatch POST / classify a prompt → { route, decision }
wave_list_routing_profiles dispatch GET /profiles flag-gated server-side
wave_find_paid_services GET /v1/mpp/services public, no key
wave_payment_schemes GET /v1/{x402,mpp}/facilitator/supported public, no key

Every path is grounded in the spoke's own router, not the API spec

The spec at wave-av/api-spec declares /voice/generate, /voice/voices, /voice/clone,
/captions/{jobId}/download, /transcribe/{id}, /phone/*, /podcast/*none of which exist.
Each spoke owns its whole /v1 namespace with an exact-match router and 404s anything else, and the
gateway forwards /v1/<product> verbatim (spoke-routing.ts is a pure prefix map, no rewriting). So
building against the spec would have shipped seven tools that 404. Filed as wave-av/api-spec#33; the
paths here come from git show origin/main:src/api.ts in each spoke.

Note this also means phone and podcast are not buildable: wave-phone-edge and
wave-podcast-edge have no api.ts at all — their src/ is favicon.ts, landing.ts,
tokens.css.ts, worker.ts. They're landing shims, and /v1/phone / /v1/podcast appear nowhere in
the gateway's routing table. No tool is claimed for either; that's the honest planned state.

Three design decisions worth flagging

1. wave_speak returns a receipt, not audio. POST /v1/voice returns audio/mpeg bytes. A tool
result is a text block, so the only ways to return audio are base64 (megabytes into the model's
context, for something it cannot listen to) or silently mangling the bytes through res.text(). It
reports content type, size, and billed usage instead, and says plainly that the bytes aren't returned
and how to get them. If you'd rather it return base64 behind an opt-in flag with a size cap, say so
and I'll add it
— this was the judgment call I was least certain about.

2. Audio input is by URL. Both spokes accept a ?url= they fetch server-side
(transcribe.ts, source.ts). That's the only sane path for tool arguments, and it's how an agent
already holds media.

3. A separate base URL, and a separate fetch helper. getBaseUrl() is wave.online (the app
surface, /api/v1/billing/...); the product spokes and payment rails live behind the gateway at
api.wave.online/v1/.... Reusing the existing waveFetch would have pointed every call at the wrong
host and forced Content-Type: application/json onto content-type-sensitive spokes. Hence
getApiBaseUrl() + gatewayFetch, with the caller owning the content type. wave-dispatch gets its
own helper again — it isn't behind the gateway at all.

Metered tools append a [billed: <meter>, <n> min] line from the spoke's x-wave-meter /
x-wave-usage-minutes response headers, so an agent can see what the call it just made cost.

Only the read half of the payment rails is exposed. The facilitator's verify and settle are
the money-moving side and are deliberately not wrapped as agent tools.

Verification

npm run type-check   # exit 0
npm run lint         # exit 0 (eslint --max-warnings 0)
npm run build        # ESM build success

Registry smoke check (the registry throws on a duplicate name at import, so a clean import is itself a check):

total tools: 25
new tools registered: 7 / 7
  ✓ wave_speak — text, voiceId, modelId
  ✓ wave_transcribe — url, engine, language, diarize
  ✓ wave_caption — url, format, engine, language
  ✓ wave_route — prompt, profile
  ✓ wave_list_routing_profiles — (no args)
  ✓ wave_find_paid_services — q, protocol, tag, topK
  ✓ wave_payment_schemes — rail

Live proof — the two public tools need no key, so they were run against production:

=== wave_payment_schemes rail=x402 ===
{"supportedNetworks":[{"networkId":"eip155:8453","version":"x402@1","asset":"0x833589…2913"},
                      {"networkId":"eip155:84532","version":"x402@1","asset":"0x036cbd…cf7e"}],
 "schemes":["exact"],"facilitatorUrl":"https://gateway.wave.online/v1/x402/facilitator"}

=== wave_find_paid_services q='video transcription' ===
Error 503: {"error":"discovery_unavailable","reason":"vectorize_unbound"}

That 503 is a real production gap, not a bug in this PR — MPP service discovery is dark because its
Vectorize index isn't bound. Filed as wave-av/wave-gateway#730. The tool surfacing it verbatim rather
than swallowing it is the intended behavior.

The five authenticated tools could not be exercised end-to-end here: that needs a live WAVE_API_KEY,
and every product call is metered, so proving them bills real money. Worth doing deliberately against
a test org rather than incidentally in a PR.

Caveats

  • This repo has no test suite (no test script, zero test files), so the gate is type-check +
    lint + build, matching existing convention. Test infrastructure is worth adding — I'll file it
    separately rather than smuggle a framework into this PR.
  • CI is currently hard-down org-wide on billing (wave-rig#174), so checks can't run here.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Note

Medium Risk
New external API integrations and billing-metered calls increase exposure to wrong-host misconfiguration and real usage charges; payment discovery is read-only and public routes omit API keys by design.

Overview
Adds seven MCP tools so agents can call WAVE fleet products and payment discovery that were missing from the registry (25 tools total). New modules wire voice / transcribe / captions through gatewayFetch to api.wave.online (/v1/voice, /v1/transcribe, /v1/captions), dispatch via getDispatchUrl() (wave_route, wave_list_routing_profiles), and public MPP/x402 discovery without sending an API key (wave_find_paid_services, wave_payment_schemes).

Auth and HTTP layering split the app host (getBaseUrl / existing waveFetch) from the gateway (getApiBaseUrl, getDispatchUrl). gatewayFetch does not force Content-Type: application/json and surfaces x-wave-meter / x-wave-usage-minutes via usageNote on metered calls.

Behavior choices: wave_speak returns a receipt (type, size, billing) instead of audio bytes through text MCP results; transcribe/caption tools take public audio URLs in query params. Payment tools intentionally expose only discovery endpoints, not verify/settle.

Reviewed by Cursor Bugbot for commit 43ddba1. Configure here.


Summary by cubic

Adds seven MCP tools for fleet products and payments (voice TTS, transcribe STT, captions, dispatch routing, and public payment discovery). Also adds gateway/dispatch fetch helpers, surfaces billed usage in tool outputs, updates SSOT facts to 25 tools, and removes a private-repo path from a payments comment (no runtime change).

  • New Features

    • Fleet: wave_speak (POST /v1/voice) returns a receipt; wave_transcribe (POST /v1/transcribe) JSON transcript; wave_caption (POST /v1/captions) VTT/SRT/JSON cues.
    • Dispatch: wave_route (POST /) classifies prompts; wave_list_routing_profiles (GET /profiles) is flag-gated.
    • Payments (public): wave_find_paid_services (GET /v1/mpp/services); wave_payment_schemes (GET /v1/{x402,mpp}/facilitator/supported). No Authorization header is sent on these public routes.
    • Metered calls append [billed: <meter>, <n> min] from response headers.
    • SSOT: .wave/repo.json and capabilities.json updated to 25 tools with claims and tool tables.
  • Migration

    • New optional env vars: WAVE_API_BASE_URL (defaults to https://api.wave.online) and WAVE_DISPATCH_URL (defaults to https://dispatch.wave.online). WAVE_BASE_URL remains for the app host.
    • STT/captions take audio by URL; provide publicly reachable links.
    • wave_speak returns no audio bytes; use the SDK or direct API if you need the audio.

Written for commit 05bc83f. Summary will update on new commits.

Review in cubic

@changeset-bot

changeset-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 05bc83f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cursor

cursor Bot commented Jul 26, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_0226eb3a-27a1-46b9-a918-c5ee7e45bbc3)

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 48ce2c94-5dfb-4183-965a-a7c0b8f81a34

📥 Commits

Reviewing files that changed from the base of the PR and between a199245 and 05bc83f.

📒 Files selected for processing (8)
  • .wave/repo.json
  • capabilities.json
  • src/auth.ts
  • src/tools/dispatch.ts
  • src/tools/fleet.ts
  • src/tools/index.ts
  • src/tools/payments.ts
  • src/tools/shared.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ce/fleet-tools
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch ce/fleet-tools

Comment @coderabbitai help to get the list of available commands.

Jake Fineman added 2 commits July 26, 2026 18:32
The registry went 18 -> 25 in the previous commit but the facts still said 18, so the generated README would have under-reported the surface and the drift gate would have measured against a stale count.

Adds 7 capability entries and 7 claims (all resolver-verified against source), three tool tables, the two new env vars, and corrects the count in both .wave/repo.json and the capabilities.json 18-tools tag.

Verified: 25 capabilities for 25 registered tools; all 29 claim resolvers resolve against the working tree.
…ckage

The public-repo content-policy gate blocked this correctly: mcp-server ships to npm, and the source comment cited a file path inside a private WAVE repo. The substance of the comment (these routes are public, rate-limited per IP) is kept; only the internal path is removed.
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