Repository navigation
fix: instructions on the result, one Implementation type for both ends (#1560) - #1561
Merged
Merged
Conversation
…on type (#1560) Every schema from 2025-03-26 on puts instructions on InitializeResult, beside serverInfo, and the 2026-07-28 draft puts it on DiscoverResult too. mcpkit carried it as a field of core.ServerInfo, so initialize nested it inside serverInfo, server/discover never sent it, and every stateless result repeated it in _meta serverInfo. Hosts that follow the spec, Claude Code and the TypeScript SDK among them, saw no instructions. The field had sat on ServerInfo since a417cda, which added the 2025-11-25 Implementation fields and took instructions along with them. No test asserted on it and no upstream conformance scenario grades it. - core.Implementation is the spec's single identity type, with Icons and core.Icon added. ServerInfo and ClientInfo become aliases of it, so client identity gains title, description, websiteUrl and icons. - ServerInfo.Instructions is gone; server.WithInstructions sets it. InitializeResult and the stateless DiscoverResult carry it top-level, and stateless.Backend gains Instructions(). - The client reads it into Client.Instructions on both wires. - The stateless _meta clientInfo marshals the whole Implementation instead of a name/version map.
This was referenced Oct 18, 2026
… before it waits (#390) The long-poll checked the side channel, then the task status, then called WaitForUpdate, which only wakes on store updates made after it registers. Two things could land in the gap and strand the handler: - TaskElicit sets input_required, then queues its request on the buffered side channel. A channel send is not a store update, so a handler that registered after both waited forever, and the tool blocked on the reply. This is the intermittent TestTaskElicitE2E failure (CI 2026-09-07, 2026-10-15, and on #1561); #874 only bounded the hang at 20s. - A task finishing between the status check and the wait was never seen. The handler now selects on the side channel and on WaitForResult together. WaitForResult checks terminal state and registers its waiter under one lock, so neither event can fall in a gap. Two tests force each interleaving with a store wrapper that holds the handler's first wait until the task has moved; both hang to their 5s bound on the old handler and pass on the new one.
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.
What changes
instructionsinsideserverInfo. Every schema since 2025-03-26 puts it onInitializeResult, besideserverInfo, andImplementationhas never had the field. Hosts that follow the spec drop it, which is how Claude Code ended up showing the model nothing from declaire (declaire#308).DiscoverResulthas a top-levelinstructions, and mcpkit never sent it there. Meanwhile the_metaserverInfo stamped onto every stateless result repeated the full text.instructionsmoves off the identity struct ontoserver.WithInstructions(s). Initialize andserver/discoverboth send it at the top level of the result, and the client reads it intoClient.Instructionson either wire.core.Implementationbecomes the one identity type, as in the spec.ServerInfoandClientInfoare now aliases of it. Client identity was stuck at the 2024-11-05 shape, so it gainstitle,description,websiteUrlandicons, and both sides gainicons(newcore.Icon)._metaclientInfo was a hand-built{name, version}map. It now marshals the wholeImplementation.Breaking for anyone setting
ServerInfo.Instructions. That field is gone, andWithInstructionsreplaces it.Minimal example (a server configured with instructions, then
initialize):On main the equivalent is
core.ServerInfo{..., Instructions: "Use search before fetch."}.Before / after
Wire output, same server on both branches:
Interop with the TypeScript reference SDK (1.29.0,
Client.getInstructions()), connected to each build:That
nullis the symptom from the issue, reproduced against the reference client.How we missed it
ServerInfoin a417cda (2026-04-02), the commit that added the 2025-11-25Implementationfields (title,description,websiteUrl).instructionssits a few lines belowserverInfoinInitializeResult, and it got swept in with them. That was the first week of the repo, so every later version inherited it.core,serverorclientasserted oninstructionsat all.getInstructions()throughsrc/connection/and no scenario checks it, so Server 31/31 stayed green the whole time.ClientInfofell behind for the same structural reason. Two Go types were named after the wire fields instead of the spec type, and only the server one got updated.Folded in: the
TestTaskElicitE2Eflake (#390)This PR's first CI run failed on
TestTaskElicitE2E, which has failed on CI three times now (2026-09-07 on main, 2026-10-15 on #1545, and here). #874 only capped the hang at 20s. The cause turns out to be a lost wakeup in the v1tasks/resultlong-poll, and it's a real server bug, not a test problem.WaitForUpdate, which only wakes on store updates made after it registers.TaskElicitsetsinput_requiredand then queues its request on the side channel. If both happened in that gap, the handler slept on the store. A channel send isn't a store update, so it never woke, and the tool blocked forever waiting for the client's answer.WaitForResulttogether.WaitForResultalready checks terminal state and registers its waiter under one lock (task_store.go), so nothing falls in a gap, and there's no polling.Two new tests force each interleaving deterministically, using a store wrapper that holds the handler's first wait until the task has moved on:
The first failure prints the same message CI did.
Diff groups
core/,client/,server/server.go,server/dispatch.go,server/stateless*,server/instructions_wire_test.go,client/instructions_test.goserver/tasks_v1.go,server/tasks_v1_wakeup_test.goCHANGELOG.md, READMEsPrerequisite knowledge
InitializeResulthasinstructionsbesideserverInfo, andImplementationis the type bothserverInfoandclientInfouse._metatypes both identity keys asImplementation. The draftDiscoverResultinschema/draft/schema.tscarries the top-levelinstructions.docs/SEP_2640_STATELESS_INTERACTION.mdexplains howserver/discoverreplacedinitializeon the stateless wire. It already said discover returns optionalinstructions, and the code never did.Reviewer's guide
Implementationis the card andinstructionsis the note. The spec keeps them as siblings on the result. mcpkit printed the note on the card, so a reader looking for a note found none._metaserverInfo). So the note went out on every reply too, and the one place it belongs (server/discover) had nothing.Read in this order:
Implementation,Icon, the two aliases, andInitializeResult.Instructions._metacard.Backend.Instructions()._meta.Then the folded fix, which has nothing to do with the card analogy:
tasks/resultloop and the newwaitForTaskEvent.stallingStore).Skim or skip: core/stateless.go (type swaps), server/stateless/dispatch_test.go (fake gains the method), CHANGELOG.md, the README lines.
Decision log
WithInstructionsover aWithImplementationoption.NewServer's first argument already carries the identity, and instructions aren't identity; in the draft they sit onDiscoverResult, which has no identity in its body at all.ServerInfoandClientInfoas aliases rather than renaming about 730 literals across about 255 files. There's one struct underneath, so the two can't drift again, and the diff stays reviewable.instructionsinserverInfo(mcpkit up to v0.8.1). That placement was off-spec, and the known affected server (declaire) will bump. Easy to add later if one turns up.Icon.Themeis a plain string withIconThemeLightandIconThemeDarkconstants, matching howcorehandles other small spec enums likeCacheScope.Risk / blast radius
ServerInfo.Instructions(compile error, fix isWithInstructions), and any out-of-treestateless.Backendimplementation (one new method). chakra uses neither._metaclientInfo marshal each turns the matching tests red. The two wakeup tests fail on main as shown above.make test, all 59 example modules build,go vetclean in every sub-module,testconf40/0,testconf-stateless30/0,testconf-client581/0,check-stateless-middlewareok.tasks/resultloop. It used to proxy a pending side-channel request before checking for terminal state. Now, if both are ready at once,selectpicks either. That only happens after a cancel (a request still queued for a task that's already terminal), and returning the result then is fine.TaskStore.WaitForUpdatehas no callers left, and it stays because the interface is public._metaclientInfo now carrying optional fields. Servers must treat it as display-only per spec, but a strict decoder somewhere could object to unknown keys.ttlMson discover) touches the sameDiscoverResult. exercise-wire: a wire-parity check that fails on #1355, #1515 and #1546 today #1555 (exercise-wire) is the wire-parity check that should grow an instructions row.Out of scope (deliberately deferred)
iconson Tool, Resource, ResourceTemplate and Prompt (also 2025-11-25) → follow-up issue.instructionsplacement would have caught this → same follow-up issue.instructionsinto the system prompt → noted in the same follow-up, since it's a separate repo.Serves mission #1552 (a 2026-07-28 client gets spec-correct results on both wires).
Closes #1560
Fixes the root cause of #390 (closed by #874, which only bounded the hang).