use include_dir::{include_dir, Dir};
use pmcp::Server;
use pmcp_server_toolkit::workbook::{EmbeddedSource, WorkbookBuilderExt};
use serde_json::Value;
static EMBEDDED_BUNDLE: Dir = include_dir!("$CARGO_MANIFEST_DIR/tests/fixtures/tax-calc@1.1.0");
const TABLE_TOOLS: [&str; 2] = ["calculate_tax", "estimate_refund"];
fn print_tool(server: &Server, name: &str) {
let Some(handler) = server.get_tool(name) else {
println!(" (tool `{name}` not registered)");
return;
};
let Some(info) = handler.metadata() else {
println!(" (tool `{name}` advertises no metadata)");
return;
};
let inputs = schema_keys(&info.input_schema["properties"]["inputs"]["properties"]);
let outputs = info
.output_schema
.as_ref()
.map(|s| schema_keys(&s["properties"]["outputs"]["properties"]))
.unwrap_or_default();
println!(" tool: {}", info.name);
println!(
" description: {}",
info.description.as_deref().unwrap_or("(none)")
);
println!(" inputSchema (DAG-derived): {inputs:?}");
println!(" outputSchema (structuredContent): {outputs:?}");
}
fn schema_keys(props: &Value) -> Vec<String> {
let mut keys: Vec<String> = props
.as_object()
.map(|m| m.keys().cloned().collect())
.unwrap_or_default();
keys.sort();
keys
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!(
"== Table-based workbook authoring: your Excel process as a governed MCP tool surface ==\n"
);
println!(
"Loading the compiled two-Table tax-suite bundle (Calculate_Tax + Estimate_Refund)...\n"
);
let server = Server::builder()
.name("workbook-tax-calc")
.version("1.1.0")
.try_with_workbook_bundle(&EmbeddedSource::new(&EMBEDDED_BUNDLE))?
.build()?;
println!("Emitted tool surface — ONE named MCP tool per output Table:\n");
for name in TABLE_TOOLS {
print_tool(&server, name);
println!();
}
println!(
"Note: only `estimate_refund` advertises `withheld` — its refund formula \n\
(withheld - tax_owed) is the only path that reaches that input. The per-tool\n\
input schemas are DAG-derived, so an LLM sees exactly the inputs each tool needs."
);
Ok(())
}