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

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