use async_trait::async_trait;
use mcp_tester::{AppValidationMode, AppValidator};
use pmcp::types::ToolInfo;
use pmcp::ToolHandler;
use serde::Deserialize;
use serde_json::{json, Value};
use super::{create_tester, default_timeout, internal_err};
#[derive(Deserialize)]
struct TestAppsInput {
url: String,
#[serde(default = "default_timeout")]
timeout: u64,
#[serde(default = "default_mode")]
mode: String,
#[serde(default)]
tool_filter: Option<String>,
#[serde(default)]
strict: bool,
}
fn default_mode() -> String {
"standard".to_string()
}
fn parse_modes(mode: &str) -> Result<Vec<AppValidationMode>, String> {
match mode {
"standard" => Ok(vec![AppValidationMode::Standard]),
"chatgpt" => Ok(vec![AppValidationMode::ChatGpt]),
"claude" | "claude-desktop" => Ok(vec![AppValidationMode::ClaudeDesktop]),
"all" => Ok(vec![
AppValidationMode::Standard,
AppValidationMode::ChatGpt,
AppValidationMode::ClaudeDesktop,
]),
other => Err(format!(
"Unknown validation mode: '{other}'. Valid: standard, chatgpt, claude, all"
)),
}
}
pub struct TestAppsTool;
#[async_trait]
impl ToolHandler for TestAppsTool {
async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
let params: TestAppsInput = serde_json::from_value(args)
.map_err(|e| pmcp::Error::validation(format!("Invalid arguments: {e}")))?;
let modes = parse_modes(¶ms.mode).map_err(pmcp::Error::validation)?;
let mut tester = create_tester(¶ms.url, params.timeout)?;
tester.run_quick_test().await.map_err(internal_err)?;
let tools_result = tester.test_tools_list().await;
if tools_result.status == mcp_tester::TestStatus::Failed {
return Err(internal_err(
tools_result
.error
.unwrap_or_else(|| "failed to list tools".into()),
));
}
let tools = tester.get_tools().cloned().unwrap_or_default();
let resources = tester
.list_resources()
.await
.map(|r| r.resources)
.unwrap_or_default();
let mut all_results = Vec::new();
let tool_filter = params.tool_filter;
for mode in modes {
let validator = AppValidator::new(mode, tool_filter.clone());
let mut results = validator.validate_tools(&tools, &resources);
if params.strict {
for r in &mut results {
if r.status == mcp_tester::TestStatus::Warning {
r.status = mcp_tester::TestStatus::Failed;
}
}
}
all_results.extend(results);
}
serde_json::to_value(&all_results).map_err(internal_err)
}
fn metadata(&self) -> Option<ToolInfo> {
Some(ToolInfo::new(
"test_apps",
Some("Validate MCP Apps metadata on a remote server's tools".to_string()),
json!({
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "MCP server URL to validate"
},
"timeout": {
"type": "integer",
"description": "Timeout in seconds",
"default": 30
},
"mode": {
"type": "string",
"description": "Validation mode",
"enum": ["standard", "chatgpt", "claude", "all"],
"default": "standard"
},
"tool_filter": {
"type": "string",
"description": "Filter to a single tool name"
},
"strict": {
"type": "boolean",
"description": "Promote warnings to failures",
"default": false
}
},
"required": ["url"]
}),
))
}
}