//! `tools/list` MCP method — returns the static schema for every tool the server exposes. use serde_json::Value; mod agent_tools; mod story_tools; mod system_tools; /// Return the full list of MCP tool definitions (name, description, inputSchema). /// /// Used by API-based runtimes (Gemini, OpenAI) that need tool schemas /// without going through the network. pub fn list_tools() -> Vec { let mut tools = Vec::new(); tools.extend(story_tools::story_tools()); tools.extend(agent_tools::agent_tools()); tools.extend(system_tools::system_tools()); tools } /// Wrap `list_tools()` in a JSON-RPC response (test-only helper). #[cfg(test)] pub(crate) fn handle_tools_list( id: Option, ) -> crate::http::gateway::jsonrpc::JsonRpcResponse { use serde_json::json; crate::http::gateway::jsonrpc::JsonRpcResponse::success(id, json!({ "tools": list_tools() })) } #[cfg(test)] mod tests { use super::*; use serde_json::json; #[test] fn tools_list_returns_all_tools() { let resp = handle_tools_list(Some(json!(2))); let result = resp.result.unwrap(); let tools = result["tools"].as_array().unwrap(); let names: Vec<&str> = tools.iter().map(|t| t["name"].as_str().unwrap()).collect(); assert!(names.contains(&"create_story")); assert!(names.contains(&"validate_stories")); assert!(names.contains(&"list_upcoming")); assert!(names.contains(&"get_story_todos")); assert!(names.contains(&"record_tests")); assert!(names.contains(&"ensure_acceptance")); assert!(names.contains(&"start_agent")); assert!(names.contains(&"stop_agent")); assert!(names.contains(&"list_agents")); assert!(names.contains(&"get_agent_config")); assert!(names.contains(&"reload_agent_config")); assert!(names.contains(&"get_agent_output")); assert!(names.contains(&"wait_for_agent")); assert!(names.contains(&"get_agent_remaining_turns_and_budget")); assert!(names.contains(&"create_worktree")); assert!(names.contains(&"list_worktrees")); assert!(names.contains(&"remove_worktree")); assert!(names.contains(&"get_editor_command")); assert!(!names.contains(&"report_completion")); assert!(names.contains(&"accept_story")); assert!(names.contains(&"check_criterion")); assert!(names.contains(&"add_criterion")); assert!(names.contains(&"update_story")); assert!(names.contains(&"create_spike")); assert!(names.contains(&"create_bug")); assert!(names.contains(&"list_bugs")); assert!(names.contains(&"close_bug")); assert!(names.contains(&"create_refactor")); assert!(names.contains(&"list_refactors")); assert!(names.contains(&"merge_agent_work")); assert!(names.contains(&"get_merge_status")); assert!(names.contains(&"move_story_to_merge")); assert!(names.contains(&"report_merge_failure")); assert!(names.contains(&"request_qa")); assert!(names.contains(&"approve_qa")); assert!(names.contains(&"reject_qa")); assert!(names.contains(&"launch_qa_app")); assert!(names.contains(&"get_server_logs")); assert!(names.contains(&"prompt_permission")); assert!(names.contains(&"get_pipeline_status")); assert!(names.contains(&"rebuild_and_restart")); assert!(names.contains(&"get_token_usage")); assert!(names.contains(&"move_story")); assert!(names.contains(&"unblock_story")); assert!(names.contains(&"delete_story")); assert!(names.contains(&"run_command")); assert!(names.contains(&"run_tests")); assert!(names.contains(&"get_test_result")); assert!(names.contains(&"run_build")); assert!(names.contains(&"run_lint")); assert!(names.contains(&"git_status")); assert!(names.contains(&"git_diff")); assert!(names.contains(&"git_add")); assert!(names.contains(&"git_commit")); assert!(names.contains(&"git_log")); assert!(names.contains(&"status")); assert!(names.contains(&"loc_file")); assert!(names.contains(&"dump_crdt")); assert!(names.contains(&"get_version")); assert!(names.contains(&"remove_criterion")); assert!(names.contains(&"mesh_status")); assert!(names.contains(&"run_check")); assert!(names.contains(&"cleanup_worktrees")); assert!(names.contains(&"create_epic")); assert!(names.contains(&"list_epics")); assert!(names.contains(&"show_epic")); assert_eq!(tools.len(), 72); } #[test] fn tools_list_schemas_have_required_fields() { let resp = handle_tools_list(Some(json!(1))); let tools = resp.result.unwrap()["tools"].as_array().unwrap().clone(); for tool in &tools { assert!(tool["name"].is_string(), "tool missing name"); assert!(tool["description"].is_string(), "tool missing description"); assert!(tool["inputSchema"].is_object(), "tool missing inputSchema"); assert_eq!(tool["inputSchema"]["type"], "object"); } } }