Skip to main content
Glama
aashari

Atlassian Bitbucket MCP Server

by aashari

bb_clone_repo

Clone Bitbucket repositories to your local filesystem using SSH or HTTPS. Specify workspace, repository, and target path for efficient setup and access.

Instructions

Clones a Bitbucket repository to your local filesystem using SSH (preferred) or HTTPS. Requires Bitbucket credentials and proper SSH key setup for optimal usage.

Parameters:

  • workspaceSlug: The Bitbucket workspace containing the repository (optional - will use default if not provided)

  • repoSlug: The repository name to clone (required)

  • targetPath: Parent directory where repository will be cloned (required)

Path Handling:

  • Absolute paths are strongly recommended (e.g., "/home/user/projects" or "C:\Users\name\projects")

  • Relative paths (e.g., "./my-repos" or "../downloads") will be resolved relative to the server's working directory, which may not be what you expect

  • The repository will be cloned into a subdirectory at targetPath/repoSlug

  • Make sure you have write permissions to the target directory

SSH Requirements:

  • SSH keys must be properly configured for Bitbucket

  • SSH agent should be running with your keys added

  • Will automatically fall back to HTTPS if SSH is unavailable

Example Usage:

// Clone a repository to a specific absolute path
bb_clone_repo({repoSlug: "my-project", targetPath: "/home/user/projects"})

// Specify the workspace and use a relative path (less reliable)
bb_clone_repo({workspaceSlug: "my-team", repoSlug: "api-service", targetPath: "./downloads"})

Returns: Success message with clone details or an error message with troubleshooting steps.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
repoSlugYesRepository name/slug to clone. This is the short name of the repository. Example: "project-api"
targetPathYesDirectory path where the repository will be cloned. IMPORTANT: Absolute paths are strongly recommended (e.g., "/home/user/projects" or "C:\Users\name\projects"). Relative paths will be resolved relative to the server's working directory, which may not be what you expect. The repository will be cloned into a subdirectory at targetPath/repoSlug. Make sure you have write permissions to this location.
workspaceSlugNoBitbucket workspace slug containing the repository. If not provided, the tool will use your default workspace (either configured via BITBUCKET_DEFAULT_WORKSPACE or the first workspace in your account). Example: "myteam"

Implementation Reference

  • Registers the bb_clone tool (Bitbucket repository cloner) with the MCP server, using handleRepoClone as the handler and CloneRepositoryToolArgs as input schema.
    server.registerTool(
    	'bb_clone',
    	{
    		title: 'Clone Bitbucket Repository',
    		description: BB_CLONE_DESCRIPTION,
    		inputSchema: CloneRepositoryToolArgs,
    	},
    	handleRepoClone,
    );
  • Tool handler wrapper that validates args, calls the controller's handleCloneRepository, formats response for MCP, and handles errors.
    async function handleRepoClone(args: Record<string, unknown>) {
    	const methodLogger = Logger.forContext(
    		'tools/atlassian.repositories.tool.ts',
    		'handleRepoClone',
    	);
    	try {
    		methodLogger.debug('Cloning repository:', args);
    
    		// Pass args directly to controller
    		const result = await handleCloneRepository(
    			args as CloneRepositoryToolArgsType,
    		);
    
    		methodLogger.debug('Successfully cloned repository via controller');
    
    		return {
    			content: [
    				{
    					type: 'text' as const,
    					text: truncateForAI(result.content, result.rawResponsePath),
    				},
    			],
    		};
    	} catch (error) {
    		methodLogger.error('Failed to clone repository', error);
    		return formatErrorForMcpTool(error);
    	}
    }
  • Core handler implementing the repository cloning: fetches repo details, determines clone URL (SSH preferred), executes git clone command, handles paths/permissions/errors comprehensively.
    export async function handleCloneRepository(
    	options: CloneRepositoryToolArgsType,
    ): Promise<ControllerResponse> {
    	const methodLogger = logger.forMethod('handleCloneRepository');
    	methodLogger.debug('Cloning repository with options:', options);
    
    	try {
    		// Handle optional workspaceSlug
    		let { workspaceSlug } = options;
    		if (!workspaceSlug) {
    			methodLogger.debug(
    				'No workspace provided, fetching default workspace',
    			);
    			const defaultWorkspace = await getDefaultWorkspace();
    			if (!defaultWorkspace) {
    				throw new Error(
    					'No default workspace found. Please provide a workspace slug.',
    				);
    			}
    			workspaceSlug = defaultWorkspace;
    			methodLogger.debug(`Using default workspace: ${defaultWorkspace}`);
    		}
    
    		// Required parameters check
    		const { repoSlug, targetPath } = options;
    		if (!repoSlug) {
    			throw new Error('Repository slug is required');
    		}
    		if (!targetPath) {
    			throw new Error('Target path is required');
    		}
    
    		// Normalize and resolve the target path
    		// If it's a relative path, convert it to absolute based on current working directory
    		const processedTargetPath = path.isAbsolute(targetPath)
    			? targetPath
    			: path.resolve(process.cwd(), targetPath);
    
    		methodLogger.debug(
    			`Normalized target path: ${processedTargetPath} (original: ${targetPath})`,
    		);
    
    		// Validate directory access and permissions before proceeding
    		try {
    			// Check if target directory exists
    			try {
    				await fs.access(processedTargetPath, constants.F_OK);
    				methodLogger.debug(
    					`Target directory exists: ${processedTargetPath}`,
    				);
    
    				// If it exists, check if we have write permission
    				try {
    					await fs.access(processedTargetPath, constants.W_OK);
    					methodLogger.debug(
    						`Have write permission to: ${processedTargetPath}`,
    					);
    				} catch {
    					throw new Error(
    						`Permission denied: You don't have write access to the target directory: ${processedTargetPath}`,
    					);
    				}
    			} catch {
    				// Directory doesn't exist, try to create it
    				methodLogger.debug(
    					`Target directory doesn't exist, creating: ${processedTargetPath}`,
    				);
    				try {
    					await fs.mkdir(processedTargetPath, { recursive: true });
    					methodLogger.debug(
    						`Successfully created directory: ${processedTargetPath}`,
    					);
    				} catch (mkdirError) {
    					throw new Error(
    						`Failed to create target directory ${processedTargetPath}: ${(mkdirError as Error).message}. Please ensure you have write permissions to the parent directory.`,
    					);
    				}
    			}
    		} catch (accessError) {
    			methodLogger.error('Path access error:', accessError);
    			throw accessError;
    		}
    
    		// Get repository details to determine clone URL
    		methodLogger.debug(
    			`Getting repository details for ${workspaceSlug}/${repoSlug}`,
    		);
    		const repoDetails = await atlassianRepositoriesService.get({
    			workspace: workspaceSlug,
    			repo_slug: repoSlug,
    		});
    
    		// Find SSH clone URL (preferred) or fall back to HTTPS
    		let cloneUrl: string | undefined;
    		let cloneProtocol: string = 'SSH'; // Default to SSH
    
    		if (repoDetails.links?.clone) {
    			// First try to find SSH clone URL
    			const sshClone = repoDetails.links.clone.find(
    				(link) => link.name === 'ssh',
    			);
    
    			if (sshClone) {
    				cloneUrl = sshClone.href;
    			} else {
    				// Fall back to HTTPS if SSH is not available
    				const httpsClone = repoDetails.links.clone.find(
    					(link) => link.name === 'https',
    				);
    
    				if (httpsClone) {
    					cloneUrl = httpsClone.href;
    					cloneProtocol = 'HTTPS';
    					methodLogger.warn(
    						'SSH clone URL not found, falling back to HTTPS',
    					);
    				}
    			}
    		}
    
    		if (!cloneUrl) {
    			throw new Error(
    				'Could not find a valid clone URL for the repository',
    			);
    		}
    
    		// Determine full target directory path
    		// Clone into a subdirectory named after the repo slug
    		const targetDir = path.join(processedTargetPath, repoSlug);
    		methodLogger.debug(`Will clone to: ${targetDir}`);
    
    		// Check if directory already exists
    		try {
    			const stats = await fs.stat(targetDir);
    			if (stats.isDirectory()) {
    				methodLogger.warn(
    					`Target directory already exists: ${targetDir}`,
    				);
    				return {
    					content: `Target directory \`${targetDir}\` already exists. Please choose a different target path or remove the existing directory.`,
    				};
    			}
    		} catch {
    			// Error means directory doesn't exist, which is what we want
    			methodLogger.debug(
    				`Target directory doesn't exist, proceeding with clone`,
    			);
    		}
    
    		// Execute git clone command
    		methodLogger.debug(`Cloning from URL (${cloneProtocol}): ${cloneUrl}`);
    		const command = `git clone ${cloneUrl} "${targetDir}"`;
    
    		try {
    			const result = await executeShellCommand(
    				command,
    				'cloning repository',
    			);
    
    			// Return success message with more detailed information
    			return {
    				content:
    					`Successfully cloned repository \`${workspaceSlug}/${repoSlug}\` to \`${targetDir}\` using ${cloneProtocol}.\n\n` +
    					`**Details:**\n` +
    					`- **Repository**: ${workspaceSlug}/${repoSlug}\n` +
    					`- **Clone Protocol**: ${cloneProtocol}\n` +
    					`- **Target Location**: ${targetDir}\n\n` +
    					`**Output:**\n\`\`\`\n${result}\n\`\`\`\n\n` +
    					`**Note**: If this is your first time cloning with SSH, ensure your SSH keys are set up correctly.`,
    			};
    		} catch (cloneError) {
    			// Enhanced error message with troubleshooting steps
    			const errorMsg = `Failed to clone repository: ${(cloneError as Error).message}`;
    			let troubleshooting = '';
    
    			if (cloneProtocol === 'SSH') {
    				troubleshooting =
    					`\n\n**Troubleshooting SSH Clone Issues:**\n` +
    					`1. Ensure you have SSH keys set up with Bitbucket\n` +
    					`2. Check if your SSH agent is running: \`eval "$(ssh-agent -s)"; ssh-add\`\n` +
    					`3. Verify connectivity: \`ssh -T git@bitbucket.org\`\n` +
    					`4. Try using HTTPS instead (modify your tool call with a different repository URL)`;
    			} else {
    				troubleshooting =
    					`\n\n**Troubleshooting HTTPS Clone Issues:**\n` +
    					`1. Check your Bitbucket credentials\n` +
    					`2. Ensure the target directory is writable\n` +
    					`3. Try running the command manually to see detailed errors`;
    			}
    
    			throw new Error(errorMsg + troubleshooting);
    		}
    	} catch (error) {
    		throw handleControllerError(error, {
    			entityType: 'Repository',
    			operation: 'clone',
    			source: 'controllers/atlassian.repositories.content.controller.ts@handleCloneRepository',
    			additionalInfo: options,
    		});
    	}
    }
  • Zod schema defining input arguments for the clone repository tool: workspaceSlug (opt), repoSlug (req), targetPath (req).
    export const CloneRepositoryToolArgs = z.object({
    	workspaceSlug: z
    		.string()
    		.optional()
    		.describe(
    			'Bitbucket workspace slug containing the repository. If not provided, the tool will use your default workspace (either configured via BITBUCKET_DEFAULT_WORKSPACE or the first workspace in your account). Example: "myteam"',
    		),
    	repoSlug: z
    		.string()
    		.min(1, 'Repository slug is required')
    		.describe(
    			'Repository name/slug to clone. This is the short name of the repository. Example: "project-api"',
    		),
    	targetPath: z
    		.string()
    		.min(1, 'Target path is required')
    		.describe(
    			'Directory path where the repository will be cloned. IMPORTANT: Absolute paths are strongly recommended (e.g., "/home/user/projects" or "C:\\Users\\name\\projects"). Relative paths will be resolved relative to the server\'s working directory, which may not be what you expect. The repository will be cloned into a subdirectory at targetPath/repoSlug. Make sure you have write permissions to this location.',
    		),
    });
    
    export type CloneRepositoryToolArgsType = z.infer<
    	typeof CloneRepositoryToolArgs
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Since no annotations are provided, the description carries the full burden. It effectively discloses key behavioral traits: authentication requirements (Bitbucket credentials, SSH setup), fallback behavior (automatic fallback to HTTPS if SSH unavailable), path resolution behavior (absolute vs. relative paths), and permission requirements (write permissions). It doesn't mention rate limits or error handling specifics beyond 'troubleshooting steps'.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections (Parameters, Path Handling, SSH Requirements, Example Usage, Returns). It's appropriately sized for a complex operation with authentication and path considerations. Some redundancy exists between the description and schema (e.g., path recommendations), but overall it's efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 3 parameters, no annotations, and no output schema, the description does a good job covering the essential context: purpose, authentication, path handling, SSH fallback, and examples. It explains the return value format (success/error message). The main gap is lack of explicit guidance on when NOT to use it versus sibling tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters thoroughly. The description's parameter section mostly repeats what's in the schema (e.g., workspaceSlug optional, repoSlug required). It adds minimal extra context like 'will use default if not provided' which is already implied in the schema. Baseline 3 is appropriate when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Clones a Bitbucket repository to your local filesystem') and distinguishes it from sibling tools like bb_get_repo or bb_ls_repos by specifying it's a clone operation rather than a retrieval or listing. It also specifies the mechanism (SSH preferred or HTTPS) and the target location (local filesystem).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool (to clone repositories) and mentions prerequisites (Bitbucket credentials, SSH key setup). However, it doesn't explicitly state when NOT to use it or name specific alternatives among the sibling tools (e.g., use bb_get_repo for metadata instead).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aashari/mcp-server-atlassian-bitbucket'

If you have feedback or need assistance with the MCP directory API, please join our Discord server