@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
JavaScript
;
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 },
});
}
}