pub const LIVE_MCP_TOOLS: &[(&str, &str)] = &[
(
"analyze_complexity",
"Analyze cyclomatic and cognitive complexity of code",
),
(
"analyze_satd",
"Detect self-admitted technical debt (TODO/FIXME/HACK)",
),
(
"analyze_dead_code",
"Detect unreachable functions and modules",
),
(
"analyze_dag",
"Build the dependency/call graph of a project",
),
(
"analyze_deep_context",
"Generate comprehensive annotated project context",
),
(
"analyze_big_o",
"Estimate algorithmic complexity of functions",
),
("refactor.start", "Start an automated refactoring session"),
(
"refactor.nextIteration",
"Advance the active refactoring session",
),
(
"refactor.getState",
"Return the active refactoring session state",
),
("refactor.stop", "Stop the active refactoring session"),
(
"quality_gate",
"Run the quality gate (complexity, satd, lint, tests)",
),
("quality_proxy", "Proxy a quality-scored analysis request"),
(
"pdmt_deterministic_todos",
"Generate deterministic PDMT todo lists",
),
("git_operation", "Perform a git operation for the workflow"),
(
"generate_context",
"Generate LLM-optimized codebase context",
),
("scaffold_project", "Scaffold a project or agent skeleton"),
("pmat_query_code", "Semantic code search ranked by quality"),
(
"pmat_get_function",
"Return a full function with quality metrics",
),
(
"pmat_find_similar",
"Find functions similar to a target (refactoring)",
),
(
"pmat_index_stats",
"Report agent-context index health and statistics",
),
];
fn tool_schema(name: &str) -> serde_json::Value {
let no_arg = matches!(
name,
"refactor.nextIteration" | "refactor.getState" | "refactor.stop" | "pmat_index_stats"
);
if no_arg {
serde_json::json!({
"type": "object",
"properties": {},
"additionalProperties": false
})
} else {
serde_json::json!({
"type": "object",
"properties": {
"paths": {"type": "array", "items": {"type": "string"}}
}
})
}
}
pub fn render_manifest(version: &str) -> String {
let tools: Vec<serde_json::Value> = LIVE_MCP_TOOLS
.iter()
.map(|(name, desc)| {
serde_json::json!({
"name": name,
"description": desc,
"inputSchema": tool_schema(name),
})
})
.collect();
let manifest = serde_json::json!({
"name": "pmat",
"version": version,
"description": "Project Analysis and Intelligence Modeling Toolkit",
"main": "target/release/pmat",
"bin": {"pmat": "target/release/pmat"},
"mcp": {
"runtime": "binary",
"launch": {"env": {"MCP_VERSION": "1"}},
"tool_count": LIVE_MCP_TOOLS.len(),
"tools": tools,
}
});
let mut out =
serde_json::to_string_pretty(&manifest).expect("manifest serialization is infallible");
out.push('\n');
out
}
pub fn manifest_tool_names(manifest: &serde_json::Value) -> Vec<String> {
let tools = manifest.get("mcp").and_then(|m| m.get("tools"));
let mut names: Vec<String> = match tools {
Some(serde_json::Value::Array(arr)) => arr
.iter()
.filter_map(|t| t.get("name").and_then(|n| n.as_str()).map(str::to_string))
.collect(),
Some(serde_json::Value::Object(map)) => map.keys().cloned().collect(),
_ => Vec::new(),
};
names.sort_unstable();
names
}
pub fn canonical_tool_names() -> Vec<String> {
let mut names: Vec<String> = LIVE_MCP_TOOLS.iter().map(|(n, _)| n.to_string()).collect();
names.sort_unstable();
names
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn manifest_matches_server() {
let src = include_str!("simple_unified_server.rs");
let run_start = src.find("pub async fn run").expect("run() present");
let run_end = src[run_start..]
.find("fn test_all_20")
.map(|i| run_start + i)
.unwrap_or(src.len());
let registered: Vec<String> = src[run_start..run_end]
.split(".tool(")
.skip(1)
.filter_map(|seg| {
let q1 = seg.find('"')?;
let rest = &seg[q1 + 1..];
let q2 = rest.find('"')?;
Some(rest[..q2].to_string())
})
.collect();
let declared: Vec<String> = LIVE_MCP_TOOLS.iter().map(|(n, _)| n.to_string()).collect();
assert_eq!(
registered, declared,
"LIVE_MCP_TOOLS drifted from server .tool() registrations"
);
assert_eq!(declared.len(), 20, "the live server advertises 20 tools");
}
#[test]
fn generated_equals_tool_defs() {
let manifest: serde_json::Value =
serde_json::from_str(&render_manifest("9.9.9")).expect("render is valid JSON");
assert_eq!(manifest_tool_names(&manifest), canonical_tool_names());
assert_eq!(
manifest["mcp"]["tool_count"].as_u64(),
Some(LIVE_MCP_TOOLS.len() as u64)
);
}
#[test]
fn two_runs_identical() {
assert_eq!(render_manifest("1.2.3"), render_manifest("1.2.3"));
}
#[test]
#[ignore = "regenerator, not a test — writes root mcp.json"]
fn regenerate_mcp_json() {
let root = env!("CARGO_MANIFEST_DIR");
let version = env!("CARGO_PKG_VERSION");
std::fs::write(
std::path::Path::new(root).join("mcp.json"),
render_manifest(version),
)
.expect("write mcp.json");
}
#[test]
fn manifest_names_reads_legacy_object_shape() {
let legacy = serde_json::json!({
"mcp": {"tools": {"generate_template": {}, "generate_unified_context": {}}}
});
assert_eq!(
manifest_tool_names(&legacy),
vec!["generate_template", "generate_unified_context"]
);
}
}