Initial commit: Bitbucket MCP server with get_pull_request tool
This commit is contained in:
commit
b1d758646c
9 changed files with 1909 additions and 0 deletions
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
test-api.js
|
||||||
|
*.test.js
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
.cache/
|
||||||
|
|
||||||
|
# Personal configuration
|
||||||
|
RELOAD_INSTRUCTIONS.md
|
||||||
|
personal-notes.md
|
134
README.md
Normal file
134
README.md
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
# Bitbucket MCP Server
|
||||||
|
|
||||||
|
An MCP (Model Context Protocol) server that provides tools for interacting with the Bitbucket API.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Currently implemented:
|
||||||
|
- `get_pull_request` - Retrieve detailed information about a pull request
|
||||||
|
|
||||||
|
Planned features:
|
||||||
|
- `create_pull_request` - Create new pull requests
|
||||||
|
- `list_pull_requests` - List pull requests with filters
|
||||||
|
- `update_pull_request` - Update PR details
|
||||||
|
- `merge_pull_request` - Merge pull requests
|
||||||
|
- And more...
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone or download this repository
|
||||||
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
3. Build the TypeScript code:
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication Setup
|
||||||
|
|
||||||
|
This server uses Bitbucket App Passwords for authentication.
|
||||||
|
|
||||||
|
### Creating an App Password
|
||||||
|
|
||||||
|
1. Log in to your Bitbucket account
|
||||||
|
2. Navigate to: https://bitbucket.org/account/settings/app-passwords/
|
||||||
|
3. Click "Create app password"
|
||||||
|
4. Give it a descriptive label (e.g., "MCP Server")
|
||||||
|
5. Select the following permissions:
|
||||||
|
- **Account**: Read
|
||||||
|
- **Repositories**: Read, Write
|
||||||
|
- **Pull requests**: Read, Write
|
||||||
|
6. Click "Create"
|
||||||
|
7. **Important**: Copy the generated password immediately (you won't be able to see it again!)
|
||||||
|
|
||||||
|
### Running the Setup Script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node scripts/setup-auth.js
|
||||||
|
```
|
||||||
|
|
||||||
|
This will guide you through the authentication setup process.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Add the server to your MCP settings file (usually located at `~/.vscode-server/data/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"bitbucket": {
|
||||||
|
"command": "node",
|
||||||
|
"args": ["/absolute/path/to/bitbucket-mcp-server/build/index.js"],
|
||||||
|
"env": {
|
||||||
|
"BITBUCKET_USERNAME": "your-username",
|
||||||
|
"BITBUCKET_APP_PASSWORD": "your-app-password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace:
|
||||||
|
- `/absolute/path/to/bitbucket-mcp-server` with the actual path to this directory
|
||||||
|
- `your-username` with your Bitbucket username (not email)
|
||||||
|
- `your-app-password` with the app password you created
|
||||||
|
|
||||||
|
For Bitbucket Server, use:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"bitbucket": {
|
||||||
|
"command": "node",
|
||||||
|
"args": ["/absolute/path/to/bitbucket-mcp-server/build/index.js"],
|
||||||
|
"env": {
|
||||||
|
"BITBUCKET_USERNAME": "your-username",
|
||||||
|
"BITBUCKET_TOKEN": "your-http-access-token",
|
||||||
|
"BITBUCKET_BASE_URL": "https://bitbucket.yourcompany.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Once configured, you can use the available tools:
|
||||||
|
|
||||||
|
### Get Pull Request
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
"tool": "get_pull_request",
|
||||||
|
"arguments": {
|
||||||
|
"workspace": "PROJ", // Required - your project key
|
||||||
|
"repository": "my-repo",
|
||||||
|
"pull_request_id": 123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns detailed information about the pull request including:
|
||||||
|
- Title and description
|
||||||
|
- Author and reviewers
|
||||||
|
- Source and destination branches
|
||||||
|
- Approval status
|
||||||
|
- Links to web UI and diff
|
||||||
|
- And more...
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
- `npm run dev` - Watch mode for development
|
||||||
|
- `npm run build` - Build the TypeScript code
|
||||||
|
- `npm start` - Run the built server
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
1. **Authentication errors**: Double-check your username and app password
|
||||||
|
2. **404 errors**: Verify the workspace, repository slug, and PR ID
|
||||||
|
3. **Permission errors**: Ensure your app password has the required permissions
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
64
SETUP_GUIDE.md
Normal file
64
SETUP_GUIDE.md
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# Bitbucket MCP Server Setup Guide
|
||||||
|
|
||||||
|
## Step 1: Find Your Bitbucket Username
|
||||||
|
|
||||||
|
1. **Log in to Bitbucket**: Go to https://bitbucket.org and log in with your credentials
|
||||||
|
|
||||||
|
2. **Find your username**:
|
||||||
|
- After logging in, click on your profile avatar in the top-right corner
|
||||||
|
- Click on "Personal settings" or go directly to: https://bitbucket.org/account/settings/
|
||||||
|
- Your username will be displayed at the top of the page
|
||||||
|
- **Note**: Your username is NOT your email address. It's usually a shorter identifier like "johndoe" or "jdoe123"
|
||||||
|
|
||||||
|
## Step 2: Create an App Password
|
||||||
|
|
||||||
|
1. **Navigate to App Passwords**:
|
||||||
|
- While logged in, go to: https://bitbucket.org/account/settings/app-passwords/
|
||||||
|
- Or from your account settings, look for "App passwords" in the left sidebar under "Access management"
|
||||||
|
|
||||||
|
2. **Create a new app password**:
|
||||||
|
- Click the "Create app password" button
|
||||||
|
- Give it a descriptive label like "MCP Server" or "Bitbucket MCP Integration"
|
||||||
|
|
||||||
|
3. **Select permissions** (IMPORTANT - select these specific permissions):
|
||||||
|
- ✅ **Account**: Read
|
||||||
|
- ✅ **Repositories**: Read, Write
|
||||||
|
- ✅ **Pull requests**: Read, Write
|
||||||
|
- You can leave other permissions unchecked
|
||||||
|
|
||||||
|
4. **Generate the password**:
|
||||||
|
- Click "Create"
|
||||||
|
- **IMPORTANT**: Copy the generated password immediately! It will look something like: `ATBBxxxxxxxxxxxxxxxxxxxxx`
|
||||||
|
- You won't be able to see this password again after closing the dialog
|
||||||
|
|
||||||
|
## Step 3: Find Your Workspace (Optional but Recommended)
|
||||||
|
|
||||||
|
Your workspace is the organization or team name in Bitbucket. To find it:
|
||||||
|
|
||||||
|
1. Look at any of your repository URLs:
|
||||||
|
- Example: `https://bitbucket.org/mycompany/my-repo`
|
||||||
|
- In this case, "mycompany" is your workspace
|
||||||
|
|
||||||
|
2. Or go to your workspace dashboard:
|
||||||
|
- Click on "Workspaces" in the top navigation
|
||||||
|
- Your workspaces will be listed there
|
||||||
|
|
||||||
|
## Example Credentials
|
||||||
|
|
||||||
|
Here's what your credentials should look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Username: johndoe # Your Bitbucket username (NOT email)
|
||||||
|
App Password: ATBB3xXx... # The generated app password
|
||||||
|
Workspace: mycompany # Your organization/workspace name
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
1. **"Username not found"**: Make sure you're using your Bitbucket username, not your email address
|
||||||
|
2. **"Invalid app password"**: Ensure you copied the entire app password including the "ATBB" prefix
|
||||||
|
3. **"Permission denied"**: Check that your app password has the required permissions (Account: Read, Repositories: Read/Write, Pull requests: Read/Write)
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Once you have these credentials, share them with me and I'll configure the MCP server for you. The credentials will be stored securely in your MCP settings configuration.
|
61
SETUP_GUIDE_SERVER.md
Normal file
61
SETUP_GUIDE_SERVER.md
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# Bitbucket Server MCP Setup Guide
|
||||||
|
|
||||||
|
Since you're using Bitbucket Server (self-hosted), you'll need to create an HTTP access token instead of an app password.
|
||||||
|
|
||||||
|
## Step 1: Your Username
|
||||||
|
|
||||||
|
Your Bitbucket Server username (not email address)
|
||||||
|
|
||||||
|
## Step 2: Create an HTTP Access Token
|
||||||
|
|
||||||
|
1. **Navigate to HTTP Access Tokens**:
|
||||||
|
- You mentioned you can see "HTTP access tokens" in your account settings
|
||||||
|
- Click on that option
|
||||||
|
|
||||||
|
2. **Create a new token**:
|
||||||
|
- Click "Create token" or similar button
|
||||||
|
- Give it a descriptive name like "MCP Server Integration"
|
||||||
|
- Set an expiration date (or leave it without expiration if allowed)
|
||||||
|
- Select the following permissions:
|
||||||
|
- **Repository**: Read, Write
|
||||||
|
- **Pull request**: Read, Write
|
||||||
|
- **Project**: Read (if available)
|
||||||
|
|
||||||
|
3. **Generate and copy the token**:
|
||||||
|
- Click "Create" or "Generate"
|
||||||
|
- **IMPORTANT**: Copy the token immediately! It will look like a long string of random characters
|
||||||
|
- You won't be able to see this token again
|
||||||
|
|
||||||
|
## Step 3: Find Your Bitbucket Server URL
|
||||||
|
|
||||||
|
Your Bitbucket Server URL is the base URL you use to access Bitbucket. For example:
|
||||||
|
- `https://bitbucket.yourcompany.com`
|
||||||
|
- `https://git.yourcompany.com`
|
||||||
|
- `https://bitbucket.internal.company.net`
|
||||||
|
|
||||||
|
## Step 4: Find Your Project/Workspace
|
||||||
|
|
||||||
|
In Bitbucket Server, repositories are organized by projects. Look at any repository URL:
|
||||||
|
- Example: `https://bitbucket.company.com/projects/PROJ/repos/my-repo`
|
||||||
|
- In this case, "PROJ" is your project key
|
||||||
|
|
||||||
|
## Example Configuration
|
||||||
|
|
||||||
|
For Bitbucket Server, your configuration will look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Username: your.username
|
||||||
|
Token: [Your HTTP access token]
|
||||||
|
Base URL: https://bitbucket.yourcompany.com
|
||||||
|
Project/Workspace: PROJ (or whatever your project key is)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Once you have:
|
||||||
|
1. Your username
|
||||||
|
2. An HTTP access token from the "HTTP access tokens" section
|
||||||
|
3. Your Bitbucket Server base URL
|
||||||
|
4. Your project key
|
||||||
|
|
||||||
|
You can configure the MCP server for Bitbucket Server.
|
1105
package-lock.json
generated
Normal file
1105
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
23
package.json
Normal file
23
package.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "bitbucket-mcp-server",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "MCP server for Bitbucket API integration",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./build/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
||||||
|
"dev": "tsc --watch",
|
||||||
|
"start": "node build/index.js"
|
||||||
|
},
|
||||||
|
"keywords": ["mcp", "bitbucket", "api"],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
||||||
|
"axios": "^1.9.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.15.29",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
}
|
||||||
|
}
|
48
scripts/setup-auth.js
Normal file
48
scripts/setup-auth.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
console.log(`
|
||||||
|
===========================================
|
||||||
|
Bitbucket MCP Server - Authentication Setup
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
To use this MCP server, you need to create a Bitbucket App Password.
|
||||||
|
|
||||||
|
Follow these steps:
|
||||||
|
|
||||||
|
1. Log in to your Bitbucket account
|
||||||
|
2. Go to: https://bitbucket.org/account/settings/app-passwords/
|
||||||
|
3. Click "Create app password"
|
||||||
|
4. Give it a label (e.g., "MCP Server")
|
||||||
|
5. Select the following permissions:
|
||||||
|
- Account: Read
|
||||||
|
- Repositories: Read, Write
|
||||||
|
- Pull requests: Read, Write
|
||||||
|
6. Click "Create"
|
||||||
|
7. Copy the generated app password (you won't be able to see it again!)
|
||||||
|
|
||||||
|
You'll need to provide:
|
||||||
|
- Your Bitbucket username (not email)
|
||||||
|
- The app password you just created
|
||||||
|
- Your default workspace/organization (optional)
|
||||||
|
|
||||||
|
Example workspace: If your repository URL is:
|
||||||
|
https://bitbucket.org/mycompany/my-repo
|
||||||
|
Then your workspace is: mycompany
|
||||||
|
|
||||||
|
These will be added to your MCP settings configuration.
|
||||||
|
|
||||||
|
Press Enter to continue...
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Wait for user to press Enter
|
||||||
|
process.stdin.once('data', () => {
|
||||||
|
console.log(`
|
||||||
|
Next steps:
|
||||||
|
1. The MCP server will be configured with your credentials
|
||||||
|
2. You'll be able to use the 'get_pull_request' tool
|
||||||
|
3. More tools can be added later (create_pull_request, list_pull_requests, etc.)
|
||||||
|
|
||||||
|
Configuration complete!
|
||||||
|
`);
|
||||||
|
process.exit(0);
|
||||||
|
});
|
413
src/index.ts
Normal file
413
src/index.ts
Normal file
|
@ -0,0 +1,413 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||||
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||||
|
import {
|
||||||
|
CallToolRequestSchema,
|
||||||
|
ErrorCode,
|
||||||
|
ListToolsRequestSchema,
|
||||||
|
McpError,
|
||||||
|
} from '@modelcontextprotocol/sdk/types.js';
|
||||||
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
|
// Get environment variables
|
||||||
|
const BITBUCKET_USERNAME = process.env.BITBUCKET_USERNAME;
|
||||||
|
const BITBUCKET_APP_PASSWORD = process.env.BITBUCKET_APP_PASSWORD;
|
||||||
|
const BITBUCKET_TOKEN = process.env.BITBUCKET_TOKEN; // For Bitbucket Server
|
||||||
|
const BITBUCKET_WORKSPACE = process.env.BITBUCKET_WORKSPACE;
|
||||||
|
const BITBUCKET_BASE_URL = process.env.BITBUCKET_BASE_URL || 'https://api.bitbucket.org/2.0';
|
||||||
|
|
||||||
|
// Check for either app password (Cloud) or token (Server)
|
||||||
|
if (!BITBUCKET_USERNAME || (!BITBUCKET_APP_PASSWORD && !BITBUCKET_TOKEN)) {
|
||||||
|
console.error('Error: BITBUCKET_USERNAME and either BITBUCKET_APP_PASSWORD (for Cloud) or BITBUCKET_TOKEN (for Server) are required');
|
||||||
|
console.error('Please set these in your MCP settings configuration');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: BITBUCKET_WORKSPACE is optional - it should be passed when invoking the tool
|
||||||
|
|
||||||
|
// Bitbucket Server API response types
|
||||||
|
interface BitbucketServerPullRequest {
|
||||||
|
id: number;
|
||||||
|
version: number;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
state: string;
|
||||||
|
open: boolean;
|
||||||
|
closed: boolean;
|
||||||
|
createdDate: number;
|
||||||
|
updatedDate: number;
|
||||||
|
fromRef: {
|
||||||
|
id: string;
|
||||||
|
displayId: string;
|
||||||
|
latestCommit: string;
|
||||||
|
repository: {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
project: {
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
toRef: {
|
||||||
|
id: string;
|
||||||
|
displayId: string;
|
||||||
|
latestCommit: string;
|
||||||
|
repository: {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
project: {
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
locked: boolean;
|
||||||
|
author: {
|
||||||
|
user: {
|
||||||
|
name: string;
|
||||||
|
emailAddress: string;
|
||||||
|
displayName: string;
|
||||||
|
};
|
||||||
|
role: string;
|
||||||
|
approved: boolean;
|
||||||
|
status: string;
|
||||||
|
};
|
||||||
|
reviewers: Array<{
|
||||||
|
user: {
|
||||||
|
name: string;
|
||||||
|
emailAddress: string;
|
||||||
|
displayName: string;
|
||||||
|
};
|
||||||
|
role: string;
|
||||||
|
approved: boolean;
|
||||||
|
status: string;
|
||||||
|
}>;
|
||||||
|
participants: Array<{
|
||||||
|
user: {
|
||||||
|
name: string;
|
||||||
|
emailAddress: string;
|
||||||
|
displayName: string;
|
||||||
|
};
|
||||||
|
role: string;
|
||||||
|
approved: boolean;
|
||||||
|
status: string;
|
||||||
|
}>;
|
||||||
|
links: {
|
||||||
|
self: Array<{
|
||||||
|
href: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitbucket Cloud API response types (keeping for compatibility)
|
||||||
|
interface BitbucketCloudPullRequest {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
state: string;
|
||||||
|
author: {
|
||||||
|
display_name: string;
|
||||||
|
account_id: string;
|
||||||
|
};
|
||||||
|
source: {
|
||||||
|
branch: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
repository: {
|
||||||
|
full_name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
destination: {
|
||||||
|
branch: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
repository: {
|
||||||
|
full_name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
reviewers: Array<{
|
||||||
|
display_name: string;
|
||||||
|
account_id: string;
|
||||||
|
}>;
|
||||||
|
participants: Array<{
|
||||||
|
user: {
|
||||||
|
display_name: string;
|
||||||
|
account_id: string;
|
||||||
|
};
|
||||||
|
role: string;
|
||||||
|
approved: boolean;
|
||||||
|
}>;
|
||||||
|
created_on: string;
|
||||||
|
updated_on: string;
|
||||||
|
links: {
|
||||||
|
html: {
|
||||||
|
href: string;
|
||||||
|
};
|
||||||
|
self: {
|
||||||
|
href: string;
|
||||||
|
};
|
||||||
|
diff: {
|
||||||
|
href: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
merge_commit?: {
|
||||||
|
hash: string;
|
||||||
|
};
|
||||||
|
close_source_branch: boolean;
|
||||||
|
closed_by?: {
|
||||||
|
display_name: string;
|
||||||
|
account_id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type guard for tool arguments
|
||||||
|
const isGetPullRequestArgs = (
|
||||||
|
args: any
|
||||||
|
): args is { workspace: string; repository: string; pull_request_id: number } =>
|
||||||
|
typeof args === 'object' &&
|
||||||
|
args !== null &&
|
||||||
|
typeof args.workspace === 'string' &&
|
||||||
|
typeof args.repository === 'string' &&
|
||||||
|
typeof args.pull_request_id === 'number';
|
||||||
|
|
||||||
|
class BitbucketMCPServer {
|
||||||
|
private server: Server;
|
||||||
|
private axiosInstance: AxiosInstance;
|
||||||
|
private isServer: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.isServer = !!BITBUCKET_TOKEN;
|
||||||
|
|
||||||
|
this.server = new Server(
|
||||||
|
{
|
||||||
|
name: 'bitbucket-mcp-server',
|
||||||
|
version: '0.1.0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: {
|
||||||
|
tools: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create axios instance with appropriate auth
|
||||||
|
const axiosConfig: any = {
|
||||||
|
baseURL: BITBUCKET_BASE_URL,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use token auth for Bitbucket Server, basic auth for Cloud
|
||||||
|
if (BITBUCKET_TOKEN) {
|
||||||
|
// Bitbucket Server uses Bearer token
|
||||||
|
axiosConfig.headers['Authorization'] = `Bearer ${BITBUCKET_TOKEN}`;
|
||||||
|
} else {
|
||||||
|
// Bitbucket Cloud uses basic auth with app password
|
||||||
|
axiosConfig.auth = {
|
||||||
|
username: BITBUCKET_USERNAME!,
|
||||||
|
password: BITBUCKET_APP_PASSWORD!,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.axiosInstance = axios.create(axiosConfig);
|
||||||
|
|
||||||
|
this.setupToolHandlers();
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
||||||
|
process.on('SIGINT', async () => {
|
||||||
|
await this.server.close();
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatServerResponse(pr: BitbucketServerPullRequest): any {
|
||||||
|
const webUrl = `${BITBUCKET_BASE_URL}/projects/${pr.toRef.repository.project.key}/repos/${pr.toRef.repository.slug}/pull-requests/${pr.id}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: pr.id,
|
||||||
|
title: pr.title,
|
||||||
|
description: pr.description || 'No description provided',
|
||||||
|
state: pr.state,
|
||||||
|
is_open: pr.open,
|
||||||
|
is_closed: pr.closed,
|
||||||
|
author: pr.author.user.displayName,
|
||||||
|
author_email: pr.author.user.emailAddress,
|
||||||
|
source_branch: pr.fromRef.displayId,
|
||||||
|
destination_branch: pr.toRef.displayId,
|
||||||
|
source_commit: pr.fromRef.latestCommit,
|
||||||
|
destination_commit: pr.toRef.latestCommit,
|
||||||
|
reviewers: pr.reviewers.map(r => ({
|
||||||
|
name: r.user.displayName,
|
||||||
|
approved: r.approved,
|
||||||
|
status: r.status,
|
||||||
|
})),
|
||||||
|
participants: pr.participants.map(p => ({
|
||||||
|
name: p.user.displayName,
|
||||||
|
role: p.role,
|
||||||
|
approved: p.approved,
|
||||||
|
status: p.status,
|
||||||
|
})),
|
||||||
|
created_on: new Date(pr.createdDate).toLocaleString(),
|
||||||
|
updated_on: new Date(pr.updatedDate).toLocaleString(),
|
||||||
|
web_url: webUrl,
|
||||||
|
api_url: pr.links.self[0]?.href || '',
|
||||||
|
is_locked: pr.locked,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatCloudResponse(pr: BitbucketCloudPullRequest): any {
|
||||||
|
return {
|
||||||
|
id: pr.id,
|
||||||
|
title: pr.title,
|
||||||
|
description: pr.description || 'No description provided',
|
||||||
|
state: pr.state,
|
||||||
|
author: pr.author.display_name,
|
||||||
|
source_branch: pr.source.branch.name,
|
||||||
|
destination_branch: pr.destination.branch.name,
|
||||||
|
reviewers: pr.reviewers.map(r => r.display_name),
|
||||||
|
participants: pr.participants.map(p => ({
|
||||||
|
name: p.user.display_name,
|
||||||
|
role: p.role,
|
||||||
|
approved: p.approved,
|
||||||
|
})),
|
||||||
|
created_on: new Date(pr.created_on).toLocaleString(),
|
||||||
|
updated_on: new Date(pr.updated_on).toLocaleString(),
|
||||||
|
web_url: pr.links.html.href,
|
||||||
|
api_url: pr.links.self.href,
|
||||||
|
diff_url: pr.links.diff.href,
|
||||||
|
is_merged: pr.state === 'MERGED',
|
||||||
|
merge_commit: pr.merge_commit?.hash,
|
||||||
|
close_source_branch: pr.close_source_branch,
|
||||||
|
closed_by: pr.closed_by?.display_name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupToolHandlers() {
|
||||||
|
// List available tools
|
||||||
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
name: 'get_pull_request',
|
||||||
|
description: 'Get details of a Bitbucket pull request',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
workspace: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Bitbucket workspace/project key (e.g., "JBIZ")',
|
||||||
|
},
|
||||||
|
repository: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Repository slug (e.g., "my-repo")',
|
||||||
|
},
|
||||||
|
pull_request_id: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'Pull request ID',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['workspace', 'repository', 'pull_request_id'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Handle tool calls
|
||||||
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
|
if (request.params.name !== 'get_pull_request') {
|
||||||
|
throw new McpError(
|
||||||
|
ErrorCode.MethodNotFound,
|
||||||
|
`Unknown tool: ${request.params.name}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isGetPullRequestArgs(request.params.arguments)) {
|
||||||
|
throw new McpError(
|
||||||
|
ErrorCode.InvalidParams,
|
||||||
|
'Invalid arguments for get_pull_request'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { workspace, repository, pull_request_id } = request.params.arguments;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Different API paths for Server vs Cloud
|
||||||
|
const apiPath = this.isServer
|
||||||
|
? `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}` // Server
|
||||||
|
: `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}`; // Cloud
|
||||||
|
|
||||||
|
console.error(`[DEBUG] Fetching PR from: ${BITBUCKET_BASE_URL}${apiPath}`);
|
||||||
|
|
||||||
|
const response = await this.axiosInstance.get(apiPath);
|
||||||
|
const pr = response.data;
|
||||||
|
|
||||||
|
// Format the response based on server type
|
||||||
|
const formattedResponse = this.isServer
|
||||||
|
? this.formatServerResponse(pr as BitbucketServerPullRequest)
|
||||||
|
: this.formatCloudResponse(pr as BitbucketCloudPullRequest);
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: JSON.stringify(formattedResponse, null, 2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
const status = error.response?.status;
|
||||||
|
const message = error.response?.data?.errors?.[0]?.message ||
|
||||||
|
error.response?.data?.error?.message ||
|
||||||
|
error.response?.data?.message ||
|
||||||
|
error.message;
|
||||||
|
|
||||||
|
console.error(`[DEBUG] API Error: ${status} - ${message}`);
|
||||||
|
console.error(`[DEBUG] Full error response:`, error.response?.data);
|
||||||
|
|
||||||
|
if (status === 404) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: `Pull request not found: ${workspace}/${repository}/pull-requests/${pull_request_id}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
} else if (status === 401) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: `Authentication failed. Please check your ${this.isServer ? 'BITBUCKET_TOKEN' : 'BITBUCKET_USERNAME and BITBUCKET_APP_PASSWORD'}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: `Bitbucket API error: ${message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await this.server.connect(transport);
|
||||||
|
console.error(`Bitbucket MCP server running on stdio (${this.isServer ? 'Server' : 'Cloud'} mode)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = new BitbucketMCPServer();
|
||||||
|
server.run().catch(console.error);
|
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "Node16",
|
||||||
|
"moduleResolution": "Node16",
|
||||||
|
"outDir": "./build",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "build"]
|
||||||
|
}
|
Loading…
Reference in a new issue