feat!: adopt MCP revision 2026-07-28 via the mcp 2.x SDK - #192
Conversation
Moves omind to the stateless MCP revision. FastMCP -> MCPServer; the tool decorators and every tool's behaviour are unchanged, and a v2 server still serves 2025-era clients from the same process, so existing clients keep working. The <2.0 cap on mcp (#131) did exactly what it was added for: it held the fleet on 1.x until this landed deliberately. Re-capped at <3.0. Fixes two real transport bugs surfaced by the upgrade: - `omind node` went SILENT over stdio. Our fd-readiness transport parsed lines with mcp.types.JSONRPCMessage.model_validate_json, but in 2.x JSONRPCMessage is a plain union alias with no such method. The AttributeError was swallowed into the read stream, so every request hung until timeout rather than erroring. It now parses through the SDK's jsonrpc_message_adapter, matching the SDK's own stdio transport. - Replies serialized with exclude_none=True; switched to exclude_unset=True (what the SDK transport uses), which is what keeps the 2026-07-28 envelope fields - resultType, ttlMs, cacheScope - on the wire. We keep our own fd-readiness transport: the 2.x stdio_server still uses anyio.wrap_file, which is the thing it was written to avoid. Tests: the in-process helper follows call_tool's new CallToolResult return (it was a (content, structured) tuple). 842 tests pass; ruff + mypy clean. mcp-conformance 0.2.0 reports 17 contracts passing, 1 skipped (injection, deliberately unset). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe project upgrades to MCP SDK 2.x, replaces ChangesMCP 2.x server migration
Estimated code review effort: 2 (Simple) | ~15 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Around line 11-16: Update the changelog entry’s conformance result to
explicitly state that 17 of 18 checks passed and injection was skipped
intentionally, replacing the ambiguous “17 contracts” wording while preserving
the surrounding SDK and compatibility details.
- Line 10: Add a blank line after the “### Changed” and “### Fixed” headings in
CHANGELOG.md, before their respective list items, to satisfy Markdown
heading-spacing requirements.
In `@src/omind/server.py`:
- Around line 489-492: Constrain the supported mcp dependency range to the
tested API versions while retaining the low-level runner in _fd_stdio_server(),
since MCPServer.run_stdio_async() cannot handle its custom streams. Update CI so
run_node executes against every supported mcp version within that range,
including the existing initialization flow via _lowlevel_server.
In `@tests/test_server.py`:
- Around line 57-63: Extend the stdio smoke test around the existing 2025-06-18
initialization and clean-EOF checks to send a tools/call request and assert its
resultType, then assert ttlMs and cacheScope on the cacheable tools/list
response. Exercise the actual _fd_stdio_server wire path rather than relying on
the in-process call() helper, while preserving the existing initialization and
EOF assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 387de27c-3b99-4b36-8338-de38835938e3
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
CHANGELOG.mdpyproject.tomlsrc/omind/server.pytests/test_server.py
📜 Review details
⏰ Context from checks skipped due to timeout. (11)
- GitHub Check: test (windows-latest, 3.14)
- GitHub Check: test (macos-latest, 3.10)
- GitHub Check: test (ubuntu-latest, 3.13)
- GitHub Check: test (ubuntu-latest, 3.14)
- GitHub Check: test (ubuntu-latest, 3.11)
- GitHub Check: test (windows-latest, 3.10)
- GitHub Check: test (ubuntu-latest, 3.12)
- GitHub Check: test (macos-latest, 3.14)
- GitHub Check: test (ubuntu-latest, 3.10)
- GitHub Check: MCP conformance (live stdio)
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Retrieval must fail open: every search layer returnsNoneon errors and callers fall back to the older path. Missing models, corrupt indexes, locked databases, and missing FTS5 support must degrade search rather than break it; failure branches must be tested.
All note writes must go throughOmiStore; external writers usenotes.upsert_note. Writes must retain flocking, atomic rename, LamportRev:stamping, and soft deletion. Deletes archive notes withDisabled: true; onlyomind mesh purgepermanently removes them.
OmiStore.safe_namemust guard every note read and write so path traversal remains impossible.
Credential notes must be de-prioritized in search and gate suggestions unless the query concerns credentials, using_CREDENTIAL_PENALTY; the gate must never steer agents toward secrets notes.
MCP tools must never return unbounded output. Every list-shaped tool must paginate throughserver._pageand exposelimit,offset,total, andhas_more.
index.mdandMemory Template.mdare scaffolding, not memories; reading either must not clear the consult gate. Preservepaths.NON_CONSULT_FILENAMES.
Recency is a ranking leg only: it may re-rank notes already matched by content legs but must never add unmatched notes to search results.
link_targets()must preserve wikilink case for dangling-link reports; only link resolution lowercases it.
Do not mutate aNoteSummaryreturned from_cached_summary; it is shared cached state. Usedataclasses.replace, as instore._indexed_search.
Treatembed.encoderesults as potentially plain lists in tests; coerce vectors throughsearchindex._query_vectorrather than assuming a.shapeattribute.
Do not reintroduce document-frequency query-term filtering; use threshold-free graded matching, where all-words matches rank above any-words matches.
Files:
src/omind/server.pytests/test_server.py
**/*.md
📄 CodeRabbit inference engine (AGENTS.md)
Markdown note files are the source of truth; indexes, caches, and vectors are derived and disposable and must live only in
paths.state_dir(), never in the vault.
Files:
CHANGELOG.md
🪛 markdownlint-cli2 (0.23.1)
CHANGELOG.md
[warning] 10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 18-18: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🔇 Additional comments (5)
pyproject.toml (1)
41-44: LGTM!src/omind/server.py (2)
29-29: LGTM!Also applies to: 66-73, 127-134, 181-181
192-192: 🗄️ Data Integrity & IntegrationDo not add explicit protocol selection.
mcp2.0.0 usesserve_dual_era_loop(). A request with the2026-07-28envelope selects modern handling;initializeselects legacy handling.create_initialization_options()only supplies the legacy handshake payload.> Likely an incorrect or invalid review comment.CHANGELOG.md (1)
19-28: LGTM!tests/test_server.py (1)
5-5: LGTM!Also applies to: 22-23, 53-54, 66-69, 72-72, 104-104, 113-113, 125-125, 142-142, 151-151, 167-167, 201-201, 215-215, 249-249, 259-267, 288-288, 318-333, 343-343
|
|
||
| ## [Unreleased] | ||
|
|
||
| ### Changed |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add blank lines after both headings.
### Changed and ### Fixed are followed immediately by list items. markdownlint reports MD022 at Lines [10] and [18].
Proposed formatting fix
### Changed
+
- **Adopt MCP revision ...
### Fixed
+
- **The `omind node` stdio transport ...Also applies to: 18-18
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)
[warning] 10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CHANGELOG.md` at line 10, Add a blank line after the “### Changed” and “###
Fixed” headings in CHANGELOG.md, before their respective list items, to satisfy
Markdown heading-spacing requirements.
Source: Linters/SAST tools
| - **Adopt MCP revision `2026-07-28` (the stateless revision) by moving to the | ||
| `mcp` 2.x SDK** (`mcp>=2.0.0,<3.0`; the previous `<2.0` cap did its job and | ||
| held the fleet at 1.x until this was reviewed). `FastMCP` is now `MCPServer`; | ||
| the tool decorators and every tool's behaviour are unchanged, and a v2 server | ||
| still serves 2025-era clients from the same process, so existing MCP clients | ||
| keep working. Verified green against `mcp-conformance` 0.2.0 (17 contracts). |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Document the skipped conformance check.
The PR reports 17 of 18 checks passing, with injection deliberately skipped. “17 contracts” omits the denominator and can imply complete conformance. State the result as “17 of 18 checks passed; injection was skipped intentionally.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CHANGELOG.md` around lines 11 - 16, Update the changelog entry’s conformance
result to explicitly state that 17 of 18 checks passed and injection was skipped
intentionally, replacing the ambiguous “17 contracts” wording while preserving
the surrounding SDK and compatibility details.
| await mcp._lowlevel_server.run( # noqa: SLF001 - MCPServer exposes no public lower-level runner. | ||
| read_stream, | ||
| write_stream, | ||
| mcp._mcp_server.create_initialization_options(), # noqa: SLF001 | ||
| mcp._lowlevel_server.create_initialization_options(), # noqa: SLF001 |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -a '^uv\.lock$' | xargs -r rg -n 'name = "mcp"|version = "2\.0\.0"|mcp-types'
rg -n 'mcp\s*[<=>]' pyproject.toml
python - <<'PY'
from importlib.metadata import version
from mcp.server.mcpserver import MCPServer
print("mcp:", version("mcp"))
server = MCPServer("compatibility-probe")
lowlevel = getattr(server, "_lowlevel_server", None)
assert callable(getattr(lowlevel, "run", None))
assert callable(getattr(lowlevel, "create_initialization_options", None))
PYRepository: CryptoJones/omind
Length of output: 517
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- server.py imports and runner context ---'
sed -n '1,90p' src/omind/server.py
sed -n '450,515p' src/omind/server.py
printf '%s\n' '--- dependency declarations and lock metadata ---'
sed -n '30,55p' pyproject.toml
sed -n '2385,2410p' uv.lock
sed -n '1845,1890p' uv.lock
printf '%s\n' '--- MCP references ---'
rg -n 'MCPServer|lowlevel_server|create_initialization_options|\.run\(' src tests pyproject.toml uv.lock 2>/dev/null || true
printf '%s\n' '--- repository test/config files ---'
git ls-files | rg '(^|/)(test|tests|pyproject|uv\.lock|server).*'Repository: CryptoJones/omind
Length of output: 18727
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import zipfile
lock = Path("uv.lock").read_text()
for needle in ("mcp-2.0.0-py3-none-any.whl", "mcp-2.0.0.tar.gz"):
print(needle, needle in lock)
PY
printf '%s\n' '--- SDK source metadata from PyPI ---'
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -fsSL -o "$tmpdir/mcp.whl" \
https://files.pythonhosted.org/packages/67/72/7d7897418912c1d12e87556630dfb7bf0eac71160e9bef8b447960804ee3/mcp-2.0.0-py3-none-any.whl
python3 - "$tmpdir/mcp.whl" <<'PY'
import sys, zipfile
with zipfile.ZipFile(sys.argv[1]) as z:
names = [n for n in z.namelist() if "mcpserver" in n or n.endswith("server.py")]
print("\n".join(names))
for name in names:
if name.endswith("mcpserver.py") or name.endswith("server.py"):
text = z.read(name).decode()
for i, line in enumerate(text.splitlines(), 1):
if "lowlevel_server" in line or "def run" in line or "create_initialization_options" in line:
print(f"{name}:{i}:{line}")
PY
printf '%s\n' '--- upstream references ---'
curl -fsSL https://api.github.com/repos/modelcontextprotocol/python-sdk/issues/1732 |
python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("title")); print(d.get("state")); print(d.get("body",""))' |
sed -n '1,160p'Repository: CryptoJones/omind
Length of output: 4803
🌐 Web query:
modelcontextprotocol python-sdk issue 1732 MCPServer lowlevel_server public runner mcp 2.0.0
💡 Result:
In the MCP Python SDK, issue #1732 tracked the major refactoring associated with the release of version 2.0.0, which included renaming the high-level FastMCP class to MCPServer [1][2]. Key points regarding these changes and the SDK structure include: - Rename and Restructure: The class FastMCP was renamed to MCPServer to clarify its purpose and reduce confusion with external projects [1][2]. As part of this refactor, transport-specific configuration parameters (such as host and port) were moved from the class constructor to the run() method to decouple server definition from transport configuration [1][3]. - MCP 2.0.0 Release: Version 2.0.0 of the MCP Python SDK was released on July 28, 2026 [2][4]. This major update supports the 2026-07-28 revision of the Model Context Protocol [2][5]. - Low-Level Server: The SDK provides a low-level Server class for advanced use cases requiring direct protocol control [6][7]. While MCPServer (the high-level, decorator-based layer) is built on top of this low-level Server, developers can drop down to the low-level implementation when specific fine-grained control is needed [6][8]. Note that utility commands like mcp run and mcp dev generally expect an MCPServer instance and do not directly support the low-level Server class [7]. - Public Runner/Infrastructure: The 2.0.0 release is published via public, GitHub-hosted runner environments [4]. For users transitioning from v1.x, the official migration guide is recommended, as v2.0.0 contains several breaking changes and architectural reworks [2][4]. Version 1.x is currently in maintenance mode [2].
Citations:
- 1: Rename FastMCP to MCPServer and restructure constructor modelcontextprotocol/python-sdk#1732
- 2: https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0
- 3: Audit MCPServer constructor parameters modelcontextprotocol/python-sdk#2052
- 4: https://pypi.org/project/mcp/2.0.0/
- 5: https://newreleases.io/project/github/modelcontextprotocol/python-sdk/release/v2.0.0
- 6: https://py.sdk.modelcontextprotocol.io/advanced/low-level-server/
- 7: https://py.sdk.modelcontextprotocol.io/low-level-server/
- 8: https://ts.sdk.modelcontextprotocol.io/v2/advanced/low-level-server.html
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -fsSL -o "$tmpdir/pypi.json" https://pypi.org/pypi/mcp/json
python3 - "$tmpdir/pypi.json" <<'PY'
import json, sys
data = json.load(open(sys.argv[1]))
versions = []
for version, files in data["releases"].items():
parts = version.split(".")
if len(parts) >= 2 and parts[0].isdigit() and parts[1].isdigit() and int(parts[0]) == 2:
versions.append(version)
print("2.x releases:", ", ".join(sorted(versions, key=lambda v: tuple(int(x) if x.isdigit() else 0 for x in v.split(".")))))
PY
curl -fsSL -o "$tmpdir/mcp-2.0.0.whl" \
https://files.pythonhosted.org/packages/67/72/7d7897418912c1d12e87556630dfb7bf0eac71160e9bef8b447960804ee3/mcp-2.0.0-py3-none-any.whl
python3 - "$tmpdir/mcp-2.0.0.whl" <<'PY'
import sys, zipfile
with zipfile.ZipFile(sys.argv[1]) as z:
name = "mcp/server/mcpserver/server.py"
lines = z.read(name).decode().splitlines()
for start, end in ((340, 405), (1008, 1030)):
print(f"--- {name}:{start}-{end} ---")
for i in range(start, min(end, len(lines)) + 1):
print(f"{i}:{lines[i-1]}")
PY
printf '%s\n' '--- local call site and tests ---'
sed -n '1,85p' tests/test_server.py
rg -n -C 4 'run_node|_fd_stdio_server|stdio' tests src/omind/server.py .github/workflowsRepository: CryptoJones/omind
Length of output: 14225
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -fsSL -o "$tmpdir/pypi.json" https://pypi.org/pypi/mcp/json
python3 - "$tmpdir/pypi.json" <<'PY'
import json, sys
data = json.load(open(sys.argv[1]))
versions = []
for version in data["releases"]:
parts = version.split(".")
if len(parts) >= 2 and parts[0].isdigit() and parts[1].isdigit() and int(parts[0]) == 2:
versions.append(version)
print("2.x releases:", ", ".join(sorted(versions, key=lambda v: tuple(int(x) if x.isdigit() else 0 for x in v.split(".")))))
PY
curl -fsSL -o "$tmpdir/mcp-2.0.0.whl" \
https://files.pythonhosted.org/packages/67/72/7d7897418912c1d12e87556630dfb7bf0eac71160e9bef8b447960804ee3/mcp-2.0.0-py3-none-any.whl
python3 - "$tmpdir/mcp-2.0.0.whl" <<'PY'
import sys, zipfile
with zipfile.ZipFile(sys.argv[1]) as z:
name = "mcp/server/mcpserver/server.py"
lines = z.read(name).decode().splitlines()
for start, end in ((340, 405), (1008, 1030)):
print(f"--- {name}:{start}-{end} ---")
for i in range(start, min(end, len(lines)) + 1):
print(f"{i}:{lines[i-1]}")
PY
printf '%s\n' '--- local call site and tests ---'
sed -n '1,85p' tests/test_server.py
rg -n -C 4 'run_node|_fd_stdio_server|stdio' tests src/omind/server.py .github/workflowsRepository: CryptoJones/omind
Length of output: 14225
Constrain the supported mcp API.
MCPServer.run_stdio_async() cannot replace this call because _fd_stdio_server() supplies custom streams. If mcp>=2.0.0,<3.0 remains, pin the tested API range and run run_node against each supported version in CI.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/omind/server.py` around lines 489 - 492, Constrain the supported mcp
dependency range to the tested API versions while retaining the low-level runner
in _fd_stdio_server(), since MCPServer.run_stdio_async() cannot handle its
custom streams. Update CI so run_node executes against every supported mcp
version within that range, including the existing initialization flow via
_lowlevel_server.
| def call(server: MCPServer, name: str, args: dict[str, Any]) -> Any: | ||
| """Invoke a tool in-process and return its structured result. | ||
|
|
||
| v2's ``call_tool`` returns a ``CallToolResult`` rather than v1's | ||
| ``(content, structured)`` tuple. | ||
| """ | ||
| return asyncio.run(server.call_tool(name, args)).structured_content |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 8 \
'run_node|_fd_stdio_server|jsonrpc_message_adapter|exclude_unset|resultType|ttlMs|cacheScope|subprocess' \
src testsRepository: CryptoJones/omind
Length of output: 50374
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- server symbols ---'
rg -n -C 6 'class MCPServer|call_tool|_fd_stdio_server|jsonrpc_message_adapter|exclude_unset|resultType|ttlMs|cacheScope|2026|2025' src/omind tests/test_server.py
printf '%s\n' '--- test_server.py structure ---'
ast-grep outline tests/test_server.py
printf '%s\n' '--- smoke test ---'
sed -n '330,435p' tests/test_server.py
printf '%s\n' '--- server implementation ---'
fd -i -t f 'server.py' srcRepository: CryptoJones/omind
Length of output: 43385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- server symbols ---'
rg -n -C 6 'class MCPServer|call_tool|_fd_stdio_server|jsonrpc_message_adapter|exclude_unset|resultType|ttlMs|cacheScope|2026|2025' src/omind tests/test_server.py
printf '%s\n' '--- test_server.py structure ---'
ast-grep outline tests/test_server.py
printf '%s\n' '--- smoke test ---'
sed -n '330,435p' tests/test_server.py
printf '%s\n' '--- server implementation ---'
fd -i -t f 'server.py' srcRepository: CryptoJones/omind
Length of output: 43385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- server transport and imports ---'
sed -n '1,155p' src/omind/server.py
printf '%s\n' '--- server construction and tool handlers ---'
sed -n '430,510p' src/omind/server.py
rg -n -C 5 'resultType|ttlMs|cacheScope|protocolVersion|2026-07-28|2025-06-18|CallToolResult|ToolAnnotations|structuredContent|structured_content' src tests pyproject.toml uv.lock
printf '%s\n' '--- dependency declarations ---'
rg -n -C 4 'mcp|model-context-protocol|pydantic' pyproject.toml uv.lockRepository: CryptoJones/omind
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- server transport and imports ---'
sed -n '1,155p' src/omind/server.py
printf '%s\n' '--- server construction and tool handlers ---'
sed -n '430,510p' src/omind/server.py
printf '%s\n' '--- protocol and result fields ---'
rg -n -C 5 'resultType|ttlMs|cacheScope|protocolVersion|2026-07-28|2025-06-18|CallToolResult|ToolAnnotations|structuredContent|structured_content' src tests pyproject.toml uv.lock
printf '%s\n' '--- dependency declarations ---'
rg -n -C 4 'mcp|model-context-protocol|pydantic' pyproject.toml uv.lockRepository: CryptoJones/omind
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- build_server and tool result declarations ---'
sed -n '150,430p' src/omind/server.py
printf '%s\n' '--- all protocol-field references, excluding lockfile noise ---'
rg -n 'resultType|ttlMs|cacheScope|protocolVersion|2026-07-28|2025-06-18|model_dump_json|exclude_unset' \
src tests --glob '*.py' --glob '*.md'
printf '%s\n' '--- package metadata and tracked test configuration ---'
rg -n -C 3 'mcp|pytest|test_server|pythonpath|addopts' pyproject.toml pytest.ini setup.cfg tox.ini .github 2>/dev/null || trueRepository: CryptoJones/omind
Length of output: 15461
🌐 Web query:
https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/migration/support-2026-07-28.md
💡 Result:
The document support-2026-07-28.md serves as a technical migration guide for developers already using the Model Context Protocol (MCP) TypeScript SDK v2 packages who wish to adopt the 2026-07-28 protocol revision [1][2]. This migration path is architectural and is not supported by automated codemods [3][4]. Key aspects of the 2026-07-28 revision include: Explicit Opt-in: The SDK does not use the 2026-07-28 protocol by default. Adopting it requires explicit configuration, such as using ClientOptions.versionNegotiation on the client side [1][2]. Server Entry Points: The revision introduces specific handlers to support the new protocol era. For HTTP, developers use createMcpHandler, which by default can serve both 2025-era and 2026-era traffic. For stdio or long-lived connections, the serveStdio entry point is used to pin the connection to the 2026-07-28 protocol [1][2]. Server Identity and Meta: Based on spec PR #3002, server identity information was moved from the DiscoverResult body to the result _meta field (using the key io.modelcontextprotocol/serverInfo). Additionally, the clientInfo field in the request envelope was demoted from a required field to a SHOULD [5][6]. Deprecated Subsystems: As of the 2026-07-28 protocol version (SEP-2577), the roots, sampling, and logging subsystems are deprecated in favor of a multi-round-trip input_required pattern [7][8]. This revision also includes updates to wire codecs, handling of per-session state, and changes to authentication and subscription mechanisms [1][2]. Users currently on MCP SDK v1.x are advised to first complete the upgrade to v2 using the upgrade-to-v2.md guide before proceeding with the 2026-07-28 migration [1][7][3].
Citations:
- 1: https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/migration/support-2026-07-28.md
- 2: https://ts.sdk.modelcontextprotocol.io/v2/migration/support-2026-07-28
- 3: https://ts.sdk.modelcontextprotocol.io/v2/migration/
- 4: https://ts.sdk.modelcontextprotocol.io/v2/api/@modelcontextprotocol/codemod/
- 5: modelcontextprotocol/typescript-sdk@f413763
- 6: Align 2026-07-28 wire with spec PR #3002: serverInfo in result _meta, clientInfo optional modelcontextprotocol/typescript-sdk#2513
- 7: https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/migration/upgrade-to-v2.md
- 8: https://ts.sdk.modelcontextprotocol.io/v2/migration/upgrade-to-v2.html
🌐 Web query:
MCP 2026-07-28 resultType ttlMs cacheScope tools/call
💡 Result:
In the Model Context Protocol (MCP) as of the July 28, 2026, specification, resultType, ttlMs, and cacheScope are key components introduced to improve protocol efficiency, specifically regarding caching and request handling [1][2]. ResultType The resultType field is a required property in all MCP result responses that allows clients to determine how to parse and handle the result object [1][3]. - "complete": Indicates the request finished successfully and the result contains the final content [1][3]. - "input_required": Indicates the request is incomplete and requires additional information (often associated with the Multi Round-Trip Requests or MRTR pattern) [1][3]. For backward compatibility, clients must treat absent resultType fields as "complete" [1][3]. Caching Fields (ttlMs and cacheScope) These fields are part of a CacheableResult interface and are required on results from specific operations, such as tools/list, prompts/list, and various resource-related calls [1][4]. They provide caching hints to clients to reduce redundant polling [1][5]. - ttlMs (Time-to-live): An integer value in milliseconds specifying how long a client may consider the result fresh [6][1]. It acts as a freshness hint, not a guarantee, and may be invalidated by other notification mechanisms like list_changed [4]. - cacheScope: Defines the intended scope of the cached response, similar to HTTP Cache-Control headers [6][5]. - "public": The response contains no user-specific data and may be shared/stored by any client or proxy [6][5]. - "private": The response contains private data that must not be shared across different authorization contexts [6][5]. tools/call The tools/call method is used to invoke a specific tool [7][8]. While the tool list results (returned via tools/list) carry the caching metadata described above, the actual execution result of a tools/call itself typically follows the standard response structure containing the final content (marked as resultType: "complete") or signals the need for further input via an InputRequiredResult [7][1]. Top results: [7][6][1][2][4]
Citations:
- 1: https://modelcontextprotocol.io/specification/2026-07-28/changelog
- 2: https://torresmateo.com/mcp-tool-call-deconstructed/
- 3: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/02dd8f61/docs/specification/draft/basic/index.mdx
- 4: https://csharp.sdk.modelcontextprotocol.io/v2/api/ModelContextProtocol.Protocol.ICacheableResult.html
- 5: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/draft/server/utilities/caching.mdx
- 6: https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/caching
- 7: https://modelcontextprotocol.io/specification/2026-07-28/server/tools
- 8: https://modelcontextprotocol.io/specification/2025-11-25/server/tools
🌐 Web query:
site:github.com/modelcontextprotocol/typescript-sdk "resultType" "ttlMs" "cacheScope"
💡 Result:
In the context of the Model Context Protocol (MCP) as of the 2026-07-28 protocol revision, resultType, ttlMs, and cacheScope are wire-level fields used to manage result handling, discrimination, and caching behavior [1][2]. resultType This is a required discriminator field on the wire that enables the receiver to classify the nature of a result before performing schema validation [1][3][2]. - For standard successful operations, it is typically set to 'complete' [1][4][5]. - Other values, such as 'input_required', trigger specific client-side behaviors (e.g., auto-fulfillment) [1][3]. - If the field is absent or contains an unknown value in the 2026-era protocol, it is treated as a protocol violation or an error [1][3][2]. - In many implementations, this field is consumed and stripped at the protocol layer, meaning it is often not visible to application-level code [1][2]. ttlMs (Time-to-Live in milliseconds) This field specifies the duration for which a cacheable result remains valid [1][2]. - It is a non-negative integer (defaults to 0 if omitted or malformed) [1][4][6]. - It informs the client's response-cache layer on how long to store the result [1]. cacheScope This field defines the visibility or sharing policy of the cached result [1][2]. - It must be either 'public' or 'private' [7][4]. - It defaults to 'private' (the most conservative policy) if absent or malformed [1][4][6]. These fields are essential components of the "cache hints" mechanism in modern MCP revisions, allowing servers to advertise caching policies to clients [1][2]. While these fields are mandatory on the wire for cacheable operations, they are often handled automatically by the SDK's encoding and decoding layers, ensuring that application code can operate with a cleaner, more abstract result type [1][7][3].
Citations:
- 1: https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/migration/support-2026-07-28.md
- 2: https://github.com/modelcontextprotocol/typescript-sdk/blob/cc4b4161/packages/core-internal/src/wire/rev2026-07-28/codec.ts
- 3: https://github.com/modelcontextprotocol/typescript-sdk/blob/cc4b4161/packages/core-internal/src/wire/codec.ts
- 4: https://github.com/modelcontextprotocol/typescript-sdk/blob/cc4b4161/packages/core-internal/src/wire/rev2026-07-28/buildSchemas.ts
- 5: https://github.com/modelcontextprotocol/typescript-sdk/blob/cc4b4161/packages/client/test/client/discover.test.ts
- 6: https://github.com/modelcontextprotocol/typescript-sdk/blob/cc4b4161/packages/client/src/client/probeClassifier.ts
- 7: https://github.com/modelcontextprotocol/typescript-sdk/blob/cc4b4161/packages/core-internal/src/wire/rev2026-07-28/encodeContract.ts
Extend the stdio smoke test to cover the negotiated wire contract. Send tools/call and assert resultType; assert ttlMs and cacheScope on the cacheable tools/list response. Keep the existing 2025-06-18 initialization and clean-EOF assertions. In-process call_tool() cannot detect serialization regressions in _fd_stdio_server.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_server.py` around lines 57 - 63, Extend the stdio smoke test
around the existing 2025-06-18 initialization and clean-EOF checks to send a
tools/call request and assert its resultType, then assert ttlMs and cacheScope
on the cacheable tools/list response. Exercise the actual _fd_stdio_server wire
path rather than relying on the in-process call() helper, while preserving the
existing initialization and EOF assertions.
What
Moves omind to MCP revision
2026-07-28— the stateless revision — by adopting themcp2.x SDK.FastMCPbecomesMCPServer. The tool decorators and every tool's behaviour are unchanged, and a v2 server still serves 2025-era clients from the same process, so existing MCP clients keep working.The
<2.0cap onmcp(#131) did exactly what it was added for: it held the fleet on 1.x until this could land deliberately rather than arriving overnight via a self-update. Re-capped at<3.0.Two real bugs this surfaced
Neither was cosmetic, and neither would have shown up as an error:
omind nodewent silent over stdio. Our fd-readiness transport parsed lines withmcp.types.JSONRPCMessage.model_validate_json. In 2.x,JSONRPCMessageis a plain union alias with no such method. The resultingAttributeErrorwas swallowed into the read stream, so every request hung until timeout instead of erroring. Now parses through the SDK'sjsonrpc_message_adapter, matching the SDK's own stdio transport.Replies dropped the new envelope fields. Serialization used
exclude_none=True; the SDK's transport usesexclude_unset=True. That flag is what keeps the 2026-07-28 envelope fields —resultType,ttlMs,cacheScope— on the wire.We deliberately keep our own fd-readiness transport: 2.x's
stdio_serverstill usesanyio.wrap_file, which is the thing ours was written to avoid.Verification
pytestruff check .mypy srcmcp-conformance0.2.0tests/test_server.pypreviously took 61s and failed the stdio handshake test; it now runs in 1.3s with all 21 passing.Note
uv.lockrefreshed:mcp 1.28.1 → 2.0.0,+mcp-types 2.0.0,+opentelemetry-api,-httpx-sse.Codeberg is the source of truth for this repo, but no Codeberg key was loaded in this session — this branch is on GitHub only and still needs a Codeberg push.
Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/