fix: navigate initialization phase before tools/list request in MCP client

This commit is contained in:
Michael Bolin
2025-05-12 14:43:01 -07:00
parent a1f51bf91b
commit 8a6cf1a8f0
2 changed files with 59 additions and 0 deletions

View File

@@ -10,10 +10,17 @@
//! program. The utility connects, issues a `tools/list` request and prints the
//! server's response as pretty JSON.
use std::time::Duration;
use anyhow::Context;
use anyhow::Result;
use codex_mcp_client::McpClient;
use mcp_types::ClientCapabilities;
use mcp_types::Implementation;
use mcp_types::InitializeRequest;
use mcp_types::InitializeRequestParams;
use mcp_types::ListToolsRequestParams;
use mcp_types::MCP_SCHEMA_VERSION;
#[tokio::main]
async fn main() -> Result<()> {
@@ -33,6 +40,28 @@ async fn main() -> Result<()> {
.await
.with_context(|| format!("failed to spawn subprocess: {original_args:?}"))?;
let params = InitializeRequestParams {
capabilities: ClientCapabilities {
experimental: None,
roots: None,
sampling: None,
},
client_info: Implementation {
name: "codex-mcp-client".to_owned(),
version: env!("CARGO_PKG_VERSION").to_owned(),
},
protocol_version: MCP_SCHEMA_VERSION.to_owned(),
};
let timeout = Some(Duration::from_secs(10));
let response = client
.send_request::<InitializeRequest>(params, timeout)
.await?;
eprintln!("initialize response: {response:?}");
client
.send_notification::<mcp_types::InitializedNotification>(None)
.await?;
// Issue `tools/list` request (no params).
let timeout = None;
let tools = client

View File

@@ -17,6 +17,7 @@ use std::sync::atomic::AtomicI64;
use std::sync::atomic::Ordering;
use std::time::Duration;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use mcp_types::CallToolRequest;
@@ -29,6 +30,7 @@ use mcp_types::JSONRPCResponse;
use mcp_types::ListToolsRequest;
use mcp_types::ListToolsRequestParams;
use mcp_types::ListToolsResult;
use mcp_types::ModelContextProtocolNotification;
use mcp_types::ModelContextProtocolRequest;
use mcp_types::RequestId;
use serde::Serialize;
@@ -273,6 +275,34 @@ impl McpClient {
}
}
pub async fn send_notification<N>(&self, params: N::Params) -> Result<()>
where
N: ModelContextProtocolNotification,
N::Params: Serialize,
{
// Serialize params -> JSON. For many request types `Params` is
// `Option<T>` and `None` should be encoded as *absence* of the field.
let params_json = serde_json::to_value(&params)?;
let params_field = if params_json.is_null() {
None
} else {
Some(params_json)
};
let method = N::METHOD.to_string();
let jsonrpc_notification = JSONRPCNotification {
jsonrpc: JSONRPC_VERSION.to_string(),
method: method.clone(),
params: params_field,
};
let notification = JSONRPCMessage::Notification(jsonrpc_notification);
self.outgoing_tx
.send(notification)
.await
.with_context(|| format!("failed to send notification `{method}` to writer task"))
}
/// Convenience wrapper around `tools/list`.
pub async fn list_tools(
&self,