#[derive(Debug, Deserialize)]
struct ContextGenerateArgs {
paths: Vec<String>,
#[serde(default)]
format: Option<String>,
#[serde(default)]
max_depth: Option<usize>,
#[serde(default)]
include_dependencies: bool,
}
fn validate_context_format(format: Option<&str>) -> Result<()> {
match format {
None | Some("json") => Ok(()),
Some(other) => Err(Error::validation(format!(
"Unsupported format: {other} (generate_context produces \"json\" only)"
))),
}
}
impl ContextGenerateTool {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self
}
}
impl Default for ContextGenerateTool {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl ToolHandler for ContextGenerateTool {
async fn handle(&self, args: Value, _extra: RequestHandlerExtra) -> Result<Value> {
debug!("Handling context.generate with args: {}", args);
let params: ContextGenerateArgs = serde_json::from_value(args)
.map_err(|e| Error::validation(format!("Invalid arguments: {e}")))?;
validate_context_format(params.format.as_deref())?;
let paths = crate::mcp_pmcp::tool_schemas::resolve_existing_paths(params.paths)?;
let context =
tool_functions::generate_context(&paths, params.max_depth, params.include_dependencies)
.await
.map_err(|e| Error::internal(format!("Context generation failed: {e}")))?;
Ok(context)
}
fn metadata(&self) -> Option<ToolInfo> {
let extra = json!({
"format": { "type": "string", "enum": ["json"], "description": "Output format" },
"max_depth": { "type": "integer", "description": "Max directory-tree depth to include" },
"include_dependencies": { "type": "boolean", "description": "Include dependency graph" }
});
Some(build_tool_info(
"generate_context",
"Generate project context (file tree + optional dependency graph) for LLM/agent consumption.",
paths_object_schema(extra, vec!["paths"]),
))
}
}
#[derive(Debug, Deserialize)]
struct ContextAnalyzeArgs {
paths: Vec<String>,
#[serde(default)]
analysis_types: Vec<String>,
}
impl ContextAnalyzeTool {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self
}
}
impl Default for ContextAnalyzeTool {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl ToolHandler for ContextAnalyzeTool {
async fn handle(&self, args: Value, _extra: RequestHandlerExtra) -> Result<Value> {
debug!("Handling context.analyze with args: {}", args);
let params: ContextAnalyzeArgs = serde_json::from_value(args)
.map_err(|e| Error::validation(format!("Invalid arguments: {e}")))?;
let paths = crate::mcp_pmcp::tool_schemas::resolve_existing_paths(params.paths)?;
let analyses = tool_functions::analyze_context(&paths, ¶ms.analysis_types)
.await
.map_err(|e| Error::internal(format!("Context analysis failed: {e}")))?;
Ok(analyses)
}
}
#[derive(Debug, Deserialize)]
struct ContextSummaryArgs {
paths: Vec<String>,
#[serde(default)]
level: Option<String>,
}
impl ContextSummaryTool {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
Self
}
}
impl Default for ContextSummaryTool {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl ToolHandler for ContextSummaryTool {
async fn handle(&self, args: Value, _extra: RequestHandlerExtra) -> Result<Value> {
debug!("Handling context.summary with args: {}", args);
let params: ContextSummaryArgs = serde_json::from_value(args)
.map_err(|e| Error::validation(format!("Invalid arguments: {e}")))?;
let paths = crate::mcp_pmcp::tool_schemas::resolve_existing_paths(params.paths)?;
tool_functions::resolve_summary_level(params.level.as_deref())
.map_err(|e| Error::validation(e.to_string()))?;
let summary = tool_functions::context_summary(&paths, params.level.as_deref())
.await
.map_err(|e| Error::internal(format!("Context summary failed: {e}")))?;
Ok(summary)
}
fn metadata(&self) -> Option<ToolInfo> {
let extra = json!({
"level": { "type": "string", "enum": ["brief", "normal", "detailed"], "description": "Summary detail level" }
});
Some(build_tool_info(
"scaffold_project",
"Produce a high-level project summary scaffold for the given paths.",
paths_object_schema(extra, vec!["paths"]),
))
}
}
#[cfg(test)]
mod context_format_tests {
use super::*;
#[test]
fn only_json_is_accepted() {
assert!(validate_context_format(None).is_ok());
assert!(validate_context_format(Some("json")).is_ok());
for stubbed in ["markdown", "xml"] {
let err = validate_context_format(Some(stubbed))
.expect_err("a format the tool cannot render must be an error, not a stub string");
assert!(
err.to_string().contains("Unsupported format"),
"got: {err}"
);
}
}
#[test]
fn input_schema_advertises_only_renderable_formats() {
use pmcp::ToolHandler;
let info = ContextGenerateTool::new()
.metadata()
.expect("generate_context advertises metadata");
let formats = info.input_schema["properties"]["format"]["enum"].clone();
assert_eq!(
formats,
json!(["json"]),
"the schema must not offer formats the handler rejects"
);
}
}