feat(commands): add /mcp, the per-server detail /mcps has no room for #1001

Merged
coilysiren merged 1 commit from aos/claude/mcp-singular-command into main 2026-08-19 01:11:38 +00:00
Member

You asked for a singular /mcp that puts server detail in a thread, and whether /mcps should thread too. This adds the singular and argues against the threading, for a reason that only shows up in the declaration.

Why neither threads

/mcps is already ephemeral. Ephemeral: true in its CommandDefinition, so it answers the caller alone and nobody else in the channel sees it.

That decides the threading question, because threadForReply hangs a thread off a real channel message (MessageThreadStartComplex(channelID, message.ID, …)) and an ephemeral interaction response is not one. Threading either command means making the roster public first. So it is not a rendering change, it is a change to who may read the answer, and it would trade a private diagnostic for a channel artifact plus a nesting fallback (the code already refuses nesting) plus extra API calls inside the three-second interaction deadline.

Length already has an answer: boundMCPRoster cuts to fit and says it cut.

If you want a durable shareable record rather than a private read, that is a real want and threading serves it. It is a different decision from this one, and I have not made it here.

What /mcp <server> adds

The half an index can never carry: what each tool does. /mcps gives names only, and a description per tool is exactly what does not fit in a list of every server.

  • The argument carries choices, not a pattern. The doctrine in docs/sirens-echo-commands.md says a value naming a thing takes the tightest bound available, and the roster's own stability note records that every entry is fixed at image build. So the set is closed and Discord's picker can offer it.
  • The roster now reaches JobCommands, so registration and dispatch read the same set. A published choice that outlived its roster is refused at dispatch rather than trusted.
  • No MCP configured publishes no /mcp, rather than a command whose only required argument names nothing a caller could pick. Validate also now refuses a required string closed to an empty choice set, which is that same mistake made a different way.
  • A tool description is the server's own text, so it is flattened to one line and bounded by a new SIRENS_ECHO_MCP_TOOL_SUMMARY_RUNES (90).
  • Same posture as the plural throughout: ephemeral, above the job guard, no addresses, and the three distinct states (n tools / no tools / did not answer this turn) preserved.

Why this is worth doing now

Dowel has 11 configured servers, and #932/#943 put its tool surface around 86 tools against mcpsReplyBudget of 1800 characters. My arithmetic says the roster renders around 1900, so /mcps is probably already dropping a server behind the truncation notice on that lane. That is an estimate from counting the roster, not a measurement: sirens_echo.commands does not exist as a metric yet, so no invocation has been recorded anywhere I can read.

One thing to look at in review

docs/sirens-echo-commands.md was at 7958 of its 8000-character cap, so there was room for about 40 characters of new documentation. Adding a 41st page is not available either: the docs count is at 40 of 40, and the hook says splitting to clear a size cap "trades one violation for another."

So I merged the /mcps section into one covering both commands and compressed it to fit. Every fact in the original survives (what it reads, the three states, no addresses, ephemeral, the job guard, the truncation notice) and the wording is tighter. Nothing outside that section was touched. Worth a read as prose rather than as a diff, since compression is where meaning goes missing.

The underlying issue is that this page is full, and the next command to be documented hits the same wall harder. Not fixed here.

Verification

just gate passes: build, policy-check, vet, test, test-skips, pre-commit. go test -race ./... clean.

Seven new tests: the singular names what each tool does and does not leak another server's, a verbose description cannot spend the whole reply, a multi-line description is flattened, a silent server stays distinct from an empty one, the argument is closed to the roster and binds accordingly, no roster publishes no command, and the registered form carries its choices to Discord.

Not exercised against Discord. Nobody has invoked any slash command on any lane yet (#988), so this is the same untested surface as the rest of the command set, one command wider.

You asked for a singular `/mcp` that puts server detail in a thread, and whether `/mcps` should thread too. This adds the singular and **argues against the threading**, for a reason that only shows up in the declaration. ## Why neither threads **`/mcps` is already ephemeral.** `Ephemeral: true` in its `CommandDefinition`, so it answers the caller alone and nobody else in the channel sees it. That decides the threading question, because `threadForReply` hangs a thread off a real channel message (`MessageThreadStartComplex(channelID, message.ID, …)`) and an ephemeral interaction response is not one. **Threading either command means making the roster public first.** So it is not a rendering change, it is a change to who may read the answer, and it would trade a private diagnostic for a channel artifact plus a nesting fallback (the code already refuses nesting) plus extra API calls inside the three-second interaction deadline. Length already has an answer: `boundMCPRoster` cuts to fit and says it cut. **If you want a durable shareable record rather than a private read, that is a real want and threading serves it.** It is a different decision from this one, and I have not made it here. ## What `/mcp <server>` adds The half an index can never carry: **what each tool does**. `/mcps` gives names only, and a description per tool is exactly what does not fit in a list of every server. * The argument carries **`choices`**, not a pattern. The doctrine in `docs/sirens-echo-commands.md` says a value naming a thing takes the tightest bound available, and the roster's own stability note records that every entry is fixed at image build. So the set is closed and Discord's picker can offer it. * The roster now reaches `JobCommands`, so **registration and dispatch read the same set**. A published choice that outlived its roster is refused at dispatch rather than trusted. * **No MCP configured publishes no `/mcp`**, rather than a command whose only required argument names nothing a caller could pick. `Validate` also now refuses a required string closed to an empty choice set, which is that same mistake made a different way. * A tool description is the server's own text, so it is flattened to one line and bounded by a new `SIRENS_ECHO_MCP_TOOL_SUMMARY_RUNES` (90). * Same posture as the plural throughout: ephemeral, above the job guard, no addresses, and the three distinct states (`n tools` / `no tools` / `did not answer this turn`) preserved. ## Why this is worth doing now Dowel has **11 configured servers**, and #932/#943 put its tool surface around 86 tools against `mcpsReplyBudget` of 1800 characters. My arithmetic says the roster renders around 1900, so **`/mcps` is probably already dropping a server behind the truncation notice on that lane**. That is an estimate from counting the roster, not a measurement: `sirens_echo.commands` does not exist as a metric yet, so no invocation has been recorded anywhere I can read. ## One thing to look at in review `docs/sirens-echo-commands.md` was at **7958 of its 8000-character cap**, so there was room for about 40 characters of new documentation. Adding a 41st page is not available either: the docs count is at 40 of 40, and the hook says splitting to clear a size cap "trades one violation for another." So I merged the `/mcps` section into one covering both commands and compressed it to fit. **Every fact in the original survives** (what it reads, the three states, no addresses, ephemeral, the job guard, the truncation notice) and the wording is tighter. Nothing outside that section was touched. Worth a read as prose rather than as a diff, since compression is where meaning goes missing. **The underlying issue is that this page is full**, and the next command to be documented hits the same wall harder. Not fixed here. ## Verification `just gate` passes: build, policy-check, vet, test, test-skips, pre-commit. `go test -race ./...` clean. Seven new tests: the singular names what each tool does and does not leak another server's, a verbose description cannot spend the whole reply, a multi-line description is flattened, a silent server stays distinct from an empty one, the argument is closed to the roster and binds accordingly, no roster publishes no command, and the registered form carries its choices to Discord. **Not exercised against Discord.** Nobody has invoked any slash command on any lane yet (#988), so this is the same untested surface as the rest of the command set, one command wider.
feat(commands): add /mcp, the per-server detail /mcps has no room for
All checks were successful
ci / image-build (pull_request) Successful in 34s
ci / test (pull_request) Successful in 59s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
6a63587835
/mcps is an index bounded by one interaction reply, and on the dowel lane
its roster is close enough to that bound that a server is likely already
being dropped behind the truncation notice. The half it can never carry
is what each tool actually does.

/mcp <server> answers that. Its argument carries choices rather than a
pattern, because a server name names a thing and the roster is fixed at
image build, so the set is closed and the picker can offer it. The roster
now reaches JobCommands, which means registration and dispatch read the
same set and a published choice that outlived its roster is refused
rather than trusted. A deployment with no MCP publishes no /mcp at all.

Neither command opens a thread, and that is a decision rather than an
omission. Both are ephemeral, a thread hangs off a real channel message,
an ephemeral reply is not one, so threading either would first make the
roster public. Length already has an answer in the send budget.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo!1001
No description provided.