UNPKG

@aashari/mcp-server-atlassian-bitbucket

Version:

Node.js/TypeScript MCP server for Atlassian Bitbucket. Enables AI systems (LLMs) to interact with workspaces, repositories, and pull requests via tools (list, get, comment, search). Connects AI directly to version control workflows through the standard MC

202 lines (201 loc) 12 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.handleRepositoriesList = handleRepositoriesList; const vendor_atlassian_repositories_service_js_1 = __importDefault(require("../services/vendor.atlassian.repositories.service.js")); const logger_util_js_1 = require("../utils/logger.util.js"); const error_handler_util_js_1 = require("../utils/error-handler.util.js"); const defaults_util_js_1 = require("../utils/defaults.util.js"); const pagination_util_js_1 = require("../utils/pagination.util.js"); const formatter_util_js_1 = require("../utils/formatter.util.js"); const atlassian_repositories_formatter_js_1 = require("./atlassian.repositories.formatter.js"); const workspace_util_js_1 = require("../utils/workspace.util.js"); const query_util_js_1 = require("../utils/query.util.js"); // Create a contextualized logger for this file const logger = logger_util_js_1.Logger.forContext('controllers/atlassian.repositories.list.controller.ts'); /** * Lists repositories for a specific workspace with pagination and filtering options * @param options - Options for listing repositories including workspaceSlug * @returns Formatted list of repositories with pagination information */ async function handleRepositoriesList(options) { const methodLogger = logger.forMethod('handleRepositoriesList'); methodLogger.debug('Listing Bitbucket repositories...', options); try { // Get workspace slug from options or default let workspaceSlug = options.workspaceSlug; if (!workspaceSlug) { methodLogger.debug('No workspace slug provided, using default workspace'); const defaultWorkspace = await (0, workspace_util_js_1.getDefaultWorkspace)(); if (!defaultWorkspace) { throw new Error('No workspace slug provided and no default workspace could be determined. Please provide a workspace slug or configure a default workspace.'); } workspaceSlug = defaultWorkspace; methodLogger.debug(`Using default workspace: ${workspaceSlug}`); } // Create defaults object with proper typing const defaults = { limit: defaults_util_js_1.DEFAULT_PAGE_SIZE, sort: '-updated_on', }; // Apply defaults const mergedOptions = (0, defaults_util_js_1.applyDefaults)({ ...options, workspaceSlug }, defaults); // Format the query for Bitbucket API if provided // Combine query and projectKey if both are present const queryParts = []; if (mergedOptions.query) { // Assuming formatBitbucketQuery handles basic name/description search queryParts.push((0, query_util_js_1.formatBitbucketQuery)(mergedOptions.query)); } if (mergedOptions.projectKey) { queryParts.push(`project.key = "${mergedOptions.projectKey}"`); } const combinedQuery = queryParts.join(' AND '); // Combine with AND // Map controller options to service parameters const serviceParams = { // Required workspace workspace: workspaceSlug, // Handle limit with default value pagelen: mergedOptions.limit, // Map cursor to page for page-based pagination page: mergedOptions.cursor ? parseInt(mergedOptions.cursor, 10) : undefined, // Set default sort to updated_on descending if not specified sort: mergedOptions.sort, // Optional filter parameters ...(combinedQuery && { q: combinedQuery }), // <-- Use combined query ...(mergedOptions.role && { role: mergedOptions.role }), }; methodLogger.debug('Using service parameters:', serviceParams); const repositoriesData = await vendor_atlassian_repositories_service_js_1.default.list(serviceParams); // Log only the count of repositories returned instead of the entire response methodLogger.debug(`Retrieved ${repositoriesData.values?.length || 0} repositories`); // Post-filter by project key if provided and Bitbucket API returned extra results if (mergedOptions.projectKey && repositoriesData.values) { const originalCount = repositoriesData.values.length; // Only keep repositories with exact project key match // NOTE: This filtering is done client-side since Bitbucket API doesn't directly support // filtering by project key in its query parameters. This means all repositories are first // fetched and then filtered locally, which may result in fewer results than expected // if the limit parameter is also used. repositoriesData.values = repositoriesData.values.filter((repo) => repo.project?.key === mergedOptions.projectKey); const filteredCount = repositoriesData.values.length; // Log filtering results to help with debugging if (filteredCount !== originalCount) { methodLogger.debug(`Post-filtered repositories by projectKey=${mergedOptions.projectKey}: ${filteredCount} of ${originalCount} matched.`); // Adjust the size to reflect the actual filtered count (matters for pagination) if (repositoriesData.size) { // Adjust total size proportionally based on how many were filtered out const filterRatio = filteredCount / originalCount; const estimatedTotalSize = Math.ceil(repositoriesData.size * filterRatio); repositoriesData.size = Math.max(filteredCount, estimatedTotalSize); methodLogger.debug(`Adjusted size from ${repositoriesData.size} to ${estimatedTotalSize} based on filtering ratio`); } // If this is the first page and we have fewer results than requested, try to fetch more if (filteredCount < (serviceParams.pagelen || defaults_util_js_1.DEFAULT_PAGE_SIZE) && repositoriesData.next) { methodLogger.debug(`After filtering, only ${filteredCount} items remain. Fetching more pages to supplement...`); // Keep fetching next pages until we have enough items or no more pages let nextPageUrl = repositoriesData.next; let totalItemsNeeded = (serviceParams.pagelen || defaults_util_js_1.DEFAULT_PAGE_SIZE) - filteredCount; while (nextPageUrl && totalItemsNeeded > 0) { try { // Extract the next page number let nextPage; try { const nextUrl = new URL(nextPageUrl); const pageParam = nextUrl.searchParams.get('page'); if (pageParam) { nextPage = parseInt(pageParam, 10); } } catch (e) { methodLogger.warn(`Could not extract next page from URL: ${nextPageUrl}`, e); break; } if (!nextPage) break; // Fetch the next page const nextPageParams = { ...serviceParams, page: nextPage, }; const nextPageData = await vendor_atlassian_repositories_service_js_1.default.list(nextPageParams); // Filter the next page results if (nextPageData.values) { const nextPageFiltered = nextPageData.values.filter((repo) => repo.project?.key === mergedOptions.projectKey); // Add items to reach the requested limit const itemsToAdd = nextPageFiltered.slice(0, totalItemsNeeded); if (itemsToAdd.length > 0) { repositoriesData.values = [ ...repositoriesData.values, ...itemsToAdd, ]; totalItemsNeeded -= itemsToAdd.length; methodLogger.debug(`Added ${itemsToAdd.length} items from page ${nextPage} to reach requested limit. ${totalItemsNeeded} more needed.`); } // Update next page URL for the loop nextPageUrl = nextPageData.next || undefined; // If we've fetched all filtered items from this page but there are more pages // and we still need more items, continue to the next page if (nextPageFiltered.length <= itemsToAdd.length && totalItemsNeeded > 0) { continue; } // If we got all the items we need, update pagination accordingly if (totalItemsNeeded <= 0) { // We have enough items now, but there are more available if (nextPageData.next) { repositoriesData.next = nextPageData.next; } break; } } else { // No values in the response, stop fetching nextPageUrl = undefined; } } catch (fetchError) { // Log the error but continue with what we have methodLogger.warn(`Error fetching page to supplement filtered results:`, fetchError); break; } } } } } // Extract pagination information using the utility const pagination = (0, pagination_util_js_1.extractPaginationInfo)(repositoriesData, pagination_util_js_1.PaginationType.PAGE); // Format the repositories data for display using the formatter const formattedRepositories = (0, atlassian_repositories_formatter_js_1.formatRepositoriesList)(repositoriesData); // Create the final content by combining the formatted repositories with pagination information let finalContent = formattedRepositories; // Add pagination information if available if (pagination && (pagination.hasMore || pagination.count !== undefined)) { const paginationString = (0, formatter_util_js_1.formatPagination)(pagination); finalContent += '\n\n' + paginationString; } return { content: finalContent, }; } catch (error) { // Use the standardized error handler throw (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Repositories', operation: 'listing', source: 'controllers/atlassian.repositories.list.controller.ts@handleRepositoriesList', additionalInfo: { options }, }); } }