Skip to content

MCPL host state misattributed when a server declares >1 stateful feature set #48

Description

@antra-tess

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:

  1. If result.state carries an explicit featureSet, use it.
  2. Else if tools are tagged (featureSet / _meta.featureSet), map tool → set.
  3. Else if exactly one stateful set exists (optionally narrowed by host- vs server-managed mode), use it.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions