mcp: add mcp-schema command for generic MCP bridge

Add `poc-memory mcp-schema` command that outputs tool definitions with
CLI routing info (name, description, inputSchema, cli args, stdin_param).

The companion memory-mcp.py (in ~/bin/) is a generic bridge that loads
definitions from mcp-schema at startup and dynamically generates typed
Python functions for FastMCP registration. No tool-specific Python code
— adding a new tool only requires changes in Rust.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-03-31 18:20:52 -04:00
parent d6b85d204a
commit c5b5051772
2 changed files with 60 additions and 0 deletions

View file

@ -264,6 +264,52 @@ pub fn get_group_content(group: &crate::config::ContextGroup, store: &crate::sto
}
}
/// MCP tool schema with CLI routing info.
///
/// Each tool definition includes:
/// - name, description, inputSchema (standard MCP)
/// - cli: the CLI args prefix to invoke this tool
/// - stdin_param: which parameter (if any) should be sent via stdin
///
/// Tools with cli=null are agent-internal (not exposed via MCP CLI bridge).
pub fn cmd_mcp_schema() -> Result<(), String> {
use serde_json::json;
// Map tool names to CLI args + stdin param.
// Tools not listed here are skipped (agent-internal).
let cli_map: std::collections::HashMap<&str, (Vec<&str>, Option<&str>)> = [
("memory_render", (vec!["render"], None)),
("memory_write", (vec!["write"], Some("content"))),
("memory_search", (vec!["search"], None)),
("memory_search_content", (vec!["search", "--content"], None)),
("memory_spread", (vec!["graph", "spread"], None)),
("memory_links", (vec!["graph", "link"], None)),
("memory_link_set", (vec!["graph", "link-set"], None)),
("memory_link_add", (vec!["graph", "link-add"], None)),
("memory_used", (vec!["used"], None)),
("memory_weight_set", (vec!["weight-set"], None)),
("memory_rename", (vec!["node", "rename"], None)),
("memory_query", (vec!["query"], None)),
].into_iter().collect();
let defs = crate::thought::memory::definitions();
let json_out: Vec<_> = defs.iter().filter_map(|d| {
let name = &d.function.name;
let (cli, stdin_param) = cli_map.get(name.as_str())?;
Some(json!({
"name": name,
"description": d.function.description,
"inputSchema": d.function.parameters,
"cli": cli,
"stdin_param": stdin_param,
}))
}).collect();
println!("{}", serde_json::to_string_pretty(&json_out)
.map_err(|e| e.to_string())?);
Ok(())
}
pub fn cmd_load_context(stats: bool) -> Result<(), String> {
let cfg = crate::config::get();
let store = crate::store::Store::load()?;

View file

@ -220,6 +220,10 @@ EXAMPLES:
/// Admin operations (fsck, health, import, export)
#[command(subcommand)]
Admin(AdminCmd),
/// Output MCP tool definitions as JSON (for generic MCP bridge)
#[command(name = "mcp-schema")]
McpSchema,
}
#[derive(Subcommand)]
@ -310,6 +314,14 @@ enum GraphCmd {
/// Node key
key: Vec<String>,
},
/// Find related nodes via spreading activation from seed nodes
Spread {
/// Seed node keys
keys: Vec<String>,
/// Maximum results (default: 20)
#[arg(short = 'n', default_value_t = 20)]
max_results: usize,
},
/// Add a link between two nodes
#[command(name = "link-add")]
LinkAdd {
@ -806,6 +818,7 @@ impl Run for Command {
Self::Cursor(sub) => sub.run(),
Self::Agent(sub) => sub.run(),
Self::Admin(sub) => sub.run(),
Self::McpSchema => cli::misc::cmd_mcp_schema(),
}
}
}
@ -837,6 +850,7 @@ impl Run for GraphCmd {
fn run(self) -> Result<(), String> {
match self {
Self::Link { key } => cli::graph::cmd_link(&key),
Self::Spread { keys, max_results } => cli::graph::cmd_spread(&keys, max_results),
Self::LinkAdd { source, target, reason }
=> cli::graph::cmd_link_add(&source, &target, &reason),
Self::LinkSet { source, target, strength }