Repository navigation
RFC-014 draft: tool presence, toolsets and surfaces - #34
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Presentation moves to the host. Servers keep their full tool list and hint per tool: static or dynamic, visible now, toolset. Toolsets open and close per session (agent, host policy, server suggestion), optionally as named instances. Hint changes ride in call results, on push events, or standalone, ordered by a per-connection rev. The four tools become the host's meta-tools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e on open as content Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… content, unique names, compat tools) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…list is truth on reconnect, ended toolsets, principal-wide state, refused pushes, rev on tools/list, retitling, tailoring) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nstance required on instance toolsets; hints vs grant Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…from toolsets/list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Related: RFC-015 #33 · host implementation anima-research/mcpl-cc-bridge#10 (stacked on #9) · native server anima-research/games-mcpl#1 |
There was a problem hiding this comment.
Automated review
Recommendation: fix, then merge. 1 high severity finding should be fixed first.
Reviewed commit d770560 with claude-opus-5-5, by reading the code. It did not run the code or look at the checks.
Does this change belong in the project?
It fits the project. This draft RFC belongs in the project. It proposes an optional, backward-compatible MCPL extension for tool hints and toolsets, and it follows the format and placement of the repository's other draft RFCs.
1 reason with sources
- It serves MCPL's stated purpose: a backward-compatible MCP extension that makes servers active participants. Servers tell hosts which tools are static and which are dynamic, and which are visible right now, using push events and manifest declarations. The RFC is optional on both sides: hosts and servers that don't advertise
toolHintsbehave as they do today. That matches the README's description of MCPL as a 'backward-compatible extension'. (README.md (intro, Features))
Findings
9 findings of medium or high severity, see the inline comments:
- High severity. Re-sending a refused push's hints as tools/hints contradicts the rev-ordering rule, so the change is dropped.
- Medium severity.
definitionhint lets a server change a tool's definition, including RFC-008 class, without the re-list hosts rely on for policy. - Medium severity. Applying hints on coalesced or deferred pushes is undefined.
- Medium severity. Gap recovery through toolsets/list cannot restore updated definitions or static/dynamic moves.
- Medium severity. Server renaming an instance breaks the 'adopt' idempotency for retried opens.
- Medium severity. Calls to tools of a toolset that is not open must fail, but the server cannot know for undeclared toolsets.
- Medium severity, test coverage. No conformance vector for the refused-push re-send or gap recovery.
- Medium severity. Adopting an existing instance silently ignores differing options.
- Medium severity. Toolset declarations fall in the manifest's
capabilitiesdomain, which conflicts with the RFC's 'no authority' stance.
7 more findings, less severe
- Low severity, documentation,
RFC-014-surfaces.md:198-209. Rationale 'makes a host's re-open after a reconnect safe' contradicts the rule that hosts do not re-open after reconnect. Line 199 justifies adoption with "a host's re-open after a reconnect". Lines 205-209 then say that on reconnect the host takestoolsets/listas the truth and "does not re-open on its own". The first sentence reads as left over from an earlier design and gives implementers the wrong reconciliation strategy. Re-opening after reconnect would also restart toolsets that ended while the host was away. - Low severity, documentation,
RFC-014-surfaces.md:176.defaultis 'not allowed with instances' but is a required field in ToolsetDecl and appears in the instances example. The field table saysdefaultis "Not allowed withinstances". The schema at line 499 makesdefault: "open" | "closed"required, and the manifest example at line 167 sets"default": "closed"together with"instances": true. The text probably means that"open"is not allowed with instances. As written, a strict validator built from the prose rejects the RFC's own example. - Low severity, documentation,
RFC-014-surfaces.md:431. Refers to RFC-015, which does not exist in the repository. Lines 13-14 and line 431 rely on RFC-015 for how a surface host obtains the agent's credentials. No RFC-015 file exists in the repository. The surface-host design in RFC §8.6 therefore depends on a credential mechanism that is unspecified. Mark the dependency as pending, or describe it here. - Low severity, documentation,
RFC-014-surfaces.md:486-493. Schema omits the hints carried in the toolsets/close result. Section 5.2 (line 211) says the close result "carries the hint changes it caused, like an open". The schema lists"mcpl/hints"only for thetools/callandtoolsets/openresults, and givestoolsets/closeas→ {content?}. Schema-driven implementations will not parse hints from close results. - Low severity, documentation,
RFC-014-surfaces.md:7-14. Header says the RFC only 'uses' SPEC sections, but it amends §9.2, §16.1 and Appendix B. The RFC addshints,toolsetandinstanceto thepush/eventparams (§9.2), reserves a newsurface:tag namespace (§16.1 reserves onlychat:andmcpl:), adds new methods and_metakeys, and changes the schema. The header lists none of these as amendments. Other RFCs list them, for example RFC-013 at lines 9-13 ("Amends SPEC §9.3 … Appendix B"). Integrators reading the header will miss the SPEC sections that need editing. - Low severity, documentation,
RFC-014-surfaces.md:118. Conflict between an explicitpresence: "static"andmcpl/toolsetis unresolved.mcpl/toolset"impliespresence: \"dynamic\"", but the RFC does not say what a host does with a tool that declares bothmcpl/presence: "static"and a toolset. The same question arises when a hint change moves a toolset tool topresence: "static". Hosts may put such a tool in the header or treat it as dynamic, and so present it differently. State which hint wins, or make the combination invalid. - Low severity, test coverage,
RFC-014-surfaces.md:507-521. No vectors forendedrecords, multi-connection propagation or static/dynamic moves. Several normative behaviours have no conformance vector. One is theendedrecord, reported once per connection and kept for a bounded time (RFC §5.3). Others are open, close and visibility changes made on one connection reaching another as hint changes (RFC §5.6), presence moves (RFC §4.2), and close results carrying hints. These vectors need to be frozen "before acceptance" as well, otherwise implementations can diverge on them unnoticed.
| A server **MUST** send hint changes on a connection in `rev` order — whichever of the three | ||
| ways each travels — so that a lower `rev` never arrives after a higher one (allocate the `rev` | ||
| and write the message in one step). The host applies changes in `rev` order and ignores any | ||
| with a `rev` it has already passed; a host that sees a gap or a reversal anyway re-reads the | ||
| state with `toolsets/list`. | ||
| A host applies the `hints` of a push only if it accepts the push; if it refuses one (or the | ||
| server gets no answer), the server re-sends the same changes as `tools/hints` with the same | ||
| `rev`. A host applies each `rev` at most once. |
There was a problem hiding this comment.
High severity. Re-sending a refused push's hints as tools/hints contradicts the rev-ordering rule, so the change is dropped.
The server MUST send hint changes in rev order so that "a lower rev never arrives after a higher one", and the host "ignores any with a rev it has already passed". But when a host refuses a push carrying hints, or the push gets no answer, the server must re-send "the same changes as tools/hints with the same rev". That re-send comes after the push response, and by then later revs (in tool results or other pushes) may already have been sent and applied. A conforming host then discards the re-send as stale, so the visibility change is lost. Or the host treats the refused push as a gap and falls back to toolsets/list every time. The rule needs a defined resolution: allocate a new rev for the re-send, send no further hints until the push is answered, or have the host hold back later revs.
| `definition` lets a dynamic tool's options change without a `tools/list` round trip. A static | ||
| tool's definition changes only through `tools/list_changed`. |
There was a problem hiding this comment.
Medium severity. definition hint lets a server change a tool's definition, including RFC-008 class, without the re-list hosts rely on for policy.
definition?: Tool in a hint change replaces a dynamic tool's whole definition "without a tools/list round trip", and line 322 says hint changes need no capability. RFC-008 (lines 100-102) has class changes announced through tools/list_changed, after which the host recomputes the effective class on re-list. A definition carried in _meta can include a new mcpl/class, a new description or new annotations. That bypasses the host's re-list and class-policy path, and the RFC's security section does not mention it. The RFC should restrict which fields definition may change (for example the inputSchema only), or require hosts to re-run their tool policy on it.
| - Latest-value state (progress, pose, health) uses RFC-006 plain coalescing with a key per | ||
| instance. Activity uses RFC-006 deferred coalescing where the host supports it, so a burst | ||
| arrives as one message describing the present. A visibility change is never carried only by | ||
| a replaceable event. |
There was a problem hiding this comment.
Medium severity. Applying hints on coalesced or deferred pushes is undefined.
Activity is told to use RFC-006 deferred coalescing, and plain coalescing for latest-value state. Hints, however, are applied "only if [the host] accepts the push" (line 315). In RFC-006 a deferred notice joins a pending batch that is not visible to the model, and a plain occurrence can be replaced. The RFC does not say whether hints on a joined or replaced notice are applied on acceptance or when rendered, or whether they are lost with the replaced occurrence. "A visibility change is never carried only by a replaceable event" implies a second carrier. But a second carrier would need either the same rev, which the host then ignores, or a new rev, which contradicts "the same changes". Implementations will disagree on whether such visibility changes take effect.
| - In `tools/list`, `mcpl/visible` on a toolset's tool means "visible in at least one open | ||
| instance"; per-instance visibility travels in hint changes (RFC §6). The list carries the | ||
| declared definitions, and its result carries `_meta["mcpl/rev"]`: the hint revision it | ||
| reflects, so a host applies only later changes on top of it. That revision covers what the | ||
| list states — definitions and coarse visibility. **A host's per-instance baseline comes from | ||
| `toolsets/list`**, whose hints state every open instance's visibility in full. |
There was a problem hiding this comment.
Medium severity. Gap recovery through toolsets/list cannot restore updated definitions or static/dynamic moves.
A host that sees a rev gap must re-read state with toolsets/list (line 313). But tools/list carries only "the declared definitions", while a dynamic tool's updated definition (new enum options) and presence moves travel only in hint changes. toolsets/list is described as returning visibility per instance. It does not cover undeclared toolsets, which the server does not track, or moves of tools outside any toolset. After a lost tool result, which can happen through cancellation or timeout, the host can therefore not recover the current options of a dynamic tool or a presence move. The baseline that toolsets/list returns should be defined to include current definitions and presence.
| - `instance` is a name the host proposes (the agent's or its own). The server MAY return a | ||
| different one if it must; the returned name is the instance's id for the session. For a | ||
| toolset without `instances`, `instance` is absent. | ||
| - Opening what is already open — a toolset without instances, or an instance by a name that is | ||
| open — **adopts** it: the result describes the existing one and changes nothing. This makes a | ||
| host's re-open after a reconnect safe. |
There was a problem hiding this comment.
Medium severity. Server renaming an instance breaks the 'adopt' idempotency for retried opens.
The server MAY return a different instance name from the one the host proposed. Re-opening is safe only by adoption, and adoption keys on "an instance by a name that is open". If an open's response is lost (timeout, disconnect) after the server renamed the instance, the host retries with the name it proposed. The server then finds no open instance by that name and opens a second game. Opening has side effects (line 208), so a retry is not idempotent exactly in the case adoption is meant to cover. Renaming should be forbidden, or the server should treat a re-proposed name as an alias.
| - `tools/list` **MUST** list every tool, visible or not, so that a host that ignores hints sees | ||
| today's behaviour. Calling an invisible tool — or a tool of a toolset that is not open — | ||
| returns an ordinary tool error that says why. |
There was a problem hiding this comment.
Medium severity. Calls to tools of a toolset that is not open must fail, but the server cannot know for undeclared toolsets.
Section 4.1 says calling "a tool of a toolset that is not open — returns an ordinary tool error". Section 5.4 says that for undeclared toolsets the host opens and closes the group alone, sends no toolsets/open or toolsets/close, and "the server does not track it". A server therefore cannot enforce or report "not open" for undeclared toolsets, and the rule cannot be met as written. Conformance vector 10 and these two sections should say which side gates calls for undeclared groups.
| ## 13. Conformance vectors (to freeze before acceptance) | ||
|
|
||
| 1. A server with hints lists every tool in `tools/list`, visible or not. | ||
| 2. A call that makes tools visible carries `mcpl/hints` in its result; no push follows for it. | ||
| 3. A push explaining a visibility change carries it in `hints`; the host applies it once. | ||
| 4. Out-of-order `rev`: the stale change is ignored. | ||
| 5. `toolsets/open` on a toolset with `instances` returns an instance name; calls carry it. | ||
| 6. A call to an instance-bound tool with two instances open and no `mcpl/instance` fails, | ||
| naming them. | ||
| 7. A `definition` change to a dynamic tool reaches the host without `tools/list_changed`. | ||
| 8. A server whose host did not advertise `toolHints` sends no `tools/hints`. | ||
| 9. After a reconnect, `toolsets/list` reports an instance the server kept; the host adopts | ||
| it rather than opening a second. | ||
| 10. A tool tagged with an undeclared toolset: the host never sends `toolsets/open` for it, | ||
| and the tool is visible only while the host has the group open. |
There was a problem hiding this comment.
Medium severity, test coverage. No conformance vector for the refused-push re-send or gap recovery.
The rev rules are the most error-prone part of the protocol: a refused or unanswered push re-sent as tools/hints with the same rev, at-most-once application, and a gap or reversal triggering toolsets/list. Yet the vectors cover only "out-of-order rev: stale change ignored" (vector 4). Add vectors for a refused push whose hints arrive later as tools/hints, and for a host detecting a gap and recovering its baseline. These would also surface the ordering conflict in RFC §6.1.
| - Opening what is already open — a toolset without instances, or an instance by a name that is | ||
| open — **adopts** it: the result describes the existing one and changes nothing. This makes a | ||
| host's re-open after a reconnect safe. |
There was a problem hiding this comment.
Medium severity. Adopting an existing instance silently ignores differing options.
An open for a name that is already open "adopts it: the result describes the existing one and changes nothing", even when the options differ (for example size 19 requested, 9×9 open). Toolsets belong to the principal and are shared across connections (RFC §5.6), so a second connection or agent that proposes a common name such as "game" gets an unrelated existing game with no signal of the mismatch. The RFC should require the server to report the mismatch, or reject the open when its options conflict with the existing instance.
|
|
||
| ### 5.1 Declaration | ||
|
|
||
| In the manifest (§17), so changes are announced by `mcpl/manifestChanged`: |
There was a problem hiding this comment.
Medium severity. Toolset declarations fall in the manifest's capabilities domain, which conflicts with the RFC's 'no authority' stance.
The RFC puts toolHints, toolsets and surface in experimental.mcpl, so that changes are announced by mcpl/manifestChanged. Under SPEC §17.1, every member other than version, revision and featureSets belongs to the capabilities domain. Per §17.5 and §17.6, adding one becomes capability-expansion-pending awaiting a policy decision, and removing one becomes capability-revoked. Adding a toolset would then produce grant and receipt work that the RFC ("adds no uses path and changes no grant") says does not exist. The RFC should state how hosts classify these members in the diff and the receipt. The surface-changed impact name also clashes with the RFC's new meaning of "surface".
…nts on coalesced pushes, definition limits, recovery, no renaming, adopted, undeclared gating, manifest domain, schema, vectors) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Automated review
Recommendation: merge and fast-follow. 11 medium severity findings can be fixed after the merge.
Reviewed commit 10513c8 with claude-opus-5-5, by reading the code. It did not run the code or look at the checks.
Does this change belong in the project?
It fits the project. This is a draft RFC that extends how MCPL servers and hosts handle tools. It follows the repo's existing RFC process and format, and it adds no new authority, so it fits.
6 reasons with sources
- Proposing protocol extensions as RFC files at the repo root is how the project works. The README says SPEC.md is the authoritative text and that RFC files keep the proposal and review history. RFC-001 through RFC-013 sit at the root, and some, such as RFC-007, are still drafts. This PR adds one more draft file and does not touch SPEC.md. (README.md (Status))
- The RFC extends MCPL's stated goal of making servers active participants in the inference lifecycle. Hint changes ride on push events, call results and a new notification. They build on features MCPL already has: push events, manifest changes and event tags. The RFC also stays backward-compatible, which the README's opening line requires: hosts that don't advertise toolHints see today's behaviour. (README.md (intro, Features))
- It follows the format and conventions of its sibling RFCs. Like RFC-007, it has a Status/Targets/Depends-on header, revision notes, SPEC amendment lists, conformance vectors listed before acceptance, and open questions. It builds on RFC-006 and RFC-008 rather than redefining them. (RFC-007-tool-lifecycle.md (header))
- It leaves authority with the existing capability grant system. The draft says RFC-002 / SPEC §5.4 remains the only source of authority and that hints confer nothing. So it does not compete with the existing grant model. (README.md (Capability Grants); RFC-014 header)
- For the maintainers: the surface-host part (§8.6) relies on an RFC-015 that is not in this repository. No RFC-015 file exists in the repo. The dependency is limited to guidance on surface hosts outside the main host, so the rest of the RFC stands on its own. (repository file list)
- Acceptance will later need executable vectors, as the README already requires for other drafts. The draft lists 20 vectors 'to freeze before acceptance', which is consistent with the project's practice. As a draft, it does not have to meet that bar yet. (README.md (Status, RFC-005 note))
Findings
10 findings of medium or high severity, see the inline comments:
- Medium severity.
revonly has to increase, so a host cannot tell a gap. - Medium severity. A refused push leaves a rev hole that looks like a gap.
- Medium severity. Accept-then-apply on pushes conflicts with ignoring revs already passed.
- Medium severity. Recovery from two separate reads has no single consistent baseline rev.
- Medium severity. A retried open without an instance name is not safe.
- Medium severity.
endedrecords repeat on every reconnect for up to an hour. - Medium severity. Per-instance
definitioncontradicts "instances share definitions". - Medium severity, documentation. Unclear which fields a hint
definitionmay change. - Medium severity, documentation. Letting servers omit "hints-only tools" contradicts "tools/list MUST list every tool".
- Medium severity. Hosts without
toolHintscannot address one of several open instances.
6 more findings, less severe or past the limit on comments
- Medium severity,
RFC-014-surfaces.md:290-294. One host's policy close affects every connection of the principal. Toolsets belong to the principal (§5.6), and the host may close a toolset by its own policy, for example when it has been idle for many turns (line 224). If two connections (for example two sessions of the same agent) share a principal, one host closing an instance it considers idle closes it for the other connection as well, possibly forfeiting a live game (see the close example on line 210). The RFC should limit host-policy closes to toolsets that host opened, or warn about the cross-connection effect. - Low severity, documentation,
RFC-014-surfaces.md:151-154. "Every toolset is implicitly open" conflicts with instance toolsets always being closed. Line 151 says that for a host withouttoolHintsevery toolset is implicitly open. Line 195 and the schema at line 536 say a toolset withinstancesis always "closed" and has no implicit instance. The text does not say what implicit openness means for an instances toolset: which instance name, or whether there is one at all. The "may instead offer plain tools" escape covers only part of the case. - Low severity, documentation,
RFC-014-surfaces.md:248-254. Garbled sentence in thetoolsets/listdescription. Line 250 reads "…differs from the listed one — and — so a host can tell…". A dash pair and "and" are left over from an edit, so the sentence that defines whattoolsets/listreturns is hard to parse. This is normative content (full visibility, current definitions,ended) and should read cleanly. - Low severity, documentation,
RFC-014-surfaces.md:11-14. Amendment list leaves out the §5.1/§5.2 capability advertisement. The RFC adds atoolHintsadvertisement for both server and host (§6.3) and asurfacemanifest member. These amend SPEC §5.1 (Server Capabilities) and §5.2 (Host Support), the sections RFC-007 lists for the same kind of change. The "Amends" list names §4, §9.2, §16.1, §17.1, Appendix B and the Changelog but not §5.1-5.2. It also leaves out the new_metaontools/listandtools/call. Revision 3 says the amendment list is complete. - Low severity, test coverage,
RFC-014-surfaces.md:132. No conformance vector for ignoringpresence: "static"on a toolset tool. Revision 3 added the rule that an explicitpresence: "static"on a tool withmcpl/toolsetis ignored and the tool is treated as dynamic. None of vectors 1-20 covers it. Add a vector in which a tool with bothmcpl/toolsetandmcpl/presence: "static"stays out of the header. - Low severity, test coverage,
RFC-014-surfaces.md:195. No vector fordefault: "open"toolsets. Adefault: "open"toolset is open from the start of a session and appears intoolsets/listwithout anytoolsets/open. No conformance vector checks this. Add one, including the host adopting such a toolset rather than opening it.
|
|
||
| ```ts | ||
| interface HintChanges { | ||
| rev: number; // per connection, strictly increasing |
There was a problem hiding this comment.
Medium severity. rev only has to increase, so a host cannot tell a gap.
HintChanges.rev is defined as "per connection, strictly increasing" (line 304). The recovery rule (lines 346-349) then tells a host to re-read both lists when it "sees a gap". With revs that only have to increase, a jump from 5 to 9 is legal and indistinguishable from lost changes. One host may treat every skip as a gap and re-read all the time, while another never detects real loss. The text should require consecutive revs (+1 per change object on a connection), or drop gap detection as a trigger.
| A host applies the `hints` of a push only if it accepts the push. If it refuses one, or the | ||
| server gets no answer, the server **restates** the current state of the instances that push | ||
| concerned under a **new** `rev`, standalone — never the old `rev`, which later changes may | ||
| already have passed. A host applies each `rev` at most once. |
There was a problem hiding this comment.
Medium severity. A refused push leaves a rev hole that looks like a gap.
When a host refuses a push carrying hints at rev N, it does not apply N. The server restates the state later under a new rev M > N. The text never says whether the refused N counts as "passed". If it does not, the host sees a gap at N as soon as N+1 arrives and starts a full tools/list + toolsets/list recovery on every refused push. If it does, the ordering rule at lines 337-339 needs to say so. Until the restatement arrives, any change with a rev between N and M is also applied on top of a state that is missing N's changes.
| A server **MUST** send hint changes on a connection in `rev` order — whichever of the three | ||
| ways each travels — so that a lower `rev` never arrives after a higher one (allocate the `rev` | ||
| and write the message in one step). The host applies changes in `rev` order and ignores any | ||
| with a `rev` it has already passed. Entries state values (`visible: true`, a definition), not | ||
| differences, so applying an entry again is harmless. | ||
|
|
||
| A host applies the `hints` of a push only if it accepts the push. If it refuses one, or the |
There was a problem hiding this comment.
Medium severity. Accept-then-apply on pushes conflicts with ignoring revs already passed.
The server only guarantees the order in which hint-carrying messages are written. However, the host applies a push's hints only once it decides to accept the push, and that decision can be asynchronous because push/event is a request. In the meantime a tool result carrying rev N+1 can arrive and be applied. When the push with rev N is then accepted, the rule "ignores any with a rev it has already passed" discards it. Because the push was accepted, the server never restates it, so the visibility change is lost silently. The rule needs to define ordering in terms of arrival rather than acceptance, or require the host to apply hints in arrival order whatever it decides about the event.
| **Recovering.** A host that sees a gap or a reversal, or lost a result carrying hints (a | ||
| timeout, a cancelled call), re-reads `tools/list` (definitions and presence) and | ||
| `toolsets/list` (open instances, their visibility and their current definitions, RFC §5.3) and | ||
| takes them as its new baseline. |
There was a problem hiding this comment.
Medium severity. Recovery from two separate reads has no single consistent baseline rev.
Recovery re-reads tools/list (which carries _meta["mcpl/rev"]) and toolsets/list (which carries mcpl/hints.rev) as two independent requests. Changes can land between the two reads, so the revs differ. The RFC does not say which rev becomes the baseline, or how to merge definitions taken at rev A with per-instance visibility taken at rev B. Hint changes with revs between A and B can then be dropped or applied twice against the wrong base.
| - `instance` is a name the host proposes (the agent's or its own), or omits to let the server | ||
| choose one. A proposed name **MUST** be used as given, or the open refused (for example an | ||
| invalid name); the server **MUST NOT** substitute another, so that a retried open finds what | ||
| the first one opened. For a toolset without `instances`, `instance` is absent. | ||
| - Opening what is already open — a toolset without instances, or an instance by a name that is | ||
| open — **adopts** it: the result describes the existing one, changes nothing, and carries | ||
| `adopted: true`. If the request's `options` were not applied, the result **MUST** say so in | ||
| its `content`. This makes a retried open safe; it is not a reconnect strategy (below). |
There was a problem hiding this comment.
Medium severity. A retried open without an instance name is not safe.
The RFC says adopting makes "a retried open safe" (line 220), and that a proposed name must be kept "so that a retried open finds what the first one opened". It also lets the host omit instance so the server picks a name. If such an open times out and is retried, nothing identifies the first instance, so the server creates a second one (for example a second game of Go). Either require hosts to propose a name whenever they might retry, or add an idempotency key.
| host can tell "never had one" from "it ended while you were away" — toolsets that closed | ||
| recently while no connection of the principal could be told. Each connection hears each such | ||
| record once, on its first `toolsets/list` after the end; servers keep the records for a | ||
| bounded time (the reference SDK: an hour): |
There was a problem hiding this comment.
Medium severity. ended records repeat on every reconnect for up to an hour.
ended records are delivered "once per connection" and kept for a bounded time (an hour in the reference SDK). A reconnect is a new connection, and hosts must call toolsets/list after every reconnect and tell the agent about anything ended. So a host on a flaky link tells the agent "your game ended" again on each reconnect during that hour. The record should be retired once any connection of the principal has been told, or deliveries should be tracked per principal or host session instead of per connection. Vector 13 makes the per-connection behaviour normative.
| tools?: Array<{ | ||
| name: string; | ||
| instance?: string; // required for a toolset with instances: visibility is per instance | ||
| presence?: "static" | "dynamic"; | ||
| visible?: boolean; | ||
| definition?: Tool; // dynamic tools only: a new definition (new enum options) | ||
| }>; |
There was a problem hiding this comment.
Medium severity. Per-instance definition contradicts "instances share definitions".
Lines 155-156 and 276 state that instances share their toolset's definitions and that a name identifies one definition. Yet a hint tools entry can carry both instance and definition (for example new enum options such as the legal moves in one particular game). §5.3 also restates "the current definition of every visible dynamic tool" without saying per instance. With two Go games open, it is undefined whether a definition sent with instance: "vs-ada" applies only to that instance or replaces the shared definition. Hosts will diverge, and act may validate arguments against the wrong game's options.
| `definition` lets a dynamic tool's options change without a `tools/list` round trip. It may | ||
| change the description, `inputSchema` and `annotations` only: its `name` and its RFC-008 class | ||
| (`_meta["mcpl/class"]`) are those of the listed definition, and a host **MUST** ignore any | ||
| other value there. A host applies its tool policy to a definition received this way exactly as | ||
| to a listed one. A static tool's definition changes only through `tools/list_changed`. |
There was a problem hiding this comment.
Medium severity, documentation. Unclear which fields a hint definition may change.
definition is typed as a full Tool, which includes _meta with mcpl/presence, mcpl/toolset and mcpl/visible. The text says it changes only description, inputSchema and annotations, but the MUST covers only name and mcpl/class ("any other value there"). It does not say whether a host must ignore a changed mcpl/toolset or mcpl/presence inside a definition, so a server could move a tool between toolsets or out of the stable list by that route. The MUST should cover every field outside the three that may change. Vector 20 tests only mcpl/class.
| or the `hints` push member to a host that did not advertise `toolHints`. It **MAY** tailor | ||
| everything else to the host as well — omit the toolset declarations, hint `_meta` and any | ||
| hints-only tools — so that a host without `toolHints` sees exactly what it saw before this | ||
| RFC. |
There was a problem hiding this comment.
Medium severity, documentation. Letting servers omit "hints-only tools" contradicts "tools/list MUST list every tool".
§4.1 (line 141) and design principle 1 say tools/list MUST list every tool so that nothing is hidden. Vector 1 tests this. §6.3 lets a server omit "any hints-only tools" for a host without toolHints, and §11 adds extra tools (toolset_open/toolset_close) for such hosts. So the tool list differs by host capability, and the MUST is not true as written. The rule should be scoped or the exception stated.
| - A server with toolsets **MAY** offer a host that did not advertise `toolHints` two plain | ||
| static tools that open and close toolsets (the reference SDK names them `toolset_open` and | ||
| `toolset_close`), so it remains usable there: the toolset's tools are in the list, and a | ||
| call without `mcpl/instance` goes to the only open instance. |
There was a problem hiding this comment.
Medium severity. Hosts without toolHints cannot address one of several open instances.
For hosts without toolHints, the fallback offers toolset_open/toolset_close, and a call without mcpl/instance "goes to the only open instance". Nothing stops such a host from opening two instances through toolset_open. A plain host does not set mcpl/instance, so every call after that fails with the multiple-instances error from §5.5, and those games can no longer be played. The fallback should limit such hosts to one instance, or name another way to choose one (for example an argument).
…st statements From review of revision 3: - Revs are consecutive per connection, so a skipped number means a lost message. - Hosts apply hint changes on arrival, in arrival order, whatever they decide about the message; a refused push is not restated and leaves no hole; the accept decision can't reorder anything. - tools/list and toolsets/list are statements as of their place in the order; recovery needs no common baseline. toolsets/list states per-instance visibility in full. - ended records carry an id, are reported until they expire, and are told once per id. - A hint definition with an instance is that instance's own; hosts take only description, schema and annotations from it. - An open without a name is not idempotent; policy closes are limited. - The list-every-tool rule applies to hosts with toolHints; hosts without can choose among instances; implicit open excludes instance toolsets. - Amendment list names §5.1/§5.2 and the MCP _meta members; vectors 21-26. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Automated review
Recommendation: fix, then merge. 1 high severity finding should be fixed first.
Reviewed commit d8ba2a0 with claude-opus-5-5, by reading the code. It did not run the code or look at the checks.
Does this change belong in the project?
It fits the project. This draft RFC fits. It proposes optional, backward-compatible tool hints and toolsets for MCPL, which is the project's own kind of work, and it uses the same RFC-file format and amendment style as the other drafts at the repo root.
6 reasons with sources
- A draft RFC filed at the root is how this project proposes protocol changes. The README says SPEC.md is the integrated text and that RFC files keep proposal and review history. Drafts RFC-006, -007, -008 and -013 already sit at the root as RFC-NNN-*.md. This PR adds RFC-014 the same way and does not touch SPEC.md. (README.md (Status))
- Hosts and servers working together on how tools reach the model during inference is within MCPL's stated purpose. MCPL is described as an extension that makes servers active participants in the inference lifecycle, with host-controlled treatment of what servers send. The RFC adds server hints, host-controlled presentation and push-event routing. All of these build on existing features: push events, event tags, manifest changes and coalescing. (README.md (intro, Features))
- It keeps to the project's rule that authority lives only in the grant. The RFC adds no
usespath and changes no grant. It says hints confer nothing, which matches the SPEC's position that the grant, not announcements, is the protection. (SPEC.md §17.9; RFC-002 / §5.4) - It follows the pattern of the related tool drafts: optional, and backward-compatible with plain MCP. The RFC builds on RFC-008's
mcpl/_metaprefix and on RFC-006's coalescing, and it is advertisement-gated (toolHints). Hosts and servers that don't advertise it keep today's behaviour. (RFC-007-tool-lifecycle.md, RFC-008-tool-classes.md) - A maintainer should check one place where it departs from §17: it declares a new manifest domain with no change receipt. §17.5 requires one receipt for each manifest change, and §17.6 already maps tool changes to
surface-changed. The RFC's amendment list names §17.1 but not §17.5–17.7. The departure is real, but it is the kind of thing review of a draft RFC settles; it is not a reason to reject the proposal. (SPEC.md §17.5 step 4, §17.6, §17.7) - §8.6 (surface hosts) depends on RFC-015, which is not in the repo, so that part brings in a concept still to be proposed. The RFC says it depends on nothing for authority, but surface hosts get their credentials through RFC-015. That RFC is mentioned in the PR but does not exist yet. Maintainers may want to treat §8.6 as provisional until RFC-015 is proposed. (README.md (RFC table); repository file list)
Findings
10 findings of medium or high severity, see the inline comments:
- High severity. Rev numbering covers three channels, but hints also travel in toolsets/open, close and list results.
- Medium severity. "No receipt work" for the new
toolsetsdomain contradicts SPEC §17.5 and §17.7. - Medium severity. No rule for open instances when a manifest change removes or alters their toolset declaration.
- Medium severity. Hints in the result of a cancelled call can be lost, and pushing them instead is forbidden.
- Medium severity. Retried push/event with hints conflicts with the rev and arrival-order rules.
- Medium severity. No recovery path for changed definitions of dynamic tools outside declared toolsets.
- Medium severity.
endedmisses toolsets that ended while another connection of the principal was online. - Medium severity. Required push for hint changes has no fallback when
pushEventsis not granted. - Medium severity, documentation. Surface hosts depend on an RFC-015 that is not in the repo and get no security analysis.
- Medium severity. Instance name syntax is undefined, yet servers may refuse "invalid" names.
2 more findings, less severe
- Low severity, documentation,
RFC-014-surfaces.md:537-542. Security section skips other server-authored text that reaches the model. §10 deals with dynamic tool definitions entering the conversation. It does not cover other server-authored strings the host is told to show the agent:suggest[].reason,ended[].reason, toolsettitles or retitles, and thecontentof open and close results. These are also injection surfaces and should carry the same attribution and trust-tier guidance. - Low severity, test coverage,
RFC-014-surfaces.md:596-639. Conformance vectors do not cover the rev placement of toolsets/ results or hints on cancelled results.* The vectors test consecutive revs across results, pushes andtools/hints(vector 4). None checks the rev carried bytoolsets/open,toolsets/closeortoolsets/listresults, or that hints on a cancelled or timed-out call result are applied (§6.1). These are the edge cases revision 4's rev rules are meant to settle, so they should be frozen as vectors.
| It travels three ways, sharing one `rev` counter: | ||
|
|
||
| 1. **In a tool result**, `_meta["mcpl/hints"]` — for what that call caused. | ||
| 2. **On a push event**, params member `hints` — for a change the event explains ("White | ||
| played E5", and `play` becomes visible). | ||
| 3. **Standalone**, as `tools/hints` (Server → Host, Notification) — for a change with nothing | ||
| to say. | ||
|
|
||
| **Order.** A server **MUST** number the messages carrying hint changes on a connection | ||
| consecutively — the first is `rev` 1, each next one more — whichever of the three ways each | ||
| travels, and **MUST** write them in that order (allocate the `rev` and write the message in one | ||
| step). |
There was a problem hiding this comment.
High severity. Rev numbering covers three channels, but hints also travel in toolsets/open, close and list results.
§6.1 says hint changes travel "three ways" (tool result, push, tools/hints), and those three share the consecutive rev counter. But §5.2 (lines 255-258), §5.3 (line 269) and the schema (line 573) also put _meta["mcpl/hints"] in toolsets/open, toolsets/close and toolsets/list results. The RFC never says whether those results use up a new rev or, like tools/list, repeat the last one sent. The §5.3 example shows rev: 41 on a list without settling it. If a server repeats the last rev, a host following the "one more than the last" rule sees a non-consecutive rev and starts recovery, which issues another toolsets/list and can loop. If a server allocates a new rev and the host assumes it does not, the next push looks like a skip. The consecutive rev rule is the main thing revision 4 sets out to fix, so this gap undermines it.
| In the manifest (§17), so changes are announced by `mcpl/manifestChanged`. `toolHints`, | ||
| `toolsets` and `surface` form their own manifest domain, **`toolsets`** (amending §17.1's list | ||
| of domains). A change in it carries no grant consequence and no receipt work: a host re-reads | ||
| the declarations and refreshes its catalog, as it does for a `tools/list` change. (The | ||
| server-level `surface` member is unrelated to §17.6's `surface-changed` impact name.) |
There was a problem hiding this comment.
Medium severity. "No receipt work" for the new toolsets domain contradicts SPEC §17.5 and §17.7.
SPEC §17.5 step 4 says a host MUST emit one receipt per manifest change. §17.7 says a host SHOULD fold tool-list changes into the resident-facing changelog, and §17.6 already has a surface-changed impact for tool changes. RFC §5.1 says a change in the new toolsets domain carries "no receipt work", but its amendment list does not touch §17.5 or §17.6. Moving toolHints out of the capabilities domain (§17.1 places every member other than version, revision and featureSets there) also means withdrawing toolHints is no longer treated as a capability removal. The RFC should either amend those sections explicitly or map toolset changes to an existing impact such as surface-changed.
| In the manifest (§17), so changes are announced by `mcpl/manifestChanged`. `toolHints`, | ||
| `toolsets` and `surface` form their own manifest domain, **`toolsets`** (amending §17.1's list | ||
| of domains). A change in it carries no grant consequence and no receipt work: a host re-reads | ||
| the declarations and refreshes its catalog, as it does for a `tools/list` change. (The |
There was a problem hiding this comment.
Medium severity. No rule for open instances when a manifest change removes or alters their toolset declaration.
Toolset declarations can change through mcpl/manifestChanged, and the host "re-reads the declarations and refreshes its catalog". The RFC does not say what happens to instances already open when their toolset disappears from the manifest, or loses instances: true. The same gap applies when a declared toolset becomes undeclared: under §5.4 the host then becomes the one gating calls, and its open or closed state for the group is undefined. Hosts and servers will disagree on whether those instances are closed and whether the agent should be told.
| **Applying.** A host applies a message's hint changes **when it reads the message, in the order | ||
| messages arrive**, whatever it then does with the message: it applies the `hints` of a push it | ||
| refuses or has not yet decided on, and the `_meta["mcpl/hints"]` of a result it no longer waits | ||
| for (a cancelled or timed-out call). Accepting an event is a decision about the event, not about | ||
| the server's state; a server never restates anything because a push was refused. Entries state | ||
| values (`visible: true`, a definition), not differences. |
There was a problem hiding this comment.
Medium severity. Hints in the result of a cancelled call can be lost, and pushing them instead is forbidden.
The host is told to apply _meta["mcpl/hints"] from "a result it no longer waits for (a cancelled ... call)". Under MCP cancellation (notifications/cancelled), the receiver SHOULD NOT send a response to a cancelled request, so a conforming server may never send that result. Meanwhile §6.2 says the server MUST NOT push a call's own consequences. Visibility changes caused by a cancelled call then reach the host either never (if no rev was allocated, leaving its state silently stale) or only after a later rev shows a gap. The RFC should require those consequences to go out as tools/hints when no result will be sent.
| **Order.** A server **MUST** number the messages carrying hint changes on a connection | ||
| consecutively — the first is `rev` 1, each next one more — whichever of the three ways each | ||
| travels, and **MUST** write them in that order (allocate the `rev` and write the message in one | ||
| step). | ||
|
|
||
| **Applying.** A host applies a message's hint changes **when it reads the message, in the order | ||
| messages arrive**, whatever it then does with the message: it applies the `hints` of a push it | ||
| refuses or has not yet decided on, and the `_meta["mcpl/hints"]` of a result it no longer waits | ||
| for (a cancelled or timed-out call). Accepting an event is a decision about the event, not about | ||
| the server's state; a server never restates anything because a push was refused. Entries state | ||
| values (`visible: true`, a definition), not differences. |
There was a problem hiding this comment.
Medium severity. Retried push/event with hints conflicts with the rev and arrival-order rules.
SPEC §9.4 lets servers retry push/event with a stable eventId, and tells hosts to deduplicate by eventId. The RFC does not say what happens to hints on a retried push. If the retry carries the original rev, the host sees a non-consecutive rev. Applying "on arrival, in arrival order" would then re-apply stale values over newer state. If the host drops the retry as a duplicate, the rev check and the arrival-order rule disagree. If the retry gets a new rev, the stale values are stated again as current. A rule is needed, for example that a push carrying hints is never retried, or that a duplicate's hints are ignored.
| What the principal has open on the server, and the hint state of those instances as of the | ||
| result's `rev`, in full: every tool visible in each open instance — **a tool not stated visible | ||
| is invisible** — and every current definition that differs from the declared one. A host | ||
| replaces its per-instance state with it. |
There was a problem hiding this comment.
Medium severity. No recovery path for changed definitions of dynamic tools outside declared toolsets.
Recovery and reconnect (§6.1 lines 379-384, §5.3) depend on toolsets/list, which restates current definitions only for open instances of declared toolsets. tools/list carries only declared definitions. A dynamic tool with no toolset, or in an undeclared toolset (§5.4), can still get a definition in hints, for example new enum options. After a rev gap or a reconnect, nothing restates that definition, so the host keeps checking arguments and announcing the tool against a stale or declared schema until the next change. §5.3 also tells hosts to send toolsets/list only to servers that declared toolsets, which leaves hint-only servers with no recovery source at all.
| It also carries `ended`, so a host can tell "never had one" from "it ended while you were | ||
| away": toolsets that closed while no connection of the principal could receive an event about | ||
| it. Each record has an `id`, unique for the principal and never reused. A server reports a record | ||
| on every `toolsets/list` until it expires (a bounded time; the reference SDK: an hour); a host | ||
| tells its agent about each `id` once, however often it reconnects in that time: |
There was a problem hiding this comment.
Medium severity. ended misses toolsets that ended while another connection of the principal was online.
ended records only toolsets that closed "while no connection of the principal could receive an event about it". Toolsets belong to the principal (§5.6), so take host B reconnecting after a game ended while host A, the same principal, was connected. B gets no ended record, and under §5.2 it quietly treats the instance as closed. B's agent is never told the game ended, which is the "ended while you were away" case this mechanism exists to cover. The phrase "could receive an event" is also undefined for a connection that has not granted pushEvents.
| - **Something else's consequences are pushed**, with the explaining event when there is one. | ||
| "Something else" includes another principal acting in a shared instance — an opponent's move | ||
| reaches you as a push carrying your visibility change. |
There was a problem hiding this comment.
Medium severity. Required push for hint changes has no fallback when pushEvents is not granted.
§6.2 says changes caused by something else "are pushed" with the explaining event, and line 399 says a host that has not granted pushEvents still tracks visibility. Without that grant the server cannot send push/event (SPEC §5.4 grants), and the RFC never tells the server to fall back to tools/hints. A server following §6.2 literally will either send a push that is not allowed or drop the change, and the host's visibility state goes stale.
| ### 8.6 Surface hosts outside the main host | ||
|
|
||
| A host that cannot present dynamic tools (a plain MCP client) can gain the capability from a | ||
| **surface host**: an MCPL server that is itself a host for the servers behind it, exposes the | ||
| meta-tools as its own static tools, and renders dynamic tools as events. It connects to those | ||
| servers as the agent, so it obtains the agent's credentials from its own host through RFC-015. |
There was a problem hiding this comment.
Medium severity, documentation. Surface hosts depend on an RFC-015 that is not in the repo and get no security analysis.
§8.6 has a surface host, a third-party MCPL server, obtain the agent's credentials "from its own host through RFC-015", and §15 names RFC-015's access/token. RFC-015 is not in this repository, so the RFC rests on an unreviewed mechanism. The header also says "Depends on: nothing for authority", while this design hands agent credentials to another server. §10 does not mention credential delegation, so reviewers cannot judge the trust boundary being added.
| - `instance` is a name the host proposes (the agent's or its own), or omits to let the server | ||
| choose one. A proposed name **MUST** be used as given, or the open refused (for example an | ||
| invalid name); the server **MUST NOT** substitute another, so that a retried open finds what | ||
| the first one opened. For a toolset without `instances`, `instance` is absent. |
There was a problem hiding this comment.
Medium severity. Instance name syntax is undefined, yet servers may refuse "invalid" names.
Hosts are told to propose instance names (SHOULD, for safe retries), and servers may refuse an "invalid name", but the RFC sets no allowed characters or length. Instance names also go into projected, host-namespaced tool names (§8.4). A host cannot know which names a server accepts, so a well-formed retry strategy can be refused for reasons that differ by server. A minimal grammar is needed.
… calls, retries, no-push hosts; ended for all ends; name grammar From review of revision 4: - Results of toolsets/open|close|list carrying hints take the next rev; tools/list takes none. - A cancelled call that gets no result, and a server that cannot push, send tools/hints. - A push carrying hints is never retried; a duplicate eventId's hints are ignored. - toolsets/list is answered by every toolHints server and states dynamic tools outside declared toolsets; ended records every end the host did not ask for. - Manifest changes removing a toolset close its instances; the domain maps to surface-changed. - Instance name grammar. - Security: all server-authored text; surface hosts and RFC-015. - Vectors 27-35. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Automated review
Recommendation: fix, then merge. 1 high severity finding should be fixed first.
Reviewed commit 170b97d with claude-opus-5-5, by reading the code. It did not run the code or look at the checks.
Does this change belong in the project?
It fits the project. This draft RFC adds an optional, backward-compatible extension to MCPL, written in the same format as the project's other RFCs, so it fits. The gaps in the amendment list and the ordering problems belong to the code review, not to the question of fit.
6 reasons with sources
- It serves MCPL's stated purpose of extending MCP in a backward-compatible way so servers can take an active part in inference. The README describes MCPL as a backward-compatible extension to MCP. The RFC is opt-in on both sides through
toolHints. Hosts and servers that don't advertise it keep today's behaviour (RFC §11). (README.md (intro, Features)) - It follows the project's process: a numbered draft RFC file at the root, separate from the authoritative SPEC.md. The README says SPEC.md is the authoritative integrated text and that RFC files keep the proposal and review history. RFC-006, RFC-007, RFC-008 and RFC-013 sit at the root as drafts in the same layout. RFC-014 leaves SPEC.md untouched and lists the SPEC sections it would amend, as RFC-007 does. (README.md (Status))
- It builds on existing MCPL mechanisms instead of adding a second way to do the same things. Hints use RFC-008's
mcpl/_metaprefix. Events go through the existingpush/eventand the event tags of §16, and coalescing goes through RFC-006. Toolset declarations ride on the §17 manifest-change mechanism. It explicitly grants no new authority beyond RFC-002. (RFC-007-tool-lifecycle.md, RFC-008-tool-classes.md, RFC-006-event-coalescing.md) - It is one coherent proposal; its revision notes and implementation status record the history of a single design. Earlier RFCs such as RFC-007 also record revision notes and implementation feedback in the same way. (RFC-007-tool-lifecycle.md (revision notes))
- Several review findings say amendments are missing (§17.3's domain list, §9.4's retry rule); these are gaps in the draft to fix, not reasons it doesn't belong. The RFC already says it amends §17.1 and §9.2. Whether its amendment list is complete is a matter for the drafting review before the RFC is accepted. (SPEC.md (§9.4, §17))
- Surface hosts (RFC §8.6) rely on RFC-015, which is not in the repository yet. Only that optional section depends on the companion PR. A maintainer may want to land the two together, but it does not change whether this RFC fits. (README.md (Status table))
Findings
9 findings of medium or high severity, see the inline comments:
- High severity. Consecutive-rev, arrival-order rule assumes a totally ordered connection that MCP's Streamable HTTP does not provide.
- Medium severity. An empty full statement in toolsets/list may carry no hints and so never clears stale visibility.
- Medium severity. 'Never retried (§9.4)' contradicts SPEC §9.4, and §9.4 is not in the amendment list.
- Medium severity. New
toolsetsmanifest domain leaves SPEC §17.3's closed domain list and existing domain derivation unamended. - Medium severity. Toolset names and the
optionsJSON Schema in the manifest can trigger §17.2's identifier_charset refusal. - Medium severity. Cross-connection sharing is keyed on a 'principal' that neither the SPEC nor the RFC defines.
- Medium severity, documentation. Allowing per-connection toolset state contradicts the principal-scoped model the rest of the RFC requires.
- Medium severity. Example meta-tools are aggregated across connections but
actandlookcannot name the server. - Medium severity, test coverage. No conformance vector for the behaviour served to hosts without toolHints.
6 more findings, less severe
- Low severity, documentation,
RFC-014-surfaces.md:179-183. 'Every toolset without instances is implicitly open' is contradicted in the next sentence. The rule says every toolset without instances is implicitly open for a host withouttoolHints. It then exempts toolsets "that cannot sensibly be open without options", which may also have no instances. The normative statement and its exception conflict, and nothing in the manifest lets a host or SDK tell which toolsets fall under the exception. The rule should be reworded, for example to usedefaultor a declared flag. - Low severity, documentation,
RFC-014-surfaces.md:626. Behaviour fordefault: "open"combined withinstances: trueis unspecified. ToolsetDecl allows anydefaultwithinstances: true. The text says such a toolset "is always closed" but does not say whether declaring"open"is invalid, is ignored, or creates an implicit instance. Hosts and servers may disagree about whether an instance exists at session start. - Low severity, documentation,
RFC-014-surfaces.md:307-309.endedexample omitsinstancefor an instanced toolset. The exampleendedrecord for toolsetgame, which §5.1 declares withinstances: true, has noinstance. A host with two open games cannot tell which one ended. The example should includeinstance, and the text should say it is required for instanced toolsets. - Low severity, documentation,
RFC-014-surfaces.md:532-534. Projection requiresmcpl/instanceset even for toolsets without instances. RFC §8.4 says a host MUST route each projected call "withmcpl/instanceset". RFC §5.2 says that for a toolset withoutinstances,instanceis absent. Read literally, a host must send an instance name that does not exist for non-instanced and undeclared toolsets. The requirement should apply only to instanced toolsets. - Low severity, test coverage,
RFC-014-surfaces.md:433-434. No vector for hosts ignoring hints from servers that did not advertise toolHints. §6.1 says a host MUST ignore hint_metaandtools/hintsfrom a server withouttoolHints, and §5.1 says a server that withdrawstoolHintsbecomes hint-less. No vector in §13 covers either rule. Nor does any vector cover themcpl/revvalue atools/listresult carries before any rev has been taken, which the RFC also leaves unstated. - Low severity, documentation,
RFC-014-surfaces.md:402-404. Unclear whether a duplicate-eventId push's rev advances the host's counter. A host ignores the hints of a push whoseeventIdit has already seen, but the RFC does not say whether that message's rev counts toward the consecutive sequence. If the host does not advance its counter, the next message looks like a gap and triggers an unnecessary recovery. If it does, its ignored changes are silently lost.
| **Order.** Every message that carries hint changes takes the next `rev`; a message without | ||
| any takes none. A server **MUST** number them consecutively on a connection — the first is | ||
| `rev` 1, each next one more — whichever way each travels, results of every kind included, and | ||
| **MUST** write them in that order (allocate the `rev` and write the message in one step). A | ||
| `tools/list` result carries no changes; its `mcpl/rev` names the last `rev` taken. | ||
|
|
||
| **Applying.** A host applies a message's hint changes **when it reads the message, in the order | ||
| messages arrive**, whatever it then does with the message: it applies the `hints` of a push it | ||
| refuses or has not yet decided on, and the `_meta["mcpl/hints"]` of a result it no longer waits | ||
| for (a timed-out call). Accepting an event is a decision about the event, not about the | ||
| server's state; a server never restates anything because a push was refused. Entries state | ||
| values (`visible: true`, a definition), not differences. | ||
|
|
||
| A push carrying hints is **never retried** (§9.4): its `rev` is spent when it is written. A host | ||
| that receives one whose `eventId` it has already seen ignores its hints. A call that will get no | ||
| result — cancelled under MCP, which then sends none — sends the changes it caused as | ||
| `tools/hints` instead. | ||
|
|
||
| A `tools/list` result states presence and declared definitions as of the `rev` it carries, and | ||
| a `toolsets/list` result states per-instance state as of its own `rev`; each applies at its | ||
| place in the arrival order. | ||
|
|
||
| **Recovering.** On an ordered connection nothing is lost. A host that nevertheless sees a `rev` | ||
| other than one more than the last — it dropped a message unread, or a bug — re-reads | ||
| `toolsets/list` and `tools/list` and goes on applying in arrival order. Each result states the | ||
| server's state as of the moment it was written, and everything applies in the order it was | ||
| written, so the two reads need no common baseline. A new connection starts from | ||
| `toolsets/list` (RFC §5.3). |
There was a problem hiding this comment.
High severity. Consecutive-rev, arrival-order rule assumes a totally ordered connection that MCP's Streamable HTTP does not provide.
RFC §6.1 requires one consecutive rev counter across tool results, toolsets/* results, pushes and tools/hints. Hosts must apply them "in the order messages arrive" and treat any rev other than last+1 as loss. SPEC §4.2 names MCP's stdio and Streamable HTTP as transports alongside WebSocket. Under Streamable HTTP, concurrent requests can be answered on separate HTTP responses or SSE streams, and server notifications travel on a separate GET stream, so nothing orders them relative to each other. A host can therefore receive rev 6 before rev 5. That is wrongly treated as a gap and forces a re-read. Worse, rev 5 is then applied after the newer state, which leaves visibility stale ('play' shown as invisible on your turn). The RFC should either limit rev ordering to ordered transports, or tell hosts to discard hint messages whose rev is not higher than the last one applied and to compare mcpl/rev on list results, instead of applying strictly in arrival order.
| What the principal has open on the server, and the hint state of its dynamic tools as of the | ||
| result's `rev`, in full: every tool visible in each open instance, and every visible dynamic | ||
| tool outside declared toolsets — **a dynamic tool not stated visible is invisible** — with every | ||
| current definition that differs from the declared one. A host replaces its dynamic-tool state | ||
| with it. Every server that advertised `toolHints` answers `toolsets/list`, with an empty `open` | ||
| if it declares no toolsets. |
There was a problem hiding this comment.
Medium severity. An empty full statement in toolsets/list may carry no hints and so never clears stale visibility.
RFC §5.3 says the toolsets/list hints are a full statement that the host replaces its dynamic-tool state with. RFC §6.1 (line 389) says "a message without any [hint changes] takes none" (no rev), and the schema makes _meta["mcpl/hints"] optional (line 613-614, 620). When the principal has nothing visible, for example after every instance has ended, a server can reasonably omit mcpl/hints. The host then has no statement to replace its state with, and no rev to place it in the order, so tools it believed visible stay visible. The RFC should require toolsets/list to always carry mcpl/hints with a rev, even when tools is empty.
| A push carrying hints is **never retried** (§9.4): its `rev` is spent when it is written. A host | ||
| that receives one whose `eventId` it has already seen ignores its hints. A call that will get no | ||
| result — cancelled under MCP, which then sends none — sends the changes it caused as | ||
| `tools/hints` instead. |
There was a problem hiding this comment.
Medium severity. 'Never retried (§9.4)' contradicts SPEC §9.4, and §9.4 is not in the amendment list.
SPEC §9.4 says hosts SHOULD deduplicate by eventId and servers SHOULD use stable eventId values for retries, which assumes pushes may be retried. RFC §6.1 forbids retrying any push that carries hints and cites §9.4 as if §9.4 said so. The amendment list in the header (lines 14-19) does not include §9.4. The rule also means that an explaining event whose delivery fails, for example a lost response or a timeout, is simply lost, which weakens push delivery for exactly the toolset events (an opponent's move) this RFC is about. The amendment should be stated explicitly. A cleaner fix is to let the push's content be retried without its hints.
| In the manifest (§17), so changes are announced by `mcpl/manifestChanged`. `toolHints`, | ||
| `toolsets` and `surface` form their own manifest domain, **`toolsets`** (amending §17.1's list | ||
| of domains). A change in it carries no grant consequence: its impact is §17.6's | ||
| `surface-changed`, disposition `applied`; the host emits its receipt as for any change (§17.5), | ||
| re-reads the declarations and refreshes its catalog. (The server-level `surface` member is | ||
| unrelated to the impact's name.) |
There was a problem hiding this comment.
Medium severity. New toolsets manifest domain leaves SPEC §17.3's closed domain list and existing domain derivation unamended.
SPEC §17.1 defines the capabilities domain as every member other than version, revision and featureSets. SPEC §17.3 says domains is a subset of capabilities | featureSets | tagOntology. RFC §5.1 moves toolHints, toolsets and surface into a new toolsets domain but lists only §17.1 and §17.6 as amended. A conforming host or library that does not know this RFC classifies these members as capabilities and may treat their removal as a capability-revoked impact. A host that does know it rejects or ignores a toolsets domain value that §17.3 does not allow. The two library implementations already pinned for domain derivation (§17.3) will diverge. The RFC should amend §17.3 and state how hosts that predate it classify these members.
| "game": { "title": "Go", "summary": "A game of Go against the bot or another agent", | ||
| "default": "closed", "instances": true, | ||
| "options": { "type": "object", "properties": { "size": { "enum": [9, 13, 19] } } } } | ||
| }, | ||
| "surface": { "title": "Arcade", "summary": "Games" } } } | ||
| ``` | ||
|
|
||
| | Field | Meaning | | ||
| |---|---| | ||
| | `title`, `summary` | For the host's catalog and reminders | | ||
| | `default` | `"open"`: the server treats the toolset as open from the start of a session — it appears in `toolsets/list` without the host opening it. `"closed"`: the host opens it. A toolset with `instances` is always `"closed"` | | ||
| | `instances` | `true`: the toolset may be opened more than once, each a named instance | | ||
| | `options` | JSON Schema for `toolsets/open` options | |
There was a problem hiding this comment.
Medium severity. Toolset names and the options JSON Schema in the manifest can trigger §17.2's identifier_charset refusal.
SPEC §17.2 makes "capability member names at every depth" identifier positions that must match ASCII [A-Za-z0-9._:*-], and refuses the digest (identifier_charset) otherwise. RFC §5.1 puts free-form toolset names and a full JSON Schema (options) inside experimental.mcpl. Ordinary JSON Schema keys such as $schema, $ref and $defs, and non-ASCII property or toolset names, fall outside that charset. Such a manifest cannot get a revision and breaks §17 manifest-change handling. The RFC gives toolset names no grammar and does not amend §17.2 to exempt the toolsets subtree, or the options schema in particular.
| ### 5.6 One principal, several connections | ||
|
|
||
| Toolsets belong to the principal, not to a connection: a second connection of the same | ||
| principal sees the same open toolsets (through `toolsets/list`, then hint changes). An open, | ||
| close or visibility change made through one connection reaches the others as ordinary hint | ||
| changes on their own `rev` counters — including `toolsets` state changes — not as | ||
| suggestions. |
There was a problem hiding this comment.
Medium severity. Cross-connection sharing is keyed on a 'principal' that neither the SPEC nor the RFC defines.
RFC §5.6 and §5.2 make open toolsets, closes (which forfeit games) and ended records shared by every connection of the same "principal". SPEC §18.8 explicitly lists identity and principals as out of scope, and §4.2.6 leaves authentication endpoint-defined; deployments commonly use a shared token in the URL. If a server derives the principal from such a credential, distinct agents or hosts sharing it can see each other's open instances, titles and ended reasons through toolsets/list. They can also close each other's games, which they should not be able to do. The RFC needs to say how a server determines the principal, or at least require that sharing happens only under an authenticated identity the operator configured for it, and list this in §10.
| A server that keeps toolsets across connections reports them after a reconnect; one that does | ||
| not reports what was opened on this connection. Hosts send it after every (re)connect to a | ||
| server that advertised `toolHints`, and tell the agent about each `ended` record once. |
There was a problem hiding this comment.
Medium severity, documentation. Allowing per-connection toolset state contradicts the principal-scoped model the rest of the RFC requires.
Lines 311-313 allow a server that "does not" keep toolsets across connections to report only what was opened on this connection. RFC §5.6 states that toolsets belong to the principal, not to a connection. §5.2 says a close takes effect for every connection, and vectors 14 and 32 require cross-connection propagation. Implementers cannot tell whether principal scoping is a requirement or optional. A host that follows §5.2's policy-close guidance, which assumes closes affect every connection, behaves differently on the two kinds of server.
| ### 8.2 Meta-tools | ||
|
|
||
| A host presenting dynamic tools exposes a small, constant set of its own, for example: | ||
|
|
||
| ``` | ||
| open {toolset | server, instance?, options?} look {toolset?, instance?, tool?} | ||
| act {tool, instance?, args} close {toolset | server, instance?} | ||
| ``` | ||
|
|
||
| They are the host's interface, aggregated across every connection and named by the host. | ||
| They are static. |
There was a problem hiding this comment.
Medium severity. Example meta-tools are aggregated across connections but act and look cannot name the server.
RFC §8.2 says the meta-tools are "aggregated across every connection". However, act {tool, instance?, args} and look {toolset?, instance?, tool?} take no server, and tool and toolset names are unique only within a server (RFC §4.1). Two connected servers can both have a game toolset with a play tool (games-mcpl and a Go server, §9). With the example signatures, act is then ambiguous or routed to the wrong server. Instance names are not unique across servers either. The example should carry a server parameter, or the RFC should require the host to namespace toolset and tool names.
| - **For a host that did not advertise `toolHints`, every toolset without instances is | ||
| implicitly open** and the server behaves as it would without this RFC. A toolset with | ||
| instances has no implicit instance; nor does one that cannot sensibly be open without options | ||
| (a game needs its settings). For those the server offers that host plain tools to open, | ||
| choose and close instances (RFC §11), or leaves them out. |
There was a problem hiding this comment.
Medium severity, test coverage. No conformance vector for the behaviour served to hosts without toolHints.
RFC §4.1 and §6.3 define what a server does for hosts that did not advertise toolHints: non-instanced toolsets implicitly open, the list may be tailored, and plain open, close and choose tools are offered. Among §13's vectors only 8 and 26 touch this. None checks that a non-instanced toolset's tools work with no open, or that a tailored list omits hint _meta. This is the compatibility promise in the PR description ("see today's behaviour") and it needs a vector.
…principal, names From review of revision 5: - Hosts apply hints in rev order; on Streamable HTTP an early change is held briefly. - toolsets/list always carries its hints and a rev, so an empty statement clears state. - Principal takes RFC-015 §4.4's definition; servers that don't keep toolsets end them. - Toolset name grammar; act/look can name the server. - Amendment list names §9.4 and §17.3. Vectors 36-38. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… are known Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds RFC-014, Tool Presence, Toolsets and Surfaces. It lets servers say which tools belong in a model's stable tool list and which come and go with state, and leaves it to hosts to present them without rebuilding the prompt cache.
The problem
tools/list_changed, paying a full rebuild per mode change.What it adds
Per-tool hints in
_meta:mcpl/presence—static(default) |dynamicmcpl/visible— available right nowmcpl/toolset— which toolset the tool belongs totools/liststays the full truth.Toolsets. They are declared in the manifest, can be opened as named instances, and are handled with
toolsets/open|close|list(Host → Server). The server is session-aware; toolsets belong to the principal;toolsets/listis the truth on reconnect, including toolsets that ended while away.Hint changes
{rev, tools, toolsets, suggest}. They travel in call results (a call reports its own consequences), on the push event that explains them, or standalone astools/hints, inrevorder.Host presentation (guidance):
Optional on both sides. Servers that send no hints, and hosts that don't advertise
toolHints, see today's behaviour.History. Revision 1, the first commit here, had servers implement four tools and a sub-protocol; that design needed per-service adapters. Revision 2 moves presentation to the host. Later commits fold in findings from three implementations. Semantic usage hints were considered and rejected: servers can't know how an agent will use their tools.
Implementations
Both PRs are linked below.
🤖 Generated with Claude Code