The harness doesn't know how to read MCP "resources" #851

Closed
opened 2026-08-16 01:17:00 +00:00 by coilysiren · 5 comments
Owner

likely the cause of #848

probably doesn't know the prompt type either

likely the cause of https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/848 probably doesn't know the `prompt` type either
Author
Owner

putting an engineer on this now

putting an engineer on this now
Member

Engineer investigation. The title's premise does not hold. Echo already reads MCP resources and already knows the prompt type. The break is a missing annotation on the resource itself.

The harness is not the gap

internal/community/mcp.go discovers resources per server (discoverResources, line 563), caches the listing, invalidates it on notifications/resources/list_changed, reads qualifying ones in priority order (readGrounding, line 610), and frames them in a system message as data rather than instruction. Bounds are 8 documents and 8KB per turn (tuning.go:56).

Prompts are implemented too. The capability check sits at mcp.go:830, ListPrompts at 836, GetPrompt at 886, selected by an HTTP caller rather than injected. All of it is already written up in docs/sirens-echo-mcp-resources.md.

The actual root cause

readGrounding includes a resource only when annotations.audience names assistant (groundingCandidate, mcp.go:589), then orders by annotations.priority. That gate is deliberate, so a server publishing a large catalogue cannot flood the turn.

The discord-timestamps resource did land, in deploy/services/sirens-echo/discord-mcp.mcp.kdl:83 and in sirens-deep-demo-discord.mcp.kdl:42. It carries uri, mime, and description, and no annotations at all. So Echo discovers it, groundingCandidate returns false, and it never enters her context. There is no second path either, because resources are never registered as callable tools, so the model cannot ask for one.

Why the spec cannot just add the annotation

ward-mcp's inline grammar has no spelling for it. parseResources accepts exactly uri, mime, and title, and errors on any other property (internal/mcpserver/resource.go:56). Adding audience="assistant" to the KDL today would fail closed and take the server down rather than annotate anything. parsePrompts is the same shape, accepting only title.

So this needs a grammar addition in ward-mcp mapping audience and priority onto mcp.Annotations, and only then the spec edit and a rollout.

A separate gap that keeps #848 open

The resource text says outright that emitting the markup still needs an epoch, and that getting one needs the speaker's timezone. Echo has no clock. There is no current-time injection anywhere in her system prompt material and no time tool in her roster. So even once the resource is reaching her, "what time is it in Paris" stays unanswerable. That is the original #848 ask and it needs its own capability rather than riding on this issue.

Suggested order

  1. ward-mcp - add audience and priority to the resource node, mapped to mcp.Annotations.
  2. deploy - set audience="assistant" on discord-timestamps in both sirens-echo discord specs, then roll out.
  3. Confirm with ward_mcp_info that Echo's discord server reports a non-zero resourceCount, and that the document lands in a turn.
  4. File a separate issue for a current-time capability, which is the real #848 blocker.

Worth noting that deploy/services/discord-mcp/discord.mcp.kdl, the general-purpose discord server, has no resource node at all, so any harness on that one sees resourceCount: 0 today.

No code changed. Investigated read-only from a task-scoped clone.

Engineer investigation. The title's premise does not hold. Echo already reads MCP resources and already knows the `prompt` type. The break is a missing annotation on the resource itself. ## The harness is not the gap `internal/community/mcp.go` discovers resources per server (`discoverResources`, line 563), caches the listing, invalidates it on `notifications/resources/list_changed`, reads qualifying ones in priority order (`readGrounding`, line 610), and frames them in a system message as data rather than instruction. Bounds are 8 documents and 8KB per turn (`tuning.go:56`). Prompts are implemented too. The capability check sits at `mcp.go:830`, `ListPrompts` at 836, `GetPrompt` at 886, selected by an HTTP caller rather than injected. All of it is already written up in `docs/sirens-echo-mcp-resources.md`. ## The actual root cause `readGrounding` includes a resource only when `annotations.audience` names `assistant` (`groundingCandidate`, `mcp.go:589`), then orders by `annotations.priority`. That gate is deliberate, so a server publishing a large catalogue cannot flood the turn. The `discord-timestamps` resource did land, in `deploy/services/sirens-echo/discord-mcp.mcp.kdl:83` and in `sirens-deep-demo-discord.mcp.kdl:42`. It carries `uri`, `mime`, and `description`, and no annotations at all. So Echo discovers it, `groundingCandidate` returns false, and it never enters her context. There is no second path either, because resources are never registered as callable tools, so the model cannot ask for one. ## Why the spec cannot just add the annotation ward-mcp's inline grammar has no spelling for it. `parseResources` accepts exactly `uri`, `mime`, and `title`, and errors on any other property (`internal/mcpserver/resource.go:56`). Adding `audience="assistant"` to the KDL today would fail closed and take the server down rather than annotate anything. `parsePrompts` is the same shape, accepting only `title`. So this needs a grammar addition in ward-mcp mapping `audience` and `priority` onto `mcp.Annotations`, and only then the spec edit and a rollout. ## A separate gap that keeps #848 open The resource text says outright that emitting the markup still needs an epoch, and that getting one needs the speaker's timezone. Echo has no clock. There is no current-time injection anywhere in her system prompt material and no time tool in her roster. So even once the resource is reaching her, "what time is it in Paris" stays unanswerable. That is the original #848 ask and it needs its own capability rather than riding on this issue. ## Suggested order 1. ward-mcp - add `audience` and `priority` to the `resource` node, mapped to `mcp.Annotations`. 2. deploy - set `audience="assistant"` on `discord-timestamps` in both sirens-echo discord specs, then roll out. 3. Confirm with `ward_mcp_info` that Echo's discord server reports a non-zero `resourceCount`, and that the document lands in a turn. 4. File a separate issue for a current-time capability, which is the real #848 blocker. Worth noting that `deploy/services/discord-mcp/discord.mcp.kdl`, the general-purpose discord server, has no `resource` node at all, so any harness on that one sees `resourceCount: 0` today. No code changed. Investigated read-only from a task-scoped clone.
Member

Both halves are done. Nothing changed in this repo, because the harness was never the gap.

mcp-beaver - 165bc7b on main, CI green, image published. Adds audience and priority to the resource node, mapped onto mcp.Annotations. Both are optional, and a resource declaring neither keeps a nil Annotations so every existing spec serves identical bytes.

Two things worth naming from that change:

  • resource children now fail closed the way prompt children already did. Previously an unknown child was silently ignored, so a typo'd audiance "assistant" would have reproduced exactly the invisible failure this issue is about.
  • kdl-go parses priority=0.9 as BigFloat, not Float, and the accessors panic across kinds. Reading only the two obvious kinds rejected a well-formed decimal as a type error. The test caught it, and it was passing the out-of-range cases for the wrong reason until the assertions were tightened to name the range.

deploy - PR #570, open and mergeable, review on Kai. Adds audience "assistant" to discord-timestamps in both sirens-echo Discord specs.

On merge ordering, there is no window to get wrong. audience is a child node, which the pinned build ignores rather than rejecting. I linted both spec files against both binaries and they parse under each, so the PR is safe before or after reconcile-pins.sh advances the pin. I deliberately left priority out: it only orders candidates and there is exactly one, and it is a property, which the pinned build would have rejected fail-closed and taken the server down with it.

Verification still outstanding. Confirming resourceCount goes non-zero on Echo's Discord server, and that the document actually lands in a turn, needs the rollout. That is live-system work and I hand it over rather than doing it.

#848 - not closed by any of this. Filed as #855: Echo has no clock, so the epoch the timestamp markup needs is still unavailable to her.

Temporary clones removed. mcp-beaver and deploy have no resident checkout on this host, so the pushed commits and the PR branch are the durable artifacts.

Both halves are done. Nothing changed in this repo, because the harness was never the gap. **mcp-beaver** - `165bc7b` on `main`, CI green, image published. Adds `audience` and `priority` to the `resource` node, mapped onto `mcp.Annotations`. Both are optional, and a resource declaring neither keeps a nil `Annotations` so every existing spec serves identical bytes. Two things worth naming from that change: - `resource` children now fail closed the way `prompt` children already did. Previously an unknown child was silently ignored, so a typo'd `audiance "assistant"` would have reproduced exactly the invisible failure this issue is about. - kdl-go parses `priority=0.9` as `BigFloat`, not `Float`, and the accessors panic across kinds. Reading only the two obvious kinds rejected a well-formed decimal as a type error. The test caught it, and it was passing the out-of-range cases for the wrong reason until the assertions were tightened to name the range. **deploy** - PR [#570](https://forgejo.coilysiren.me/coilyco-bridge/deploy/pulls/570), open and mergeable, review on Kai. Adds `audience "assistant"` to `discord-timestamps` in both sirens-echo Discord specs. On merge ordering, there is no window to get wrong. `audience` is a child node, which the pinned build ignores rather than rejecting. I linted both spec files against both binaries and they parse under each, so the PR is safe before or after `reconcile-pins.sh` advances the pin. I deliberately left `priority` out: it only orders candidates and there is exactly one, and it is a property, which the pinned build would have rejected fail-closed and taken the server down with it. **Verification still outstanding.** Confirming `resourceCount` goes non-zero on Echo's Discord server, and that the document actually lands in a turn, needs the rollout. That is live-system work and I hand it over rather than doing it. **#848** - not closed by any of this. Filed as #855: Echo has no clock, so the epoch the timestamp markup needs is still unavailable to her. Temporary clones removed. `mcp-beaver` and `deploy` have no resident checkout on this host, so the pushed commits and the PR branch are the durable artifacts.
Member

Closing: the premise is false against current code. Both types are supported.

Darren (director seat), 2026-08-17. Kai confirmed the close.

This issue says the harness does not know how to read MCP resources, and probably not the prompt type either. Reading internal/community/mcp.go at bd96d36, both are implemented:

  • mcp.go:585 - session.ListResources(ctx, &mcp.ListResourcesParams{Cursor: cursor})
  • mcp.go:674 - entry.session.ReadResource(ctx, &mcp.ReadResourceParams{URI: resource.URI})
  • mcp.go:882 - session.ListPrompts(ctx, &mcp.ListPromptsParams{Cursor: cursor})
  • mcp.go:932 - entry.session.GetPrompt(getCtx, &mcp.GetPromptParams{...})

readGrounding is called from the per-server loop at mcp.go:194, and marked resources become grounding documents that reach the turn. So ward://discord/timestamps in the discord guardfile is read rather than ignored. #855 records the same conclusion from the other direction, noting that deploy PR 570 makes Echo actually read that resource.

Prompt support is independently corroborated by #884, which is about promoting an allowlisted MCP prompt to a slash command. That issue would be incoherent if the type were unsupported.

What this does not close. #848, which this issue guessed at as the cause, needs a different explanation. And #858 is worth reading next by anyone in this area: grounding is documented as a per-turn budget of 8 documents and 8 KB but enforced per server across an 11-server roster, so the real ceiling is 88 documents and 88 KB. Resources are read. They may be read far more than intended.

Found during a capability audit mapping Go stdlib surface areas against what Echo can actually reach. Closing as fixed.

## Closing: the premise is false against current code. Both types are supported. Darren (director seat), 2026-08-17. Kai confirmed the close. This issue says the harness does not know how to read MCP resources, and probably not the `prompt` type either. Reading `internal/community/mcp.go` at `bd96d36`, both are implemented: * `mcp.go:585` - `session.ListResources(ctx, &mcp.ListResourcesParams{Cursor: cursor})` * `mcp.go:674` - `entry.session.ReadResource(ctx, &mcp.ReadResourceParams{URI: resource.URI})` * `mcp.go:882` - `session.ListPrompts(ctx, &mcp.ListPromptsParams{Cursor: cursor})` * `mcp.go:932` - `entry.session.GetPrompt(getCtx, &mcp.GetPromptParams{...})` `readGrounding` is called from the per-server loop at `mcp.go:194`, and marked resources become grounding documents that reach the turn. So `ward://discord/timestamps` in the discord guardfile is read rather than ignored. #855 records the same conclusion from the other direction, noting that deploy PR 570 makes Echo actually read that resource. Prompt support is independently corroborated by #884, which is about promoting an allowlisted MCP prompt to a slash command. That issue would be incoherent if the type were unsupported. **What this does not close.** #848, which this issue guessed at as the cause, needs a different explanation. And #858 is worth reading next by anyone in this area: grounding is documented as a per-turn budget of 8 documents and 8 KB but enforced per server across an 11-server roster, so the real ceiling is 88 documents and 88 KB. Resources are read. They may be read far more than intended. Found during a capability audit mapping Go stdlib surface areas against what Echo can actually reach. Closing as fixed.
Member

Verified end to end. Closing. Angie (ENG, claude seat), working the headless engineer queue.

The outstanding item was the live half: "Confirming resourceCount goes non-zero on Echo's Discord server, and that the document actually lands in a turn, needs the rollout." It has.

The rollout landed

coilyco-bridge/deploy PR 570 merged 2026-08-16T03:58:08Z.

The Echo lane reads it now

mcp.tools.discovered on the sirens-echo pods, grouped by grounding_documents, three-hour buckets:

  • 2026-08-15 18:00Z - 0
  • 2026-08-15 21:00Z - 0
  • 2026-08-16 00:00Z - 0
  • 2026-08-16 15:00Z - 1

Sixteen records at zero before the merge, then one after it. sirens-deep and sirens-deep-owl-glass both report 1 across the same window.

One caveat on my own evidence: retention reaches two days, and the Echo lane has logged no discovery since that 15:00Z bucket, so this is two records rather than a long run of them. It is the flip the rollout predicted, at the right time, and I would not call it a trend.

The title's premise, re-checked against source rather than taken on the earlier comment

internal/community/mcp.go on current main: discoverResources calls ListResources with cursor paging (573), groundingCandidate gates on annotations.audience naming assistant (599), resourcePriority orders them (611), readGrounding calls ReadResource (674). Prompts are the same shape: discoverPrompts calls ListPrompts (875) and Prompt calls GetPrompt (932). The harness knew how to read both. The resource was missing an annotation, and the annotation had no grammar to be written in.

No change in this repo, then or now.

Still not closed by this

#855. Echo has no clock, so the epoch the timestamp markup needs is still unavailable to her. That is the part of #848 this never reached, and I am picking it up next.

**Verified end to end. Closing.** Angie (ENG, `claude` seat), working the headless engineer queue. The outstanding item was the live half: *"Confirming `resourceCount` goes non-zero on Echo's Discord server, and that the document actually lands in a turn, needs the rollout."* It has. ## The rollout landed `coilyco-bridge/deploy` PR 570 merged **2026-08-16T03:58:08Z**. ## The Echo lane reads it now `mcp.tools.discovered` on the `sirens-echo` pods, grouped by `grounding_documents`, three-hour buckets: * 2026-08-15 18:00Z - **0** * 2026-08-15 21:00Z - **0** * 2026-08-16 00:00Z - **0** * 2026-08-16 15:00Z - **1** Sixteen records at zero before the merge, then one after it. `sirens-deep` and `sirens-deep-owl-glass` both report **1** across the same window. One caveat on my own evidence: retention reaches two days, and the Echo lane has logged no discovery since that 15:00Z bucket, so this is two records rather than a long run of them. It is the flip the rollout predicted, at the right time, and I would not call it a trend. ## The title's premise, re-checked against source rather than taken on the earlier comment `internal/community/mcp.go` on current `main`: `discoverResources` calls `ListResources` with cursor paging (573), `groundingCandidate` gates on `annotations.audience` naming `assistant` (599), `resourcePriority` orders them (611), `readGrounding` calls `ReadResource` (674). Prompts are the same shape: `discoverPrompts` calls `ListPrompts` (875) and `Prompt` calls `GetPrompt` (932). **The harness knew how to read both. The resource was missing an annotation**, and the annotation had no grammar to be written in. No change in this repo, then or now. ## Still not closed by this **#855.** Echo has no clock, so the epoch the timestamp markup needs is still unavailable to her. That is the part of #848 this never reached, and I am picking it up next.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#851
No description provided.