Skip to content

Commit 85bd36f

Browse files
committed
feat(bitbucket): add workspace and repo list MCP tools
Add bitbucket_workspace_list and bitbucket_repo_list tool definitions and handlers, following the existing MCP handler pattern.
1 parent 1b69791 commit 85bd36f

2 files changed

Lines changed: 174 additions & 0 deletions

File tree

crates/mcptools/src/mcp/tools/atlassian.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,3 +1320,131 @@ pub async fn handle_jira_comment_delete(
13201320
data: None,
13211321
})
13221322
}
1323+
1324+
/// Handle Bitbucket workspace list command via MCP
1325+
pub async fn handle_bitbucket_workspace_list(
1326+
arguments: Option<serde_json::Value>,
1327+
global: &crate::Global,
1328+
) -> Result<serde_json::Value, JsonRpcError> {
1329+
use crate::atlassian::bitbucket::{list_workspace_data, ListWorkspaceParams};
1330+
1331+
#[derive(Deserialize)]
1332+
struct BitbucketWorkspaceListArgs {
1333+
limit: Option<usize>,
1334+
#[serde(rename = "nextPage")]
1335+
next_page: Option<String>,
1336+
}
1337+
1338+
let args: BitbucketWorkspaceListArgs =
1339+
serde_json::from_value(arguments.unwrap_or(serde_json::Value::Null)).map_err(|e| {
1340+
JsonRpcError {
1341+
code: -32602,
1342+
message: format!("Invalid arguments: {e}"),
1343+
data: None,
1344+
}
1345+
})?;
1346+
1347+
if global.verbose {
1348+
eprintln!(
1349+
"Calling bitbucket_workspace_list: limit={:?}, nextPage={:?}",
1350+
args.limit, args.next_page
1351+
);
1352+
}
1353+
1354+
let params = ListWorkspaceParams {
1355+
limit: args.limit.unwrap_or(10),
1356+
next_page: args.next_page,
1357+
base_url_override: None,
1358+
app_password_override: global.bitbucket_app_password.clone(),
1359+
};
1360+
1361+
let list_data = list_workspace_data(params, None)
1362+
.await
1363+
.map_err(|e| JsonRpcError {
1364+
code: -32603,
1365+
message: format!("Tool execution error: {e}"),
1366+
data: None,
1367+
})?;
1368+
1369+
let json_string = serde_json::to_string_pretty(&list_data).map_err(|e| JsonRpcError {
1370+
code: -32603,
1371+
message: format!("Serialization error: {e}"),
1372+
data: None,
1373+
})?;
1374+
1375+
let result = CallToolResult {
1376+
content: vec![Content::Text { text: json_string }],
1377+
is_error: None,
1378+
};
1379+
1380+
serde_json::to_value(result).map_err(|e| JsonRpcError {
1381+
code: -32603,
1382+
message: format!("Internal error: {e}"),
1383+
data: None,
1384+
})
1385+
}
1386+
1387+
/// Handle Bitbucket repo list command via MCP
1388+
pub async fn handle_bitbucket_repo_list(
1389+
arguments: Option<serde_json::Value>,
1390+
global: &crate::Global,
1391+
) -> Result<serde_json::Value, JsonRpcError> {
1392+
use crate::atlassian::bitbucket::{list_repo_data, ListRepoParams};
1393+
1394+
#[derive(Deserialize)]
1395+
struct BitbucketRepoListArgs {
1396+
workspace: String,
1397+
limit: Option<usize>,
1398+
#[serde(rename = "nextPage")]
1399+
next_page: Option<String>,
1400+
}
1401+
1402+
let args: BitbucketRepoListArgs =
1403+
serde_json::from_value(arguments.unwrap_or(serde_json::Value::Null)).map_err(|e| {
1404+
JsonRpcError {
1405+
code: -32602,
1406+
message: format!("Invalid arguments: {e}"),
1407+
data: None,
1408+
}
1409+
})?;
1410+
1411+
if global.verbose {
1412+
eprintln!(
1413+
"Calling bitbucket_repo_list: workspace={}, limit={:?}, nextPage={:?}",
1414+
args.workspace, args.limit, args.next_page
1415+
);
1416+
}
1417+
1418+
let params = ListRepoParams {
1419+
workspace: args.workspace,
1420+
limit: args.limit.unwrap_or(10),
1421+
next_page: args.next_page,
1422+
base_url_override: None,
1423+
app_password_override: global.bitbucket_app_password.clone(),
1424+
};
1425+
1426+
let list_data = list_repo_data(params, None)
1427+
.await
1428+
.map_err(|e| JsonRpcError {
1429+
code: -32603,
1430+
message: format!("Tool execution error: {e}"),
1431+
data: None,
1432+
})?;
1433+
1434+
let json_string = serde_json::to_string_pretty(&list_data).map_err(|e| JsonRpcError {
1435+
code: -32603,
1436+
message: format!("Serialization error: {e}"),
1437+
data: None,
1438+
})?;
1439+
1440+
let result = CallToolResult {
1441+
content: vec![Content::Text { text: json_string }],
1442+
is_error: None,
1443+
};
1444+
1445+
serde_json::to_value(result).map_err(|e| JsonRpcError {
1446+
code: -32603,
1447+
message: format!("Internal error: {e}"),
1448+
data: None,
1449+
})
1450+
}

crates/mcptools/src/mcp/tools/mod.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,46 @@ pub fn handle_tools_list() -> Result<serde_json::Value, JsonRpcError> {
653653
"required": ["repo", "title", "sourceBranch"]
654654
}),
655655
},
656+
Tool {
657+
name: "bitbucket_workspace_list".to_string(),
658+
description: "List Bitbucket workspaces accessible to the authenticated user. Returns workspace slugs and names. Supports pagination. Requires BITBUCKET_USERNAME and BITBUCKET_APP_PASSWORD environment variables.".to_string(),
659+
input_schema: serde_json::json!({
660+
"type": "object",
661+
"properties": {
662+
"limit": {
663+
"type": "number",
664+
"description": "Maximum number of results per page (default: 10)"
665+
},
666+
"nextPage": {
667+
"type": "string",
668+
"description": "Pagination URL for fetching the next page of results"
669+
}
670+
},
671+
"required": []
672+
}),
673+
},
674+
Tool {
675+
name: "bitbucket_repo_list".to_string(),
676+
description: "List repositories in a Bitbucket workspace. Returns repository names, full names, and clone URLs (SSH and HTTPS). Supports pagination. Requires BITBUCKET_USERNAME and BITBUCKET_APP_PASSWORD environment variables.".to_string(),
677+
input_schema: serde_json::json!({
678+
"type": "object",
679+
"properties": {
680+
"workspace": {
681+
"type": "string",
682+
"description": "Workspace slug (e.g., 'my-workspace')"
683+
},
684+
"limit": {
685+
"type": "number",
686+
"description": "Maximum number of results per page (default: 10)"
687+
},
688+
"nextPage": {
689+
"type": "string",
690+
"description": "Pagination URL for fetching the next page of results"
691+
}
692+
},
693+
"required": ["workspace"]
694+
}),
695+
},
656696
Tool {
657697
name: "generate_code".to_string(),
658698
description: "Generate Rust code using a local Ollama model. Accepts an instruction, optional context, and optional file paths for context. Returns raw Rust source code. Requires a running Ollama instance with the specified model.".to_string(),
@@ -928,6 +968,12 @@ pub async fn handle_tools_call(
928968
"bitbucket_pr_create" => {
929969
atlassian::handle_bitbucket_pr_create(params.arguments, global).await
930970
}
971+
"bitbucket_workspace_list" => {
972+
atlassian::handle_bitbucket_workspace_list(params.arguments, global).await
973+
}
974+
"bitbucket_repo_list" => {
975+
atlassian::handle_bitbucket_repo_list(params.arguments, global).await
976+
}
931977
"hn_read_item" => hn::handle_hn_read_item(params.arguments, global).await,
932978
"hn_list_items" => hn::handle_hn_list_items(params.arguments, global).await,
933979
"md_fetch" => md::handle_md_fetch(params.arguments, global).await,

0 commit comments

Comments
 (0)