use super::super::{
get_thread_original_working_directory, get_thread_working_directory,
set_thread_working_directory, McpFunction, McpToolCall, McpToolResult,
};
use anyhow::Result;
use serde_json::{json, Value};
pub fn get_workdir_function() -> McpFunction {
McpFunction {
name: "workdir".to_string(),
description: "Get or set the working directory used by all MCP tools (shell, text_editor, etc.).
- Get current: `{}` or `{\"path\": null}`
- Set new: `{\"path\": \"/path/to/dir\"}` (absolute or relative)
- Reset to session root: `{\"reset\": true}`
Changes apply to the current thread only. Subsequent tool calls resolve paths relative to this directory.".to_string(),
parameters: json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Optional path to set as new working directory. Can be absolute or relative to current working directory."
},
"reset": {
"type": "boolean",
"default": false,
"description": "If true, reset to original project directory (ignores 'path' parameter)"
}
}
}),
}
}
pub async fn execute_workdir_command(call: &McpToolCall) -> Result<McpToolResult> {
let reset = call
.parameters
.get("reset")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if reset {
let original_dir = get_thread_original_working_directory();
set_thread_working_directory(original_dir.clone());
return Ok(McpToolResult::success(
"workdir".to_string(),
call.tool_id.clone(),
json!({
"success": true,
"action": "reset",
"working_directory": original_dir.to_string_lossy(),
"message": format!("Working directory reset to: {}", original_dir.display())
})
.to_string(),
));
}
match call.parameters.get("path") {
Some(Value::String(path_str)) if !path_str.trim().is_empty() => {
let path_str = path_str.trim();
let new_path = if std::path::Path::new(path_str).is_absolute() {
std::path::PathBuf::from(path_str)
} else {
let current = get_thread_working_directory();
current.join(path_str)
};
let canonical_path = match new_path.canonicalize() {
Ok(p) => p,
Err(e) => {
return Ok(McpToolResult::error(
"workdir".to_string(),
call.tool_id.clone(),
format!(
"Path does not exist or is not accessible: {} (error: {})",
new_path.display(),
e
),
));
}
};
if !canonical_path.is_dir() {
return Ok(McpToolResult::error(
"workdir".to_string(),
call.tool_id.clone(),
format!("Path is not a directory: {}", canonical_path.display()),
));
}
let old_dir = get_thread_working_directory();
set_thread_working_directory(canonical_path.clone());
Ok(McpToolResult::success(
"workdir".to_string(),
call.tool_id.clone(),
json!({
"success": true,
"action": "set",
"previous_directory": old_dir.to_string_lossy(),
"working_directory": canonical_path.to_string_lossy(),
"message": format!("Working directory changed from {} to {}", old_dir.display(), canonical_path.display())
}).to_string(),
))
}
Some(_) => Ok(McpToolResult::error(
"workdir".to_string(),
call.tool_id.clone(),
"Parameter 'path' must be a non-empty string".to_string(),
)),
None => {
let current_dir = get_thread_working_directory();
Ok(McpToolResult::success(
"workdir".to_string(),
call.tool_id.clone(),
json!({
"success": true,
"action": "get",
"working_directory": current_dir.to_string_lossy(),
"message": format!("Current working directory: {}", current_dir.display())
})
.to_string(),
))
}
}
}