use serde::Deserialize;
use serde_json::{Value, json};
use crate::{
domain::{CodeRepositorySelector, CodebaseViewKind, CodebaseViewRequest},
interfaces::agent::{
AgentAdapterError, AgentAdapterErrorKind, MAX_AGENT_PATH_CHARS, authorize_limit,
validate_path_texts,
},
};
use super::super::{
McpServer,
tool_contract::{
api_error_result, domain_argument_error, invalid_arguments, parse_freshness,
request_context, tool_error_result, tool_success_result,
},
tool_registry::CODEBASE_VIEW_TOOL,
};
#[derive(Debug, Deserialize)]
struct CodebaseViewArgs {
repository: String,
#[serde(default)]
kind: Option<String>,
#[serde(default)]
limit: Option<usize>,
#[serde(default)]
ref_selector: Option<String>,
#[serde(default)]
path_filters: Vec<String>,
#[serde(default)]
language_filters: Vec<String>,
#[serde(default)]
freshness: Option<String>,
#[serde(default)]
changed_paths: Vec<String>,
}
pub(super) async fn run(server: &McpServer, arguments: Value, request_id: String) -> Value {
let args = match serde_json::from_value::<CodebaseViewArgs>(arguments) {
Ok(args) => args,
Err(error) => return tool_error_result(invalid_arguments(error)),
};
if let Err(error) = validate_path_texts("path_filters", &args.path_filters)
.and_then(|_| validate_path_texts("changed_paths", &args.changed_paths))
{
return tool_error_result(error);
}
let repository = match server
.scope_authorizer
.authorize_scope(
&server.service,
&server.agent.access_policy,
Some(args.repository),
)
.await
{
Ok(Some(repository)) => repository,
Ok(None) => {
return tool_error_result(AgentAdapterError::new(
AgentAdapterErrorKind::InvalidScope,
"repository is required for relay_codebase_view",
));
}
Err(error) => return tool_error_result(error),
};
let requested_limit = args.limit;
let limit = match authorize_limit(requested_limit, &server.agent.access_policy) {
Ok(limit) => limit,
Err(error) => return tool_error_result(error),
};
let limit = if requested_limit.is_none() {
limit.min(100)
} else {
limit
};
let kind = match parse_codebase_view_kind(args.kind.as_deref().unwrap_or("architecture_layers"))
{
Ok(kind) => kind,
Err(error) => return tool_error_result(error),
};
let freshness = match parse_freshness(args.freshness.as_deref()) {
Ok(freshness) => freshness,
Err(error) => return tool_error_result(error),
};
let selector = match CodeRepositorySelector::new(
repository,
args.ref_selector.unwrap_or_else(|| "HEAD".to_owned()),
args.path_filters,
args.language_filters,
) {
Ok(selector) => selector,
Err(error) => return tool_error_result(domain_argument_error(error)),
};
let request =
match CodebaseViewRequest::new(selector, kind, freshness, limit, args.changed_paths) {
Ok(request) => request,
Err(error) => return tool_error_result(domain_argument_error(error)),
};
match server
.service
.codebase_view(request, request_context(request_id))
.await
{
Ok(response) => tool_success_result(
format!(
"codebase view returned {} section(s)",
response.sections.len()
),
json!(response),
),
Err(error) => api_error_result(error),
}
}
pub(crate) fn definition() -> Value {
json!({
"name": CODEBASE_VIEW_TOOL,
"description": "Return a deterministic, evidence-backed codebase understanding view derived from indexed graph facts.",
"inputSchema": {
"type": "object",
"properties": {
"repository": {"type": "string", "minLength": 1},
"kind": {
"type": "string",
"enum": ["architecture_layers", "architecture-layers", "business_domains", "business-domains", "dependency_tour", "dependency-tour", "process_flow", "process-flow", "affected_scope", "affected-scope"]
},
"limit": {"type": "integer", "minimum": 1, "maximum": 100},
"ref_selector": {"type": "string"},
"path_filters": {"type": "array", "items": {"type": "string", "maxLength": MAX_AGENT_PATH_CHARS}},
"language_filters": {"type": "array", "items": {"type": "string"}},
"changed_paths": {"type": "array", "items": {"type": "string", "maxLength": MAX_AGENT_PATH_CHARS}},
"freshness": {
"type": "string",
"enum": ["allow-stale", "wait-until-fresh", "graph-only"]
}
},
"required": ["repository"]
}
})
}
fn parse_codebase_view_kind(value: &str) -> Result<CodebaseViewKind, AgentAdapterError> {
match value {
"architecture_layers" | "architecture-layers" => Ok(CodebaseViewKind::ArchitectureLayers),
"business_domains" | "business-domains" => Ok(CodebaseViewKind::BusinessDomains),
"dependency_tour" | "dependency-tour" => Ok(CodebaseViewKind::DependencyTour),
"process_flow" | "process-flow" => Ok(CodebaseViewKind::ProcessFlow),
"affected_scope" | "affected-scope" => Ok(CodebaseViewKind::AffectedScope),
other => Err(AgentAdapterError::new(
AgentAdapterErrorKind::InvalidArgument,
format!("invalid codebase view kind '{other}'"),
)),
}
}