refactor: split http/mcp/mod.rs into 3 logical files
The 1882-line mod.rs is split into: - tools_list.rs: handle_tools_list — the static schema for every MCP tool (1172 lines) - dispatch.rs: handle_tools_call — the tool-name → *_tools router (157 lines) - mod.rs: doc, sub-mod decls, JsonRpc structs, Poem handlers, handle_initialize (586 lines) Tests stay co-located with the code they exercise. No behaviour change. All 267 http::mcp tests pass; full suite green (2635 tests with --test-threads=1).
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
//! `tools/call` MCP method — dispatches a tool name to the appropriate `*_tools` module.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::JsonRpcResponse;
|
||||
use crate::http::context::AppContext;
|
||||
use super::{
|
||||
agent_tools, diagnostics, git_tools, merge_tools, qa_tools, shell_tools, status_tools,
|
||||
story_tools, wizard_tools,
|
||||
};
|
||||
use crate::slog_warn;
|
||||
|
||||
// ── Tool dispatch ─────────────────────────────────────────────────
|
||||
|
||||
pub(super) async fn handle_tools_call(id: Option<Value>, params: &Value, ctx: &AppContext) -> JsonRpcResponse {
|
||||
let tool_name = params.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let args = params.get("arguments").cloned().unwrap_or(json!({}));
|
||||
|
||||
let result = match tool_name {
|
||||
// Workflow tools
|
||||
"create_story" => story_tools::tool_create_story(&args, ctx),
|
||||
"validate_stories" => story_tools::tool_validate_stories(ctx),
|
||||
"list_upcoming" => story_tools::tool_list_upcoming(ctx),
|
||||
"get_story_todos" => story_tools::tool_get_story_todos(&args, ctx),
|
||||
"record_tests" => story_tools::tool_record_tests(&args, ctx),
|
||||
"ensure_acceptance" => story_tools::tool_ensure_acceptance(&args, ctx),
|
||||
// Agent tools (async)
|
||||
"start_agent" => agent_tools::tool_start_agent(&args, ctx).await,
|
||||
"stop_agent" => agent_tools::tool_stop_agent(&args, ctx).await,
|
||||
"list_agents" => agent_tools::tool_list_agents(ctx),
|
||||
"get_agent_config" => agent_tools::tool_get_agent_config(ctx),
|
||||
"reload_agent_config" => agent_tools::tool_get_agent_config(ctx),
|
||||
"get_agent_output" => agent_tools::tool_get_agent_output(&args, ctx).await,
|
||||
"wait_for_agent" => agent_tools::tool_wait_for_agent(&args, ctx).await,
|
||||
"get_agent_remaining_turns_and_budget" => {
|
||||
agent_tools::tool_get_agent_remaining_turns_and_budget(&args, ctx)
|
||||
}
|
||||
// Worktree tools
|
||||
"create_worktree" => agent_tools::tool_create_worktree(&args, ctx).await,
|
||||
"list_worktrees" => agent_tools::tool_list_worktrees(ctx),
|
||||
"remove_worktree" => agent_tools::tool_remove_worktree(&args, ctx).await,
|
||||
// Editor tools
|
||||
"get_editor_command" => agent_tools::tool_get_editor_command(&args, ctx),
|
||||
// Lifecycle tools
|
||||
"accept_story" => story_tools::tool_accept_story(&args, ctx),
|
||||
// Story mutation tools (auto-commit to master)
|
||||
"check_criterion" => story_tools::tool_check_criterion(&args, ctx),
|
||||
"edit_criterion" => story_tools::tool_edit_criterion(&args, ctx),
|
||||
"add_criterion" => story_tools::tool_add_criterion(&args, ctx),
|
||||
"remove_criterion" => story_tools::tool_remove_criterion(&args, ctx),
|
||||
"update_story" => story_tools::tool_update_story(&args, ctx),
|
||||
// Spike lifecycle tools
|
||||
"create_spike" => story_tools::tool_create_spike(&args, ctx),
|
||||
// Bug lifecycle tools
|
||||
"create_bug" => story_tools::tool_create_bug(&args, ctx),
|
||||
"list_bugs" => story_tools::tool_list_bugs(ctx),
|
||||
"close_bug" => story_tools::tool_close_bug(&args, ctx),
|
||||
// Refactor lifecycle tools
|
||||
"create_refactor" => story_tools::tool_create_refactor(&args, ctx),
|
||||
"list_refactors" => story_tools::tool_list_refactors(ctx),
|
||||
// Mergemaster tools
|
||||
"merge_agent_work" => merge_tools::tool_merge_agent_work(&args, ctx).await,
|
||||
"get_merge_status" => merge_tools::tool_get_merge_status(&args, ctx),
|
||||
"move_story_to_merge" => merge_tools::tool_move_story_to_merge(&args, ctx).await,
|
||||
"report_merge_failure" => merge_tools::tool_report_merge_failure(&args, ctx),
|
||||
// QA tools
|
||||
"request_qa" => qa_tools::tool_request_qa(&args, ctx).await,
|
||||
"approve_qa" => qa_tools::tool_approve_qa(&args, ctx).await,
|
||||
"reject_qa" => qa_tools::tool_reject_qa(&args, ctx).await,
|
||||
"launch_qa_app" => qa_tools::tool_launch_qa_app(&args, ctx).await,
|
||||
// Pipeline status
|
||||
"get_pipeline_status" => story_tools::tool_get_pipeline_status(ctx),
|
||||
// Diagnostics
|
||||
"get_server_logs" => diagnostics::tool_get_server_logs(&args),
|
||||
"get_version" => diagnostics::tool_get_version(ctx),
|
||||
// Server lifecycle
|
||||
"rebuild_and_restart" => diagnostics::tool_rebuild_and_restart(ctx).await,
|
||||
// Permission bridge (Claude Code → frontend dialog)
|
||||
"prompt_permission" => diagnostics::tool_prompt_permission(&args, ctx).await,
|
||||
// Token usage
|
||||
"get_token_usage" => diagnostics::tool_get_token_usage(&args, ctx),
|
||||
// Delete story
|
||||
"delete_story" => story_tools::tool_delete_story(&args, ctx).await,
|
||||
// Purge story (CRDT tombstone — story 521)
|
||||
"purge_story" => story_tools::tool_purge_story(&args, ctx),
|
||||
// Debug CRDT dump (story 515)
|
||||
"dump_crdt" => diagnostics::tool_dump_crdt(&args),
|
||||
// Arbitrary pipeline movement
|
||||
"move_story" => diagnostics::tool_move_story(&args, ctx),
|
||||
// Unblock story
|
||||
"unblock_story" => story_tools::tool_unblock_story(&args, ctx),
|
||||
// Shell command execution
|
||||
"run_command" => shell_tools::tool_run_command(&args, ctx).await,
|
||||
"run_tests" => shell_tools::tool_run_tests(&args, ctx).await,
|
||||
"get_test_result" => shell_tools::tool_get_test_result(&args, ctx).await,
|
||||
"run_build" => shell_tools::tool_run_build(&args, ctx).await,
|
||||
"run_lint" => shell_tools::tool_run_lint(&args, ctx).await,
|
||||
// Git operations
|
||||
"git_status" => git_tools::tool_git_status(&args, ctx).await,
|
||||
"git_diff" => git_tools::tool_git_diff(&args, ctx).await,
|
||||
"git_add" => git_tools::tool_git_add(&args, ctx).await,
|
||||
"git_commit" => git_tools::tool_git_commit(&args, ctx).await,
|
||||
"git_log" => git_tools::tool_git_log(&args, ctx).await,
|
||||
// Story triage
|
||||
"status" => status_tools::tool_status(&args, ctx).await,
|
||||
// File line count
|
||||
"loc_file" => diagnostics::tool_loc_file(&args, ctx),
|
||||
// Setup wizard tools
|
||||
"wizard_status" => wizard_tools::tool_wizard_status(ctx),
|
||||
"wizard_generate" => wizard_tools::tool_wizard_generate(&args, ctx),
|
||||
"wizard_confirm" => wizard_tools::tool_wizard_confirm(ctx),
|
||||
"wizard_skip" => wizard_tools::tool_wizard_skip(ctx),
|
||||
"wizard_retry" => wizard_tools::tool_wizard_retry(ctx),
|
||||
_ => Err(format!("Unknown tool: {tool_name}")),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(content) => JsonRpcResponse::success(
|
||||
id,
|
||||
json!({
|
||||
"content": [{ "type": "text", "text": content }]
|
||||
}),
|
||||
),
|
||||
Err(msg) => {
|
||||
slog_warn!("[mcp] Tool call failed: tool={tool_name} error={msg}");
|
||||
JsonRpcResponse::success(
|
||||
id,
|
||||
json!({
|
||||
"content": [{ "type": "text", "text": msg }],
|
||||
"isError": true
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::http::test_helpers::test_ctx;
|
||||
|
||||
#[test]
|
||||
fn handle_tools_call_unknown_tool() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = test_ctx(tmp.path());
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let resp = rt.block_on(handle_tools_call(
|
||||
Some(json!(1)),
|
||||
&json!({"name": "bogus_tool", "arguments": {}}),
|
||||
&ctx,
|
||||
));
|
||||
let result = resp.result.unwrap();
|
||||
assert_eq!(result["isError"], true);
|
||||
assert!(
|
||||
result["content"][0]["text"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("Unknown tool")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user