Summary
MCPL host-managed state is misattributed when a server declares more than one stateful feature set. The host injects and records state for the first stateful feature set only, regardless of which feature set the called tool actually belongs to.
Where
CheckpointManager.getStatefulFeatureSet(serverId) returns the first stateful feature set (Map iteration order):
// src/mcpl/checkpoint-manager.ts
getStatefulFeatureSet(serverId: string): string | null {
const prefix = `${serverId}:`;
for (const [key] of this.trees) {
if (key.startsWith(prefix)) return key.slice(prefix.length); // first only
}
return null;
}
It's then used for both sides of the tool-call state exchange in src/framework.ts:
// inject (≈3524)
const fs = this.checkpointManager.getStatefulFeatureSet(serverId);
if (fs) { stateParams = isHostManaged ? { state: getCurrentState(serverId, fs) }
: { checkpoint: getCurrentCheckpoint(serverId, fs) }; }
// record (≈3542)
if (result.state) {
const fs = this.checkpointManager.getStatefulFeatureSet(serverId);
if (fs) this.checkpointManager.recordCheckpoint(serverId, fs, result.state);
}
Consequence
For a server with multiple stateful feature sets (e.g. xgate/x-mcpl declares x.post, x.notifications, x.feed, x.dm):
- A checkpoint returned by a tool belonging to feature set B is recorded onto feature set A (the first one) — corrupting A's tree and silently losing B's state.
- Every tool call injects A's state/checkpoint, so tools in B never receive their own feature set's state.
Only the first stateful set works; the rest are broken on the tool-call path.
Root cause
The wire gives the host no way to attribute a tool call (or a result.state) to a feature set:
tools/list tool definitions carry no featureSet.
- The
result.state checkpoint carries no featureSet.
So getStatefulFeatureSet "guesses" the first set. That's only correct when a server has exactly one stateful feature set.
Note: the server-driven path (state/update, which carries an explicit featureSet) is unambiguous and not affected — only the synchronous tool-result piggyback + pre-call injection path is.
Suggested fix
Attribute state by an explicit signal only, and never guess among several:
- If
result.state carries an explicit featureSet, use it.
- Else if tools are tagged (
featureSet / _meta.featureSet), map tool → set.
- Else if exactly one stateful set exists (optionally narrowed by host- vs server-managed mode), use it.
- Otherwise log a warning and drop the update — do not record/inject against the wrong set.
This is backward-compatible (single-stateful-set servers keep working) and "lights up" per-set correctness once servers tag tools or result.state with a featureSet. Longer term, consider standardizing a featureSet field on tool definitions and/or on result.state.
Related (minor)
applyJsonPatch throws on move/copy ("not yet supported", checkpoint-manager.ts:169). Worth completing for full RFC-6902 coverage.
Provenance
Found while building the standalone MCPL host harness, which carried the same first-set assumption (ported from this CheckpointManager) and was fixed there: https://github.com/anima-research/mcpl-harness (commit b616e24). The harness fix above is a reference implementation of the suggested approach.
Summary
MCPL host-managed state is misattributed when a server declares more than one stateful feature set. The host injects and records state for the first stateful feature set only, regardless of which feature set the called tool actually belongs to.
Where
CheckpointManager.getStatefulFeatureSet(serverId)returns the first stateful feature set (Map iteration order):It's then used for both sides of the tool-call state exchange in
src/framework.ts:Consequence
For a server with multiple stateful feature sets (e.g. xgate/x-mcpl declares
x.post,x.notifications,x.feed,x.dm):Only the first stateful set works; the rest are broken on the tool-call path.
Root cause
The wire gives the host no way to attribute a tool call (or a
result.state) to a feature set:tools/listtool definitions carry nofeatureSet.result.statecheckpoint carries nofeatureSet.So
getStatefulFeatureSet"guesses" the first set. That's only correct when a server has exactly one stateful feature set.Note: the server-driven path (
state/update, which carries an explicitfeatureSet) is unambiguous and not affected — only the synchronous tool-result piggyback + pre-call injection path is.Suggested fix
Attribute state by an explicit signal only, and never guess among several:
result.statecarries an explicitfeatureSet, use it.featureSet/_meta.featureSet), map tool → set.This is backward-compatible (single-stateful-set servers keep working) and "lights up" per-set correctness once servers tag tools or
result.statewith afeatureSet. Longer term, consider standardizing afeatureSetfield on tool definitions and/or onresult.state.Related (minor)
applyJsonPatchthrows onmove/copy("not yet supported", checkpoint-manager.ts:169). Worth completing for full RFC-6902 coverage.Provenance
Found while building the standalone MCPL host harness, which carried the same first-set assumption (ported from this CheckpointManager) and was fixed there: https://github.com/anima-research/mcpl-harness (commit
b616e24). The harness fix above is a reference implementation of the suggested approach.