feat!: add server discovery and negotiation (SEP-2575) (#973)

* feat!: add server discovery and negotiation

* feat!: make protocol unions extensible

* ci: add discovery conformance coverage
This commit is contained in:
Dale Seo 2026-07-16 13:47:30 -04:00 • committed by GitHub
commit 3549e86d28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -37,7 +37,7 @@ jobs:
- name: Test conformance server
run: cargo test -p mcp-conformance --bin conformance-server
- name: Start conformance server
- name: Start 2025-11-25 server
run: |
PORT=8001 ./target/debug/conformance-server &
echo $! > server.pid
@ -50,7 +50,7 @@ jobs:
echo "conformance server did not become ready" >&2
exit 1
- name: Run server conformance suite
- name: Run 2025-11-25 server suite
run: |
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
--url http://127.0.0.1:8001/mcp \
@ -59,7 +59,7 @@ jobs:
# These pass today but are excluded from the default "active" suite;
# run them explicitly so regressions are still caught.
- name: Run pending scenarios
- name: Run 2025-11-25 pending scenarios
run: |
for scenario in json-schema-2020-12 server-sse-polling; do
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
@ -68,7 +68,7 @@ jobs:
-o conformance-results
done
- name: Start draft conformance server
- name: Start draft server
run: |
STATELESS=1 PORT=8002 ./target/debug/conformance-server &
echo $! > draft-server.pid
@ -81,27 +81,85 @@ jobs:
echo "draft conformance server did not become ready" >&2
exit 1
- name: Run draft SEP scenarios
# Run discovery separately until #985 enables the full draft suite.
- name: Run SEP-2575 discovery contract
run: |
endpoint=http://127.0.0.1:8002/mcp
common_headers=(
-H "Content-Type: application/json"
-H "Accept: application/json, text/event-stream"
-H "Mcp-Method: server/discover"
)
discover_response="$(
curl --fail-with-body --silent --show-error \
"${common_headers[@]}" \
-H "MCP-Protocol-Version: 2026-07-28" \
--data '{
"jsonrpc": "2.0",
"id": "discover",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "conformance-workflow",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}' \
"$endpoint"
)"
jq -e '
.result.resultType == "complete" and
(.result.supportedVersions | index("2026-07-28") != null) and
(.result.capabilities | type == "object") and
(.result.serverInfo.name | type == "string") and
.result.ttlMs == 0 and
.result.cacheScope == "private"
' <<<"$discover_response"
status="$(
curl --silent --show-error \
--output /tmp/unsupported-version.json \
--write-out "%{http_code}" \
"${common_headers[@]}" \
-H "MCP-Protocol-Version: 2099-01-01" \
--data '{
"jsonrpc": "2.0",
"id": "unsupported",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2099-01-01",
"io.modelcontextprotocol/clientInfo": {
"name": "conformance-workflow",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}' \
"$endpoint"
)"
test "$status" = "400"
jq -e '
.id == "unsupported" and
.error.code == -32022 and
.error.data.requested == "2099-01-01" and
(.error.data.supported | index("2026-07-28") != null)
' /tmp/unsupported-version.json
# Keep this explicit list until the full draft suite is enabled by #985.
- name: Run supported draft server scenarios
run: |
for scenario in \
sep-2164-resource-not-found \
caching \
http-header-validation \
http-custom-header-server-validation \
; do
npx -y "@modelcontextprotocol/conformance@${DRAFT_CONFORMANCE_VERSION}" server \
--url http://127.0.0.1:8002/mcp \
--scenario "$scenario" \
--spec-version draft \
-o conformance-results
done
# SEP-2322 MRTR scenarios (spec 2026-07-28). They speak the stateless
# lifecycle (bare JSON-RPC POSTs, no initialize handshake), so they run
# against the stateless draft server.
- name: Run SEP-2322 MRTR scenarios
run: |
for scenario in \
input-required-result-basic-elicitation \
input-required-result-basic-sampling \
input-required-result-basic-list-roots \
@ -120,17 +178,15 @@ jobs:
npx -y "@modelcontextprotocol/conformance@${DRAFT_CONFORMANCE_VERSION}" server \
--url http://127.0.0.1:8002/mcp \
--scenario "$scenario" \
--spec-version draft \
-o conformance-results
done
- name: Stop draft conformance server
- name: Stop conformance servers
if: always()
run: kill "$(cat draft-server.pid)" 2>/dev/null || true
- name: Stop conformance server
if: always()
run: kill "$(cat server.pid)" 2>/dev/null || true
run: |
kill "$(cat draft-server.pid)" 2>/dev/null || true
kill "$(cat server.pid)" 2>/dev/null || true
- name: Upload results
if: always()
@ -154,7 +210,7 @@ jobs:
- name: Build conformance binaries
run: cargo build -p mcp-conformance
- name: Run full client conformance suite
- name: Run 2025-11-25 client suite
run: |
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" client \
--command "$(pwd)/target/debug/conformance-client" \
@ -163,7 +219,7 @@ jobs:
-o conformance-client-results/full
# SEP-2322 MRTR client scenario (spec 2026-07-28).
- name: Run SEP-2322 MRTR client scenario
- name: Run draft SEP-2322 client scenario
run: |
npx -y "@modelcontextprotocol/conformance@${DRAFT_CONFORMANCE_VERSION}" client \
--command "$(pwd)/target/debug/conformance-client" \