Apparently `std::env::args()` will panic during iteration if any argument to the process is not valid Unicode: https://doc.rust-lang.org/std/env/fn.args.html Let's avoid the risk and just go with `std::env::args_os()`. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/1698). * #1705 * #1703 * #1702 * __->__ #1698 * #1697
89 lines
2.9 KiB
Rust
89 lines
2.9 KiB
Rust
//! Simple command-line utility to exercise `McpClient`.
|
|
//!
|
|
//! Example usage:
|
|
//!
|
|
//! ```bash
|
|
//! cargo run -p codex-mcp-client -- `codex-mcp-server`
|
|
//! ```
|
|
//!
|
|
//! Any additional arguments after the first one are forwarded to the spawned
|
|
//! program. The utility connects, issues a `tools/list` request and prints the
|
|
//! server's response as pretty JSON.
|
|
|
|
use std::ffi::OsString;
|
|
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::InitializeRequestParams;
|
|
use mcp_types::ListToolsRequestParams;
|
|
use mcp_types::MCP_SCHEMA_VERSION;
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let default_level = "debug";
|
|
let _ = tracing_subscriber::fmt()
|
|
// Fallback to the `default_level` log filter if the environment
|
|
// variable is not set _or_ contains an invalid value
|
|
.with_env_filter(
|
|
EnvFilter::try_from_default_env()
|
|
.or_else(|_| EnvFilter::try_new(default_level))
|
|
.unwrap_or_else(|_| EnvFilter::new(default_level)),
|
|
)
|
|
.with_writer(std::io::stderr)
|
|
.try_init();
|
|
|
|
// Collect command-line arguments excluding the program name itself.
|
|
let mut args: Vec<OsString> = std::env::args_os().skip(1).collect();
|
|
|
|
if args.is_empty() || args[0] == "--help" || args[0] == "-h" {
|
|
eprintln!("Usage: mcp-client <program> [args..]\n\nExample: mcp-client codex-mcp-server");
|
|
std::process::exit(1);
|
|
}
|
|
let original_args = args.clone();
|
|
|
|
// Spawn the subprocess and connect the client.
|
|
let program = args.remove(0);
|
|
let env = None;
|
|
let client = McpClient::new_stdio_client(program, args, env)
|
|
.await
|
|
.with_context(|| format!("failed to spawn subprocess: {original_args:?}"))?;
|
|
|
|
let params = InitializeRequestParams {
|
|
capabilities: ClientCapabilities {
|
|
experimental: None,
|
|
roots: None,
|
|
sampling: None,
|
|
elicitation: None,
|
|
},
|
|
client_info: Implementation {
|
|
name: "codex-mcp-client".to_owned(),
|
|
version: env!("CARGO_PKG_VERSION").to_owned(),
|
|
title: Some("Codex".to_string()),
|
|
},
|
|
protocol_version: MCP_SCHEMA_VERSION.to_owned(),
|
|
};
|
|
let initialize_notification_params = None;
|
|
let timeout = Some(Duration::from_secs(10));
|
|
let response = client
|
|
.initialize(params, initialize_notification_params, timeout)
|
|
.await?;
|
|
eprintln!("initialize response: {response:?}");
|
|
|
|
// Issue `tools/list` request (no params).
|
|
let timeout = None;
|
|
let tools = client
|
|
.list_tools(None::<ListToolsRequestParams>, timeout)
|
|
.await
|
|
.context("tools/list request failed")?;
|
|
|
|
// Print the result in a human readable form.
|
|
println!("{}", serde_json::to_string_pretty(&tools)?);
|
|
|
|
Ok(())
|
|
}
|