Skip to content

Three AgentToolkit tools call paths that cannot succeed: two are gateway-denied (403), one 404s at the captions spoke #63

Description

@yakimoto

Three of AgentToolkit's ten tools call gateway paths that cannot succeed. Two are live-proven against production; the third is proven from the spoke's own router.

1 & 2 — wave_show_graphic and wave_moderate_chat hit fail-closed prefixes

// src/tools/AgentToolkit.ts
this.call('POST', '/v1/graphics/show', params)                     // wave_show_graphic
this.call('POST', '/v1/moderation/action', params)                 // wave_moderate_chat

graphics and moderation are not in the gateway's V1_GROUPS catalog (wave-gateway/src/scope-groups.ts — 139 groups mined from WSC). The catalog has graphics-engine, not graphics, and no moderation at all. Every listed group becomes a longest-prefix scope rule; requiredScope() then does:

// wave-gateway/src/scopes.ts
// Under an enforced prefix but unmapped → FAIL-CLOSED (never silently allow an unbilled route).
if (ENFORCED_PREFIXES.some((p) => path.startsWith(p))) return { deny: true };

Live proof against https://api.wave.online — note the control line:

/v1/streams                        402
/v1/clips                          402
/v1/switcher/abc/switch            402
/v1/graphics/show                  403   <-- denied
/v1/moderation/action              403   <-- denied
/v1/analytics/stream/abc/qoe       402
/v1/replay/poi                     402
/v1/cameras/abc/control            402
/v1/definitely-not-real-xyz        403   <-- known-bogus control

402 means the prefix is mapped and the billing gate fired first. 403 means the gateway refused to map the path at all. Both tools land in the same bucket as a deliberately nonsense path.

3 — wave_start_captions was orphaned by the spoke migration

this.call('POST', '/v1/captions/start', params)                    // wave_start_captions

/v1/captions/* now routes to the captions spoke, which owns the whole namespace with an exact-match router:

// wave-captions-edge/src/api.ts @ origin/main
if (url.pathname === '/v1/captions') { ... }
if (url.pathname === '/v1/live/caption') { ... }
// …
return json({ error: 'not found' }, 404);

So /v1/captions/start reaches the spoke and 404s. It cannot be caught by probing — the prefix is routed, so an unauthenticated request returns 402 exactly like a valid path (the billing gate fires before routing). This one is only visible from the spoke source.

The real endpoint is POST /v1/captions, which #58 wraps as wave_caption. wave_start_captions was presumably correct before captions moved behind a spoke.

Not a bug, but worth deciding

AgentToolkit targets https://api.wave.online/v1/* (the product gateway). @wave-av/mcp-server targets https://wave.online/api/v1/* (the app surface) for the same conceptual operations/api/v1/streams/{id}/captions/start, /api/v1/switcher/{id}/control, /api/v1/cameras/{id}/control. Two SDKs for the same platform disagree on both the host and the path shape. At most one is right, and nothing currently forces them to converge. Worth settling deliberately rather than letting it drift further.

Suggested fix

  • wave_start_captions → repoint at POST /v1/captions, or remove it as superseded by wave_caption (feat(tools): fleet, dispatch and payment toolkits (#72) #58).
  • wave_show_graphic / wave_moderate_chat → establish where these actually live (the graphics-engine group exists; the moderation surface needs finding) and repoint, or mark them planned in .wave/repo.json and remove them from the advertised tool list. Shipping a tool that returns 403 is worse than not shipping it.
  • Add a CI check that every path in the toolkits resolves to a mapped gateway prefix. The 402-vs-403 discriminator above is mechanical and needs no credential, so it can run unauthenticated.

Blocked on #60 in practice — changing tool paths with no running test suite is how the next one of these ships.

Related: wave-av/api-spec#33 (the spec over-declares this surface). Surfaced while building #58 for wave-av/wave-context#72.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageOn the board but missing Type/Area/Priority

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions