feat(conformance): add SEP-2243 header validation tool (#997)
This commit is contained in:
parent
839922d8fd
commit
b93220d4a7
3 changed files with 82 additions and 1 deletions
10
.github/workflows/conformance.yml
vendored
10
.github/workflows/conformance.yml
vendored
|
|
@ -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" \
|
||||
|
|
|
|||
|
|
@ -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")));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue