Skip to content

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
feat/agent-directory-mcp-tools
Open

feat: port cueapi PR #630 — Agent Directory roster + describe MCP tools#26
mikemolinet wants to merge 2 commits intofeat/parity-port-619-message-bcc-lightfrom
feat/agent-directory-mcp-tools

Conversation

@mikemolinet
Copy link
Copy Markdown
Collaborator

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 existing GET /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

Tool Endpoint Notes
cueapi_agents_list GET /v1/agents/roster NEW per cueapi PR #630. Display-optimized snapshot: always-full list, drops opaque IDs, derived online/last_seen_relative/preferred_contact. Server-side ETag/304 (delegated to client).
cueapi_agents_describe GET /v1/agents/{name} Existing endpoint; just hadn't been wrapped. Richer management view (webhook_url, full metadata).
cueapi_send_message (description tightened) POST /v1/messages Now references cueapi_agents_list as 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:

  1. Calls cueapi_agents_list once → sees all peers + their reachability state.
  2. Picks recipient by name → calls cueapi_send_message with to: <name>.
  3. Server resolves routing internally — no cue_id, no task name extrapolation.

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_list and 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):

Test What it pins
agents_list uses GET /v1/agents/roster (NOT /v1/agents) Critical: /v1/agents/roster is the prompt-injection surface; /v1/agents is the management list (paginated, tenancy metadata). Don't confuse them.
agents_list takes no parameters (no pagination, no filters) PR #630 spec: always-full list. Future client trying to filter shouldn't slip through.
agents_describe uses GET /v1/agents/{name} Path + method pinning.
agents_describe URL-encodes the name in the path Slug-form agt_pm@mike needs @ encoded — verify URL-encoding works for cross-tenant slugs.
agents_describe rejects empty name at the schema layer Zod min(1) before HTTP.
$ npm test
Test Files  5 passed (5)
     Tests  92 passed (92)

$ npm run build
(clean)

Placeholder 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 pass
  • npm run build → clean
  • Manual smoke against staging cueapi (after PR #630 merges):
    • cueapi_agents_list → returns {generated_at, agents: [...]} with online state derivation correct
    • cueapi_agents_describe { name: "cue-mac-app" } → returns full AgentResponse
    • cueapi_agents_describe { name: "agt_x@mike" } → URL-encoding works, single round-trip
    • LLM dogfood: spawn a fresh CC session, prompt-inject roster from cueapi_agents_list, verify it can pick the right recipient + send via cueapi_send_message

Companion PRs

🤖 Generated with Claude Code

mikemolinet and others added 2 commits May 5, 2026 07:55
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>
@govindkavaturi-art govindkavaturi-art force-pushed the feat/parity-port-619-message-bcc-light branch from fe47414 to 1bafdff Compare May 6, 2026 23:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant