Skip to main content
Glama
133institute

mcp-stateless-lab

by 133institute

mcp-stateless-lab

A before/after lab for MCP 2025-11-252026-07-28, told through a procure-to-pay agent.

繁體中文版:README.zh-TW.md

Two MCP servers, one business logic. The same purchasing story runs against both, so every difference you observe comes from the protocol and nothing else. Then a round-robin cluster shows why the protocol changed.

git clone https://github.com/133institute/mcp-stateless-lab && cd mcp-stateless-lab
./scripts/setup.sh          # Windows: scripts\setup.bat
./scripts/run_experiments.sh

Results land in experiments/report.md — generated from a real run, never hand-written. The committed copy: 29 checks, 29 passed.

The short version

2025-11-25 let a server keep state where only one process holding one live connection could reach it. 2026-07-28 removes every such place. The consequence is not stylistic — it decides whether a purchasing agent can be load-balanced, redeployed mid-decision, or picked up tomorrow by somebody else.

2025-11-25

2026-07-28

Opening a connection

initialize + Mcp-Session-Id

nothing; _meta on every request

State across calls

dies with the session

server-minted handle, passed as a tool argument

Asking a human mid-call

server request on an open stream, with a timeout

input_required result, retried, no timeout

A long job

blocking tasks/result

tasks/get polling + tasks/update

Tool listing

no cache lifetime, may vary per connection

ttlMs / cacheScope, deterministic order

Missing resource

-32002

-32602

GET / DELETE on the endpoint

present

405

Load balancer

must be sticky, plus a session store

plain round robin

Reasoning: docs/comparison.md. Task list: docs/migration.md. Diagrams: docs/diagrams/.

Related MCP server: agent-audit-trail

Pick a way in

Read it. experiments/report.md is a generated record of what happened, check by check.

Step through it. notebooks/01_walkthrough.ipynb does the whole story on 2026-07-28 in twelve steps; 02_before_after.ipynb runs it against 2025-11-25 alongside and shows where it breaks. Both are committed with real outputs, so they read on GitHub without running anything.

Run it. ./scripts/run_experiments.sh starts and stops every server process it needs and regenerates the report.

Drive it from an agent. agents/ — Claude Code, Claude Desktop, ChatGPT, OpenAI API. Start with the era probe; see agents/STATUS.md for what has and has not been verified.

The story

NovaTech, a fictional company, equips a newly hired engineer. Every company, person, document number, amount and supplier in this repository is invented.

[Requester]  requisition ──► parallel review
                              ├─ demand   (unit manager)  always
                              ├─ budget   (finance)       always
                              └─ legal                    only if a line ≥ 50,000
[Purchasing] purchase order (after comparing suppliers) ──► issue to supplier
[Warehouse]  goods receipt (accept / reject / partially accept)
[Requester]  settlement report (cost allocation, reversal of rejected lines)
[Accounts]   payment: three-way match PO × GRN × invoice ──► schedule

Six prompts drive it, the same for both revisions — they are in src/client_lab/scenarios.py as PROMPTS:

Prompt

What it exercises

P1

raise a requisition, ask me before anything over 50,000

handles, and one human confirmation

P2

in a new conversation — add a line to yesterday's requisition

whether a handle outlives a connection

P3

which desk is it stuck at; absorb a budget return

state queries and re-routing

P4

approve everything, raise the purchase order

composing handles across tools

P5

as the warehouse, on another device — record a partial receipt

hand-over across roles

P6

three-way match, and stop if the amounts disagree

a long job with a human gate

P2 and P5 are the ones that matter. Both need a handle to survive a boundary that 2025-11-25 cannot cross.

What the experiments found

exp01

2025-11-25, one instance, one conversation

all six prompts pass — the reference point

exp02

2025-11-25, three instances behind a round-robin proxy

three failures reproduced

exp03

2026-07-28, identical conditions

all pass, with per-instance evidence

exp04

the two revisions meeting each other

12 conformance checks

exp01 exists so that nothing in exp02 can be blamed on the business logic. In exp02 a pass means the failure reproduced:

  • A session dies on the first hop. initialize mints it on instance 1; the next request lands on instance 3 and gets HTTP 404. The document rows are in the shared database the whole time — the addressing is what broke.

  • A confirmation answer cannot find the process that asked. The question goes down an open SSE stream; the answer is a separate POST the balancer routes on its own. Also reproduced without any balancer: a person slower than the server's patience loses the operation.

  • A restart orphans documents. exp02 reads the row straight out of SQLite to show it is still there, and unreachable.

exp03 runs the same six prompts through the same round-robin cluster with a four-second human in the loop, and additionally replaces every instance between the two halves of a confirmation. The submission still completes: requestState is a sealed continuation, verified rather than trusted. A tampered one is rejected.

Layout

src/procure_core/     business logic and SQLite storage — knows nothing about MCP
src/mcp_wire/         JSON-RPC, stdio and Streamable HTTP, round-robin proxy
src/server_2025/      the 2025-11-25 server: handshake, sessions, blocking tasks
src/server_2026/      the 2026-07-28 server: discover, handles, MRTR, tasks ext
src/client_lab/       protocol-level clients for both revisions, and the prompts
experiments/          exp01..exp04 and the generated report
notebooks/            the same material, step by step
tests/                97 tests: state machine, thresholds, and both protocols
scripts/              one command each; .sh and .bat wrappers over scripts/lab.py
agents/               Claude Code / Desktop, ChatGPT, OpenAI API, and an era probe
docs/                 comparison, migration checklist, Mermaid diagrams

src/mcp_wire/ is an addition to the planned layout, and scripts/lab.py is where the shell and batch wrappers keep their shared logic so they cannot drift apart.

Commands

./scripts/setup.sh                # idempotent; ends by running a real tool call
./scripts/run_2026.sh --http      # :8002/mcp   (--stdio also works)
./scripts/run_2025.sh --stdio     # :8001/mcp with --http
./scripts/run_cluster_2026.sh     # 3 instances :8201-8203 + proxy :8200
./scripts/run_cluster_2025.sh     # 3 instances :8101-8103 + proxy :8100
./scripts/run_experiments.sh      # exp01..exp04 -> experiments/report.md
./scripts/run_tests.sh            # pytest, ruff, mypy, shellcheck
./scripts/stop_all.sh
./scripts/clean.sh                # lists what it would delete; --force to do it

Every one has --help, exits non-zero on failure, prints reproduction steps when it fails, and refuses to silently pick a different port when one is busy. Windows equivalents are scripts\*.bat.

Two decisions worth knowing about

The official SDK is not used. Both servers and both clients are implemented directly on JSON-RPC. 2025-11-25 support lives in mcp 1.x and 2026-07-28 in mcp 2.x — same distribution name, so they cannot coexist in one environment, and the lab needs both revisions in one process. The experiments also have to send requests an SDK is built to prevent: a mismatched Mcp-Name, a missing Mcp-Method, a tampered requestState. requirements-2025.txt and requirements-2026.txt pin the two SDK versions for comparison in a separate environment. One virtual environment is therefore enough.

The 50,000 threshold is evaluated on the line total (quantity × unit_price), not the unit price. Two laptops at 45,000 make a 90,000 line, which crosses it — which is what gives the walkthrough a confirmation to ask for and a legal desk to route to. tests/test_rules_boundaries.py pins 49,999 / 50,000 / 50,001 and 2 × 25,000 explicitly. See docs/comparison.md.

Scope

Not here, on purpose: no real ERP integration, no real payments or credentials, no complete OAuth chain (the authorization changes are documented in docs/comparison.md, not implemented), and no attempt at completeness as a purchasing system — the flow is the minimum that makes the protocol difference visible.

No credential, token or personal datum is read, logged or stored anywhere in this repository. The one path that exposes anything to the internet is the ChatGPT connector, and agents/chatgpt/setup.md leads with the safety notes.

Verified on

Python 3.12.7, Windows 11, 2026-07-30: 97 tests pass, ruff and mypy clean, 29 of 29 experiment checks pass. shellcheck was not installed, so scripts/*.sh is reported as not run rather than passing — ./scripts/run_tests.sh will pick it up if you have it. The four agent-host integrations have not been run against their real hosts; agents/STATUS.md says so in detail.

Sources

The two authoritative ones:

Also useful: the MRTR pattern, Streamable HTTP, versioning and the tasks extension.

Licence

MIT — see LICENSE.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/133institute/mcp-stateless-lab'

If you have feedback or need assistance with the MCP directory API, please join our Discord server