Zenn
Open7

MCPのRust用SDKのexampleであるcounter-serverをstdioやSSEで叩いてみる

ojapiojapi

コンテナでMCPサーバーを立ち上げたいので、上記のリポジトリをforkした上で、ルートディレクトリにDockerfileを追加。

FROM rust:1.85 AS builder
WORKDIR /app

COPY . .
RUN cargo build  --release --example counter-server

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY --from=builder /app/target/release/examples/counter-server ./counter-server

USER root

CMD ["./counter-server"]

ビルドする。

docker build -t mcp/counter-server:latest .
ojapiojapi

MCPのクライアントであるClaude Desktopの claude_desktop_config.json は以下のように記述。
dockerコンテナは -i を付けてインタラクティブモードで立ち上げる。(Claude DesktopはデフォルトでMCPサーバーとstdioで会話する仕様である様子)

{
    "mcpServers": {
        "counter-server": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "mcp/counter-server"
            ]
        }
    }
}
ojapiojapi

Claude Desktopを立ち上げると counter-server と接続できていることが確認できる。

以下の通りカウントの加算減算をお願いしつつ、現在の数値を取得できることを確認。

ojapiojapi

2. counter-server をwebサーバーとして起動してServer-Sent-Eventsで接続

上記と同じく、rust-sdkのリポジトリに用意されているexampleのコードを利用する。

https://github.com/modelcontextprotocol/rust-sdk/tree/main/examples

actix-web でWebサーバーとして起動するコードがあったのでこれを利用。

cargo run --example actix_web
ojapiojapi

README.mdによると cargo run --example sse するだけで挙動を確かめられそうではあったが、エラーが出たため examples/clients/src/sse.rs を以下の通り変更

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;

#[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();

    // Create the base transport
    let transport = SseTransport::new("http://localhost:8000/sse", HashMap::new());

    // 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);
    println!("Client created\n");

    // 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(
            "increment",
            serde_json::json!({ "message": "Client with SSE transport - calling a tool" }),
        )
        .await?;
    println!("'increment': Tool result: {tool_result:?}\n");

    let tool_result = client
        .call_tool(
            "decrement",
            serde_json::json!({ "message": "Client with SSE transport - calling a tool" }),
        )
        .await?;
    println!("'decrement': Tool result: {tool_result:?}\n");

    let tool_result = client
        .call_tool(
            "get_value",
            serde_json::json!({ "message": "Client with SSE transport - calling a tool" }),
        )
        .await?;
    println!("'get_value': Tool result: {tool_result:?}\n");

    // List resources
    // let resources = client.list_resources(None).await?;
    // println!("Resources: {resources:?}\n");

    // Read resource
    // let resource = client.read_resource("echo://fixedresource").await?;
    // println!("Resource: {resource:?}\n");

    Ok(())
}


ojapiojapi

SSE clientコードの実行

cargo run --example sse

出力結果

2025-03-23T03:26:42.802688Z DEBUG mcp_client::transport::sse: Discovered SSE POST endpoint: http://localhost:8000/sse?sessionId=b9f03919f625f918848df10d336af205
Client created

Connected to server: InitializeResult { protocol_version: "2024-11-05", capabilities: ServerCapabilities { prompts: Some(PromptsCapability { list_changed: Some(false) }), resources: Some(ResourcesCapability { subscribe: Some(false), list_changed: Some(false) }), tools: Some(ToolsCapability { list_changed: Some(false) }) }, server_info: Implementation { name: "counter", version: "1.0.7" }, instructions: Some("This server provides a counter tool that can increment and decrement values. The counter starts at 0 and can be modified using the 'increment' and 'decrement' tools. Use 'get_value' to check the current count.") }

Available tools: ListToolsResult { tools: [Tool { name: "increment", description: "Increment the counter by 1", input_schema: Object {"properties": Object {}, "required": Array [], "type": String("object")} }, Tool { name: "decrement", description: "Decrement the counter by 1", input_schema: Object {"properties": Object {}, "required": Array [], "type": String("object")} }, Tool { name: "get_value", description: "Get the current counter value", input_schema: Object {"properties": Object {}, "required": Array [], "type": String("object")} }], next_cursor: None }

'increment': Tool result: CallToolResult { content: [Text(TextContent { text: "1", annotations: None })], is_error: None }

'decrement': Tool result: CallToolResult { content: [Text(TextContent { text: "0", annotations: None })], is_error: None }

'get_value': Tool result: CallToolResult { content: [Text(TextContent { text: "0", annotations: None })], is_error: None }
作成者以外のコメントは許可されていません