feat: port cueapi PR #630 — Agent Directory roster + describe MCP tools#26
Open
mikemolinet wants to merge 2 commits intofeat/parity-port-619-message-bcc-lightfrom
Open
feat: port cueapi PR #630 — Agent Directory roster + describe MCP tools#26mikemolinet wants to merge 2 commits intofeat/parity-port-619-message-bcc-lightfrom
mikemolinet wants to merge 2 commits intofeat/parity-port-619-message-bcc-lightfrom
Conversation
Adds `cueapi_send_message` tool with full §17 BCC-light support (`notify: [agent_ref, ...]` array). Each notify recipient gets a stripped notification copy alongside the main delivery, threaded with the main message so they can reply into the conversation. ## Server contract pinned Two non-obvious bits of the messaging-primitive contract are pinned by tests so refactors break loudly at unit-test time: 1. **`from` goes via X-Cueapi-From-Agent HEADER, not body.** The server reads it as `Header(default=None, alias='X-Cueapi-From-Agent')` in app/routers/messages.py. A refactor putting `from` in the body would technically still 400 via Pydantic `extra='forbid'`, but silently — the test pins the header placement so the failure surfaces at unit-test time. 2. **`idempotency_key` goes via Idempotency-Key HEADER, not body.** Same pattern. ## Schema-level guards (clean error UX) - `notify` capped at 10 entries via Zod (mirrors server's 422 cap). Caller gets a Zod error before any HTTP round-trip. - `priority` clamped to 1-5 via Zod `.int().min(1).max(5)`. - `body` capped at 32KB at the schema layer (server limit). ## §17 BCC-light semantics covered (8 tests) - Required fields → POST /v1/messages with body shape pinned - `from` via header (NOT body) — pinned - `notify=[a,b,c]` passes through verbatim - `notify=[]` is omitted from body (no field === empty list at server) - `notify` 11+ entries → Zod reject before HTTP - `idempotency_key` via header (NOT body) — pinned - `expects_reply=true` passes through; false omitted (omit-when-default pattern matching cueapi-cli #29) - `priority` outside 1-5 + `body` >32KB schema-rejected ## Scope (deliberate) Minimum-viable port focused on the PR #619 surface. The broader messaging-primitive lifecycle (`get`/`read`/`ack`/`inbox`/`sent` + agent identity) is intentionally OUT — tracked in `endpoints_missing.Messaging primitive (residual: ...)` for a follow-up port that mirrors cueapi-cli #29's full surface. ## Tests 8 new (38 → 46 in tests/tools.test.ts, **87/87 total pass**). ``` $ npm test Test Files 5 passed (5) Tests 87 passed (87) $ npm run build (clean) ``` ## Parity manifest - `endpoints_covered` now lists `POST /v1/messages` (with scope note). - `endpoints_missing` "Messaging primitive (...)" entry retitled to "residual" — POST removed from the gap, residual GET/agent endpoints retained for follow-up. - `ported_pr_history` adds PR #619 PORTED entry. ## Test plan - [x] `npm test` → 87/87 pass - [x] `npm run build` → clean - [ ] Manual smoke against staging cueapi: - Two-agent send (no notify) → main delivery only, X-Cueapi-From-Agent surfaces in recipient - Send with notify=[agt_x, agt_y] → response includes bcc_emitted=[msg_id, msg_id] (length 2) - Same idempotency_key replayed → second response has bcc_emitted=[] (no re-emit), main message_id matches first - notify=[primary_to] → silently de-duped (bcc_emitted=[]) - notify with 11 entries → server 422 (Zod catches at 11; if Zod is bypassed somehow, server is the next layer) ## Companion PRs - cueapi-cli #29 — already merged 2026-05-04 (added `cueapi messages send` with notify support). - cueapi-action — pending (action.yml exposure). - cueapi-python — pending (SDK port). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A of the Agent Directory productization (PRD:
trydock.ai/mike/agent-directory-productization-prd). Ports cueapi
PR #630 (still OPEN at port time per CTO greenlight in token
CTO-CMA-NUDGE-NEXT-WORK 2026-05-05) — eliminates the failure mode
where agents had to remember 6+ fields per recipient AND had no
way to discover the live roster.
Two new tools + expanded send_message description:
## `cueapi_agents_list`
Wraps the new `GET /v1/agents/roster` from cueapi PR #630.
Display-optimized snapshot for prompt-injection at session boot:
- Always-full list (no pagination)
- Drops opaque IDs / secrets / timestamps / tenancy metadata
- Adds derived `online`, `last_seen_relative`, `preferred_contact`
- Server-side ETag / `If-None-Match` → 304 (delegated to client)
- Cache-Control: private, max-age=300
The LLM calls this when it needs to message a peer — gets back a
clean directory it can scan, then passes the resolved name to
`cueapi_send_message`'s `to` field. Replaces the cue-fire dance
that caused the 2026-05-04 silent-drop incident.
## `cueapi_agents_describe`
Wraps the existing `GET /v1/agents/{name}` (richer management view —
webhook_url, full metadata, canonical timestamps). Distinct from
`cueapi_agents_list` which returns the lean roster shape.
## `cueapi_send_message` description tightened
Now references `cueapi_agents_list` as the discovery primitive +
calls itself out as "the natural primitive for inter-agent comm"
(PRD §Migration positioning).
## Tests
5 new (46 → 51 in tests/tools.test.ts, **92/92 total pass** across
all 5 test files):
- agents_list uses `/v1/agents/roster` (NOT `/v1/agents` — pin
against accidentally pointing at the management surface)
- agents_list takes no params (no body, no query — full list spec)
- agents_describe uses `GET /v1/agents/{name}` with name in path
- agents_describe URL-encodes the name (slug-form `agt_pm@mike`
needs the `@` encoded)
- agents_describe rejects empty name at the schema layer
`npm test` → 92/92 pass. `npm run build` → clean.
## Placeholder caveats
PR #630 was OPEN at port time. Self-described response shape in PR
description + diff is stable; tests pin path/method but not field
shape (handler is pure passthrough — when the server PR shape
evolves, the tools forward whatever the server returns). When PR
#630 merges + cueapi-mcp publishes:
1. CueAPI Desktop's roster poller (Surface 5 follow-up) calls
`cueapi_agents_list` every 5min, writes response to local
roster file. Replaces the manually-curated stub at
CueAPIDesktop/Resources/agent-roster.txt (shipped 2026-05-05
in cue-mac-app commit ee31508).
2. The handler-template prompt-injection (already shipped) reads
the live roster automatically — no handler change needed.
## Parity manifest
- `endpoints_covered`: GET /v1/agents/roster + GET /v1/agents/{name}
added.
- `endpoints_missing.Messaging primitive (...)` retitled — POST
/v1/messages + GET /v1/agents/roster + GET /v1/agents/{name} now
removed; remaining gap clarified (agent CRUD, message read-side
lifecycle).
- `ported_pr_history`: PR #630 marked PORTED placeholder with full
scope notes.
## Companion work
- cueapi PR #630 — OPEN, head 93951f4 at port time
- cue-mac-app ee31508 — Surface 5 stub end-to-end (handler-template
prompt-injection + bundled stub roster + Paths helper); awaiting
PR #630 merge to swap stub for live poller
- cueapi-mcp PR #25 (PR #619 BCC-light port) — open, this branch
bases off it for the test stub client's extraHeaders signature
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
govindkavaturi-art
pushed a commit
that referenced
this pull request
May 6, 2026
Closes 2 entries from `endpoints_missing` / `tool_param_drift` in cueapi-mcp #13's parity-manifest.json. ## PR #589 — effective payload on GET responses `cueapi_get_execution` and `cueapi_list_executions` are passthrough handlers, so the new `payload` field surfaces automatically without handler changes. Updated descriptions to call out the field so MCP hosts know it's there (effective payload = payload_override if set on the fire, else the parent cue's payload at delivery time — useful for audit, forensics, and "reply via fresh API call" flows from agents not running inside the bundled-worker handler context). ## PR #590 — server-side payload_override enforcement Added two new optional fields to `cueapi_create_cue` + `cueapi_update_cue` schemas and handler passthroughs: - `require_payload_override` (bool) — when true, fires without `payload_override` are rejected with HTTP 400 `payload_override_required`. - `required_payload_keys` (string[]) — keys that must be present in the resolved post-merge payload. Missing keys yield HTTP 400 `missing_required_payload_keys` with a `missing_keys` list. Updated `cueapi_fire_cue` description to surface the 3 new error codes (the third being `inconsistent_message_instruction`, which fires when both `message` and `instruction` are required and the server enforces byte-equality so different recipient handlers route on the same content). ## Tests 7 new (38 → 45 in `tests/tools.test.ts`, 86 total across all 5 test files): - cueapi_create_cue: smoke test (didn't exist before — first PR to add HTTP-contract tests for the create handler), passes PR #590 fields through to body, false/empty edge cases, omits fields not explicitly passed (server-default contract) - cueapi_update_cue: 4 new tests covering the two new field passthroughs + their false/empty edge cases (sparse-update semantics — explicit false must round-trip, not be conflated with unset) `npm test` — 86/86 pass. `npm run build` — clean. ## Parity manifest - `tool_param_drift.cueapi_get_execution.covered_response_fields` now lists `payload`. Removed from missing. - `tool_param_drift."cueapi_create_cue / cueapi_update_cue".covered_request_fields` now lists the two new PR #590 fields. Removed from missing. - `ported_pr_history` updated: PR #589 + #590 now PORTED. ## Companion ports - cueapi-cli #26 (PR #589 + #590 port) — already merged 2026-05-04. - cueapi-action #2 — pending. - cueapi-python #23 — pending. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
fe47414 to
1bafdff
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase A of the Agent Directory productization (PRD: trydock.ai/mike/agent-directory-productization-prd). Ports cueapi PR #630 (OPEN at port time per CTO greenlight) — surfaces the new
GET /v1/agents/roster+ the existingGET /v1/agents/{name}as MCP tools so the LLM can discover peers natively instead of remembering 6+ fields per recipient.Branch base:
feat/parity-port-619-message-bcc-light(PR #25). The new test stub client signature from #25 (extraHeaders) is reused; rebases cleanly to main once #25 merges.What this PR adds
cueapi_agents_listonline/last_seen_relative/preferred_contact. Server-side ETag/304 (delegated to client).cueapi_agents_describecueapi_send_message(description tightened)cueapi_agents_listas the discovery primitive + positions itself as "the natural primitive for inter-agent comm" per PRD §Migration.Why this matters (PRD context)
The 2026-05-04 silent-drop incident (CMA's outbound replies dropped for 24h on wrong task name) was the trigger for the PRD. The 6-field cue-fire dance + no roster discovery meant agents had to memorize cue_id + task name + payload_override shape per recipient. With these tools the LLM:
cueapi_agents_listonce → sees all peers + their reachability state.cueapi_send_messagewithto: <name>.Surface 5 (session-boot prompt-injection) shipped end-to-end on the cue-mac-app side at commit ee31508 with a manually-curated stub roster. When cueapi PR #630 merges + this PR ships in a new cueapi-mcp release, cue-mac-app's Desktop poller (5min cadence) calls
cueapi_agents_listand writes the response to the local roster file — replacing the stub with live data. The handler-template prompt-injection already shipped reads the roster automatically; no further handler change needed.Tests
5 new (46 → 51 in
tests/tools.test.ts, 92/92 total pass across all 5 test files):agents_list uses GET /v1/agents/roster (NOT /v1/agents)/v1/agents/rosteris the prompt-injection surface;/v1/agentsis the management list (paginated, tenancy metadata). Don't confuse them.agents_list takes no parameters (no pagination, no filters)agents_describe uses GET /v1/agents/{name}agents_describe URL-encodes the name in the pathagt_pm@mikeneeds@encoded — verify URL-encoding works for cross-tenant slugs.agents_describe rejects empty name at the schema layerPlaceholder caveats
PR #630 was OPEN at port time. Self-described response shape in the PR description + diff is stable. Tests pin path/method but not field shape (handler is pure passthrough — when the server PR shape evolves, tools forward whatever the server returns). If PR #630 lands with a meaningfully different shape, this PR can be revised before its own merge.
Parity manifest
endpoints_covered: GET /v1/agents/roster + GET /v1/agents/{name} added.endpoints_missing.\"Messaging primitive (...)\"retitled to clarify residual gap (agent CRUD beyond list/describe; message read-side lifecycle).ported_pr_history: PR #630 marked PORTED placeholder with caveats.Test plan
npm test→ 92/92 passnpm run build→ cleancueapi_agents_list→ returns{generated_at, agents: [...]}with online state derivation correctcueapi_agents_describe { name: "cue-mac-app" }→ returns full AgentResponsecueapi_agents_describe { name: "agt_x@mike" }→ URL-encoding works, single round-tripcueapi_agents_list, verify it can pick the right recipient + send viacueapi_send_messageCompanion PRs
🤖 Generated with Claude Code