TL;DR: rust-faf-mcp migrates from hand-rolled JSON-RPC to the rmcp SDK. main.rs drops from 253 lines to ~20. Three new tools land: faf_compress, faf_discover, faf_tokens. Tests go from 49 to 91 across 5 files.
Why rmcp
v0.1.0 shipped with a hand-rolled JSON-RPC stdio loop. It worked — 49 tests proved it. But it meant maintaining transport, schema generation, and protocol compliance manually. The rmcp crate (v1.1) provides all of that via #[tool_router] and ServerHandler.
The migration deletes 230+ lines and replaces them with derive macros:
#[tool(tool_box)]
impl FafServer {
#[tool(description = "Create or enhance a project.faf file")]
async fn faf_init(
&self,
params: Parameters<PathParams>,
) -> Result<String, String> {
let args = params_to_value(¶ms.0);
value_to_string_result(tools::faf_init(&args))
}
} Parameter structs get schemars::JsonSchema for automatic schema generation. main.rs becomes ~20 lines:
#[tokio::main(flavor = "current_thread")]
async fn main() {
let server = server::FafServer;
let transport = stdio();
server.serve(transport).await.unwrap();
}Three New Tools
| Tool | What |
|---|---|
faf_compress | Compress .faf for token-limited contexts — minimal, standard, or full |
faf_discover | Walk up the directory tree to find the nearest project.faf |
faf_tokens | Estimate token count at each compression level |
All three are powered by faf-rust-sdk v1.3 — the same SDK that drives the Axum middleware. compress(), find_faf_file(), and estimate_tokens() do the real work. The MCP server adapts them to JSON-RPC.
Architecture
src/
├── main.rs # ~20 lines — tokio entry, rmcp stdio transport
├── server.rs # FafServer: #[tool_router], ServerHandler, resources
└── tools.rs # Business logic — all 8 tools, pure functions Tools return serde_json::Value. The server adapts them to Result<String, String> for rmcp's IntoCallToolResult. Business logic stays testable. Protocol concerns stay in the server layer.
91 Tests
| File | Tests | Coverage |
|---|---|---|
mcp_protocol.rs | 9 | Init handshake, tools/list, resources, schema validation |
tools_functional.rs | 25 | All 8 tools — happy path, error paths, language detection |
tier1_security.rs | 12 | Path traversal, null bytes, shell injection, oversized input |
tier2_engine.rs | 35 | Corrupt YAML, sync, pipelines, dual manifests, legacy filenames |
tier3_edge_cases.rs | 10 | Unicode, CJK, score boundaries, GitHub URL parsing |
Every test spawns the compiled binary as a subprocess and communicates via stdin/stdout JSON-RPC. No mocks. The server under test is the server you install.
Install
cargo install rust-faf-mcp brew install Wolfe-Jam/faf/rust-faf-mcp Configure for Claude Code:
claude mcp add faf rust-faf-mcp Or any MCP client (WARP, Cursor, Zed, Claude Desktop):
{
"mcpServers": {
"faf": {
"command": "rust-faf-mcp"
}
}
} The Numbers
- v0.2.0 — Released March 7, 2026
- 91/91 — Tests passing (was 49)
- 8 tools — Was 5
- 4.3 MB — Stripped binary
- ~20 lines —
main.rs(was 253)