Skip to content

Commit 61ce41b

Browse files
committed
Merge remote-tracking branch 'upstream/main'
Merge upstream v2.2.0 (SEP-2322 MRTR, SEP-2106 relaxed output schemas, SEP-2243 headers, SEP-2575 client lifecycle, Meta->MetaObject split) while preserving the fork's anthropic-ext extensions and dep bumps. Conflict/semantic resolutions: - IntoCallToolResult now yields CallToolResponse: JsonAndArtifact impl, structured_with_text_fallback, and the router normalize hook updated to handle the Complete/InputRequired split. - schema_for_output is now infallible (SEP-2106): dropped the macro panic wrappers and the obsolete degenerate-Json<Value> rejection test; kept the fork's ReplaceBoolSchemas normalization + tests. - CallToolRequestParams gained input_responses/request_state: fork test literal switched to ..Default::default(). - Kept fork dep bumps (schemars 1.2, reqwest 0.13.4, url 2.5, chrono 0.4.45, pastey 0.2.3, process-wrap 9.1, rand removal) alongside upstream's hmac/sha2 request-state feature and sse-stream 0.2.4. - Merged ROADMAP/CHANGELOG additively. Verified: rmcp lib (448) + integration (628) tests green under --all-features, clippy --all-features -D warnings clean.
2 parents 6d2181e + 9df629e commit 61ce41b

94 files changed

Lines changed: 13634 additions & 2032 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
fetch-depth: 0
2424

2525
- name: Setup Node.js
26-
uses: actions/setup-node@v6
26+
uses: actions/setup-node@v7
2727
with:
2828
node-version: '22'
2929

@@ -170,18 +170,32 @@ jobs:
170170
171171
- name: Check rmcp (all features except local)
172172
run: |
173-
FEATURES=$(cargo metadata --no-deps --format-version 1 \
174-
| jq -r '[.packages[] | select(.name == "rmcp") | .features | keys[]
175-
| select(startswith("__") | not)
176-
| select(. != "local")] | join(",")')
173+
BASE_SHA=${{ github.event.pull_request.base.sha }}
174+
# `cargo public-api diff` builds both revisions with the same feature
175+
# set, so a feature that exists on only one side (e.g. a feature added
176+
# or removed by this PR) would fail to build the other revision. Diff
177+
# only the features present in BOTH the base and the head; features
178+
# unique to one side are necessarily pure additions/removals, which the
179+
# release-type deny flags already govern.
180+
list_features() {
181+
cargo metadata --no-deps --format-version 1 --manifest-path "$1/Cargo.toml" \
182+
| jq -r '.packages[] | select(.name == "rmcp") | .features | keys[]
183+
| select(startswith("__") | not)
184+
| select(. != "local")'
185+
}
186+
list_features "." | sort -u > "$RUNNER_TEMP/head_features"
187+
git worktree add --detach "$RUNNER_TEMP/rmcp-base" "$BASE_SHA"
188+
list_features "$RUNNER_TEMP/rmcp-base" | sort -u > "$RUNNER_TEMP/base_features"
189+
git worktree remove --force "$RUNNER_TEMP/rmcp-base"
190+
FEATURES=$(comm -12 "$RUNNER_TEMP/head_features" "$RUNNER_TEMP/base_features" | paste -sd, -)
177191
cargo public-api \
178192
--package rmcp \
179193
--features "$FEATURES" \
180194
-ss \
181195
diff \
182196
$DENY \
183197
--force \
184-
${{ github.event.pull_request.base.sha }}..${{ github.sha }}
198+
"$BASE_SHA"..${{ github.sha }}
185199
186200
spelling:
187201
name: spell check with typos
@@ -199,7 +213,7 @@ jobs:
199213

200214
# install nodejs
201215
- name: Setup Node.js
202-
uses: actions/setup-node@v6
216+
uses: actions/setup-node@v7
203217
with:
204218
node-version: '22'
205219

@@ -228,7 +242,7 @@ jobs:
228242

229243
# install nodejs
230244
- name: Setup Node.js
231-
uses: actions/setup-node@v6
245+
uses: actions/setup-node@v7
232246
with:
233247
node-version: '22'
234248

@@ -264,7 +278,7 @@ jobs:
264278

265279
# install nodejs
266280
- name: Setup Node.js
267-
uses: actions/setup-node@v6
281+
uses: actions/setup-node@v7
268282
with:
269283
node-version: '22'
270284

@@ -299,7 +313,7 @@ jobs:
299313

300314
# install nodejs
301315
- name: Setup Node.js
302-
uses: actions/setup-node@v6
316+
uses: actions/setup-node@v7
303317
with:
304318
node-version: '22'
305319

.github/workflows/conformance.yml

Lines changed: 172 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ concurrency:
1414
env:
1515
# Pinned for reproducible runs; bump deliberately when the suite updates.
1616
CONFORMANCE_VERSION: "0.1.16"
17+
DRAFT_CONFORMANCE_VERSION: "0.2.0-alpha.9"
1718

1819
jobs:
1920
server:
@@ -29,12 +30,14 @@ jobs:
2930
- uses: Swatinem/rust-cache@v2
3031

3132
# Build the whole package (server + client bins): the conformance crate is
32-
# excluded from the workspace default-members, so this is the only CI job
33-
# that catches compile breakage in it.
33+
# excluded from the workspace default-members.
3434
- name: Build conformance binaries
3535
run: cargo build -p mcp-conformance
3636

37-
- name: Start conformance server
37+
- name: Test conformance server
38+
run: cargo test -p mcp-conformance --bin conformance-server
39+
40+
- name: Start 2025-11-25 server
3841
run: |
3942
PORT=8001 ./target/debug/conformance-server &
4043
echo $! > server.pid
@@ -47,7 +50,7 @@ jobs:
4750
echo "conformance server did not become ready" >&2
4851
exit 1
4952
50-
- name: Run server conformance suite
53+
- name: Run 2025-11-25 server suite
5154
run: |
5255
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
5356
--url http://127.0.0.1:8001/mcp \
@@ -56,7 +59,7 @@ jobs:
5659
5760
# These pass today but are excluded from the default "active" suite;
5861
# run them explicitly so regressions are still caught.
59-
- name: Run pending scenarios
62+
- name: Run 2025-11-25 pending scenarios
6063
run: |
6164
for scenario in json-schema-2020-12 server-sse-polling; do
6265
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
@@ -65,13 +68,175 @@ jobs:
6568
-o conformance-results
6669
done
6770
68-
- name: Stop conformance server
71+
- name: Start draft server
72+
run: |
73+
STATELESS=1 PORT=8002 ./target/debug/conformance-server &
74+
echo $! > draft-server.pid
75+
for _ in $(seq 1 30); do
76+
if curl -s -o /dev/null http://127.0.0.1:8002/mcp; then
77+
exit 0
78+
fi
79+
sleep 1
80+
done
81+
echo "draft conformance server did not become ready" >&2
82+
exit 1
83+
84+
# Run discovery separately until #985 enables the full draft suite.
85+
- name: Run SEP-2575 discovery contract
86+
run: |
87+
endpoint=http://127.0.0.1:8002/mcp
88+
common_headers=(
89+
-H "Content-Type: application/json"
90+
-H "Accept: application/json, text/event-stream"
91+
-H "Mcp-Method: server/discover"
92+
)
93+
94+
discover_response="$(
95+
curl --fail-with-body --silent --show-error \
96+
"${common_headers[@]}" \
97+
-H "MCP-Protocol-Version: 2026-07-28" \
98+
--data '{
99+
"jsonrpc": "2.0",
100+
"id": "discover",
101+
"method": "server/discover",
102+
"params": {
103+
"_meta": {
104+
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
105+
"io.modelcontextprotocol/clientInfo": {
106+
"name": "conformance-workflow",
107+
"version": "1.0.0"
108+
},
109+
"io.modelcontextprotocol/clientCapabilities": {}
110+
}
111+
}
112+
}' \
113+
"$endpoint"
114+
)"
115+
jq -e '
116+
.result.resultType == "complete" and
117+
(.result.supportedVersions | index("2026-07-28") != null) and
118+
(.result.capabilities | type == "object") and
119+
(.result.serverInfo.name | type == "string") and
120+
.result.ttlMs == 0 and
121+
.result.cacheScope == "private"
122+
' <<<"$discover_response"
123+
124+
status="$(
125+
curl --silent --show-error \
126+
--output /tmp/unsupported-version.json \
127+
--write-out "%{http_code}" \
128+
"${common_headers[@]}" \
129+
-H "MCP-Protocol-Version: 2099-01-01" \
130+
--data '{
131+
"jsonrpc": "2.0",
132+
"id": "unsupported",
133+
"method": "server/discover",
134+
"params": {
135+
"_meta": {
136+
"io.modelcontextprotocol/protocolVersion": "2099-01-01",
137+
"io.modelcontextprotocol/clientInfo": {
138+
"name": "conformance-workflow",
139+
"version": "1.0.0"
140+
},
141+
"io.modelcontextprotocol/clientCapabilities": {}
142+
}
143+
}
144+
}' \
145+
"$endpoint"
146+
)"
147+
test "$status" = "400"
148+
jq -e '
149+
.id == "unsupported" and
150+
.error.code == -32022 and
151+
.error.data.requested == "2099-01-01" and
152+
(.error.data.supported | index("2026-07-28") != null)
153+
' /tmp/unsupported-version.json
154+
155+
# Keep this explicit list until the full draft suite is enabled by #985.
156+
- name: Run supported draft server scenarios
157+
run: |
158+
for scenario in \
159+
sep-2164-resource-not-found \
160+
caching \
161+
http-header-validation \
162+
http-custom-header-server-validation \
163+
input-required-result-basic-elicitation \
164+
input-required-result-basic-sampling \
165+
input-required-result-basic-list-roots \
166+
input-required-result-request-state \
167+
input-required-result-multiple-input-requests \
168+
input-required-result-multi-round \
169+
input-required-result-missing-input-response \
170+
input-required-result-non-tool-request \
171+
input-required-result-result-type \
172+
input-required-result-unsupported-methods \
173+
input-required-result-tampered-state \
174+
input-required-result-capability-check \
175+
input-required-result-ignore-extra-params \
176+
input-required-result-validate-input \
177+
; do
178+
npx -y "@modelcontextprotocol/conformance@${DRAFT_CONFORMANCE_VERSION}" server \
179+
--url http://127.0.0.1:8002/mcp \
180+
--scenario "$scenario" \
181+
--spec-version draft \
182+
-o conformance-results
183+
done
184+
185+
- name: Stop conformance servers
69186
if: always()
70-
run: kill "$(cat server.pid)" 2>/dev/null || true
187+
run: |
188+
kill "$(cat draft-server.pid)" 2>/dev/null || true
189+
kill "$(cat server.pid)" 2>/dev/null || true
71190
72191
- name: Upload results
73192
if: always()
74193
uses: actions/upload-artifact@v7
75194
with:
76195
name: conformance-server-results
77196
path: conformance-results
197+
198+
client:
199+
runs-on: ubuntu-latest
200+
permissions:
201+
contents: read
202+
steps:
203+
- uses: actions/checkout@v7
204+
205+
- name: Install Rust toolchain
206+
uses: dtolnay/rust-toolchain@stable
207+
208+
- uses: Swatinem/rust-cache@v2
209+
210+
- name: Build conformance binaries
211+
run: cargo build -p mcp-conformance
212+
213+
- name: Run 2025-11-25 client suite
214+
run: |
215+
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" client \
216+
--command "$(pwd)/target/debug/conformance-client" \
217+
--suite all \
218+
--spec-version 2025-11-25 \
219+
-o conformance-client-results/full
220+
221+
# SEP-2322 MRTR client scenario (spec 2026-07-28).
222+
- name: Run draft SEP-2322 client scenario
223+
run: |
224+
npx -y "@modelcontextprotocol/conformance@${DRAFT_CONFORMANCE_VERSION}" client \
225+
--command "$(pwd)/target/debug/conformance-client" \
226+
--scenario sep-2322-client-request-state \
227+
-o conformance-client-results/mrtr
228+
229+
- name: Run draft SEP-2575 client scenario
230+
run: |
231+
npx -y "@modelcontextprotocol/conformance@${DRAFT_CONFORMANCE_VERSION}" client \
232+
--command "$(pwd)/target/debug/conformance-client" \
233+
--scenario request-metadata \
234+
--spec-version draft \
235+
-o conformance-client-results/sep-2575
236+
237+
- name: Upload results
238+
if: always()
239+
uses: actions/upload-artifact@v7
240+
with:
241+
name: conformance-client-results
242+
path: conformance-client-results

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ default-members = ["crates/rmcp", "crates/rmcp-macros"]
44
resolver = "2"
55

66
[workspace.dependencies]
7-
rmcp = { version = "2.1.0", path = "./crates/rmcp" }
8-
rmcp-macros = { version = "2.1.0", path = "./crates/rmcp-macros" }
7+
rmcp = { version = "2.2.0", path = "./crates/rmcp" }
8+
rmcp-macros = { version = "2.2.0", path = "./crates/rmcp-macros" }
99

1010
[workspace.package]
1111
edition = "2024"
12-
version = "2.1.0"
12+
version = "2.2.0"
1313
authors = ["4t145 <u4t145@163.com>"]
1414
license = "Apache-2.0"
1515
repository = "https://github.com/modelcontextprotocol/rust-sdk/"

README.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,44 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
7474
```
7575
</details>
7676

77+
### Client lifecycle modes
78+
79+
`serve()` uses the legacy MCP lifecycle: the client sends `initialize`, receives
80+
the negotiated server information, and then sends `notifications/initialized`.
81+
Use [`ClientServiceExt::serve_with_lifecycle`](crates/rmcp/src/service/client.rs) to
82+
select another lifecycle explicitly:
83+
84+
```rust, ignore
85+
use rmcp::{ClientInfo, ClientLifecycleMode, ClientServiceExt, ProtocolVersion};
86+
87+
// Start directly with server/discover and include client metadata on every request.
88+
let client = ClientInfo::default()
89+
.serve_with_lifecycle(
90+
transport,
91+
ClientLifecycleMode::Discover {
92+
preferred_versions: vec![ProtocolVersion::V_2026_07_28],
93+
},
94+
)
95+
.await?;
96+
97+
// Or probe the discover lifecycle and fall back when a legacy server reports
98+
// that server/discover is not implemented.
99+
let client = ClientInfo::default()
100+
.serve_with_lifecycle(
101+
transport,
102+
ClientLifecycleMode::Auto {
103+
preferred_versions: vec![ProtocolVersion::V_2026_07_28],
104+
legacy_version: Some(ProtocolVersion::V_2025_11_25),
105+
},
106+
)
107+
.await?;
108+
```
109+
110+
`ClientLifecycleMode::Initialize` is equivalent to the existing `serve()` behavior.
111+
Discover startup does not send `notifications/initialized`; discovery completes
112+
startup, and each subsequent request carries its protocol version, client
113+
information, and capabilities in `_meta`.
114+
77115
### Build a Server
78116

79117
<details>
@@ -812,10 +850,11 @@ impl ServerHandler for MyServer {
812850

813851
### Initialized notification
814852

815-
Clients send `initialized` after the handshake completes:
853+
Legacy clients send `initialized` after the `initialize` handshake completes.
854+
Clients using `ClientLifecycleMode::Discover` do not send this notification:
816855

817856
```rust
818-
// Sent automatically by rmcp during the serve() handshake.
857+
// Sent automatically by rmcp during the legacy serve() handshake.
819858
// Servers handle it via:
820859
impl ServerHandler for MyServer {
821860
async fn on_initialized(

0 commit comments

Comments
 (0)