feat: add support for custom requests (#590)

#580 and #556 introduced support for custom notifications,
so this PR takes the next logical step and adds support for custom requests:

- Introduces `CustomRequest` and `CustomResult` model types, wires them into the client/server
  request and result unions, and allows `ClientRequest::method()` to return the dynamic method
  name.
- Implements serde and meta handling for `CustomRequest` so `_meta` is carried through
  extensions; adds default `on_custom_request` handlers that return `METHOD_NOT_FOUND` unless
  overridden.
- Updates JSON schema fixtures to include the new request/result shapes and `EmptyObject`
  strictness.
- Adds tests for custom request roundtrips and end-to-end client↔server handling.
- Focused integration test in `crates/rmcp/tests/test_custom_request.rs`.

For additional testing, I used this locally to update Codex to use a custom
request instead of a custom notification so that it gets an "ack" from the MCP
server to ensure it has processed the update before sending more messages:
https://github.com/openai/codex/pull/8142.
This commit is contained in:
Michael Bolin 2025-12-18 09:41:06 -08:00 • committed by GitHub
commit e0faf1ed51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -69,6 +69,10 @@ impl<H: ServerHandler> Service<RoleServer> for H {
.list_tools(request.params, context)
.await
.map(ServerResult::ListToolsResult),
ClientRequest::CustomRequest(request) => self
.on_custom_request(request, context)
.await
.map(ServerResult::CustomResult),
}
}
@ -200,6 +204,19 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
) -> impl Future<Output = Result<ListToolsResult, McpError>> + Send + '_ {
std::future::ready(Ok(ListToolsResult::default()))
}
fn on_custom_request(
&self,
request: CustomRequest,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<CustomResult, McpError>> + Send + '_ {
let CustomRequest { method, .. } = request;
let _ = context;
std::future::ready(Err(McpError::new(
ErrorCode::METHOD_NOT_FOUND,
method,
None,
)))
}
fn on_cancelled(
&self,