Skip to content

Instantly share code, notes, and snippets.

@kuc-arc-f
Created October 16, 2025 01:12
Show Gist options
  • Save kuc-arc-f/0ea1a8c22ae60f6d8c1a005e84cab059 to your computer and use it in GitHub Desktop.
Save kuc-arc-f/0ea1a8c22ae60f6d8c1a005e84cab059 to your computer and use it in GitHub Desktop.
Rust , remoto MCP Server , example
[package]
name = "rust_remoto_mcp_1"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.100"
axum = "0.7.5"
axum-extra = { version = "0.10.1", features = ["cookie"] }
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.6.6", features = ["fs", "trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
use axum::{
extract::State,
http::StatusCode,
response::IntoResponse,
routing::post,
Json, Router,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::sync::Arc;
use tower_http::trace::TraceLayer;
// JSON-RPC 2.0 Request
#[derive(Debug, Deserialize)]
struct JsonRpcRequest {
jsonrpc: String,
method: String,
#[serde(default)]
params: Option<Value>,
id: Option<Value>,
}
// JSON-RPC 2.0 Response
#[derive(Debug, Serialize)]
struct JsonRpcResponse {
jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<JsonRpcError>,
id: Option<Value>,
}
// JSON-RPC 2.0 Error
#[derive(Debug, Serialize)]
struct JsonRpcError {
code: i32,
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<Value>,
}
// MCP Server State
#[derive(Clone)]
struct AppState {
server_name: String,
version: String,
}
#[tokio::main]
async fn main() {
// ロギング初期化
tracing_subscriber::fmt()
.with_target(false)
.compact()
.init();
// アプリケーションステート
let state = Arc::new(AppState {
server_name: "MCP Server Example".to_string(),
version: "1.0.0".to_string(),
});
// ルーター設定
let app = Router::new()
.route("/", post(handle_jsonrpc))
.route("/mcp", post(handle_jsonrpc))
.with_state(state)
.layer(TraceLayer::new_for_http());
// サーバー起動
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
tracing::info!("MCP Server listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
}
// JSON-RPC ハンドラー
async fn handle_jsonrpc(
State(state): State<Arc<AppState>>,
Json(request): Json<JsonRpcRequest>,
) -> impl IntoResponse {
tracing::info!("Received request: method={}, id={:?}", request.method, request.id);
// JSON-RPC 2.0 バージョンチェック
if request.jsonrpc != "2.0" {
return (
StatusCode::OK,
Json(JsonRpcResponse {
jsonrpc: "2.0".to_string(),
result: None,
error: Some(JsonRpcError {
code: -32600,
message: "Invalid Request".to_string(),
data: None,
}),
id: request.id,
}),
);
}
// メソッドディスパッチ
let result = match request.method.as_str() {
"initialize" => handle_initialize(&state, request.params),
"tools/list" => handle_tools_list(),
"tools/call" => handle_tools_call(request.params),
"resources/list" => handle_resources_list(),
"resources/read" => handle_resources_read(request.params),
"prompts/list" => handle_prompts_list(),
_ => Err(JsonRpcError {
code: -32601,
message: "Method not found".to_string(),
data: None,
}),
};
let response = match result {
Ok(result) => JsonRpcResponse {
jsonrpc: "2.0".to_string(),
result: Some(result),
error: None,
id: request.id,
},
Err(error) => JsonRpcResponse {
jsonrpc: "2.0".to_string(),
result: None,
error: Some(error),
id: request.id,
},
};
(StatusCode::OK, Json(response))
}
// MCP initialize メソッド
fn handle_initialize(state: &AppState, _params: Option<Value>) -> Result<Value, JsonRpcError> {
Ok(json!({
"protocolVersion": "2024-11-05",
"serverInfo": {
"name": state.server_name,
"version": state.version
},
"capabilities": {
"tools": {},
"resources": {},
"prompts": {}
}
}))
}
// tools/list メソッド
fn handle_tools_list() -> Result<Value, JsonRpcError> {
Ok(json!({
"tools": [
{
"name": "echo",
"description": "Echo back the input message",
"inputSchema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Message to echo"
}
},
"required": ["message"]
}
},
{
"name": "add",
"description": "Add two numbers",
"inputSchema": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
}
]
}))
}
// tools/call メソッド
fn handle_tools_call(params: Option<Value>) -> Result<Value, JsonRpcError> {
let params = params.ok_or(JsonRpcError {
code: -32602,
message: "Invalid params".to_string(),
data: None,
})?;
let tool_name = params["name"].as_str().ok_or(JsonRpcError {
code: -32602,
message: "Tool name is required".to_string(),
data: None,
})?;
let arguments = &params["arguments"];
match tool_name {
"echo" => {
let message = arguments["message"].as_str().unwrap_or("No message");
Ok(json!({
"content": [
{
"type": "text",
"text": format!("Echo: {}", message)
}
]
}))
}
"add" => {
let a = arguments["a"].as_f64().unwrap_or(0.0);
let b = arguments["b"].as_f64().unwrap_or(0.0);
Ok(json!({
"content": [
{
"type": "text",
"text": format!("Result: {}", a + b)
}
]
}))
}
_ => Err(JsonRpcError {
code: -32602,
message: format!("Unknown tool: {}", tool_name),
data: None,
}),
}
}
// resources/list メソッド
fn handle_resources_list() -> Result<Value, JsonRpcError> {
Ok(json!({
"resources": [
{
"uri": "file:///example.txt",
"name": "Example Resource",
"description": "An example resource",
"mimeType": "text/plain"
}
]
}))
}
// resources/read メソッド
fn handle_resources_read(params: Option<Value>) -> Result<Value, JsonRpcError> {
let params = params.ok_or(JsonRpcError {
code: -32602,
message: "Invalid params".to_string(),
data: None,
})?;
let uri = params["uri"].as_str().ok_or(JsonRpcError {
code: -32602,
message: "URI is required".to_string(),
data: None,
})?;
Ok(json!({
"contents": [
{
"uri": uri,
"mimeType": "text/plain",
"text": "This is the content of the resource"
}
]
}))
}
// prompts/list メソッド
fn handle_prompts_list() -> Result<Value, JsonRpcError> {
Ok(json!({
"prompts": [
{
"name": "example_prompt",
"description": "An example prompt",
"arguments": []
}
]
}))
}
const start = async function() {
try{
const item = {
"jsonrpc": "2.0",
"method": "tools/list",
"id": 2
}
const response = await fetch("http://localhost:3000/mcp", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': '',
},
body: JSON.stringify(item),
});
if (!response.ok) {
const text = await response.text();
console.log(text);
throw new Error('Failed to create item');
}else{
const json = await response.json();
console.log(json);
console.log(json.result.tools);
}
}catch(e){console.log(e)}
}
start();
const start = async function() {
try{
const item = {
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "echo",
"arguments": {
"message": "Hello, MCP!"
}
},
"id": 2
}
const response = await fetch("http://localhost:3000/mcp", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': '',
},
body: JSON.stringify(item),
});
if (!response.ok) {
const text = await response.text();
console.log(text);
throw new Error('Failed to create item');
}else{
const json = await response.json();
console.log(json);
console.log("text=", json.result.content[0].text);
}
}catch(e){console.log(e)}
}
start();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment