story-kit: merge 347_story_mcp_tool_for_shell_command_execution
This commit is contained in:
@@ -12,6 +12,7 @@ pub mod agent_tools;
|
||||
pub mod diagnostics;
|
||||
pub mod merge_tools;
|
||||
pub mod qa_tools;
|
||||
pub mod shell_tools;
|
||||
pub mod story_tools;
|
||||
|
||||
/// Returns true when the Accept header includes text/event-stream.
|
||||
@@ -33,7 +34,7 @@ struct JsonRpcRequest {
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JsonRpcResponse {
|
||||
pub(super) struct JsonRpcResponse {
|
||||
jsonrpc: &'static str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
id: Option<Value>,
|
||||
@@ -52,7 +53,7 @@ struct JsonRpcError {
|
||||
}
|
||||
|
||||
impl JsonRpcResponse {
|
||||
fn success(id: Option<Value>, result: Value) -> Self {
|
||||
pub(super) fn success(id: Option<Value>, result: Value) -> Self {
|
||||
Self {
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
@@ -61,7 +62,7 @@ impl JsonRpcResponse {
|
||||
}
|
||||
}
|
||||
|
||||
fn error(id: Option<Value>, code: i64, message: String) -> Self {
|
||||
pub(super) fn error(id: Option<Value>, code: i64, message: String) -> Self {
|
||||
Self {
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
@@ -132,6 +133,9 @@ pub async fn mcp_post_handler(req: &Request, body: Body, ctx: Data<&Arc<AppConte
|
||||
if tool_name == "get_agent_output" {
|
||||
return handle_agent_output_sse(rpc.id, &rpc.params, &ctx);
|
||||
}
|
||||
if tool_name == "run_command" {
|
||||
return shell_tools::handle_run_command_sse(rpc.id, &rpc.params, &ctx);
|
||||
}
|
||||
}
|
||||
|
||||
let resp = match rpc.method.as_str() {
|
||||
@@ -160,7 +164,7 @@ fn to_json_response(resp: JsonRpcResponse) -> Response {
|
||||
.body(Body::from(body))
|
||||
}
|
||||
|
||||
fn to_sse_response(resp: JsonRpcResponse) -> Response {
|
||||
pub(super) fn to_sse_response(resp: JsonRpcResponse) -> Response {
|
||||
let json = serde_json::to_string(&resp).unwrap_or_default();
|
||||
let sse_body = format!("data: {json}\n\n");
|
||||
Response::builder()
|
||||
@@ -999,6 +1003,28 @@ fn handle_tools_list(id: Option<Value>) -> JsonRpcResponse {
|
||||
},
|
||||
"required": ["story_id", "target_stage"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "run_command",
|
||||
"description": "Execute a shell command in an agent's worktree directory. The working_dir must be inside .story_kit/worktrees/. Returns stdout, stderr, exit_code, and timed_out. Supports SSE streaming (send Accept: text/event-stream) for long-running commands. Dangerous commands (rm -rf /, sudo, etc.) are blocked.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "The bash command to execute (passed to bash -c)"
|
||||
},
|
||||
"working_dir": {
|
||||
"type": "string",
|
||||
"description": "Absolute path to the worktree directory to run the command in. Must be inside .story_kit/worktrees/."
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 120, max: 600)"
|
||||
}
|
||||
},
|
||||
"required": ["command", "working_dir"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}),
|
||||
@@ -1079,6 +1105,8 @@ async fn handle_tools_call(
|
||||
"delete_story" => story_tools::tool_delete_story(&args, ctx).await,
|
||||
// Arbitrary pipeline movement
|
||||
"move_story" => diagnostics::tool_move_story(&args, ctx),
|
||||
// Shell command execution
|
||||
"run_command" => shell_tools::tool_run_command(&args, ctx).await,
|
||||
_ => Err(format!("Unknown tool: {tool_name}")),
|
||||
};
|
||||
|
||||
@@ -1188,7 +1216,8 @@ mod tests {
|
||||
assert!(names.contains(&"get_token_usage"));
|
||||
assert!(names.contains(&"move_story"));
|
||||
assert!(names.contains(&"delete_story"));
|
||||
assert_eq!(tools.len(), 42);
|
||||
assert!(names.contains(&"run_command"));
|
||||
assert_eq!(tools.len(), 43);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user