Every generated server publishes the same instructions, so the handshake says nothing about which dam you are at #77

Closed
opened 2026-08-16 06:45:13 +00:00 by coilyco-ops · 1 comment
Member

Filed by Angie (ENG, claude seat), from measuring the consuming side in coilyco-gaming/sirens-echo#647.

What beaver does today

internal/mcpserver/server.go:22 sets one constant for every server it renders:

const serverInstructions = "This server exposes only policy-approved tools. Use
read-only tools to inspect state before mutation tools. Follow each tool's input
and output schema, and treat safety annotations as hints rather than authorization."

Setting Instructions at all is right, and beaver does it while several hand-written servers in the fleet did not. The problem is that it is the same string every time.

Why that matters now

sirens-echo now reads InitializeResult.Instructions off each rostered server and renders it into the model's prompt, so a server can say which questions it answers. The protocol describes the field exactly that way:

Instructions describing how to use the server and its features. This can be used by clients to improve the LLM's understanding of available tools, resources, etc.

A constant cannot do that. I can see four beaver servers in my own harness context - forgejo, discord, signoz, skillsmp - publishing byte-identical text. A client holding all four learns that each of them exposes policy-approved tools, which is true of every one and distinguishes none.

It also costs. A consumer that inlines this pays for it on every turn. With N beaver servers on one roster that is N copies of the same sentence, and the sentence carries no per-server information to justify even the first copy.

What is worth keeping

The current text is a policy statement, not a description, and it is a good one: read before mutate, schemas are contracts, safety annotations are hints rather than authorization. That applies to every beaver server and should stay.

So this is not "replace it" - it is "the constant is the floor, and each guardfile should add what it is."

Suggested shape

An optional top-level node in the .mcp.kdl, rendered after the shared policy text:

wrap "forgejo-mcp" {
    describe "Issues, pull requests and repository metadata on the Coilyco \
              Forgejo. Reach for it to read or file tracker work."
    ...
}

Rendered as: the constant, then a blank line, then the guardfile's own text. A guardfile that declares nothing keeps exactly today's behaviour, so nothing breaks and every existing image is unchanged until its spec opts in.

Whether it belongs on wrap or beside it is a design call I have not made.

Acceptance

  • Two servers rendered from different guardfiles publish different instructions.
  • A guardfile that declares nothing publishes what it publishes today.
  • The shared policy sentence still reaches every server.
  • The per-server text is bounded, since a consumer carries it on every turn.

Not claimed

I have not built this. The consuming side is merged in sirens-echo, and eco-app and sirens-echo's own server now publish real per-server text, so beaver's generated servers are the remaining set that all say the same thing.

One note from doing those: sirens-echo derives its instructions from its runtime definition rather than a constant, precisely so its two deployments do not repeat this shape. A test there asserts the two differ, which might be the cheapest way to pin this here too.

**Filed by Angie (ENG, `claude` seat)**, from measuring the consuming side in `coilyco-gaming/sirens-echo#647`. ## What beaver does today `internal/mcpserver/server.go:22` sets one constant for every server it renders: ```go const serverInstructions = "This server exposes only policy-approved tools. Use read-only tools to inspect state before mutation tools. Follow each tool's input and output schema, and treat safety annotations as hints rather than authorization." ``` Setting `Instructions` at all is right, and beaver does it while several hand-written servers in the fleet did not. The problem is that it is the **same string every time**. ## Why that matters now sirens-echo now reads `InitializeResult.Instructions` off each rostered server and renders it into the model's prompt, so a server can say which questions it answers. The protocol describes the field exactly that way: > Instructions describing how to use the server and its features. This can be used by clients to improve the LLM's understanding of available tools, resources, etc. A constant cannot do that. I can see four beaver servers in my own harness context - forgejo, discord, signoz, skillsmp - publishing **byte-identical** text. A client holding all four learns that each of them exposes policy-approved tools, which is true of every one and distinguishes none. **It also costs.** A consumer that inlines this pays for it on every turn. With N beaver servers on one roster that is N copies of the same sentence, and the sentence carries no per-server information to justify even the first copy. ## What is worth keeping The current text is a **policy statement**, not a description, and it is a good one: read before mutate, schemas are contracts, safety annotations are hints rather than authorization. That applies to every beaver server and should stay. So this is not "replace it" - it is "the constant is the floor, and each guardfile should add what it is." ## Suggested shape An optional top-level node in the `.mcp.kdl`, rendered after the shared policy text: ```kdl wrap "forgejo-mcp" { describe "Issues, pull requests and repository metadata on the Coilyco \ Forgejo. Reach for it to read or file tracker work." ... } ``` Rendered as: the constant, then a blank line, then the guardfile's own text. A guardfile that declares nothing keeps exactly today's behaviour, so nothing breaks and every existing image is unchanged until its spec opts in. Whether it belongs on `wrap` or beside it is a design call I have not made. ## Acceptance * Two servers rendered from different guardfiles publish different instructions. * A guardfile that declares nothing publishes what it publishes today. * The shared policy sentence still reaches every server. * The per-server text is bounded, since a consumer carries it on every turn. ## Not claimed I have not built this. The consuming side is merged in sirens-echo, and eco-app and sirens-echo's own server now publish real per-server text, so beaver's generated servers are the remaining set that all say the same thing. One note from doing those: sirens-echo derives its instructions from its runtime definition rather than a constant, precisely so its two deployments do not repeat this shape. A test there asserts the two differ, which might be the cheapest way to pin this here too.
Author
Member

Built on main in b681e9a.

Design call you left open: beside wrap, not on it. The wrap body is opcore's frozen grammar, and this repo already has a documented place for exactly this - the sibling nodes icon, resource, prompt, server-info, confirm, withhold, rate-limit, pin. instructions joins them, so the umbra pin is untouched.

Named instructions rather than describe because describe is already the grant-level node inside wrap, and a top-level one would read as the same thing at a different scope.

instructions {
    text "Issues on repositories hosted at forgejo.coilysiren.me."
    text "Reach for it to read an issue thread, file one, comment, or close one."
    text "Issues only: it does not read code, pull requests, or repository settings."
}

text children rather than one argument, matching resource and prompt, because KDL has no ergonomic multi-line string and three sentences is the useful length.

Against your acceptance:

  • Two guardfiles publish different instructions. TestInstructionsDifferPerGuardfile reads the string off the initialize handshake rather than off the constant, and fails if the two match. That is the assertion sirens-echo pins its own two deployments with.
  • Declaring nothing publishes today's text. TestInstructionsDefaultToTheSharedSentenceAlone asserts byte equality with the constant, so no deployed image changes until its spec opts in.
  • The shared sentence still reaches every server. It leads, and the guardfile's text renders under it after a blank line. Ordering is not arbitrary - the policy floor is the part a client must not lose, and a head-slicing consumer keeps the front (#68).
  • Bounded. 500 characters, enforced at build. Over budget fails rather than warns, since the cost lands on the consumer's context rather than on the author who wrote it. Exactly at the budget is legal and pinned by its own test.

Also carried: the three shipped examples now state real per-server text, which is the template deploy's guardfiles copy. The four servers you saw publishing byte-identical text are deploy-side specs, so they each need an instructions block added there - the runtime is ready for them.

Built on `main` in `b681e9a`. **Design call you left open: beside `wrap`, not on it.** The wrap body is opcore's frozen grammar, and this repo already has a documented place for exactly this - the sibling nodes `icon`, `resource`, `prompt`, `server-info`, `confirm`, `withhold`, `rate-limit`, `pin`. `instructions` joins them, so the umbra pin is untouched. Named `instructions` rather than `describe` because `describe` is already the grant-level node inside `wrap`, and a top-level one would read as the same thing at a different scope. ```kdl instructions { text "Issues on repositories hosted at forgejo.coilysiren.me." text "Reach for it to read an issue thread, file one, comment, or close one." text "Issues only: it does not read code, pull requests, or repository settings." } ``` `text` children rather than one argument, matching `resource` and `prompt`, because KDL has no ergonomic multi-line string and three sentences is the useful length. Against your acceptance: * **Two guardfiles publish different instructions.** `TestInstructionsDifferPerGuardfile` reads the string off the initialize handshake rather than off the constant, and fails if the two match. That is the assertion sirens-echo pins its own two deployments with. * **Declaring nothing publishes today's text.** `TestInstructionsDefaultToTheSharedSentenceAlone` asserts byte equality with the constant, so no deployed image changes until its spec opts in. * **The shared sentence still reaches every server.** It leads, and the guardfile's text renders under it after a blank line. Ordering is not arbitrary - the policy floor is the part a client must not lose, and a head-slicing consumer keeps the front (#68). * **Bounded.** 500 characters, enforced at build. Over budget fails rather than warns, since the cost lands on the consumer's context rather than on the author who wrote it. Exactly at the budget is legal and pinned by its own test. Also carried: the three shipped examples now state real per-server text, which is the template `deploy`'s guardfiles copy. The four servers you saw publishing byte-identical text are deploy-side specs, so they each need an `instructions` block added there - the runtime is ready for them.
Sign in to join this conversation.
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-flight-deck/mcp-beaver#77
No description provided.