feat: introduce mcp-types crate (#787)
This adds our own `mcp-types` crate to our Cargo workspace. We vendor in
the
[`2025-03-26/schema.json`](05f2045136/schema/2025-03-26/schema.json)
from the MCP repo and introduce a `generate_mcp_types.py` script to
codegen the `lib.rs` from the JSON schema.
Test coverage is currently light, but I plan to refine things as we
start making use of this crate.
And yes, I am aware that
https://github.com/modelcontextprotocol/rust-sdk exists, though the
published https://crates.io/crates/rmcp appears to be a competing
effort. While things are up in the air, it seems better for us to
control our own version of this code.
Incidentally, Codex did a lot of the work for this PR. I told it to
never edit `lib.rs` directly and instead to update
`generate_mcp_types.py` and then re-run it to update `lib.rs`. It
followed these instructions and once things were working end-to-end, I
iteratively asked for changes to the tests until the API looked
reasonable (and the code worked). Codex was responsible for figuring out
what to do to `generate_mcp_types.py` to achieve the requested test/API
changes.
This commit is contained in:
65
codex-rs/mcp-types/tests/initialize.rs
Normal file
65
codex-rs/mcp-types/tests/initialize.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use mcp_types::ClientCapabilities;
|
||||
use mcp_types::ClientRequest;
|
||||
use mcp_types::Implementation;
|
||||
use mcp_types::InitializeRequestParams;
|
||||
use mcp_types::JSONRPCMessage;
|
||||
use mcp_types::JSONRPCRequest;
|
||||
use mcp_types::RequestId;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn deserialize_initialize_request() {
|
||||
let raw = r#"{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"capabilities": {},
|
||||
"clientInfo": { "name": "acme-client", "version": "1.2.3" },
|
||||
"protocolVersion": "2025-03-26"
|
||||
}
|
||||
}"#;
|
||||
|
||||
// Deserialize full JSONRPCMessage first.
|
||||
let msg: JSONRPCMessage =
|
||||
serde_json::from_str(raw).expect("failed to deserialize JSONRPCMessage");
|
||||
|
||||
// Extract the request variant.
|
||||
let JSONRPCMessage::Request(json_req) = msg else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let expected_req = JSONRPCRequest {
|
||||
id: RequestId::Integer(1),
|
||||
method: "initialize".into(),
|
||||
params: Some(json!({
|
||||
"capabilities": {},
|
||||
"clientInfo": { "name": "acme-client", "version": "1.2.3" },
|
||||
"protocolVersion": "2025-03-26"
|
||||
})),
|
||||
};
|
||||
|
||||
assert_eq!(json_req, expected_req);
|
||||
|
||||
let client_req: ClientRequest =
|
||||
ClientRequest::try_from(json_req).expect("conversion must succeed");
|
||||
let ClientRequest::InitializeRequest(init_params) = client_req else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
init_params,
|
||||
InitializeRequestParams {
|
||||
capabilities: ClientCapabilities {
|
||||
experimental: None,
|
||||
roots: None,
|
||||
sampling: None,
|
||||
},
|
||||
client_info: Implementation {
|
||||
name: "acme-client".into(),
|
||||
version: "1.2.3".into(),
|
||||
},
|
||||
protocol_version: "2025-03-26".into(),
|
||||
}
|
||||
);
|
||||
}
|
||||
43
codex-rs/mcp-types/tests/progress_notification.rs
Normal file
43
codex-rs/mcp-types/tests/progress_notification.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use mcp_types::JSONRPCMessage;
|
||||
use mcp_types::ProgressNotificationParams;
|
||||
use mcp_types::ProgressToken;
|
||||
use mcp_types::ServerNotification;
|
||||
|
||||
#[test]
|
||||
fn deserialize_progress_notification() {
|
||||
let raw = r#"{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/progress",
|
||||
"params": {
|
||||
"message": "Half way there",
|
||||
"progress": 0.5,
|
||||
"progressToken": 99,
|
||||
"total": 1.0
|
||||
}
|
||||
}"#;
|
||||
|
||||
// Deserialize full JSONRPCMessage first.
|
||||
let msg: JSONRPCMessage = serde_json::from_str(raw).expect("invalid JSONRPCMessage");
|
||||
|
||||
// Extract the notification variant.
|
||||
let JSONRPCMessage::Notification(notif) = msg else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
// Convert via generated TryFrom.
|
||||
let server_notif: ServerNotification =
|
||||
ServerNotification::try_from(notif).expect("conversion must succeed");
|
||||
|
||||
let ServerNotification::ProgressNotification(params) = server_notif else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let expected_params = ProgressNotificationParams {
|
||||
message: Some("Half way there".into()),
|
||||
progress: 0.5,
|
||||
progress_token: ProgressToken::Integer(99),
|
||||
total: Some(1.0),
|
||||
};
|
||||
|
||||
assert_eq!(params, expected_params);
|
||||
}
|
||||
Reference in New Issue
Block a user