| Crates.io | exfiltrate |
| lib.rs | exfiltrate |
| version | 0.1.0 |
| created_at | 2025-09-02 22:45:08.963688+00 |
| updated_at | 2025-09-02 22:45:08.963688+00 |
| description | An embeddable MCP server for Rust. |
| homepage | https://sealedabstract.com/code/exfiltrate |
| repository | https://github.com/drewcrawford/exfiltrate |
| max_upload_size | |
| id | 1821746 |
| size | 4,608,485 |

An embeddable Model Context Protocol (MCP) server for Rust.
exfiltrate provides a simple, self-contained and embeddable MCP server implementation, primarily motivated by the need to embed in debuggable programs. It is designed to be easy to use, easy to extend, and easy to integrate with existing Rust codebases.
The Model Context Protocol (MCP) enables AI models and agents to interact with external systems through a standardized JSON-RPC interface. exfiltrate implements this protocol without requiring async runtimes like tokio, making it suitable for embedding in any Rust application, including those running in constrained environments.
exfiltrate is the answer to these frequently-asked questions:
use exfiltrate::mcp::tools::{Tool, InputSchema, Argument, ToolCallResponse, ToolCallError};
use std::collections::HashMap;
// Define a simple tool
struct HelloTool;
impl Tool for HelloTool {
fn name(&self) -> &str {
"hello"
}
fn description(&self) -> &str {
"Greets a user by name"
}
fn input_schema(&self) -> InputSchema {
InputSchema::new(vec![
Argument::new(
"name".to_string(),
"string".to_string(),
"Name to greet".to_string(),
true
),
])
}
fn call(&self, params: HashMap<String, serde_json::Value>)
-> Result<ToolCallResponse, ToolCallError> {
let name = params.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolCallError::new(vec!["Missing name parameter".into()]))?;
Ok(ToolCallResponse::new(vec![
format!("Hello, {}!", name).into()
]))
}
}
// Register the tool
exfiltrate::mcp::tools::add_tool(Box::new(HelloTool));
use exfiltrate::transit::{transit_proxy::TransitProxy, http::Server};
// Create and start an HTTP proxy server
let proxy = TransitProxy::new();
let server = Server::new("127.0.0.1:1984", proxy);
// Server runs in background threads
// In a real application, keep the main thread alive
std::thread::sleep(std::time::Duration::from_millis(10));
// Enable log capture to forward logs through MCP
// Note: This would typically be called once at program start
// exfiltrate::logwise::begin_capture();
// Logs using logwise syntax will be captured
// Example:
// logwise::info_sync!("Application started", version="1.0.0");
// logwise::warn_sync!("Low memory", available_mb=256);
When implementing tools, always validate input parameters and provide clear error messages:
use exfiltrate::mcp::tools::{Tool, ToolCallResponse, ToolCallError};
use std::collections::HashMap;
struct SafeTool;
impl Tool for SafeTool {
fn name(&self) -> &str { "safe_divide" }
fn description(&self) -> &str { "Safely divides two numbers" }
fn input_schema(&self) -> exfiltrate::mcp::tools::InputSchema {
use exfiltrate::mcp::tools::{InputSchema, Argument};
InputSchema::new(vec![
Argument::new("dividend".into(), "number".into(), "Number to divide".into(), true),
Argument::new("divisor".into(), "number".into(), "Number to divide by".into(), true),
])
}
fn call(&self, params: HashMap<String, serde_json::Value>)
-> Result<ToolCallResponse, ToolCallError> {
// Validate and extract parameters with clear error messages
let dividend = params.get("dividend")
.ok_or_else(|| ToolCallError::new(vec!["Missing 'dividend' parameter".into()]))?
.as_f64()
.ok_or_else(|| ToolCallError::new(vec!["'dividend' must be a number".into()]))?;
let divisor = params.get("divisor")
.ok_or_else(|| ToolCallError::new(vec!["Missing 'divisor' parameter".into()]))?
.as_f64()
.ok_or_else(|| ToolCallError::new(vec!["'divisor' must be a number".into()]))?;
// Check for division by zero
if divisor == 0.0 {
return Err(ToolCallError::new(vec!["Cannot divide by zero".into()]));
}
let result = dividend / divisor;
Ok(ToolCallResponse::new(vec![format!("{}", result).into()]))
}
}
file_read, file_write)exfiltrate is also the answer to these less-frequently-asked questions:
In theory, the MCP protocol allows you to push updates when tools change. In practice, support for this is often unimplemented.
But there's an elegant workaround: write a "tell me the latest tools" tool, and a "run another tool by name" tool, boom, dynamic tool discovery and use by all agents. Tools that are built into the proxy persist whereas tools built into your program come and go.
Probably because that makes sense for internet-deployed MCP servers but it makes no sense for debugging an arbitrary program that doesn't even work which is why you're debugging it.
This codebase has no dependency on tokio or the official SDK. Instead it just uses threads. Threads for everyone.
By proxying it of course. Your browser opens a websocket to the proxy application which sends it to Claude.
Since the async story is bad there too, I just wrote a ground-up WebSocket implementation with threads. Threads for everyone.
transit - Enables the transit proxy system for remote debugging (not available on wasm32)logwise - Enables integration with the logwise logging framework for log capturemcp - Model Context Protocol core implementationtransit - Transit proxy system (requires transit feature, not available on wasm32)logwise - Logwise logging integration (requires logwise feature)