#
tokens: 45040/50000 28/30 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 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

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/

# Build output
build/
dist/

# Environment files
.env
.env.local
.env.*.local

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# OS files
.DS_Store
Thumbs.db

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Test files
test-api.js
*.test.js

# Temporary files
*.tmp
*.temp
.cache/

# Personal configuration
RELOAD_INSTRUCTIONS.md
personal-notes.md
currentTask.yml

```

--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------

```
# Source files
src/
*.ts
!*.d.ts

# Development files
.gitignore
tsconfig.json
.vscode/
.idea/

# Test files
test/
*.test.js
*.spec.js

# Build artifacts
*.log
node_modules/
.npm/

# OS files
.DS_Store
Thumbs.db

# Editor files
*.swp
*.swo
*~

# Documentation source
docs/

# Scripts (except built ones)
scripts/

# Setup guides (keep README)
SETUP_GUIDE.md
SETUP_GUIDE_SERVER.md

# Git
.git/
.gitattributes

# Other
.env
.env.local
.env.*.local

```

--------------------------------------------------------------------------------
/memory-bank/.clinerules:
--------------------------------------------------------------------------------

```
# Bitbucket MCP Server - Project Intelligence

## Critical Implementation Paths

### Adding New Tools
1. Create handler in src/handlers/ following existing patterns
2. Add TypeScript interfaces in src/types/bitbucket.ts
3. Add type guard in src/types/guards.ts
4. Add formatter in src/utils/formatters.ts if needed
5. Register tool in src/tools/definitions.ts
6. Wire handler in src/index.ts
7. Update version, CHANGELOG.md, and README.md

### API Variant Handling
- Always check `apiClient.getIsServer()` for Cloud vs Server
- Server uses `/rest/api/1.0/` prefix
- Cloud uses direct paths under base URL
- Different parameter names (e.g., pagelen vs limit)

### Error Handling Pattern
```typescript
try {
  // API call
} catch (error: any) {
  const errorMessage = error.response?.data?.errors?.[0]?.message || error.message;
  return {
    content: [{
      type: 'text',
      text: JSON.stringify({
        error: `Failed to ${action}: ${errorMessage}`,
        details: error.response?.data
      }, null, 2)
    }],
    isError: true
  };
}
```

## User Preferences

### Documentation Style
- Comprehensive examples for each tool
- Clear parameter descriptions
- Response format examples
- Note differences between Cloud and Server

### Code Style
- TypeScript with strict typing
- ES modules with .js extensions in imports
- Consistent error handling
- Modular architecture

## Project-Specific Patterns

### Authentication
- Cloud: Username (not email) + App Password
- Server: Email address + HTTP Access Token
- Environment variables for configuration

### Pagination Pattern
- `limit` and `start` parameters
- Return `has_more` and `next_start`
- Include `total_count` when available

### Response Formatting
- Consistent JSON structure
- Include operation status message
- Provide detailed error information
- Format dates as ISO strings

## Known Challenges

### Bitbucket API Differences
- Parameter naming varies (e.g., pagelen vs limit)
- Response structures differ significantly
- Some features only available on one variant
- Authentication methods completely different

### Search Functionality
- Only available on Bitbucket Server
- Query syntax requires specific format
- No Cloud API equivalent currently

## Tool Usage Patterns

### List Operations
- Always include pagination parameters
- Return consistent metadata
- Support filtering where applicable

### Modification Operations
- Validate required parameters
- Preserve existing data when updating
- Return updated resource in response

### File Operations
- Smart truncation for large files
- Type-based default limits
- Support line range selection

## Evolution of Decisions

### Version 0.3.0
- Modularized codebase into handlers
- Separated types and utilities
- Improved maintainability

### Version 0.6.0
- Enhanced PR details with comments/files
- Added parallel API calls for performance

### Version 0.9.0
- Added code snippet matching for comments
- Implemented confidence scoring

### Version 1.0.0
- Added code search functionality
- Reached feature completeness
- Ready for production use

### Version 1.0.1
- Improved search response formatting for AI consumption
- Added simplified formatCodeSearchOutput
- Enhanced HTML entity decoding and tag stripping
- Established live MCP testing workflow

## Important Architecture Decisions

### Handler Pattern
Each tool category has its own handler class to maintain single responsibility and make the codebase more maintainable.

### Type Guards
All tool inputs are validated with type guards to ensure type safety at runtime.

### Response Normalization
Different API responses are normalized to consistent formats for easier consumption.

### Error Handling
Consistent error handling across all tools with detailed error messages and recovery suggestions.

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Bitbucket MCP Server

[![npm version](https://badge.fury.io/js/@nexus2520%2Fbitbucket-mcp-server.svg)](https://www.npmjs.com/package/@nexus2520/bitbucket-mcp-server)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

An MCP (Model Context Protocol) server that provides tools for interacting with the Bitbucket API, supporting both Bitbucket Cloud and Bitbucket Server.

## Features

### Currently Implemented Tools

#### Core PR Lifecycle Tools
- `get_pull_request` - Retrieve detailed information about a pull request
- `list_pull_requests` - List pull requests with filters (state, author, pagination)
- `create_pull_request` - Create new pull requests
- `update_pull_request` - Update PR details (title, description, reviewers, destination branch)
- `add_comment` - Add comments to pull requests (supports replies)
- `merge_pull_request` - Merge pull requests with various strategies
- `list_pr_commits` - List all commits that are part of a pull request
- `delete_branch` - Delete branches after merge

#### Branch Management Tools
- `list_branches` - List branches with filtering and pagination
- `delete_branch` - Delete branches (with protection checks)
- `get_branch` - Get detailed branch information including associated PRs
- `list_branch_commits` - List commits in a branch with advanced filtering

#### File and Directory Tools
- `list_directory_content` - List files and directories in a repository path
- `get_file_content` - Get file content with smart truncation for large files

#### Code Review Tools
- `get_pull_request_diff` - Get the diff/changes for a pull request
- `approve_pull_request` - Approve a pull request
- `unapprove_pull_request` - Remove approval from a pull request
- `request_changes` - Request changes on a pull request
- `remove_requested_changes` - Remove change request from a pull request

#### Search Tools
- `search_code` - Search for code across repositories (currently Bitbucket Server only)

#### Project and Repository Discovery Tools
- `list_projects` - List all accessible Bitbucket projects/workspaces with filtering
- `list_repositories` - List repositories in a project or across all accessible projects

## Installation

### Using npx (Recommended)

The easiest way to use this MCP server is directly with npx:

```json
{
  "mcpServers": {
    "bitbucket": {
      "command": "npx",
      "args": [
        "-y",
        "@nexus2520/bitbucket-mcp-server"
      ],
      "env": {
        "BITBUCKET_USERNAME": "your-username",
        "BITBUCKET_APP_PASSWORD": "your-app-password"
      }
    }
  }
}
```

For Bitbucket Server:
```json
{
  "mcpServers": {
    "bitbucket": {
      "command": "npx",
      "args": [
        "-y",
        "@nexus2520/bitbucket-mcp-server"
      ],
      "env": {
        "BITBUCKET_USERNAME": "[email protected]",
        "BITBUCKET_TOKEN": "your-http-access-token",
        "BITBUCKET_BASE_URL": "https://bitbucket.yourcompany.com"
      }
    }
  }
}
```

### From Source

1. Clone or download this repository
2. Install dependencies:
   ```bash
   npm install
   ```
3. Build the TypeScript code:
   ```bash
   npm run build
   ```

## Authentication Setup

This server uses Bitbucket App Passwords for authentication.

### Creating an App Password

1. Log in to your Bitbucket account
2. Navigate to: https://bitbucket.org/account/settings/app-passwords/
3. Click "Create app password"
4. Give it a descriptive label (e.g., "MCP Server")
5. Select the following permissions:
   - **Account**: Read
   - **Repositories**: Read, Write
   - **Pull requests**: Read, Write
6. Click "Create"
7. **Important**: Copy the generated password immediately (you won't be able to see it again!)

### Running the Setup Script

```bash
node scripts/setup-auth.js
```

This will guide you through the authentication setup process.

## Configuration

Add the server to your MCP settings file (usually located at `~/.vscode-server/data/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`):

```json
{
  "mcpServers": {
    "bitbucket": {
      "command": "node",
      "args": ["/absolute/path/to/bitbucket-mcp-server/build/index.js"],
      "env": {
        "BITBUCKET_USERNAME": "your-username",
        "BITBUCKET_APP_PASSWORD": "your-app-password"
      }
    }
  }
}
```

Replace:
- `/absolute/path/to/bitbucket-mcp-server` with the actual path to this directory
- `your-username` with your Bitbucket username (not email)
- `your-app-password` with the app password you created

For Bitbucket Server, use:
```json
{
  "mcpServers": {
    "bitbucket": {
      "command": "node",
      "args": ["/absolute/path/to/bitbucket-mcp-server/build/index.js"],
      "env": {
        "BITBUCKET_USERNAME": "[email protected]",
        "BITBUCKET_TOKEN": "your-http-access-token",
        "BITBUCKET_BASE_URL": "https://bitbucket.yourcompany.com"
      }
    }
  }
}
```

**Important for Bitbucket Server users:**
- Use your full email address as the username (e.g., "[email protected]")
- This is required for approval/review actions to work correctly

## Usage

Once configured, you can use the available tools:

### Get Pull Request

```typescript
{
  "tool": "get_pull_request",
  "arguments": {
    "workspace": "PROJ",  // Required - your project key
    "repository": "my-repo",
    "pull_request_id": 123
  }
}
```

Returns detailed information about the pull request including:
- Title and description
- Author and reviewers
- Source and destination branches
- Approval status
- Links to web UI and diff
- **Merge commit details** (when PR is merged):
  - `merge_commit_hash`: The hash of the merge commit
  - `merged_by`: Who performed the merge
  - `merged_at`: When the merge occurred
  - `merge_commit_message`: The merge commit message
- **Active comments with nested replies** (unresolved comments that need attention):
  - `active_comments`: Array of active comments (up to 20 most recent top-level comments)
    - Comment text and author
    - Creation date
    - Whether it's an inline comment (with file path and line number)
    - **Nested replies** (for Bitbucket Server):
      - `replies`: Array of reply comments with same structure
      - Replies can be nested multiple levels deep
    - **Parent reference** (for Bitbucket Cloud):
      - `parent_id`: ID of the parent comment for replies
  - `active_comment_count`: Total count of unresolved comments (including nested replies)
  - `total_comment_count`: Total count of all comments (including resolved and replies)
- **File changes**:
  - `file_changes`: Array of all files modified in the PR
    - File path
    - Status (added, modified, removed, or renamed)
    - Old path (for renamed files)
  - `file_changes_summary`: Summary statistics
    - Total files changed
- And more...

### Search Code

Search for code across Bitbucket repositories (currently only supported for Bitbucket Server):

```typescript
// Search in a specific repository
{
  "tool": "search_code",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "search_query": "TODO",
    "limit": 50
  }
}

// Search across all repositories in a workspace
{
  "tool": "search_code",
  "arguments": {
    "workspace": "PROJ",
    "search_query": "deprecated",
    "file_pattern": "*.java",  // Optional: filter by file pattern
    "limit": 100
  }
}

// Search with file pattern filtering
{
  "tool": "search_code",
  "arguments": {
    "workspace": "PROJ",
    "repository": "frontend-app",
    "search_query": "useState",
    "file_pattern": "*.tsx",  // Only search in .tsx files
    "start": 0,
    "limit": 25
  }
}
```

Returns search results with:
- File path and name
- Repository and project information
- Matched lines with:
  - Line number
  - Full line content
  - Highlighted segments showing exact matches
- Pagination information

Example response:
```json
{
  "message": "Code search completed successfully",
  "workspace": "PROJ",
  "repository": "my-repo",
  "search_query": "TODO",
  "results": [
    {
      "file_path": "src/utils/helper.js",
      "file_name": "helper.js",
      "repository": "my-repo",
      "project": "PROJ",
      "matches": [
        {
          "line_number": 42,
          "line_content": "    // TODO: Implement error handling",
          "highlighted_segments": [
            { "text": "    // ", "is_match": false },
            { "text": "TODO", "is_match": true },
            { "text": ": Implement error handling", "is_match": false }
          ]
        }
      ]
    }
  ],
  "total_count": 15,
  "start": 0,
  "limit": 50,
  "has_more": false
}
```

**Note**: This tool currently only works with Bitbucket Server. Bitbucket Cloud support is planned for a future release.

### List Pull Requests

```typescript
{
  "tool": "list_pull_requests",
  "arguments": {
    "workspace": "PROJ",  // Required - your project key
    "repository": "my-repo",
    "state": "OPEN",  // Optional: OPEN, MERGED, DECLINED, ALL (default: OPEN)
    "author": "username",  // Optional: filter by author (see note below)
    "limit": 25,  // Optional: max results per page (default: 25)
    "start": 0  // Optional: pagination start index (default: 0)
  }
}
```

Returns a paginated list of pull requests with:
- Array of pull requests with same details as get_pull_request
- Total count of matching PRs
- Pagination info (has_more, next_start)

**Note on Author Filter:**
- For Bitbucket Cloud: Use the username (e.g., "johndoe")
- For Bitbucket Server: Use the full email address (e.g., "[email protected]")

### Create Pull Request

```typescript
{
  "tool": "create_pull_request",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "title": "Add new feature",
    "source_branch": "feature/new-feature",
    "destination_branch": "main",
    "description": "This PR adds a new feature...",  // Optional
    "reviewers": ["john.doe", "jane.smith"],  // Optional
    "close_source_branch": true  // Optional (default: false)
  }
}
```

### Update Pull Request

```typescript
{
  "tool": "update_pull_request",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "title": "Updated title",  // Optional
    "description": "Updated description",  // Optional
    "destination_branch": "develop",  // Optional
    "reviewers": ["new.reviewer"]  // Optional - see note below
  }
}
```

**Important Note on Reviewers:**
- When updating a PR without specifying the `reviewers` parameter, existing reviewers and their approval status are preserved
- When providing the `reviewers` parameter:
  - The reviewer list is replaced with the new list
  - For reviewers that already exist on the PR, their approval status is preserved
  - New reviewers are added without approval status
- This prevents accidentally removing reviewers when you only want to update the PR description or title

### Add Comment

Add a comment to a pull request, either as a general comment or inline on specific code:

```javascript
// General comment
{
  "tool": "add_comment",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "comment_text": "Great work on this PR!"
  }
}

// Inline comment on specific line
{
  "tool": "add_comment",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "comment_text": "Consider extracting this into a separate function",
    "file_path": "src/utils/helpers.js",
    "line_number": 42,
    "line_type": "CONTEXT"  // ADDED, REMOVED, or CONTEXT
  }
}

// Reply to existing comment
{
  "tool": "add_comment",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "comment_text": "I agree with this suggestion",
    "parent_comment_id": 456
  }
}

// Add comment with code suggestion (single line)
{
  "tool": "add_comment",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "comment_text": "This variable name could be more descriptive.",
    "file_path": "src/utils/helpers.js",
    "line_number": 42,
    "line_type": "CONTEXT",
    "suggestion": "const userAuthenticationToken = token;"
  }
}

// Add comment with multi-line code suggestion
{
  "tool": "add_comment",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "comment_text": "This function could be simplified using array methods.",
    "file_path": "src/utils/calculations.js",
    "line_number": 50,
    "suggestion_end_line": 55,
    "line_type": "CONTEXT",
    "suggestion": "function calculateTotal(items) {\n  return items.reduce((sum, item) => sum + item.price, 0);\n}"
  }
}
```

The suggestion feature formats comments using GitHub-style markdown suggestion blocks that Bitbucket can render. When adding a suggestion:
- `suggestion` is required and contains the replacement code
- `file_path` and `line_number` are required when using suggestions
- `suggestion_end_line` is optional and used for multi-line suggestions (defaults to `line_number`)
- The comment will be formatted with a ````suggestion` markdown block that may be applicable in the Bitbucket UI

### Using Code Snippets Instead of Line Numbers

The `add_comment` tool now supports finding line numbers automatically using code snippets. This is especially useful when AI tools analyze diffs and may struggle with exact line numbers:

```javascript
// Add comment using code snippet
{
  "tool": "add_comment",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "comment_text": "This variable name could be more descriptive",
    "file_path": "src/components/Button.res",
    "code_snippet": "let isDisabled = false",
    "search_context": {
      "before": ["let onClick = () => {"],
      "after": ["setLoading(true)"]
    }
  }
}

// Handle multiple matches with strategy
{
  "tool": "add_comment",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "comment_text": "Consider extracting this",
    "file_path": "src/utils/helpers.js",
    "code_snippet": "return result;",
    "search_context": {
      "before": ["const result = calculate();"],
      "after": ["}"]
    },
    "match_strategy": "best"  // Auto-select highest confidence match
  }
}
```

**Code Snippet Parameters:**
- `code_snippet`: The exact code line to find (alternative to `line_number`)
- `search_context`: Optional context to disambiguate multiple matches
  - `before`: Array of lines that should appear before the target
  - `after`: Array of lines that should appear after the target
- `match_strategy`: How to handle multiple matches
  - `"strict"` (default): Fail with error showing all matches
  - `"best"`: Auto-select the highest confidence match

**Error Response for Multiple Matches (strict mode):**
```json
{
  "error": {
    "code": "MULTIPLE_MATCHES_FOUND",
    "message": "Code snippet 'return result;' found in 3 locations",
    "occurrences": [
      {
        "line_number": 42,
        "file_path": "src/utils/helpers.js",
        "preview": "  const result = calculate();\n> return result;\n}",
        "confidence": 0.9,
        "line_type": "ADDED"
      },
      // ... more matches
    ],
    "suggestion": "To resolve, either:\n1. Add more context...\n2. Use match_strategy: 'best'...\n3. Use line_number directly"
  }
}
```

This feature is particularly useful for:
- AI-powered code review tools that analyze diffs
- Scripts that automatically add comments based on code patterns
- Avoiding line number confusion in large diffs

**Note on comment replies:**
- Use `parent_comment_id` to reply to any comment (general or inline)
- In `get_pull_request` responses:
  - Bitbucket Server shows replies nested in a `replies` array
  - Bitbucket Cloud shows a `parent_id` field for reply comments
- You can reply to replies, creating nested conversations

**Note on inline comments:**
- `file_path`: The path to the file as shown in the diff
- `line_number`: The line number as shown in the diff
- `line_type`: 
  - `ADDED` - For newly added lines (green in diff)
  - `REMOVED` - For deleted lines (red in diff)
  - `CONTEXT` - For unchanged context lines

#### Add Comment - Complete Usage Guide

The `add_comment` tool supports multiple scenarios. Here's when and how to use each approach:

**1. General PR Comments (No file/line)**
- Use when: Making overall feedback about the PR
- Required params: `comment_text` only
- Example: "LGTM!", "Please update the documentation"

**2. Reply to Existing Comments**
- Use when: Continuing a conversation thread
- Required params: `comment_text`, `parent_comment_id`
- Works for both general and inline comment replies

**3. Inline Comments with Line Number**
- Use when: You know the exact line number from the diff
- Required params: `comment_text`, `file_path`, `line_number`
- Optional: `line_type` (defaults to CONTEXT)

**4. Inline Comments with Code Snippet**
- Use when: You have the code but not the line number (common for AI tools)
- Required params: `comment_text`, `file_path`, `code_snippet`
- The tool will automatically find the line number
- Add `search_context` if the code appears multiple times
- Use `match_strategy: "best"` to auto-select when multiple matches exist

**5. Code Suggestions**
- Use when: Proposing specific code changes
- Required params: `comment_text`, `file_path`, `line_number`, `suggestion`
- For multi-line: also add `suggestion_end_line`
- Creates applicable suggestion blocks in Bitbucket UI

**Decision Flow for AI/Automated Tools:**
```
1. Do you want to suggest code changes?
   → Use suggestion with line_number
   
2. Do you have the exact line number?
   → Use line_number directly
   
3. Do you have the code snippet but not line number?
   → Use code_snippet (add search_context if needed)
   
4. Is it a general comment about the PR?
   → Use comment_text only
   
5. Are you replying to another comment?
   → Add parent_comment_id
```

**Common Pitfalls to Avoid:**
- Don't use both `line_number` and `code_snippet` - pick one
- Suggestions always need `file_path` and `line_number`
- Code snippets must match exactly (including whitespace)
- REMOVED lines reference the source file, ADDED/CONTEXT reference the destination

### Merge Pull Request

```typescript
{
  "tool": "merge_pull_request",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "merge_strategy": "squash",  // Optional: merge-commit, squash, fast-forward
    "close_source_branch": true,  // Optional
    "commit_message": "Custom merge message"  // Optional
  }
}
```

### List Branches

```typescript
{
  "tool": "list_branches",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "filter": "feature",  // Optional: filter by name pattern
    "limit": 25,  // Optional (default: 25)
    "start": 0  // Optional: for pagination (default: 0)
  }
}
```

Returns a paginated list of branches with:
- Branch name and ID
- Latest commit hash
- Default branch indicator
- Pagination info

### Delete Branch

```typescript
{
  "tool": "delete_branch",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "branch_name": "feature/old-feature",
    "force": false  // Optional (default: false)
  }
}
```

**Note**: Branch deletion requires appropriate permissions. The branch will be permanently deleted.

### Get Branch

```typescript
{
  "tool": "get_branch",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "branch_name": "feature/new-feature",
    "include_merged_prs": false  // Optional (default: false)
  }
}
```

Returns comprehensive branch information including:
- Branch details:
  - Name and ID
  - Latest commit (hash, message, author, date)
  - Default branch indicator
- Open pull requests from this branch:
  - PR title and ID
  - Destination branch
  - Author and reviewers
  - Approval status (approved by, changes requested by, pending)
  - PR URL
- Merged pull requests (if `include_merged_prs` is true):
  - PR title and ID
  - Merge date and who merged it
- Statistics:
  - Total open PRs count
  - Total merged PRs count
  - Days since last commit

This tool is particularly useful for:
- Checking if a branch has open PRs before deletion
- Getting an overview of branch activity
- Understanding PR review status
- Identifying stale branches

### List Branch Commits

Get all commits in a specific branch with advanced filtering options:

```typescript
// Basic usage - get recent commits
{
  "tool": "list_branch_commits",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "branch_name": "feature/new-feature",
    "limit": 50  // Optional (default: 25)
  }
}

// Filter by date range
{
  "tool": "list_branch_commits",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "branch_name": "main",
    "since": "2025-01-01T00:00:00Z",  // ISO date string
    "until": "2025-01-15T23:59:59Z"   // ISO date string
  }
}

// Filter by author
{
  "tool": "list_branch_commits",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "branch_name": "develop",
    "author": "[email protected]",  // Email or username
    "limit": 100
  }
}

// Exclude merge commits
{
  "tool": "list_branch_commits",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "branch_name": "release/v2.0",
    "include_merge_commits": false
  }
}

// Search in commit messages
{
  "tool": "list_branch_commits",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "branch_name": "main",
    "search": "bugfix",  // Search in commit messages
    "limit": 50
  }
}

// Combine multiple filters
{
  "tool": "list_branch_commits",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "branch_name": "develop",
    "author": "[email protected]",
    "since": "2025-01-01T00:00:00Z",
    "include_merge_commits": false,
    "search": "feature",
    "limit": 100,
    "start": 0  // For pagination
  }
}

// Include CI/CD build status (Bitbucket Server only)
{
  "tool": "list_branch_commits",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "branch_name": "main",
    "include_build_status": true,  // Fetch build status for each commit
    "limit": 50
  }
}
```

**Filter Parameters:**
- `since`: ISO date string - only show commits after this date
- `until`: ISO date string - only show commits before this date
- `author`: Filter by author email/username
- `include_merge_commits`: Boolean to include/exclude merge commits (default: true)
- `search`: Search for text in commit messages
- `include_build_status`: Boolean to include CI/CD build status (default: false, Bitbucket Server only)

Returns detailed commit information:
```json
{
  "branch_name": "feature/new-feature",
  "branch_head": "abc123def456",  // Latest commit hash
  "commits": [
    {
      "hash": "abc123def456",
      "abbreviated_hash": "abc123d",
      "message": "Add new feature implementation",
      "author": {
        "name": "John Doe",
        "email": "[email protected]"
      },
      "date": "2025-01-03T10:30:00Z",
      "parents": ["parent1hash", "parent2hash"],
      "is_merge_commit": false,
      "build_status": {  // Only present when include_build_status is true
        "successful": 5,
        "failed": 0,
        "in_progress": 1,
        "unknown": 0
      }
    }
    // ... more commits
  ],
  "total_count": 150,
  "start": 0,
  "limit": 25,
  "has_more": true,
  "next_start": 25,
  "filters_applied": {
    "author": "[email protected]",
    "since": "2025-01-01",
    "include_merge_commits": false,
    "include_build_status": true
  }
}
```

This tool is particularly useful for:
- Reviewing commit history before releases
- Finding commits by specific authors
- Tracking changes within date ranges
- Searching for specific features or fixes
- Analyzing branch activity patterns
- Monitoring CI/CD build status for commits (Bitbucket Server only)

### List PR Commits

Get all commits that are part of a pull request:

```typescript
// Basic usage
{
  "tool": "list_pr_commits",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "limit": 50,  // Optional (default: 25)
    "start": 0    // Optional: for pagination
  }
}

// Include CI/CD build status (Bitbucket Server only)
{
  "tool": "list_pr_commits",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "include_build_status": true,  // Fetch build status for each commit
    "limit": 50
  }
}
```

Returns commit information for the PR:
```json
{
  "pull_request_id": 123,
  "pull_request_title": "Add awesome feature",
  "commits": [
    {
      "hash": "def456ghi789",
      "abbreviated_hash": "def456g",
      "message": "Initial implementation",
      "author": {
        "name": "Jane Smith",
        "email": "[email protected]"
      },
      "date": "2025-01-02T14:20:00Z",
      "parents": ["parent1hash"],
      "is_merge_commit": false,
      "build_status": {  // Only present when include_build_status is true
        "successful": 3,
        "failed": 0,
        "in_progress": 0,
        "unknown": 0
      }
    }
    // ... more commits
  ],
  "total_count": 5,
  "start": 0,
  "limit": 25,
  "has_more": false
}
```

This tool is particularly useful for:
- Reviewing all changes in a PR before merging
- Understanding the development history of a PR
- Checking commit messages for quality
- Verifying authorship of changes
- Analyzing PR complexity by commit count
- Monitoring CI/CD build status for all PR commits (Bitbucket Server only)

### Get Pull Request Diff

Get the diff/changes for a pull request with optional filtering capabilities:

```typescript
// Get full diff (default behavior)
{
  "tool": "get_pull_request_diff",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "context_lines": 5  // Optional (default: 3)
  }
}

// Exclude specific file types
{
  "tool": "get_pull_request_diff",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "exclude_patterns": ["*.lock", "*.svg", "node_modules/**", "*.min.js"]
  }
}

// Include only specific file types
{
  "tool": "get_pull_request_diff",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "include_patterns": ["*.res", "*.resi", "src/**/*.js"]
  }
}

// Get diff for a specific file only
{
  "tool": "get_pull_request_diff",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "file_path": "src/components/Button.res"
  }
}

// Combine filters
{
  "tool": "get_pull_request_diff",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "include_patterns": ["src/**/*"],
    "exclude_patterns": ["*.test.js", "*.spec.js"]
  }
}
```

**Filtering Options:**
- `include_patterns`: Array of glob patterns to include (whitelist)
- `exclude_patterns`: Array of glob patterns to exclude (blacklist)
- `file_path`: Get diff for a specific file only
- Patterns support standard glob syntax (e.g., `*.js`, `src/**/*.res`, `!test/**`)

**Response includes filtering metadata:**
```json
{
  "message": "Pull request diff retrieved successfully",
  "pull_request_id": 123,
  "diff": "..filtered diff content..",
  "filter_metadata": {
    "total_files": 15,
    "included_files": 12,
    "excluded_files": 3,
    "excluded_file_list": ["package-lock.json", "logo.svg", "yarn.lock"],
    "filters_applied": {
      "exclude_patterns": ["*.lock", "*.svg"]
    }
  }
}
```

### Approve Pull Request

```typescript
{
  "tool": "approve_pull_request",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123
  }
}
```

### Request Changes

```typescript
{
  "tool": "request_changes",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "pull_request_id": 123,
    "comment": "Please address the following issues..."  // Optional
  }
}
```

### List Directory Content

```typescript
{
  "tool": "list_directory_content",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "path": "src/components",  // Optional (defaults to root)
    "branch": "main"  // Optional (defaults to default branch)
  }
}
```

Returns directory listing with:
- Path and branch information
- Array of contents with:
  - Name
  - Type (file or directory)
  - Size (for files)
  - Full path
- Total items count

### Get File Content

```typescript
{
  "tool": "get_file_content",
  "arguments": {
    "workspace": "PROJ",
    "repository": "my-repo",
    "file_path": "src/index.ts",
    "branch": "main",  // Optional (defaults to default branch)
    "start_line": 1,  // Optional: starting line (1-based, use negative for from end)
    "line_count": 100,  // Optional: number of lines to return
    "full_content": false  // Optional: force full content (default: false)
  }
}
```

**Smart Truncation Features:**
- Automatically truncates large files (>50KB) to prevent token overload
- Default line counts based on file type:
  - Config files (.yml, .json): 200 lines
  - Documentation (.md, .txt): 300 lines
  - Code files (.ts, .js, .py): 500 lines
  - Log files: Last 100 lines
- Use `start_line: -50` to get last 50 lines (tail functionality)
- Files larger than 1MB require explicit `full_content: true` or line parameters

Returns file content with:
- File path and branch
- File size and encoding
- Content (full or truncated based on parameters)
- Line information (if truncated):
  - Total lines in file
  - Range of returned lines
  - Truncation indicator
- Last modified information (commit, author, date)

Example responses:

```json
// Small file - returns full content
{
  "file_path": "package.json",
  "branch": "main",
  "size": 1234,
  "encoding": "utf-8",
  "content": "{\n  \"name\": \"my-project\",\n  ...",
  "last_modified": {
    "commit_id": "abc123",
    "author": "John Doe",
    "date": "2025-01-21T10:00:00Z"
  }
}

// Large file - automatically truncated
{
  "file_path": "src/components/LargeComponent.tsx",
  "branch": "main",
  "size": 125000,
  "encoding": "utf-8",
  "content": "... first 500 lines ...",
  "line_info": {
    "total_lines": 3500,
    "returned_lines": {
      "start": 1,
      "end": 500
    },
    "truncated": true,
    "message": "Showing lines 1-500 of 3500. File size: 122.1KB"
  }
}
```

### List Projects

List all accessible Bitbucket projects (Server) or workspaces (Cloud):

```typescript
// List all accessible projects
{
  "tool": "list_projects",
  "arguments": {
    "limit": 25,  // Optional (default: 25)
    "start": 0    // Optional: for pagination (default: 0)
  }
}

// Filter by project name
{
  "tool": "list_projects",
  "arguments": {
    "name": "backend",  // Partial name match
    "limit": 50
  }
}

// Filter by permission level (Bitbucket Server only)
{
  "tool": "list_projects",
  "arguments": {
    "permission": "PROJECT_WRITE",  // PROJECT_READ, PROJECT_WRITE, PROJECT_ADMIN
    "limit": 100
  }
}
```

**Parameters:**
- `name`: Filter by project/workspace name (partial match, optional)
- `permission`: Filter by permission level (Bitbucket Server only, optional)
  - `PROJECT_READ`: Read access
  - `PROJECT_WRITE`: Write access
  - `PROJECT_ADMIN`: Admin access
- `limit`: Maximum number of projects to return (default: 25)
- `start`: Start index for pagination (default: 0)

Returns project/workspace information:
```json
{
  "projects": [
    {
      "key": "PROJ",
      "id": 1234,
      "name": "My Project",
      "description": "Project description",
      "is_public": false,
      "type": "NORMAL",  // NORMAL or PERSONAL (Server), WORKSPACE (Cloud)
      "url": "https://bitbucket.yourcompany.com/projects/PROJ"
    }
    // ... more projects
  ],
  "total_count": 15,
  "start": 0,
  "limit": 25,
  "has_more": false,
  "next_start": null
}
```

**Note**:
- For Bitbucket Cloud, this returns workspaces (not projects in the traditional sense)
- For Bitbucket Server, this returns both personal and team projects

This tool is particularly useful for:
- Discovering available projects/workspaces for your account
- Finding project keys needed for other API calls
- Identifying projects you have specific permissions on
- Browsing organizational structure

### List Repositories

List repositories within a specific project/workspace or across all accessible repositories:

```typescript
// List all repositories in a workspace/project
{
  "tool": "list_repositories",
  "arguments": {
    "workspace": "PROJ",  // Required for Bitbucket Cloud, optional for Server
    "limit": 25,          // Optional (default: 25)
    "start": 0            // Optional: for pagination (default: 0)
  }
}

// List all accessible repositories (Bitbucket Server only)
{
  "tool": "list_repositories",
  "arguments": {
    "limit": 100
  }
}

// Filter by repository name
{
  "tool": "list_repositories",
  "arguments": {
    "workspace": "PROJ",
    "name": "frontend",  // Partial name match
    "limit": 50
  }
}

// Filter by permission level (Bitbucket Server only)
{
  "tool": "list_repositories",
  "arguments": {
    "workspace": "PROJ",
    "permission": "REPO_WRITE",  // REPO_READ, REPO_WRITE, REPO_ADMIN
    "limit": 100
  }
}
```

**Parameters:**
- `workspace`: Project key (Server) or workspace slug (Cloud)
  - **Required for Bitbucket Cloud**
  - Optional for Bitbucket Server (omit to list all accessible repos)
- `name`: Filter by repository name (partial match, optional)
- `permission`: Filter by permission level (Bitbucket Server only, optional)
  - `REPO_READ`: Read access
  - `REPO_WRITE`: Write access
  - `REPO_ADMIN`: Admin access
- `limit`: Maximum number of repositories to return (default: 25)
- `start`: Start index for pagination (default: 0)

Returns repository information:
```json
{
  "repositories": [
    {
      "slug": "my-repo",
      "id": 5678,
      "name": "My Repository",
      "description": "Repository description",
      "project_key": "PROJ",
      "project_name": "My Project",
      "state": "AVAILABLE",  // AVAILABLE, INITIALISING, INITIALISATION_FAILED (Server)
      "is_public": false,
      "is_forkable": true,
      "clone_urls": {
        "http": "https://bitbucket.yourcompany.com/scm/PROJ/my-repo.git",
        "ssh": "ssh://[email protected]:7999/PROJ/my-repo.git"
      },
      "url": "https://bitbucket.yourcompany.com/projects/PROJ/repos/my-repo"
    }
    // ... more repositories
  ],
  "total_count": 42,
  "start": 0,
  "limit": 25,
  "has_more": true,
  "next_start": 25,
  "workspace": "PROJ"
}
```

**Important Notes:**
- **Bitbucket Cloud** requires the `workspace` parameter. If omitted, you'll receive an error message
- **Bitbucket Server** allows listing all accessible repos by omitting the `workspace` parameter
- Clone URLs are provided for both HTTP(S) and SSH protocols

This tool is particularly useful for:
- Discovering available repositories in a project/workspace
- Finding repository slugs needed for other API calls
- Identifying repositories you have specific permissions on
- Getting clone URLs for repositories
- Browsing repository structure within an organization

## Development

- `npm run dev` - Watch mode for development
- `npm run build` - Build the TypeScript code
- `npm start` - Run the built server

## Troubleshooting

1. **Authentication errors**: Double-check your username and app password
2. **404 errors**: Verify the workspace, repository slug, and PR ID
3. **Permission errors**: Ensure your app password has the required permissions

## License

MIT

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "build"]
}

```

--------------------------------------------------------------------------------
/src/utils/suggestion-formatter.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Formats a comment with a code suggestion in markdown format
 * that Bitbucket can render as an applicable suggestion
 */
export function formatSuggestionComment(
  commentText: string,
  suggestion: string,
  startLine?: number,
  endLine?: number
): string {
  // Add line range info if it's a multi-line suggestion
  const lineInfo = startLine && endLine && endLine > startLine 
    ? ` (lines ${startLine}-${endLine})` 
    : '';
  
  // Format with GitHub-style suggestion markdown
  return `${commentText}${lineInfo}

\`\`\`suggestion
${suggestion}
\`\`\``;
}

```

--------------------------------------------------------------------------------
/scripts/setup-auth.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

console.log(`
===========================================
Bitbucket MCP Server - Authentication Setup
===========================================

To use this MCP server, you need to create a Bitbucket App Password.

Follow these steps:

1. Log in to your Bitbucket account
2. Go to: https://bitbucket.org/account/settings/app-passwords/
3. Click "Create app password"
4. Give it a label (e.g., "MCP Server")
5. Select the following permissions:
   - Account: Read
   - Repositories: Read, Write
   - Pull requests: Read, Write
6. Click "Create"
7. Copy the generated app password (you won't be able to see it again!)

You'll need to provide:
- Your Bitbucket username (not email)
- The app password you just created
- Your default workspace/organization (optional)

Example workspace: If your repository URL is:
https://bitbucket.org/mycompany/my-repo
Then your workspace is: mycompany

These will be added to your MCP settings configuration.

Press Enter to continue...
`);

// Wait for user to press Enter
process.stdin.once('data', () => {
  console.log(`
Next steps:
1. The MCP server will be configured with your credentials
2. You'll be able to use the 'get_pull_request' tool
3. More tools can be added later (create_pull_request, list_pull_requests, etc.)

Configuration complete!
`);
  process.exit(0);
});

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@nexus2520/bitbucket-mcp-server",
  "version": "1.1.2",
  "description": "MCP server for Bitbucket API integration - supports both Cloud and Server",
  "type": "module",
  "main": "./build/index.js",
  "bin": {
    "bitbucket-mcp-server": "build/index.js"
  },
  "files": [
    "build/**/*",
    "README.md",
    "LICENSE",
    "CHANGELOG.md"
  ],
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
    "dev": "tsc --watch",
    "start": "node build/index.js",
    "prepublishOnly": "npm run build"
  },
  "keywords": [
    "mcp",
    "bitbucket",
    "api",
    "model-context-protocol",
    "bitbucket-server",
    "bitbucket-cloud",
    "pull-request",
    "code-review"
  ],
  "author": "Parth Dogra",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/pdogra1299/bitbucket-mcp-server.git"
  },
  "bugs": {
    "url": "https://github.com/pdogra1299/bitbucket-mcp-server/issues"
  },
  "homepage": "https://github.com/pdogra1299/bitbucket-mcp-server#readme",
  "engines": {
    "node": ">=16.0.0"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.12.1",
    "axios": "^1.10.0",
    "minimatch": "^9.0.3"
  },
  "devDependencies": {
    "@types/minimatch": "^5.1.2",
    "@types/node": "^22.15.29",
    "typescript": "^5.8.3"
  }
}

```

--------------------------------------------------------------------------------
/memory-bank/projectbrief.yml:
--------------------------------------------------------------------------------

```yaml
# Project Brief - Bitbucket MCP Server

project_name: "bitbucket-mcp-server"
version: "1.0.0"
description: "MCP (Model Context Protocol) server for Bitbucket API integration"
primary_purpose: "Provide tools for interacting with Bitbucket APIs via MCP"

key_features:
  - "Support for both Bitbucket Cloud and Server"
  - "19 comprehensive tools for PR management, branch operations, and code review"
  - "Modular architecture with separate handlers for different tool categories"
  - "Smart file content handling with automatic truncation"
  - "Advanced PR commenting with code suggestions and snippet matching"
  - "Code search functionality across repositories"

scope:
  included:
    - "Pull Request lifecycle management"
    - "Branch management and operations"
    - "Code review and approval workflows"
    - "File and directory operations"
    - "Code search (Bitbucket Server only)"
    - "Authentication via app passwords/tokens"
  
  excluded:
    - "Repository creation/deletion"
    - "User management"
    - "Pipeline/build operations"
    - "Wiki/documentation management"
    - "Bitbucket Cloud code search (future enhancement)"

constraints:
  technical:
    - "Node.js >= 16.0.0 required"
    - "TypeScript with ES modules"
    - "MCP SDK for protocol implementation"
  
  authentication:
    - "Bitbucket Cloud: App passwords required"
    - "Bitbucket Server: HTTP access tokens required"
    - "Different username formats for Cloud vs Server"

success_criteria:
  - "Seamless integration with MCP-compatible clients"
  - "Reliable API interactions with proper error handling"
  - "Consistent response formatting across tools"
  - "Maintainable and extensible codebase"

```

--------------------------------------------------------------------------------
/memory-bank/productContext.yml:
--------------------------------------------------------------------------------

```yaml
# Product Context - Bitbucket MCP Server

problem_statement: |
  Developers using AI assistants need programmatic access to Bitbucket operations
  without leaving their development environment. Manual API interactions are
  time-consuming and error-prone.

target_users:
  primary:
    - "Software developers using AI coding assistants"
    - "DevOps engineers automating PR workflows"
    - "Code reviewers needing efficient review tools"
  
  secondary:
    - "Project managers tracking development progress"
    - "QA engineers reviewing code changes"

user_needs:
  core:
    - "Create and manage pull requests programmatically"
    - "Review code changes with AI assistance"
    - "Automate branch management tasks"
    - "Search code across repositories"
    - "Access file contents without cloning"
  
  workflow:
    - "Comment on PRs with code suggestions"
    - "Approve/request changes on PRs"
    - "List and filter pull requests"
    - "Get comprehensive PR details including comments"
    - "Manage branch lifecycle"

value_proposition:
  - "Seamless Bitbucket integration within AI assistants"
  - "Unified interface for Cloud and Server variants"
  - "Time savings through automation"
  - "Reduced context switching"
  - "Enhanced code review quality with AI"

user_experience_goals:
  - "Simple tool invocation syntax"
  - "Clear and consistent response formats"
  - "Helpful error messages with recovery suggestions"
  - "Smart defaults (pagination, truncation)"
  - "Flexible filtering and search options"

adoption_strategy:
  - "npm package for easy installation"
  - "Comprehensive documentation with examples"
  - "Support for both Cloud and Server"
  - "Gradual feature addition based on user needs"

```

--------------------------------------------------------------------------------
/SETUP_GUIDE_SERVER.md:
--------------------------------------------------------------------------------

```markdown
# Bitbucket Server MCP Setup Guide

Since you're using Bitbucket Server (self-hosted), you'll need to create an HTTP access token instead of an app password.

## Step 1: Your Username

Your Bitbucket Server username (not email address)

## Step 2: Create an HTTP Access Token

1. **Navigate to HTTP Access Tokens**:
   - You mentioned you can see "HTTP access tokens" in your account settings
   - Click on that option

2. **Create a new token**:
   - Click "Create token" or similar button
   - Give it a descriptive name like "MCP Server Integration"
   - Set an expiration date (or leave it without expiration if allowed)
   - Select the following permissions:
     - **Repository**: Read, Write
     - **Pull request**: Read, Write
     - **Project**: Read (if available)

3. **Generate and copy the token**:
   - Click "Create" or "Generate"
   - **IMPORTANT**: Copy the token immediately! It will look like a long string of random characters
   - You won't be able to see this token again

## Step 3: Find Your Bitbucket Server URL

Your Bitbucket Server URL is the base URL you use to access Bitbucket. For example:
- `https://bitbucket.yourcompany.com`
- `https://git.yourcompany.com`
- `https://bitbucket.internal.company.net`

## Step 4: Find Your Project/Workspace

In Bitbucket Server, repositories are organized by projects. Look at any repository URL:
- Example: `https://bitbucket.company.com/projects/PROJ/repos/my-repo`
- In this case, "PROJ" is your project key

## Example Configuration

For Bitbucket Server, your configuration will look like:

```
Username: your.username
Token: [Your HTTP access token]
Base URL: https://bitbucket.yourcompany.com
Project/Workspace: PROJ (or whatever your project key is)
```

## Next Steps

Once you have:
1. Your username
2. An HTTP access token from the "HTTP access tokens" section
3. Your Bitbucket Server base URL
4. Your project key

You can configure the MCP server for Bitbucket Server.

```

--------------------------------------------------------------------------------
/memory-bank/progress.yml:
--------------------------------------------------------------------------------

```yaml
# Progress - Bitbucket MCP Server

project_status:
  overall_progress: "95%"
  phase: "Post-1.0 Improvements"
  version: "1.0.1"
  release_status: "Production with improvements"

milestones_completed:
  - name: "Core PR Tools"
    completion_date: "2025-01-06"
    features:
      - "get_pull_request with merge details"
      - "list_pull_requests with filtering"
      - "create_pull_request"
      - "update_pull_request"
      - "merge_pull_request"
    
  - name: "Enhanced Commenting"
    completion_date: "2025-01-26"
    features:
      - "Inline comments"
      - "Code suggestions"
      - "Code snippet matching"
      - "Nested replies support"
  
  - name: "Code Review Tools"
    completion_date: "2025-01-26"
    features:
      - "get_pull_request_diff with filtering"
      - "approve_pull_request"
      - "request_changes"
      - "Review state management"
  
  - name: "Branch Management"
    completion_date: "2025-01-21"
    features:
      - "list_branches"
      - "delete_branch"
      - "get_branch with PR info"
      - "list_branch_commits"
  
  - name: "File Operations"
    completion_date: "2025-01-21"
    features:
      - "list_directory_content"
      - "get_file_content with smart truncation"
  
  - name: "Code Search"
    completion_date: "2025-07-25"
    features:
      - "search_code for Bitbucket Server"
      - "File pattern filtering"
      - "Highlighted search results"

active_development:
  current_sprint: "Post 1.0 Planning"
  in_progress: []
  blocked: []

testing_status:
  unit_tests: "Not implemented"
  integration_tests: "Manual testing completed"
  user_acceptance: "In production use"
  known_issues: []

deployment_status:
  npm_package: "Published as @nexus2520/bitbucket-mcp-server"
  version_published: "1.0.0"
  documentation: "Comprehensive README with examples"
  adoption_metrics:
    - "npm weekly downloads tracking"
    - "GitHub stars and issues"

performance_metrics:
  api_response_time: "< 2s average"
  memory_usage: "< 100MB typical"
  concurrent_operations: "Supports parallel API calls"

next_release_planning:
  version: "1.1.0"
  planned_features:
    - "Bitbucket Cloud search support"
    - "Repository management tools"
    - "Pipeline/build integration"
  timeline: "TBD based on user feedback"

```

--------------------------------------------------------------------------------
/SETUP_GUIDE.md:
--------------------------------------------------------------------------------

```markdown
# Bitbucket MCP Server Setup Guide

## Step 1: Find Your Bitbucket Username

1. **Log in to Bitbucket**: Go to https://bitbucket.org and log in with your credentials

2. **Find your username**:
   - After logging in, click on your profile avatar in the top-right corner
   - Click on "Personal settings" or go directly to: https://bitbucket.org/account/settings/
   - Your username will be displayed at the top of the page
   - **Note**: Your username is NOT your email address. It's usually a shorter identifier like "johndoe" or "jdoe123"

## Step 2: Create an App Password

1. **Navigate to App Passwords**:
   - While logged in, go to: https://bitbucket.org/account/settings/app-passwords/
   - Or from your account settings, look for "App passwords" in the left sidebar under "Access management"

2. **Create a new app password**:
   - Click the "Create app password" button
   - Give it a descriptive label like "MCP Server" or "Bitbucket MCP Integration"
   
3. **Select permissions** (IMPORTANT - select these specific permissions):
   - ✅ **Account**: Read
   - ✅ **Repositories**: Read, Write
   - ✅ **Pull requests**: Read, Write
   - You can leave other permissions unchecked

4. **Generate the password**:
   - Click "Create"
   - **IMPORTANT**: Copy the generated password immediately! It will look something like: `ATBBxxxxxxxxxxxxxxxxxxxxx`
   - You won't be able to see this password again after closing the dialog

## Step 3: Find Your Workspace (Optional but Recommended)

Your workspace is the organization or team name in Bitbucket. To find it:

1. Look at any of your repository URLs:
   - Example: `https://bitbucket.org/mycompany/my-repo`
   - In this case, "mycompany" is your workspace

2. Or go to your workspace dashboard:
   - Click on "Workspaces" in the top navigation
   - Your workspaces will be listed there

## Example Credentials

Here's what your credentials should look like:

```
Username: johndoe              # Your Bitbucket username (NOT email)
App Password: ATBB3xXx...      # The generated app password
Workspace: mycompany           # Your organization/workspace name
```

## Common Issues

1. **"Username not found"**: Make sure you're using your Bitbucket username, not your email address
2. **"Invalid app password"**: Ensure you copied the entire app password including the "ATBB" prefix
3. **"Permission denied"**: Check that your app password has the required permissions (Account: Read, Repositories: Read/Write, Pull requests: Read/Write)

## Next Steps

Once you have these credentials, share them with me and I'll configure the MCP server for you. The credentials will be stored securely in your MCP settings configuration.

```

--------------------------------------------------------------------------------
/memory-bank/techContext.yml:
--------------------------------------------------------------------------------

```yaml
# Technical Context - Bitbucket MCP Server

core_technologies:
  language: "TypeScript"
  version: "5.8.3"
  module_system: "ES Modules"
  runtime:
    name: "Node.js"
    version: ">= 16.0.0"
    package_manager: "npm"

libraries_and_bindings:
  - name: "@modelcontextprotocol/sdk"
    version: "^1.12.1"
    purpose: "MCP protocol implementation"
  
  - name: "axios"
    version: "^1.10.0"
    purpose: "HTTP client for API requests"
  
  - name: "minimatch"
    version: "^9.0.3"
    purpose: "Glob pattern matching for file filtering"

development_environment:
  build_tools:
    - "TypeScript compiler (tsc)"
    - "npm scripts for build automation"
  
  commands:
    build: "npm run build"
    dev: "npm run dev"
    start: "npm start"
    publish: "npm publish"
  
  project_structure:
    src_directory: "src/"
    build_directory: "build/"
    entry_point: "src/index.ts"
    compiled_entry: "build/index.js"

technical_patterns:
  - name: "Shebang for CLI execution"
    description: "#!/usr/bin/env node at top of index.ts"
    usage: "Enables direct execution as CLI tool"
  
  - name: "ES Module imports"
    description: "Using .js extensions in TypeScript imports"
    usage: "Required for ES module compatibility"
    examples:
      - "import { Server } from '@modelcontextprotocol/sdk/server/index.js'"
  
  - name: "Type-safe error handling"
    description: "Custom ApiError interface with typed errors"
    usage: "Consistent error handling across API calls"
  
  - name: "Environment variable configuration"
    description: "Process.env for authentication and base URL"
    usage: "Flexible configuration without code changes"

api_integration:
  bitbucket_cloud:
    base_url: "https://api.bitbucket.org/2.0"
    auth_method: "Basic Auth with App Password"
    api_style: "RESTful with JSON"
    pagination: "page-based with pagelen parameter"
  
  bitbucket_server:
    base_url: "Custom URL (e.g., https://bitbucket.company.com)"
    auth_method: "Bearer token (HTTP Access Token)"
    api_style: "RESTful with JSON"
    pagination: "offset-based with start/limit"
    api_version: "/rest/api/1.0"
    search_api: "/rest/search/latest"

deployment:
  package_name: "@nexus2520/bitbucket-mcp-server"
  registry: "npm public registry"
  distribution:
    - "Compiled JavaScript in build/"
    - "Type definitions excluded"
    - "Source maps excluded"
  
  execution_methods:
    - "npx -y @nexus2520/bitbucket-mcp-server"
    - "Direct node execution after install"
    - "MCP client integration"

security_considerations:
  - "Credentials stored in environment variables"
  - "No credential logging or exposure"
  - "HTTPS only for API communications"
  - "Token/password validation on startup"

performance_optimizations:
  - "Parallel API calls for PR details"
  - "Smart file truncation to prevent token overflow"
  - "Pagination for large result sets"
  - "Early exit on authentication failure"

compatibility:
  mcp_clients:
    - "Cline (VSCode extension)"
    - "Other MCP-compatible AI assistants"
  
  bitbucket_versions:
    - "Bitbucket Cloud (latest API)"
    - "Bitbucket Server 7.x+"
    - "Bitbucket Data Center"

```

--------------------------------------------------------------------------------
/memory-bank/systemPatterns.yml:
--------------------------------------------------------------------------------

```yaml
# System Patterns - Bitbucket MCP Server

architecture_overview:
  high_level_architecture: |
    MCP Server implementation with modular handler architecture.
    Main server class delegates tool calls to specialized handlers.
    Each handler manages a specific domain (PRs, branches, reviews, files, search).
  
  component_relationships: |
    - BitbucketMCPServer (main) → Handler classes → BitbucketApiClient → Axios
    - Tool definitions → MCP SDK → Client applications
    - Type guards validate inputs → Handlers process → Formatters standardize output

design_patterns:
  - name: "Handler Pattern"
    category: "architecture"
    description: "Separate handler classes for different tool categories"
    usage: "Organizing related tools and maintaining single responsibility"
    implementation:
      - "PullRequestHandlers for PR lifecycle"
      - "BranchHandlers for branch operations"
      - "ReviewHandlers for code review tools"
      - "FileHandlers for file/directory operations"
      - "SearchHandlers for code search"
    example_files:
      - "src/handlers/*.ts"
    related_patterns:
      - "Dependency Injection"

  - name: "API Client Abstraction"
    category: "integration"
    description: "Unified client handling both Cloud and Server APIs"
    usage: "Abstracting API differences between Bitbucket variants"
    implementation:
      - "Single makeRequest method for all HTTP operations"
      - "Automatic auth header selection (Bearer vs Basic)"
      - "Consistent error handling across variants"
    example_files:
      - "src/utils/api-client.ts"

  - name: "Type Guard Pattern"
    category: "validation"
    description: "Runtime type checking for tool arguments"
    usage: "Ensuring type safety for dynamic tool inputs"
    implementation:
      - "Guard functions return type predicates"
      - "Comprehensive validation of required/optional fields"
      - "Array and nested object validation"
    example_files:
      - "src/types/guards.ts"

  - name: "Response Formatting"
    category: "data_transformation"
    description: "Consistent response formatting across API variants"
    usage: "Normalizing different API response structures"
    implementation:
      - "formatServerResponse/formatCloudResponse for PRs"
      - "Unified FormattedXXX interfaces"
      - "Separate formatters for different data types"
    example_files:
      - "src/utils/formatters.ts"

project_specific_patterns:
  mcp_patterns:
    - name: "Tool Definition Structure"
      description: "Standardized tool definition with inputSchema"
      implementation:
        - "Name, description, and JSON schema for each tool"
        - "Required vs optional parameter specification"
        - "Enum constraints for valid values"
    
    - name: "Error Response Pattern"
      description: "Consistent error handling and reporting"
      implementation:
        - "Return isError: true for tool failures"
        - "Include detailed error messages"
        - "Provide context-specific error details"

  bitbucket_patterns:
    - name: "Pagination Pattern"
      description: "Consistent pagination across list operations"
      implementation:
        - "limit and start parameters"
        - "has_more and next_start in responses"
        - "total_count for result sets"
    
    - name: "Dual API Support"
      description: "Supporting both Cloud and Server APIs"
      implementation:
        - "isServer flag determines API paths"
        - "Different parameter names mapped appropriately"
        - "Response structure normalization"

  code_patterns:
    - name: "Smart Truncation"
      description: "Intelligent file content truncation"
      implementation:
        - "File type-based default limits"
        - "Size-based automatic truncation"
        - "Line range selection support"
    
    - name: "Code Snippet Matching"
      description: "Finding line numbers from code snippets"
      implementation:
        - "Exact text matching with context"
        - "Confidence scoring for multiple matches"
        - "Strategy selection (strict vs best)"

```

--------------------------------------------------------------------------------
/memory-bank/activeContext.yml:
--------------------------------------------------------------------------------

```yaml
# Active Context - Bitbucket MCP Server

current_focus_areas:
  - name: "Search Code Feature Implementation"
    status: "completed"
    priority: "high"
    team: "frontend"
    timeline: "2025-07-25"

recent_changes:
  - date: "2025-08-08"
    feature: "testing_and_release_v1.0.1"
    description: "Comprehensive testing of search functionality and release of version 1.0.1"
    status: "completed"
    files_affected:
      - "package.json"
      - "CHANGELOG.md"
      - "memory-bank/activeContext.yml"
      - "memory-bank/progress.yml"
    technical_details: "Tested search functionality with real Bitbucket data, verified context-aware patterns, file filtering, and error handling"
    business_impact: "Validated production readiness of search improvements and properly versioned the release"
    patterns_introduced:
      - "Live MCP testing workflow using connected Bitbucket server"
      - "Real-world validation of search functionality"
      
  - date: "2025-07-25"
    feature: "code_search_response_fix"
    description: "Fixed search_code tool response handling to match actual Bitbucket API structure"
    status: "completed"
    files_affected:
      - "src/utils/formatters.ts"
      - "src/handlers/search-handlers.ts"
    technical_details: "Added formatCodeSearchOutput for simplified AI-friendly output showing only filename, line number, and text"
    business_impact: "Search results now properly formatted for AI consumption with cleaner output"
    patterns_introduced:
      - "Simplified formatter pattern for AI-friendly output"
      
  - date: "2025-07-25"
    feature: "code_search"
    description: "Added search_code tool for searching code across repositories"
    status: "completed"
    files_affected:
      - "src/handlers/search-handlers.ts"
      - "src/types/bitbucket.ts"
      - "src/utils/formatters.ts"
      - "src/types/guards.ts"
      - "src/tools/definitions.ts"
      - "src/index.ts"
    technical_details: "Implemented Bitbucket Server search API integration"
    business_impact: "Enables code search functionality for Bitbucket Server users"
    patterns_introduced:
      - "SearchHandlers following modular architecture"
      - "Search result formatting pattern"

patterns_discovered:
  - name: "Modular Handler Architecture"
    description: "Each tool category has its own handler class"
    usage: "Add new handlers for new tool categories"
    implementation: "Create handler class, add to index.ts, register tools"

  - name: "API Variant Handling"
    description: "Single handler supports both Cloud and Server APIs"
    usage: "Check isServer flag and adjust API paths/parameters"
    implementation: "Use apiClient.getIsServer() to determine variant"
    
  - name: "Simplified AI Formatter Pattern"
    description: "Separate formatters for detailed vs AI-friendly output"
    usage: "Create simplified formatters that extract only essential information for AI"
    implementation: "formatCodeSearchOutput strips HTML, shows only file:line:text format"

recent_learnings:
  - date: "2025-07-25"
    topic: "Bitbucket Search API Response Structure"
    description: "API returns file as string (not object), uses hitContexts with HTML formatting"
    impact: "Need to parse HTML <em> tags and handle different response structure"
    
  - date: "2025-07-25"
    topic: "Bitbucket Search API"
    description: "Search API only available on Bitbucket Server, not Cloud"
    impact: "Search tool marked as Server-only with future Cloud support planned"

  - date: "2025-07-25"
    topic: "Version Management"
    description: "Major version 1.0.0 indicates stable API with comprehensive features"
    impact: "Project ready for production use"

key_decisions:
  - decision: "Separate handler for search tools"
    rationale: "Maintains single responsibility and modularity"
    date: "2025-07-25"
    
  - decision: "YAML format for Memory Bank"
    rationale: "Better merge conflict handling and structured data"
    date: "2025-07-25"

current_challenges:
  - "Bitbucket Cloud search API not yet available"
  - "Need to handle different search syntaxes across platforms"

next_priorities:
  - "Add Bitbucket Cloud search when API becomes available"
  - "Enhance search with more filtering options"
  - "Add support for searching in other entities (commits, PRs)"

```

--------------------------------------------------------------------------------
/src/utils/api-client.ts:
--------------------------------------------------------------------------------

```typescript
import axios, { AxiosInstance, AxiosError } from 'axios';
import { BitbucketServerBuildSummary } from '../types/bitbucket.js';

export interface ApiError {
  status?: number;
  message: string;
  isAxiosError: boolean;
  originalError?: AxiosError;
}

export class BitbucketApiClient {
  private axiosInstance: AxiosInstance;
  private isServer: boolean;

  constructor(
    baseURL: string,
    username: string,
    password?: string,
    token?: string
  ) {
    this.isServer = !!token;
    
    const axiosConfig: any = {
      baseURL,
      headers: {
        'Content-Type': 'application/json',
      },
    };

    // Use token auth for Bitbucket Server, basic auth for Cloud
    if (token) {
      // Bitbucket Server uses Bearer token
      axiosConfig.headers['Authorization'] = `Bearer ${token}`;
    } else {
      // Bitbucket Cloud uses basic auth with app password
      axiosConfig.auth = {
        username,
        password,
      };
    }

    this.axiosInstance = axios.create(axiosConfig);
  }

  async makeRequest<T>(
    method: 'get' | 'post' | 'put' | 'delete',
    path: string,
    data?: any,
    config?: any
  ): Promise<T> {
    try {
      let response;
      if (method === 'get') {
        // For GET, config is the second parameter
        response = await this.axiosInstance[method](path, config || {});
      } else if (method === 'delete') {
        // For DELETE, we might need to pass data in config
        if (data) {
          response = await this.axiosInstance[method](path, { ...config, data });
        } else {
          response = await this.axiosInstance[method](path, config || {});
        }
      } else {
        // For POST and PUT, data is second, config is third
        response = await this.axiosInstance[method](path, data, config);
      }
      return response.data;
    } catch (error) {
      if (axios.isAxiosError(error)) {
        const status = error.response?.status;
        const message = error.response?.data?.errors?.[0]?.message || 
                       error.response?.data?.error?.message || 
                       error.response?.data?.message ||
                       error.message;

        throw {
          status,
          message,
          isAxiosError: true,
          originalError: error
        } as ApiError;
      }
      throw error;
    }
  }

  handleApiError(error: any, context: string) {
    if (error.isAxiosError) {
      const { status, message } = error as ApiError;

      if (status === 404) {
        return {
          content: [
            {
              type: 'text',
              text: `Not found: ${context}`,
            },
          ],
          isError: true,
        };
      } else if (status === 401) {
        return {
          content: [
            {
              type: 'text',
              text: `Authentication failed. Please check your ${this.isServer ? 'BITBUCKET_TOKEN' : 'BITBUCKET_USERNAME and BITBUCKET_APP_PASSWORD'}`,
            },
          ],
          isError: true,
        };
      } else if (status === 403) {
        return {
          content: [
            {
              type: 'text',
              text: `Permission denied: ${context}. Ensure your credentials have the necessary permissions.`,
            },
          ],
          isError: true,
        };
      }

      return {
        content: [
          {
            type: 'text',
            text: `Bitbucket API error: ${message}`,
          },
        ],
        isError: true,
      };
    }
    throw error;
  }

  getIsServer(): boolean {
    return this.isServer;
  }

  async getBuildSummaries(
    workspace: string,
    repository: string,
    commitIds: string[]
  ): Promise<BitbucketServerBuildSummary> {
    if (!this.isServer) {
      // Build summaries only available for Bitbucket Server
      return {};
    }

    if (commitIds.length === 0) {
      return {};
    }

    try {
      // Build query string with multiple commitId parameters
      const apiPath = `/rest/ui/latest/projects/${workspace}/repos/${repository}/build-summaries`;

      // Create params with custom serializer for multiple commitId parameters
      const response = await this.makeRequest<BitbucketServerBuildSummary>(
        'get',
        apiPath,
        undefined,
        {
          params: { commitId: commitIds },
          paramsSerializer: (params: any) => {
            // Custom serializer to create multiple commitId= parameters
            if (params.commitId && Array.isArray(params.commitId)) {
              return params.commitId.map((id: string) => `commitId=${encodeURIComponent(id)}`).join('&');
            }
            return '';
          }
        }
      );

      return response;
    } catch (error) {
      // If build-summaries endpoint fails, return empty object (graceful degradation)
      console.error('Failed to fetch build summaries:', error);
      return {};
    }
  }
}

```

--------------------------------------------------------------------------------
/src/utils/diff-parser.ts:
--------------------------------------------------------------------------------

```typescript
import { minimatch } from 'minimatch';

export interface DiffSection {
  filePath: string;
  oldPath?: string; // For renamed files
  content: string;
  isNew: boolean;
  isDeleted: boolean;
  isRenamed: boolean;
  isBinary: boolean;
}

export interface FilterOptions {
  includePatterns?: string[];
  excludePatterns?: string[];
  filePath?: string;
}

export interface FilteredResult {
  sections: DiffSection[];
  metadata: {
    totalFiles: number;
    includedFiles: number;
    excludedFiles: number;
    excludedFileList: string[];
  };
}

export class DiffParser {
  /**
   * Parse a unified diff into file sections
   */
  parseDiffIntoSections(diff: string): DiffSection[] {
    const sections: DiffSection[] = [];
    
    // Split by file boundaries - handle both formats
    const fileChunks = diff.split(/(?=^diff --git)/gm).filter(chunk => chunk.trim());
    
    for (const chunk of fileChunks) {
      const section = this.parseFileSection(chunk);
      if (section) {
        sections.push(section);
      }
    }
    
    return sections;
  }

  /**
   * Parse a single file section from the diff
   */
  private parseFileSection(chunk: string): DiffSection | null {
    const lines = chunk.split('\n');
    if (lines.length === 0) return null;
    
    // Extract file paths from the diff header
    let filePath = '';
    let oldPath: string | undefined;
    let isNew = false;
    let isDeleted = false;
    let isRenamed = false;
    let isBinary = false;
    
    // Look for diff --git line - handle both standard and Bitbucket Server formats
    const gitDiffMatch = lines[0].match(/^diff --git (?:a\/|src:\/\/)(.+?) (?:b\/|dst:\/\/)(.+?)$/);
    if (gitDiffMatch) {
      const [, aPath, bPath] = gitDiffMatch;
      filePath = bPath;
      
      // Check subsequent lines for file status
      for (let i = 1; i < Math.min(lines.length, 10); i++) {
        const line = lines[i];
        
        if (line.startsWith('new file mode')) {
          isNew = true;
        } else if (line.startsWith('deleted file mode')) {
          isDeleted = true;
          filePath = aPath; // Use the original path for deleted files
        } else if (line.startsWith('rename from')) {
          isRenamed = true;
          oldPath = line.replace('rename from ', '');
        } else if (line.includes('Binary files') && line.includes('differ')) {
          isBinary = true;
        } else if (line.startsWith('--- ')) {
          // Alternative way to detect new/deleted
          if (line.includes('/dev/null')) {
            isNew = true;
          }
        } else if (line.startsWith('+++ ')) {
          if (line.includes('/dev/null')) {
            isDeleted = true;
          }
          // Extract path from +++ line if needed - handle both formats
          const match = line.match(/^\+\+\+ (?:b\/|dst:\/\/)(.+)$/);
          if (match && !filePath) {
            filePath = match[1];
          }
        }
      }
    }
    
    // Fallback: try to extract from --- and +++ lines
    if (!filePath) {
      for (const line of lines) {
        if (line.startsWith('+++ ')) {
          const match = line.match(/^\+\+\+ (?:b\/|dst:\/\/)(.+)$/);
          if (match) {
            filePath = match[1];
            break;
          }
        } else if (line.startsWith('--- ')) {
          const match = line.match(/^--- (?:a\/|src:\/\/)(.+)$/);
          if (match) {
            filePath = match[1];
          }
        }
      }
    }
    
    if (!filePath) return null;
    
    return {
      filePath,
      oldPath,
      content: chunk,
      isNew,
      isDeleted,
      isRenamed,
      isBinary
    };
  }

  /**
   * Apply filters to diff sections
   */
  filterSections(sections: DiffSection[], options: FilterOptions): FilteredResult {
    const excludedFileList: string[] = [];
    let filteredSections = sections;
    
    // If specific file path is requested, only keep that file
    if (options.filePath) {
      filteredSections = sections.filter(section => 
        section.filePath === options.filePath || 
        section.oldPath === options.filePath
      );
      
      // Track excluded files
      sections.forEach(section => {
        if (section.filePath !== options.filePath && 
            section.oldPath !== options.filePath) {
          excludedFileList.push(section.filePath);
        }
      });
    } else {
      // Apply exclude patterns first (blacklist)
      if (options.excludePatterns && options.excludePatterns.length > 0) {
        filteredSections = filteredSections.filter(section => {
          const shouldExclude = options.excludePatterns!.some(pattern => 
            minimatch(section.filePath, pattern, { matchBase: true })
          );
          
          if (shouldExclude) {
            excludedFileList.push(section.filePath);
            return false;
          }
          return true;
        });
      }
      
      // Apply include patterns if specified (whitelist)
      if (options.includePatterns && options.includePatterns.length > 0) {
        filteredSections = filteredSections.filter(section => {
          const shouldInclude = options.includePatterns!.some(pattern => 
            minimatch(section.filePath, pattern, { matchBase: true })
          );
          
          if (!shouldInclude) {
            excludedFileList.push(section.filePath);
            return false;
          }
          return true;
        });
      }
    }
    
    return {
      sections: filteredSections,
      metadata: {
        totalFiles: sections.length,
        includedFiles: filteredSections.length,
        excludedFiles: sections.length - filteredSections.length,
        excludedFileList
      }
    };
  }

  /**
   * Reconstruct a unified diff from filtered sections
   */
  reconstructDiff(sections: DiffSection[]): string {
    if (sections.length === 0) {
      return '';
    }
    
    // Join all sections with proper spacing
    return sections.map(section => section.content).join('\n');
  }
}

```

--------------------------------------------------------------------------------
/src/handlers/search-handlers.ts:
--------------------------------------------------------------------------------

```typescript
import { BitbucketApiClient } from '../utils/api-client.js';
import { 
  BitbucketServerSearchRequest,
  BitbucketServerSearchResult,
  FormattedSearchResult
} from '../types/bitbucket.js';
import { formatSearchResults, formatCodeSearchOutput } from '../utils/formatters.js';

interface SearchContext {
  assignment: string[];
  declaration: string[];
  usage: string[];
  exact: string[];
  any: string[];
}

function buildContextualPatterns(searchTerm: string): SearchContext {
  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: string, 
  searchContext: string = 'any',
  includePatterns: string[] = []
): string {
  const contextPatterns = buildContextualPatterns(searchTerm);
  
  let patterns: string[] = [];
  
  // Add patterns based on context
  if (searchContext in contextPatterns) {
    patterns = [...contextPatterns[searchContext as keyof 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 {
  constructor(
    private apiClient: BitbucketApiClient,
    private baseUrl: string
  ) {}

  async handleSearchCode(args: any) {
    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: BitbucketServerSearchRequest = {
        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<BitbucketServerSearchResult>(
        '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: any) {
      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
      };
    }
  }
}

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
} from '@modelcontextprotocol/sdk/types.js';

import { BitbucketApiClient } from './utils/api-client.js';
import { PullRequestHandlers } from './handlers/pull-request-handlers.js';
import { BranchHandlers } from './handlers/branch-handlers.js';
import { ReviewHandlers } from './handlers/review-handlers.js';
import { FileHandlers } from './handlers/file-handlers.js';
import { SearchHandlers } from './handlers/search-handlers.js';
import { ProjectHandlers } from './handlers/project-handlers.js';
import { toolDefinitions } from './tools/definitions.js';

// Get environment variables
const BITBUCKET_USERNAME = process.env.BITBUCKET_USERNAME;
const BITBUCKET_APP_PASSWORD = process.env.BITBUCKET_APP_PASSWORD;
const BITBUCKET_TOKEN = process.env.BITBUCKET_TOKEN; // For Bitbucket Server
const BITBUCKET_BASE_URL = process.env.BITBUCKET_BASE_URL || 'https://api.bitbucket.org/2.0';

// Check for either app password (Cloud) or token (Server)
if (!BITBUCKET_USERNAME || (!BITBUCKET_APP_PASSWORD && !BITBUCKET_TOKEN)) {
  console.error('Error: BITBUCKET_USERNAME and either BITBUCKET_APP_PASSWORD (for Cloud) or BITBUCKET_TOKEN (for Server) are required');
  console.error('Please set these in your MCP settings configuration');
  process.exit(1);
}

class BitbucketMCPServer {
  private server: Server;
  private apiClient: BitbucketApiClient;
  private pullRequestHandlers: PullRequestHandlers;
  private branchHandlers: BranchHandlers;
  private reviewHandlers: ReviewHandlers;
  private fileHandlers: FileHandlers;
  private searchHandlers: SearchHandlers;
  private projectHandlers: ProjectHandlers;

  constructor() {
    this.server = new Server(
      {
        name: 'bitbucket-mcp-server',
        version: '1.1.2',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );

    // Initialize API client
    this.apiClient = new BitbucketApiClient(
      BITBUCKET_BASE_URL,
      BITBUCKET_USERNAME!,
      BITBUCKET_APP_PASSWORD,
      BITBUCKET_TOKEN
    );

    // Initialize handlers
    this.pullRequestHandlers = new PullRequestHandlers(
      this.apiClient,
      BITBUCKET_BASE_URL,
      BITBUCKET_USERNAME!
    );
    this.branchHandlers = new BranchHandlers(this.apiClient, BITBUCKET_BASE_URL);
    this.reviewHandlers = new ReviewHandlers(this.apiClient, BITBUCKET_USERNAME!);
    this.fileHandlers = new FileHandlers(this.apiClient, BITBUCKET_BASE_URL);
    this.searchHandlers = new SearchHandlers(this.apiClient, BITBUCKET_BASE_URL);
    this.projectHandlers = new ProjectHandlers(this.apiClient, BITBUCKET_BASE_URL);

    this.setupToolHandlers();

    // Error handling
    this.server.onerror = (error) => console.error('[MCP Error]', error);
    process.on('SIGINT', async () => {
      await this.server.close();
      process.exit(0);
    });
  }

  private setupToolHandlers() {
    // List available tools
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: toolDefinitions,
    }));

    // Handle tool calls
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      switch (request.params.name) {
        // Pull Request tools
        case 'get_pull_request':
          return this.pullRequestHandlers.handleGetPullRequest(request.params.arguments);
        case 'list_pull_requests':
          return this.pullRequestHandlers.handleListPullRequests(request.params.arguments);
        case 'create_pull_request':
          return this.pullRequestHandlers.handleCreatePullRequest(request.params.arguments);
        case 'update_pull_request':
          return this.pullRequestHandlers.handleUpdatePullRequest(request.params.arguments);
        case 'add_comment':
          return this.pullRequestHandlers.handleAddComment(request.params.arguments);
        case 'merge_pull_request':
          return this.pullRequestHandlers.handleMergePullRequest(request.params.arguments);
        case 'list_pr_commits':
          return this.pullRequestHandlers.handleListPrCommits(request.params.arguments);
        
        // Branch tools
        case 'list_branches':
          return this.branchHandlers.handleListBranches(request.params.arguments);
        case 'delete_branch':
          return this.branchHandlers.handleDeleteBranch(request.params.arguments);
        case 'get_branch':
          return this.branchHandlers.handleGetBranch(request.params.arguments);
        case 'list_branch_commits':
          return this.branchHandlers.handleListBranchCommits(request.params.arguments);
        
        // Code Review tools
        case 'get_pull_request_diff':
          return this.reviewHandlers.handleGetPullRequestDiff(request.params.arguments);
        case 'approve_pull_request':
          return this.reviewHandlers.handleApprovePullRequest(request.params.arguments);
        case 'unapprove_pull_request':
          return this.reviewHandlers.handleUnapprovePullRequest(request.params.arguments);
        case 'request_changes':
          return this.reviewHandlers.handleRequestChanges(request.params.arguments);
        case 'remove_requested_changes':
          return this.reviewHandlers.handleRemoveRequestedChanges(request.params.arguments);
        
        // File tools
        case 'list_directory_content':
          return this.fileHandlers.handleListDirectoryContent(request.params.arguments);
        case 'get_file_content':
          return this.fileHandlers.handleGetFileContent(request.params.arguments);
        
        // Search tools
        case 'search_code':
          return this.searchHandlers.handleSearchCode(request.params.arguments);

        // Project tools
        case 'list_projects':
          return this.projectHandlers.handleListProjects(request.params.arguments);
        case 'list_repositories':
          return this.projectHandlers.handleListRepositories(request.params.arguments);

        default:
          throw new McpError(
            ErrorCode.MethodNotFound,
            `Unknown tool: ${request.params.name}`
          );
      }
    });
  }

  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error(`Bitbucket MCP server running on stdio (${this.apiClient.getIsServer() ? 'Server' : 'Cloud'} mode)`);
  }
}

const server = new BitbucketMCPServer();
server.run().catch(console.error);

```

--------------------------------------------------------------------------------
/src/handlers/project-handlers.ts:
--------------------------------------------------------------------------------

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { BitbucketApiClient } from '../utils/api-client.js';
import {
  isListProjectsArgs,
  isListRepositoriesArgs
} from '../types/guards.js';
import {
  BitbucketServerProject,
  BitbucketCloudProject,
  BitbucketServerRepository,
  BitbucketCloudRepository
} from '../types/bitbucket.js';

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

  async handleListProjects(args: any) {
    if (!isListProjectsArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for list_projects'
      );
    }

    const { name, permission, limit = 25, start = 0 } = args;

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

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API
        apiPath = `/rest/api/1.0/projects`;
        params = {
          limit,
          start
        };

        if (name) {
          params.name = name;
        }
        if (permission) {
          params.permission = permission;
        }

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

        // Format projects
        projects = (response.values || []).map((project: BitbucketServerProject) => ({
          key: project.key,
          id: project.id,
          name: project.name,
          description: project.description || '',
          is_public: project.public,
          type: project.type,
          url: `${this.baseUrl}/projects/${project.key}`
        }));

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

        // Cloud uses workspaces, not projects exactly
        const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });

        projects = (response.values || []).map((workspace: any) => ({
          key: workspace.slug,
          id: workspace.uuid,
          name: workspace.name,
          description: '',
          is_public: !workspace.is_private,
          type: 'WORKSPACE',
          url: workspace.links.html.href
        }));

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

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              projects,
              total_count: totalCount,
              start,
              limit,
              has_more: nextPageStart !== null,
              next_start: nextPageStart
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, 'listing projects');
    }
  }

  async handleListRepositories(args: any) {
    if (!isListRepositoriesArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for list_repositories'
      );
    }

    const { workspace, name, permission, limit = 25, start = 0 } = args;

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

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API
        if (workspace) {
          // List repos in a specific project
          apiPath = `/rest/api/1.0/projects/${workspace}/repos`;
        } else {
          // List all accessible repos
          apiPath = `/rest/api/1.0/repos`;
        }

        params = {
          limit,
          start
        };

        if (name) {
          params.name = name;
        }
        if (permission) {
          params.permission = permission;
        }
        if (!workspace && name) {
          // When listing all repos and filtering by name
          params.projectname = name;
        }

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

        // Format repositories
        repositories = (response.values || []).map((repo: BitbucketServerRepository) => ({
          slug: repo.slug,
          id: repo.id,
          name: repo.name,
          description: repo.description || '',
          project_key: repo.project.key,
          project_name: repo.project.name,
          state: repo.state,
          is_public: repo.public,
          is_forkable: repo.forkable,
          clone_urls: {
            http: repo.links.clone.find(c => c.name === 'http')?.href || '',
            ssh: repo.links.clone.find(c => c.name === 'ssh')?.href || ''
          },
          url: `${this.baseUrl}/projects/${repo.project.key}/repos/${repo.slug}`
        }));

        totalCount = response.size || repositories.length;
        if (!response.isLastPage && response.nextPageStart !== undefined) {
          nextPageStart = response.nextPageStart;
        }
      } else {
        // Bitbucket Cloud API
        if (workspace) {
          // List repos in a specific workspace
          apiPath = `/repositories/${workspace}`;
        } else {
          // Cloud doesn't support listing all repos without workspace
          // We'll return an error message
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: 'Bitbucket Cloud requires a workspace parameter to list repositories. Please provide a workspace.'
                }, null, 2),
              },
            ],
            isError: true,
          };
        }

        params = {
          pagelen: limit,
          page: Math.floor(start / limit) + 1
        };

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

        repositories = (response.values || []).map((repo: BitbucketCloudRepository) => ({
          slug: repo.slug,
          id: repo.uuid,
          name: repo.name,
          description: repo.description || '',
          project_key: repo.project?.key || '',
          project_name: repo.project?.name || '',
          state: 'AVAILABLE',
          is_public: !repo.is_private,
          is_forkable: true,
          clone_urls: {
            http: repo.links.clone.find(c => c.name === 'https')?.href || '',
            ssh: repo.links.clone.find(c => c.name === 'ssh')?.href || ''
          },
          url: repo.links.html.href
        }));

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

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              repositories,
              total_count: totalCount,
              start,
              limit,
              has_more: nextPageStart !== null,
              next_start: nextPageStart,
              workspace: workspace || 'all'
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, workspace ? `listing repositories in ${workspace}` : 'listing repositories');
    }
  }
}

```

--------------------------------------------------------------------------------
/src/utils/formatters.ts:
--------------------------------------------------------------------------------

```typescript
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();
}

```

--------------------------------------------------------------------------------
/src/handlers/review-handlers.ts:
--------------------------------------------------------------------------------

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { BitbucketApiClient } from '../utils/api-client.js';
import {
  isGetPullRequestDiffArgs,
  isApprovePullRequestArgs,
  isRequestChangesArgs
} from '../types/guards.js';
import { DiffParser } from '../utils/diff-parser.js';

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

  async handleGetPullRequestDiff(args: any) {
    if (!isGetPullRequestDiffArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for get_pull_request_diff'
      );
    }

    const { 
      workspace, 
      repository, 
      pull_request_id, 
      context_lines = 3,
      include_patterns,
      exclude_patterns,
      file_path
    } = args;

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

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

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

      // Check if filtering is needed
      const needsFiltering = file_path || include_patterns || exclude_patterns;
      
      if (!needsFiltering) {
        // Return raw diff without filtering
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                message: 'Pull request diff retrieved successfully',
                pull_request_id,
                diff: rawDiff
              }, null, 2),
            },
          ],
        };
      }

      // Apply filtering
      const diffParser = new DiffParser();
      const sections = diffParser.parseDiffIntoSections(rawDiff);
      
      const filterOptions = {
        includePatterns: include_patterns,
        excludePatterns: exclude_patterns,
        filePath: file_path
      };
      
      const filteredResult = diffParser.filterSections(sections, filterOptions);
      const filteredDiff = diffParser.reconstructDiff(filteredResult.sections);

      // Build response with filtering metadata
      const response: any = {
        message: 'Pull request diff retrieved successfully',
        pull_request_id,
        diff: filteredDiff
      };

      // Add filter metadata
      if (filteredResult.metadata.excludedFiles > 0 || file_path || include_patterns || exclude_patterns) {
        response.filter_metadata = {
          total_files: filteredResult.metadata.totalFiles,
          included_files: filteredResult.metadata.includedFiles,
          excluded_files: filteredResult.metadata.excludedFiles
        };

        if (filteredResult.metadata.excludedFileList.length > 0) {
          response.filter_metadata.excluded_file_list = filteredResult.metadata.excludedFileList;
        }

        response.filter_metadata.filters_applied = {};
        if (file_path) {
          response.filter_metadata.filters_applied.file_path = file_path;
        }
        if (include_patterns) {
          response.filter_metadata.filters_applied.include_patterns = include_patterns;
        }
        if (exclude_patterns) {
          response.filter_metadata.filters_applied.exclude_patterns = exclude_patterns;
        }
      }

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

  async handleApprovePullRequest(args: any) {
    if (!isApprovePullRequestArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for approve_pull_request'
      );
    }

    const { workspace, repository, pull_request_id } = args;

    try {
      let apiPath: string;

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API - use participants endpoint
        // Convert email format: @ to _ for the API
        const username = this.username.replace('@', '_');
        apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
        await this.apiClient.makeRequest<any>('put', apiPath, { status: 'APPROVED' });
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/approve`;
        await this.apiClient.makeRequest<any>('post', apiPath);
      }

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

  async handleUnapprovePullRequest(args: any) {
    if (!isApprovePullRequestArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for unapprove_pull_request'
      );
    }

    const { workspace, repository, pull_request_id } = args;

    try {
      let apiPath: string;

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API - use participants endpoint
        const username = this.username.replace('@', '_');
        apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
        await this.apiClient.makeRequest<any>('put', apiPath, { status: 'UNAPPROVED' });
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/approve`;
        await this.apiClient.makeRequest<any>('delete', apiPath);
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              message: 'Pull request approval removed successfully',
              pull_request_id,
              unapproved_by: this.username
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `removing approval from pull request ${pull_request_id} in ${workspace}/${repository}`);
    }
  }

  async handleRequestChanges(args: any) {
    if (!isRequestChangesArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for request_changes'
      );
    }

    const { workspace, repository, pull_request_id, comment } = args;

    try {
      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API - use needs-work status
        const username = this.username.replace('@', '_');
        const apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
        await this.apiClient.makeRequest<any>('put', apiPath, { status: 'NEEDS_WORK' });
        
        // Add comment if provided
        if (comment) {
          const commentPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/comments`;
          await this.apiClient.makeRequest<any>('post', commentPath, { text: comment });
        }
      } else {
        // Bitbucket Cloud API - use request-changes status
        const apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/request-changes`;
        await this.apiClient.makeRequest<any>('post', apiPath);
        
        // Add comment if provided
        if (comment) {
          const commentPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/comments`;
          await this.apiClient.makeRequest<any>('post', commentPath, {
            content: { raw: comment }
          });
        }
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              message: 'Changes requested on pull request',
              pull_request_id,
              requested_by: this.username,
              comment: comment || 'No comment provided'
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `requesting changes on pull request ${pull_request_id} in ${workspace}/${repository}`);
    }
  }

  async handleRemoveRequestedChanges(args: any) {
    if (!isApprovePullRequestArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for remove_requested_changes'
      );
    }

    const { workspace, repository, pull_request_id } = args;

    try {
      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API - remove needs-work status
        const username = this.username.replace('@', '_');
        const apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
        await this.apiClient.makeRequest<any>('put', apiPath, { status: 'UNAPPROVED' });
      } else {
        // Bitbucket Cloud API
        const apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/request-changes`;
        await this.apiClient.makeRequest<any>('delete', apiPath);
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              message: 'Change request removed from pull request',
              pull_request_id,
              removed_by: this.username
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `removing change request from pull request ${pull_request_id} in ${workspace}/${repository}`);
    }
  }
}

```

--------------------------------------------------------------------------------
/src/types/bitbucket.ts:
--------------------------------------------------------------------------------

```typescript
// Bitbucket Server API response types
export interface BitbucketServerPullRequest {
  id: number;
  version: number;
  title: string;
  description?: string;
  state: string;
  open: boolean;
  closed: boolean;
  createdDate: number;
  updatedDate: number;
  fromRef: {
    id: string;
    displayId: string;
    latestCommit: string;
    repository: {
      slug: string;
      name: string;
      project: {
        key: string;
      };
    };
  };
  toRef: {
    id: string;
    displayId: string;
    latestCommit: string;
    repository: {
      slug: string;
      name: string;
      project: {
        key: string;
      };
    };
  };
  locked: boolean;
  author: {
    user: {
      name: string;
      emailAddress: string;
      displayName: string;
    };
    role: string;
    approved: boolean;
    status: string;
  };
  reviewers: Array<{
    user: {
      name: string;
      emailAddress: string;
      displayName: string;
    };
    role: string;
    approved: boolean;
    status: string;
  }>;
  participants: Array<{
    user: {
      name: string;
      emailAddress: string;
      displayName: string;
    };
    role: string;
    approved: boolean;
    status: string;
  }>;
  links: {
    self: Array<{
      href: string;
    }>;
  };
  properties?: {
    mergeCommit?: {
      id: string;
      displayId: string;
    };
  };
}

// Bitbucket Server Activity types
export interface BitbucketServerActivity {
  id: number;
  createdDate: number;
  user: {
    name: string;
    emailAddress: string;
    displayName: string;
  };
  action: string;
  comment?: any;
  commit?: {
    id: string;
    displayId: string;
    message?: string;
  };
}

// Bitbucket Server Branch types
export interface BitbucketServerBranch {
  id: string;
  displayId: string;
  type: string;
  latestCommit: string;
  latestChangeset: string;
  isDefault: boolean;
  metadata?: {
    "com.atlassian.bitbucket.server.bitbucket-branch:latest-commit-metadata": {
      author: {
        name: string;
        emailAddress: string;
      };
      authorTimestamp: number;
      message: string;
    };
  };
}

// Bitbucket Server Directory Entry
export interface BitbucketServerDirectoryEntry {
  path: {
    name: string;
    toString: string;
  };
  type: 'FILE' | 'DIRECTORY';
  size?: number;
  contentId?: string;
}

// Bitbucket Cloud API response types
export interface BitbucketCloudPullRequest {
  id: number;
  title: string;
  description: string;
  state: string;
  author: {
    display_name: string;
    account_id: string;
  };
  source: {
    branch: {
      name: string;
    };
    repository: {
      full_name: string;
    };
  };
  destination: {
    branch: {
      name: string;
    };
    repository: {
      full_name: string;
    };
  };
  reviewers: Array<{
    display_name: string;
    account_id: string;
  }>;
  participants: Array<{
    user: {
      display_name: string;
      account_id: string;
    };
    role: string;
    approved: boolean;
  }>;
  created_on: string;
  updated_on: string;
  links: {
    html: {
      href: string;
    };
    self: {
      href: string;
    };
    diff: {
      href: string;
    };
  };
  merge_commit?: {
    hash: string;
  };
  close_source_branch: boolean;
  closed_by?: {
    display_name: string;
    account_id: string;
  };
}

// Bitbucket Cloud Branch types
export interface BitbucketCloudBranch {
  name: string;
  target: {
    hash: string;
    type: string;
    message: string;
    author: {
      raw: string;
      user?: {
        display_name: string;
        account_id: string;
      };
    };
    date: string;
  };
  type: string;
}

// Bitbucket Cloud Directory Entry
export interface BitbucketCloudDirectoryEntry {
  path: string;
  type: 'commit_file' | 'commit_directory';
  size?: number;
  commit?: {
    hash: string;
  };
  links?: {
    self: {
      href: string;
    };
    html: {
      href: string;
    };
  };
}

// Bitbucket Cloud File Metadata
export interface BitbucketCloudFileMetadata {
  path: string;
  size: number;
  encoding?: string;
  mimetype?: string;
  links: {
    self: {
      href: string;
    };
    html: {
      href: string;
    };
    download: {
      href: string;
    };
  };
  commit?: {
    hash: string;
    author?: {
      raw: string;
      user?: {
        display_name: string;
        account_id: string;
      };
    };
    date?: string;
    message?: string;
  };
}

// Merge info type for enhanced PR details
export interface MergeInfo {
  mergeCommitHash?: string;
  mergedBy?: string;
  mergedAt?: string;
  mergeCommitMessage?: string;
}

// Comment types
export interface BitbucketServerComment {
  id: number;
  version: number;
  text: string;
  author: {
    name: string;
    emailAddress: string;
    displayName: string;
  };
  createdDate: number;
  updatedDate: number;
  state?: 'OPEN' | 'RESOLVED';
  anchor?: {
    line: number;
    lineType: string;
    fileType: string;
    path: string;
  };
}

export interface BitbucketCloudComment {
  id: number;
  content: {
    raw: string;
    markup: string;
    html: string;
  };
  user: {
    display_name: string;
    account_id: string;
  };
  created_on: string;
  updated_on: string;
  deleted?: boolean;
  resolved?: boolean;
  inline?: {
    to: number;
    from?: number;
    path: string;
  };
}

// File change types
export interface BitbucketServerFileChange {
  path: {
    toString: string;
  };
  executable: boolean;
  percentUnchanged: number;
  type: string;
  nodeType: string;
  srcPath?: {
    toString: string;
  };
  linesAdded?: number;
  linesRemoved?: number;
}

export interface BitbucketCloudFileChange {
  path: string;
  type: 'added' | 'modified' | 'removed' | 'renamed';
  lines_added: number;
  lines_removed: number;
  old?: {
    path: string;
  };
}

// Formatted comment type for response
export interface FormattedComment {
  id: number;
  author: string;
  text: string;
  created_on: string;
  is_inline: boolean;
  file_path?: string;
  line_number?: number;
  state?: 'OPEN' | 'RESOLVED';
  parent_id?: number;  // For Bitbucket Cloud style replies
  replies?: FormattedComment[];  // For Bitbucket Server nested replies
}

// Formatted file change type for response
export interface FormattedFileChange {
  path: string;
  status: 'added' | 'modified' | 'removed' | 'renamed';
  old_path?: string;
}

// Types for code snippet matching
export interface CodeMatch {
  line_number: number;
  line_type: 'ADDED' | 'REMOVED' | 'CONTEXT';
  exact_content: string;
  preview: string;
  confidence: number;
  context: {
    lines_before: string[];
    lines_after: string[];
  };
  sequential_position?: number; // Position within diff (for ADDED lines)
  hunk_info?: {
    hunk_index: number;
    destination_start: number;
    line_in_hunk: number;
  };
}

export interface MultipleMatchesError {
  code: 'MULTIPLE_MATCHES_FOUND';
  message: string;
  occurrences: Array<{
    line_number: number;
    file_path: string;
    preview: string;
    confidence: number;
    line_type: 'ADDED' | 'REMOVED' | 'CONTEXT';
  }>;
  suggestion: string;
}

// Commit types
export interface BitbucketServerCommit {
  id: string;
  displayId: string;
  message: string;
  author: {
    name: string;
    emailAddress: string;
  };
  authorTimestamp: number;
  committer?: {
    name: string;
    emailAddress: string;
  };
  committerTimestamp?: number;
  parents: Array<{
    id: string;
    displayId: string;
  }>;
}

export interface BitbucketCloudCommit {
  hash: string;
  message: string;
  author: {
    raw: string;
    user?: {
      display_name: string;
      account_id: string;
    };
  };
  date: string;
  parents: Array<{
    hash: string;
    type: string;
  }>;
  links?: {
    self: {
      href: string;
    };
    html: {
      href: string;
    };
  };
}

export interface FormattedCommit {
  hash: string;
  abbreviated_hash: string;
  message: string;
  author: {
    name: string;
    email: string;
  };
  date: string;
  parents: string[];
  is_merge_commit: boolean;
  build_status?: BuildStatus;
}

// Search types
export interface BitbucketServerSearchRequest {
  query: string;
  entities: {
    code?: {
      start?: number;
      limit?: number;
    };
    commits?: {
      start?: number;
      limit?: number;
    };
    pull_requests?: {
      start?: number;
      limit?: number;
    };
  };
}

export interface BitbucketServerSearchResult {
  scope?: {
    repository?: {
      slug: string;
      name: string;
      project: {
        key: string;
        name: string;
      };
    };
    type: string;
  };
  code?: {
    category: string;
    isLastPage: boolean;
    count: number;
    start: number;
    nextStart?: number;
    values: Array<{
      file: string; // Just the file path as string
      repository: {
        slug: string;
        name: string;
        project: {
          key: string;
          name: string;
        };
      };
      hitContexts: Array<Array<{
        line: number;
        text: string; // HTML-formatted with <em> tags
      }>>;
      pathMatches: Array<any>;
      hitCount: number;
    }>;
  };
  query?: {
    substituted: boolean;
  };
}

export interface FormattedSearchResult {
  file_path: string;
  file_name: string;
  repository: string;
  project: string;
  matches: Array<{
    line_number: number;
    line_content: string;
    highlighted_segments: Array<{
      text: string;
      is_match: boolean;
    }>;
  }>;
}

// Build status types for Bitbucket Server
export interface BitbucketServerBuildSummary {
  [commitId: string]: {
    failed?: number;
    inProgress?: number;
    successful?: number;
    unknown?: number;
  };
}

export interface BuildStatus {
  successful: number;
  failed: number;
  in_progress: number;
  unknown: number;
}

// Project and Repository types
export interface BitbucketServerProject {
  key: string;
  id: number;
  name: string;
  description?: string;
  public: boolean;
  type: 'NORMAL' | 'PERSONAL';
  links: {
    self: Array<{
      href: string;
    }>;
  };
}

export interface BitbucketCloudProject {
  key: string;
  uuid: string;
  name: string;
  description?: string;
  is_private: boolean;
  links: {
    html: {
      href: string;
    };
  };
}

export interface BitbucketServerRepository {
  slug: string;
  id: number;
  name: string;
  description?: string;
  hierarchyId: string;
  scmId: string;
  state: 'AVAILABLE' | 'INITIALISING' | 'INITIALISATION_FAILED';
  statusMessage: string;
  forkable: boolean;
  project: {
    key: string;
    id: number;
    name: string;
    public: boolean;
    type: string;
  };
  public: boolean;
  links: {
    clone: Array<{
      href: string;
      name: string;
    }>;
    self: Array<{
      href: string;
    }>;
  };
}

export interface BitbucketCloudRepository {
  slug: string;
  uuid: string;
  name: string;
  full_name: string;
  description?: string;
  scm: string;
  is_private: boolean;
  owner: {
    display_name: string;
    uuid: string;
  };
  project: {
    key: string;
    name: string;
  };
  mainbranch?: {
    name: string;
    type: string;
  };
  links: {
    html: {
      href: string;
    };
    clone: Array<{
      href: string;
      name: string;
    }>;
  };
}

```

--------------------------------------------------------------------------------
/src/handlers/file-handlers.ts:
--------------------------------------------------------------------------------

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { BitbucketApiClient } from '../utils/api-client.js';
import {
  isListDirectoryContentArgs,
  isGetFileContentArgs
} from '../types/guards.js';
import {
  BitbucketServerDirectoryEntry,
  BitbucketCloudDirectoryEntry,
  BitbucketCloudFileMetadata
} from '../types/bitbucket.js';
import * as path from 'path';

export class FileHandlers {
  // Default lines by file extension
  private readonly DEFAULT_LINES_BY_EXT: Record<string, number> = {
    '.yml': 200, '.yaml': 200, '.json': 200,  // Config files
    '.md': 300, '.txt': 300,                   // Docs
    '.ts': 500, '.js': 500, '.py': 500,       // Code
    '.tsx': 500, '.jsx': 500, '.java': 500,   // More code
    '.log': -100  // Last 100 lines for logs
  };

  constructor(
    private apiClient: BitbucketApiClient,
    private baseUrl: string
  ) {}

  async handleListDirectoryContent(args: any) {
    if (!isListDirectoryContentArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for list_directory_content'
      );
    }

    const { workspace, repository, path: dirPath = '', branch } = args;

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

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API
        apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/browse`;
        if (dirPath) {
          apiPath += `/${dirPath}`;
        }
        if (branch) {
          params.at = `refs/heads/${branch}`;
        }
        
        response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });
      } else {
        // Bitbucket Cloud API
        const branchOrDefault = branch || 'HEAD';
        apiPath = `/repositories/${workspace}/${repository}/src/${branchOrDefault}`;
        if (dirPath) {
          apiPath += `/${dirPath}`;
        }
        
        response = await this.apiClient.makeRequest<any>('get', apiPath);
      }

      // Format the response
      let contents: any[] = [];
      let actualBranch = branch;

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server response
        const entries = response.children?.values || [];
        contents = entries.map((entry: BitbucketServerDirectoryEntry) => ({
          name: entry.path.name,
          type: entry.type === 'FILE' ? 'file' : 'directory',
          size: entry.size,
          path: dirPath ? `${dirPath}/${entry.path.name}` : entry.path.name
        }));
        
        // Get the actual branch from the response if available
        if (!branch && response.path?.components) {
          // Server returns default branch info in the response
          actualBranch = 'default';
        }
      } else {
        // Bitbucket Cloud response
        const entries = response.values || [];
        contents = entries.map((entry: BitbucketCloudDirectoryEntry) => ({
          name: entry.path.split('/').pop() || entry.path,
          type: entry.type === 'commit_file' ? 'file' : 'directory',
          size: entry.size,
          path: entry.path
        }));
        
        // Cloud returns the branch in the response
        actualBranch = branch || response.commit?.branch || 'main';
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              path: dirPath || '/',
              branch: actualBranch,
              contents,
              total_items: contents.length
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `listing directory '${dirPath}' in ${workspace}/${repository}`);
    }
  }

  async handleGetFileContent(args: any) {
    if (!isGetFileContentArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for get_file_content'
      );
    }

    const { workspace, repository, file_path, branch, start_line, line_count, full_content = false } = args;

    try {
      let fileContent: string;
      let fileMetadata: any = {};
      const fileSizeLimit = 1024 * 1024; // 1MB default limit

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server - get file metadata first to check size
        const browsePath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/browse/${file_path}`;
        const browseParams: any = {};
        if (branch) {
          browseParams.at = `refs/heads/${branch}`;
        }
        
        try {
          const metadataResponse = await this.apiClient.makeRequest<any>('get', browsePath, undefined, { params: browseParams });
          fileMetadata = {
            size: metadataResponse.size || 0,
            path: file_path
          };

          // Check file size
          if (!full_content && fileMetadata.size > fileSizeLimit) {
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify({
                    error: 'File too large',
                    file_path,
                    size: fileMetadata.size,
                    size_mb: (fileMetadata.size / (1024 * 1024)).toFixed(2),
                    message: `File exceeds size limit. Use full_content: true to force retrieval or use start_line/line_count for partial content.`
                  }, null, 2),
                },
              ],
              isError: true,
            };
          }
        } catch (e) {
          // If browse fails, continue to try raw endpoint
        }

        // Get raw content
        const rawPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/raw/${file_path}`;
        const rawParams: any = {};
        if (branch) {
          rawParams.at = `refs/heads/${branch}`;
        }
        
        const response = await this.apiClient.makeRequest<any>('get', rawPath, undefined, { 
          params: rawParams,
          responseType: 'text',
          headers: { 'Accept': 'text/plain' }
        });
        
        fileContent = response;
      } else {
        // Bitbucket Cloud - first get metadata
        const branchOrDefault = branch || 'HEAD';
        const metaPath = `/repositories/${workspace}/${repository}/src/${branchOrDefault}/${file_path}`;
        
        const metadataResponse = await this.apiClient.makeRequest<BitbucketCloudFileMetadata>('get', metaPath);
        
        fileMetadata = {
          size: metadataResponse.size,
          encoding: metadataResponse.encoding,
          path: metadataResponse.path,
          commit: metadataResponse.commit
        };

        // Check file size
        if (!full_content && fileMetadata.size > fileSizeLimit) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  error: 'File too large',
                  file_path,
                  size: fileMetadata.size,
                  size_mb: (fileMetadata.size / (1024 * 1024)).toFixed(2),
                  message: `File exceeds size limit. Use full_content: true to force retrieval or use start_line/line_count for partial content.`
                }, null, 2),
              },
            ],
            isError: true,
          };
        }

        // Follow the download link to get actual content
        const downloadUrl = metadataResponse.links.download.href;
        const downloadResponse = await this.apiClient.makeRequest<any>('get', downloadUrl, undefined, {
          baseURL: '', // Use full URL
          responseType: 'text',
          headers: { 'Accept': 'text/plain' }
        });
        
        fileContent = downloadResponse;
      }

      // Apply line filtering if requested
      let processedContent = fileContent;
      let lineInfo: any = null;

      if (!full_content || start_line !== undefined || line_count !== undefined) {
        const lines = fileContent.split('\n');
        const totalLines = lines.length;

        // Determine default line count based on file extension
        const ext = path.extname(file_path).toLowerCase();
        const defaultLineCount = this.DEFAULT_LINES_BY_EXT[ext] || 500;
        const shouldUseTail = defaultLineCount < 0;

        // Calculate start and end indices
        let startIdx: number;
        let endIdx: number;

        if (start_line !== undefined) {
          if (start_line < 0) {
            // Negative start_line means from end
            startIdx = Math.max(0, totalLines + start_line);
            endIdx = totalLines;
          } else {
            // 1-based to 0-based index
            startIdx = Math.max(0, start_line - 1);
            endIdx = startIdx + (line_count || Math.abs(defaultLineCount));
          }
        } else if (!full_content && fileMetadata.size > 50 * 1024) {
          // Auto-truncate large files
          if (shouldUseTail) {
            startIdx = Math.max(0, totalLines + defaultLineCount);
            endIdx = totalLines;
          } else {
            startIdx = 0;
            endIdx = Math.abs(defaultLineCount);
          }
        } else {
          // Return full content for small files
          startIdx = 0;
          endIdx = totalLines;
        }

        // Ensure indices are within bounds
        startIdx = Math.max(0, Math.min(startIdx, totalLines));
        endIdx = Math.max(startIdx, Math.min(endIdx, totalLines));

        // Extract the requested lines
        const selectedLines = lines.slice(startIdx, endIdx);
        processedContent = selectedLines.join('\n');

        lineInfo = {
          total_lines: totalLines,
          returned_lines: {
            start: startIdx + 1,
            end: endIdx
          },
          truncated: startIdx > 0 || endIdx < totalLines,
          message: endIdx < totalLines 
            ? `Showing lines ${startIdx + 1}-${endIdx} of ${totalLines}. File size: ${(fileMetadata.size / 1024).toFixed(1)}KB`
            : null
        };
      }

      // Build response
      const response: any = {
        file_path,
        branch: branch || (this.apiClient.getIsServer() ? 'default' : 'main'),
        size: fileMetadata.size || fileContent.length,
        encoding: fileMetadata.encoding || 'utf-8',
        content: processedContent
      };

      if (lineInfo) {
        response.line_info = lineInfo;
      }

      if (fileMetadata.commit) {
        response.last_modified = {
          commit_id: fileMetadata.commit.hash,
          author: fileMetadata.commit.author?.user?.display_name || fileMetadata.commit.author?.raw,
          date: fileMetadata.commit.date,
          message: fileMetadata.commit.message
        };
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(response, null, 2),
          },
        ],
      };
    } catch (error: any) {
      // Handle specific not found error
      if (error.status === 404) {
        return {
          content: [
            {
              type: 'text',
              text: `File '${file_path}' not found in ${workspace}/${repository}${branch ? ` on branch '${branch}'` : ''}`,
            },
          ],
          isError: true,
        };
      }
      return this.apiClient.handleApiError(error, `getting file content for '${file_path}' in ${workspace}/${repository}`);
    }
  }

  // Helper method to get default line count based on file extension
  private getDefaultLines(filePath: string, fileSize: number): { full: boolean } | { start: number; count: number } {
    // Small files: return full content
    if (fileSize < 50 * 1024) { // 50KB
      return { full: true };
    }
    
    const ext = path.extname(filePath).toLowerCase();
    const defaultLines = this.DEFAULT_LINES_BY_EXT[ext] || 500;
    
    return {
      start: defaultLines < 0 ? defaultLines : 1,
      count: Math.abs(defaultLines)
    };
  }
}

```

--------------------------------------------------------------------------------
/src/types/guards.ts:
--------------------------------------------------------------------------------

```typescript
// Type guards for tool arguments
export const isGetPullRequestArgs = (
  args: any
): args is { workspace: string; repository: string; pull_request_id: number } =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.pull_request_id === 'number';

export const isListPullRequestsArgs = (
  args: any
): args is { 
  workspace: string; 
  repository: string; 
  state?: string; 
  author?: string;
  limit?: number;
  start?: number;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  (args.state === undefined || typeof args.state === 'string') &&
  (args.author === undefined || typeof args.author === 'string') &&
  (args.limit === undefined || typeof args.limit === 'number') &&
  (args.start === undefined || typeof args.start === 'number');

export const isCreatePullRequestArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  title: string;
  source_branch: string;
  destination_branch: string;
  description?: string;
  reviewers?: string[];
  close_source_branch?: boolean;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.title === 'string' &&
  typeof args.source_branch === 'string' &&
  typeof args.destination_branch === 'string' &&
  (args.description === undefined || typeof args.description === 'string') &&
  (args.reviewers === undefined || Array.isArray(args.reviewers)) &&
  (args.close_source_branch === undefined || typeof args.close_source_branch === 'boolean');

export const isUpdatePullRequestArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  pull_request_id: number;
  title?: string;
  description?: string;
  destination_branch?: string;
  reviewers?: string[];
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.pull_request_id === 'number' &&
  (args.title === undefined || typeof args.title === 'string') &&
  (args.description === undefined || typeof args.description === 'string') &&
  (args.destination_branch === undefined || typeof args.destination_branch === 'string') &&
  (args.reviewers === undefined || Array.isArray(args.reviewers));

export const isAddCommentArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  pull_request_id: number;
  comment_text: string;
  parent_comment_id?: number;
  file_path?: string;
  line_number?: number;
  line_type?: 'ADDED' | 'REMOVED' | 'CONTEXT';
  suggestion?: string;
  suggestion_end_line?: number;
  code_snippet?: string;
  search_context?: {
    before?: string[];
    after?: string[];
  };
  match_strategy?: 'strict' | 'best';
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.pull_request_id === 'number' &&
  typeof args.comment_text === 'string' &&
  (args.parent_comment_id === undefined || typeof args.parent_comment_id === 'number') &&
  (args.file_path === undefined || typeof args.file_path === 'string') &&
  (args.line_number === undefined || typeof args.line_number === 'number') &&
  (args.line_type === undefined || ['ADDED', 'REMOVED', 'CONTEXT'].includes(args.line_type)) &&
  (args.suggestion === undefined || typeof args.suggestion === 'string') &&
  (args.suggestion_end_line === undefined || typeof args.suggestion_end_line === 'number') &&
  (args.code_snippet === undefined || typeof args.code_snippet === 'string') &&
  (args.search_context === undefined || (
    typeof args.search_context === 'object' &&
    (args.search_context.before === undefined || Array.isArray(args.search_context.before)) &&
    (args.search_context.after === undefined || Array.isArray(args.search_context.after))
  )) &&
  (args.match_strategy === undefined || ['strict', 'best'].includes(args.match_strategy));

export const isMergePullRequestArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  pull_request_id: number;
  merge_strategy?: string;
  close_source_branch?: boolean;
  commit_message?: string;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.pull_request_id === 'number' &&
  (args.merge_strategy === undefined || typeof args.merge_strategy === 'string') &&
  (args.close_source_branch === undefined || typeof args.close_source_branch === 'boolean') &&
  (args.commit_message === undefined || typeof args.commit_message === 'string');

export const isDeleteBranchArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  branch_name: string;
  force?: boolean;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.branch_name === 'string' &&
  (args.force === undefined || typeof args.force === 'boolean');

export const isListBranchesArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  filter?: string;
  limit?: number;
  start?: number;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  (args.filter === undefined || typeof args.filter === 'string') &&
  (args.limit === undefined || typeof args.limit === 'number') &&
  (args.start === undefined || typeof args.start === 'number');

export const isGetPullRequestDiffArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  pull_request_id: number;
  context_lines?: number;
  include_patterns?: string[];
  exclude_patterns?: string[];
  file_path?: string;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.pull_request_id === 'number' &&
  (args.context_lines === undefined || typeof args.context_lines === 'number') &&
  (args.include_patterns === undefined || (Array.isArray(args.include_patterns) && args.include_patterns.every((p: any) => typeof p === 'string'))) &&
  (args.exclude_patterns === undefined || (Array.isArray(args.exclude_patterns) && args.exclude_patterns.every((p: any) => typeof p === 'string'))) &&
  (args.file_path === undefined || typeof args.file_path === 'string');

export const isApprovePullRequestArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  pull_request_id: number;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.pull_request_id === 'number';

export const isRequestChangesArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  pull_request_id: number;
  comment?: string;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.pull_request_id === 'number' &&
  (args.comment === undefined || typeof args.comment === 'string');

export const isGetBranchArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  branch_name: string;
  include_merged_prs?: boolean;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.branch_name === 'string' &&
  (args.include_merged_prs === undefined || typeof args.include_merged_prs === 'boolean');

export const isListDirectoryContentArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  path?: string;
  branch?: string;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  (args.path === undefined || typeof args.path === 'string') &&
  (args.branch === undefined || typeof args.branch === 'string');

export const isGetFileContentArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  file_path: string;
  branch?: string;
  start_line?: number;
  line_count?: number;
  full_content?: boolean;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.file_path === 'string' &&
  (args.branch === undefined || typeof args.branch === 'string') &&
  (args.start_line === undefined || typeof args.start_line === 'number') &&
  (args.line_count === undefined || typeof args.line_count === 'number') &&
  (args.full_content === undefined || typeof args.full_content === 'boolean');

export const isListBranchCommitsArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  branch_name: string;
  limit?: number;
  start?: number;
  since?: string;
  until?: string;
  author?: string;
  include_merge_commits?: boolean;
  search?: string;
  include_build_status?: boolean;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.branch_name === 'string' &&
  (args.limit === undefined || typeof args.limit === 'number') &&
  (args.start === undefined || typeof args.start === 'number') &&
  (args.since === undefined || typeof args.since === 'string') &&
  (args.until === undefined || typeof args.until === 'string') &&
  (args.author === undefined || typeof args.author === 'string') &&
  (args.include_merge_commits === undefined || typeof args.include_merge_commits === 'boolean') &&
  (args.search === undefined || typeof args.search === 'string') &&
  (args.include_build_status === undefined || typeof args.include_build_status === 'boolean');

export const isListPrCommitsArgs = (
  args: any
): args is {
  workspace: string;
  repository: string;
  pull_request_id: number;
  limit?: number;
  start?: number;
  include_build_status?: boolean;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.repository === 'string' &&
  typeof args.pull_request_id === 'number' &&
  (args.limit === undefined || typeof args.limit === 'number') &&
  (args.start === undefined || typeof args.start === 'number') &&
  (args.include_build_status === undefined || typeof args.include_build_status === 'boolean');

export const isSearchCodeArgs = (
  args: any
): args is {
  workspace: string;
  repository?: string;
  search_query: string;
  file_pattern?: string;
  limit?: number;
  start?: number;
} =>
  typeof args === 'object' &&
  args !== null &&
  typeof args.workspace === 'string' &&
  typeof args.search_query === 'string' &&
  (args.repository === undefined || typeof args.repository === 'string') &&
  (args.file_pattern === undefined || typeof args.file_pattern === 'string') &&
  (args.limit === undefined || typeof args.limit === 'number') &&
  (args.start === undefined || typeof args.start === 'number');

export const isListProjectsArgs = (
  args: any
): args is {
  name?: string;
  permission?: string;
  limit?: number;
  start?: number;
} =>
  typeof args === 'object' &&
  args !== null &&
  (args.name === undefined || typeof args.name === 'string') &&
  (args.permission === undefined || typeof args.permission === 'string') &&
  (args.limit === undefined || typeof args.limit === 'number') &&
  (args.start === undefined || typeof args.start === 'number');

export const isListRepositoriesArgs = (
  args: any
): args is {
  workspace?: string;
  name?: string;
  permission?: string;
  limit?: number;
  start?: number;
} =>
  typeof args === 'object' &&
  args !== null &&
  (args.workspace === undefined || typeof args.workspace === 'string') &&
  (args.name === undefined || typeof args.name === 'string') &&
  (args.permission === undefined || typeof args.permission === 'string') &&
  (args.limit === undefined || typeof args.limit === 'number') &&
  (args.start === undefined || typeof args.start === 'number');

```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.2] - 2025-10-14

### Added
- **CI/CD build status support in `list_pr_commits` tool**:
  - Added `include_build_status` optional parameter to fetch build/CI status for pull request commits
  - Returns build status with counts: successful, failed, in_progress, and unknown builds
  - Uses same Bitbucket Server UI API endpoint as `list_branch_commits` for consistency
  - Graceful degradation: failures in fetching build status don't break commit listing
  - Currently only supports Bitbucket Server (Cloud has different build status APIs)
  - Useful for tracking CI/CD pipeline status for all commits in a pull request

### Changed
- Enhanced README.md with comprehensive documentation for new v1.1.0 features:
  - Added usage examples and documentation for `include_build_status` parameter in `list_branch_commits` tool
  - Added complete documentation for `list_projects` tool with examples, parameters, and response format
  - Added complete documentation for `list_repositories` tool with examples, parameters, and response format
  - Improved feature list to include new Project and Repository Discovery Tools section

## [1.1.0] - 2025-10-14

### Added
- **CI/CD build status support in `list_branch_commits` tool**:
  - Added `include_build_status` optional parameter to fetch build/CI status for commits
  - Returns build status with counts: successful, failed, in_progress, and unknown builds
  - Uses Bitbucket Server UI API endpoint for efficient batch fetching of build summaries
  - Graceful degradation: failures in fetching build status don't break commit listing
  - Currently only supports Bitbucket Server (Cloud has different build status APIs)
  - Useful for tracking CI/CD pipeline status alongside commit history

- **New `list_projects` tool for project/workspace discovery**:
  - List all accessible Bitbucket projects (Server) or workspaces (Cloud)
  - Optional filtering by project name and permission level
  - Returns project metadata: key, ID, name, description, visibility, and type
  - Pagination support with `limit` and `start` parameters
  - Works with both Bitbucket Server and Cloud with unified response format

- **New `list_repositories` tool for repository discovery**:
  - List repositories within a specific project/workspace or across all accessible repos
  - Optional filtering by repository name and permission level
  - Returns comprehensive repository details:
    - Basic info: slug, ID, name, description, state
    - Project association: project key and name
    - Clone URLs for both HTTP(S) and SSH
    - Repository settings: visibility, forkable status
  - Pagination support with `limit` and `start` parameters
  - Bitbucket Cloud requires workspace parameter (documented in response)
  - Bitbucket Server supports listing all accessible repos without workspace filter

### Changed
- Added `ProjectHandlers` class following the modular architecture pattern
- Enhanced TypeScript interfaces with project and repository types:
  - `BitbucketServerProject` and `BitbucketCloudProject`
  - `BitbucketServerRepository` and `BitbucketCloudRepository`
  - `BitbucketServerBuildSummary` and `BuildStatus`
- Added custom params serializer for multiple `commitId` parameters in build status API
- Enhanced `FormattedCommit` interface with optional `build_status` field
- Updated API client with `getBuildSummaries` method for batch build status fetching

## [1.0.1] - 2025-08-08

### Fixed
- **Improved search_code tool response formatting**:
  - Added simplified `formatCodeSearchOutput` for cleaner AI consumption
  - Enhanced HTML entity decoding (handles &quot;, &lt;, &gt;, &amp;, &#x2F;, &#x27;)
  - Improved response structure showing file paths and line numbers clearly
  - Removed HTML formatting tags for better readability

### Changed
- Search results now use simplified formatter by default for better AI tool integration
- Enhanced query display to show actual search patterns used

## [1.0.0] - 2025-07-25

### Added
- **New `search_code` tool for searching code across repositories**:
  - Search for code snippets, functions, or any text within Bitbucket repositories
  - Supports searching within a specific repository or across all repositories in a workspace
  - File path pattern filtering with glob patterns (e.g., `*.java`, `src/**/*.ts`)
  - Returns matched lines with highlighted segments showing exact matches
  - Pagination support for large result sets
  - Currently only supports Bitbucket Server (Cloud API support planned for future)
- Added `SearchHandlers` class following the modular architecture pattern
- Added TypeScript interfaces for search requests and responses
- Added `formatSearchResults` formatter function for consistent output

### Changed
- Major version bump to 1.0.0 indicating stable API with comprehensive feature set
- Enhanced documentation with search examples

## [0.10.0] - 2025-07-03

### Added
- **New `list_branch_commits` tool for retrieving commit history**:
  - List all commits in a specific branch with detailed information
  - Advanced filtering options:
    - `since` and `until` parameters for date range filtering (ISO date strings)
    - `author` parameter to filter by author email/username
    - `include_merge_commits` parameter to include/exclude merge commits (default: true)
    - `search` parameter to search in commit messages
  - Returns branch head information and paginated commit list
  - Each commit includes hash, message, author details, date, parents, and merge status
  - Supports both Bitbucket Server and Cloud APIs with appropriate parameter mapping
  - Useful for reviewing commit history, tracking changes, and analyzing branch activity

- **New `list_pr_commits` tool for pull request commits**:
  - List all commits that are part of a specific pull request
  - Returns PR title and paginated commit list
  - Simpler than branch commits - focused specifically on PR changes
  - Each commit includes same detailed information as branch commits
  - Supports pagination with `limit` and `start` parameters
  - Useful for reviewing all changes in a PR before merging

### Changed
- Added new TypeScript interfaces for commit types:
  - `BitbucketServerCommit` and `BitbucketCloudCommit` for API responses
  - `FormattedCommit` for consistent commit representation
- Added formatter functions `formatServerCommit` and `formatCloudCommit` for unified output
- Enhanced type guards with `isListBranchCommitsArgs` and `isListPrCommitsArgs`

## [0.9.1] - 2025-01-27

### Fixed
- **Fixed `update_pull_request` reviewer preservation**:
  - When updating a PR without specifying reviewers, existing reviewers are now preserved
  - Previously, omitting the `reviewers` parameter would clear all reviewers
  - Now properly includes existing reviewers in the API request when not explicitly updating them
  - When updating reviewers, approval status is preserved for existing reviewers
  - This prevents accidentally removing reviewers when only updating PR title or description

### Changed
- Updated tool documentation to clarify reviewer behavior in `update_pull_request`
- Enhanced README with detailed explanation of reviewer handling

## [0.9.0] - 2025-01-26

### Added
- **Code snippet support in `add_comment` tool**:
  - Added `code_snippet` parameter to find line numbers automatically using code text
  - Added `search_context` parameter with `before` and `after` arrays to disambiguate multiple matches
  - Added `match_strategy` parameter with options:
    - `"strict"` (default): Fails with detailed error when multiple matches found
    - `"best"`: Auto-selects the highest confidence match
  - Returns detailed error with all occurrences when multiple matches found in strict mode
  - Particularly useful for AI-powered code review tools that analyze diffs
- Created comprehensive line matching algorithm that:
  - Parses diffs to find exact code snippets
  - Calculates confidence scores based on context matching
  - Handles added, removed, and context lines appropriately

### Changed
- Enhanced `add_comment` tool to resolve line numbers from code snippets when `line_number` is not provided
- Improved error messages to include preview and suggestions for resolving ambiguous matches

## [0.8.0] - 2025-01-26

### Added
- **Code suggestions support in `add_comment` tool**:
  - Added `suggestion` parameter to add code suggestions in comments
  - Added `suggestion_end_line` parameter for multi-line suggestions
  - Suggestions are formatted using GitHub-style markdown ````suggestion` blocks
  - Works with both single-line and multi-line code replacements
  - Requires `file_path` and `line_number` to be specified when using suggestions
  - Compatible with both Bitbucket Cloud and Server
- Created `suggestion-formatter.ts` utility for formatting suggestion comments

### Changed
- Enhanced `add_comment` tool to validate suggestion requirements
- Updated tool response to indicate when a comment contains a suggestion

## [0.7.0] - 2025-01-26

### Added
- **Enhanced `get_pull_request_diff` with filtering capabilities**:
  - Added `include_patterns` parameter to filter diff by file patterns (whitelist)
  - Added `exclude_patterns` parameter to exclude files from diff (blacklist)
  - Added `file_path` parameter to get diff for a specific file only
  - Patterns support standard glob syntax (e.g., `*.js`, `src/**/*.res`, `node_modules/**`)
  - Response includes filtering metadata showing total files, included/excluded counts, and excluded file list
- Added `minimatch` dependency for glob pattern matching
- Created `DiffParser` utility class for parsing and filtering unified diff format

### Changed
- Modified `get_pull_request_diff` tool to support optional filtering without breaking existing usage
- Updated tool definition and type guards to include new optional parameters
- Enhanced documentation with comprehensive examples of filtering usage

## [0.6.1] - 2025-01-26

### Added
- Support for nested comment replies in Bitbucket Server
  - Added `replies` field to `FormattedComment` interface to support nested comment threads
  - Comments now include nested replies that are still relevant (not orphaned or resolved)
  - Total and active comment counts now include nested replies

### Changed
- Updated comment fetching logic to handle Bitbucket Server's nested comment structure
  - Server uses `comments` array inside each comment object for replies
  - Cloud continues to use `parent` field for reply relationships
- Improved comment filtering to exclude orphaned inline comments when code has changed

### Fixed
- Fixed missing comment replies in PR details - replies are now properly included in the response

## [0.6.0] - 2025-01-26

### Added
- **Enhanced `get_pull_request` with active comments and file changes**:
  - Fetches and displays active (unresolved) comments that need attention
  - Shows up to 20 most recent active comments with:
    - Comment text, author, and creation date
    - Inline comment details (file path and line number)
    - Comment state (OPEN/RESOLVED for Server)
  - Provides comment counts:
    - `active_comment_count`: Total unresolved comments
    - `total_comment_count`: Total comments including resolved
  - Includes file change statistics:
    - List of all modified files with lines added/removed
    - File status (added, modified, removed, renamed)
    - Summary statistics (total files, lines added/removed)
- Added new TypeScript interfaces for comments and file changes
- Added `FormattedComment` and `FormattedFileChange` types for consistent response format

### Changed
- Modified `handleGetPullRequest` to make parallel API calls for better performance
- Enhanced error handling to gracefully continue if comment/file fetching fails

## [0.5.0] - 2025-01-21

### Added
- **New file and directory handling tools**:
  - `list_directory_content` - List files and directories in any repository path
    - Shows file/directory type, size, and full paths
    - Supports browsing specific branches
    - Works with both Bitbucket Server and Cloud APIs
  - `get_file_content` - Retrieve file content with smart truncation for large files
    - Automatic smart defaults by file type (config: 200 lines, docs: 300 lines, code: 500 lines)
    - Pagination support with `start_line` and `line_count` parameters
    - Tail functionality using negative `start_line` values (e.g., -50 for last 50 lines)
    - Automatic truncation for files >50KB to prevent token overload
    - Files >1MB require explicit `full_content: true` parameter
    - Returns metadata including file size, encoding, and last modified info
- Added `FileHandlers` class following existing modular architecture patterns
- Added TypeScript interfaces for file/directory entries and metadata
- Added type guards `isListDirectoryContentArgs` and `isGetFileContentArgs`

### Changed
- Enhanced documentation with comprehensive examples for file handling tools

## [0.4.0] - 2025-01-21

### Added
- **New `get_branch` tool for comprehensive branch information**:
  - Returns detailed branch information including name, ID, and latest commit details
  - Lists all open pull requests originating from the branch with approval status
  - Optionally includes merged pull requests when `include_merged_prs` is true
  - Provides useful statistics like PR counts and days since last commit
  - Supports both Bitbucket Server and Cloud APIs
  - Particularly useful for checking if a branch has open PRs before deletion
- Added TypeScript interfaces for `BitbucketServerBranch` and `BitbucketCloudBranch`
- Added type guard `isGetBranchArgs` for input validation

### Changed
- Updated documentation to include the new `get_branch` tool with comprehensive examples

## [0.3.0] - 2025-01-06

### Added
- **Enhanced merge commit details in `get_pull_request`**:
  - Added `merge_commit_hash` field for both Cloud and Server
  - Added `merged_by` field showing who performed the merge
  - Added `merged_at` timestamp for when the merge occurred
  - Added `merge_commit_message` with the merge commit message
  - For Bitbucket Server: Fetches merge details from activities API when PR is merged
  - For Bitbucket Cloud: Extracts merge information from existing response fields

### Changed
- **Major code refactoring for better maintainability**:
  - Split monolithic `index.ts` into modular architecture
  - Created separate handler classes for different tool categories:
    - `PullRequestHandlers` for PR lifecycle operations
    - `BranchHandlers` for branch management
    - `ReviewHandlers` for code review tools
  - Extracted types into dedicated files (`types/bitbucket.ts`, `types/guards.ts`)
  - Created utility modules (`utils/api-client.ts`, `utils/formatters.ts`)
  - Centralized tool definitions in `tools/definitions.ts`
- Improved error handling and API client abstraction
- Better separation of concerns between Cloud and Server implementations

### Fixed
- Improved handling of merge commit information retrieval failures
- Fixed API parameter passing for GET requests across all handlers (was passing config as third parameter instead of fourth)
- Updated Bitbucket Server branch listing to use `/rest/api/latest/` endpoint with proper parameters
- Branch filtering now works correctly with the `filterText` parameter for Bitbucket Server

## [0.2.0] - 2025-06-04

### Added
- Complete implementation of all Bitbucket MCP tools
- Support for both Bitbucket Cloud and Server
- Core PR lifecycle tools:
  - `create_pull_request` - Create new pull requests
  - `update_pull_request` - Update PR details
  - `merge_pull_request` - Merge pull requests
  - `list_branches` - List repository branches
  - `delete_branch` - Delete branches
- Enhanced `add_comment` with inline comment support
- Code review tools:
  - `get_pull_request_diff` - Get PR diff/changes
  - `approve_pull_request` - Approve PRs
  - `unapprove_pull_request` - Remove approval
  - `request_changes` - Request changes on PRs
  - `remove_requested_changes` - Remove change requests
- npm package configuration for easy installation via npx

### Fixed
- Author filter for Bitbucket Server (uses `role.1=AUTHOR` and `username.1=email`)
- Branch deletion handling for 204 No Content responses

### Changed
- Package name to `@nexus2520/bitbucket-mcp-server` for npm publishing

## [0.1.0] - 2025-06-03

### Added
- Initial implementation with basic tools:
  - `get_pull_request` - Get PR details
  - `list_pull_requests` - List PRs with filters
- Support for Bitbucket Cloud with app passwords
- Support for Bitbucket Server with HTTP access tokens
- Authentication setup script
- Comprehensive documentation

```

--------------------------------------------------------------------------------
/src/handlers/branch-handlers.ts:
--------------------------------------------------------------------------------

```typescript
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import { BitbucketApiClient } from '../utils/api-client.js';
import {
  isListBranchesArgs,
  isDeleteBranchArgs,
  isGetBranchArgs,
  isListBranchCommitsArgs
} from '../types/guards.js';
import { 
  BitbucketServerBranch, 
  BitbucketCloudBranch,
  BitbucketServerCommit,
  BitbucketCloudCommit,
  FormattedCommit
} from '../types/bitbucket.js';
import { formatServerCommit, formatCloudCommit } from '../utils/formatters.js';

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

  async handleListBranches(args: any) {
    if (!isListBranchesArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for list_branches'
      );
    }

    const { workspace, repository, filter, limit = 25, start = 0 } = args;

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

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server API - using latest version for better filtering support
        apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/branches`;
        params = {
          limit,
          start,
          details: true,
          orderBy: 'MODIFICATION'
        };
        if (filter) {
          params.filterText = filter;
        }
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/refs/branches`;
        params = {
          pagelen: limit,
          page: Math.floor(start / limit) + 1,
        };
        if (filter) {
          params.q = `name ~ "${filter}"`;
        }
      }

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

      // Format the response
      let branches: any[] = [];
      let totalCount = 0;
      let nextPageStart = null;

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server response
        branches = (response.values || []).map((branch: any) => ({
          name: branch.displayId,
          id: branch.id,
          latest_commit: branch.latestCommit,
          is_default: branch.isDefault || false
        }));
        totalCount = response.size || 0;
        if (!response.isLastPage && response.nextPageStart !== undefined) {
          nextPageStart = response.nextPageStart;
        }
      } else {
        // Bitbucket Cloud response
        branches = (response.values || []).map((branch: any) => ({
          name: branch.name,
          target: branch.target.hash,
          is_default: branch.name === 'main' || branch.name === 'master'
        }));
        totalCount = response.size || 0;
        if (response.next) {
          nextPageStart = start + limit;
        }
      }

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

  async handleDeleteBranch(args: any) {
    if (!isDeleteBranchArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for delete_branch'
      );
    }

    const { workspace, repository, branch_name, force } = args;

    try {
      let apiPath: string;

      if (this.apiClient.getIsServer()) {
        // First, we need to get the branch details to find the latest commit
        const branchesPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/branches`;
        const branchesResponse = await this.apiClient.makeRequest<any>('get', branchesPath, undefined, {
          params: {
            filterText: branch_name,
            limit: 100
          }
        });
        
        // Find the exact branch
        const branch = branchesResponse.values?.find((b: any) => b.displayId === branch_name);
        if (!branch) {
          throw new Error(`Branch '${branch_name}' not found`);
        }
        
        // Now delete using branch-utils endpoint with correct format
        apiPath = `/rest/branch-utils/latest/projects/${workspace}/repos/${repository}/branches`;
        
        try {
          await this.apiClient.makeRequest<any>('delete', apiPath, {
            name: branch_name,
            endPoint: branch.latestCommit
          });
        } catch (deleteError: any) {
          // If the error is about empty response but status is 204 (No Content), it's successful
          if (deleteError.originalError?.response?.status === 204 || 
              deleteError.message?.includes('No content to map')) {
            // Branch was deleted successfully
          } else {
            throw deleteError;
          }
        }
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/refs/branches/${branch_name}`;
        try {
          await this.apiClient.makeRequest<any>('delete', apiPath);
        } catch (deleteError: any) {
          // If the error is about empty response but status is 204 (No Content), it's successful
          if (deleteError.originalError?.response?.status === 204 || 
              deleteError.message?.includes('No content to map')) {
            // Branch was deleted successfully
          } else {
            throw deleteError;
          }
        }
      }

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              message: `Branch '${branch_name}' deleted successfully`,
              branch: branch_name,
              repository: `${workspace}/${repository}`
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `deleting branch '${branch_name}' in ${workspace}/${repository}`);
    }
  }

  async handleGetBranch(args: any) {
    if (!isGetBranchArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for get_branch'
      );
    }

    const { workspace, repository, branch_name, include_merged_prs = false } = args;

    try {
      // Step 1: Get branch details
      let branchInfo: any;
      let branchCommitInfo: any = {};

      if (this.apiClient.getIsServer()) {
        // Bitbucket Server - get branch details
        const branchesPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/branches`;
        const branchesResponse = await this.apiClient.makeRequest<any>('get', branchesPath, undefined, {
          params: {
            filterText: branch_name,
            limit: 100,
            details: true
          }
        });
        
        // Find the exact branch
        const branch = branchesResponse.values?.find((b: BitbucketServerBranch) => b.displayId === branch_name);
        if (!branch) {
          throw new Error(`Branch '${branch_name}' not found`);
        }

        branchInfo = {
          name: branch.displayId,
          id: branch.id,
          latest_commit: {
            id: branch.latestCommit,
            message: branch.metadata?.['com.atlassian.bitbucket.server.bitbucket-branch:latest-commit-metadata']?.message || null,
            author: branch.metadata?.['com.atlassian.bitbucket.server.bitbucket-branch:latest-commit-metadata']?.author || null,
            date: branch.metadata?.['com.atlassian.bitbucket.server.bitbucket-branch:latest-commit-metadata']?.authorTimestamp 
              ? new Date(branch.metadata['com.atlassian.bitbucket.server.bitbucket-branch:latest-commit-metadata'].authorTimestamp).toISOString()
              : null
          },
          is_default: branch.isDefault || false
        };
      } else {
        // Bitbucket Cloud - get branch details
        const branchPath = `/repositories/${workspace}/${repository}/refs/branches/${encodeURIComponent(branch_name)}`;
        const branch = await this.apiClient.makeRequest<BitbucketCloudBranch>('get', branchPath);

        branchInfo = {
          name: branch.name,
          id: `refs/heads/${branch.name}`,
          latest_commit: {
            id: branch.target.hash,
            message: branch.target.message,
            author: branch.target.author.user?.display_name || branch.target.author.raw,
            date: branch.target.date
          },
          is_default: false // Will check this with default branch info
        };

        // Check if this is the default branch
        try {
          const repoPath = `/repositories/${workspace}/${repository}`;
          const repoInfo = await this.apiClient.makeRequest<any>('get', repoPath);
          branchInfo.is_default = branch.name === repoInfo.mainbranch?.name;
        } catch (e) {
          // Ignore error, just assume not default
        }
      }

      // Step 2: Get open PRs from this branch
      let openPRs: any[] = [];
      
      if (this.apiClient.getIsServer()) {
        // Bitbucket Server
        const prPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests`;
        const prResponse = await this.apiClient.makeRequest<any>('get', prPath, undefined, {
          params: {
            state: 'OPEN',
            direction: 'OUTGOING',
            at: `refs/heads/${branch_name}`,
            limit: 100
          }
        });

        openPRs = (prResponse.values || []).map((pr: any) => ({
          id: pr.id,
          title: pr.title,
          destination_branch: pr.toRef.displayId,
          author: pr.author.user.displayName,
          created_on: new Date(pr.createdDate).toISOString(),
          reviewers: pr.reviewers.map((r: any) => r.user.displayName),
          approval_status: {
            approved_by: pr.reviewers.filter((r: any) => r.approved).map((r: any) => r.user.displayName),
            changes_requested_by: pr.reviewers.filter((r: any) => r.status === 'NEEDS_WORK').map((r: any) => r.user.displayName),
            pending: pr.reviewers.filter((r: any) => !r.approved && r.status !== 'NEEDS_WORK').map((r: any) => r.user.displayName)
          },
          url: `${this.baseUrl}/projects/${workspace}/repos/${repository}/pull-requests/${pr.id}`
        }));
      } else {
        // Bitbucket Cloud
        const prPath = `/repositories/${workspace}/${repository}/pullrequests`;
        const prResponse = await this.apiClient.makeRequest<any>('get', prPath, undefined, {
          params: {
            state: 'OPEN',
            q: `source.branch.name="${branch_name}"`,
            pagelen: 50
          }
        });

        openPRs = (prResponse.values || []).map((pr: any) => ({
          id: pr.id,
          title: pr.title,
          destination_branch: pr.destination.branch.name,
          author: pr.author.display_name,
          created_on: pr.created_on,
          reviewers: pr.reviewers.map((r: any) => r.display_name),
          approval_status: {
            approved_by: pr.participants.filter((p: any) => p.approved).map((p: any) => p.user.display_name),
            changes_requested_by: [], // Cloud doesn't have explicit "changes requested" status
            pending: pr.reviewers.filter((r: any) => !pr.participants.find((p: any) => p.user.account_id === r.account_id && p.approved))
              .map((r: any) => r.display_name)
          },
          url: pr.links.html.href
        }));
      }

      // Step 3: Optionally get merged PRs
      let mergedPRs: any[] = [];
      
      if (include_merged_prs) {
        if (this.apiClient.getIsServer()) {
          // Bitbucket Server
          const mergedPrPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests`;
          const mergedPrResponse = await this.apiClient.makeRequest<any>('get', mergedPrPath, undefined, {
            params: {
              state: 'MERGED',
              direction: 'OUTGOING',
              at: `refs/heads/${branch_name}`,
              limit: 25
            }
          });

          mergedPRs = (mergedPrResponse.values || []).map((pr: any) => ({
            id: pr.id,
            title: pr.title,
            merged_at: new Date(pr.updatedDate).toISOString(), // Using updated date as merge date
            merged_by: pr.participants.find((p: any) => p.role === 'PARTICIPANT' && p.approved)?.user.displayName || 'Unknown'
          }));
        } else {
          // Bitbucket Cloud
          const mergedPrPath = `/repositories/${workspace}/${repository}/pullrequests`;
          const mergedPrResponse = await this.apiClient.makeRequest<any>('get', mergedPrPath, undefined, {
            params: {
              state: 'MERGED',
              q: `source.branch.name="${branch_name}"`,
              pagelen: 25
            }
          });

          mergedPRs = (mergedPrResponse.values || []).map((pr: any) => ({
            id: pr.id,
            title: pr.title,
            merged_at: pr.updated_on,
            merged_by: pr.closed_by?.display_name || 'Unknown'
          }));
        }
      }

      // Step 4: Calculate statistics
      const daysSinceLastCommit = branchInfo.latest_commit.date 
        ? Math.floor((Date.now() - new Date(branchInfo.latest_commit.date).getTime()) / (1000 * 60 * 60 * 24))
        : null;

      // Step 5: Format and return combined response
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              branch: branchInfo,
              open_pull_requests: openPRs,
              merged_pull_requests: mergedPRs,
              statistics: {
                total_open_prs: openPRs.length,
                total_merged_prs: mergedPRs.length,
                days_since_last_commit: daysSinceLastCommit
              }
            }, null, 2),
          },
        ],
      };
    } catch (error: any) {
      // Handle specific not found error
      if (error.message?.includes('not found')) {
        return {
          content: [
            {
              type: 'text',
              text: `Branch '${branch_name}' not found in ${workspace}/${repository}`,
            },
          ],
          isError: true,
        };
      }
      return this.apiClient.handleApiError(error, `getting branch '${branch_name}' in ${workspace}/${repository}`);
    }
  }

  async handleListBranchCommits(args: any) {
    if (!isListBranchCommitsArgs(args)) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Invalid arguments for list_branch_commits'
      );
    }

    const {
      workspace,
      repository,
      branch_name,
      limit = 25,
      start = 0,
      since,
      until,
      author,
      include_merge_commits = true,
      search,
      include_build_status = false
    } = args;

    try {
      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}/commits`;
        params = {
          until: `refs/heads/${branch_name}`,
          limit,
          start,
          withCounts: true
        };

        // Add filters
        if (since) {
          params.since = since;
        }
        if (!include_merge_commits) {
          params.merges = 'exclude';
        }

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

        // Format commits
        commits = (response.values || []).map((commit: BitbucketServerCommit) => formatServerCommit(commit));
        
        // Apply client-side filters for Server API
        if (author) {
          // Filter by author email or name
          commits = commits.filter(c => 
            c.author.email === author || 
            c.author.name === author ||
            c.author.email.toLowerCase() === author.toLowerCase() ||
            c.author.name.toLowerCase() === author.toLowerCase()
          );
        }
        
        // Filter by date if 'until' is provided (Server API doesn't support 'until' param directly)
        if (until) {
          const untilDate = new Date(until).getTime();
          commits = commits.filter(c => new Date(c.date).getTime() <= untilDate);
        }

        // Filter by message search if provided
        if (search) {
          const searchLower = search.toLowerCase();
          commits = commits.filter(c => c.message.toLowerCase().includes(searchLower));
        }

        // If we applied client-side filters, update the total count
        if (author || until || search) {
          totalCount = commits.length;
          // Can't determine if there are more results when filtering client-side
          nextPageStart = null;
        } else {
          totalCount = response.size || commits.length;
          if (!response.isLastPage && response.nextPageStart !== undefined) {
            nextPageStart = response.nextPageStart;
          }
        }
      } else {
        // Bitbucket Cloud API
        apiPath = `/repositories/${workspace}/${repository}/commits/${encodeURIComponent(branch_name)}`;
        params = {
          pagelen: limit,
          page: Math.floor(start / limit) + 1
        };

        // Build query string for filters
        const queryParts: string[] = [];
        if (author) {
          queryParts.push(`author.raw ~ "${author}"`);
        }
        if (!include_merge_commits) {
          // Cloud API doesn't have direct merge exclusion, we'll filter client-side
        }
        if (queryParts.length > 0) {
          params.q = queryParts.join(' AND ');
        }

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

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

        // Apply client-side filters
        if (!include_merge_commits) {
          cloudCommits = cloudCommits.filter((c: FormattedCommit) => !c.is_merge_commit);
        }
        if (since) {
          const sinceDate = new Date(since).getTime();
          cloudCommits = cloudCommits.filter((c: FormattedCommit) => new Date(c.date).getTime() >= sinceDate);
        }
        if (until) {
          const untilDate = new Date(until).getTime();
          cloudCommits = cloudCommits.filter((c: FormattedCommit) => new Date(c.date).getTime() <= untilDate);
        }
        if (search) {
          const searchLower = search.toLowerCase();
          cloudCommits = cloudCommits.filter((c: FormattedCommit) => c.message.toLowerCase().includes(searchLower));
        }

        commits = cloudCommits;
        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 {
          // Extract commit hashes (use full hash, not abbreviated)
          const commitIds = commits.map(c => c.hash);

          // Fetch build summaries for all commits
          const buildSummaries = await this.apiClient.getBuildSummaries(
            workspace,
            repository,
            commitIds
          );

          // Merge build status into commits
          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) {
          // Gracefully degrade - log error but don't fail the entire request
          console.error('Failed to fetch build status:', error);
        }
      }

      // Get branch head info
      let branchHead: string | null = null;
      try {
        if (this.apiClient.getIsServer()) {
          const branchesPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/branches`;
          const branchesResponse = await this.apiClient.makeRequest<any>('get', branchesPath, undefined, {
            params: { filterText: branch_name, limit: 1 }
          });
          const branch = branchesResponse.values?.find((b: any) => b.displayId === branch_name);
          branchHead = branch?.latestCommit || null;
        } else {
          const branchPath = `/repositories/${workspace}/${repository}/refs/branches/${encodeURIComponent(branch_name)}`;
          const branch = await this.apiClient.makeRequest<any>('get', branchPath);
          branchHead = branch.target?.hash || null;
        }
      } catch (e) {
        // Ignore error, branch head is optional
      }

      // Build filters applied summary
      const filtersApplied: any = {};
      if (author) filtersApplied.author = author;
      if (since) filtersApplied.since = since;
      if (until) filtersApplied.until = until;
      if (include_merge_commits !== undefined) filtersApplied.include_merge_commits = include_merge_commits;
      if (search) filtersApplied.search = search;
      if (include_build_status) filtersApplied.include_build_status = include_build_status;

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              branch_name,
              branch_head: branchHead,
              commits,
              total_count: totalCount,
              start,
              limit,
              has_more: nextPageStart !== null,
              next_start: nextPageStart,
              filters_applied: filtersApplied
            }, null, 2),
          },
        ],
      };
    } catch (error) {
      return this.apiClient.handleApiError(error, `listing commits for branch '${branch_name}' in ${workspace}/${repository}`);
    }
  }
}

```
Page 1/2FirstPrevNextLast