mcp_runner/transport/mod.rs
1//! Transport module for communication with MCP servers.
2//!
3//! This module provides transport layer implementations for communicating with
4//! Model Context Protocol (MCP) servers. It defines a common `Transport` trait
5//! that abstracts the communication details and provides implementations for
6//! different transport mechanisms.
7//!
8//! Currently supported transport mechanisms:
9//! - `StdioTransport`: Communicates with MCP servers via standard input/output
10
11pub mod json_rpc;
12mod stdio;
13
14use crate::error::Result;
15use async_trait::async_trait;
16pub use json_rpc::{JsonRpcMessage, JsonRpcRequest, JsonRpcResponse};
17use serde_json::Value;
18pub use stdio::StdioTransport;
19
20/// Transport is the core trait for communication with MCP servers.
21///
22/// This trait defines the interface for interacting with Model Context Protocol (MCP)
23/// servers through various transport mechanisms. Implementations of this trait handle
24/// the low-level communication details, allowing clients to focus on high-level interactions.
25///
26/// # Examples
27///
28/// Using a transport to list available tools:
29///
30/// ```no_run
31/// use mcp_runner::transport::Transport;
32/// use mcp_runner::error::Result;
33/// use serde_json::Value;
34///
35/// async fn example<T: Transport>(transport: &T) -> Result<()> {
36/// // Initialize the transport
37/// transport.initialize().await?;
38///
39/// // List available tools
40/// let tools = transport.list_tools().await?;
41/// println!("Available tools: {:?}", tools);
42///
43/// Ok(())
44/// }
45/// ```
46#[async_trait]
47pub trait Transport: Send + Sync {
48 /// Initializes the transport connection to the MCP server.
49 ///
50 /// This method should be called before any other methods to ensure
51 /// the transport is ready for communication.
52 ///
53 /// # Returns
54 ///
55 /// A `Result<()>` that is:
56 /// - `Ok(())` if initialization was successful
57 /// - `Err(Error)` if initialization failed
58 async fn initialize(&self) -> Result<()>;
59
60 /// Lists all available tools provided by the MCP server.
61 ///
62 /// # Returns
63 ///
64 /// A `Result<Vec<Value>>` that is:
65 /// - `Ok(Vec<Value>)` containing a list of tool definitions if successful
66 /// - `Err(Error)` if the request failed
67 ///
68 /// # Tool Definition Format
69 ///
70 /// Each tool definition is a JSON object with at least:
71 /// - `name`: A string identifier for the tool
72 /// - `description`: A human-readable description of the tool
73 /// - Additional fields as specified by the MCP server
74 async fn list_tools(&self) -> Result<Vec<Value>>;
75
76 /// Calls a specific tool on the MCP server with the provided arguments.
77 ///
78 /// # Arguments
79 ///
80 /// * `name` - The name of the tool to call
81 /// * `args` - Arguments to pass to the tool as a JSON value
82 ///
83 /// # Returns
84 ///
85 /// A `Result<Value>` that is:
86 /// - `Ok(Value)` containing the tool's response if successful
87 /// - `Err(Error)` if the tool call failed
88 async fn call_tool(&self, name: &str, args: Value) -> Result<Value>;
89
90 /// Lists all available resources provided by the MCP server.
91 ///
92 /// Resources can include model metadata, usage information, or other
93 /// static or dynamic data exposed by the server.
94 ///
95 /// # Returns
96 ///
97 /// A `Result<Vec<Value>>` that is:
98 /// - `Ok(Vec<Value>)` containing a list of resource definitions if successful
99 /// - `Err(Error)` if the request failed
100 async fn list_resources(&self) -> Result<Vec<Value>>;
101
102 /// Retrieves a specific resource from the MCP server.
103 ///
104 /// # Arguments
105 ///
106 /// * `uri` - The URI identifying the resource to retrieve
107 ///
108 /// # Returns
109 ///
110 /// A `Result<Value>` that is:
111 /// - `Ok(Value)` containing the resource data if successful
112 /// - `Err(Error)` if the resource retrieval failed
113 async fn get_resource(&self, uri: &str) -> Result<Value>;
114}
115
116/// Creates a new transport for a server using the provided configuration
117///
118/// This function creates an appropriate transport based on the server configuration.
119/// Currently, it only supports stdio transport.
120///
121/// # Arguments
122///
123/// * `server_name` - Name of the server for which to create transport
124/// * `server_config` - Server configuration
125///
126/// # Returns
127///
128/// A `Result<StdioTransport>` containing a transport implementation
129pub fn create_transport_for_config(
130 server_name: &str,
131 server_config: &crate::config::ServerConfig,
132) -> Result<StdioTransport> {
133 use crate::error::Error;
134 use async_process::{Command, Stdio as AsyncStdio};
135 use tracing;
136
137 tracing::debug!(server = %server_name, "Creating transport for server");
138
139 // Currently we only support launching a new process for connection
140 let mut cmd = Command::new(&server_config.command);
141
142 // Add arguments
143 cmd.args(&server_config.args);
144
145 // Set environment variables
146 for (key, value) in &server_config.env {
147 cmd.env(key, value);
148 }
149
150 // Configure stdio
151 cmd.stdin(AsyncStdio::piped())
152 .stdout(AsyncStdio::piped())
153 .stderr(AsyncStdio::inherit());
154
155 // Spawn the process
156 let child = cmd.spawn().map_err(|e| {
157 tracing::error!(error = %e, "Failed to spawn server process");
158 Error::Process(format!("Failed to spawn server process: {}", e))
159 })?;
160
161 let stdin = child.stdin.ok_or_else(|| {
162 tracing::error!("Failed to open stdin for server process");
163 Error::Process("Failed to open stdin for server process".into())
164 })?;
165
166 let stdout = child.stdout.ok_or_else(|| {
167 tracing::error!("Failed to open stdout from server process");
168 Error::Process("Failed to open stdout from server process".into())
169 })?;
170
171 // Create the StdioTransport
172 let transport = StdioTransport::new(server_name.to_string(), stdin, stdout);
173
174 // Return the transport
175 Ok(transport)
176}