feat!: scylla-mcp speaks MCP revision 2026-07-28 (stateless) - #50
Conversation
The scylla-mcp head is a hand-rolled JSON-RPC MCP server (no SDK), so the 2026-07-28 revision is implemented directly in the dispatch: - server/discover (a MUST in 2026-07-28): advertises supportedVersions ["2026-07-28"], with serverInfo in the result _meta (it moved out of the body this revision). This is how a client selects the version now that the initialize handshake is gone. - Every result carries resultType: "complete"; tools/list also carries the CacheableResult envelope (ttlMs + cacheScope). - ping — removed in 2026-07-28 — is now rejected (-32601), alongside logging/setLevel and resources/subscribe (which already fell through to the not-found arm). - initialize is retained (advertising 2025-06-18) so pre-2026 clients still connect; a 2026 client discovers first and never reaches it. Adds conformance.toml (probing the read-only list_functions/search against the bundled mathlib.scylla) and Rust unit tests for discover, the result envelope, and the removed methods. scylla-mcp: 16 unit + 4 integration tests pass. Workspace: clippy -D warnings clean, cargo test --workspace --locked all green. mcp-conformance 0.2.0 reports 17 passed, 1 skipped (injection: the head WRAPS binary-derived output as untrusted rather than flagging its own inputs, DD-035). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📝 WalkthroughSummary by CodeRabbit
WalkthroughScylla MCP now supports MCP revision ChangesMCP protocol update
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant MCPConformance
participant ScyllaMCP
participant MathlibFixture
MCPConformance->>ScyllaMCP: server/discover
ScyllaMCP-->>MCPConformance: protocol and capability metadata
MCPConformance->>ScyllaMCP: tools/list
ScyllaMCP-->>MCPConformance: tools with completion and cache metadata
MCPConformance->>ScyllaMCP: tools/call search
ScyllaMCP->>MathlibFixture: read-only search
MathlibFixture-->>ScyllaMCP: search output
ScyllaMCP-->>MCPConformance: resultType complete response
🚥 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 16-18: Update the changelog sentence around the resultType
description to remove the claim that every result includes it, and specify that
resultType applies only to the 2026 discovery, tool-list, and tool-call response
envelopes while preserving the existing initialize behavior.
In `@conformance.toml`:
- Around line 10-11: Update the scylla-mcp command’s path-handling and dispatch
flow so SCYLLA_MCP_ROOT is read and enforced as the base directory for all
client-supplied paths, including the std::fs::read calls used by diff and merge
and the std::fs::write call used by export. Resolve or validate each path
against the configured root before performing filesystem access, preserving "."
as the configured root for this conformance command.
In `@crates/scylla-mcp/src/lib.rs`:
- Around line 454-484: Expand discover_advertises_the_2026_revision to assert
the exact supportedVersions list, ttlMs, cacheScope, and
_meta["io.modelcontextprotocol/serverInfo"], while confirming serverInfo is
absent from the result body. Add a tools/call error-path assertion that still
requires resultType to equal "complete", using the existing dispatch/session
test patterns.
- Around line 226-232: Update the legacy "initialize" handling in the request
dispatcher to associate sessions advertising protocolVersion "2025-06-18" with a
handler that returns the MCP-required empty response for "ping"; otherwise
remove this compatibility response rather than advertising unsupported behavior.
Add a regression test covering initialize followed by ping and asserting the
successful empty ping result.
🪄 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: 2863ca3d-2f86-4f5b-bd2a-700e68730ebf
📒 Files selected for processing (3)
CHANGELOG.mdconformance.tomlcrates/scylla-mcp/src/lib.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
- GitHub Check: engine-service
- GitHub Check: test
🔇 Additional comments (2)
crates/scylla-mcp/src/lib.rs (2)
25-27: LGTM!
254-260: LGTM!
| (advertising `2026-07-28`, with `serverInfo` in the result `_meta`), stamps | ||
| every result with `resultType`, and adds the `CacheableResult` envelope | ||
| (`ttlMs` + `cacheScope`) to `tools/list`. `ping` — removed in this revision — |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Limit the resultType claim to 2026 response envelopes.
initialize intentionally returns a 2025-06-18 result without resultType. Replace “every result” with language that identifies the 2026 discovery, tool-list, and tool-call responses.
🤖 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 16 - 18, Update the changelog sentence around the
resultType description to remove the claim that every result includes it, and
specify that resultType applies only to the 2026 discovery, tool-list, and
tool-call response envelopes while preserving the existing initialize behavior.
| command = ["target/debug/scylla-mcp", "crates/scylla-wasm/web/mathlib.scylla"] | ||
| env = { SCYLLA_MCP_ROOT = "." } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 5 'SCYLLA_MCP_ROOT' crates
rg -n -C 4 'std::fs::(read|write)' crates/scylla-mcpRepository: CryptoJones/Scylla
Length of output: 156
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- tracked files ---'
git ls-files | sed -n '1,160p'
printf '%s\n' '--- conformance.toml ---'
cat -n conformance.toml
printf '%s\n' '--- root-variable references ---'
rg -n -i -C 4 'SCYLLA_MCP_ROOT|dispatch|diff|export|merge' . --glob '!target/**' --glob '!node_modules/**' | sed -n '1,260p'
printf '%s\n' '--- filesystem calls ---'
rg -n -C 3 'std::fs|fs::|read_to_string|write_all|File::|canonicalize|PathBuf|Path::' crates --glob '*.rs' | sed -n '1,260p'Repository: CryptoJones/Scylla
Length of output: 48451
🏁 Script executed:
set -euo pipefail
printf '%s\n' '--- MCP source outline ---'
ast-grep outline crates/scylla-mcp/src/lib.rs
ast-grep outline crates/scylla-mcp/src/main.rs
printf '%s\n' '--- MCP main ---'
cat -n crates/scylla-mcp/src/main.rs
printf '%s\n' '--- MCP dispatch and filesystem tools ---'
cat -n crates/scylla-mcp/src/lib.rs | sed -n '1,235p'
printf '%s\n' '--- MCP tests around filesystem tools ---'
cat -n crates/scylla-mcp/src/lib.rs | sed -n '350,435p'
cat -n crates/scylla-mcp/tests/conformance.rs | sed -n '1,180p'Repository: CryptoJones/Scylla
Length of output: 31104
Enforce SCYLLA_MCP_ROOT before dispatch
scylla-mcp does not read SCYLLA_MCP_ROOT. diff and merge call std::fs::read, and export calls std::fs::write with client-supplied paths. The configuration does not confine these tools to ".".
🤖 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 `@conformance.toml` around lines 10 - 11, Update the scylla-mcp command’s
path-handling and dispatch flow so SCYLLA_MCP_ROOT is read and enforced as the
base directory for all client-supplied paths, including the std::fs::read calls
used by diff and merge and the std::fs::write call used by export. Resolve or
validate each path against the configured root before performing filesystem
access, preserving "." as the configured root for this conformance command.
| // Retained for pre-2026 clients that still open with the handshake. The | ||
| // 2026-07-28 client never reaches this — it discovers first. | ||
| "initialize" => json!({"jsonrpc": "2.0", "id": id, "result": { | ||
| "protocolVersion": "2024-11-05", | ||
| "protocolVersion": "2025-06-18", | ||
| "serverInfo": {"name": "scylla-mcp", "version": env!("CARGO_PKG_VERSION")}, | ||
| "capabilities": {"tools": {}} | ||
| }}), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate file map ---'
ast-grep outline crates/scylla-mcp/src/lib.rs --lang rust || true
printf '%s\n' '--- relevant source ---'
sed -n '150,280p' crates/scylla-mcp/src/lib.rs
printf '%s\n' '--- references to protocol dispatch and ping ---'
rg -n -C 3 'initialize|ping|protocolVersion|jsonrpc|32601|2025-06-18' crates/scylla-mcp
printf '%s\n' '--- tests ---'
rg -n -C 4 '#\[test\]|#\[tokio::test\]|initialize|ping' crates/scylla-mcp --glob '*.rs'Repository: CryptoJones/Scylla
Length of output: 44283
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- imports, protocol constants, and session type ---'
sed -n '1,120p' crates/scylla-mcp/src/lib.rs
printf '%s\n' '--- exact dispatch tests and helper context ---'
sed -n '440,500p' crates/scylla-mcp/src/lib.rs
printf '%s\n' '--- session declarations and protocol-related fields ---'
rg -n -C 4 'struct Session|pub struct Session|protocol|version|dispatch\(' crates/scylla-mcp crates --glob '*.rs'
printf '%s\n' '--- deterministic source-level behavior probe ---'
python3 - <<'PY'
from pathlib import Path
p = Path("crates/scylla-mcp/src/lib.rs").read_text()
dispatch = p[p.index("pub fn dispatch"):p.index("#[cfg(test)]", p.index("pub fn dispatch"))]
init = dispatch[dispatch.index('"initialize"'):dispatch.index('// List results', dispatch.index('"initialize"'))]
ping = '"ping" in dispatch or '"ping/' in dispatch
default = 'code": -32601' in dispatch and 'method not found' in dispatch
print("initialize advertises 2025-06-18:", '"protocolVersion": "2025-06-18"' in init)
print("initialize arm stores protocol state:", any(x in init for x in ("session.", "protocol", "selected")))
print("dispatch has a ping arm:", ping)
print("unknown methods return -32601:", default)
assert '"protocolVersion": "2025-06-18"' in init
assert not any(x in init for x in ("session.", "protocol", "selected"))
assert not ping
assert default
PYRepository: CryptoJones/Scylla
Length of output: 50374
🌐 Web query:
site:modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping MCP 2025-06-18 ping receiver MUST respond
💡 Result:
According to the Model Context Protocol (MCP) specification dated June 18, 2025, the ping mechanism is a utility used by either the client or the server to verify that the connection is active and the counterpart is responsive [1]. Per the specification's behavior requirements, the receiver of a ping request MUST respond promptly with an empty JSON-RPC response [1]. The required response format is as follows: { "jsonrpc": "2.0", "id": "123", "result": {} } If the sender does not receive a response within a reasonable timeout, it may consider the connection stale, terminate the connection, or attempt to reconnect [1]. Additionally, the specification suggests that implementations should periodically issue pings, make ping frequency configurable, and avoid excessive pinging to minimize network overhead [1].
Citations:
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- focused dispatch source ---'
sed -n '200,270p' crates/scylla-mcp/src/lib.rs
printf '%s\n' '--- MCP main loop ---'
sed -n '1,75p' crates/scylla-mcp/src/main.rs
printf '%s\n' '--- deterministic dispatch-shape probe ---'
python3 - <<'PY'
from pathlib import Path
src = Path("crates/scylla-mcp/src/lib.rs").read_text()
start = src.index("pub fn dispatch")
end = src.index("#[cfg(test)]", start)
body = src[start:end]
init_start = body.index('"initialize"')
init_end = body.index('// List results', init_start)
init_arm = body[init_start:init_end]
print("initialize advertises:", "2025-06-18" if '"protocolVersion": "2025-06-18"' in init_arm else "none")
print("initialize mutates session:", any(token in init_arm for token in ("session.", "session,", "selected_protocol")))
print("explicit ping arm:", '"ping" =>' in body)
print("default -32601 branch:", '"error": {"code": -32601' in body and 'method not found' in body)
print("same dispatch function handles initialize and ping:", '"initialize"' in body and '"ping" =>' not in body)
assert '"protocolVersion": "2025-06-18"' in init_arm
assert not any(token in init_arm for token in ("session.", "session,", "selected_protocol"))
assert '"ping" =>' not in body
assert '"error": {"code": -32601' in body and 'method not found' in body
PYRepository: CryptoJones/Scylla
Length of output: 6075
Preserve the advertised legacy protocol behavior.
If initialize advertises 2025-06-18, route that session to a handler that supports ping; otherwise remove the legacy compatibility response. ping currently falls through to -32601, although MCP 2025-06-18 requires an empty response. Add a regression test for initialize followed by ping.
🤖 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 `@crates/scylla-mcp/src/lib.rs` around lines 226 - 232, Update the legacy
"initialize" handling in the request dispatcher to associate sessions
advertising protocolVersion "2025-06-18" with a handler that returns the
MCP-required empty response for "ping"; otherwise remove this compatibility
response rather than advertising unsupported behavior. Add a regression test
covering initialize followed by ping and asserting the successful empty ping
result.
| #[test] | ||
| fn discover_advertises_the_2026_revision() { | ||
| // MCP 2026-07-28: server/discover is a MUST and is how a client selects a | ||
| // version without the (removed) initialize handshake. | ||
| let mut s = session(); | ||
| let resp = dispatch(&mut s, &json!({"jsonrpc": "2.0", "id": 1, "method": "server/discover"})); | ||
| let versions = resp["result"]["supportedVersions"].as_array().unwrap(); | ||
| assert!( | ||
| versions.iter().any(|v| v == "2026-07-28"), | ||
| "discover must advertise 2026-07-28, got {versions:?}" | ||
| ); | ||
| assert_eq!(resp["result"]["resultType"], "complete"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn list_result_carries_the_cacheable_envelope() { | ||
| // Every result carries resultType; list results also carry ttlMs + cacheScope. | ||
| let mut s = session(); | ||
| let resp = dispatch(&mut s, &json!({"jsonrpc": "2.0", "id": 1, "method": "tools/list"})); | ||
| assert_eq!(resp["result"]["resultType"], "complete"); | ||
| assert!(resp["result"]["ttlMs"].is_number(), "tools/list must carry ttlMs"); | ||
| assert_eq!(resp["result"]["cacheScope"], "public"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn call_result_carries_result_type() { | ||
| let mut s = session(); | ||
| let resp = dispatch(&mut s, &json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call", | ||
| "params": {"name": "list_functions", "arguments": {}}})); | ||
| assert_eq!(resp["result"]["resultType"], "complete"); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Assert the complete discovery contract.
The test only checks that supportedVersions contains the new revision. Assert the exact version list, ttlMs, cacheScope, and _meta["io.modelcontextprotocol/serverInfo"]. Also assert that serverInfo is absent from the discovery result body.
Add an error-path tools/call assertion for resultType: "complete".
🤖 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 `@crates/scylla-mcp/src/lib.rs` around lines 454 - 484, Expand
discover_advertises_the_2026_revision to assert the exact supportedVersions
list, ttlMs, cacheScope, and _meta["io.modelcontextprotocol/serverInfo"], while
confirming serverInfo is absent from the result body. Add a tools/call
error-path assertion that still requires resultType to equal "complete", using
the existing dispatch/session test patterns.
What
Brings the
scylla-mcphead to MCP revision2026-07-28— the stateless revision.scylla-mcpis a hand-rolled JSON-RPC server (no MCP SDK — the Rust SDK's 2026-07-28 support is still beta anyway, and this crate never used it), so the revision is implemented directly in the dispatch:server/discover(a MUST in 2026-07-28): advertisessupportedVersions: ["2026-07-28"], withserverInfoin the result_meta(it moved out of the body this revision). This is how a client selects the protocol now that theinitializehandshake is gone.resultType: "complete"on every result;tools/listalso carries theCacheableResultenvelope (ttlMs+cacheScope).ping— removed in this revision — is now rejected (-32601), alongsidelogging/setLevelandresources/subscribe.initializeis retained (now advertising2025-06-18) so pre-2026 clients still connect; a 2026 client discovers first and never reaches it.Adds a
conformance.toml(probing the read-onlylist_functions/searchagainst the bundledmathlib.scylla) and Rust unit tests for discover, the result envelope, and the removed methods.Verification
cargo test -p scylla-mcpcargo test --workspace --lockedcargo clippy --workspace --all-targets -- -D warningsmcp-conformance0.2.0The one skip is intentional: the head wraps binary-derived output in an
<untrusted-data>envelope (DD-035) rather than flagging injection in its own inputs, soexpect.injectionis unset.(No
cargo fmtrun — this repo's CI gates on clippy + test, notfmt --check, andmainis notfmt-clean; the diff is only the logical protocol change.)Note
This branch was cut from
github/main. The working checkout had a separatefix/review-backlog-3-9branch active; this change is independent of it.