Skip to main content
Glama
formatters.ts8.09 kB
import { BitbucketServerPullRequest, BitbucketCloudPullRequest, MergeInfo, BitbucketServerCommit, BitbucketCloudCommit, FormattedCommit, BitbucketServerSearchResult, FormattedSearchResult } from '../types/bitbucket.js'; export function formatServerResponse( pr: BitbucketServerPullRequest, mergeInfo?: MergeInfo, baseUrl?: string ): any { const webUrl = `${baseUrl}/projects/${pr.toRef.repository.project.key}/repos/${pr.toRef.repository.slug}/pull-requests/${pr.id}`; return { id: pr.id, title: pr.title, description: pr.description || 'No description provided', state: pr.state, is_open: pr.open, is_closed: pr.closed, author: pr.author.user.displayName, author_username: pr.author.user.name, author_email: pr.author.user.emailAddress, source_branch: pr.fromRef.displayId, destination_branch: pr.toRef.displayId, source_commit: pr.fromRef.latestCommit, destination_commit: pr.toRef.latestCommit, reviewers: pr.reviewers.map(r => ({ name: r.user.displayName, approved: r.approved, status: r.status, })), participants: pr.participants.map(p => ({ name: p.user.displayName, role: p.role, approved: p.approved, status: p.status, })), created_on: new Date(pr.createdDate).toLocaleString(), updated_on: new Date(pr.updatedDate).toLocaleString(), web_url: webUrl, api_url: pr.links.self[0]?.href || '', is_locked: pr.locked, // Add merge commit details is_merged: pr.state === 'MERGED', merge_commit_hash: mergeInfo?.mergeCommitHash || pr.properties?.mergeCommit?.id || null, merged_by: mergeInfo?.mergedBy || null, merged_at: mergeInfo?.mergedAt || null, merge_commit_message: mergeInfo?.mergeCommitMessage || null, }; } export function formatCloudResponse(pr: BitbucketCloudPullRequest): any { return { id: pr.id, title: pr.title, description: pr.description || 'No description provided', state: pr.state, author: pr.author.display_name, source_branch: pr.source.branch.name, destination_branch: pr.destination.branch.name, reviewers: pr.reviewers.map(r => r.display_name), participants: pr.participants.map(p => ({ name: p.user.display_name, role: p.role, approved: p.approved, })), created_on: new Date(pr.created_on).toLocaleString(), updated_on: new Date(pr.updated_on).toLocaleString(), web_url: pr.links.html.href, api_url: pr.links.self.href, diff_url: pr.links.diff.href, is_merged: pr.state === 'MERGED', merge_commit_hash: pr.merge_commit?.hash || null, merged_by: pr.closed_by?.display_name || null, merged_at: pr.state === 'MERGED' ? pr.updated_on : null, merge_commit_message: null, // Would need additional API call to get this close_source_branch: pr.close_source_branch, }; } export function formatServerCommit(commit: BitbucketServerCommit): FormattedCommit { return { hash: commit.id, abbreviated_hash: commit.displayId, message: commit.message, author: { name: commit.author.name, email: commit.author.emailAddress, }, date: new Date(commit.authorTimestamp).toISOString(), parents: commit.parents.map(p => p.id), is_merge_commit: commit.parents.length > 1, }; } export function formatCloudCommit(commit: BitbucketCloudCommit): FormattedCommit { // Parse the author raw string which is in format "Name <email>" const authorMatch = commit.author.raw.match(/^(.+?)\s*<(.+?)>$/); const authorName = authorMatch ? authorMatch[1] : (commit.author.user?.display_name || commit.author.raw); const authorEmail = authorMatch ? authorMatch[2] : ''; return { hash: commit.hash, abbreviated_hash: commit.hash.substring(0, 7), message: commit.message, author: { name: authorName, email: authorEmail, }, date: commit.date, parents: commit.parents.map(p => p.hash), is_merge_commit: commit.parents.length > 1, }; } export function formatSearchResults(searchResult: BitbucketServerSearchResult): FormattedSearchResult[] { const results: FormattedSearchResult[] = []; if (!searchResult.code?.values) { return results; } for (const value of searchResult.code.values) { // Extract file name from path const fileName = value.file.split('/').pop() || value.file; const formattedResult: FormattedSearchResult = { file_path: value.file, file_name: fileName, repository: value.repository.slug, project: value.repository.project.key, matches: [] }; // Process hitContexts (array of arrays of line contexts) if (value.hitContexts && value.hitContexts.length > 0) { for (const contextGroup of value.hitContexts) { for (const lineContext of contextGroup) { // Parse HTML to extract text and highlight information const { text, segments } = parseHighlightedText(lineContext.text); formattedResult.matches.push({ line_number: lineContext.line, line_content: text, highlighted_segments: segments }); } } } results.push(formattedResult); } return results; } // Helper function to parse HTML-formatted text with <em> tags function parseHighlightedText(htmlText: string): { text: string; segments: Array<{ text: string; is_match: boolean }>; } { // Decode HTML entities const decodedText = htmlText .replace(/&quot;/g, '"') .replace(/&lt;/g, '<') .replace(/&gt;/g, '>') .replace(/&amp;/g, '&') .replace(/&#x2F;/g, '/'); // Remove HTML tags and track highlighted segments const segments: Array<{ text: string; is_match: boolean }> = []; let plainText = ''; let currentPos = 0; // Match all <em> tags and their content const emRegex = /<em>(.*?)<\/em>/g; let lastEnd = 0; let match; while ((match = emRegex.exec(decodedText)) !== null) { // Add non-highlighted text before this match if (match.index > lastEnd) { const beforeText = decodedText.substring(lastEnd, match.index); segments.push({ text: beforeText, is_match: false }); plainText += beforeText; } // Add highlighted text const highlightedText = match[1]; segments.push({ text: highlightedText, is_match: true }); plainText += highlightedText; lastEnd = match.index + match[0].length; } // Add any remaining non-highlighted text if (lastEnd < decodedText.length) { const remainingText = decodedText.substring(lastEnd); segments.push({ text: remainingText, is_match: false }); plainText += remainingText; } // If no <em> tags were found, the entire text is non-highlighted if (segments.length === 0) { segments.push({ text: decodedText, is_match: false }); plainText = decodedText; } return { text: plainText, segments }; } // Simplified formatter for MCP tool output export function formatCodeSearchOutput(searchResult: BitbucketServerSearchResult): string { if (!searchResult.code?.values || searchResult.code.values.length === 0) { return 'No results found'; } const outputLines: string[] = []; for (const value of searchResult.code.values) { outputLines.push(`File: ${value.file}`); // Process all hit contexts if (value.hitContexts && value.hitContexts.length > 0) { for (const contextGroup of value.hitContexts) { for (const lineContext of contextGroup) { // Remove HTML tags and decode entities const cleanText = lineContext.text .replace(/<em>/g, '') .replace(/<\/em>/g, '') .replace(/&quot;/g, '"') .replace(/&lt;/g, '<') .replace(/&gt;/g, '>') .replace(/&amp;/g, '&') .replace(/&#x2F;/g, '/') .replace(/&#x27;/g, "'"); outputLines.push(` Line ${lineContext.line}: ${cleanText}`); } } } outputLines.push(''); // Empty line between files } return outputLines.join('\n').trim(); }

Latest Blog Posts

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/pdogra1299/bitbucket-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server