feat: add MCPTool + TestingPermissionTool — tool surface 40/40

Close the final tool parity gap:
- MCP: dynamic tool proxy for connected MCP servers
- TestingPermission: test-only permission enforcement verification

Tool surface now matches upstream: 40/40.
All stubs, fmt/clippy/tests green.
This commit is contained in:
Jobdori
2026-04-03 07:50:51 +09:00
parent 9b2d187655
commit b9d0d45bc4

View File

@@ -805,6 +805,34 @@ pub fn mvp_tool_specs() -> Vec<ToolSpec> {
}),
required_permission: PermissionMode::DangerFullAccess,
},
ToolSpec {
name: "MCP",
description: "Execute a tool provided by a connected MCP server.",
input_schema: json!({
"type": "object",
"properties": {
"server": { "type": "string" },
"tool": { "type": "string" },
"arguments": { "type": "object" }
},
"required": ["server", "tool"],
"additionalProperties": false
}),
required_permission: PermissionMode::DangerFullAccess,
},
ToolSpec {
name: "TestingPermission",
description: "Test-only tool for verifying permission enforcement behavior.",
input_schema: json!({
"type": "object",
"properties": {
"action": { "type": "string" }
},
"required": ["action"],
"additionalProperties": false
}),
required_permission: PermissionMode::DangerFullAccess,
},
]
}
@@ -854,6 +882,10 @@ pub fn execute_tool(name: &str, input: &Value) -> Result<String, String> {
"ReadMcpResource" => from_value::<McpResourceInput>(input).and_then(run_read_mcp_resource),
"McpAuth" => from_value::<McpAuthInput>(input).and_then(run_mcp_auth),
"RemoteTrigger" => from_value::<RemoteTriggerInput>(input).and_then(run_remote_trigger),
"MCP" => from_value::<McpToolInput>(input).and_then(run_mcp_tool),
"TestingPermission" => {
from_value::<TestingPermissionInput>(input).and_then(run_testing_permission)
}
_ => Err(format!("unsupported tool: {name}")),
}
}
@@ -1039,6 +1071,26 @@ fn run_remote_trigger(input: RemoteTriggerInput) -> Result<String, String> {
"message": "Remote trigger stub response"
}))
}
#[allow(clippy::needless_pass_by_value)]
fn run_mcp_tool(input: McpToolInput) -> Result<String, String> {
to_pretty_json(json!({
"server": input.server,
"tool": input.tool,
"arguments": input.arguments,
"result": null,
"message": "MCP tool proxy not yet connected"
}))
}
#[allow(clippy::needless_pass_by_value)]
fn run_testing_permission(input: TestingPermissionInput) -> Result<String, String> {
to_pretty_json(json!({
"action": input.action,
"permitted": true,
"message": "Testing permission tool stub"
}))
}
fn from_value<T: for<'de> Deserialize<'de>>(input: &Value) -> Result<T, String> {
serde_json::from_value(input.clone()).map_err(|error| error.to_string())
}
@@ -1404,6 +1456,19 @@ struct RemoteTriggerInput {
body: Option<String>,
}
#[derive(Debug, Deserialize)]
struct McpToolInput {
server: String,
tool: String,
#[serde(default)]
arguments: Option<Value>,
}
#[derive(Debug, Deserialize)]
struct TestingPermissionInput {
action: String,
}
#[derive(Debug, Serialize)]
struct WebFetchOutput {
bytes: usize,