#![cfg(all(feature = "comms", any(unix, windows)))]
use rmcp::ErrorData as McpError;
use rmcp::model::CallToolResult;
use super::ServerState;
use super::helpers::json_result;
use super::helpers_comms::{comms_err, resolve_comms_client};
use super::mode::{WorkspaceMode, reject_unsupported};
use super::types_registry::{
BranchDto, BranchesParams, BranchesResponse, WorkspaceDto, WorkspaceParams, WorkspacesParams, WorkspacesResponse,
WorktreeClaimParams, WorktreeClaimResponse, WorktreeDto, WorktreeReleaseParams, WorktreesParams, WorktreesResponse,
};
fn require(mode: WorkspaceMode, field: &str, value: Option<String>) -> Result<String, McpError> {
value.ok_or_else(|| {
McpError::invalid_params(
format!(
"`{}` mode=\"{}\" requires `{field}`",
WorkspaceMode::DOMAIN,
mode.as_str()
),
None,
)
})
}
pub(super) async fn run_workspace(state: &ServerState, params: WorkspaceParams) -> Result<CallToolResult, McpError> {
let WorkspaceParams {
mode,
repo_id,
name,
as_agent,
} = params;
let reject = |present: &[(&str, bool)]| reject_unsupported(WorkspaceMode::DOMAIN, mode.as_str(), present);
match mode {
WorkspaceMode::Workspaces => {
reject(&[("repo_id", repo_id.is_some()), ("name", name.is_some())])?;
run_workspaces(state, WorkspacesParams { as_agent }).await
}
WorkspaceMode::Worktrees => {
reject(&[("name", name.is_some())])?;
let repo_id = require(mode, "repo_id", repo_id)?;
run_worktrees(state, WorktreesParams { repo_id, as_agent }).await
}
WorkspaceMode::Branches => {
reject(&[("name", name.is_some())])?;
let repo_id = require(mode, "repo_id", repo_id)?;
run_branches(state, BranchesParams { repo_id, as_agent }).await
}
WorkspaceMode::Claim => {
let repo_id = require(mode, "repo_id", repo_id)?;
let name = require(mode, "name", name)?;
run_worktree_claim(
state,
WorktreeClaimParams {
repo_id,
name,
as_agent,
},
)
.await
}
WorkspaceMode::Release => {
let repo_id = require(mode, "repo_id", repo_id)?;
let name = require(mode, "name", name)?;
run_worktree_release(
state,
WorktreeReleaseParams {
repo_id,
name,
as_agent,
},
)
.await
}
}
}
async fn run_workspaces(state: &ServerState, params: WorkspacesParams) -> Result<CallToolResult, McpError> {
let handle = resolve_comms_client(state, params.as_agent).await?;
let mut client = handle.lock().await;
let records = client.list_workspaces().await.map_err(comms_err)?;
let workspaces: Vec<WorkspaceDto> = records.iter().map(WorkspaceDto::from).collect();
json_result(&WorkspacesResponse {
total: workspaces.len(),
workspaces,
})
}
async fn run_worktrees(state: &ServerState, params: WorktreesParams) -> Result<CallToolResult, McpError> {
let repo_id = params.repo_id.clone();
let handle = resolve_comms_client(state, params.as_agent).await?;
let mut client = handle.lock().await;
let records = client.list_worktrees(params.repo_id).await.map_err(comms_err)?;
let worktrees: Vec<WorktreeDto> = records.iter().map(WorktreeDto::from).collect();
json_result(&WorktreesResponse {
repo_id,
total: worktrees.len(),
worktrees,
})
}
async fn run_branches(state: &ServerState, params: BranchesParams) -> Result<CallToolResult, McpError> {
let repo_id = params.repo_id.clone();
let handle = resolve_comms_client(state, params.as_agent).await?;
let mut client = handle.lock().await;
let records = client.list_branches(params.repo_id).await.map_err(comms_err)?;
let branches: Vec<BranchDto> = records.iter().map(BranchDto::from).collect();
json_result(&BranchesResponse {
repo_id,
total: branches.len(),
branches,
})
}
async fn run_worktree_claim(state: &ServerState, params: WorktreeClaimParams) -> Result<CallToolResult, McpError> {
let repo_id = params.repo_id.clone();
let name = params.name.clone();
let handle = resolve_comms_client(state, params.as_agent).await?;
let mut client = handle.lock().await;
let claimant = client.agent().as_str().to_string();
let held = client
.claim_worktree(params.repo_id, params.name, claimant.clone())
.await
.map_err(comms_err)?;
json_result(&WorktreeClaimResponse {
repo_id,
name,
claimant,
held,
})
}
async fn run_worktree_release(state: &ServerState, params: WorktreeReleaseParams) -> Result<CallToolResult, McpError> {
let repo_id = params.repo_id.clone();
let name = params.name.clone();
let handle = resolve_comms_client(state, params.as_agent).await?;
let mut client = handle.lock().await;
let claimant = client.agent().as_str().to_string();
let held = client
.release_worktree(params.repo_id, params.name, claimant.clone())
.await
.map_err(comms_err)?;
json_result(&WorktreeClaimResponse {
repo_id,
name,
claimant,
held,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_name_the_mode_and_field_when_a_required_sibling_is_missing() {
let error = require(WorkspaceMode::Claim, "repo_id", None).expect_err("a missing repo_id must fail");
let message = error.message.to_string();
assert_eq!(message, "`workspace` mode=\"claim\" requires `repo_id`");
}
#[test]
fn should_pass_a_supplied_sibling_through_untouched() {
let value = require(WorkspaceMode::Worktrees, "repo_id", Some("path:/tmp/repo".to_string()))
.expect("a supplied repo_id must pass");
assert_eq!(value, "path:/tmp/repo");
}
}