Skip to main content
Glama

Atlassian Bitbucket MCP Server

by aashari
atlassian.repositories.content.controller.ts10.2 kB
import atlassianRepositoriesService from '../services/vendor.atlassian.repositories.service.js'; import { Logger } from '../utils/logger.util.js'; import { handleControllerError } from '../utils/error-handler.util.js'; import { ControllerResponse } from '../types/common.types.js'; import { CloneRepositoryToolArgsType } from '../tools/atlassian.repositories.types.js'; import { getDefaultWorkspace } from '../utils/workspace.util.js'; import { executeShellCommand } from '../utils/shell.util.js'; import * as path from 'path'; import * as fs from 'fs/promises'; import { constants } from 'fs'; // Logger instance for this module const logger = Logger.forContext( 'controllers/atlassian.repositories.content.controller.ts', ); /** * Clones a Bitbucket repository to the local filesystem * @param options Options including repository identifiers and target path * @returns Information about the cloned repository */ 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, }); } } /** * Retrieves file content from a repository * @param options Options including repository identifiers and file path * @returns The file content as text */ export async function handleGetFileContent(options: { workspaceSlug?: string; repoSlug: string; path: string; ref?: string; }): Promise<ControllerResponse> { const methodLogger = logger.forMethod('handleGetFileContent'); methodLogger.debug('Getting file content with options:', options); try { // Required parameters check const { repoSlug, path: filePath } = options; 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}`); } if (!repoSlug) { throw new Error('Repository slug is required'); } if (!filePath) { throw new Error('File path is required'); } // Get repository details to determine the correct default branch let commitRef = options.ref; if (!commitRef) { methodLogger.debug( `No ref provided, fetching repository details to get default branch`, ); try { const repoDetails = await atlassianRepositoriesService.get({ workspace: workspaceSlug, repo_slug: repoSlug, }); // Use the repository's actual default branch if (repoDetails.mainbranch?.name) { commitRef = repoDetails.mainbranch.name; methodLogger.debug( `Using repository default branch: ${commitRef}`, ); } else { // Fallback to common default branches commitRef = 'main'; methodLogger.debug( `No default branch found, falling back to: ${commitRef}`, ); } } catch (repoError) { methodLogger.warn( 'Failed to get repository details, using fallback branch', repoError, ); commitRef = 'main'; } } // Get file content from service methodLogger.debug( `Fetching file content for ${workspaceSlug}/${repoSlug}/${filePath}`, { ref: commitRef }, ); const fileContent = await atlassianRepositoriesService.getFileContent({ workspace: workspaceSlug, repo_slug: repoSlug, path: filePath, commit: commitRef, }); // Return the file content as is methodLogger.debug( `Retrieved file content (${fileContent.length} bytes)`, ); return { content: fileContent, }; } catch (error) { throw handleControllerError(error, { entityType: 'File Content', operation: 'get', source: 'controllers/atlassian.repositories.content.controller.ts@handleGetFileContent', additionalInfo: options, }); } }

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