fastmcp-rs 0.2.0

Rust prototype for the FastMCP server
Documentation
use fastmcp_rs::{
    FastMcpServer, ToolDefinition, ToolResponse,
    prompt::{PromptMessage, PromptMessageContent, PromptTemplate},
    run_stdio_with_io,
};
use serde_json::{Value, json};
use tokio::io::{self, AsyncBufReadExt, AsyncWriteExt, BufReader};

#[tokio::test]
async fn stdio_transport_processes_commands() {
    let server = FastMcpServer::builder().name("stdio").build().into_shared();

    server
        .register_tool(ToolDefinition::new(
            "echo",
            |_, payload: Value| async move {
                Ok(ToolResponse::new(vec![json!({
                    "type": "text",
                    "text": payload.to_string()
                })]))
            },
        ))
        .unwrap();

    server
        .register_prompt(PromptTemplate::new(
            "welcome",
            vec![PromptMessage {
                role: "system".into(),
                content: PromptMessageContent::Text {
                    text: "Welcome {{ user }}".into(),
                },
            }],
        ))
        .unwrap();

    let (mut client_writer, server_reader) = io::duplex(4096);
    let (server_writer, client_reader) = io::duplex(4096);

    let server_task = tokio::spawn({
        let server = server.clone();
        async move {
            run_stdio_with_io(server, BufReader::new(server_reader), server_writer)
                .await
                .unwrap();
        }
    });

    client_writer
        .write_all(b"{\"command\":\"metadata\"}\n")
        .await
        .unwrap();
    client_writer
        .write_all(
            b"{\"command\":\"call_tool\",\"name\":\"echo\",\"arguments\":{\"value\":\"hello\"}}\n",
        )
        .await
        .unwrap();
    client_writer
        .write_all(b"{\"command\":\"shutdown\"}\n")
        .await
        .unwrap();
    client_writer.flush().await.unwrap();

    let mut reader = BufReader::new(client_reader);
    let mut response = String::new();

    reader.read_line(&mut response).await.unwrap();
    let metadata: Value = serde_json::from_str(response.trim()).unwrap();
    assert_eq!(metadata["type"], "metadata");
    response.clear();

    reader.read_line(&mut response).await.unwrap();
    let tool_call: Value = serde_json::from_str(response.trim()).unwrap();
    assert_eq!(tool_call["type"], "tool_invocation");
    assert_eq!(
        tool_call["data"]["content"][0]["text"],
        "{\"value\":\"hello\"}"
    );
    response.clear();

    reader.read_line(&mut response).await.unwrap();
    let ack: Value = serde_json::from_str(response.trim()).unwrap();
    assert_eq!(ack["type"], "ack");
    assert_eq!(ack["message"], "shutdown");

    server_task.await.unwrap();
}