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

133 lines (132 loc) 6.71 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.handleCodeSearch = handleCodeSearch; const logger_util_js_1 = require("../utils/logger.util.js"); const defaults_util_js_1 = require("../utils/defaults.util.js"); const vendor_atlassian_search_service_js_1 = __importDefault(require("../services/vendor.atlassian.search.service.js")); const pagination_util_js_1 = require("../utils/pagination.util.js"); const formatter_util_js_1 = require("../utils/formatter.util.js"); const atlassian_search_formatter_js_1 = require("./atlassian.search.formatter.js"); /** * Handle search for code content (uses Bitbucket's Code Search API) */ async function handleCodeSearch(workspaceSlug, repoSlug, query, limit = defaults_util_js_1.DEFAULT_PAGE_SIZE, cursor, language, extension) { const methodLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.search.code.controller.ts', 'handleCodeSearch'); methodLogger.debug('Performing code search'); if (!query) { return { content: 'Please provide a search query for code search.', }; } try { // Convert cursor to page number if provided let page = 1; if (cursor) { const parsedPage = parseInt(cursor, 10); if (!isNaN(parsedPage)) { page = parsedPage; } else { methodLogger.warn('Invalid page cursor:', cursor); } } // Use the search service const searchResponse = await vendor_atlassian_search_service_js_1.default.searchCode({ workspaceSlug: workspaceSlug, searchQuery: query, repoSlug: repoSlug, page: page, pageLen: limit, language: language, extension: extension, }); methodLogger.debug(`Search complete, found ${searchResponse.size} matches`); // Post-filter by language if specified and Bitbucket API returned mixed results let filteredValues = searchResponse.values || []; let originalSize = searchResponse.size; if (language && filteredValues.length > 0) { // Language extension mapping for post-filtering const languageExtMap = { hcl: ['.tf', '.tfvars', '.hcl'], terraform: ['.tf', '.tfvars', '.hcl'], java: ['.java', '.class', '.jar'], javascript: ['.js', '.jsx', '.mjs'], typescript: ['.ts', '.tsx'], python: ['.py', '.pyw', '.pyc'], ruby: ['.rb', '.rake'], go: ['.go'], rust: ['.rs'], c: ['.c', '.h'], cpp: ['.cpp', '.cc', '.cxx', '.h', '.hpp'], csharp: ['.cs'], php: ['.php'], html: ['.html', '.htm'], css: ['.css'], shell: ['.sh', '.bash', '.zsh'], sql: ['.sql'], yaml: ['.yml', '.yaml'], json: ['.json'], xml: ['.xml'], markdown: ['.md', '.markdown'], }; // Normalize the language name to lowercase const normalizedLang = language.toLowerCase(); const extensions = languageExtMap[normalizedLang] || []; // Only apply post-filtering if we have extension mappings for this language if (extensions.length > 0) { const beforeFilterCount = filteredValues.length; // Filter results to only include files with the expected extensions filteredValues = filteredValues.filter((result) => { const filePath = result.file.path.toLowerCase(); return extensions.some((ext) => filePath.endsWith(ext)); }); const afterFilterCount = filteredValues.length; if (afterFilterCount !== beforeFilterCount) { methodLogger.debug(`Post-filtered code search results by language=${language}: ${afterFilterCount} of ${beforeFilterCount} matched extensions ${extensions.join(', ')}`); // Adjust the size estimate originalSize = searchResponse.size; const filterRatio = afterFilterCount / beforeFilterCount; searchResponse.size = Math.max(afterFilterCount, Math.ceil(searchResponse.size * filterRatio)); methodLogger.debug(`Adjusted size from ${originalSize} to ${searchResponse.size} based on filtering`); } } } // Extract pagination information const transformedResponse = { pagelen: limit, page: page, size: searchResponse.size, values: filteredValues, next: 'available', // Fallback to 'available' since searchResponse doesn't have a next property }; const pagination = (0, pagination_util_js_1.extractPaginationInfo)(transformedResponse, pagination_util_js_1.PaginationType.PAGE); // Format the code search results let formattedCode = (0, atlassian_search_formatter_js_1.formatCodeSearchResults)({ ...searchResponse, values: filteredValues, }); // Add note about language filtering if applied if (language) { // Make it clear that language filtering is a best-effort by the API and we've improved it const languageNote = `> **Note:** Language filtering for '${language}' combines Bitbucket API filtering with client-side filtering for more accurate results. Due to limitations in the Bitbucket API, some files in other languages might still appear in search results, and filtering is based on file extensions rather than content analysis. This is a known limitation of the Bitbucket API that this tool attempts to mitigate through additional filtering.`; formattedCode = `${languageNote}\n\n${formattedCode}`; } // Add pagination information if available let finalContent = formattedCode; if (pagination && (pagination.hasMore || pagination.count !== undefined)) { const paginationString = (0, formatter_util_js_1.formatPagination)(pagination); finalContent += '\n\n' + paginationString; } return { content: finalContent, }; } catch (searchError) { methodLogger.error('Error performing code search:', searchError); throw searchError; } }