langchainrust 0.22.0

A LangChain-inspired framework for building LLM applications in Rust. Supports OpenAI, Agents, Tools, Memory, Chains, RAG, BM25, Hybrid Retrieval, LangGraph, HyDE, Reranking, MultiQuery, and native Function Calling.
//! Example / deployable MCP stateless HTTP server.
//!
//! Exposes langchainrust's built-in tools as a networked MCP server via `MCPServer::serve_http`,
//! callable by stateless MCP clients (StatelessMcpClient, or any HTTP client that POSTs
//! self-contained JSON-RPC with the `Mcp-Method` header).
//!
//! # Run (local testing)
//!
//! ```powershell
//! cargo run -p langchainrust --example mcp_http_server
//! ```
//!
//! # Build a standalone executable (for deployment)
//!
//! ```powershell
//! cargo build --release -p langchainrust --example mcp_http_server
//! # Artifact: target/release/examples/mcp_http_server.exe, copy it to the remote server and run
//! ```
//!
//! # Runtime configuration (environment variables, not hardcoded)
//!
//! | Variable | Default | Description |
//! |---|---|---|
//! | `MCP_SERVER_HOST` | `127.0.0.1` | Bind address (default local-only; for remote access set it explicitly to `0.0.0.0` and configure auth / network whitelist yourself) |
//! | `MCP_SERVER_PORT` | `8788` | Listening port |
//!
//! ```powershell
//! $env:MCP_SERVER_PORT = "8788"
//! .\target\release\examples\mcp_http_server.exe
//! ```
//!
//! On startup it prints the client connection endpoint: `http://<host>:<port>/mcp`.

use langchainrust::mcp::{MCPRequest, MCPServer};
use langchainrust::{
    Calculator, DateTimeTool, DuckDuckGoSearchTool, SimpleMathTool, URLFetchTool, WikipediaTool,
};
use std::sync::Arc;
use tokio::net::TcpListener;

/// Registers a set of ready-to-use built-in tools.
///
/// Deliberately does not register `PythonREPLTool` (it can execute arbitrary code remotely,
/// a security risk); add more tools here with `with_tool` when needed.
fn build_server() -> MCPServer {
    MCPServer::new()
        .with_server_info("langchainrust-mcp-server", env!("CARGO_PKG_VERSION"))
        .with_tool(Arc::new(Calculator))
        .with_tool(Arc::new(SimpleMathTool))
        .with_tool(Arc::new(DateTimeTool))
        .with_tool(Arc::new(URLFetchTool::new()))
        .with_tool(Arc::new(WikipediaTool::new()))
        .with_tool(Arc::new(DuckDuckGoSearchTool::new()))
}

#[tokio::main]
async fn main() {
    // 1. Read the configuration (environment variables with defaults)
    // Default binds to the local loopback only; this server has no auth and mounts
    // SSRF-reachable tools like url_fetch. Binding 0.0.0.0 would expose your internal
    // network and cloud metadata to anyone who can reach this port, so it must be
    // an explicit choice.
    let host = std::env::var("MCP_SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
    let port: u16 = std::env::var("MCP_SERVER_PORT")
        .ok()
        .and_then(|v| v.parse().ok())
        .unwrap_or(8788);

    // 2. Bind the listen address (default 127.0.0.1 = local only; 0.0.0.0 = all interfaces, must be explicit)
    let listener = TcpListener::bind((host.as_str(), port))
        .await
        .unwrap_or_else(|e| {
            eprintln!("failed to bind {host}:{port}: {e}");
            std::process::exit(1);
        });

    // 3. Build the server, print the registered tools, and start serving
    let server = Arc::new(build_server());
    let names = registered_tool_names(&server).await;
    println!("registered {} tools: {}", names.len(), names.join(", "));

    let url = server.serve_http(listener);
    println!("MCP stateless HTTP server started ✅");
    println!("client connection endpoint: {url}");
    println!("press Ctrl+C to stop.");

    // 4. Keep the process alive (the accept loop runs in a background task)
    std::future::pending::<()>().await;
}

/// Fetches the actually registered tool names via tools/list (what a client would see).
async fn registered_tool_names(server: &MCPServer) -> Vec<String> {
    let resp = server
        .handle_request(MCPRequest::new(1, "tools/list", None))
        .await;
    resp.result
        .as_ref()
        .and_then(|r| r.get("tools"))
        .and_then(|t| t.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|t| t.get("name").and_then(|n| n.as_str()).map(String::from))
                .collect()
        })
        .unwrap_or_default()
}