#
tokens: 14447/50000 2/30 files (page 2/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 2. Use http://codebase.md/pdogra1299/bitbucket-mcp-server?page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── memory-bank
│   ├── .clinerules
│   ├── activeContext.yml
│   ├── productContext.yml
│   ├── progress.yml
│   ├── projectbrief.yml
│   ├── systemPatterns.yml
│   └── techContext.yml
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   └── setup-auth.js
├── SETUP_GUIDE_SERVER.md
├── SETUP_GUIDE.md
├── src
│   ├── handlers
│   │   ├── branch-handlers.ts
│   │   ├── file-handlers.ts
│   │   ├── project-handlers.ts
│   │   ├── pull-request-handlers.ts
│   │   ├── review-handlers.ts
│   │   └── search-handlers.ts
│   ├── index.ts
│   ├── tools
│   │   └── definitions.ts
│   ├── types
│   │   ├── bitbucket.ts
│   │   └── guards.ts
│   └── utils
│       ├── api-client.ts
│       ├── diff-parser.ts
│       ├── formatters.ts
│       └── suggestion-formatter.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/src/tools/definitions.ts:
--------------------------------------------------------------------------------

```typescript
export const toolDefinitions = [
  {
    name: 'get_pull_request',
    description: 'Get details of a Bitbucket pull request including merge commit information',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        pull_request_id: {
          type: 'number',
          description: 'Pull request ID',
        },
      },
      required: ['workspace', 'repository', 'pull_request_id'],
    },
  },
  {
    name: 'list_pull_requests',
    description: 'List pull requests for a repository with optional filters',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        state: {
          type: 'string',
          description: 'Filter by PR state: OPEN, MERGED, DECLINED, ALL (default: OPEN)',
          enum: ['OPEN', 'MERGED', 'DECLINED', 'ALL'],
        },
        author: {
          type: 'string',
          description: 'Filter by author username',
        },
        limit: {
          type: 'number',
          description: 'Maximum number of PRs to return (default: 25)',
        },
        start: {
          type: 'number',
          description: 'Start index for pagination (default: 0)',
        },
      },
      required: ['workspace', 'repository'],
    },
  },
  {
    name: 'create_pull_request',
    description: 'Create a new pull request',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        title: {
          type: 'string',
          description: 'Title of the pull request',
        },
        source_branch: {
          type: 'string',
          description: 'Source branch name',
        },
        destination_branch: {
          type: 'string',
          description: 'Destination branch name (e.g., "main", "master")',
        },
        description: {
          type: 'string',
          description: 'Description of the pull request (optional)',
        },
        reviewers: {
          type: 'array',
          items: { type: 'string' },
          description: 'Array of reviewer usernames/emails (optional)',
        },
        close_source_branch: {
          type: 'boolean',
          description: 'Whether to close source branch after merge (optional, default: false)',
        },
      },
      required: ['workspace', 'repository', 'title', 'source_branch', 'destination_branch'],
    },
  },
  {
    name: 'update_pull_request',
    description: 'Update an existing pull request. When updating without specifying reviewers, existing reviewers and their approval status will be preserved.',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        pull_request_id: {
          type: 'number',
          description: 'Pull request ID',
        },
        title: {
          type: 'string',
          description: 'New title (optional)',
        },
        description: {
          type: 'string',
          description: 'New description (optional)',
        },
        destination_branch: {
          type: 'string',
          description: 'New destination branch (optional)',
        },
        reviewers: {
          type: 'array',
          items: { type: 'string' },
          description: 'New list of reviewer usernames/emails. If provided, replaces the reviewer list (preserving approval status for existing reviewers). If omitted, existing reviewers are preserved. (optional)',
        },
      },
      required: ['workspace', 'repository', 'pull_request_id'],
    },
  },
  {
    name: 'add_comment',
    description: 'Add a comment to a pull request. Supports: 1) General PR comments, 2) Replies to existing comments, 3) Inline comments on specific code lines (using line_number OR code_snippet), 4) Code suggestions for single or multi-line replacements. For inline comments, you can either provide exact line_number or use code_snippet to auto-detect the line.',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        pull_request_id: {
          type: 'number',
          description: 'Pull request ID',
        },
        comment_text: {
          type: 'string',
          description: 'The main comment text. For suggestions, this is the explanation before the code suggestion.',
        },
        parent_comment_id: {
          type: 'number',
          description: 'ID of comment to reply to. Use this to create threaded conversations (optional)',
        },
        file_path: {
          type: 'string',
          description: 'File path for inline comment. Required for inline comments. Example: "src/components/Button.js" (optional)',
        },
        line_number: {
          type: 'number',
          description: 'Exact line number in the file. Use this OR code_snippet, not both. Required with file_path unless using code_snippet (optional)',
        },
        line_type: {
          type: 'string',
          description: 'Type of line: ADDED (green/new lines), REMOVED (red/deleted lines), or CONTEXT (unchanged lines). Default: CONTEXT',
          enum: ['ADDED', 'REMOVED', 'CONTEXT'],
        },
        suggestion: {
          type: 'string',
          description: 'Replacement code for a suggestion. Creates a suggestion block that can be applied in Bitbucket UI. Requires file_path and line_number. For multi-line, include newlines in the string (optional)',
        },
        suggestion_end_line: {
          type: 'number',
          description: 'For multi-line suggestions: the last line number to replace. If not provided, only replaces the single line at line_number (optional)',
        },
        code_snippet: {
          type: 'string',
          description: 'Exact code text from the diff to find and comment on. Use this instead of line_number for auto-detection. Must match exactly including whitespace (optional)',
        },
        search_context: {
          type: 'object',
          properties: {
            before: {
              type: 'array',
              items: { type: 'string' },
              description: 'Array of code lines that appear BEFORE the target line. Helps disambiguate when code_snippet appears multiple times',
            },
            after: {
              type: 'array',
              items: { type: 'string' },
              description: 'Array of code lines that appear AFTER the target line. Helps disambiguate when code_snippet appears multiple times',
            },
          },
          description: 'Additional context lines to help locate the exact position when using code_snippet. Useful when the same code appears multiple times (optional)',
        },
        match_strategy: {
          type: 'string',
          enum: ['strict', 'best'],
          description: 'How to handle multiple matches when using code_snippet. "strict": fail with detailed error showing all matches. "best": automatically pick the highest confidence match. Default: "strict"',
        },
      },
      required: ['workspace', 'repository', 'pull_request_id', 'comment_text'],
    },
  },
  {
    name: 'merge_pull_request',
    description: 'Merge a pull request',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        pull_request_id: {
          type: 'number',
          description: 'Pull request ID',
        },
        merge_strategy: {
          type: 'string',
          description: 'Merge strategy: merge-commit, squash, fast-forward (optional)',
          enum: ['merge-commit', 'squash', 'fast-forward'],
        },
        close_source_branch: {
          type: 'boolean',
          description: 'Whether to close source branch after merge (optional)',
        },
        commit_message: {
          type: 'string',
          description: 'Custom merge commit message (optional)',
        },
      },
      required: ['workspace', 'repository', 'pull_request_id'],
    },
  },
  {
    name: 'list_branches',
    description: 'List branches in a repository',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        filter: {
          type: 'string',
          description: 'Filter branches by name pattern (optional)',
        },
        limit: {
          type: 'number',
          description: 'Maximum number of branches to return (default: 25)',
        },
        start: {
          type: 'number',
          description: 'Start index for pagination (default: 0)',
        },
      },
      required: ['workspace', 'repository'],
    },
  },
  {
    name: 'delete_branch',
    description: 'Delete a branch',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        branch_name: {
          type: 'string',
          description: 'Branch name to delete',
        },
        force: {
          type: 'boolean',
          description: 'Force delete even if branch is not merged (optional, default: false)',
        },
      },
      required: ['workspace', 'repository', 'branch_name'],
    },
  },
  {
    name: 'get_pull_request_diff',
    description: 'Get the diff/changes for a pull request with optional filtering',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        pull_request_id: {
          type: 'number',
          description: 'Pull request ID',
        },
        context_lines: {
          type: 'number',
          description: 'Number of context lines around changes (optional, default: 3)',
        },
        include_patterns: {
          type: 'array',
          items: { type: 'string' },
          description: 'Array of glob patterns to include (e.g., ["*.res", "src/**/*.js"]) (optional)',
        },
        exclude_patterns: {
          type: 'array',
          items: { type: 'string' },
          description: 'Array of glob patterns to exclude (e.g., ["*.lock", "*.svg"]) (optional)',
        },
        file_path: {
          type: 'string',
          description: 'Specific file path to get diff for (e.g., "src/index.ts") (optional)',
        },
      },
      required: ['workspace', 'repository', 'pull_request_id'],
    },
  },
  {
    name: 'approve_pull_request',
    description: 'Approve a pull request',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        pull_request_id: {
          type: 'number',
          description: 'Pull request ID',
        },
      },
      required: ['workspace', 'repository', 'pull_request_id'],
    },
  },
  {
    name: 'unapprove_pull_request',
    description: 'Remove approval from a pull request',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        pull_request_id: {
          type: 'number',
          description: 'Pull request ID',
        },
      },
      required: ['workspace', 'repository', 'pull_request_id'],
    },
  },
  {
    name: 'request_changes',
    description: 'Request changes on a pull request',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        pull_request_id: {
          type: 'number',
          description: 'Pull request ID',
        },
        comment: {
          type: 'string',
          description: 'Comment explaining requested changes (optional)',
        },
      },
      required: ['workspace', 'repository', 'pull_request_id'],
    },
  },
  {
    name: 'remove_requested_changes',
    description: 'Remove change request from a pull request',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        pull_request_id: {
          type: 'number',
          description: 'Pull request ID',
        },
      },
      required: ['workspace', 'repository', 'pull_request_id'],
    },
  },
  {
    name: 'get_branch',
    description: 'Get detailed information about a branch including associated pull requests',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        branch_name: {
          type: 'string',
          description: 'Branch name to get details for',
        },
        include_merged_prs: {
          type: 'boolean',
          description: 'Include merged PRs from this branch (default: false)',
        },
      },
      required: ['workspace', 'repository', 'branch_name'],
    },
  },
  {
    name: 'list_directory_content',
    description: 'List files and directories in a repository path',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        path: {
          type: 'string',
          description: 'Directory path (optional, defaults to root, e.g., "src/components")',
        },
        branch: {
          type: 'string',
          description: 'Branch name (optional, defaults to default branch)',
        },
      },
      required: ['workspace', 'repository'],
    },
  },
  {
    name: 'get_file_content',
    description: 'Get file content from a repository with smart truncation for large files',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        file_path: {
          type: 'string',
          description: 'Path to the file (e.g., "src/index.ts")',
        },
        branch: {
          type: 'string',
          description: 'Branch name (optional, defaults to default branch)',
        },
        start_line: {
          type: 'number',
          description: 'Starting line number (1-based). Use negative for lines from end (optional)',
        },
        line_count: {
          type: 'number',
          description: 'Number of lines to return (optional, default varies by file size)',
        },
        full_content: {
          type: 'boolean',
          description: 'Force return full content regardless of size (optional, default: false)',
        },
      },
      required: ['workspace', 'repository', 'file_path'],
    },
  },
  {
    name: 'list_branch_commits',
    description: 'List commits in a branch with detailed information and filtering options',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        branch_name: {
          type: 'string',
          description: 'Branch name to get commits from',
        },
        limit: {
          type: 'number',
          description: 'Maximum number of commits to return (default: 25)',
        },
        start: {
          type: 'number',
          description: 'Start index for pagination (default: 0)',
        },
        since: {
          type: 'string',
          description: 'ISO date string - only show commits after this date (optional)',
        },
        until: {
          type: 'string',
          description: 'ISO date string - only show commits before this date (optional)',
        },
        author: {
          type: 'string',
          description: 'Filter by author email/username (optional)',
        },
        include_merge_commits: {
          type: 'boolean',
          description: 'Include merge commits in results (default: true)',
        },
        search: {
          type: 'string',
          description: 'Search for text in commit messages (optional)',
        },
        include_build_status: {
          type: 'boolean',
          description: 'Include CI/CD build status for each commit (Bitbucket Server only, default: false)',
        },
      },
      required: ['workspace', 'repository', 'branch_name'],
    },
  },
  {
    name: 'list_pr_commits',
    description: 'List all commits that are part of a pull request',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug (e.g., "my-repo")',
        },
        pull_request_id: {
          type: 'number',
          description: 'Pull request ID',
        },
        limit: {
          type: 'number',
          description: 'Maximum number of commits to return (default: 25)',
        },
        start: {
          type: 'number',
          description: 'Start index for pagination (default: 0)',
        },
        include_build_status: {
          type: 'boolean',
          description: 'Include CI/CD build status for each commit (Bitbucket Server only, default: false)',
        },
      },
      required: ['workspace', 'repository', 'pull_request_id'],
    },
  },
  {
    name: 'search_code',
    description: 'Search for code across Bitbucket repositories with enhanced context-aware search patterns (currently only supported for Bitbucket Server)',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key (e.g., "PROJ")',
        },
        repository: {
          type: 'string',
          description: 'Repository slug to search in (optional, searches all repos if not specified)',
        },
        search_query: {
          type: 'string',
          description: 'The search term or phrase to look for in code (e.g., "variable")',
        },
        search_context: {
          type: 'string',
          enum: ['assignment', 'declaration', 'usage', 'exact', 'any'],
          description: 'Context to search for: assignment (term=value), declaration (defining term), usage (calling/accessing term), exact (quoted match), or any (all patterns)',
        },
        file_pattern: {
          type: 'string',
          description: 'File path pattern to filter results (e.g., "*.java", "src/**/*.ts") (optional)',
        },
        include_patterns: {
          type: 'array',
          items: { type: 'string' },
          description: 'Additional custom search patterns to include (e.g., ["variable =", ".variable"]) (optional)',
        },
        limit: {
          type: 'number',
          description: 'Maximum number of results to return (default: 25)',
        },
        start: {
          type: 'number',
          description: 'Start index for pagination (default: 0)',
        },
      },
      required: ['workspace', 'search_query'],
    },
  },
  {
    name: 'list_projects',
    description: 'List all accessible Bitbucket projects with optional filtering',
    inputSchema: {
      type: 'object',
      properties: {
        name: {
          type: 'string',
          description: 'Filter by project name (partial match, optional)',
        },
        permission: {
          type: 'string',
          description: 'Filter by permission level (e.g., PROJECT_READ, PROJECT_WRITE, PROJECT_ADMIN, optional)',
        },
        limit: {
          type: 'number',
          description: 'Maximum number of projects to return (default: 25)',
        },
        start: {
          type: 'number',
          description: 'Start index for pagination (default: 0)',
        },
      },
      required: [],
    },
  },
  {
    name: 'list_repositories',
    description: 'List repositories in a project or across all accessible projects',
    inputSchema: {
      type: 'object',
      properties: {
        workspace: {
          type: 'string',
          description: 'Bitbucket workspace/project key to filter repositories (optional, if not provided lists all accessible repos)',
        },
        name: {
          type: 'string',
          description: 'Filter by repository name (partial match, optional)',
        },
        permission: {
          type: 'string',
          description: 'Filter by permission level (e.g., REPO_READ, REPO_WRITE, REPO_ADMIN, optional)',
        },
        limit: {
          type: 'number',
          description: 'Maximum number of repositories to return (default: 25)',
        },
        start: {
          type: 'number',
          description: 'Start index for pagination (default: 0)',
        },
      },
      required: [],
    },
  },
];

```

--------------------------------------------------------------------------------
/src/handlers/pull-request-handlers.ts:
--------------------------------------------------------------------------------

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { BitbucketApiClient } from '../utils/api-client.js';
import { formatServerResponse, formatCloudResponse, formatServerCommit, formatCloudCommit } from '../utils/formatters.js';
import { formatSuggestionComment } from '../utils/suggestion-formatter.js';
import { DiffParser } from '../utils/diff-parser.js';
import { 
  BitbucketServerPullRequest, 
  BitbucketCloudPullRequest, 
  BitbucketServerActivity,
  MergeInfo,
  BitbucketCloudComment,
  BitbucketCloudFileChange,
  FormattedComment,
  FormattedFileChange,
  CodeMatch,
  MultipleMatchesError,
  BitbucketServerCommit,
  BitbucketCloudCommit,
  FormattedCommit
} from '../types/bitbucket.js';
import {
  isGetPullRequestArgs,
  isListPullRequestsArgs,
  isCreatePullRequestArgs,
  isUpdatePullRequestArgs,
  isAddCommentArgs,
  isMergePullRequestArgs,
  isListPrCommitsArgs
} from '../types/guards.js';

export class PullRequestHandlers {
  constructor(
    private apiClient: BitbucketApiClient,
    private baseUrl: string,
    private username: string
  ) {}

  private async getFilteredPullRequestDiff(
    workspace: string,
    repository: string,
    pullRequestId: number,
    filePath: string,
    contextLines: number = 3
  ): Promise<string> {
    let apiPath: string;
    let config: any = {};

    if (this.apiClient.getIsServer()) {
      // Bitbucket Server API
      apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pullRequestId}/diff`;
      config.params = { contextLines };
    } else {
      // Bitbucket Cloud API
      apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pullRequestId}/diff`;
      config.params = { context: contextLines };
    }

    config.headers = { 'Accept': 'text/plain' };
    
    const rawDiff = await this.apiClient.makeRequest<string>('get', apiPath, undefined, config);

    const diffParser = new DiffParser();
    const sections = diffParser.parseDiffIntoSections(rawDiff);
    
    const filterOptions = {
      filePath: filePath
    };
    
    const filteredResult = diffParser.filterSections(sections, filterOptions);
    const filteredDiff = diffParser.reconstructDiff(filteredResult.sections);

    return filteredDiff;
  }

  async handleGetPullRequest(args: any) {
    if (!isGetPullRequestArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for get_pull_request'
      );
    }

    const { workspace, repository, pull_request_id } = args;

    try {
      const apiPath = this.apiClient.getIsServer()
        ? `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}`
        : `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}`;
      
      const pr = await this.apiClient.makeRequest<any>('get', apiPath);

      let mergeInfo: MergeInfo = {};

      if (this.apiClient.getIsServer() && pr.state === 'MERGED') {
        try {
          const activitiesPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/activities`;
          const activitiesResponse = await this.apiClient.makeRequest<any>('get', activitiesPath, undefined, {
            params: { limit: 100 }
          });
          
          const activities = activitiesResponse.values || [];
          const mergeActivity = activities.find((a: BitbucketServerActivity) => a.action === 'MERGED');
          
          if (mergeActivity) {
            mergeInfo.mergeCommitHash = mergeActivity.commit?.id || null;
            mergeInfo.mergedBy = mergeActivity.user?.displayName || null;
            mergeInfo.mergedAt = new Date(mergeActivity.createdDate).toISOString();
            
            if (mergeActivity.commit?.id) {
              try {
                const commitPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/commits/${mergeActivity.commit.id}`;
                const commitResponse = await this.apiClient.makeRequest<any>('get', commitPath);
                mergeInfo.mergeCommitMessage = commitResponse.message || null;
              } catch (commitError) {
                console.error('Failed to fetch merge commit message:', commitError);
              }
            }
          }
        } catch (activitiesError) {
          console.error('Failed to fetch PR activities:', activitiesError);
        }
      }

      let comments: FormattedComment[] = [];
      let activeCommentCount = 0;
      let totalCommentCount = 0;
      let fileChanges: FormattedFileChange[] = [];
      let fileChangesSummary: any = null;

      try {
        const [commentsResult, fileChangesResult] = await Promise.all([
          this.fetchPullRequestComments(workspace, repository, pull_request_id),
          this.fetchPullRequestFileChanges(workspace, repository, pull_request_id)
        ]);

        comments = commentsResult.comments;
        activeCommentCount = commentsResult.activeCount;
        totalCommentCount = commentsResult.totalCount;
        fileChanges = fileChangesResult.fileChanges;
        fileChangesSummary = fileChangesResult.summary;
      } catch (error) {
        console.error('Failed to fetch additional PR data:', error);
      }

      const formattedResponse = this.apiClient.getIsServer() 
        ? formatServerResponse(pr as BitbucketServerPullRequest, mergeInfo, this.baseUrl)
        : formatCloudResponse(pr as BitbucketCloudPullRequest);

      const enhancedResponse = {
        ...formattedResponse,
        active_comments: comments,
        active_comment_count: activeCommentCount,
        total_comment_count: totalCommentCount,
        file_changes: fileChanges,
        file_changes_summary: fileChangesSummary
      };

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(enhancedResponse, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `getting pull request ${pull_request_id} in ${workspace}/${repository}`);
    }
  }

  async handleListPullRequests(args: any) {
    if (!isListPullRequestsArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for list_pull_requests'
      );
    }

    const { workspace, repository, state = 'OPEN', author, limit = 25, start = 0 } = args;

    try {
      let apiPath: string;
      let params: any = {};

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API
        apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests`;
        params = {
          state: state === 'ALL' ? undefined : state,
          limit,
          start,
        };
        if (author) {
          params['role.1'] = 'AUTHOR';
          params['username.1'] = author;
        }
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/pullrequests`;
        params = {
          state: state === 'ALL' ? undefined : state,
          pagelen: limit,
          page: Math.floor(start / limit) + 1,
        };
        if (author) {
          params['q'] = `author.username="${author}"`;
        }
      }

      const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });

      let pullRequests: any[] = [];
      let totalCount = 0;
      let nextPageStart = null;

      if (this.apiClient.getIsServer()) {
        pullRequests = (response.values || []).map((pr: BitbucketServerPullRequest) => 
          formatServerResponse(pr, undefined, this.baseUrl)
        );
        totalCount = response.size || 0;
        if (!response.isLastPage && response.nextPageStart !== undefined) {
          nextPageStart = response.nextPageStart;
        }
      } else {
        pullRequests = (response.values || []).map((pr: BitbucketCloudPullRequest) => 
          formatCloudResponse(pr)
        );
        totalCount = response.size || 0;
        if (response.next) {
          nextPageStart = start + limit;
        }
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              pull_requests: pullRequests,
              total_count: totalCount,
              start,
              limit,
              has_more: nextPageStart !== null,
              next_start: nextPageStart,
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `listing pull requests in ${workspace}/${repository}`);
    }
  }

  async handleCreatePullRequest(args: any) {
    if (!isCreatePullRequestArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for create_pull_request'
      );
    }

    const { workspace, repository, title, source_branch, destination_branch, description, reviewers, close_source_branch } = args;

    try {
      let apiPath: string;
      let requestBody: any;

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API
        apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests`;
        requestBody = {
          title,
          description: description || '',
          fromRef: {
            id: `refs/heads/${source_branch}`,
            repository: {
              slug: repository,
              project: {
                key: workspace
              }
            }
          },
          toRef: {
            id: `refs/heads/${destination_branch}`,
            repository: {
              slug: repository,
              project: {
                key: workspace
              }
            }
          },
          reviewers: reviewers?.map(r => ({ user: { name: r } })) || []
        };
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/pullrequests`;
        requestBody = {
          title,
          description: description || '',
          source: {
            branch: {
              name: source_branch
            }
          },
          destination: {
            branch: {
              name: destination_branch
            }
          },
          close_source_branch: close_source_branch || false,
          reviewers: reviewers?.map(r => ({ username: r })) || []
        };
      }

      const pr = await this.apiClient.makeRequest<any>('post', apiPath, requestBody);
      
      const formattedResponse = this.apiClient.getIsServer() 
        ? formatServerResponse(pr as BitbucketServerPullRequest, undefined, this.baseUrl)
        : formatCloudResponse(pr as BitbucketCloudPullRequest);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              message: 'Pull request created successfully',
              pull_request: formattedResponse
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `creating pull request in ${workspace}/${repository}`);
    }
  }

  async handleUpdatePullRequest(args: any) {
    if (!isUpdatePullRequestArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for update_pull_request'
      );
    }

    const { workspace, repository, pull_request_id, title, description, destination_branch, reviewers } = args;

    try {
      let apiPath: string;
      let requestBody: any = {};

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API
        apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}`;
        
        // First get the current PR to get version number and existing data
        const currentPr = await this.apiClient.makeRequest<any>('get', apiPath);
        
        requestBody.version = currentPr.version;
        if (title !== undefined) requestBody.title = title;
        if (description !== undefined) requestBody.description = description;
        if (destination_branch !== undefined) {
          requestBody.toRef = {
            id: `refs/heads/${destination_branch}`,
            repository: {
              slug: repository,
              project: {
                key: workspace
              }
            }
          };
        }
        
        // Handle reviewers: preserve existing ones if not explicitly updating
        if (reviewers !== undefined) {
          // User wants to update reviewers
          // Create a map of existing reviewers for preservation of approval status
          const existingReviewersMap = new Map(
            currentPr.reviewers.map((r: any) => [r.user.name, r])
          );
          
          requestBody.reviewers = reviewers.map(username => {
            const existing = existingReviewersMap.get(username);
            if (existing) {
              // Preserve existing reviewer's full data including approval status
              return existing;
            } else {
              // Add new reviewer (without approval status)
              return { user: { name: username } };
            }
          });
        } else {
          // No reviewers provided - preserve existing reviewers with their full data
          requestBody.reviewers = currentPr.reviewers;
        }
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}`;
        
        if (title !== undefined) requestBody.title = title;
        if (description !== undefined) requestBody.description = description;
        if (destination_branch !== undefined) {
          requestBody.destination = {
            branch: {
              name: destination_branch
            }
          };
        }
        if (reviewers !== undefined) {
          requestBody.reviewers = reviewers.map(r => ({ username: r }));
        }
      }

      const pr = await this.apiClient.makeRequest<any>('put', apiPath, requestBody);
      
      const formattedResponse = this.apiClient.getIsServer() 
        ? formatServerResponse(pr as BitbucketServerPullRequest, undefined, this.baseUrl)
        : formatCloudResponse(pr as BitbucketCloudPullRequest);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              message: 'Pull request updated successfully',
              pull_request: formattedResponse
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `updating pull request ${pull_request_id} in ${workspace}/${repository}`);
    }
  }

  async handleAddComment(args: any) {
    if (!isAddCommentArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for add_comment'
      );
    }

    let { 
      workspace, 
      repository, 
      pull_request_id, 
      comment_text, 
      parent_comment_id, 
      file_path, 
      line_number, 
      line_type,
      suggestion,
      suggestion_end_line,
      code_snippet,
      search_context,
      match_strategy = 'strict'
    } = args;

    let sequentialPosition: number | undefined;
    if (code_snippet && !line_number && file_path) {
      try {
        const resolved = await this.resolveLineFromCode(
          workspace,
          repository,
          pull_request_id,
          file_path,
          code_snippet,
          search_context,
          match_strategy
        );
        
        line_number = resolved.line_number;
        line_type = resolved.line_type;
        sequentialPosition = resolved.sequential_position;
      } catch (error) {
        throw error;
      }
    }

    if (suggestion && (!file_path || !line_number)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Suggestions require file_path and line_number to be specified'
      );
    }

    const isInlineComment = file_path !== undefined && line_number !== undefined;

    let finalCommentText = comment_text;
    if (suggestion) {
      finalCommentText = formatSuggestionComment(
        comment_text,
        suggestion,
        line_number,
        suggestion_end_line || line_number
      );
    }

    try {
      let apiPath: string;
      let requestBody: any;

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API
        apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/comments`;
        requestBody = {
          text: finalCommentText
        };
        
        if (parent_comment_id !== undefined) {
          requestBody.parent = { id: parent_comment_id };
        }
        
        if (isInlineComment) {
          requestBody.anchor = {
            line: line_number,
            lineType: line_type || 'CONTEXT', 
            fileType: line_type === 'REMOVED' ? 'FROM' : 'TO',
            path: file_path,
            diffType: 'EFFECTIVE'
          };
          
        }
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/comments`;
        requestBody = {
          content: {
            raw: finalCommentText
          }
        };
        
        if (parent_comment_id !== undefined) {
          requestBody.parent = { id: parent_comment_id };
        }
        
        if (isInlineComment) {
          requestBody.inline = {
            to: line_number,
            path: file_path
          };
        }
      }

      const comment = await this.apiClient.makeRequest<any>('post', apiPath, requestBody);

      const responseMessage = suggestion 
        ? 'Comment with code suggestion added successfully'
        : (isInlineComment ? 'Inline comment added successfully' : 'Comment added successfully');

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              message: responseMessage,
              comment: {
                id: comment.id,
                text: this.apiClient.getIsServer() ? comment.text : comment.content.raw,
                author: this.apiClient.getIsServer() ? comment.author.displayName : comment.user.display_name,
                created_on: this.apiClient.getIsServer() ? new Date(comment.createdDate).toLocaleString() : comment.created_on,
                file_path: isInlineComment ? file_path : undefined,
                line_number: isInlineComment ? line_number : undefined,
                line_type: isInlineComment ? (line_type || 'CONTEXT') : undefined,
                has_suggestion: !!suggestion,
                suggestion_lines: suggestion ? (suggestion_end_line ? `${line_number}-${suggestion_end_line}` : `${line_number}`) : undefined
              }
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `adding ${isInlineComment ? 'inline ' : ''}comment to pull request ${pull_request_id} in ${workspace}/${repository}`);
    }
  }

  async handleMergePullRequest(args: any) {
    if (!isMergePullRequestArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for merge_pull_request'
      );
    }

    const { workspace, repository, pull_request_id, merge_strategy, close_source_branch, commit_message } = args;

    try {
      let apiPath: string;
      let requestBody: any = {};

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API
        apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/merge`;
        
        // Get current PR version
        const prPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}`;
        const currentPr = await this.apiClient.makeRequest<any>('get', prPath);
        
        requestBody.version = currentPr.version;
        if (commit_message) {
          requestBody.message = commit_message;
        }
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/merge`;
        
        if (merge_strategy) {
          requestBody.merge_strategy = merge_strategy;
        }
        if (close_source_branch !== undefined) {
          requestBody.close_source_branch = close_source_branch;
        }
        if (commit_message) {
          requestBody.message = commit_message;
        }
      }

      const result = await this.apiClient.makeRequest<any>('post', apiPath, requestBody);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              message: 'Pull request merged successfully',
              merge_commit: this.apiClient.getIsServer() ? result.properties?.mergeCommit : result.merge_commit?.hash,
              pull_request_id
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `merging pull request ${pull_request_id} in ${workspace}/${repository}`);
    }
  }

  private async fetchPullRequestComments(
    workspace: string,
    repository: string,
    pullRequestId: number
  ): Promise<{ comments: FormattedComment[]; activeCount: number; totalCount: number }> {
    try {
      let comments: FormattedComment[] = [];
      let activeCount = 0;
      let totalCount = 0;

      if (this.apiClient.getIsServer()) {
        const processNestedComments = (comment: any, anchor: any): FormattedComment => {
          const formattedComment: FormattedComment = {
            id: comment.id,
            author: comment.author.displayName,
            text: comment.text,
            created_on: new Date(comment.createdDate).toISOString(),
            is_inline: !!anchor,
            file_path: anchor?.path,
            line_number: anchor?.line,
            state: comment.state
          };

          if (comment.comments && comment.comments.length > 0) {
            formattedComment.replies = comment.comments
              .filter((reply: any) => {
                if (reply.state === 'RESOLVED') return false;
                if (anchor && anchor.orphaned === true) return false;
                return true;
              })
              .map((reply: any) => processNestedComments(reply, anchor));
          }

          return formattedComment;
        };

        const countAllComments = (comment: any): number => {
          let count = 1;
          if (comment.comments && comment.comments.length > 0) {
            count += comment.comments.reduce((sum: number, reply: any) => sum + countAllComments(reply), 0);
          }
          return count;
        };

        const countActiveComments = (comment: any, anchor: any): number => {
          let count = 0;
          
          if (comment.state !== 'RESOLVED' && (!anchor || anchor.orphaned !== true)) {
            count = 1;
          }
          
          if (comment.comments && comment.comments.length > 0) {
            count += comment.comments.reduce((sum: number, reply: any) => sum + countActiveComments(reply, anchor), 0);
          }
          
          return count;
        };

        const apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pullRequestId}/activities`;
        const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, {
          params: { limit: 1000 }
        });

        const activities = response.values || [];
        
        const commentActivities = activities.filter((a: any) => 
          a.action === 'COMMENTED' && a.comment
        );

        totalCount = commentActivities.reduce((sum: number, activity: any) => {
          return sum + countAllComments(activity.comment);
        }, 0);

        activeCount = commentActivities.reduce((sum: number, activity: any) => {
          return sum + countActiveComments(activity.comment, activity.commentAnchor);
        }, 0);

        const processedComments = commentActivities
          .filter((a: any) => {
            const c = a.comment;
            const anchor = a.commentAnchor;
            
            if (c.state === 'RESOLVED') return false;
            if (anchor && anchor.orphaned === true) return false;
            
            return true;
          })
          .map((a: any) => processNestedComments(a.comment, a.commentAnchor));

        comments = processedComments.slice(0, 20);
      } else {
        const apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pullRequestId}/comments`;
        const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, {
          params: { pagelen: 100 }
        });

        const allComments = response.values || [];
        totalCount = allComments.length;

        const activeComments = allComments
          .filter((c: BitbucketCloudComment) => !c.deleted && !c.resolved)
          .slice(0, 20);

        activeCount = allComments.filter((c: BitbucketCloudComment) => !c.deleted && !c.resolved).length;

        comments = activeComments.map((c: BitbucketCloudComment) => ({
          id: c.id,
          author: c.user.display_name,
          text: c.content.raw,
          created_on: c.created_on,
          is_inline: !!c.inline,
          file_path: c.inline?.path,
          line_number: c.inline?.to
        }));
      }

      return { comments, activeCount, totalCount };
    } catch (error) {
      console.error('Failed to fetch comments:', error);
      return { comments: [], activeCount: 0, totalCount: 0 };
    }
  }

  private async fetchPullRequestFileChanges(
    workspace: string,
    repository: string,
    pullRequestId: number
  ): Promise<{ fileChanges: FormattedFileChange[]; summary: any }> {
    try {
      let fileChanges: FormattedFileChange[] = [];
      let totalLinesAdded = 0;
      let totalLinesRemoved = 0;

      if (this.apiClient.getIsServer()) {
        const apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pullRequestId}/changes`;
        const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, {
          params: { limit: 1000 }
        });

        const changes = response.values || [];

        fileChanges = changes.map((change: any) => {
          let status: 'added' | 'modified' | 'removed' | 'renamed' = 'modified';
          if (change.type === 'ADD') status = 'added';
          else if (change.type === 'DELETE') status = 'removed';
          else if (change.type === 'MOVE' || change.type === 'RENAME') status = 'renamed';

          return {
            path: change.path.toString,
            status,
            old_path: change.srcPath?.toString
          };
        });
      } else {
        const apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pullRequestId}/diffstat`;
        const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, {
          params: { pagelen: 100 }
        });

        const diffstats = response.values || [];

        fileChanges = diffstats.map((stat: BitbucketCloudFileChange) => {
          totalLinesAdded += stat.lines_added;
          totalLinesRemoved += stat.lines_removed;

          return {
            path: stat.path,
            status: stat.type,
            old_path: stat.old?.path
          };
        });
      }

      const summary = {
        total_files: fileChanges.length
      };

      return { fileChanges, summary };
    } catch (error) {
      console.error('Failed to fetch file changes:', error);
      return {
        fileChanges: [],
        summary: {
          total_files: 0
        }
      };
    }
  }

  private async resolveLineFromCode(
    workspace: string,
    repository: string,
    pullRequestId: number,
    filePath: string,
    codeSnippet: string,
    searchContext?: { before?: string[]; after?: string[] },
    matchStrategy: 'strict' | 'best' = 'strict'
  ): Promise<{ 
    line_number: number; 
    line_type: 'ADDED' | 'REMOVED' | 'CONTEXT'; 
    sequential_position?: number;
    hunk_info?: any;
    diff_context?: string;
    diff_content_preview?: string;
    calculation_details?: string;
  }> {
    try {
      const diffContent = await this.getFilteredPullRequestDiff(workspace, repository, pullRequestId, filePath);
      
      const parser = new DiffParser();
      const sections = parser.parseDiffIntoSections(diffContent);
      
      let fileSection = sections[0];
      if (!this.apiClient.getIsServer()) {
        fileSection = sections.find(s => s.filePath === filePath) || sections[0];
      }

      if (!fileSection) {
        throw new McpError(
          ErrorCode.InvalidParams,
          `File ${filePath} not found in pull request diff`
        );
      }

      const matches = this.findCodeMatches(
        fileSection.content,
        codeSnippet,
        searchContext
      );
      
      if (matches.length === 0) {
        throw new McpError(
          ErrorCode.InvalidParams,
          `Code snippet not found in ${filePath}`
        );
      }

      if (matches.length === 1) {
        return {
          line_number: matches[0].line_number,
          line_type: matches[0].line_type,
          sequential_position: matches[0].sequential_position,
          hunk_info: matches[0].hunk_info,
          diff_context: matches[0].preview,
          diff_content_preview: diffContent.split('\n').slice(0, 50).join('\n'),
          calculation_details: `Direct line number from diff: ${matches[0].line_number}`
        };
      }

      if (matchStrategy === 'best') {
        const best = this.selectBestMatch(matches);
        
        return {
          line_number: best.line_number,
          line_type: best.line_type,
          sequential_position: best.sequential_position,
          hunk_info: best.hunk_info,
          diff_context: best.preview,
          diff_content_preview: diffContent.split('\n').slice(0, 50).join('\n'),
          calculation_details: `Best match selected from ${matches.length} matches, line: ${best.line_number}`
        };
      }

      const error: MultipleMatchesError = {
        code: 'MULTIPLE_MATCHES_FOUND',
        message: `Code snippet '${codeSnippet.substring(0, 50)}...' found in ${matches.length} locations`,
        occurrences: matches.map(m => ({
          line_number: m.line_number,
          file_path: filePath,
          preview: m.preview,
          confidence: m.confidence,
          line_type: m.line_type
        })),
        suggestion: 'To resolve, either:\n1. Add more context to uniquely identify the location\n2. Use match_strategy: \'best\' to auto-select highest confidence match\n3. Use line_number directly'
      };

      throw new McpError(
        ErrorCode.InvalidParams,
        JSON.stringify({ error })
      );
    } catch (error) {
      if (error instanceof McpError) {
        throw error;
      }
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to resolve line from code: ${error instanceof Error ? error.message : String(error)}`
      );
    }
  }

  private findCodeMatches(
    diffContent: string,
    codeSnippet: string,
    searchContext?: { before?: string[]; after?: string[] }
  ): CodeMatch[] {
    const lines = diffContent.split('\n');
    const matches: CodeMatch[] = [];
    let currentDestLine = 0; // Destination file line number
    let currentSrcLine = 0;  // Source file line number
    let inHunk = false;
    let sequentialAddedCount = 0; // Track sequential ADDED lines
    let currentHunkIndex = -1;
    let currentHunkDestStart = 0;
    let currentHunkSrcStart = 0;
    let destPositionInHunk = 0; // Track position in destination file relative to hunk start
    let srcPositionInHunk = 0;  // Track position in source file relative to hunk start

    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];

      if (line.startsWith('@@')) {
        const match = line.match(/@@ -(\d+),\d+ \+(\d+),\d+ @@/);
        if (match) {
          currentHunkSrcStart = parseInt(match[1]);
          currentHunkDestStart = parseInt(match[2]);
          currentSrcLine = currentHunkSrcStart;
          currentDestLine = currentHunkDestStart;
          inHunk = true;
          currentHunkIndex++;
          destPositionInHunk = 0;
          srcPositionInHunk = 0;
          continue;
        }
      }

      if (!inHunk) continue;

      if (line === '') {
        inHunk = false;
        continue;
      }

      let lineType: 'ADDED' | 'REMOVED' | 'CONTEXT';
      let lineContent = '';
      let lineNumber = 0;

      if (line.startsWith('+')) {
        lineType = 'ADDED';
        lineContent = line.substring(1);
        lineNumber = currentHunkDestStart + destPositionInHunk;
        destPositionInHunk++;
        sequentialAddedCount++;
      } else if (line.startsWith('-')) {
        lineType = 'REMOVED';
        lineContent = line.substring(1);
        lineNumber = currentHunkSrcStart + srcPositionInHunk;
        srcPositionInHunk++;
      } else if (line.startsWith(' ')) {
        lineType = 'CONTEXT';
        lineContent = line.substring(1);
        lineNumber = currentHunkDestStart + destPositionInHunk;
        destPositionInHunk++;
        srcPositionInHunk++;
      } else {
        inHunk = false;
        continue;
      }

      if (lineContent.trim() === codeSnippet.trim()) {
        const confidence = this.calculateConfidence(
          lines,
          i,
          searchContext,
          lineType
        );

        matches.push({
          line_number: lineNumber,
          line_type: lineType,
          exact_content: codeSnippet,
          preview: this.getPreview(lines, i),
          confidence,
          context: this.extractContext(lines, i),
          sequential_position: lineType === 'ADDED' ? sequentialAddedCount : undefined,
          hunk_info: {
            hunk_index: currentHunkIndex,
            destination_start: currentHunkDestStart,
            line_in_hunk: destPositionInHunk
          }
        });
      }

      if (lineType === 'ADDED') {
        currentDestLine++;
      } else if (lineType === 'REMOVED') {
        currentSrcLine++;
      } else if (lineType === 'CONTEXT') {
        currentSrcLine++;
        currentDestLine++;
      }
    }

    return matches;
  }

  private calculateConfidence(
    lines: string[],
    index: number,
    searchContext?: { before?: string[]; after?: string[] },
    lineType?: 'ADDED' | 'REMOVED' | 'CONTEXT'
  ): number {
    let confidence = 0.5; // Base confidence

    if (!searchContext) {
      return confidence;
    }

    if (searchContext.before) {
      let matchedBefore = 0;
      for (let j = 0; j < searchContext.before.length; j++) {
        const contextLine = searchContext.before[searchContext.before.length - 1 - j];
        const checkIndex = index - j - 1;
        if (checkIndex >= 0) {
          const checkLine = lines[checkIndex].substring(1);
          if (checkLine.trim() === contextLine.trim()) {
            matchedBefore++;
          }
        }
      }
      confidence += (matchedBefore / searchContext.before.length) * 0.3;
    }

    if (searchContext.after) {
      let matchedAfter = 0;
      for (let j = 0; j < searchContext.after.length; j++) {
        const contextLine = searchContext.after[j];
        const checkIndex = index + j + 1;
        if (checkIndex < lines.length) {
          const checkLine = lines[checkIndex].substring(1);
          if (checkLine.trim() === contextLine.trim()) {
            matchedAfter++;
          }
        }
      }
      confidence += (matchedAfter / searchContext.after.length) * 0.3;
    }

    if (lineType === 'ADDED') {
      confidence += 0.1;
    }

    return Math.min(confidence, 1.0);
  }

  private getPreview(lines: string[], index: number): string {
    const start = Math.max(0, index - 1);
    const end = Math.min(lines.length, index + 2);
    const previewLines = [];

    for (let i = start; i < end; i++) {
      const prefix = i === index ? '> ' : '  ';
      previewLines.push(prefix + lines[i]);
    }

    return previewLines.join('\n');
  }

  private extractContext(lines: string[], index: number): { lines_before: string[]; lines_after: string[] } {
    const linesBefore: string[] = [];
    const linesAfter: string[] = [];

    for (let i = Math.max(0, index - 2); i < index; i++) {
      if (lines[i].match(/^[+\- ]/)) {
        linesBefore.push(lines[i].substring(1));
      }
    }

    for (let i = index + 1; i < Math.min(lines.length, index + 3); i++) {
      if (lines[i].match(/^[+\- ]/)) {
        linesAfter.push(lines[i].substring(1));
      }
    }

    return {
      lines_before: linesBefore,
      lines_after: linesAfter
    };
  }

  private selectBestMatch(matches: CodeMatch[]): CodeMatch {
    return matches.sort((a, b) => b.confidence - a.confidence)[0];
  }

  async handleListPrCommits(args: any) {
    if (!isListPrCommitsArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for list_pr_commits'
      );
    }

    const { workspace, repository, pull_request_id, limit = 25, start = 0, include_build_status = false } = args;

    try {
      // First get the PR details to include in response
      const prPath = this.apiClient.getIsServer()
        ? `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}`
        : `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}`;
      
      let prTitle = '';
      try {
        const pr = await this.apiClient.makeRequest<any>('get', prPath);
        prTitle = pr.title;
      } catch (e) {
        // Ignore error, PR title is optional
      }

      let apiPath: string;
      let params: any = {};
      let commits: FormattedCommit[] = [];
      let totalCount = 0;
      let nextPageStart: number | null = null;

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API
        apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/commits`;
        params = {
          limit,
          start,
          withCounts: true
        };

        const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });

        // Format commits
        commits = (response.values || []).map((commit: BitbucketServerCommit) => formatServerCommit(commit));

        totalCount = response.size || commits.length;
        if (!response.isLastPage && response.nextPageStart !== undefined) {
          nextPageStart = response.nextPageStart;
        }
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/commits`;
        params = {
          pagelen: limit,
          page: Math.floor(start / limit) + 1
        };

        const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });

        // Format commits
        commits = (response.values || []).map((commit: BitbucketCloudCommit) => formatCloudCommit(commit));

        totalCount = response.size || commits.length;
        if (response.next) {
          nextPageStart = start + limit;
        }
      }

      // Fetch build status if requested (Server only)
      if (include_build_status && this.apiClient.getIsServer() && commits.length > 0) {
        try {
          const commitIds = commits.map(c => c.hash);
          const buildSummaries = await this.apiClient.getBuildSummaries(
            workspace,
            repository,
            commitIds
          );

          // Enhance commits with build status
          commits = commits.map(commit => {
            const buildData = buildSummaries[commit.hash];
            if (buildData) {
              return {
                ...commit,
                build_status: {
                  successful: buildData.successful || 0,
                  failed: buildData.failed || 0,
                  in_progress: buildData.inProgress || 0,
                  unknown: buildData.unknown || 0
                }
              };
            }
            return commit;
          });
        } catch (error) {
          console.error('Failed to fetch build status for PR commits:', error);
          // Graceful degradation - continue without build status
        }
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              pull_request_id,
              pull_request_title: prTitle,
              commits,
              total_count: totalCount,
              start,
              limit,
              has_more: nextPageStart !== null,
              next_start: nextPageStart
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `listing commits for pull request ${pull_request_id} in ${workspace}/${repository}`);
    }
  }
}

```
Page 2/2FirstPrevNextLast