Skip to content

feat!: adopt MCP revision 2026-07-28 via the mcp 2.x SDK - #192

Merged
CryptoJones merged 1 commit into
mainfrom
feat/mcp-2026-07-28
Aug 1, 2026
Merged

feat!: adopt MCP revision 2026-07-28 via the mcp 2.x SDK#192
CryptoJones merged 1 commit into
mainfrom
feat/mcp-2026-07-28

Conversation

@CryptoJones

Copy link
Copy Markdown
Owner

What

Moves omind to MCP revision 2026-07-28 — the stateless revision — by adopting the mcp 2.x SDK.

FastMCP becomes 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.

The <2.0 cap on mcp (#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:

  1. omind node went silent over stdio. Our fd-readiness transport parsed lines with mcp.types.JSONRPCMessage.model_validate_json. In 2.x, JSONRPCMessage is a plain union alias with no such method. The resulting AttributeError was swallowed into the read stream, so every request hung until timeout instead of erroring. Now parses through the SDK's jsonrpc_message_adapter, matching the SDK's own stdio transport.

  2. Replies dropped the new envelope fields. Serialization used exclude_none=True; the SDK's transport uses exclude_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_server still uses anyio.wrap_file, which is the thing ours was written to avoid.

Verification

Gate Result
pytest 842 passed
ruff check . clean
mypy src clean (46 files)
mcp-conformance 0.2.0 17 passed, 1 skipped (injection, deliberately unset)

tests/test_server.py previously took 61s and failed the stdio handshake test; it now runs in 1.3s with all 21 passing.

Note

uv.lock refreshed: 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/

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>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Updated the server for the latest MCP protocol capabilities while maintaining existing tool behavior and client compatibility.
    • Preserved newly introduced response envelope fields in serialized results.
  • Bug Fixes

    • Improved stdio message parsing for more reliable request handling.
    • Corrected response serialization so explicitly provided fields are retained.
  • Documentation

    • Updated the unreleased changelog with protocol, compatibility, and reliability improvements.

Walkthrough

The project upgrades to MCP SDK 2.x, replaces FastMCP with MCPServer, updates stdio JSON-RPC handling, preserves response envelope fields, and migrates server tests to MCP 2.x result structures.

Changes

MCP 2.x server migration

Layer / File(s) Summary
MCPServer API migration
pyproject.toml, src/omind/server.py, tests/test_server.py, CHANGELOG.md
The dependency and server construction now use MCP 2.x MCPServer. Tests use structured_content from CallToolResult.
Stdio transport compatibility
src/omind/server.py, CHANGELOG.md
JSON-RPC input uses jsonrpc_message_adapter.validate_json. Responses use exclude_unset=True. Stdio execution uses the MCPServer low-level runner.
Server regression test migration
tests/test_server.py
Server fixtures and test signatures use MCPServer. Existing tool, graph, error, traversal, and side-effect assertions remain in place.

Estimated code review effort: 2 (Simple) | ~15 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the MCP 2.x SDK migration and adoption of revision 2026-07-28.
Description check ✅ Passed The description directly explains the MCP 2.x migration, transport fixes, compatibility, and verification results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mcp-2026-07-28

Comment @coderabbitai help to get the list of available commands.

@CryptoJones
CryptoJones merged commit fc102b7 into main Aug 1, 2026
15 of 16 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 186e67e and 7921cfd.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • CHANGELOG.md
  • pyproject.toml
  • src/omind/server.py
  • tests/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 returns None on 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 through OmiStore; external writers use notes.upsert_note. Writes must retain flocking, atomic rename, Lamport Rev: stamping, and soft deletion. Deletes archive notes with Disabled: true; only omind mesh purge permanently removes them.
OmiStore.safe_name must 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 through server._page and expose limit, offset, total, and has_more.
index.md and Memory Template.md are scaffolding, not memories; reading either must not clear the consult gate. Preserve paths.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 a NoteSummary returned from _cached_summary; it is shared cached state. Use dataclasses.replace, as in store._indexed_search.
Treat embed.encode results as potentially plain lists in tests; coerce vectors through searchindex._query_vector rather than assuming a .shape attribute.
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.py
  • tests/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 & Integration

Do not add explicit protocol selection.

mcp 2.0.0 uses serve_dual_era_loop(). A request with the 2026-07-28 envelope selects modern handling; initialize selects 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

Comment thread CHANGELOG.md

## [Unreleased]

### Changed

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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

Comment thread CHANGELOG.md
Comment on lines +11 to +16
- **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).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

Comment thread src/omind/server.py
Comment on lines +489 to +492
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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))
PY

Repository: 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:


🏁 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/workflows

Repository: 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/workflows

Repository: 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.

Comment thread tests/test_server.py
Comment on lines +57 to +63
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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 tests

Repository: 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' src

Repository: 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' src

Repository: 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.lock

Repository: 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.lock

Repository: 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 || true

Repository: 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:


🌐 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:


🌐 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:


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.

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.

1 participant