@zhanglc77/bitbucket-mcp-server
Version:
MCP server for Bitbucket API integration - supports both Cloud and Server
151 lines • 6.22 kB
JavaScript
import { formatCodeSearchOutput } from '../utils/formatters.js';
function buildContextualPatterns(searchTerm) {
return {
assignment: [
`${searchTerm} =`, // Variable assignment
`${searchTerm}:`, // Object property, JSON key
`= ${searchTerm}`, // Right-hand assignment
],
declaration: [
`${searchTerm} =`, // Variable definition
`${searchTerm}:`, // Object key, parameter definition
`function ${searchTerm}`, // Function declaration
`class ${searchTerm}`, // Class declaration
`interface ${searchTerm}`, // Interface declaration
`const ${searchTerm}`, // Const declaration
`let ${searchTerm}`, // Let declaration
`var ${searchTerm}`, // Var declaration
],
usage: [
`.${searchTerm}`, // Property/method access
`${searchTerm}(`, // Function call
`${searchTerm}.`, // Method chaining
`${searchTerm}[`, // Array/object access
`(${searchTerm}`, // Parameter usage
],
exact: [
`"${searchTerm}"`, // Exact quoted match
],
any: [
`"${searchTerm}"`, // Exact match
`${searchTerm} =`, // Assignment
`${searchTerm}:`, // Object property
`.${searchTerm}`, // Property access
`${searchTerm}(`, // Function call
`function ${searchTerm}`, // Function definition
`class ${searchTerm}`, // Class definition
]
};
}
function buildSmartQuery(searchTerm, searchContext = 'any', includePatterns = []) {
const contextPatterns = buildContextualPatterns(searchTerm);
let patterns = [];
// Add patterns based on context
if (searchContext in contextPatterns) {
patterns = [...contextPatterns[searchContext]];
}
else {
patterns = [...contextPatterns.any];
}
// Add user-provided patterns
if (includePatterns && includePatterns.length > 0) {
patterns = [...patterns, ...includePatterns];
}
// Remove duplicates and join with OR
const uniquePatterns = [...new Set(patterns)];
// If only one pattern, return it without parentheses
if (uniquePatterns.length === 1) {
return uniquePatterns[0];
}
// Wrap each pattern in quotes for safety and join with OR
const quotedPatterns = uniquePatterns.map(pattern => `"${pattern}"`);
return `(${quotedPatterns.join(' OR ')})`;
}
export class SearchHandlers {
apiClient;
baseUrl;
constructor(apiClient, baseUrl) {
this.apiClient = apiClient;
this.baseUrl = baseUrl;
}
async handleSearchCode(args) {
try {
const { workspace, repository, search_query, search_context = 'any', file_pattern, include_patterns = [], limit = 25, start = 0 } = args;
if (!workspace || !search_query) {
throw new Error('Workspace and search_query are required');
}
// Only works for Bitbucket Server currently
if (!this.apiClient.getIsServer()) {
throw new Error('Code search is currently only supported for Bitbucket Server');
}
// Build the enhanced query string
let query = `project:${workspace}`;
if (repository) {
query += ` repo:${repository}`;
}
if (file_pattern) {
query += ` path:${file_pattern}`;
}
// Build smart search patterns
const smartQuery = buildSmartQuery(search_query, search_context, include_patterns);
query += ` ${smartQuery}`;
// Prepare the request payload
const payload = {
query: query.trim(),
entities: {
code: {
start: start,
limit: limit
}
}
};
// Make the API request (no query params needed, pagination is in payload)
const response = await this.apiClient.makeRequest('post', `/rest/search/latest/search?avatarSize=64`, payload);
const searchResult = response;
// Use simplified formatter for cleaner output
const simplifiedOutput = formatCodeSearchOutput(searchResult);
// Prepare pagination info
const hasMore = searchResult.code?.isLastPage === false;
const nextStart = hasMore ? (searchResult.code?.nextStart || start + limit) : undefined;
const totalCount = searchResult.code?.count || 0;
// Build a concise response with search context info
let resultText = `Code search results for "${search_query}"`;
if (search_context !== 'any') {
resultText += ` (context: ${search_context})`;
}
resultText += ` in ${workspace}`;
if (repository) {
resultText += `/${repository}`;
}
// Show the actual search query used
resultText += `\n\nSearch query: ${query.trim()}`;
resultText += `\n\n${simplifiedOutput}`;
if (totalCount > 0) {
resultText += `\n\nTotal matches: ${totalCount}`;
if (hasMore) {
resultText += ` (showing ${start + 1}-${start + (searchResult.code?.values?.length || 0)})`;
}
}
return {
content: [{
type: 'text',
text: resultText
}]
};
}
catch (error) {
const errorMessage = error.response?.data?.errors?.[0]?.message || error.message;
return {
content: [{
type: 'text',
text: JSON.stringify({
error: `Failed to search code: ${errorMessage}`,
details: error.response?.data
}, null, 2)
}],
isError: true
};
}
}
}
//# sourceMappingURL=search-handlers.js.map