#
tokens: 46807/50000 25/30 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/pdogra1299/bitbucket-mcp-server?lines=true&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:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | 
 4 | # Build output
 5 | build/
 6 | dist/
 7 | 
 8 | # Environment files
 9 | .env
10 | .env.local
11 | .env.*.local
12 | 
13 | # IDE files
14 | .vscode/
15 | .idea/
16 | *.swp
17 | *.swo
18 | *~
19 | 
20 | # OS files
21 | .DS_Store
22 | Thumbs.db
23 | 
24 | # Logs
25 | logs/
26 | *.log
27 | npm-debug.log*
28 | yarn-debug.log*
29 | yarn-error.log*
30 | 
31 | # Test files
32 | test-api.js
33 | *.test.js
34 | 
35 | # Temporary files
36 | *.tmp
37 | *.temp
38 | .cache/
39 | 
40 | # Personal configuration
41 | RELOAD_INSTRUCTIONS.md
42 | personal-notes.md
43 | currentTask.yml
44 | 
```

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

```
 1 | # Source files
 2 | src/
 3 | *.ts
 4 | !*.d.ts
 5 | 
 6 | # Development files
 7 | .gitignore
 8 | tsconfig.json
 9 | .vscode/
10 | .idea/
11 | 
12 | # Test files
13 | test/
14 | *.test.js
15 | *.spec.js
16 | 
17 | # Build artifacts
18 | *.log
19 | node_modules/
20 | .npm/
21 | 
22 | # OS files
23 | .DS_Store
24 | Thumbs.db
25 | 
26 | # Editor files
27 | *.swp
28 | *.swo
29 | *~
30 | 
31 | # Documentation source
32 | docs/
33 | 
34 | # Scripts (except built ones)
35 | scripts/
36 | 
37 | # Setup guides (keep README)
38 | SETUP_GUIDE.md
39 | SETUP_GUIDE_SERVER.md
40 | 
41 | # Git
42 | .git/
43 | .gitattributes
44 | 
45 | # Other
46 | .env
47 | .env.local
48 | .env.*.local
49 | 
```

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

```
  1 | # Bitbucket MCP Server - Project Intelligence
  2 | 
  3 | ## Critical Implementation Paths
  4 | 
  5 | ### Adding New Tools
  6 | 1. Create handler in src/handlers/ following existing patterns
  7 | 2. Add TypeScript interfaces in src/types/bitbucket.ts
  8 | 3. Add type guard in src/types/guards.ts
  9 | 4. Add formatter in src/utils/formatters.ts if needed
 10 | 5. Register tool in src/tools/definitions.ts
 11 | 6. Wire handler in src/index.ts
 12 | 7. Update version, CHANGELOG.md, and README.md
 13 | 
 14 | ### API Variant Handling
 15 | - Always check `apiClient.getIsServer()` for Cloud vs Server
 16 | - Server uses `/rest/api/1.0/` prefix
 17 | - Cloud uses direct paths under base URL
 18 | - Different parameter names (e.g., pagelen vs limit)
 19 | 
 20 | ### Error Handling Pattern
 21 | ```typescript
 22 | try {
 23 |   // API call
 24 | } catch (error: any) {
 25 |   const errorMessage = error.response?.data?.errors?.[0]?.message || error.message;
 26 |   return {
 27 |     content: [{
 28 |       type: 'text',
 29 |       text: JSON.stringify({
 30 |         error: `Failed to ${action}: ${errorMessage}`,
 31 |         details: error.response?.data
 32 |       }, null, 2)
 33 |     }],
 34 |     isError: true
 35 |   };
 36 | }
 37 | ```
 38 | 
 39 | ## User Preferences
 40 | 
 41 | ### Documentation Style
 42 | - Comprehensive examples for each tool
 43 | - Clear parameter descriptions
 44 | - Response format examples
 45 | - Note differences between Cloud and Server
 46 | 
 47 | ### Code Style
 48 | - TypeScript with strict typing
 49 | - ES modules with .js extensions in imports
 50 | - Consistent error handling
 51 | - Modular architecture
 52 | 
 53 | ## Project-Specific Patterns
 54 | 
 55 | ### Authentication
 56 | - Cloud: Username (not email) + App Password
 57 | - Server: Email address + HTTP Access Token
 58 | - Environment variables for configuration
 59 | 
 60 | ### Pagination Pattern
 61 | - `limit` and `start` parameters
 62 | - Return `has_more` and `next_start`
 63 | - Include `total_count` when available
 64 | 
 65 | ### Response Formatting
 66 | - Consistent JSON structure
 67 | - Include operation status message
 68 | - Provide detailed error information
 69 | - Format dates as ISO strings
 70 | 
 71 | ## Known Challenges
 72 | 
 73 | ### Bitbucket API Differences
 74 | - Parameter naming varies (e.g., pagelen vs limit)
 75 | - Response structures differ significantly
 76 | - Some features only available on one variant
 77 | - Authentication methods completely different
 78 | 
 79 | ### Search Functionality
 80 | - Only available on Bitbucket Server
 81 | - Query syntax requires specific format
 82 | - No Cloud API equivalent currently
 83 | 
 84 | ## Tool Usage Patterns
 85 | 
 86 | ### List Operations
 87 | - Always include pagination parameters
 88 | - Return consistent metadata
 89 | - Support filtering where applicable
 90 | 
 91 | ### Modification Operations
 92 | - Validate required parameters
 93 | - Preserve existing data when updating
 94 | - Return updated resource in response
 95 | 
 96 | ### File Operations
 97 | - Smart truncation for large files
 98 | - Type-based default limits
 99 | - Support line range selection
100 | 
101 | ## Evolution of Decisions
102 | 
103 | ### Version 0.3.0
104 | - Modularized codebase into handlers
105 | - Separated types and utilities
106 | - Improved maintainability
107 | 
108 | ### Version 0.6.0
109 | - Enhanced PR details with comments/files
110 | - Added parallel API calls for performance
111 | 
112 | ### Version 0.9.0
113 | - Added code snippet matching for comments
114 | - Implemented confidence scoring
115 | 
116 | ### Version 1.0.0
117 | - Added code search functionality
118 | - Reached feature completeness
119 | - Ready for production use
120 | 
121 | ### Version 1.0.1
122 | - Improved search response formatting for AI consumption
123 | - Added simplified formatCodeSearchOutput
124 | - Enhanced HTML entity decoding and tag stripping
125 | - Established live MCP testing workflow
126 | 
127 | ## Important Architecture Decisions
128 | 
129 | ### Handler Pattern
130 | Each tool category has its own handler class to maintain single responsibility and make the codebase more maintainable.
131 | 
132 | ### Type Guards
133 | All tool inputs are validated with type guards to ensure type safety at runtime.
134 | 
135 | ### Response Normalization
136 | Different API responses are normalized to consistent formats for easier consumption.
137 | 
138 | ### Error Handling
139 | Consistent error handling across all tools with detailed error messages and recovery suggestions.
140 | 
```

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

```markdown
   1 | # Bitbucket MCP Server
   2 | 
   3 | [![npm version](https://badge.fury.io/js/@nexus2520%2Fbitbucket-mcp-server.svg)](https://www.npmjs.com/package/@nexus2520/bitbucket-mcp-server)
   4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
   5 | 
   6 | An MCP (Model Context Protocol) server that provides tools for interacting with the Bitbucket API, supporting both Bitbucket Cloud and Bitbucket Server.
   7 | 
   8 | ## Features
   9 | 
  10 | ### Currently Implemented Tools
  11 | 
  12 | #### Core PR Lifecycle Tools
  13 | - `get_pull_request` - Retrieve detailed information about a pull request
  14 | - `list_pull_requests` - List pull requests with filters (state, author, pagination)
  15 | - `create_pull_request` - Create new pull requests
  16 | - `update_pull_request` - Update PR details (title, description, reviewers, destination branch)
  17 | - `add_comment` - Add comments to pull requests (supports replies)
  18 | - `merge_pull_request` - Merge pull requests with various strategies
  19 | - `list_pr_commits` - List all commits that are part of a pull request
  20 | - `delete_branch` - Delete branches after merge
  21 | 
  22 | #### Branch Management Tools
  23 | - `list_branches` - List branches with filtering and pagination
  24 | - `delete_branch` - Delete branches (with protection checks)
  25 | - `get_branch` - Get detailed branch information including associated PRs
  26 | - `list_branch_commits` - List commits in a branch with advanced filtering
  27 | 
  28 | #### File and Directory Tools
  29 | - `list_directory_content` - List files and directories in a repository path
  30 | - `get_file_content` - Get file content with smart truncation for large files
  31 | 
  32 | #### Code Review Tools
  33 | - `get_pull_request_diff` - Get the diff/changes for a pull request
  34 | - `approve_pull_request` - Approve a pull request
  35 | - `unapprove_pull_request` - Remove approval from a pull request
  36 | - `request_changes` - Request changes on a pull request
  37 | - `remove_requested_changes` - Remove change request from a pull request
  38 | 
  39 | #### Search Tools
  40 | - `search_code` - Search for code across repositories (currently Bitbucket Server only)
  41 | 
  42 | #### Project and Repository Discovery Tools
  43 | - `list_projects` - List all accessible Bitbucket projects/workspaces with filtering
  44 | - `list_repositories` - List repositories in a project or across all accessible projects
  45 | 
  46 | ## Installation
  47 | 
  48 | ### Using npx (Recommended)
  49 | 
  50 | The easiest way to use this MCP server is directly with npx:
  51 | 
  52 | ```json
  53 | {
  54 |   "mcpServers": {
  55 |     "bitbucket": {
  56 |       "command": "npx",
  57 |       "args": [
  58 |         "-y",
  59 |         "@nexus2520/bitbucket-mcp-server"
  60 |       ],
  61 |       "env": {
  62 |         "BITBUCKET_USERNAME": "your-username",
  63 |         "BITBUCKET_APP_PASSWORD": "your-app-password"
  64 |       }
  65 |     }
  66 |   }
  67 | }
  68 | ```
  69 | 
  70 | For Bitbucket Server:
  71 | ```json
  72 | {
  73 |   "mcpServers": {
  74 |     "bitbucket": {
  75 |       "command": "npx",
  76 |       "args": [
  77 |         "-y",
  78 |         "@nexus2520/bitbucket-mcp-server"
  79 |       ],
  80 |       "env": {
  81 |         "BITBUCKET_USERNAME": "[email protected]",
  82 |         "BITBUCKET_TOKEN": "your-http-access-token",
  83 |         "BITBUCKET_BASE_URL": "https://bitbucket.yourcompany.com"
  84 |       }
  85 |     }
  86 |   }
  87 | }
  88 | ```
  89 | 
  90 | ### From Source
  91 | 
  92 | 1. Clone or download this repository
  93 | 2. Install dependencies:
  94 |    ```bash
  95 |    npm install
  96 |    ```
  97 | 3. Build the TypeScript code:
  98 |    ```bash
  99 |    npm run build
 100 |    ```
 101 | 
 102 | ## Authentication Setup
 103 | 
 104 | This server uses Bitbucket App Passwords for authentication.
 105 | 
 106 | ### Creating an App Password
 107 | 
 108 | 1. Log in to your Bitbucket account
 109 | 2. Navigate to: https://bitbucket.org/account/settings/app-passwords/
 110 | 3. Click "Create app password"
 111 | 4. Give it a descriptive label (e.g., "MCP Server")
 112 | 5. Select the following permissions:
 113 |    - **Account**: Read
 114 |    - **Repositories**: Read, Write
 115 |    - **Pull requests**: Read, Write
 116 | 6. Click "Create"
 117 | 7. **Important**: Copy the generated password immediately (you won't be able to see it again!)
 118 | 
 119 | ### Running the Setup Script
 120 | 
 121 | ```bash
 122 | node scripts/setup-auth.js
 123 | ```
 124 | 
 125 | This will guide you through the authentication setup process.
 126 | 
 127 | ## Configuration
 128 | 
 129 | Add the server to your MCP settings file (usually located at `~/.vscode-server/data/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`):
 130 | 
 131 | ```json
 132 | {
 133 |   "mcpServers": {
 134 |     "bitbucket": {
 135 |       "command": "node",
 136 |       "args": ["/absolute/path/to/bitbucket-mcp-server/build/index.js"],
 137 |       "env": {
 138 |         "BITBUCKET_USERNAME": "your-username",
 139 |         "BITBUCKET_APP_PASSWORD": "your-app-password"
 140 |       }
 141 |     }
 142 |   }
 143 | }
 144 | ```
 145 | 
 146 | Replace:
 147 | - `/absolute/path/to/bitbucket-mcp-server` with the actual path to this directory
 148 | - `your-username` with your Bitbucket username (not email)
 149 | - `your-app-password` with the app password you created
 150 | 
 151 | For Bitbucket Server, use:
 152 | ```json
 153 | {
 154 |   "mcpServers": {
 155 |     "bitbucket": {
 156 |       "command": "node",
 157 |       "args": ["/absolute/path/to/bitbucket-mcp-server/build/index.js"],
 158 |       "env": {
 159 |         "BITBUCKET_USERNAME": "[email protected]",
 160 |         "BITBUCKET_TOKEN": "your-http-access-token",
 161 |         "BITBUCKET_BASE_URL": "https://bitbucket.yourcompany.com"
 162 |       }
 163 |     }
 164 |   }
 165 | }
 166 | ```
 167 | 
 168 | **Important for Bitbucket Server users:**
 169 | - Use your full email address as the username (e.g., "[email protected]")
 170 | - This is required for approval/review actions to work correctly
 171 | 
 172 | ## Usage
 173 | 
 174 | Once configured, you can use the available tools:
 175 | 
 176 | ### Get Pull Request
 177 | 
 178 | ```typescript
 179 | {
 180 |   "tool": "get_pull_request",
 181 |   "arguments": {
 182 |     "workspace": "PROJ",  // Required - your project key
 183 |     "repository": "my-repo",
 184 |     "pull_request_id": 123
 185 |   }
 186 | }
 187 | ```
 188 | 
 189 | Returns detailed information about the pull request including:
 190 | - Title and description
 191 | - Author and reviewers
 192 | - Source and destination branches
 193 | - Approval status
 194 | - Links to web UI and diff
 195 | - **Merge commit details** (when PR is merged):
 196 |   - `merge_commit_hash`: The hash of the merge commit
 197 |   - `merged_by`: Who performed the merge
 198 |   - `merged_at`: When the merge occurred
 199 |   - `merge_commit_message`: The merge commit message
 200 | - **Active comments with nested replies** (unresolved comments that need attention):
 201 |   - `active_comments`: Array of active comments (up to 20 most recent top-level comments)
 202 |     - Comment text and author
 203 |     - Creation date
 204 |     - Whether it's an inline comment (with file path and line number)
 205 |     - **Nested replies** (for Bitbucket Server):
 206 |       - `replies`: Array of reply comments with same structure
 207 |       - Replies can be nested multiple levels deep
 208 |     - **Parent reference** (for Bitbucket Cloud):
 209 |       - `parent_id`: ID of the parent comment for replies
 210 |   - `active_comment_count`: Total count of unresolved comments (including nested replies)
 211 |   - `total_comment_count`: Total count of all comments (including resolved and replies)
 212 | - **File changes**:
 213 |   - `file_changes`: Array of all files modified in the PR
 214 |     - File path
 215 |     - Status (added, modified, removed, or renamed)
 216 |     - Old path (for renamed files)
 217 |   - `file_changes_summary`: Summary statistics
 218 |     - Total files changed
 219 | - And more...
 220 | 
 221 | ### Search Code
 222 | 
 223 | Search for code across Bitbucket repositories (currently only supported for Bitbucket Server):
 224 | 
 225 | ```typescript
 226 | // Search in a specific repository
 227 | {
 228 |   "tool": "search_code",
 229 |   "arguments": {
 230 |     "workspace": "PROJ",
 231 |     "repository": "my-repo",
 232 |     "search_query": "TODO",
 233 |     "limit": 50
 234 |   }
 235 | }
 236 | 
 237 | // Search across all repositories in a workspace
 238 | {
 239 |   "tool": "search_code",
 240 |   "arguments": {
 241 |     "workspace": "PROJ",
 242 |     "search_query": "deprecated",
 243 |     "file_pattern": "*.java",  // Optional: filter by file pattern
 244 |     "limit": 100
 245 |   }
 246 | }
 247 | 
 248 | // Search with file pattern filtering
 249 | {
 250 |   "tool": "search_code",
 251 |   "arguments": {
 252 |     "workspace": "PROJ",
 253 |     "repository": "frontend-app",
 254 |     "search_query": "useState",
 255 |     "file_pattern": "*.tsx",  // Only search in .tsx files
 256 |     "start": 0,
 257 |     "limit": 25
 258 |   }
 259 | }
 260 | ```
 261 | 
 262 | Returns search results with:
 263 | - File path and name
 264 | - Repository and project information
 265 | - Matched lines with:
 266 |   - Line number
 267 |   - Full line content
 268 |   - Highlighted segments showing exact matches
 269 | - Pagination information
 270 | 
 271 | Example response:
 272 | ```json
 273 | {
 274 |   "message": "Code search completed successfully",
 275 |   "workspace": "PROJ",
 276 |   "repository": "my-repo",
 277 |   "search_query": "TODO",
 278 |   "results": [
 279 |     {
 280 |       "file_path": "src/utils/helper.js",
 281 |       "file_name": "helper.js",
 282 |       "repository": "my-repo",
 283 |       "project": "PROJ",
 284 |       "matches": [
 285 |         {
 286 |           "line_number": 42,
 287 |           "line_content": "    // TODO: Implement error handling",
 288 |           "highlighted_segments": [
 289 |             { "text": "    // ", "is_match": false },
 290 |             { "text": "TODO", "is_match": true },
 291 |             { "text": ": Implement error handling", "is_match": false }
 292 |           ]
 293 |         }
 294 |       ]
 295 |     }
 296 |   ],
 297 |   "total_count": 15,
 298 |   "start": 0,
 299 |   "limit": 50,
 300 |   "has_more": false
 301 | }
 302 | ```
 303 | 
 304 | **Note**: This tool currently only works with Bitbucket Server. Bitbucket Cloud support is planned for a future release.
 305 | 
 306 | ### List Pull Requests
 307 | 
 308 | ```typescript
 309 | {
 310 |   "tool": "list_pull_requests",
 311 |   "arguments": {
 312 |     "workspace": "PROJ",  // Required - your project key
 313 |     "repository": "my-repo",
 314 |     "state": "OPEN",  // Optional: OPEN, MERGED, DECLINED, ALL (default: OPEN)
 315 |     "author": "username",  // Optional: filter by author (see note below)
 316 |     "limit": 25,  // Optional: max results per page (default: 25)
 317 |     "start": 0  // Optional: pagination start index (default: 0)
 318 |   }
 319 | }
 320 | ```
 321 | 
 322 | Returns a paginated list of pull requests with:
 323 | - Array of pull requests with same details as get_pull_request
 324 | - Total count of matching PRs
 325 | - Pagination info (has_more, next_start)
 326 | 
 327 | **Note on Author Filter:**
 328 | - For Bitbucket Cloud: Use the username (e.g., "johndoe")
 329 | - For Bitbucket Server: Use the full email address (e.g., "[email protected]")
 330 | 
 331 | ### Create Pull Request
 332 | 
 333 | ```typescript
 334 | {
 335 |   "tool": "create_pull_request",
 336 |   "arguments": {
 337 |     "workspace": "PROJ",
 338 |     "repository": "my-repo",
 339 |     "title": "Add new feature",
 340 |     "source_branch": "feature/new-feature",
 341 |     "destination_branch": "main",
 342 |     "description": "This PR adds a new feature...",  // Optional
 343 |     "reviewers": ["john.doe", "jane.smith"],  // Optional
 344 |     "close_source_branch": true  // Optional (default: false)
 345 |   }
 346 | }
 347 | ```
 348 | 
 349 | ### Update Pull Request
 350 | 
 351 | ```typescript
 352 | {
 353 |   "tool": "update_pull_request",
 354 |   "arguments": {
 355 |     "workspace": "PROJ",
 356 |     "repository": "my-repo",
 357 |     "pull_request_id": 123,
 358 |     "title": "Updated title",  // Optional
 359 |     "description": "Updated description",  // Optional
 360 |     "destination_branch": "develop",  // Optional
 361 |     "reviewers": ["new.reviewer"]  // Optional - see note below
 362 |   }
 363 | }
 364 | ```
 365 | 
 366 | **Important Note on Reviewers:**
 367 | - When updating a PR without specifying the `reviewers` parameter, existing reviewers and their approval status are preserved
 368 | - When providing the `reviewers` parameter:
 369 |   - The reviewer list is replaced with the new list
 370 |   - For reviewers that already exist on the PR, their approval status is preserved
 371 |   - New reviewers are added without approval status
 372 | - This prevents accidentally removing reviewers when you only want to update the PR description or title
 373 | 
 374 | ### Add Comment
 375 | 
 376 | Add a comment to a pull request, either as a general comment or inline on specific code:
 377 | 
 378 | ```javascript
 379 | // General comment
 380 | {
 381 |   "tool": "add_comment",
 382 |   "arguments": {
 383 |     "workspace": "PROJ",
 384 |     "repository": "my-repo",
 385 |     "pull_request_id": 123,
 386 |     "comment_text": "Great work on this PR!"
 387 |   }
 388 | }
 389 | 
 390 | // Inline comment on specific line
 391 | {
 392 |   "tool": "add_comment",
 393 |   "arguments": {
 394 |     "workspace": "PROJ",
 395 |     "repository": "my-repo",
 396 |     "pull_request_id": 123,
 397 |     "comment_text": "Consider extracting this into a separate function",
 398 |     "file_path": "src/utils/helpers.js",
 399 |     "line_number": 42,
 400 |     "line_type": "CONTEXT"  // ADDED, REMOVED, or CONTEXT
 401 |   }
 402 | }
 403 | 
 404 | // Reply to existing comment
 405 | {
 406 |   "tool": "add_comment",
 407 |   "arguments": {
 408 |     "workspace": "PROJ",
 409 |     "repository": "my-repo",
 410 |     "pull_request_id": 123,
 411 |     "comment_text": "I agree with this suggestion",
 412 |     "parent_comment_id": 456
 413 |   }
 414 | }
 415 | 
 416 | // Add comment with code suggestion (single line)
 417 | {
 418 |   "tool": "add_comment",
 419 |   "arguments": {
 420 |     "workspace": "PROJ",
 421 |     "repository": "my-repo",
 422 |     "pull_request_id": 123,
 423 |     "comment_text": "This variable name could be more descriptive.",
 424 |     "file_path": "src/utils/helpers.js",
 425 |     "line_number": 42,
 426 |     "line_type": "CONTEXT",
 427 |     "suggestion": "const userAuthenticationToken = token;"
 428 |   }
 429 | }
 430 | 
 431 | // Add comment with multi-line code suggestion
 432 | {
 433 |   "tool": "add_comment",
 434 |   "arguments": {
 435 |     "workspace": "PROJ",
 436 |     "repository": "my-repo",
 437 |     "pull_request_id": 123,
 438 |     "comment_text": "This function could be simplified using array methods.",
 439 |     "file_path": "src/utils/calculations.js",
 440 |     "line_number": 50,
 441 |     "suggestion_end_line": 55,
 442 |     "line_type": "CONTEXT",
 443 |     "suggestion": "function calculateTotal(items) {\n  return items.reduce((sum, item) => sum + item.price, 0);\n}"
 444 |   }
 445 | }
 446 | ```
 447 | 
 448 | The suggestion feature formats comments using GitHub-style markdown suggestion blocks that Bitbucket can render. When adding a suggestion:
 449 | - `suggestion` is required and contains the replacement code
 450 | - `file_path` and `line_number` are required when using suggestions
 451 | - `suggestion_end_line` is optional and used for multi-line suggestions (defaults to `line_number`)
 452 | - The comment will be formatted with a ````suggestion` markdown block that may be applicable in the Bitbucket UI
 453 | 
 454 | ### Using Code Snippets Instead of Line Numbers
 455 | 
 456 | 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:
 457 | 
 458 | ```javascript
 459 | // Add comment using code snippet
 460 | {
 461 |   "tool": "add_comment",
 462 |   "arguments": {
 463 |     "workspace": "PROJ",
 464 |     "repository": "my-repo",
 465 |     "pull_request_id": 123,
 466 |     "comment_text": "This variable name could be more descriptive",
 467 |     "file_path": "src/components/Button.res",
 468 |     "code_snippet": "let isDisabled = false",
 469 |     "search_context": {
 470 |       "before": ["let onClick = () => {"],
 471 |       "after": ["setLoading(true)"]
 472 |     }
 473 |   }
 474 | }
 475 | 
 476 | // Handle multiple matches with strategy
 477 | {
 478 |   "tool": "add_comment",
 479 |   "arguments": {
 480 |     "workspace": "PROJ",
 481 |     "repository": "my-repo",
 482 |     "pull_request_id": 123,
 483 |     "comment_text": "Consider extracting this",
 484 |     "file_path": "src/utils/helpers.js",
 485 |     "code_snippet": "return result;",
 486 |     "search_context": {
 487 |       "before": ["const result = calculate();"],
 488 |       "after": ["}"]
 489 |     },
 490 |     "match_strategy": "best"  // Auto-select highest confidence match
 491 |   }
 492 | }
 493 | ```
 494 | 
 495 | **Code Snippet Parameters:**
 496 | - `code_snippet`: The exact code line to find (alternative to `line_number`)
 497 | - `search_context`: Optional context to disambiguate multiple matches
 498 |   - `before`: Array of lines that should appear before the target
 499 |   - `after`: Array of lines that should appear after the target
 500 | - `match_strategy`: How to handle multiple matches
 501 |   - `"strict"` (default): Fail with error showing all matches
 502 |   - `"best"`: Auto-select the highest confidence match
 503 | 
 504 | **Error Response for Multiple Matches (strict mode):**
 505 | ```json
 506 | {
 507 |   "error": {
 508 |     "code": "MULTIPLE_MATCHES_FOUND",
 509 |     "message": "Code snippet 'return result;' found in 3 locations",
 510 |     "occurrences": [
 511 |       {
 512 |         "line_number": 42,
 513 |         "file_path": "src/utils/helpers.js",
 514 |         "preview": "  const result = calculate();\n> return result;\n}",
 515 |         "confidence": 0.9,
 516 |         "line_type": "ADDED"
 517 |       },
 518 |       // ... more matches
 519 |     ],
 520 |     "suggestion": "To resolve, either:\n1. Add more context...\n2. Use match_strategy: 'best'...\n3. Use line_number directly"
 521 |   }
 522 | }
 523 | ```
 524 | 
 525 | This feature is particularly useful for:
 526 | - AI-powered code review tools that analyze diffs
 527 | - Scripts that automatically add comments based on code patterns
 528 | - Avoiding line number confusion in large diffs
 529 | 
 530 | **Note on comment replies:**
 531 | - Use `parent_comment_id` to reply to any comment (general or inline)
 532 | - In `get_pull_request` responses:
 533 |   - Bitbucket Server shows replies nested in a `replies` array
 534 |   - Bitbucket Cloud shows a `parent_id` field for reply comments
 535 | - You can reply to replies, creating nested conversations
 536 | 
 537 | **Note on inline comments:**
 538 | - `file_path`: The path to the file as shown in the diff
 539 | - `line_number`: The line number as shown in the diff
 540 | - `line_type`: 
 541 |   - `ADDED` - For newly added lines (green in diff)
 542 |   - `REMOVED` - For deleted lines (red in diff)
 543 |   - `CONTEXT` - For unchanged context lines
 544 | 
 545 | #### Add Comment - Complete Usage Guide
 546 | 
 547 | The `add_comment` tool supports multiple scenarios. Here's when and how to use each approach:
 548 | 
 549 | **1. General PR Comments (No file/line)**
 550 | - Use when: Making overall feedback about the PR
 551 | - Required params: `comment_text` only
 552 | - Example: "LGTM!", "Please update the documentation"
 553 | 
 554 | **2. Reply to Existing Comments**
 555 | - Use when: Continuing a conversation thread
 556 | - Required params: `comment_text`, `parent_comment_id`
 557 | - Works for both general and inline comment replies
 558 | 
 559 | **3. Inline Comments with Line Number**
 560 | - Use when: You know the exact line number from the diff
 561 | - Required params: `comment_text`, `file_path`, `line_number`
 562 | - Optional: `line_type` (defaults to CONTEXT)
 563 | 
 564 | **4. Inline Comments with Code Snippet**
 565 | - Use when: You have the code but not the line number (common for AI tools)
 566 | - Required params: `comment_text`, `file_path`, `code_snippet`
 567 | - The tool will automatically find the line number
 568 | - Add `search_context` if the code appears multiple times
 569 | - Use `match_strategy: "best"` to auto-select when multiple matches exist
 570 | 
 571 | **5. Code Suggestions**
 572 | - Use when: Proposing specific code changes
 573 | - Required params: `comment_text`, `file_path`, `line_number`, `suggestion`
 574 | - For multi-line: also add `suggestion_end_line`
 575 | - Creates applicable suggestion blocks in Bitbucket UI
 576 | 
 577 | **Decision Flow for AI/Automated Tools:**
 578 | ```
 579 | 1. Do you want to suggest code changes?
 580 |    → Use suggestion with line_number
 581 |    
 582 | 2. Do you have the exact line number?
 583 |    → Use line_number directly
 584 |    
 585 | 3. Do you have the code snippet but not line number?
 586 |    → Use code_snippet (add search_context if needed)
 587 |    
 588 | 4. Is it a general comment about the PR?
 589 |    → Use comment_text only
 590 |    
 591 | 5. Are you replying to another comment?
 592 |    → Add parent_comment_id
 593 | ```
 594 | 
 595 | **Common Pitfalls to Avoid:**
 596 | - Don't use both `line_number` and `code_snippet` - pick one
 597 | - Suggestions always need `file_path` and `line_number`
 598 | - Code snippets must match exactly (including whitespace)
 599 | - REMOVED lines reference the source file, ADDED/CONTEXT reference the destination
 600 | 
 601 | ### Merge Pull Request
 602 | 
 603 | ```typescript
 604 | {
 605 |   "tool": "merge_pull_request",
 606 |   "arguments": {
 607 |     "workspace": "PROJ",
 608 |     "repository": "my-repo",
 609 |     "pull_request_id": 123,
 610 |     "merge_strategy": "squash",  // Optional: merge-commit, squash, fast-forward
 611 |     "close_source_branch": true,  // Optional
 612 |     "commit_message": "Custom merge message"  // Optional
 613 |   }
 614 | }
 615 | ```
 616 | 
 617 | ### List Branches
 618 | 
 619 | ```typescript
 620 | {
 621 |   "tool": "list_branches",
 622 |   "arguments": {
 623 |     "workspace": "PROJ",
 624 |     "repository": "my-repo",
 625 |     "filter": "feature",  // Optional: filter by name pattern
 626 |     "limit": 25,  // Optional (default: 25)
 627 |     "start": 0  // Optional: for pagination (default: 0)
 628 |   }
 629 | }
 630 | ```
 631 | 
 632 | Returns a paginated list of branches with:
 633 | - Branch name and ID
 634 | - Latest commit hash
 635 | - Default branch indicator
 636 | - Pagination info
 637 | 
 638 | ### Delete Branch
 639 | 
 640 | ```typescript
 641 | {
 642 |   "tool": "delete_branch",
 643 |   "arguments": {
 644 |     "workspace": "PROJ",
 645 |     "repository": "my-repo",
 646 |     "branch_name": "feature/old-feature",
 647 |     "force": false  // Optional (default: false)
 648 |   }
 649 | }
 650 | ```
 651 | 
 652 | **Note**: Branch deletion requires appropriate permissions. The branch will be permanently deleted.
 653 | 
 654 | ### Get Branch
 655 | 
 656 | ```typescript
 657 | {
 658 |   "tool": "get_branch",
 659 |   "arguments": {
 660 |     "workspace": "PROJ",
 661 |     "repository": "my-repo",
 662 |     "branch_name": "feature/new-feature",
 663 |     "include_merged_prs": false  // Optional (default: false)
 664 |   }
 665 | }
 666 | ```
 667 | 
 668 | Returns comprehensive branch information including:
 669 | - Branch details:
 670 |   - Name and ID
 671 |   - Latest commit (hash, message, author, date)
 672 |   - Default branch indicator
 673 | - Open pull requests from this branch:
 674 |   - PR title and ID
 675 |   - Destination branch
 676 |   - Author and reviewers
 677 |   - Approval status (approved by, changes requested by, pending)
 678 |   - PR URL
 679 | - Merged pull requests (if `include_merged_prs` is true):
 680 |   - PR title and ID
 681 |   - Merge date and who merged it
 682 | - Statistics:
 683 |   - Total open PRs count
 684 |   - Total merged PRs count
 685 |   - Days since last commit
 686 | 
 687 | This tool is particularly useful for:
 688 | - Checking if a branch has open PRs before deletion
 689 | - Getting an overview of branch activity
 690 | - Understanding PR review status
 691 | - Identifying stale branches
 692 | 
 693 | ### List Branch Commits
 694 | 
 695 | Get all commits in a specific branch with advanced filtering options:
 696 | 
 697 | ```typescript
 698 | // Basic usage - get recent commits
 699 | {
 700 |   "tool": "list_branch_commits",
 701 |   "arguments": {
 702 |     "workspace": "PROJ",
 703 |     "repository": "my-repo",
 704 |     "branch_name": "feature/new-feature",
 705 |     "limit": 50  // Optional (default: 25)
 706 |   }
 707 | }
 708 | 
 709 | // Filter by date range
 710 | {
 711 |   "tool": "list_branch_commits",
 712 |   "arguments": {
 713 |     "workspace": "PROJ",
 714 |     "repository": "my-repo",
 715 |     "branch_name": "main",
 716 |     "since": "2025-01-01T00:00:00Z",  // ISO date string
 717 |     "until": "2025-01-15T23:59:59Z"   // ISO date string
 718 |   }
 719 | }
 720 | 
 721 | // Filter by author
 722 | {
 723 |   "tool": "list_branch_commits",
 724 |   "arguments": {
 725 |     "workspace": "PROJ",
 726 |     "repository": "my-repo",
 727 |     "branch_name": "develop",
 728 |     "author": "[email protected]",  // Email or username
 729 |     "limit": 100
 730 |   }
 731 | }
 732 | 
 733 | // Exclude merge commits
 734 | {
 735 |   "tool": "list_branch_commits",
 736 |   "arguments": {
 737 |     "workspace": "PROJ",
 738 |     "repository": "my-repo",
 739 |     "branch_name": "release/v2.0",
 740 |     "include_merge_commits": false
 741 |   }
 742 | }
 743 | 
 744 | // Search in commit messages
 745 | {
 746 |   "tool": "list_branch_commits",
 747 |   "arguments": {
 748 |     "workspace": "PROJ",
 749 |     "repository": "my-repo",
 750 |     "branch_name": "main",
 751 |     "search": "bugfix",  // Search in commit messages
 752 |     "limit": 50
 753 |   }
 754 | }
 755 | 
 756 | // Combine multiple filters
 757 | {
 758 |   "tool": "list_branch_commits",
 759 |   "arguments": {
 760 |     "workspace": "PROJ",
 761 |     "repository": "my-repo",
 762 |     "branch_name": "develop",
 763 |     "author": "[email protected]",
 764 |     "since": "2025-01-01T00:00:00Z",
 765 |     "include_merge_commits": false,
 766 |     "search": "feature",
 767 |     "limit": 100,
 768 |     "start": 0  // For pagination
 769 |   }
 770 | }
 771 | 
 772 | // Include CI/CD build status (Bitbucket Server only)
 773 | {
 774 |   "tool": "list_branch_commits",
 775 |   "arguments": {
 776 |     "workspace": "PROJ",
 777 |     "repository": "my-repo",
 778 |     "branch_name": "main",
 779 |     "include_build_status": true,  // Fetch build status for each commit
 780 |     "limit": 50
 781 |   }
 782 | }
 783 | ```
 784 | 
 785 | **Filter Parameters:**
 786 | - `since`: ISO date string - only show commits after this date
 787 | - `until`: ISO date string - only show commits before this date
 788 | - `author`: Filter by author email/username
 789 | - `include_merge_commits`: Boolean to include/exclude merge commits (default: true)
 790 | - `search`: Search for text in commit messages
 791 | - `include_build_status`: Boolean to include CI/CD build status (default: false, Bitbucket Server only)
 792 | 
 793 | Returns detailed commit information:
 794 | ```json
 795 | {
 796 |   "branch_name": "feature/new-feature",
 797 |   "branch_head": "abc123def456",  // Latest commit hash
 798 |   "commits": [
 799 |     {
 800 |       "hash": "abc123def456",
 801 |       "abbreviated_hash": "abc123d",
 802 |       "message": "Add new feature implementation",
 803 |       "author": {
 804 |         "name": "John Doe",
 805 |         "email": "[email protected]"
 806 |       },
 807 |       "date": "2025-01-03T10:30:00Z",
 808 |       "parents": ["parent1hash", "parent2hash"],
 809 |       "is_merge_commit": false,
 810 |       "build_status": {  // Only present when include_build_status is true
 811 |         "successful": 5,
 812 |         "failed": 0,
 813 |         "in_progress": 1,
 814 |         "unknown": 0
 815 |       }
 816 |     }
 817 |     // ... more commits
 818 |   ],
 819 |   "total_count": 150,
 820 |   "start": 0,
 821 |   "limit": 25,
 822 |   "has_more": true,
 823 |   "next_start": 25,
 824 |   "filters_applied": {
 825 |     "author": "[email protected]",
 826 |     "since": "2025-01-01",
 827 |     "include_merge_commits": false,
 828 |     "include_build_status": true
 829 |   }
 830 | }
 831 | ```
 832 | 
 833 | This tool is particularly useful for:
 834 | - Reviewing commit history before releases
 835 | - Finding commits by specific authors
 836 | - Tracking changes within date ranges
 837 | - Searching for specific features or fixes
 838 | - Analyzing branch activity patterns
 839 | - Monitoring CI/CD build status for commits (Bitbucket Server only)
 840 | 
 841 | ### List PR Commits
 842 | 
 843 | Get all commits that are part of a pull request:
 844 | 
 845 | ```typescript
 846 | // Basic usage
 847 | {
 848 |   "tool": "list_pr_commits",
 849 |   "arguments": {
 850 |     "workspace": "PROJ",
 851 |     "repository": "my-repo",
 852 |     "pull_request_id": 123,
 853 |     "limit": 50,  // Optional (default: 25)
 854 |     "start": 0    // Optional: for pagination
 855 |   }
 856 | }
 857 | 
 858 | // Include CI/CD build status (Bitbucket Server only)
 859 | {
 860 |   "tool": "list_pr_commits",
 861 |   "arguments": {
 862 |     "workspace": "PROJ",
 863 |     "repository": "my-repo",
 864 |     "pull_request_id": 123,
 865 |     "include_build_status": true,  // Fetch build status for each commit
 866 |     "limit": 50
 867 |   }
 868 | }
 869 | ```
 870 | 
 871 | Returns commit information for the PR:
 872 | ```json
 873 | {
 874 |   "pull_request_id": 123,
 875 |   "pull_request_title": "Add awesome feature",
 876 |   "commits": [
 877 |     {
 878 |       "hash": "def456ghi789",
 879 |       "abbreviated_hash": "def456g",
 880 |       "message": "Initial implementation",
 881 |       "author": {
 882 |         "name": "Jane Smith",
 883 |         "email": "[email protected]"
 884 |       },
 885 |       "date": "2025-01-02T14:20:00Z",
 886 |       "parents": ["parent1hash"],
 887 |       "is_merge_commit": false,
 888 |       "build_status": {  // Only present when include_build_status is true
 889 |         "successful": 3,
 890 |         "failed": 0,
 891 |         "in_progress": 0,
 892 |         "unknown": 0
 893 |       }
 894 |     }
 895 |     // ... more commits
 896 |   ],
 897 |   "total_count": 5,
 898 |   "start": 0,
 899 |   "limit": 25,
 900 |   "has_more": false
 901 | }
 902 | ```
 903 | 
 904 | This tool is particularly useful for:
 905 | - Reviewing all changes in a PR before merging
 906 | - Understanding the development history of a PR
 907 | - Checking commit messages for quality
 908 | - Verifying authorship of changes
 909 | - Analyzing PR complexity by commit count
 910 | - Monitoring CI/CD build status for all PR commits (Bitbucket Server only)
 911 | 
 912 | ### Get Pull Request Diff
 913 | 
 914 | Get the diff/changes for a pull request with optional filtering capabilities:
 915 | 
 916 | ```typescript
 917 | // Get full diff (default behavior)
 918 | {
 919 |   "tool": "get_pull_request_diff",
 920 |   "arguments": {
 921 |     "workspace": "PROJ",
 922 |     "repository": "my-repo",
 923 |     "pull_request_id": 123,
 924 |     "context_lines": 5  // Optional (default: 3)
 925 |   }
 926 | }
 927 | 
 928 | // Exclude specific file types
 929 | {
 930 |   "tool": "get_pull_request_diff",
 931 |   "arguments": {
 932 |     "workspace": "PROJ",
 933 |     "repository": "my-repo",
 934 |     "pull_request_id": 123,
 935 |     "exclude_patterns": ["*.lock", "*.svg", "node_modules/**", "*.min.js"]
 936 |   }
 937 | }
 938 | 
 939 | // Include only specific file types
 940 | {
 941 |   "tool": "get_pull_request_diff",
 942 |   "arguments": {
 943 |     "workspace": "PROJ",
 944 |     "repository": "my-repo",
 945 |     "pull_request_id": 123,
 946 |     "include_patterns": ["*.res", "*.resi", "src/**/*.js"]
 947 |   }
 948 | }
 949 | 
 950 | // Get diff for a specific file only
 951 | {
 952 |   "tool": "get_pull_request_diff",
 953 |   "arguments": {
 954 |     "workspace": "PROJ",
 955 |     "repository": "my-repo",
 956 |     "pull_request_id": 123,
 957 |     "file_path": "src/components/Button.res"
 958 |   }
 959 | }
 960 | 
 961 | // Combine filters
 962 | {
 963 |   "tool": "get_pull_request_diff",
 964 |   "arguments": {
 965 |     "workspace": "PROJ",
 966 |     "repository": "my-repo",
 967 |     "pull_request_id": 123,
 968 |     "include_patterns": ["src/**/*"],
 969 |     "exclude_patterns": ["*.test.js", "*.spec.js"]
 970 |   }
 971 | }
 972 | ```
 973 | 
 974 | **Filtering Options:**
 975 | - `include_patterns`: Array of glob patterns to include (whitelist)
 976 | - `exclude_patterns`: Array of glob patterns to exclude (blacklist)
 977 | - `file_path`: Get diff for a specific file only
 978 | - Patterns support standard glob syntax (e.g., `*.js`, `src/**/*.res`, `!test/**`)
 979 | 
 980 | **Response includes filtering metadata:**
 981 | ```json
 982 | {
 983 |   "message": "Pull request diff retrieved successfully",
 984 |   "pull_request_id": 123,
 985 |   "diff": "..filtered diff content..",
 986 |   "filter_metadata": {
 987 |     "total_files": 15,
 988 |     "included_files": 12,
 989 |     "excluded_files": 3,
 990 |     "excluded_file_list": ["package-lock.json", "logo.svg", "yarn.lock"],
 991 |     "filters_applied": {
 992 |       "exclude_patterns": ["*.lock", "*.svg"]
 993 |     }
 994 |   }
 995 | }
 996 | ```
 997 | 
 998 | ### Approve Pull Request
 999 | 
1000 | ```typescript
1001 | {
1002 |   "tool": "approve_pull_request",
1003 |   "arguments": {
1004 |     "workspace": "PROJ",
1005 |     "repository": "my-repo",
1006 |     "pull_request_id": 123
1007 |   }
1008 | }
1009 | ```
1010 | 
1011 | ### Request Changes
1012 | 
1013 | ```typescript
1014 | {
1015 |   "tool": "request_changes",
1016 |   "arguments": {
1017 |     "workspace": "PROJ",
1018 |     "repository": "my-repo",
1019 |     "pull_request_id": 123,
1020 |     "comment": "Please address the following issues..."  // Optional
1021 |   }
1022 | }
1023 | ```
1024 | 
1025 | ### List Directory Content
1026 | 
1027 | ```typescript
1028 | {
1029 |   "tool": "list_directory_content",
1030 |   "arguments": {
1031 |     "workspace": "PROJ",
1032 |     "repository": "my-repo",
1033 |     "path": "src/components",  // Optional (defaults to root)
1034 |     "branch": "main"  // Optional (defaults to default branch)
1035 |   }
1036 | }
1037 | ```
1038 | 
1039 | Returns directory listing with:
1040 | - Path and branch information
1041 | - Array of contents with:
1042 |   - Name
1043 |   - Type (file or directory)
1044 |   - Size (for files)
1045 |   - Full path
1046 | - Total items count
1047 | 
1048 | ### Get File Content
1049 | 
1050 | ```typescript
1051 | {
1052 |   "tool": "get_file_content",
1053 |   "arguments": {
1054 |     "workspace": "PROJ",
1055 |     "repository": "my-repo",
1056 |     "file_path": "src/index.ts",
1057 |     "branch": "main",  // Optional (defaults to default branch)
1058 |     "start_line": 1,  // Optional: starting line (1-based, use negative for from end)
1059 |     "line_count": 100,  // Optional: number of lines to return
1060 |     "full_content": false  // Optional: force full content (default: false)
1061 |   }
1062 | }
1063 | ```
1064 | 
1065 | **Smart Truncation Features:**
1066 | - Automatically truncates large files (>50KB) to prevent token overload
1067 | - Default line counts based on file type:
1068 |   - Config files (.yml, .json): 200 lines
1069 |   - Documentation (.md, .txt): 300 lines
1070 |   - Code files (.ts, .js, .py): 500 lines
1071 |   - Log files: Last 100 lines
1072 | - Use `start_line: -50` to get last 50 lines (tail functionality)
1073 | - Files larger than 1MB require explicit `full_content: true` or line parameters
1074 | 
1075 | Returns file content with:
1076 | - File path and branch
1077 | - File size and encoding
1078 | - Content (full or truncated based on parameters)
1079 | - Line information (if truncated):
1080 |   - Total lines in file
1081 |   - Range of returned lines
1082 |   - Truncation indicator
1083 | - Last modified information (commit, author, date)
1084 | 
1085 | Example responses:
1086 | 
1087 | ```json
1088 | // Small file - returns full content
1089 | {
1090 |   "file_path": "package.json",
1091 |   "branch": "main",
1092 |   "size": 1234,
1093 |   "encoding": "utf-8",
1094 |   "content": "{\n  \"name\": \"my-project\",\n  ...",
1095 |   "last_modified": {
1096 |     "commit_id": "abc123",
1097 |     "author": "John Doe",
1098 |     "date": "2025-01-21T10:00:00Z"
1099 |   }
1100 | }
1101 | 
1102 | // Large file - automatically truncated
1103 | {
1104 |   "file_path": "src/components/LargeComponent.tsx",
1105 |   "branch": "main",
1106 |   "size": 125000,
1107 |   "encoding": "utf-8",
1108 |   "content": "... first 500 lines ...",
1109 |   "line_info": {
1110 |     "total_lines": 3500,
1111 |     "returned_lines": {
1112 |       "start": 1,
1113 |       "end": 500
1114 |     },
1115 |     "truncated": true,
1116 |     "message": "Showing lines 1-500 of 3500. File size: 122.1KB"
1117 |   }
1118 | }
1119 | ```
1120 | 
1121 | ### List Projects
1122 | 
1123 | List all accessible Bitbucket projects (Server) or workspaces (Cloud):
1124 | 
1125 | ```typescript
1126 | // List all accessible projects
1127 | {
1128 |   "tool": "list_projects",
1129 |   "arguments": {
1130 |     "limit": 25,  // Optional (default: 25)
1131 |     "start": 0    // Optional: for pagination (default: 0)
1132 |   }
1133 | }
1134 | 
1135 | // Filter by project name
1136 | {
1137 |   "tool": "list_projects",
1138 |   "arguments": {
1139 |     "name": "backend",  // Partial name match
1140 |     "limit": 50
1141 |   }
1142 | }
1143 | 
1144 | // Filter by permission level (Bitbucket Server only)
1145 | {
1146 |   "tool": "list_projects",
1147 |   "arguments": {
1148 |     "permission": "PROJECT_WRITE",  // PROJECT_READ, PROJECT_WRITE, PROJECT_ADMIN
1149 |     "limit": 100
1150 |   }
1151 | }
1152 | ```
1153 | 
1154 | **Parameters:**
1155 | - `name`: Filter by project/workspace name (partial match, optional)
1156 | - `permission`: Filter by permission level (Bitbucket Server only, optional)
1157 |   - `PROJECT_READ`: Read access
1158 |   - `PROJECT_WRITE`: Write access
1159 |   - `PROJECT_ADMIN`: Admin access
1160 | - `limit`: Maximum number of projects to return (default: 25)
1161 | - `start`: Start index for pagination (default: 0)
1162 | 
1163 | Returns project/workspace information:
1164 | ```json
1165 | {
1166 |   "projects": [
1167 |     {
1168 |       "key": "PROJ",
1169 |       "id": 1234,
1170 |       "name": "My Project",
1171 |       "description": "Project description",
1172 |       "is_public": false,
1173 |       "type": "NORMAL",  // NORMAL or PERSONAL (Server), WORKSPACE (Cloud)
1174 |       "url": "https://bitbucket.yourcompany.com/projects/PROJ"
1175 |     }
1176 |     // ... more projects
1177 |   ],
1178 |   "total_count": 15,
1179 |   "start": 0,
1180 |   "limit": 25,
1181 |   "has_more": false,
1182 |   "next_start": null
1183 | }
1184 | ```
1185 | 
1186 | **Note**:
1187 | - For Bitbucket Cloud, this returns workspaces (not projects in the traditional sense)
1188 | - For Bitbucket Server, this returns both personal and team projects
1189 | 
1190 | This tool is particularly useful for:
1191 | - Discovering available projects/workspaces for your account
1192 | - Finding project keys needed for other API calls
1193 | - Identifying projects you have specific permissions on
1194 | - Browsing organizational structure
1195 | 
1196 | ### List Repositories
1197 | 
1198 | List repositories within a specific project/workspace or across all accessible repositories:
1199 | 
1200 | ```typescript
1201 | // List all repositories in a workspace/project
1202 | {
1203 |   "tool": "list_repositories",
1204 |   "arguments": {
1205 |     "workspace": "PROJ",  // Required for Bitbucket Cloud, optional for Server
1206 |     "limit": 25,          // Optional (default: 25)
1207 |     "start": 0            // Optional: for pagination (default: 0)
1208 |   }
1209 | }
1210 | 
1211 | // List all accessible repositories (Bitbucket Server only)
1212 | {
1213 |   "tool": "list_repositories",
1214 |   "arguments": {
1215 |     "limit": 100
1216 |   }
1217 | }
1218 | 
1219 | // Filter by repository name
1220 | {
1221 |   "tool": "list_repositories",
1222 |   "arguments": {
1223 |     "workspace": "PROJ",
1224 |     "name": "frontend",  // Partial name match
1225 |     "limit": 50
1226 |   }
1227 | }
1228 | 
1229 | // Filter by permission level (Bitbucket Server only)
1230 | {
1231 |   "tool": "list_repositories",
1232 |   "arguments": {
1233 |     "workspace": "PROJ",
1234 |     "permission": "REPO_WRITE",  // REPO_READ, REPO_WRITE, REPO_ADMIN
1235 |     "limit": 100
1236 |   }
1237 | }
1238 | ```
1239 | 
1240 | **Parameters:**
1241 | - `workspace`: Project key (Server) or workspace slug (Cloud)
1242 |   - **Required for Bitbucket Cloud**
1243 |   - Optional for Bitbucket Server (omit to list all accessible repos)
1244 | - `name`: Filter by repository name (partial match, optional)
1245 | - `permission`: Filter by permission level (Bitbucket Server only, optional)
1246 |   - `REPO_READ`: Read access
1247 |   - `REPO_WRITE`: Write access
1248 |   - `REPO_ADMIN`: Admin access
1249 | - `limit`: Maximum number of repositories to return (default: 25)
1250 | - `start`: Start index for pagination (default: 0)
1251 | 
1252 | Returns repository information:
1253 | ```json
1254 | {
1255 |   "repositories": [
1256 |     {
1257 |       "slug": "my-repo",
1258 |       "id": 5678,
1259 |       "name": "My Repository",
1260 |       "description": "Repository description",
1261 |       "project_key": "PROJ",
1262 |       "project_name": "My Project",
1263 |       "state": "AVAILABLE",  // AVAILABLE, INITIALISING, INITIALISATION_FAILED (Server)
1264 |       "is_public": false,
1265 |       "is_forkable": true,
1266 |       "clone_urls": {
1267 |         "http": "https://bitbucket.yourcompany.com/scm/PROJ/my-repo.git",
1268 |         "ssh": "ssh://[email protected]:7999/PROJ/my-repo.git"
1269 |       },
1270 |       "url": "https://bitbucket.yourcompany.com/projects/PROJ/repos/my-repo"
1271 |     }
1272 |     // ... more repositories
1273 |   ],
1274 |   "total_count": 42,
1275 |   "start": 0,
1276 |   "limit": 25,
1277 |   "has_more": true,
1278 |   "next_start": 25,
1279 |   "workspace": "PROJ"
1280 | }
1281 | ```
1282 | 
1283 | **Important Notes:**
1284 | - **Bitbucket Cloud** requires the `workspace` parameter. If omitted, you'll receive an error message
1285 | - **Bitbucket Server** allows listing all accessible repos by omitting the `workspace` parameter
1286 | - Clone URLs are provided for both HTTP(S) and SSH protocols
1287 | 
1288 | This tool is particularly useful for:
1289 | - Discovering available repositories in a project/workspace
1290 | - Finding repository slugs needed for other API calls
1291 | - Identifying repositories you have specific permissions on
1292 | - Getting clone URLs for repositories
1293 | - Browsing repository structure within an organization
1294 | 
1295 | ## Development
1296 | 
1297 | - `npm run dev` - Watch mode for development
1298 | - `npm run build` - Build the TypeScript code
1299 | - `npm start` - Run the built server
1300 | 
1301 | ## Troubleshooting
1302 | 
1303 | 1. **Authentication errors**: Double-check your username and app password
1304 | 2. **404 errors**: Verify the workspace, repository slug, and PR ID
1305 | 3. **Permission errors**: Ensure your app password has the required permissions
1306 | 
1307 | ## License
1308 | 
1309 | MIT
1310 | 
```

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

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "resolveJsonModule": true,
13 |     "declaration": true,
14 |     "declarationMap": true,
15 |     "sourceMap": true
16 |   },
17 |   "include": ["src/**/*"],
18 |   "exclude": ["node_modules", "build"]
19 | }
20 | 
```

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

```typescript
 1 | /**
 2 |  * Formats a comment with a code suggestion in markdown format
 3 |  * that Bitbucket can render as an applicable suggestion
 4 |  */
 5 | export function formatSuggestionComment(
 6 |   commentText: string,
 7 |   suggestion: string,
 8 |   startLine?: number,
 9 |   endLine?: number
10 | ): string {
11 |   // Add line range info if it's a multi-line suggestion
12 |   const lineInfo = startLine && endLine && endLine > startLine 
13 |     ? ` (lines ${startLine}-${endLine})` 
14 |     : '';
15 |   
16 |   // Format with GitHub-style suggestion markdown
17 |   return `${commentText}${lineInfo}
18 | 
19 | \`\`\`suggestion
20 | ${suggestion}
21 | \`\`\``;
22 | }
23 | 
```

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

```javascript
 1 | #!/usr/bin/env node
 2 | 
 3 | console.log(`
 4 | ===========================================
 5 | Bitbucket MCP Server - Authentication Setup
 6 | ===========================================
 7 | 
 8 | To use this MCP server, you need to create a Bitbucket App Password.
 9 | 
10 | Follow these steps:
11 | 
12 | 1. Log in to your Bitbucket account
13 | 2. Go to: https://bitbucket.org/account/settings/app-passwords/
14 | 3. Click "Create app password"
15 | 4. Give it a label (e.g., "MCP Server")
16 | 5. Select the following permissions:
17 |    - Account: Read
18 |    - Repositories: Read, Write
19 |    - Pull requests: Read, Write
20 | 6. Click "Create"
21 | 7. Copy the generated app password (you won't be able to see it again!)
22 | 
23 | You'll need to provide:
24 | - Your Bitbucket username (not email)
25 | - The app password you just created
26 | - Your default workspace/organization (optional)
27 | 
28 | Example workspace: If your repository URL is:
29 | https://bitbucket.org/mycompany/my-repo
30 | Then your workspace is: mycompany
31 | 
32 | These will be added to your MCP settings configuration.
33 | 
34 | Press Enter to continue...
35 | `);
36 | 
37 | // Wait for user to press Enter
38 | process.stdin.once('data', () => {
39 |   console.log(`
40 | Next steps:
41 | 1. The MCP server will be configured with your credentials
42 | 2. You'll be able to use the 'get_pull_request' tool
43 | 3. More tools can be added later (create_pull_request, list_pull_requests, etc.)
44 | 
45 | Configuration complete!
46 | `);
47 |   process.exit(0);
48 | });
49 | 
```

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

```json
 1 | {
 2 |   "name": "@nexus2520/bitbucket-mcp-server",
 3 |   "version": "1.1.2",
 4 |   "description": "MCP server for Bitbucket API integration - supports both Cloud and Server",
 5 |   "type": "module",
 6 |   "main": "./build/index.js",
 7 |   "bin": {
 8 |     "bitbucket-mcp-server": "build/index.js"
 9 |   },
10 |   "files": [
11 |     "build/**/*",
12 |     "README.md",
13 |     "LICENSE",
14 |     "CHANGELOG.md"
15 |   ],
16 |   "scripts": {
17 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
18 |     "dev": "tsc --watch",
19 |     "start": "node build/index.js",
20 |     "prepublishOnly": "npm run build"
21 |   },
22 |   "keywords": [
23 |     "mcp",
24 |     "bitbucket",
25 |     "api",
26 |     "model-context-protocol",
27 |     "bitbucket-server",
28 |     "bitbucket-cloud",
29 |     "pull-request",
30 |     "code-review"
31 |   ],
32 |   "author": "Parth Dogra",
33 |   "license": "MIT",
34 |   "repository": {
35 |     "type": "git",
36 |     "url": "git+https://github.com/pdogra1299/bitbucket-mcp-server.git"
37 |   },
38 |   "bugs": {
39 |     "url": "https://github.com/pdogra1299/bitbucket-mcp-server/issues"
40 |   },
41 |   "homepage": "https://github.com/pdogra1299/bitbucket-mcp-server#readme",
42 |   "engines": {
43 |     "node": ">=16.0.0"
44 |   },
45 |   "dependencies": {
46 |     "@modelcontextprotocol/sdk": "^1.12.1",
47 |     "axios": "^1.10.0",
48 |     "minimatch": "^9.0.3"
49 |   },
50 |   "devDependencies": {
51 |     "@types/minimatch": "^5.1.2",
52 |     "@types/node": "^22.15.29",
53 |     "typescript": "^5.8.3"
54 |   }
55 | }
56 | 
```

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

```yaml
 1 | # Project Brief - Bitbucket MCP Server
 2 | 
 3 | project_name: "bitbucket-mcp-server"
 4 | version: "1.0.0"
 5 | description: "MCP (Model Context Protocol) server for Bitbucket API integration"
 6 | primary_purpose: "Provide tools for interacting with Bitbucket APIs via MCP"
 7 | 
 8 | key_features:
 9 |   - "Support for both Bitbucket Cloud and Server"
10 |   - "19 comprehensive tools for PR management, branch operations, and code review"
11 |   - "Modular architecture with separate handlers for different tool categories"
12 |   - "Smart file content handling with automatic truncation"
13 |   - "Advanced PR commenting with code suggestions and snippet matching"
14 |   - "Code search functionality across repositories"
15 | 
16 | scope:
17 |   included:
18 |     - "Pull Request lifecycle management"
19 |     - "Branch management and operations"
20 |     - "Code review and approval workflows"
21 |     - "File and directory operations"
22 |     - "Code search (Bitbucket Server only)"
23 |     - "Authentication via app passwords/tokens"
24 |   
25 |   excluded:
26 |     - "Repository creation/deletion"
27 |     - "User management"
28 |     - "Pipeline/build operations"
29 |     - "Wiki/documentation management"
30 |     - "Bitbucket Cloud code search (future enhancement)"
31 | 
32 | constraints:
33 |   technical:
34 |     - "Node.js >= 16.0.0 required"
35 |     - "TypeScript with ES modules"
36 |     - "MCP SDK for protocol implementation"
37 |   
38 |   authentication:
39 |     - "Bitbucket Cloud: App passwords required"
40 |     - "Bitbucket Server: HTTP access tokens required"
41 |     - "Different username formats for Cloud vs Server"
42 | 
43 | success_criteria:
44 |   - "Seamless integration with MCP-compatible clients"
45 |   - "Reliable API interactions with proper error handling"
46 |   - "Consistent response formatting across tools"
47 |   - "Maintainable and extensible codebase"
48 | 
```

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

```yaml
 1 | # Product Context - Bitbucket MCP Server
 2 | 
 3 | problem_statement: |
 4 |   Developers using AI assistants need programmatic access to Bitbucket operations
 5 |   without leaving their development environment. Manual API interactions are
 6 |   time-consuming and error-prone.
 7 | 
 8 | target_users:
 9 |   primary:
10 |     - "Software developers using AI coding assistants"
11 |     - "DevOps engineers automating PR workflows"
12 |     - "Code reviewers needing efficient review tools"
13 |   
14 |   secondary:
15 |     - "Project managers tracking development progress"
16 |     - "QA engineers reviewing code changes"
17 | 
18 | user_needs:
19 |   core:
20 |     - "Create and manage pull requests programmatically"
21 |     - "Review code changes with AI assistance"
22 |     - "Automate branch management tasks"
23 |     - "Search code across repositories"
24 |     - "Access file contents without cloning"
25 |   
26 |   workflow:
27 |     - "Comment on PRs with code suggestions"
28 |     - "Approve/request changes on PRs"
29 |     - "List and filter pull requests"
30 |     - "Get comprehensive PR details including comments"
31 |     - "Manage branch lifecycle"
32 | 
33 | value_proposition:
34 |   - "Seamless Bitbucket integration within AI assistants"
35 |   - "Unified interface for Cloud and Server variants"
36 |   - "Time savings through automation"
37 |   - "Reduced context switching"
38 |   - "Enhanced code review quality with AI"
39 | 
40 | user_experience_goals:
41 |   - "Simple tool invocation syntax"
42 |   - "Clear and consistent response formats"
43 |   - "Helpful error messages with recovery suggestions"
44 |   - "Smart defaults (pagination, truncation)"
45 |   - "Flexible filtering and search options"
46 | 
47 | adoption_strategy:
48 |   - "npm package for easy installation"
49 |   - "Comprehensive documentation with examples"
50 |   - "Support for both Cloud and Server"
51 |   - "Gradual feature addition based on user needs"
52 | 
```

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

```markdown
 1 | # Bitbucket Server MCP Setup Guide
 2 | 
 3 | Since you're using Bitbucket Server (self-hosted), you'll need to create an HTTP access token instead of an app password.
 4 | 
 5 | ## Step 1: Your Username
 6 | 
 7 | Your Bitbucket Server username (not email address)
 8 | 
 9 | ## Step 2: Create an HTTP Access Token
10 | 
11 | 1. **Navigate to HTTP Access Tokens**:
12 |    - You mentioned you can see "HTTP access tokens" in your account settings
13 |    - Click on that option
14 | 
15 | 2. **Create a new token**:
16 |    - Click "Create token" or similar button
17 |    - Give it a descriptive name like "MCP Server Integration"
18 |    - Set an expiration date (or leave it without expiration if allowed)
19 |    - Select the following permissions:
20 |      - **Repository**: Read, Write
21 |      - **Pull request**: Read, Write
22 |      - **Project**: Read (if available)
23 | 
24 | 3. **Generate and copy the token**:
25 |    - Click "Create" or "Generate"
26 |    - **IMPORTANT**: Copy the token immediately! It will look like a long string of random characters
27 |    - You won't be able to see this token again
28 | 
29 | ## Step 3: Find Your Bitbucket Server URL
30 | 
31 | Your Bitbucket Server URL is the base URL you use to access Bitbucket. For example:
32 | - `https://bitbucket.yourcompany.com`
33 | - `https://git.yourcompany.com`
34 | - `https://bitbucket.internal.company.net`
35 | 
36 | ## Step 4: Find Your Project/Workspace
37 | 
38 | In Bitbucket Server, repositories are organized by projects. Look at any repository URL:
39 | - Example: `https://bitbucket.company.com/projects/PROJ/repos/my-repo`
40 | - In this case, "PROJ" is your project key
41 | 
42 | ## Example Configuration
43 | 
44 | For Bitbucket Server, your configuration will look like:
45 | 
46 | ```
47 | Username: your.username
48 | Token: [Your HTTP access token]
49 | Base URL: https://bitbucket.yourcompany.com
50 | Project/Workspace: PROJ (or whatever your project key is)
51 | ```
52 | 
53 | ## Next Steps
54 | 
55 | Once you have:
56 | 1. Your username
57 | 2. An HTTP access token from the "HTTP access tokens" section
58 | 3. Your Bitbucket Server base URL
59 | 4. Your project key
60 | 
61 | You can configure the MCP server for Bitbucket Server.
62 | 
```

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

```yaml
 1 | # Progress - Bitbucket MCP Server
 2 | 
 3 | project_status:
 4 |   overall_progress: "95%"
 5 |   phase: "Post-1.0 Improvements"
 6 |   version: "1.0.1"
 7 |   release_status: "Production with improvements"
 8 | 
 9 | milestones_completed:
10 |   - name: "Core PR Tools"
11 |     completion_date: "2025-01-06"
12 |     features:
13 |       - "get_pull_request with merge details"
14 |       - "list_pull_requests with filtering"
15 |       - "create_pull_request"
16 |       - "update_pull_request"
17 |       - "merge_pull_request"
18 |     
19 |   - name: "Enhanced Commenting"
20 |     completion_date: "2025-01-26"
21 |     features:
22 |       - "Inline comments"
23 |       - "Code suggestions"
24 |       - "Code snippet matching"
25 |       - "Nested replies support"
26 |   
27 |   - name: "Code Review Tools"
28 |     completion_date: "2025-01-26"
29 |     features:
30 |       - "get_pull_request_diff with filtering"
31 |       - "approve_pull_request"
32 |       - "request_changes"
33 |       - "Review state management"
34 |   
35 |   - name: "Branch Management"
36 |     completion_date: "2025-01-21"
37 |     features:
38 |       - "list_branches"
39 |       - "delete_branch"
40 |       - "get_branch with PR info"
41 |       - "list_branch_commits"
42 |   
43 |   - name: "File Operations"
44 |     completion_date: "2025-01-21"
45 |     features:
46 |       - "list_directory_content"
47 |       - "get_file_content with smart truncation"
48 |   
49 |   - name: "Code Search"
50 |     completion_date: "2025-07-25"
51 |     features:
52 |       - "search_code for Bitbucket Server"
53 |       - "File pattern filtering"
54 |       - "Highlighted search results"
55 | 
56 | active_development:
57 |   current_sprint: "Post 1.0 Planning"
58 |   in_progress: []
59 |   blocked: []
60 | 
61 | testing_status:
62 |   unit_tests: "Not implemented"
63 |   integration_tests: "Manual testing completed"
64 |   user_acceptance: "In production use"
65 |   known_issues: []
66 | 
67 | deployment_status:
68 |   npm_package: "Published as @nexus2520/bitbucket-mcp-server"
69 |   version_published: "1.0.0"
70 |   documentation: "Comprehensive README with examples"
71 |   adoption_metrics:
72 |     - "npm weekly downloads tracking"
73 |     - "GitHub stars and issues"
74 | 
75 | performance_metrics:
76 |   api_response_time: "< 2s average"
77 |   memory_usage: "< 100MB typical"
78 |   concurrent_operations: "Supports parallel API calls"
79 | 
80 | next_release_planning:
81 |   version: "1.1.0"
82 |   planned_features:
83 |     - "Bitbucket Cloud search support"
84 |     - "Repository management tools"
85 |     - "Pipeline/build integration"
86 |   timeline: "TBD based on user feedback"
87 | 
```

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

```markdown
 1 | # Bitbucket MCP Server Setup Guide
 2 | 
 3 | ## Step 1: Find Your Bitbucket Username
 4 | 
 5 | 1. **Log in to Bitbucket**: Go to https://bitbucket.org and log in with your credentials
 6 | 
 7 | 2. **Find your username**:
 8 |    - After logging in, click on your profile avatar in the top-right corner
 9 |    - Click on "Personal settings" or go directly to: https://bitbucket.org/account/settings/
10 |    - Your username will be displayed at the top of the page
11 |    - **Note**: Your username is NOT your email address. It's usually a shorter identifier like "johndoe" or "jdoe123"
12 | 
13 | ## Step 2: Create an App Password
14 | 
15 | 1. **Navigate to App Passwords**:
16 |    - While logged in, go to: https://bitbucket.org/account/settings/app-passwords/
17 |    - Or from your account settings, look for "App passwords" in the left sidebar under "Access management"
18 | 
19 | 2. **Create a new app password**:
20 |    - Click the "Create app password" button
21 |    - Give it a descriptive label like "MCP Server" or "Bitbucket MCP Integration"
22 |    
23 | 3. **Select permissions** (IMPORTANT - select these specific permissions):
24 |    - ✅ **Account**: Read
25 |    - ✅ **Repositories**: Read, Write
26 |    - ✅ **Pull requests**: Read, Write
27 |    - You can leave other permissions unchecked
28 | 
29 | 4. **Generate the password**:
30 |    - Click "Create"
31 |    - **IMPORTANT**: Copy the generated password immediately! It will look something like: `ATBBxxxxxxxxxxxxxxxxxxxxx`
32 |    - You won't be able to see this password again after closing the dialog
33 | 
34 | ## Step 3: Find Your Workspace (Optional but Recommended)
35 | 
36 | Your workspace is the organization or team name in Bitbucket. To find it:
37 | 
38 | 1. Look at any of your repository URLs:
39 |    - Example: `https://bitbucket.org/mycompany/my-repo`
40 |    - In this case, "mycompany" is your workspace
41 | 
42 | 2. Or go to your workspace dashboard:
43 |    - Click on "Workspaces" in the top navigation
44 |    - Your workspaces will be listed there
45 | 
46 | ## Example Credentials
47 | 
48 | Here's what your credentials should look like:
49 | 
50 | ```
51 | Username: johndoe              # Your Bitbucket username (NOT email)
52 | App Password: ATBB3xXx...      # The generated app password
53 | Workspace: mycompany           # Your organization/workspace name
54 | ```
55 | 
56 | ## Common Issues
57 | 
58 | 1. **"Username not found"**: Make sure you're using your Bitbucket username, not your email address
59 | 2. **"Invalid app password"**: Ensure you copied the entire app password including the "ATBB" prefix
60 | 3. **"Permission denied"**: Check that your app password has the required permissions (Account: Read, Repositories: Read/Write, Pull requests: Read/Write)
61 | 
62 | ## Next Steps
63 | 
64 | 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.
65 | 
```

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

```yaml
  1 | # Technical Context - Bitbucket MCP Server
  2 | 
  3 | core_technologies:
  4 |   language: "TypeScript"
  5 |   version: "5.8.3"
  6 |   module_system: "ES Modules"
  7 |   runtime:
  8 |     name: "Node.js"
  9 |     version: ">= 16.0.0"
 10 |     package_manager: "npm"
 11 | 
 12 | libraries_and_bindings:
 13 |   - name: "@modelcontextprotocol/sdk"
 14 |     version: "^1.12.1"
 15 |     purpose: "MCP protocol implementation"
 16 |   
 17 |   - name: "axios"
 18 |     version: "^1.10.0"
 19 |     purpose: "HTTP client for API requests"
 20 |   
 21 |   - name: "minimatch"
 22 |     version: "^9.0.3"
 23 |     purpose: "Glob pattern matching for file filtering"
 24 | 
 25 | development_environment:
 26 |   build_tools:
 27 |     - "TypeScript compiler (tsc)"
 28 |     - "npm scripts for build automation"
 29 |   
 30 |   commands:
 31 |     build: "npm run build"
 32 |     dev: "npm run dev"
 33 |     start: "npm start"
 34 |     publish: "npm publish"
 35 |   
 36 |   project_structure:
 37 |     src_directory: "src/"
 38 |     build_directory: "build/"
 39 |     entry_point: "src/index.ts"
 40 |     compiled_entry: "build/index.js"
 41 | 
 42 | technical_patterns:
 43 |   - name: "Shebang for CLI execution"
 44 |     description: "#!/usr/bin/env node at top of index.ts"
 45 |     usage: "Enables direct execution as CLI tool"
 46 |   
 47 |   - name: "ES Module imports"
 48 |     description: "Using .js extensions in TypeScript imports"
 49 |     usage: "Required for ES module compatibility"
 50 |     examples:
 51 |       - "import { Server } from '@modelcontextprotocol/sdk/server/index.js'"
 52 |   
 53 |   - name: "Type-safe error handling"
 54 |     description: "Custom ApiError interface with typed errors"
 55 |     usage: "Consistent error handling across API calls"
 56 |   
 57 |   - name: "Environment variable configuration"
 58 |     description: "Process.env for authentication and base URL"
 59 |     usage: "Flexible configuration without code changes"
 60 | 
 61 | api_integration:
 62 |   bitbucket_cloud:
 63 |     base_url: "https://api.bitbucket.org/2.0"
 64 |     auth_method: "Basic Auth with App Password"
 65 |     api_style: "RESTful with JSON"
 66 |     pagination: "page-based with pagelen parameter"
 67 |   
 68 |   bitbucket_server:
 69 |     base_url: "Custom URL (e.g., https://bitbucket.company.com)"
 70 |     auth_method: "Bearer token (HTTP Access Token)"
 71 |     api_style: "RESTful with JSON"
 72 |     pagination: "offset-based with start/limit"
 73 |     api_version: "/rest/api/1.0"
 74 |     search_api: "/rest/search/latest"
 75 | 
 76 | deployment:
 77 |   package_name: "@nexus2520/bitbucket-mcp-server"
 78 |   registry: "npm public registry"
 79 |   distribution:
 80 |     - "Compiled JavaScript in build/"
 81 |     - "Type definitions excluded"
 82 |     - "Source maps excluded"
 83 |   
 84 |   execution_methods:
 85 |     - "npx -y @nexus2520/bitbucket-mcp-server"
 86 |     - "Direct node execution after install"
 87 |     - "MCP client integration"
 88 | 
 89 | security_considerations:
 90 |   - "Credentials stored in environment variables"
 91 |   - "No credential logging or exposure"
 92 |   - "HTTPS only for API communications"
 93 |   - "Token/password validation on startup"
 94 | 
 95 | performance_optimizations:
 96 |   - "Parallel API calls for PR details"
 97 |   - "Smart file truncation to prevent token overflow"
 98 |   - "Pagination for large result sets"
 99 |   - "Early exit on authentication failure"
100 | 
101 | compatibility:
102 |   mcp_clients:
103 |     - "Cline (VSCode extension)"
104 |     - "Other MCP-compatible AI assistants"
105 |   
106 |   bitbucket_versions:
107 |     - "Bitbucket Cloud (latest API)"
108 |     - "Bitbucket Server 7.x+"
109 |     - "Bitbucket Data Center"
110 | 
```

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

```yaml
  1 | # System Patterns - Bitbucket MCP Server
  2 | 
  3 | architecture_overview:
  4 |   high_level_architecture: |
  5 |     MCP Server implementation with modular handler architecture.
  6 |     Main server class delegates tool calls to specialized handlers.
  7 |     Each handler manages a specific domain (PRs, branches, reviews, files, search).
  8 |   
  9 |   component_relationships: |
 10 |     - BitbucketMCPServer (main) → Handler classes → BitbucketApiClient → Axios
 11 |     - Tool definitions → MCP SDK → Client applications
 12 |     - Type guards validate inputs → Handlers process → Formatters standardize output
 13 | 
 14 | design_patterns:
 15 |   - name: "Handler Pattern"
 16 |     category: "architecture"
 17 |     description: "Separate handler classes for different tool categories"
 18 |     usage: "Organizing related tools and maintaining single responsibility"
 19 |     implementation:
 20 |       - "PullRequestHandlers for PR lifecycle"
 21 |       - "BranchHandlers for branch operations"
 22 |       - "ReviewHandlers for code review tools"
 23 |       - "FileHandlers for file/directory operations"
 24 |       - "SearchHandlers for code search"
 25 |     example_files:
 26 |       - "src/handlers/*.ts"
 27 |     related_patterns:
 28 |       - "Dependency Injection"
 29 | 
 30 |   - name: "API Client Abstraction"
 31 |     category: "integration"
 32 |     description: "Unified client handling both Cloud and Server APIs"
 33 |     usage: "Abstracting API differences between Bitbucket variants"
 34 |     implementation:
 35 |       - "Single makeRequest method for all HTTP operations"
 36 |       - "Automatic auth header selection (Bearer vs Basic)"
 37 |       - "Consistent error handling across variants"
 38 |     example_files:
 39 |       - "src/utils/api-client.ts"
 40 | 
 41 |   - name: "Type Guard Pattern"
 42 |     category: "validation"
 43 |     description: "Runtime type checking for tool arguments"
 44 |     usage: "Ensuring type safety for dynamic tool inputs"
 45 |     implementation:
 46 |       - "Guard functions return type predicates"
 47 |       - "Comprehensive validation of required/optional fields"
 48 |       - "Array and nested object validation"
 49 |     example_files:
 50 |       - "src/types/guards.ts"
 51 | 
 52 |   - name: "Response Formatting"
 53 |     category: "data_transformation"
 54 |     description: "Consistent response formatting across API variants"
 55 |     usage: "Normalizing different API response structures"
 56 |     implementation:
 57 |       - "formatServerResponse/formatCloudResponse for PRs"
 58 |       - "Unified FormattedXXX interfaces"
 59 |       - "Separate formatters for different data types"
 60 |     example_files:
 61 |       - "src/utils/formatters.ts"
 62 | 
 63 | project_specific_patterns:
 64 |   mcp_patterns:
 65 |     - name: "Tool Definition Structure"
 66 |       description: "Standardized tool definition with inputSchema"
 67 |       implementation:
 68 |         - "Name, description, and JSON schema for each tool"
 69 |         - "Required vs optional parameter specification"
 70 |         - "Enum constraints for valid values"
 71 |     
 72 |     - name: "Error Response Pattern"
 73 |       description: "Consistent error handling and reporting"
 74 |       implementation:
 75 |         - "Return isError: true for tool failures"
 76 |         - "Include detailed error messages"
 77 |         - "Provide context-specific error details"
 78 | 
 79 |   bitbucket_patterns:
 80 |     - name: "Pagination Pattern"
 81 |       description: "Consistent pagination across list operations"
 82 |       implementation:
 83 |         - "limit and start parameters"
 84 |         - "has_more and next_start in responses"
 85 |         - "total_count for result sets"
 86 |     
 87 |     - name: "Dual API Support"
 88 |       description: "Supporting both Cloud and Server APIs"
 89 |       implementation:
 90 |         - "isServer flag determines API paths"
 91 |         - "Different parameter names mapped appropriately"
 92 |         - "Response structure normalization"
 93 | 
 94 |   code_patterns:
 95 |     - name: "Smart Truncation"
 96 |       description: "Intelligent file content truncation"
 97 |       implementation:
 98 |         - "File type-based default limits"
 99 |         - "Size-based automatic truncation"
100 |         - "Line range selection support"
101 |     
102 |     - name: "Code Snippet Matching"
103 |       description: "Finding line numbers from code snippets"
104 |       implementation:
105 |         - "Exact text matching with context"
106 |         - "Confidence scoring for multiple matches"
107 |         - "Strategy selection (strict vs best)"
108 | 
```

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

```yaml
  1 | # Active Context - Bitbucket MCP Server
  2 | 
  3 | current_focus_areas:
  4 |   - name: "Search Code Feature Implementation"
  5 |     status: "completed"
  6 |     priority: "high"
  7 |     team: "frontend"
  8 |     timeline: "2025-07-25"
  9 | 
 10 | recent_changes:
 11 |   - date: "2025-08-08"
 12 |     feature: "testing_and_release_v1.0.1"
 13 |     description: "Comprehensive testing of search functionality and release of version 1.0.1"
 14 |     status: "completed"
 15 |     files_affected:
 16 |       - "package.json"
 17 |       - "CHANGELOG.md"
 18 |       - "memory-bank/activeContext.yml"
 19 |       - "memory-bank/progress.yml"
 20 |     technical_details: "Tested search functionality with real Bitbucket data, verified context-aware patterns, file filtering, and error handling"
 21 |     business_impact: "Validated production readiness of search improvements and properly versioned the release"
 22 |     patterns_introduced:
 23 |       - "Live MCP testing workflow using connected Bitbucket server"
 24 |       - "Real-world validation of search functionality"
 25 |       
 26 |   - date: "2025-07-25"
 27 |     feature: "code_search_response_fix"
 28 |     description: "Fixed search_code tool response handling to match actual Bitbucket API structure"
 29 |     status: "completed"
 30 |     files_affected:
 31 |       - "src/utils/formatters.ts"
 32 |       - "src/handlers/search-handlers.ts"
 33 |     technical_details: "Added formatCodeSearchOutput for simplified AI-friendly output showing only filename, line number, and text"
 34 |     business_impact: "Search results now properly formatted for AI consumption with cleaner output"
 35 |     patterns_introduced:
 36 |       - "Simplified formatter pattern for AI-friendly output"
 37 |       
 38 |   - date: "2025-07-25"
 39 |     feature: "code_search"
 40 |     description: "Added search_code tool for searching code across repositories"
 41 |     status: "completed"
 42 |     files_affected:
 43 |       - "src/handlers/search-handlers.ts"
 44 |       - "src/types/bitbucket.ts"
 45 |       - "src/utils/formatters.ts"
 46 |       - "src/types/guards.ts"
 47 |       - "src/tools/definitions.ts"
 48 |       - "src/index.ts"
 49 |     technical_details: "Implemented Bitbucket Server search API integration"
 50 |     business_impact: "Enables code search functionality for Bitbucket Server users"
 51 |     patterns_introduced:
 52 |       - "SearchHandlers following modular architecture"
 53 |       - "Search result formatting pattern"
 54 | 
 55 | patterns_discovered:
 56 |   - name: "Modular Handler Architecture"
 57 |     description: "Each tool category has its own handler class"
 58 |     usage: "Add new handlers for new tool categories"
 59 |     implementation: "Create handler class, add to index.ts, register tools"
 60 | 
 61 |   - name: "API Variant Handling"
 62 |     description: "Single handler supports both Cloud and Server APIs"
 63 |     usage: "Check isServer flag and adjust API paths/parameters"
 64 |     implementation: "Use apiClient.getIsServer() to determine variant"
 65 |     
 66 |   - name: "Simplified AI Formatter Pattern"
 67 |     description: "Separate formatters for detailed vs AI-friendly output"
 68 |     usage: "Create simplified formatters that extract only essential information for AI"
 69 |     implementation: "formatCodeSearchOutput strips HTML, shows only file:line:text format"
 70 | 
 71 | recent_learnings:
 72 |   - date: "2025-07-25"
 73 |     topic: "Bitbucket Search API Response Structure"
 74 |     description: "API returns file as string (not object), uses hitContexts with HTML formatting"
 75 |     impact: "Need to parse HTML <em> tags and handle different response structure"
 76 |     
 77 |   - date: "2025-07-25"
 78 |     topic: "Bitbucket Search API"
 79 |     description: "Search API only available on Bitbucket Server, not Cloud"
 80 |     impact: "Search tool marked as Server-only with future Cloud support planned"
 81 | 
 82 |   - date: "2025-07-25"
 83 |     topic: "Version Management"
 84 |     description: "Major version 1.0.0 indicates stable API with comprehensive features"
 85 |     impact: "Project ready for production use"
 86 | 
 87 | key_decisions:
 88 |   - decision: "Separate handler for search tools"
 89 |     rationale: "Maintains single responsibility and modularity"
 90 |     date: "2025-07-25"
 91 |     
 92 |   - decision: "YAML format for Memory Bank"
 93 |     rationale: "Better merge conflict handling and structured data"
 94 |     date: "2025-07-25"
 95 | 
 96 | current_challenges:
 97 |   - "Bitbucket Cloud search API not yet available"
 98 |   - "Need to handle different search syntaxes across platforms"
 99 | 
100 | next_priorities:
101 |   - "Add Bitbucket Cloud search when API becomes available"
102 |   - "Enhance search with more filtering options"
103 |   - "Add support for searching in other entities (commits, PRs)"
104 | 
```

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

```typescript
  1 | import axios, { AxiosInstance, AxiosError } from 'axios';
  2 | import { BitbucketServerBuildSummary } from '../types/bitbucket.js';
  3 | 
  4 | export interface ApiError {
  5 |   status?: number;
  6 |   message: string;
  7 |   isAxiosError: boolean;
  8 |   originalError?: AxiosError;
  9 | }
 10 | 
 11 | export class BitbucketApiClient {
 12 |   private axiosInstance: AxiosInstance;
 13 |   private isServer: boolean;
 14 | 
 15 |   constructor(
 16 |     baseURL: string,
 17 |     username: string,
 18 |     password?: string,
 19 |     token?: string
 20 |   ) {
 21 |     this.isServer = !!token;
 22 |     
 23 |     const axiosConfig: any = {
 24 |       baseURL,
 25 |       headers: {
 26 |         'Content-Type': 'application/json',
 27 |       },
 28 |     };
 29 | 
 30 |     // Use token auth for Bitbucket Server, basic auth for Cloud
 31 |     if (token) {
 32 |       // Bitbucket Server uses Bearer token
 33 |       axiosConfig.headers['Authorization'] = `Bearer ${token}`;
 34 |     } else {
 35 |       // Bitbucket Cloud uses basic auth with app password
 36 |       axiosConfig.auth = {
 37 |         username,
 38 |         password,
 39 |       };
 40 |     }
 41 | 
 42 |     this.axiosInstance = axios.create(axiosConfig);
 43 |   }
 44 | 
 45 |   async makeRequest<T>(
 46 |     method: 'get' | 'post' | 'put' | 'delete',
 47 |     path: string,
 48 |     data?: any,
 49 |     config?: any
 50 |   ): Promise<T> {
 51 |     try {
 52 |       let response;
 53 |       if (method === 'get') {
 54 |         // For GET, config is the second parameter
 55 |         response = await this.axiosInstance[method](path, config || {});
 56 |       } else if (method === 'delete') {
 57 |         // For DELETE, we might need to pass data in config
 58 |         if (data) {
 59 |           response = await this.axiosInstance[method](path, { ...config, data });
 60 |         } else {
 61 |           response = await this.axiosInstance[method](path, config || {});
 62 |         }
 63 |       } else {
 64 |         // For POST and PUT, data is second, config is third
 65 |         response = await this.axiosInstance[method](path, data, config);
 66 |       }
 67 |       return response.data;
 68 |     } catch (error) {
 69 |       if (axios.isAxiosError(error)) {
 70 |         const status = error.response?.status;
 71 |         const message = error.response?.data?.errors?.[0]?.message || 
 72 |                        error.response?.data?.error?.message || 
 73 |                        error.response?.data?.message ||
 74 |                        error.message;
 75 | 
 76 |         throw {
 77 |           status,
 78 |           message,
 79 |           isAxiosError: true,
 80 |           originalError: error
 81 |         } as ApiError;
 82 |       }
 83 |       throw error;
 84 |     }
 85 |   }
 86 | 
 87 |   handleApiError(error: any, context: string) {
 88 |     if (error.isAxiosError) {
 89 |       const { status, message } = error as ApiError;
 90 | 
 91 |       if (status === 404) {
 92 |         return {
 93 |           content: [
 94 |             {
 95 |               type: 'text',
 96 |               text: `Not found: ${context}`,
 97 |             },
 98 |           ],
 99 |           isError: true,
100 |         };
101 |       } else if (status === 401) {
102 |         return {
103 |           content: [
104 |             {
105 |               type: 'text',
106 |               text: `Authentication failed. Please check your ${this.isServer ? 'BITBUCKET_TOKEN' : 'BITBUCKET_USERNAME and BITBUCKET_APP_PASSWORD'}`,
107 |             },
108 |           ],
109 |           isError: true,
110 |         };
111 |       } else if (status === 403) {
112 |         return {
113 |           content: [
114 |             {
115 |               type: 'text',
116 |               text: `Permission denied: ${context}. Ensure your credentials have the necessary permissions.`,
117 |             },
118 |           ],
119 |           isError: true,
120 |         };
121 |       }
122 | 
123 |       return {
124 |         content: [
125 |           {
126 |             type: 'text',
127 |             text: `Bitbucket API error: ${message}`,
128 |           },
129 |         ],
130 |         isError: true,
131 |       };
132 |     }
133 |     throw error;
134 |   }
135 | 
136 |   getIsServer(): boolean {
137 |     return this.isServer;
138 |   }
139 | 
140 |   async getBuildSummaries(
141 |     workspace: string,
142 |     repository: string,
143 |     commitIds: string[]
144 |   ): Promise<BitbucketServerBuildSummary> {
145 |     if (!this.isServer) {
146 |       // Build summaries only available for Bitbucket Server
147 |       return {};
148 |     }
149 | 
150 |     if (commitIds.length === 0) {
151 |       return {};
152 |     }
153 | 
154 |     try {
155 |       // Build query string with multiple commitId parameters
156 |       const apiPath = `/rest/ui/latest/projects/${workspace}/repos/${repository}/build-summaries`;
157 | 
158 |       // Create params with custom serializer for multiple commitId parameters
159 |       const response = await this.makeRequest<BitbucketServerBuildSummary>(
160 |         'get',
161 |         apiPath,
162 |         undefined,
163 |         {
164 |           params: { commitId: commitIds },
165 |           paramsSerializer: (params: any) => {
166 |             // Custom serializer to create multiple commitId= parameters
167 |             if (params.commitId && Array.isArray(params.commitId)) {
168 |               return params.commitId.map((id: string) => `commitId=${encodeURIComponent(id)}`).join('&');
169 |             }
170 |             return '';
171 |           }
172 |         }
173 |       );
174 | 
175 |       return response;
176 |     } catch (error) {
177 |       // If build-summaries endpoint fails, return empty object (graceful degradation)
178 |       console.error('Failed to fetch build summaries:', error);
179 |       return {};
180 |     }
181 |   }
182 | }
183 | 
```

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

```typescript
  1 | import { minimatch } from 'minimatch';
  2 | 
  3 | export interface DiffSection {
  4 |   filePath: string;
  5 |   oldPath?: string; // For renamed files
  6 |   content: string;
  7 |   isNew: boolean;
  8 |   isDeleted: boolean;
  9 |   isRenamed: boolean;
 10 |   isBinary: boolean;
 11 | }
 12 | 
 13 | export interface FilterOptions {
 14 |   includePatterns?: string[];
 15 |   excludePatterns?: string[];
 16 |   filePath?: string;
 17 | }
 18 | 
 19 | export interface FilteredResult {
 20 |   sections: DiffSection[];
 21 |   metadata: {
 22 |     totalFiles: number;
 23 |     includedFiles: number;
 24 |     excludedFiles: number;
 25 |     excludedFileList: string[];
 26 |   };
 27 | }
 28 | 
 29 | export class DiffParser {
 30 |   /**
 31 |    * Parse a unified diff into file sections
 32 |    */
 33 |   parseDiffIntoSections(diff: string): DiffSection[] {
 34 |     const sections: DiffSection[] = [];
 35 |     
 36 |     // Split by file boundaries - handle both formats
 37 |     const fileChunks = diff.split(/(?=^diff --git)/gm).filter(chunk => chunk.trim());
 38 |     
 39 |     for (const chunk of fileChunks) {
 40 |       const section = this.parseFileSection(chunk);
 41 |       if (section) {
 42 |         sections.push(section);
 43 |       }
 44 |     }
 45 |     
 46 |     return sections;
 47 |   }
 48 | 
 49 |   /**
 50 |    * Parse a single file section from the diff
 51 |    */
 52 |   private parseFileSection(chunk: string): DiffSection | null {
 53 |     const lines = chunk.split('\n');
 54 |     if (lines.length === 0) return null;
 55 |     
 56 |     // Extract file paths from the diff header
 57 |     let filePath = '';
 58 |     let oldPath: string | undefined;
 59 |     let isNew = false;
 60 |     let isDeleted = false;
 61 |     let isRenamed = false;
 62 |     let isBinary = false;
 63 |     
 64 |     // Look for diff --git line - handle both standard and Bitbucket Server formats
 65 |     const gitDiffMatch = lines[0].match(/^diff --git (?:a\/|src:\/\/)(.+?) (?:b\/|dst:\/\/)(.+?)$/);
 66 |     if (gitDiffMatch) {
 67 |       const [, aPath, bPath] = gitDiffMatch;
 68 |       filePath = bPath;
 69 |       
 70 |       // Check subsequent lines for file status
 71 |       for (let i = 1; i < Math.min(lines.length, 10); i++) {
 72 |         const line = lines[i];
 73 |         
 74 |         if (line.startsWith('new file mode')) {
 75 |           isNew = true;
 76 |         } else if (line.startsWith('deleted file mode')) {
 77 |           isDeleted = true;
 78 |           filePath = aPath; // Use the original path for deleted files
 79 |         } else if (line.startsWith('rename from')) {
 80 |           isRenamed = true;
 81 |           oldPath = line.replace('rename from ', '');
 82 |         } else if (line.includes('Binary files') && line.includes('differ')) {
 83 |           isBinary = true;
 84 |         } else if (line.startsWith('--- ')) {
 85 |           // Alternative way to detect new/deleted
 86 |           if (line.includes('/dev/null')) {
 87 |             isNew = true;
 88 |           }
 89 |         } else if (line.startsWith('+++ ')) {
 90 |           if (line.includes('/dev/null')) {
 91 |             isDeleted = true;
 92 |           }
 93 |           // Extract path from +++ line if needed - handle both formats
 94 |           const match = line.match(/^\+\+\+ (?:b\/|dst:\/\/)(.+)$/);
 95 |           if (match && !filePath) {
 96 |             filePath = match[1];
 97 |           }
 98 |         }
 99 |       }
100 |     }
101 |     
102 |     // Fallback: try to extract from --- and +++ lines
103 |     if (!filePath) {
104 |       for (const line of lines) {
105 |         if (line.startsWith('+++ ')) {
106 |           const match = line.match(/^\+\+\+ (?:b\/|dst:\/\/)(.+)$/);
107 |           if (match) {
108 |             filePath = match[1];
109 |             break;
110 |           }
111 |         } else if (line.startsWith('--- ')) {
112 |           const match = line.match(/^--- (?:a\/|src:\/\/)(.+)$/);
113 |           if (match) {
114 |             filePath = match[1];
115 |           }
116 |         }
117 |       }
118 |     }
119 |     
120 |     if (!filePath) return null;
121 |     
122 |     return {
123 |       filePath,
124 |       oldPath,
125 |       content: chunk,
126 |       isNew,
127 |       isDeleted,
128 |       isRenamed,
129 |       isBinary
130 |     };
131 |   }
132 | 
133 |   /**
134 |    * Apply filters to diff sections
135 |    */
136 |   filterSections(sections: DiffSection[], options: FilterOptions): FilteredResult {
137 |     const excludedFileList: string[] = [];
138 |     let filteredSections = sections;
139 |     
140 |     // If specific file path is requested, only keep that file
141 |     if (options.filePath) {
142 |       filteredSections = sections.filter(section => 
143 |         section.filePath === options.filePath || 
144 |         section.oldPath === options.filePath
145 |       );
146 |       
147 |       // Track excluded files
148 |       sections.forEach(section => {
149 |         if (section.filePath !== options.filePath && 
150 |             section.oldPath !== options.filePath) {
151 |           excludedFileList.push(section.filePath);
152 |         }
153 |       });
154 |     } else {
155 |       // Apply exclude patterns first (blacklist)
156 |       if (options.excludePatterns && options.excludePatterns.length > 0) {
157 |         filteredSections = filteredSections.filter(section => {
158 |           const shouldExclude = options.excludePatterns!.some(pattern => 
159 |             minimatch(section.filePath, pattern, { matchBase: true })
160 |           );
161 |           
162 |           if (shouldExclude) {
163 |             excludedFileList.push(section.filePath);
164 |             return false;
165 |           }
166 |           return true;
167 |         });
168 |       }
169 |       
170 |       // Apply include patterns if specified (whitelist)
171 |       if (options.includePatterns && options.includePatterns.length > 0) {
172 |         filteredSections = filteredSections.filter(section => {
173 |           const shouldInclude = options.includePatterns!.some(pattern => 
174 |             minimatch(section.filePath, pattern, { matchBase: true })
175 |           );
176 |           
177 |           if (!shouldInclude) {
178 |             excludedFileList.push(section.filePath);
179 |             return false;
180 |           }
181 |           return true;
182 |         });
183 |       }
184 |     }
185 |     
186 |     return {
187 |       sections: filteredSections,
188 |       metadata: {
189 |         totalFiles: sections.length,
190 |         includedFiles: filteredSections.length,
191 |         excludedFiles: sections.length - filteredSections.length,
192 |         excludedFileList
193 |       }
194 |     };
195 |   }
196 | 
197 |   /**
198 |    * Reconstruct a unified diff from filtered sections
199 |    */
200 |   reconstructDiff(sections: DiffSection[]): string {
201 |     if (sections.length === 0) {
202 |       return '';
203 |     }
204 |     
205 |     // Join all sections with proper spacing
206 |     return sections.map(section => section.content).join('\n');
207 |   }
208 | }
209 | 
```

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

```typescript
  1 | import { BitbucketApiClient } from '../utils/api-client.js';
  2 | import { 
  3 |   BitbucketServerSearchRequest,
  4 |   BitbucketServerSearchResult,
  5 |   FormattedSearchResult
  6 | } from '../types/bitbucket.js';
  7 | import { formatSearchResults, formatCodeSearchOutput } from '../utils/formatters.js';
  8 | 
  9 | interface SearchContext {
 10 |   assignment: string[];
 11 |   declaration: string[];
 12 |   usage: string[];
 13 |   exact: string[];
 14 |   any: string[];
 15 | }
 16 | 
 17 | function buildContextualPatterns(searchTerm: string): SearchContext {
 18 |   return {
 19 |     assignment: [
 20 |       `${searchTerm} =`,           // Variable assignment
 21 |       `${searchTerm}:`,            // Object property, JSON key
 22 |       `= ${searchTerm}`,           // Right-hand assignment
 23 |     ],
 24 |     declaration: [
 25 |       `${searchTerm} =`,           // Variable definition
 26 |       `${searchTerm}:`,            // Object key, parameter definition
 27 |       `function ${searchTerm}`,    // Function declaration
 28 |       `class ${searchTerm}`,       // Class declaration
 29 |       `interface ${searchTerm}`,   // Interface declaration
 30 |       `const ${searchTerm}`,       // Const declaration
 31 |       `let ${searchTerm}`,         // Let declaration
 32 |       `var ${searchTerm}`,         // Var declaration
 33 |     ],
 34 |     usage: [
 35 |       `.${searchTerm}`,            // Property/method access
 36 |       `${searchTerm}(`,            // Function call
 37 |       `${searchTerm}.`,            // Method chaining
 38 |       `${searchTerm}[`,            // Array/object access
 39 |       `(${searchTerm}`,            // Parameter usage
 40 |     ],
 41 |     exact: [
 42 |       `"${searchTerm}"`,           // Exact quoted match
 43 |     ],
 44 |     any: [
 45 |       `"${searchTerm}"`,           // Exact match
 46 |       `${searchTerm} =`,           // Assignment
 47 |       `${searchTerm}:`,            // Object property
 48 |       `.${searchTerm}`,            // Property access
 49 |       `${searchTerm}(`,            // Function call
 50 |       `function ${searchTerm}`,    // Function definition
 51 |       `class ${searchTerm}`,       // Class definition
 52 |     ]
 53 |   };
 54 | }
 55 | 
 56 | function buildSmartQuery(
 57 |   searchTerm: string, 
 58 |   searchContext: string = 'any',
 59 |   includePatterns: string[] = []
 60 | ): string {
 61 |   const contextPatterns = buildContextualPatterns(searchTerm);
 62 |   
 63 |   let patterns: string[] = [];
 64 |   
 65 |   // Add patterns based on context
 66 |   if (searchContext in contextPatterns) {
 67 |     patterns = [...contextPatterns[searchContext as keyof SearchContext]];
 68 |   } else {
 69 |     patterns = [...contextPatterns.any];
 70 |   }
 71 |   
 72 |   // Add user-provided patterns
 73 |   if (includePatterns && includePatterns.length > 0) {
 74 |     patterns = [...patterns, ...includePatterns];
 75 |   }
 76 |   
 77 |   // Remove duplicates and join with OR
 78 |   const uniquePatterns = [...new Set(patterns)];
 79 |   
 80 |   // If only one pattern, return it without parentheses
 81 |   if (uniquePatterns.length === 1) {
 82 |     return uniquePatterns[0];
 83 |   }
 84 |   
 85 |   // Wrap each pattern in quotes for safety and join with OR
 86 |   const quotedPatterns = uniquePatterns.map(pattern => `"${pattern}"`);
 87 |   return `(${quotedPatterns.join(' OR ')})`;
 88 | }
 89 | 
 90 | export class SearchHandlers {
 91 |   constructor(
 92 |     private apiClient: BitbucketApiClient,
 93 |     private baseUrl: string
 94 |   ) {}
 95 | 
 96 |   async handleSearchCode(args: any) {
 97 |     try {
 98 |       const { 
 99 |         workspace, 
100 |         repository, 
101 |         search_query, 
102 |         search_context = 'any',
103 |         file_pattern, 
104 |         include_patterns = [],
105 |         limit = 25, 
106 |         start = 0 
107 |       } = args;
108 | 
109 |       if (!workspace || !search_query) {
110 |         throw new Error('Workspace and search_query are required');
111 |       }
112 | 
113 |       // Only works for Bitbucket Server currently
114 |       if (!this.apiClient.getIsServer()) {
115 |         throw new Error('Code search is currently only supported for Bitbucket Server');
116 |       }
117 | 
118 |       // Build the enhanced query string
119 |       let query = `project:${workspace}`;
120 |       if (repository) {
121 |         query += ` repo:${repository}`;
122 |       }
123 |       if (file_pattern) {
124 |         query += ` path:${file_pattern}`;
125 |       }
126 |       
127 |       // Build smart search patterns
128 |       const smartQuery = buildSmartQuery(search_query, search_context, include_patterns);
129 |       query += ` ${smartQuery}`;
130 | 
131 |       // Prepare the request payload
132 |       const payload: BitbucketServerSearchRequest = {
133 |         query: query.trim(),
134 |         entities: { 
135 |           code: {
136 |             start: start,
137 |             limit: limit
138 |           }
139 |         }
140 |       };
141 | 
142 |       // Make the API request (no query params needed, pagination is in payload)
143 |       const response = await this.apiClient.makeRequest<BitbucketServerSearchResult>(
144 |         'post',
145 |         `/rest/search/latest/search?avatarSize=64`,
146 |         payload
147 |       );
148 | 
149 |       const searchResult = response;
150 | 
151 |       // Use simplified formatter for cleaner output
152 |       const simplifiedOutput = formatCodeSearchOutput(searchResult);
153 | 
154 |       // Prepare pagination info
155 |       const hasMore = searchResult.code?.isLastPage === false;
156 |       const nextStart = hasMore ? (searchResult.code?.nextStart || start + limit) : undefined;
157 |       const totalCount = searchResult.code?.count || 0;
158 | 
159 |       // Build a concise response with search context info
160 |       let resultText = `Code search results for "${search_query}"`;
161 |       if (search_context !== 'any') {
162 |         resultText += ` (context: ${search_context})`;
163 |       }
164 |       resultText += ` in ${workspace}`;
165 |       if (repository) {
166 |         resultText += `/${repository}`;
167 |       }
168 |       
169 |       // Show the actual search query used
170 |       resultText += `\n\nSearch query: ${query.trim()}`;
171 |       resultText += `\n\n${simplifiedOutput}`;
172 |       
173 |       if (totalCount > 0) {
174 |         resultText += `\n\nTotal matches: ${totalCount}`;
175 |         if (hasMore) {
176 |           resultText += ` (showing ${start + 1}-${start + (searchResult.code?.values?.length || 0)})`;
177 |         }
178 |       }
179 | 
180 |       return {
181 |         content: [{
182 |           type: 'text',
183 |           text: resultText
184 |         }]
185 |       };
186 |     } catch (error: any) {
187 |       const errorMessage = error.response?.data?.errors?.[0]?.message || error.message;
188 |       return {
189 |         content: [{
190 |           type: 'text',
191 |           text: JSON.stringify({
192 |             error: `Failed to search code: ${errorMessage}`,
193 |             details: error.response?.data
194 |           }, null, 2)
195 |         }],
196 |         isError: true
197 |       };
198 |     }
199 |   }
200 | }
201 | 
```

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

```typescript
  1 | #!/usr/bin/env node
  2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4 | import {
  5 |   CallToolRequestSchema,
  6 |   ErrorCode,
  7 |   ListToolsRequestSchema,
  8 |   McpError,
  9 | } from '@modelcontextprotocol/sdk/types.js';
 10 | 
 11 | import { BitbucketApiClient } from './utils/api-client.js';
 12 | import { PullRequestHandlers } from './handlers/pull-request-handlers.js';
 13 | import { BranchHandlers } from './handlers/branch-handlers.js';
 14 | import { ReviewHandlers } from './handlers/review-handlers.js';
 15 | import { FileHandlers } from './handlers/file-handlers.js';
 16 | import { SearchHandlers } from './handlers/search-handlers.js';
 17 | import { ProjectHandlers } from './handlers/project-handlers.js';
 18 | import { toolDefinitions } from './tools/definitions.js';
 19 | 
 20 | // Get environment variables
 21 | const BITBUCKET_USERNAME = process.env.BITBUCKET_USERNAME;
 22 | const BITBUCKET_APP_PASSWORD = process.env.BITBUCKET_APP_PASSWORD;
 23 | const BITBUCKET_TOKEN = process.env.BITBUCKET_TOKEN; // For Bitbucket Server
 24 | const BITBUCKET_BASE_URL = process.env.BITBUCKET_BASE_URL || 'https://api.bitbucket.org/2.0';
 25 | 
 26 | // Check for either app password (Cloud) or token (Server)
 27 | if (!BITBUCKET_USERNAME || (!BITBUCKET_APP_PASSWORD && !BITBUCKET_TOKEN)) {
 28 |   console.error('Error: BITBUCKET_USERNAME and either BITBUCKET_APP_PASSWORD (for Cloud) or BITBUCKET_TOKEN (for Server) are required');
 29 |   console.error('Please set these in your MCP settings configuration');
 30 |   process.exit(1);
 31 | }
 32 | 
 33 | class BitbucketMCPServer {
 34 |   private server: Server;
 35 |   private apiClient: BitbucketApiClient;
 36 |   private pullRequestHandlers: PullRequestHandlers;
 37 |   private branchHandlers: BranchHandlers;
 38 |   private reviewHandlers: ReviewHandlers;
 39 |   private fileHandlers: FileHandlers;
 40 |   private searchHandlers: SearchHandlers;
 41 |   private projectHandlers: ProjectHandlers;
 42 | 
 43 |   constructor() {
 44 |     this.server = new Server(
 45 |       {
 46 |         name: 'bitbucket-mcp-server',
 47 |         version: '1.1.2',
 48 |       },
 49 |       {
 50 |         capabilities: {
 51 |           tools: {},
 52 |         },
 53 |       }
 54 |     );
 55 | 
 56 |     // Initialize API client
 57 |     this.apiClient = new BitbucketApiClient(
 58 |       BITBUCKET_BASE_URL,
 59 |       BITBUCKET_USERNAME!,
 60 |       BITBUCKET_APP_PASSWORD,
 61 |       BITBUCKET_TOKEN
 62 |     );
 63 | 
 64 |     // Initialize handlers
 65 |     this.pullRequestHandlers = new PullRequestHandlers(
 66 |       this.apiClient,
 67 |       BITBUCKET_BASE_URL,
 68 |       BITBUCKET_USERNAME!
 69 |     );
 70 |     this.branchHandlers = new BranchHandlers(this.apiClient, BITBUCKET_BASE_URL);
 71 |     this.reviewHandlers = new ReviewHandlers(this.apiClient, BITBUCKET_USERNAME!);
 72 |     this.fileHandlers = new FileHandlers(this.apiClient, BITBUCKET_BASE_URL);
 73 |     this.searchHandlers = new SearchHandlers(this.apiClient, BITBUCKET_BASE_URL);
 74 |     this.projectHandlers = new ProjectHandlers(this.apiClient, BITBUCKET_BASE_URL);
 75 | 
 76 |     this.setupToolHandlers();
 77 | 
 78 |     // Error handling
 79 |     this.server.onerror = (error) => console.error('[MCP Error]', error);
 80 |     process.on('SIGINT', async () => {
 81 |       await this.server.close();
 82 |       process.exit(0);
 83 |     });
 84 |   }
 85 | 
 86 |   private setupToolHandlers() {
 87 |     // List available tools
 88 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
 89 |       tools: toolDefinitions,
 90 |     }));
 91 | 
 92 |     // Handle tool calls
 93 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
 94 |       switch (request.params.name) {
 95 |         // Pull Request tools
 96 |         case 'get_pull_request':
 97 |           return this.pullRequestHandlers.handleGetPullRequest(request.params.arguments);
 98 |         case 'list_pull_requests':
 99 |           return this.pullRequestHandlers.handleListPullRequests(request.params.arguments);
100 |         case 'create_pull_request':
101 |           return this.pullRequestHandlers.handleCreatePullRequest(request.params.arguments);
102 |         case 'update_pull_request':
103 |           return this.pullRequestHandlers.handleUpdatePullRequest(request.params.arguments);
104 |         case 'add_comment':
105 |           return this.pullRequestHandlers.handleAddComment(request.params.arguments);
106 |         case 'merge_pull_request':
107 |           return this.pullRequestHandlers.handleMergePullRequest(request.params.arguments);
108 |         case 'list_pr_commits':
109 |           return this.pullRequestHandlers.handleListPrCommits(request.params.arguments);
110 |         
111 |         // Branch tools
112 |         case 'list_branches':
113 |           return this.branchHandlers.handleListBranches(request.params.arguments);
114 |         case 'delete_branch':
115 |           return this.branchHandlers.handleDeleteBranch(request.params.arguments);
116 |         case 'get_branch':
117 |           return this.branchHandlers.handleGetBranch(request.params.arguments);
118 |         case 'list_branch_commits':
119 |           return this.branchHandlers.handleListBranchCommits(request.params.arguments);
120 |         
121 |         // Code Review tools
122 |         case 'get_pull_request_diff':
123 |           return this.reviewHandlers.handleGetPullRequestDiff(request.params.arguments);
124 |         case 'approve_pull_request':
125 |           return this.reviewHandlers.handleApprovePullRequest(request.params.arguments);
126 |         case 'unapprove_pull_request':
127 |           return this.reviewHandlers.handleUnapprovePullRequest(request.params.arguments);
128 |         case 'request_changes':
129 |           return this.reviewHandlers.handleRequestChanges(request.params.arguments);
130 |         case 'remove_requested_changes':
131 |           return this.reviewHandlers.handleRemoveRequestedChanges(request.params.arguments);
132 |         
133 |         // File tools
134 |         case 'list_directory_content':
135 |           return this.fileHandlers.handleListDirectoryContent(request.params.arguments);
136 |         case 'get_file_content':
137 |           return this.fileHandlers.handleGetFileContent(request.params.arguments);
138 |         
139 |         // Search tools
140 |         case 'search_code':
141 |           return this.searchHandlers.handleSearchCode(request.params.arguments);
142 | 
143 |         // Project tools
144 |         case 'list_projects':
145 |           return this.projectHandlers.handleListProjects(request.params.arguments);
146 |         case 'list_repositories':
147 |           return this.projectHandlers.handleListRepositories(request.params.arguments);
148 | 
149 |         default:
150 |           throw new McpError(
151 |             ErrorCode.MethodNotFound,
152 |             `Unknown tool: ${request.params.name}`
153 |           );
154 |       }
155 |     });
156 |   }
157 | 
158 |   async run() {
159 |     const transport = new StdioServerTransport();
160 |     await this.server.connect(transport);
161 |     console.error(`Bitbucket MCP server running on stdio (${this.apiClient.getIsServer() ? 'Server' : 'Cloud'} mode)`);
162 |   }
163 | }
164 | 
165 | const server = new BitbucketMCPServer();
166 | server.run().catch(console.error);
167 | 
```

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

```typescript
  1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
  2 | import { BitbucketApiClient } from '../utils/api-client.js';
  3 | import {
  4 |   isListProjectsArgs,
  5 |   isListRepositoriesArgs
  6 | } from '../types/guards.js';
  7 | import {
  8 |   BitbucketServerProject,
  9 |   BitbucketCloudProject,
 10 |   BitbucketServerRepository,
 11 |   BitbucketCloudRepository
 12 | } from '../types/bitbucket.js';
 13 | 
 14 | export class ProjectHandlers {
 15 |   constructor(
 16 |     private apiClient: BitbucketApiClient,
 17 |     private baseUrl: string
 18 |   ) {}
 19 | 
 20 |   async handleListProjects(args: any) {
 21 |     if (!isListProjectsArgs(args)) {
 22 |       throw new McpError(
 23 |         ErrorCode.InvalidParams,
 24 |         'Invalid arguments for list_projects'
 25 |       );
 26 |     }
 27 | 
 28 |     const { name, permission, limit = 25, start = 0 } = args;
 29 | 
 30 |     try {
 31 |       let apiPath: string;
 32 |       let params: any = {};
 33 |       let projects: any[] = [];
 34 |       let totalCount = 0;
 35 |       let nextPageStart: number | null = null;
 36 | 
 37 |       if (this.apiClient.getIsServer()) {
 38 |         // Bitbucket Server API
 39 |         apiPath = `/rest/api/1.0/projects`;
 40 |         params = {
 41 |           limit,
 42 |           start
 43 |         };
 44 | 
 45 |         if (name) {
 46 |           params.name = name;
 47 |         }
 48 |         if (permission) {
 49 |           params.permission = permission;
 50 |         }
 51 | 
 52 |         const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });
 53 | 
 54 |         // Format projects
 55 |         projects = (response.values || []).map((project: BitbucketServerProject) => ({
 56 |           key: project.key,
 57 |           id: project.id,
 58 |           name: project.name,
 59 |           description: project.description || '',
 60 |           is_public: project.public,
 61 |           type: project.type,
 62 |           url: `${this.baseUrl}/projects/${project.key}`
 63 |         }));
 64 | 
 65 |         totalCount = response.size || projects.length;
 66 |         if (!response.isLastPage && response.nextPageStart !== undefined) {
 67 |           nextPageStart = response.nextPageStart;
 68 |         }
 69 |       } else {
 70 |         // Bitbucket Cloud API
 71 |         apiPath = `/workspaces`;
 72 |         params = {
 73 |           pagelen: limit,
 74 |           page: Math.floor(start / limit) + 1
 75 |         };
 76 | 
 77 |         // Cloud uses workspaces, not projects exactly
 78 |         const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });
 79 | 
 80 |         projects = (response.values || []).map((workspace: any) => ({
 81 |           key: workspace.slug,
 82 |           id: workspace.uuid,
 83 |           name: workspace.name,
 84 |           description: '',
 85 |           is_public: !workspace.is_private,
 86 |           type: 'WORKSPACE',
 87 |           url: workspace.links.html.href
 88 |         }));
 89 | 
 90 |         totalCount = response.size || projects.length;
 91 |         if (response.next) {
 92 |           nextPageStart = start + limit;
 93 |         }
 94 |       }
 95 | 
 96 |       return {
 97 |         content: [
 98 |           {
 99 |             type: 'text',
100 |             text: JSON.stringify({
101 |               projects,
102 |               total_count: totalCount,
103 |               start,
104 |               limit,
105 |               has_more: nextPageStart !== null,
106 |               next_start: nextPageStart
107 |             }, null, 2),
108 |           },
109 |         ],
110 |       };
111 |     } catch (error) {
112 |       return this.apiClient.handleApiError(error, 'listing projects');
113 |     }
114 |   }
115 | 
116 |   async handleListRepositories(args: any) {
117 |     if (!isListRepositoriesArgs(args)) {
118 |       throw new McpError(
119 |         ErrorCode.InvalidParams,
120 |         'Invalid arguments for list_repositories'
121 |       );
122 |     }
123 | 
124 |     const { workspace, name, permission, limit = 25, start = 0 } = args;
125 | 
126 |     try {
127 |       let apiPath: string;
128 |       let params: any = {};
129 |       let repositories: any[] = [];
130 |       let totalCount = 0;
131 |       let nextPageStart: number | null = null;
132 | 
133 |       if (this.apiClient.getIsServer()) {
134 |         // Bitbucket Server API
135 |         if (workspace) {
136 |           // List repos in a specific project
137 |           apiPath = `/rest/api/1.0/projects/${workspace}/repos`;
138 |         } else {
139 |           // List all accessible repos
140 |           apiPath = `/rest/api/1.0/repos`;
141 |         }
142 | 
143 |         params = {
144 |           limit,
145 |           start
146 |         };
147 | 
148 |         if (name) {
149 |           params.name = name;
150 |         }
151 |         if (permission) {
152 |           params.permission = permission;
153 |         }
154 |         if (!workspace && name) {
155 |           // When listing all repos and filtering by name
156 |           params.projectname = name;
157 |         }
158 | 
159 |         const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });
160 | 
161 |         // Format repositories
162 |         repositories = (response.values || []).map((repo: BitbucketServerRepository) => ({
163 |           slug: repo.slug,
164 |           id: repo.id,
165 |           name: repo.name,
166 |           description: repo.description || '',
167 |           project_key: repo.project.key,
168 |           project_name: repo.project.name,
169 |           state: repo.state,
170 |           is_public: repo.public,
171 |           is_forkable: repo.forkable,
172 |           clone_urls: {
173 |             http: repo.links.clone.find(c => c.name === 'http')?.href || '',
174 |             ssh: repo.links.clone.find(c => c.name === 'ssh')?.href || ''
175 |           },
176 |           url: `${this.baseUrl}/projects/${repo.project.key}/repos/${repo.slug}`
177 |         }));
178 | 
179 |         totalCount = response.size || repositories.length;
180 |         if (!response.isLastPage && response.nextPageStart !== undefined) {
181 |           nextPageStart = response.nextPageStart;
182 |         }
183 |       } else {
184 |         // Bitbucket Cloud API
185 |         if (workspace) {
186 |           // List repos in a specific workspace
187 |           apiPath = `/repositories/${workspace}`;
188 |         } else {
189 |           // Cloud doesn't support listing all repos without workspace
190 |           // We'll return an error message
191 |           return {
192 |             content: [
193 |               {
194 |                 type: 'text',
195 |                 text: JSON.stringify({
196 |                   error: 'Bitbucket Cloud requires a workspace parameter to list repositories. Please provide a workspace.'
197 |                 }, null, 2),
198 |               },
199 |             ],
200 |             isError: true,
201 |           };
202 |         }
203 | 
204 |         params = {
205 |           pagelen: limit,
206 |           page: Math.floor(start / limit) + 1
207 |         };
208 | 
209 |         const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });
210 | 
211 |         repositories = (response.values || []).map((repo: BitbucketCloudRepository) => ({
212 |           slug: repo.slug,
213 |           id: repo.uuid,
214 |           name: repo.name,
215 |           description: repo.description || '',
216 |           project_key: repo.project?.key || '',
217 |           project_name: repo.project?.name || '',
218 |           state: 'AVAILABLE',
219 |           is_public: !repo.is_private,
220 |           is_forkable: true,
221 |           clone_urls: {
222 |             http: repo.links.clone.find(c => c.name === 'https')?.href || '',
223 |             ssh: repo.links.clone.find(c => c.name === 'ssh')?.href || ''
224 |           },
225 |           url: repo.links.html.href
226 |         }));
227 | 
228 |         totalCount = response.size || repositories.length;
229 |         if (response.next) {
230 |           nextPageStart = start + limit;
231 |         }
232 |       }
233 | 
234 |       return {
235 |         content: [
236 |           {
237 |             type: 'text',
238 |             text: JSON.stringify({
239 |               repositories,
240 |               total_count: totalCount,
241 |               start,
242 |               limit,
243 |               has_more: nextPageStart !== null,
244 |               next_start: nextPageStart,
245 |               workspace: workspace || 'all'
246 |             }, null, 2),
247 |           },
248 |         ],
249 |       };
250 |     } catch (error) {
251 |       return this.apiClient.handleApiError(error, workspace ? `listing repositories in ${workspace}` : 'listing repositories');
252 |     }
253 |   }
254 | }
255 | 
```

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

```typescript
  1 | import {
  2 |   BitbucketServerPullRequest,
  3 |   BitbucketCloudPullRequest,
  4 |   MergeInfo,
  5 |   BitbucketServerCommit,
  6 |   BitbucketCloudCommit,
  7 |   FormattedCommit,
  8 |   BitbucketServerSearchResult,
  9 |   FormattedSearchResult
 10 | } from '../types/bitbucket.js';
 11 | 
 12 | export function formatServerResponse(
 13 |   pr: BitbucketServerPullRequest,
 14 |   mergeInfo?: MergeInfo,
 15 |   baseUrl?: string
 16 | ): any {
 17 |   const webUrl = `${baseUrl}/projects/${pr.toRef.repository.project.key}/repos/${pr.toRef.repository.slug}/pull-requests/${pr.id}`;
 18 |   
 19 |   return {
 20 |     id: pr.id,
 21 |     title: pr.title,
 22 |     description: pr.description || 'No description provided',
 23 |     state: pr.state,
 24 |     is_open: pr.open,
 25 |     is_closed: pr.closed,
 26 |     author: pr.author.user.displayName,
 27 |     author_username: pr.author.user.name,
 28 |     author_email: pr.author.user.emailAddress,
 29 |     source_branch: pr.fromRef.displayId,
 30 |     destination_branch: pr.toRef.displayId,
 31 |     source_commit: pr.fromRef.latestCommit,
 32 |     destination_commit: pr.toRef.latestCommit,
 33 |     reviewers: pr.reviewers.map(r => ({
 34 |       name: r.user.displayName,
 35 |       approved: r.approved,
 36 |       status: r.status,
 37 |     })),
 38 |     participants: pr.participants.map(p => ({
 39 |       name: p.user.displayName,
 40 |       role: p.role,
 41 |       approved: p.approved,
 42 |       status: p.status,
 43 |     })),
 44 |     created_on: new Date(pr.createdDate).toLocaleString(),
 45 |     updated_on: new Date(pr.updatedDate).toLocaleString(),
 46 |     web_url: webUrl,
 47 |     api_url: pr.links.self[0]?.href || '',
 48 |     is_locked: pr.locked,
 49 |     // Add merge commit details
 50 |     is_merged: pr.state === 'MERGED',
 51 |     merge_commit_hash: mergeInfo?.mergeCommitHash || pr.properties?.mergeCommit?.id || null,
 52 |     merged_by: mergeInfo?.mergedBy || null,
 53 |     merged_at: mergeInfo?.mergedAt || null,
 54 |     merge_commit_message: mergeInfo?.mergeCommitMessage || null,
 55 |   };
 56 | }
 57 | 
 58 | export function formatCloudResponse(pr: BitbucketCloudPullRequest): any {
 59 |   return {
 60 |     id: pr.id,
 61 |     title: pr.title,
 62 |     description: pr.description || 'No description provided',
 63 |     state: pr.state,
 64 |     author: pr.author.display_name,
 65 |     source_branch: pr.source.branch.name,
 66 |     destination_branch: pr.destination.branch.name,
 67 |     reviewers: pr.reviewers.map(r => r.display_name),
 68 |     participants: pr.participants.map(p => ({
 69 |       name: p.user.display_name,
 70 |       role: p.role,
 71 |       approved: p.approved,
 72 |     })),
 73 |     created_on: new Date(pr.created_on).toLocaleString(),
 74 |     updated_on: new Date(pr.updated_on).toLocaleString(),
 75 |     web_url: pr.links.html.href,
 76 |     api_url: pr.links.self.href,
 77 |     diff_url: pr.links.diff.href,
 78 |     is_merged: pr.state === 'MERGED',
 79 |     merge_commit_hash: pr.merge_commit?.hash || null,
 80 |     merged_by: pr.closed_by?.display_name || null,
 81 |     merged_at: pr.state === 'MERGED' ? pr.updated_on : null,
 82 |     merge_commit_message: null, // Would need additional API call to get this
 83 |     close_source_branch: pr.close_source_branch,
 84 |   };
 85 | }
 86 | 
 87 | export function formatServerCommit(commit: BitbucketServerCommit): FormattedCommit {
 88 |   return {
 89 |     hash: commit.id,
 90 |     abbreviated_hash: commit.displayId,
 91 |     message: commit.message,
 92 |     author: {
 93 |       name: commit.author.name,
 94 |       email: commit.author.emailAddress,
 95 |     },
 96 |     date: new Date(commit.authorTimestamp).toISOString(),
 97 |     parents: commit.parents.map(p => p.id),
 98 |     is_merge_commit: commit.parents.length > 1,
 99 |   };
100 | }
101 | 
102 | export function formatCloudCommit(commit: BitbucketCloudCommit): FormattedCommit {
103 |   // Parse the author raw string which is in format "Name <email>"
104 |   const authorMatch = commit.author.raw.match(/^(.+?)\s*<(.+?)>$/);
105 |   const authorName = authorMatch ? authorMatch[1] : (commit.author.user?.display_name || commit.author.raw);
106 |   const authorEmail = authorMatch ? authorMatch[2] : '';
107 | 
108 |   return {
109 |     hash: commit.hash,
110 |     abbreviated_hash: commit.hash.substring(0, 7),
111 |     message: commit.message,
112 |     author: {
113 |       name: authorName,
114 |       email: authorEmail,
115 |     },
116 |     date: commit.date,
117 |     parents: commit.parents.map(p => p.hash),
118 |     is_merge_commit: commit.parents.length > 1,
119 |   };
120 | }
121 | 
122 | export function formatSearchResults(searchResult: BitbucketServerSearchResult): FormattedSearchResult[] {
123 |   const results: FormattedSearchResult[] = [];
124 |   
125 |   if (!searchResult.code?.values) {
126 |     return results;
127 |   }
128 | 
129 |   for (const value of searchResult.code.values) {
130 |     // Extract file name from path
131 |     const fileName = value.file.split('/').pop() || value.file;
132 |     
133 |     const formattedResult: FormattedSearchResult = {
134 |       file_path: value.file,
135 |       file_name: fileName,
136 |       repository: value.repository.slug,
137 |       project: value.repository.project.key,
138 |       matches: []
139 |     };
140 | 
141 |     // Process hitContexts (array of arrays of line contexts)
142 |     if (value.hitContexts && value.hitContexts.length > 0) {
143 |       for (const contextGroup of value.hitContexts) {
144 |         for (const lineContext of contextGroup) {
145 |           // Parse HTML to extract text and highlight information
146 |           const { text, segments } = parseHighlightedText(lineContext.text);
147 |           
148 |           formattedResult.matches.push({
149 |             line_number: lineContext.line,
150 |             line_content: text,
151 |             highlighted_segments: segments
152 |           });
153 |         }
154 |       }
155 |     }
156 | 
157 |     results.push(formattedResult);
158 |   }
159 | 
160 |   return results;
161 | }
162 | 
163 | // Helper function to parse HTML-formatted text with <em> tags
164 | function parseHighlightedText(htmlText: string): {
165 |   text: string;
166 |   segments: Array<{ text: string; is_match: boolean }>;
167 | } {
168 |   // Decode HTML entities
169 |   const decodedText = htmlText
170 |     .replace(/&quot;/g, '"')
171 |     .replace(/&lt;/g, '<')
172 |     .replace(/&gt;/g, '>')
173 |     .replace(/&amp;/g, '&')
174 |     .replace(/&#x2F;/g, '/');
175 | 
176 |   // Remove HTML tags and track highlighted segments
177 |   const segments: Array<{ text: string; is_match: boolean }> = [];
178 |   let plainText = '';
179 |   let currentPos = 0;
180 | 
181 |   // Match all <em> tags and their content
182 |   const emRegex = /<em>(.*?)<\/em>/g;
183 |   let lastEnd = 0;
184 |   let match;
185 | 
186 |   while ((match = emRegex.exec(decodedText)) !== null) {
187 |     // Add non-highlighted text before this match
188 |     if (match.index > lastEnd) {
189 |       const beforeText = decodedText.substring(lastEnd, match.index);
190 |       segments.push({ text: beforeText, is_match: false });
191 |       plainText += beforeText;
192 |     }
193 | 
194 |     // Add highlighted text
195 |     const highlightedText = match[1];
196 |     segments.push({ text: highlightedText, is_match: true });
197 |     plainText += highlightedText;
198 | 
199 |     lastEnd = match.index + match[0].length;
200 |   }
201 | 
202 |   // Add any remaining non-highlighted text
203 |   if (lastEnd < decodedText.length) {
204 |     const remainingText = decodedText.substring(lastEnd);
205 |     segments.push({ text: remainingText, is_match: false });
206 |     plainText += remainingText;
207 |   }
208 | 
209 |   // If no <em> tags were found, the entire text is non-highlighted
210 |   if (segments.length === 0) {
211 |     segments.push({ text: decodedText, is_match: false });
212 |     plainText = decodedText;
213 |   }
214 | 
215 |   return { text: plainText, segments };
216 | }
217 | 
218 | // Simplified formatter for MCP tool output
219 | export function formatCodeSearchOutput(searchResult: BitbucketServerSearchResult): string {
220 |   if (!searchResult.code?.values || searchResult.code.values.length === 0) {
221 |     return 'No results found';
222 |   }
223 | 
224 |   const outputLines: string[] = [];
225 |   
226 |   for (const value of searchResult.code.values) {
227 |     outputLines.push(`File: ${value.file}`);
228 |     
229 |     // Process all hit contexts
230 |     if (value.hitContexts && value.hitContexts.length > 0) {
231 |       for (const contextGroup of value.hitContexts) {
232 |         for (const lineContext of contextGroup) {
233 |           // Remove HTML tags and decode entities
234 |           const cleanText = lineContext.text
235 |             .replace(/<em>/g, '')
236 |             .replace(/<\/em>/g, '')
237 |             .replace(/&quot;/g, '"')
238 |             .replace(/&lt;/g, '<')
239 |             .replace(/&gt;/g, '>')
240 |             .replace(/&amp;/g, '&')
241 |             .replace(/&#x2F;/g, '/')
242 |             .replace(/&#x27;/g, "'");
243 |           
244 |           outputLines.push(`  Line ${lineContext.line}: ${cleanText}`);
245 |         }
246 |       }
247 |     }
248 |     
249 |     outputLines.push(''); // Empty line between files
250 |   }
251 |   
252 |   return outputLines.join('\n').trim();
253 | }
254 | 
```

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

```typescript
  1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
  2 | import { BitbucketApiClient } from '../utils/api-client.js';
  3 | import {
  4 |   isGetPullRequestDiffArgs,
  5 |   isApprovePullRequestArgs,
  6 |   isRequestChangesArgs
  7 | } from '../types/guards.js';
  8 | import { DiffParser } from '../utils/diff-parser.js';
  9 | 
 10 | export class ReviewHandlers {
 11 |   constructor(
 12 |     private apiClient: BitbucketApiClient,
 13 |     private username: string
 14 |   ) {}
 15 | 
 16 |   async handleGetPullRequestDiff(args: any) {
 17 |     if (!isGetPullRequestDiffArgs(args)) {
 18 |       throw new McpError(
 19 |         ErrorCode.InvalidParams,
 20 |         'Invalid arguments for get_pull_request_diff'
 21 |       );
 22 |     }
 23 | 
 24 |     const { 
 25 |       workspace, 
 26 |       repository, 
 27 |       pull_request_id, 
 28 |       context_lines = 3,
 29 |       include_patterns,
 30 |       exclude_patterns,
 31 |       file_path
 32 |     } = args;
 33 | 
 34 |     try {
 35 |       let apiPath: string;
 36 |       let config: any = {};
 37 | 
 38 |       if (this.apiClient.getIsServer()) {
 39 |         // Bitbucket Server API
 40 |         apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/diff`;
 41 |         config.params = { contextLines: context_lines };
 42 |       } else {
 43 |         // Bitbucket Cloud API
 44 |         apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/diff`;
 45 |         config.params = { context: context_lines };
 46 |       }
 47 | 
 48 |       // For diff, we want the raw text response
 49 |       config.headers = { 'Accept': 'text/plain' };
 50 |       
 51 |       const rawDiff = await this.apiClient.makeRequest<string>('get', apiPath, undefined, config);
 52 | 
 53 |       // Check if filtering is needed
 54 |       const needsFiltering = file_path || include_patterns || exclude_patterns;
 55 |       
 56 |       if (!needsFiltering) {
 57 |         // Return raw diff without filtering
 58 |         return {
 59 |           content: [
 60 |             {
 61 |               type: 'text',
 62 |               text: JSON.stringify({
 63 |                 message: 'Pull request diff retrieved successfully',
 64 |                 pull_request_id,
 65 |                 diff: rawDiff
 66 |               }, null, 2),
 67 |             },
 68 |           ],
 69 |         };
 70 |       }
 71 | 
 72 |       // Apply filtering
 73 |       const diffParser = new DiffParser();
 74 |       const sections = diffParser.parseDiffIntoSections(rawDiff);
 75 |       
 76 |       const filterOptions = {
 77 |         includePatterns: include_patterns,
 78 |         excludePatterns: exclude_patterns,
 79 |         filePath: file_path
 80 |       };
 81 |       
 82 |       const filteredResult = diffParser.filterSections(sections, filterOptions);
 83 |       const filteredDiff = diffParser.reconstructDiff(filteredResult.sections);
 84 | 
 85 |       // Build response with filtering metadata
 86 |       const response: any = {
 87 |         message: 'Pull request diff retrieved successfully',
 88 |         pull_request_id,
 89 |         diff: filteredDiff
 90 |       };
 91 | 
 92 |       // Add filter metadata
 93 |       if (filteredResult.metadata.excludedFiles > 0 || file_path || include_patterns || exclude_patterns) {
 94 |         response.filter_metadata = {
 95 |           total_files: filteredResult.metadata.totalFiles,
 96 |           included_files: filteredResult.metadata.includedFiles,
 97 |           excluded_files: filteredResult.metadata.excludedFiles
 98 |         };
 99 | 
100 |         if (filteredResult.metadata.excludedFileList.length > 0) {
101 |           response.filter_metadata.excluded_file_list = filteredResult.metadata.excludedFileList;
102 |         }
103 | 
104 |         response.filter_metadata.filters_applied = {};
105 |         if (file_path) {
106 |           response.filter_metadata.filters_applied.file_path = file_path;
107 |         }
108 |         if (include_patterns) {
109 |           response.filter_metadata.filters_applied.include_patterns = include_patterns;
110 |         }
111 |         if (exclude_patterns) {
112 |           response.filter_metadata.filters_applied.exclude_patterns = exclude_patterns;
113 |         }
114 |       }
115 | 
116 |       return {
117 |         content: [
118 |           {
119 |             type: 'text',
120 |             text: JSON.stringify(response, null, 2),
121 |           },
122 |         ],
123 |       };
124 |     } catch (error) {
125 |       return this.apiClient.handleApiError(error, `getting diff for pull request ${pull_request_id} in ${workspace}/${repository}`);
126 |     }
127 |   }
128 | 
129 |   async handleApprovePullRequest(args: any) {
130 |     if (!isApprovePullRequestArgs(args)) {
131 |       throw new McpError(
132 |         ErrorCode.InvalidParams,
133 |         'Invalid arguments for approve_pull_request'
134 |       );
135 |     }
136 | 
137 |     const { workspace, repository, pull_request_id } = args;
138 | 
139 |     try {
140 |       let apiPath: string;
141 | 
142 |       if (this.apiClient.getIsServer()) {
143 |         // Bitbucket Server API - use participants endpoint
144 |         // Convert email format: @ to _ for the API
145 |         const username = this.username.replace('@', '_');
146 |         apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
147 |         await this.apiClient.makeRequest<any>('put', apiPath, { status: 'APPROVED' });
148 |       } else {
149 |         // Bitbucket Cloud API
150 |         apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/approve`;
151 |         await this.apiClient.makeRequest<any>('post', apiPath);
152 |       }
153 | 
154 |       return {
155 |         content: [
156 |           {
157 |             type: 'text',
158 |             text: JSON.stringify({
159 |               message: 'Pull request approved successfully',
160 |               pull_request_id,
161 |               approved_by: this.username
162 |             }, null, 2),
163 |           },
164 |         ],
165 |       };
166 |     } catch (error) {
167 |       return this.apiClient.handleApiError(error, `approving pull request ${pull_request_id} in ${workspace}/${repository}`);
168 |     }
169 |   }
170 | 
171 |   async handleUnapprovePullRequest(args: any) {
172 |     if (!isApprovePullRequestArgs(args)) {
173 |       throw new McpError(
174 |         ErrorCode.InvalidParams,
175 |         'Invalid arguments for unapprove_pull_request'
176 |       );
177 |     }
178 | 
179 |     const { workspace, repository, pull_request_id } = args;
180 | 
181 |     try {
182 |       let apiPath: string;
183 | 
184 |       if (this.apiClient.getIsServer()) {
185 |         // Bitbucket Server API - use participants endpoint
186 |         const username = this.username.replace('@', '_');
187 |         apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
188 |         await this.apiClient.makeRequest<any>('put', apiPath, { status: 'UNAPPROVED' });
189 |       } else {
190 |         // Bitbucket Cloud API
191 |         apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/approve`;
192 |         await this.apiClient.makeRequest<any>('delete', apiPath);
193 |       }
194 | 
195 |       return {
196 |         content: [
197 |           {
198 |             type: 'text',
199 |             text: JSON.stringify({
200 |               message: 'Pull request approval removed successfully',
201 |               pull_request_id,
202 |               unapproved_by: this.username
203 |             }, null, 2),
204 |           },
205 |         ],
206 |       };
207 |     } catch (error) {
208 |       return this.apiClient.handleApiError(error, `removing approval from pull request ${pull_request_id} in ${workspace}/${repository}`);
209 |     }
210 |   }
211 | 
212 |   async handleRequestChanges(args: any) {
213 |     if (!isRequestChangesArgs(args)) {
214 |       throw new McpError(
215 |         ErrorCode.InvalidParams,
216 |         'Invalid arguments for request_changes'
217 |       );
218 |     }
219 | 
220 |     const { workspace, repository, pull_request_id, comment } = args;
221 | 
222 |     try {
223 |       if (this.apiClient.getIsServer()) {
224 |         // Bitbucket Server API - use needs-work status
225 |         const username = this.username.replace('@', '_');
226 |         const apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
227 |         await this.apiClient.makeRequest<any>('put', apiPath, { status: 'NEEDS_WORK' });
228 |         
229 |         // Add comment if provided
230 |         if (comment) {
231 |           const commentPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/comments`;
232 |           await this.apiClient.makeRequest<any>('post', commentPath, { text: comment });
233 |         }
234 |       } else {
235 |         // Bitbucket Cloud API - use request-changes status
236 |         const apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/request-changes`;
237 |         await this.apiClient.makeRequest<any>('post', apiPath);
238 |         
239 |         // Add comment if provided
240 |         if (comment) {
241 |           const commentPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/comments`;
242 |           await this.apiClient.makeRequest<any>('post', commentPath, {
243 |             content: { raw: comment }
244 |           });
245 |         }
246 |       }
247 | 
248 |       return {
249 |         content: [
250 |           {
251 |             type: 'text',
252 |             text: JSON.stringify({
253 |               message: 'Changes requested on pull request',
254 |               pull_request_id,
255 |               requested_by: this.username,
256 |               comment: comment || 'No comment provided'
257 |             }, null, 2),
258 |           },
259 |         ],
260 |       };
261 |     } catch (error) {
262 |       return this.apiClient.handleApiError(error, `requesting changes on pull request ${pull_request_id} in ${workspace}/${repository}`);
263 |     }
264 |   }
265 | 
266 |   async handleRemoveRequestedChanges(args: any) {
267 |     if (!isApprovePullRequestArgs(args)) {
268 |       throw new McpError(
269 |         ErrorCode.InvalidParams,
270 |         'Invalid arguments for remove_requested_changes'
271 |       );
272 |     }
273 | 
274 |     const { workspace, repository, pull_request_id } = args;
275 | 
276 |     try {
277 |       if (this.apiClient.getIsServer()) {
278 |         // Bitbucket Server API - remove needs-work status
279 |         const username = this.username.replace('@', '_');
280 |         const apiPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/participants/${username}`;
281 |         await this.apiClient.makeRequest<any>('put', apiPath, { status: 'UNAPPROVED' });
282 |       } else {
283 |         // Bitbucket Cloud API
284 |         const apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/request-changes`;
285 |         await this.apiClient.makeRequest<any>('delete', apiPath);
286 |       }
287 | 
288 |       return {
289 |         content: [
290 |           {
291 |             type: 'text',
292 |             text: JSON.stringify({
293 |               message: 'Change request removed from pull request',
294 |               pull_request_id,
295 |               removed_by: this.username
296 |             }, null, 2),
297 |           },
298 |         ],
299 |       };
300 |     } catch (error) {
301 |       return this.apiClient.handleApiError(error, `removing change request from pull request ${pull_request_id} in ${workspace}/${repository}`);
302 |     }
303 |   }
304 | }
305 | 
```

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

```typescript
  1 | // Bitbucket Server API response types
  2 | export interface BitbucketServerPullRequest {
  3 |   id: number;
  4 |   version: number;
  5 |   title: string;
  6 |   description?: string;
  7 |   state: string;
  8 |   open: boolean;
  9 |   closed: boolean;
 10 |   createdDate: number;
 11 |   updatedDate: number;
 12 |   fromRef: {
 13 |     id: string;
 14 |     displayId: string;
 15 |     latestCommit: string;
 16 |     repository: {
 17 |       slug: string;
 18 |       name: string;
 19 |       project: {
 20 |         key: string;
 21 |       };
 22 |     };
 23 |   };
 24 |   toRef: {
 25 |     id: string;
 26 |     displayId: string;
 27 |     latestCommit: string;
 28 |     repository: {
 29 |       slug: string;
 30 |       name: string;
 31 |       project: {
 32 |         key: string;
 33 |       };
 34 |     };
 35 |   };
 36 |   locked: boolean;
 37 |   author: {
 38 |     user: {
 39 |       name: string;
 40 |       emailAddress: string;
 41 |       displayName: string;
 42 |     };
 43 |     role: string;
 44 |     approved: boolean;
 45 |     status: string;
 46 |   };
 47 |   reviewers: Array<{
 48 |     user: {
 49 |       name: string;
 50 |       emailAddress: string;
 51 |       displayName: string;
 52 |     };
 53 |     role: string;
 54 |     approved: boolean;
 55 |     status: string;
 56 |   }>;
 57 |   participants: Array<{
 58 |     user: {
 59 |       name: string;
 60 |       emailAddress: string;
 61 |       displayName: string;
 62 |     };
 63 |     role: string;
 64 |     approved: boolean;
 65 |     status: string;
 66 |   }>;
 67 |   links: {
 68 |     self: Array<{
 69 |       href: string;
 70 |     }>;
 71 |   };
 72 |   properties?: {
 73 |     mergeCommit?: {
 74 |       id: string;
 75 |       displayId: string;
 76 |     };
 77 |   };
 78 | }
 79 | 
 80 | // Bitbucket Server Activity types
 81 | export interface BitbucketServerActivity {
 82 |   id: number;
 83 |   createdDate: number;
 84 |   user: {
 85 |     name: string;
 86 |     emailAddress: string;
 87 |     displayName: string;
 88 |   };
 89 |   action: string;
 90 |   comment?: any;
 91 |   commit?: {
 92 |     id: string;
 93 |     displayId: string;
 94 |     message?: string;
 95 |   };
 96 | }
 97 | 
 98 | // Bitbucket Server Branch types
 99 | export interface BitbucketServerBranch {
100 |   id: string;
101 |   displayId: string;
102 |   type: string;
103 |   latestCommit: string;
104 |   latestChangeset: string;
105 |   isDefault: boolean;
106 |   metadata?: {
107 |     "com.atlassian.bitbucket.server.bitbucket-branch:latest-commit-metadata": {
108 |       author: {
109 |         name: string;
110 |         emailAddress: string;
111 |       };
112 |       authorTimestamp: number;
113 |       message: string;
114 |     };
115 |   };
116 | }
117 | 
118 | // Bitbucket Server Directory Entry
119 | export interface BitbucketServerDirectoryEntry {
120 |   path: {
121 |     name: string;
122 |     toString: string;
123 |   };
124 |   type: 'FILE' | 'DIRECTORY';
125 |   size?: number;
126 |   contentId?: string;
127 | }
128 | 
129 | // Bitbucket Cloud API response types
130 | export interface BitbucketCloudPullRequest {
131 |   id: number;
132 |   title: string;
133 |   description: string;
134 |   state: string;
135 |   author: {
136 |     display_name: string;
137 |     account_id: string;
138 |   };
139 |   source: {
140 |     branch: {
141 |       name: string;
142 |     };
143 |     repository: {
144 |       full_name: string;
145 |     };
146 |   };
147 |   destination: {
148 |     branch: {
149 |       name: string;
150 |     };
151 |     repository: {
152 |       full_name: string;
153 |     };
154 |   };
155 |   reviewers: Array<{
156 |     display_name: string;
157 |     account_id: string;
158 |   }>;
159 |   participants: Array<{
160 |     user: {
161 |       display_name: string;
162 |       account_id: string;
163 |     };
164 |     role: string;
165 |     approved: boolean;
166 |   }>;
167 |   created_on: string;
168 |   updated_on: string;
169 |   links: {
170 |     html: {
171 |       href: string;
172 |     };
173 |     self: {
174 |       href: string;
175 |     };
176 |     diff: {
177 |       href: string;
178 |     };
179 |   };
180 |   merge_commit?: {
181 |     hash: string;
182 |   };
183 |   close_source_branch: boolean;
184 |   closed_by?: {
185 |     display_name: string;
186 |     account_id: string;
187 |   };
188 | }
189 | 
190 | // Bitbucket Cloud Branch types
191 | export interface BitbucketCloudBranch {
192 |   name: string;
193 |   target: {
194 |     hash: string;
195 |     type: string;
196 |     message: string;
197 |     author: {
198 |       raw: string;
199 |       user?: {
200 |         display_name: string;
201 |         account_id: string;
202 |       };
203 |     };
204 |     date: string;
205 |   };
206 |   type: string;
207 | }
208 | 
209 | // Bitbucket Cloud Directory Entry
210 | export interface BitbucketCloudDirectoryEntry {
211 |   path: string;
212 |   type: 'commit_file' | 'commit_directory';
213 |   size?: number;
214 |   commit?: {
215 |     hash: string;
216 |   };
217 |   links?: {
218 |     self: {
219 |       href: string;
220 |     };
221 |     html: {
222 |       href: string;
223 |     };
224 |   };
225 | }
226 | 
227 | // Bitbucket Cloud File Metadata
228 | export interface BitbucketCloudFileMetadata {
229 |   path: string;
230 |   size: number;
231 |   encoding?: string;
232 |   mimetype?: string;
233 |   links: {
234 |     self: {
235 |       href: string;
236 |     };
237 |     html: {
238 |       href: string;
239 |     };
240 |     download: {
241 |       href: string;
242 |     };
243 |   };
244 |   commit?: {
245 |     hash: string;
246 |     author?: {
247 |       raw: string;
248 |       user?: {
249 |         display_name: string;
250 |         account_id: string;
251 |       };
252 |     };
253 |     date?: string;
254 |     message?: string;
255 |   };
256 | }
257 | 
258 | // Merge info type for enhanced PR details
259 | export interface MergeInfo {
260 |   mergeCommitHash?: string;
261 |   mergedBy?: string;
262 |   mergedAt?: string;
263 |   mergeCommitMessage?: string;
264 | }
265 | 
266 | // Comment types
267 | export interface BitbucketServerComment {
268 |   id: number;
269 |   version: number;
270 |   text: string;
271 |   author: {
272 |     name: string;
273 |     emailAddress: string;
274 |     displayName: string;
275 |   };
276 |   createdDate: number;
277 |   updatedDate: number;
278 |   state?: 'OPEN' | 'RESOLVED';
279 |   anchor?: {
280 |     line: number;
281 |     lineType: string;
282 |     fileType: string;
283 |     path: string;
284 |   };
285 | }
286 | 
287 | export interface BitbucketCloudComment {
288 |   id: number;
289 |   content: {
290 |     raw: string;
291 |     markup: string;
292 |     html: string;
293 |   };
294 |   user: {
295 |     display_name: string;
296 |     account_id: string;
297 |   };
298 |   created_on: string;
299 |   updated_on: string;
300 |   deleted?: boolean;
301 |   resolved?: boolean;
302 |   inline?: {
303 |     to: number;
304 |     from?: number;
305 |     path: string;
306 |   };
307 | }
308 | 
309 | // File change types
310 | export interface BitbucketServerFileChange {
311 |   path: {
312 |     toString: string;
313 |   };
314 |   executable: boolean;
315 |   percentUnchanged: number;
316 |   type: string;
317 |   nodeType: string;
318 |   srcPath?: {
319 |     toString: string;
320 |   };
321 |   linesAdded?: number;
322 |   linesRemoved?: number;
323 | }
324 | 
325 | export interface BitbucketCloudFileChange {
326 |   path: string;
327 |   type: 'added' | 'modified' | 'removed' | 'renamed';
328 |   lines_added: number;
329 |   lines_removed: number;
330 |   old?: {
331 |     path: string;
332 |   };
333 | }
334 | 
335 | // Formatted comment type for response
336 | export interface FormattedComment {
337 |   id: number;
338 |   author: string;
339 |   text: string;
340 |   created_on: string;
341 |   is_inline: boolean;
342 |   file_path?: string;
343 |   line_number?: number;
344 |   state?: 'OPEN' | 'RESOLVED';
345 |   parent_id?: number;  // For Bitbucket Cloud style replies
346 |   replies?: FormattedComment[];  // For Bitbucket Server nested replies
347 | }
348 | 
349 | // Formatted file change type for response
350 | export interface FormattedFileChange {
351 |   path: string;
352 |   status: 'added' | 'modified' | 'removed' | 'renamed';
353 |   old_path?: string;
354 | }
355 | 
356 | // Types for code snippet matching
357 | export interface CodeMatch {
358 |   line_number: number;
359 |   line_type: 'ADDED' | 'REMOVED' | 'CONTEXT';
360 |   exact_content: string;
361 |   preview: string;
362 |   confidence: number;
363 |   context: {
364 |     lines_before: string[];
365 |     lines_after: string[];
366 |   };
367 |   sequential_position?: number; // Position within diff (for ADDED lines)
368 |   hunk_info?: {
369 |     hunk_index: number;
370 |     destination_start: number;
371 |     line_in_hunk: number;
372 |   };
373 | }
374 | 
375 | export interface MultipleMatchesError {
376 |   code: 'MULTIPLE_MATCHES_FOUND';
377 |   message: string;
378 |   occurrences: Array<{
379 |     line_number: number;
380 |     file_path: string;
381 |     preview: string;
382 |     confidence: number;
383 |     line_type: 'ADDED' | 'REMOVED' | 'CONTEXT';
384 |   }>;
385 |   suggestion: string;
386 | }
387 | 
388 | // Commit types
389 | export interface BitbucketServerCommit {
390 |   id: string;
391 |   displayId: string;
392 |   message: string;
393 |   author: {
394 |     name: string;
395 |     emailAddress: string;
396 |   };
397 |   authorTimestamp: number;
398 |   committer?: {
399 |     name: string;
400 |     emailAddress: string;
401 |   };
402 |   committerTimestamp?: number;
403 |   parents: Array<{
404 |     id: string;
405 |     displayId: string;
406 |   }>;
407 | }
408 | 
409 | export interface BitbucketCloudCommit {
410 |   hash: string;
411 |   message: string;
412 |   author: {
413 |     raw: string;
414 |     user?: {
415 |       display_name: string;
416 |       account_id: string;
417 |     };
418 |   };
419 |   date: string;
420 |   parents: Array<{
421 |     hash: string;
422 |     type: string;
423 |   }>;
424 |   links?: {
425 |     self: {
426 |       href: string;
427 |     };
428 |     html: {
429 |       href: string;
430 |     };
431 |   };
432 | }
433 | 
434 | export interface FormattedCommit {
435 |   hash: string;
436 |   abbreviated_hash: string;
437 |   message: string;
438 |   author: {
439 |     name: string;
440 |     email: string;
441 |   };
442 |   date: string;
443 |   parents: string[];
444 |   is_merge_commit: boolean;
445 |   build_status?: BuildStatus;
446 | }
447 | 
448 | // Search types
449 | export interface BitbucketServerSearchRequest {
450 |   query: string;
451 |   entities: {
452 |     code?: {
453 |       start?: number;
454 |       limit?: number;
455 |     };
456 |     commits?: {
457 |       start?: number;
458 |       limit?: number;
459 |     };
460 |     pull_requests?: {
461 |       start?: number;
462 |       limit?: number;
463 |     };
464 |   };
465 | }
466 | 
467 | export interface BitbucketServerSearchResult {
468 |   scope?: {
469 |     repository?: {
470 |       slug: string;
471 |       name: string;
472 |       project: {
473 |         key: string;
474 |         name: string;
475 |       };
476 |     };
477 |     type: string;
478 |   };
479 |   code?: {
480 |     category: string;
481 |     isLastPage: boolean;
482 |     count: number;
483 |     start: number;
484 |     nextStart?: number;
485 |     values: Array<{
486 |       file: string; // Just the file path as string
487 |       repository: {
488 |         slug: string;
489 |         name: string;
490 |         project: {
491 |           key: string;
492 |           name: string;
493 |         };
494 |       };
495 |       hitContexts: Array<Array<{
496 |         line: number;
497 |         text: string; // HTML-formatted with <em> tags
498 |       }>>;
499 |       pathMatches: Array<any>;
500 |       hitCount: number;
501 |     }>;
502 |   };
503 |   query?: {
504 |     substituted: boolean;
505 |   };
506 | }
507 | 
508 | export interface FormattedSearchResult {
509 |   file_path: string;
510 |   file_name: string;
511 |   repository: string;
512 |   project: string;
513 |   matches: Array<{
514 |     line_number: number;
515 |     line_content: string;
516 |     highlighted_segments: Array<{
517 |       text: string;
518 |       is_match: boolean;
519 |     }>;
520 |   }>;
521 | }
522 | 
523 | // Build status types for Bitbucket Server
524 | export interface BitbucketServerBuildSummary {
525 |   [commitId: string]: {
526 |     failed?: number;
527 |     inProgress?: number;
528 |     successful?: number;
529 |     unknown?: number;
530 |   };
531 | }
532 | 
533 | export interface BuildStatus {
534 |   successful: number;
535 |   failed: number;
536 |   in_progress: number;
537 |   unknown: number;
538 | }
539 | 
540 | // Project and Repository types
541 | export interface BitbucketServerProject {
542 |   key: string;
543 |   id: number;
544 |   name: string;
545 |   description?: string;
546 |   public: boolean;
547 |   type: 'NORMAL' | 'PERSONAL';
548 |   links: {
549 |     self: Array<{
550 |       href: string;
551 |     }>;
552 |   };
553 | }
554 | 
555 | export interface BitbucketCloudProject {
556 |   key: string;
557 |   uuid: string;
558 |   name: string;
559 |   description?: string;
560 |   is_private: boolean;
561 |   links: {
562 |     html: {
563 |       href: string;
564 |     };
565 |   };
566 | }
567 | 
568 | export interface BitbucketServerRepository {
569 |   slug: string;
570 |   id: number;
571 |   name: string;
572 |   description?: string;
573 |   hierarchyId: string;
574 |   scmId: string;
575 |   state: 'AVAILABLE' | 'INITIALISING' | 'INITIALISATION_FAILED';
576 |   statusMessage: string;
577 |   forkable: boolean;
578 |   project: {
579 |     key: string;
580 |     id: number;
581 |     name: string;
582 |     public: boolean;
583 |     type: string;
584 |   };
585 |   public: boolean;
586 |   links: {
587 |     clone: Array<{
588 |       href: string;
589 |       name: string;
590 |     }>;
591 |     self: Array<{
592 |       href: string;
593 |     }>;
594 |   };
595 | }
596 | 
597 | export interface BitbucketCloudRepository {
598 |   slug: string;
599 |   uuid: string;
600 |   name: string;
601 |   full_name: string;
602 |   description?: string;
603 |   scm: string;
604 |   is_private: boolean;
605 |   owner: {
606 |     display_name: string;
607 |     uuid: string;
608 |   };
609 |   project: {
610 |     key: string;
611 |     name: string;
612 |   };
613 |   mainbranch?: {
614 |     name: string;
615 |     type: string;
616 |   };
617 |   links: {
618 |     html: {
619 |       href: string;
620 |     };
621 |     clone: Array<{
622 |       href: string;
623 |       name: string;
624 |     }>;
625 |   };
626 | }
627 | 
```

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

```typescript
  1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
  2 | import { BitbucketApiClient } from '../utils/api-client.js';
  3 | import {
  4 |   isListDirectoryContentArgs,
  5 |   isGetFileContentArgs
  6 | } from '../types/guards.js';
  7 | import {
  8 |   BitbucketServerDirectoryEntry,
  9 |   BitbucketCloudDirectoryEntry,
 10 |   BitbucketCloudFileMetadata
 11 | } from '../types/bitbucket.js';
 12 | import * as path from 'path';
 13 | 
 14 | export class FileHandlers {
 15 |   // Default lines by file extension
 16 |   private readonly DEFAULT_LINES_BY_EXT: Record<string, number> = {
 17 |     '.yml': 200, '.yaml': 200, '.json': 200,  // Config files
 18 |     '.md': 300, '.txt': 300,                   // Docs
 19 |     '.ts': 500, '.js': 500, '.py': 500,       // Code
 20 |     '.tsx': 500, '.jsx': 500, '.java': 500,   // More code
 21 |     '.log': -100  // Last 100 lines for logs
 22 |   };
 23 | 
 24 |   constructor(
 25 |     private apiClient: BitbucketApiClient,
 26 |     private baseUrl: string
 27 |   ) {}
 28 | 
 29 |   async handleListDirectoryContent(args: any) {
 30 |     if (!isListDirectoryContentArgs(args)) {
 31 |       throw new McpError(
 32 |         ErrorCode.InvalidParams,
 33 |         'Invalid arguments for list_directory_content'
 34 |       );
 35 |     }
 36 | 
 37 |     const { workspace, repository, path: dirPath = '', branch } = args;
 38 | 
 39 |     try {
 40 |       let apiPath: string;
 41 |       let params: any = {};
 42 |       let response: any;
 43 | 
 44 |       if (this.apiClient.getIsServer()) {
 45 |         // Bitbucket Server API
 46 |         apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/browse`;
 47 |         if (dirPath) {
 48 |           apiPath += `/${dirPath}`;
 49 |         }
 50 |         if (branch) {
 51 |           params.at = `refs/heads/${branch}`;
 52 |         }
 53 |         
 54 |         response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });
 55 |       } else {
 56 |         // Bitbucket Cloud API
 57 |         const branchOrDefault = branch || 'HEAD';
 58 |         apiPath = `/repositories/${workspace}/${repository}/src/${branchOrDefault}`;
 59 |         if (dirPath) {
 60 |           apiPath += `/${dirPath}`;
 61 |         }
 62 |         
 63 |         response = await this.apiClient.makeRequest<any>('get', apiPath);
 64 |       }
 65 | 
 66 |       // Format the response
 67 |       let contents: any[] = [];
 68 |       let actualBranch = branch;
 69 | 
 70 |       if (this.apiClient.getIsServer()) {
 71 |         // Bitbucket Server response
 72 |         const entries = response.children?.values || [];
 73 |         contents = entries.map((entry: BitbucketServerDirectoryEntry) => ({
 74 |           name: entry.path.name,
 75 |           type: entry.type === 'FILE' ? 'file' : 'directory',
 76 |           size: entry.size,
 77 |           path: dirPath ? `${dirPath}/${entry.path.name}` : entry.path.name
 78 |         }));
 79 |         
 80 |         // Get the actual branch from the response if available
 81 |         if (!branch && response.path?.components) {
 82 |           // Server returns default branch info in the response
 83 |           actualBranch = 'default';
 84 |         }
 85 |       } else {
 86 |         // Bitbucket Cloud response
 87 |         const entries = response.values || [];
 88 |         contents = entries.map((entry: BitbucketCloudDirectoryEntry) => ({
 89 |           name: entry.path.split('/').pop() || entry.path,
 90 |           type: entry.type === 'commit_file' ? 'file' : 'directory',
 91 |           size: entry.size,
 92 |           path: entry.path
 93 |         }));
 94 |         
 95 |         // Cloud returns the branch in the response
 96 |         actualBranch = branch || response.commit?.branch || 'main';
 97 |       }
 98 | 
 99 |       return {
100 |         content: [
101 |           {
102 |             type: 'text',
103 |             text: JSON.stringify({
104 |               path: dirPath || '/',
105 |               branch: actualBranch,
106 |               contents,
107 |               total_items: contents.length
108 |             }, null, 2),
109 |           },
110 |         ],
111 |       };
112 |     } catch (error) {
113 |       return this.apiClient.handleApiError(error, `listing directory '${dirPath}' in ${workspace}/${repository}`);
114 |     }
115 |   }
116 | 
117 |   async handleGetFileContent(args: any) {
118 |     if (!isGetFileContentArgs(args)) {
119 |       throw new McpError(
120 |         ErrorCode.InvalidParams,
121 |         'Invalid arguments for get_file_content'
122 |       );
123 |     }
124 | 
125 |     const { workspace, repository, file_path, branch, start_line, line_count, full_content = false } = args;
126 | 
127 |     try {
128 |       let fileContent: string;
129 |       let fileMetadata: any = {};
130 |       const fileSizeLimit = 1024 * 1024; // 1MB default limit
131 | 
132 |       if (this.apiClient.getIsServer()) {
133 |         // Bitbucket Server - get file metadata first to check size
134 |         const browsePath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/browse/${file_path}`;
135 |         const browseParams: any = {};
136 |         if (branch) {
137 |           browseParams.at = `refs/heads/${branch}`;
138 |         }
139 |         
140 |         try {
141 |           const metadataResponse = await this.apiClient.makeRequest<any>('get', browsePath, undefined, { params: browseParams });
142 |           fileMetadata = {
143 |             size: metadataResponse.size || 0,
144 |             path: file_path
145 |           };
146 | 
147 |           // Check file size
148 |           if (!full_content && fileMetadata.size > fileSizeLimit) {
149 |             return {
150 |               content: [
151 |                 {
152 |                   type: 'text',
153 |                   text: JSON.stringify({
154 |                     error: 'File too large',
155 |                     file_path,
156 |                     size: fileMetadata.size,
157 |                     size_mb: (fileMetadata.size / (1024 * 1024)).toFixed(2),
158 |                     message: `File exceeds size limit. Use full_content: true to force retrieval or use start_line/line_count for partial content.`
159 |                   }, null, 2),
160 |                 },
161 |               ],
162 |               isError: true,
163 |             };
164 |           }
165 |         } catch (e) {
166 |           // If browse fails, continue to try raw endpoint
167 |         }
168 | 
169 |         // Get raw content
170 |         const rawPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/raw/${file_path}`;
171 |         const rawParams: any = {};
172 |         if (branch) {
173 |           rawParams.at = `refs/heads/${branch}`;
174 |         }
175 |         
176 |         const response = await this.apiClient.makeRequest<any>('get', rawPath, undefined, { 
177 |           params: rawParams,
178 |           responseType: 'text',
179 |           headers: { 'Accept': 'text/plain' }
180 |         });
181 |         
182 |         fileContent = response;
183 |       } else {
184 |         // Bitbucket Cloud - first get metadata
185 |         const branchOrDefault = branch || 'HEAD';
186 |         const metaPath = `/repositories/${workspace}/${repository}/src/${branchOrDefault}/${file_path}`;
187 |         
188 |         const metadataResponse = await this.apiClient.makeRequest<BitbucketCloudFileMetadata>('get', metaPath);
189 |         
190 |         fileMetadata = {
191 |           size: metadataResponse.size,
192 |           encoding: metadataResponse.encoding,
193 |           path: metadataResponse.path,
194 |           commit: metadataResponse.commit
195 |         };
196 | 
197 |         // Check file size
198 |         if (!full_content && fileMetadata.size > fileSizeLimit) {
199 |           return {
200 |             content: [
201 |               {
202 |                 type: 'text',
203 |                 text: JSON.stringify({
204 |                   error: 'File too large',
205 |                   file_path,
206 |                   size: fileMetadata.size,
207 |                   size_mb: (fileMetadata.size / (1024 * 1024)).toFixed(2),
208 |                   message: `File exceeds size limit. Use full_content: true to force retrieval or use start_line/line_count for partial content.`
209 |                 }, null, 2),
210 |               },
211 |             ],
212 |             isError: true,
213 |           };
214 |         }
215 | 
216 |         // Follow the download link to get actual content
217 |         const downloadUrl = metadataResponse.links.download.href;
218 |         const downloadResponse = await this.apiClient.makeRequest<any>('get', downloadUrl, undefined, {
219 |           baseURL: '', // Use full URL
220 |           responseType: 'text',
221 |           headers: { 'Accept': 'text/plain' }
222 |         });
223 |         
224 |         fileContent = downloadResponse;
225 |       }
226 | 
227 |       // Apply line filtering if requested
228 |       let processedContent = fileContent;
229 |       let lineInfo: any = null;
230 | 
231 |       if (!full_content || start_line !== undefined || line_count !== undefined) {
232 |         const lines = fileContent.split('\n');
233 |         const totalLines = lines.length;
234 | 
235 |         // Determine default line count based on file extension
236 |         const ext = path.extname(file_path).toLowerCase();
237 |         const defaultLineCount = this.DEFAULT_LINES_BY_EXT[ext] || 500;
238 |         const shouldUseTail = defaultLineCount < 0;
239 | 
240 |         // Calculate start and end indices
241 |         let startIdx: number;
242 |         let endIdx: number;
243 | 
244 |         if (start_line !== undefined) {
245 |           if (start_line < 0) {
246 |             // Negative start_line means from end
247 |             startIdx = Math.max(0, totalLines + start_line);
248 |             endIdx = totalLines;
249 |           } else {
250 |             // 1-based to 0-based index
251 |             startIdx = Math.max(0, start_line - 1);
252 |             endIdx = startIdx + (line_count || Math.abs(defaultLineCount));
253 |           }
254 |         } else if (!full_content && fileMetadata.size > 50 * 1024) {
255 |           // Auto-truncate large files
256 |           if (shouldUseTail) {
257 |             startIdx = Math.max(0, totalLines + defaultLineCount);
258 |             endIdx = totalLines;
259 |           } else {
260 |             startIdx = 0;
261 |             endIdx = Math.abs(defaultLineCount);
262 |           }
263 |         } else {
264 |           // Return full content for small files
265 |           startIdx = 0;
266 |           endIdx = totalLines;
267 |         }
268 | 
269 |         // Ensure indices are within bounds
270 |         startIdx = Math.max(0, Math.min(startIdx, totalLines));
271 |         endIdx = Math.max(startIdx, Math.min(endIdx, totalLines));
272 | 
273 |         // Extract the requested lines
274 |         const selectedLines = lines.slice(startIdx, endIdx);
275 |         processedContent = selectedLines.join('\n');
276 | 
277 |         lineInfo = {
278 |           total_lines: totalLines,
279 |           returned_lines: {
280 |             start: startIdx + 1,
281 |             end: endIdx
282 |           },
283 |           truncated: startIdx > 0 || endIdx < totalLines,
284 |           message: endIdx < totalLines 
285 |             ? `Showing lines ${startIdx + 1}-${endIdx} of ${totalLines}. File size: ${(fileMetadata.size / 1024).toFixed(1)}KB`
286 |             : null
287 |         };
288 |       }
289 | 
290 |       // Build response
291 |       const response: any = {
292 |         file_path,
293 |         branch: branch || (this.apiClient.getIsServer() ? 'default' : 'main'),
294 |         size: fileMetadata.size || fileContent.length,
295 |         encoding: fileMetadata.encoding || 'utf-8',
296 |         content: processedContent
297 |       };
298 | 
299 |       if (lineInfo) {
300 |         response.line_info = lineInfo;
301 |       }
302 | 
303 |       if (fileMetadata.commit) {
304 |         response.last_modified = {
305 |           commit_id: fileMetadata.commit.hash,
306 |           author: fileMetadata.commit.author?.user?.display_name || fileMetadata.commit.author?.raw,
307 |           date: fileMetadata.commit.date,
308 |           message: fileMetadata.commit.message
309 |         };
310 |       }
311 | 
312 |       return {
313 |         content: [
314 |           {
315 |             type: 'text',
316 |             text: JSON.stringify(response, null, 2),
317 |           },
318 |         ],
319 |       };
320 |     } catch (error: any) {
321 |       // Handle specific not found error
322 |       if (error.status === 404) {
323 |         return {
324 |           content: [
325 |             {
326 |               type: 'text',
327 |               text: `File '${file_path}' not found in ${workspace}/${repository}${branch ? ` on branch '${branch}'` : ''}`,
328 |             },
329 |           ],
330 |           isError: true,
331 |         };
332 |       }
333 |       return this.apiClient.handleApiError(error, `getting file content for '${file_path}' in ${workspace}/${repository}`);
334 |     }
335 |   }
336 | 
337 |   // Helper method to get default line count based on file extension
338 |   private getDefaultLines(filePath: string, fileSize: number): { full: boolean } | { start: number; count: number } {
339 |     // Small files: return full content
340 |     if (fileSize < 50 * 1024) { // 50KB
341 |       return { full: true };
342 |     }
343 |     
344 |     const ext = path.extname(filePath).toLowerCase();
345 |     const defaultLines = this.DEFAULT_LINES_BY_EXT[ext] || 500;
346 |     
347 |     return {
348 |       start: defaultLines < 0 ? defaultLines : 1,
349 |       count: Math.abs(defaultLines)
350 |     };
351 |   }
352 | }
353 | 
```
Page 1/2FirstPrevNextLast