use std::io::{self, BufRead, Write};
fn main() {
let mode = std::env::args().nth(1).unwrap_or_else(|| "echo".into());
match mode.as_str() {
"malformed" => {
let _ = writeln!(io::stdout(), "not json");
let _ = io::stdout().flush();
loop {
std::thread::sleep(std::time::Duration::from_secs(60));
}
}
"timeout" => {
let stdin = io::stdin();
let mut buf = String::new();
loop {
buf.clear();
if stdin.lock().read_line(&mut buf).unwrap_or(0) == 0 {
std::thread::sleep(std::time::Duration::from_secs(60));
}
}
}
_ => {}
}
let stdin = io::stdin();
let stdout = io::stdout();
for line in stdin.lock().lines() {
let Ok(line) = line else { break };
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let Ok(req) = serde_json::from_str::<serde_json::Value>(trimmed) else {
continue;
};
let method = req.get("method").and_then(|v| v.as_str()).unwrap_or("");
let id = req.get("id").cloned().unwrap_or(serde_json::Value::Null);
if id.is_null() {
continue;
}
if let Some(response) = dispatch(&mode, method, &id) {
let mut out = stdout.lock();
let _ = writeln!(out, "{response}");
let _ = out.flush();
}
}
}
fn dispatch(mode: &str, method: &str, id: &serde_json::Value) -> Option<String> {
use serde_json::json;
let result: serde_json::Value = match (mode, method) {
(_, "initialize") => {
let capabilities = match mode {
"echo" => json!({"tools": true, "resources": true, "prompts": true}),
"read-blob" | "empty-resources" => json!({"resources": true}),
"prompt-args" | "prompt-default" => json!({"prompts": true}),
"tools-only" => json!({"tools": true}),
"error-tool" => json!({}),
_ => json!({}),
};
json!({
"protocolVersion": "2024-11-05",
"capabilities": capabilities,
"serverInfo": {"name": "mock-server", "version": "1.0"},
})
}
("echo", "tools/list") => json!({
"tools": [{
"name": "echo",
"description": "Echo back the input",
"inputSchema": {
"type": "object",
"properties": {"message": {"type": "string"}},
"required": ["message"],
},
}]
}),
("echo", "tools/call") => json!({
"content": [{"type": "text", "text": "Echo: hello"}]
}),
("error-tool", "tools/list") => json!({
"tools": [{
"name": "failing",
"description": "Always fails",
"inputSchema": {"type": "object"},
}]
}),
("error-tool", "tools/call") => json!({
"isError": true,
"content": [{"type": "text", "text": "Something went wrong"}],
}),
("echo", "resources/list") => json!({
"resources": [{
"uri": "file:///tmp/test.txt",
"name": "Test File",
"description": "A test file",
"mimeType": "text/plain",
}]
}),
("echo", "resources/read") => json!({
"contents": [{
"uri": "file:///tmp/test.txt",
"mimeType": "text/plain",
"text": "Hello, world!",
}]
}),
("read-blob", "resources/read") => json!({
"contents": [{
"uri": "file:///tmp/image.png",
"mimeType": "image/png",
"blob": "iVBORw0KGgoAAAANSUhEUgAAAAE=",
}]
}),
("empty-resources", "resources/read") => json!({"contents": []}),
("echo", "prompts/list") => json!({
"prompts": [{
"name": "greet",
"description": "Greet someone",
"arguments": [{
"name": "name",
"description": "The name to greet",
"required": true,
}],
}]
}),
("echo", "prompts/get") => json!({
"messages": [{
"role": "user",
"content": {"type": "text", "text": "Hello, world!"},
}]
}),
("prompt-args", "prompts/get") => json!({
"messages": [{
"role": "user",
"content": {"type": "text", "text": "Hello, Alice!"},
}]
}),
("prompt-default", "prompts/get") => json!({
"messages": [{
"role": "assistant",
"content": {"type": "text", "text": "default prompt"},
}]
}),
_ => {
return Some(
json!({
"jsonrpc": "2.0",
"id": id,
"error": {"code": -32601, "message": "Method not found"},
})
.to_string(),
);
}
};
Some(json!({"jsonrpc": "2.0", "id": id, "result": result}).to_string())
}