feat: Handle MCP server notification messages (#2613)

Co-authored-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
Jack Amadeo
2025-05-30 11:50:14 -04:00
committed by GitHub
parent eeb61ace22
commit 03e5549b54
40 changed files with 1186 additions and 443 deletions

View File

@@ -1,7 +1,6 @@
use mcp_client::{
client::{ClientCapabilities, ClientInfo, McpClient, McpClientTrait},
transport::{SseTransport, StdioTransport, Transport},
McpService,
};
use rand::Rng;
use rand::SeedableRng;
@@ -20,18 +19,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let transport1 = StdioTransport::new("uvx", vec!["mcp-server-git".to_string()], HashMap::new());
let handle1 = transport1.start().await?;
let service1 = McpService::with_timeout(handle1, Duration::from_secs(30));
let client1 = McpClient::new(service1);
let client1 = McpClient::connect(handle1, Duration::from_secs(30)).await?;
let transport2 = StdioTransport::new("uvx", vec!["mcp-server-git".to_string()], HashMap::new());
let handle2 = transport2.start().await?;
let service2 = McpService::with_timeout(handle2, Duration::from_secs(30));
let client2 = McpClient::new(service2);
let client2 = McpClient::connect(handle2, Duration::from_secs(30)).await?;
let transport3 = SseTransport::new("http://localhost:8000/sse", HashMap::new());
let handle3 = transport3.start().await?;
let service3 = McpService::with_timeout(handle3, Duration::from_secs(10));
let client3 = McpClient::new(service3);
let client3 = McpClient::connect(handle3, Duration::from_secs(10)).await?;
// Initialize both clients
let mut clients: Vec<Box<dyn McpClientTrait>> =

View File

@@ -0,0 +1,122 @@
use anyhow::Result;
use futures::lock::Mutex;
use mcp_client::client::{ClientCapabilities, ClientInfo, McpClient, McpClientTrait};
use mcp_client::transport::{SseTransport, Transport};
use mcp_client::StdioTransport;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tracing_subscriber::EnvFilter;
#[tokio::main]
async fn main() -> Result<()> {
// Initialize logging
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::from_default_env()
.add_directive("mcp_client=debug".parse().unwrap())
.add_directive("eventsource_client=info".parse().unwrap()),
)
.init();
test_transport(sse_transport().await?).await?;
test_transport(stdio_transport().await?).await?;
Ok(())
}
async fn sse_transport() -> Result<SseTransport> {
let port = "60053";
tokio::process::Command::new("npx")
.env("PORT", port)
.arg("@modelcontextprotocol/server-everything")
.arg("sse")
.spawn()?;
tokio::time::sleep(Duration::from_secs(1)).await;
Ok(SseTransport::new(
format!("http://localhost:{}/sse", port),
HashMap::new(),
))
}
async fn stdio_transport() -> Result<StdioTransport> {
Ok(StdioTransport::new(
"npx",
vec!["@modelcontextprotocol/server-everything"]
.into_iter()
.map(|s| s.to_string())
.collect(),
HashMap::new(),
))
}
async fn test_transport<T>(transport: T) -> Result<()>
where
T: Transport + Send + 'static,
{
// Start transport
let handle = transport.start().await?;
// Create client
let mut client = McpClient::connect(handle, Duration::from_secs(10)).await?;
println!("Client created\n");
let mut receiver = client.subscribe().await;
let events = Arc::new(Mutex::new(Vec::new()));
let events_clone = events.clone();
tokio::spawn(async move {
while let Some(event) = receiver.recv().await {
println!("Received event: {event:?}");
events_clone.lock().await.push(event);
}
});
// Initialize
let server_info = client
.initialize(
ClientInfo {
name: "test-client".into(),
version: "1.0.0".into(),
},
ClientCapabilities::default(),
)
.await?;
println!("Connected to server: {server_info:?}\n");
// Sleep for 100ms to allow the server to start - surprisingly this is required!
tokio::time::sleep(Duration::from_millis(500)).await;
// List tools
let tools = client.list_tools(None).await?;
println!("Available tools: {tools:#?}\n");
// Call tool
let tool_result = client
.call_tool("echo", serde_json::json!({ "message": "honk" }))
.await?;
println!("Tool result: {tool_result:#?}\n");
let collected_eventes_before = events.lock().await.len();
let n_steps = 5;
let long_op = client
.call_tool(
"longRunningOperation",
serde_json::json!({ "duration": 3, "steps": n_steps }),
)
.await?;
println!("Long op result: {long_op:#?}\n");
let collected_events_after = events.lock().await.len();
assert_eq!(collected_events_after - collected_eventes_before, n_steps);
// List resources
let resources = client.list_resources(None).await?;
println!("Resources: {resources:#?}\n");
// Read resource
let resource = client.read_resource("test://static/resource/1").await?;
println!("Resource: {resource:#?}\n");
Ok(())
}

View File

@@ -1,7 +1,6 @@
use anyhow::Result;
use mcp_client::client::{ClientCapabilities, ClientInfo, McpClient, McpClientTrait};
use mcp_client::transport::{SseTransport, Transport};
use mcp_client::McpService;
use std::collections::HashMap;
use std::time::Duration;
use tracing_subscriber::EnvFilter;
@@ -23,11 +22,8 @@ async fn main() -> Result<()> {
// Start transport
let handle = transport.start().await?;
// Create the service with timeout middleware
let service = McpService::with_timeout(handle, Duration::from_secs(3));
// Create client
let mut client = McpClient::new(service);
let mut client = McpClient::connect(handle, Duration::from_secs(3)).await?;
println!("Client created\n");
// Initialize

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use anyhow::Result;
use mcp_client::{
ClientCapabilities, ClientInfo, Error as ClientError, McpClient, McpClientTrait, McpService,
ClientCapabilities, ClientInfo, Error as ClientError, McpClient, McpClientTrait,
StdioTransport, Transport,
};
use std::time::Duration;
@@ -25,11 +25,8 @@ async fn main() -> Result<(), ClientError> {
// 2) Start the transport to get a handle
let transport_handle = transport.start().await?;
// 3) Create the service with timeout middleware
let service = McpService::with_timeout(transport_handle, Duration::from_secs(10));
// 4) Create the client with the middleware-wrapped service
let mut client = McpClient::new(service);
// 3) Create the client with the middleware-wrapped service
let mut client = McpClient::connect(transport_handle, Duration::from_secs(10)).await?;
// Initialize
let server_info = client

View File

@@ -5,7 +5,6 @@ use mcp_client::client::{
ClientCapabilities, ClientInfo, Error as ClientError, McpClient, McpClientTrait,
};
use mcp_client::transport::{StdioTransport, Transport};
use mcp_client::McpService;
use std::collections::HashMap;
use std::time::Duration;
use tracing_subscriber::EnvFilter;
@@ -34,11 +33,8 @@ async fn main() -> Result<(), ClientError> {
// Start the transport to get a handle
let transport_handle = transport.start().await.unwrap();
// Create the service with timeout middleware
let service = McpService::with_timeout(transport_handle, Duration::from_secs(10));
// Create client
let mut client = McpClient::new(service);
let mut client = McpClient::connect(transport_handle, Duration::from_secs(10)).await?;
// Initialize
let server_info = client