#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum McpToolLicense {
Free,
Freemium,
}
impl McpToolLicense {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Free => "free",
Self::Freemium => "freemium",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct McpToolInfo {
pub name: &'static str,
pub kind: &'static str,
pub description: &'static str,
pub key_params: &'static [&'static str],
pub license: McpToolLicense,
pub license_note: Option<&'static str>,
pub read_only: bool,
}
pub const RUNTIME_COVERAGE_LICENSE_NOTE: &str = "A single local runtime-coverage capture is free; continuous or multi-capture runtime monitoring requires an active license (fallow license activate).";
pub const MCP_TOOLS: &[McpToolInfo] = &[
McpToolInfo {
name: "code_execute",
kind: "composition",
description: "Run a bounded read-only JavaScript snippet that composes fallow's analysis tools inside a sandbox (Code Mode meta-tool, not a plain analysis call)",
key_params: &["code", "timeout_ms", "max_output_bytes"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "analyze",
kind: "analysis",
description: "Full dead-code analysis: unused files, exports, types, dependencies, circular dependencies, and boundary violations",
key_params: &[
"issue_types",
"production",
"workspace",
"baseline",
"group_by",
"file",
],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "check_changed",
kind: "analysis",
description: "Incremental dead-code analysis scoped to files changed since a git ref (ideal for PR review)",
key_params: &["since", "baseline", "fail_on_regression"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "security_candidates",
kind: "analysis",
description: "Unverified local security candidates (tainted sinks) for downstream agent verification",
key_params: &["gate", "surface", "changed_since", "paths"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "inspect_target",
kind: "analysis",
description: "One evidence bundle for a file or exported symbol: trace, dead-code actions, duplication, complexity, and security candidates",
key_params: &["target", "production"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "find_dupes",
kind: "analysis",
description: "Code duplication detection with clone groups and refactoring suggestions",
key_params: &["mode", "min_tokens", "min_occurrences", "top", "threshold"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "check_health",
kind: "analysis",
description: "Complexity metrics, health score, hotspots, ownership, refactoring targets, and coverage gaps",
key_params: &[
"score",
"file_scores",
"hotspots",
"targets",
"coverage",
"runtime_coverage",
"max_crap",
"group_by",
],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "check_runtime_coverage",
kind: "runtime-coverage",
description: "Merge V8 or Istanbul runtime coverage into the health report (hot paths, cold paths, verdicts)",
key_params: &[
"coverage",
"min_invocations_hot",
"min_observation_volume",
"low_traffic_threshold",
"group_by",
],
license: McpToolLicense::Freemium,
license_note: Some(RUNTIME_COVERAGE_LICENSE_NOTE),
read_only: true,
},
McpToolInfo {
name: "get_hot_paths",
kind: "runtime-coverage",
description: "Production hot paths from runtime coverage, sorted by invocation volume",
key_params: &["coverage", "top", "min_invocations_hot"],
license: McpToolLicense::Freemium,
license_note: Some(RUNTIME_COVERAGE_LICENSE_NOTE),
read_only: true,
},
McpToolInfo {
name: "get_blast_radius",
kind: "runtime-coverage",
description: "Blast-radius context (caller counts, risk bands) from runtime coverage",
key_params: &["coverage", "group_by"],
license: McpToolLicense::Freemium,
license_note: Some(RUNTIME_COVERAGE_LICENSE_NOTE),
read_only: true,
},
McpToolInfo {
name: "get_importance",
kind: "runtime-coverage",
description: "Production-importance scores (0-100) combining invocations, complexity, and ownership",
key_params: &["coverage", "group_by"],
license: McpToolLicense::Freemium,
license_note: Some(RUNTIME_COVERAGE_LICENSE_NOTE),
read_only: true,
},
McpToolInfo {
name: "get_cleanup_candidates",
kind: "runtime-coverage",
description: "Cleanup candidates with safe_to_delete, review_required, and low_traffic verdicts from runtime coverage",
key_params: &["coverage", "group_by"],
license: McpToolLicense::Freemium,
license_note: Some(RUNTIME_COVERAGE_LICENSE_NOTE),
read_only: true,
},
McpToolInfo {
name: "audit",
kind: "analysis",
description: "Combined dead-code, complexity, and duplication audit for changed files with a pass/warn/fail verdict",
key_params: &["gate", "base", "max_crap", "coverage", "runtime_coverage"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "fallow_explain",
kind: "introspection",
description: "Explain one issue type (rationale, examples, fix guidance) without running an analysis",
key_params: &["issue_type"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "fix_preview",
kind: "fix",
description: "Dry-run auto-fix preview; shows what would change without modifying files",
key_params: &["no_create_config"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "fix_apply",
kind: "fix",
description: "Apply auto-fixes: removes unused exports, dependencies, and enum members (mutates files)",
key_params: &["no_create_config"],
license: McpToolLicense::Free,
license_note: None,
read_only: false,
},
McpToolInfo {
name: "project_info",
kind: "introspection",
description: "Project metadata: active framework plugins, discovered files, entry points, and boundary zones",
key_params: &["entry_points", "files", "plugins", "boundaries"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "list_boundaries",
kind: "introspection",
description: "List architecture boundary zones and access rules",
key_params: &[],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "feature_flags",
kind: "analysis",
description: "Detect feature flag patterns (environment variables, SDK calls, config objects)",
key_params: &["workspace", "production"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "impact",
kind: "introspection",
description: "Read the local Fallow Impact value-tracking report (.fallow/impact.json, local-dev only)",
key_params: &["root"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "trace_export",
kind: "trace",
description: "Trace why an export is used or unused, including re-export chains and entry-point status",
key_params: &["file", "export_name"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "trace_file",
kind: "trace",
description: "Trace all module-graph edges for a file (imports, exports, importers, re-exports)",
key_params: &["file"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "trace_dependency",
kind: "trace",
description: "Trace where a dependency is imported and whether scripts or CI use it",
key_params: &["package_name"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
McpToolInfo {
name: "trace_clone",
kind: "trace",
description: "Deep-dive a duplicate-code clone group by location or fingerprint",
key_params: &["file", "line", "fingerprint"],
license: McpToolLicense::Free,
license_note: None,
read_only: true,
},
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tool_names_are_unique() {
let mut names: Vec<&str> = MCP_TOOLS.iter().map(|t| t.name).collect();
let total = names.len();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), total, "duplicate tool name in MCP_TOOLS");
}
#[test]
fn freemium_set_is_exactly_the_runtime_coverage_family() {
let freemium: Vec<&str> = MCP_TOOLS
.iter()
.filter(|t| t.license == McpToolLicense::Freemium)
.map(|t| t.name)
.collect();
assert_eq!(
freemium,
[
"check_runtime_coverage",
"get_hot_paths",
"get_blast_radius",
"get_importance",
"get_cleanup_candidates",
],
"freemium marking must cover exactly the runtime-coverage family"
);
}
#[test]
fn license_note_present_exactly_when_freemium() {
for tool in MCP_TOOLS {
assert_eq!(
tool.license_note.is_some(),
tool.license == McpToolLicense::Freemium,
"tool {} must carry a license_note iff freemium",
tool.name
);
}
}
#[test]
fn kinds_are_from_the_documented_set() {
let allowed = [
"analysis",
"trace",
"fix",
"introspection",
"runtime-coverage",
"composition",
];
for tool in MCP_TOOLS {
assert!(
allowed.contains(&tool.kind),
"tool {} has undocumented kind {}",
tool.name,
tool.kind
);
}
}
#[test]
fn only_fix_apply_mutates() {
for tool in MCP_TOOLS {
assert_eq!(
tool.read_only,
tool.name != "fix_apply",
"read_only flag wrong for {}",
tool.name
);
}
}
#[test]
fn descriptions_are_single_line_and_non_empty() {
for tool in MCP_TOOLS {
assert!(
!tool.description.is_empty(),
"{} has empty description",
tool.name
);
assert!(
!tool.description.contains('\n'),
"{} description must be one line",
tool.name
);
}
}
}