feat(conformance): add SEP-2243 header validation tool (#997)

This commit is contained in:
King Star 2026-07-17 01:11:45 +08:00 • committed by GitHub
commit b93220d4a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 82 additions and 1 deletions

View file

@ -34,6 +34,9 @@ jobs:
- name: Build conformance binaries
run: cargo build -p mcp-conformance
- name: Test conformance server
run: cargo test -p mcp-conformance --bin conformance-server
- name: Start conformance server
run: |
PORT=8001 ./target/debug/conformance-server &
@ -80,7 +83,12 @@ jobs:
- name: Run draft SEP scenarios
run: |
for scenario in sep-2164-resource-not-found caching http-header-validation; do
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" \

View file

@ -28,6 +28,20 @@ fn json_object(v: Value) -> JsonObject {
}
}
fn custom_header_tool() -> Tool {
Tool::new(
"test_custom_header",
"Validates SEP-2243 custom parameter headers",
json_object(json!({
"type": "object",
"properties": {
"value": { "type": "string", "x-mcp-header": "Value" }
},
"required": ["value"]
})),
)
}
/// Signing key for SEP-2322 `requestState` sealing. A fixed key is fine for a
/// conformance harness; real servers must load a secret out of clients' reach.
const REQUEST_STATE_KEY: &[u8] = b"rust-sdk-conformance-request-state-key!!";
@ -361,6 +375,10 @@ impl ConformanceServer {
}
impl ServerHandler for ConformanceServer {
fn get_tool(&self, name: &str) -> Option<Tool> {
(name == "test_custom_header").then(custom_header_tool)
}
async fn initialize(
&self,
request: InitializeRequestParams,
@ -521,6 +539,7 @@ impl ServerHandler for ConformanceServer {
"properties": {}
})),
),
custom_header_tool(),
];
// SEP-2322 MRTR test tools; all take no arguments.
let mrtr_tools = [
@ -668,6 +687,14 @@ impl ServerHandler for ConformanceServer {
)]))
}
"test_custom_header" => {
let value = args
.get("value")
.and_then(Value::as_str)
.ok_or_else(|| ErrorData::invalid_params("value must be a string", None))?;
Ok(CallToolResult::success(vec![ContentBlock::text(value)]))
}
"test_sampling" => {
let prompt = args
.get("prompt")
@ -1218,3 +1245,26 @@ async fn main() -> anyhow::Result<()> {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn server_exposes_custom_header_tool_for_transport_validation() {
let tool = ConformanceServer::new()
.get_tool("test_custom_header")
.expect("custom-header conformance tool");
let value = Value::Object((*tool.input_schema).clone());
assert_eq!(
value.pointer("/properties/value/type"),
Some(&json!("string"))
);
assert_eq!(
value.pointer("/properties/value/x-mcp-header"),
Some(&json!("Value"))
);
assert_eq!(value.pointer("/required/0"), Some(&json!("value")));
}
}

View file

@ -267,6 +267,29 @@ async fn rejects_param_mismatch_with_32020() -> anyhow::Result<()> {
Ok(())
}
#[tokio::test]
async fn rejects_decoded_base64_param_mismatch_with_32020() -> anyhow::Result<()> {
let (client, url, ct) = spawn_server().await;
let response = post_tool_call(
&client,
&url,
SEP_VERSION,
"deploy",
serde_json::json!({ "region": "us-west1" }),
Some("tools/call"),
Some("deploy"),
Some("=?base64?ZXUtY2VudHJhbDE=?="),
)
.await;
assert_eq!(response.status(), 400);
let body: serde_json::Value = response.json().await?;
assert_eq!(body["error"]["code"], -32020);
ct.cancel();
Ok(())
}
#[tokio::test]
async fn rejects_missing_param_header_with_32020() -> anyhow::Result<()> {
let (client, url, ct) = spawn_server().await;