Skip to content

feat: sturdier session handles — issue IDs first, structured mcp_session status - #53

Merged
naji247 merged 4 commits into
mainfrom
feat/session-copy-mcp-session
Aug 31, 2026
Merged

feat: sturdier session handles — issue IDs first, structured mcp_session status#53
naji247 merged 4 commits into
mainfrom
feat/session-copy-mcp-session

Conversation

@naji247

@naji247 naji247 commented Aug 27, 2026

Copy link
Copy Markdown
Member

Session handles are easy for callers to miss when they arrive at the tail of a long result — clients that truncate large tool outputs can drop the issuance notice entirely, and agents then start new sessions instead of continuing the one they were given. This change makes the handle mechanism sturdier and easier for agents to consume:

  • IDs are issued at the start of the response. The issuance text block is now the first content element instead of the last, so it survives client-side truncation of long results.
  • session_id and agent_id are now required parameters with an explicit value contract. session_id takes one of two values — the ses_ ID issued for the task, or the literal start to begin a new one — declared with a validation pattern in the schema (^(start|ses_[0-9A-Za-z]{27})$). Every call states its session explicitly, so sessions never split by accident. Servers keep accepting calls that omit the parameters (treated as the start of a new task), so requiredness is enforced only by schema-aware clients and nothing existing breaks server-side.
  • mcp_session replaces _mcp_instructions as the structured mirror in structuredContent, declared in each tool's output schema. It carries a machine-readable status enum (issued / active / unrecognized) instead of prose — every response state an agent can encounter is pre-announced in the schema.
  • Leaner, self-describing parameter copy. The session_id and agent_id descriptions now explain the mechanism in terms of what this server does, pre-announce both text-block headers so responses match the schema's stated contract, and collapse the two agent_id description variants into one.
  • Recovery is explicit. An unrecognized value is reported both as mcp_session.status: "unrecognized" and as a leading text block, with the recovery path (re-send the earlier ID, or send start if none was issued) stated in the schema.

Breaking wire changes (for consumers that assert on response internals or schemas): the structuredContent mirror key is renamed _mcp_instructionsmcp_session; its instructions string member is removed in favor of status; the issuance text block moved from the last to the first content position and its header text changed ([session_id issued …] / [session_id unrecognized …]); session_id and agent_id now appear in each tool's required array, and session_id declares the validation pattern above.

Version bumped to 2.1.0.

Testing

  • uv run --no-sync pytest: 815 passed / 26 skipped
  • uvx ruff check ., uvx ruff format --check ., uvx mypy src/agentcat: no new findings vs main (pre-existing repo-wide debt unchanged; all touched files clean)

Cross-SDK note: this lands the same strings and wire shape in all three SDKs, byte-identical where applicable. Sibling PRs: agentcathq/agentcat-typescript-sdk#67 · agentcathq/agentcat-go-sdk#13

…ion status

Session IDs issued at the tail of long tool results can be lost to
client-side truncation, so issuance now moves to the top of the response:
the mint-back text block leads as the first content element, and the
mcp_session mirror is inserted as the first key of structuredContent.

The agent-facing copy is rewritten leaner and self-describing, with every
response state pre-announced in the schema:

- The structured mirror key is renamed _mcp_instructions -> mcp_session,
  and its prose instructions member is replaced by a machine-readable
  status enum (issued | active | unrecognized), present in prompted mode
  only; hook mode and customer-owned session parameters mirror agent_id
  alone.
- New pre-announced text-block headers ([session_id issued ...] /
  [session_id unrecognized ...]) with a bare "session_id: <id>" line.
- One agent_id description now serves both modes; hook mode gains its own
  mcp_session field description.
- The session_id and agent_id parameter descriptions explain the handles
  in their own terms and name every place an ID can come back.
- session_id and agent_id are now required, with an explicit start value
  and a validation pattern on session_id — every call states its session
  explicitly and sessions never split by accident. Servers still accept
  calls that omit the parameters (treated as the start of a new task), so
  requiredness is enforced only by schema-aware clients.

Breaking wire changes, stated for reviewers: the structuredContent mirror
key is renamed, the instructions member is removed, and the text block
moved from last to first content element.

Version 2.1.0.
@naji247
naji247 force-pushed the feat/session-copy-mcp-session branch from bb1bf52 to 23647eb Compare August 27, 2026 21:26
… expect session_id required

handles.py now prepends the session-id mint-back block, and injection.py
requires session_id. test_inner_tap.py followed; these three files are
_LEGACY_ONLY (conftest) so the mcp 2.x dev env never ran them.
mcp 2.1 wraps a tool crash in UnexpectedToolError("Error executing tool
<name>") and keeps the crash's text off the wire; every earlier generation
put ": <text>" after the prefix. The cause still carries it, so
InnerTap.error() appends it to the event's message — matched by class name,
skipped when already present (nested crashes) — and error.message reads the
same on every generation. The wire is untouched.

Tests: MCPSERVER_CRASH_WRAPPER / MCPSERVER_CRASH_TEXT_ON_WIRE probes in
test_utils; TestOfficialV2 asserts the wrapper name via the probe and gates
only the wire check; two new tests pin the parity and no-duplication.
fastmcp < 3.4 has no ToolResult.is_error, so an error result gets no
mint-back in front; >= 3.4 does. The backend's own error block is last on
every version, so index it from the end instead of assuming the mint-back.
@naji247
naji247 force-pushed the feat/session-copy-mcp-session branch from 2c9b9d9 to cce91c7 Compare August 31, 2026 17:15
@naji247
naji247 merged commit 8533bdf into main Aug 31, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants