bitbucket-mcp
Advanced tools
Comparing version
#!/usr/bin/env node | ||
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; | ||
import express from "express"; | ||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; | ||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; | ||
import { CallToolRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js"; | ||
import axios from "axios"; | ||
import winston from "winston"; | ||
// Constants | ||
const LOG_FILE = "bitbucket.log"; | ||
const API_VERSION = "1.1.0"; | ||
const API_PATH = "/rest/api/1.0"; | ||
// Enums | ||
var MergeStrategy; | ||
(function (MergeStrategy) { | ||
MergeStrategy["MERGE_COMMIT"] = "merge-commit"; | ||
MergeStrategy["SQUASH"] = "squash"; | ||
MergeStrategy["FAST_FORWARD"] = "fast-forward"; | ||
})(MergeStrategy || (MergeStrategy = {})); | ||
var ReviewAction; | ||
(function (ReviewAction) { | ||
ReviewAction["APPROVED"] = "APPROVED"; | ||
ReviewAction["REVIEWED"] = "REVIEWED"; | ||
})(ReviewAction || (ReviewAction = {})); | ||
// Configuration for logger | ||
const logger = winston.createLogger({ | ||
level: "info", | ||
format: winston.format.json(), | ||
transports: [new winston.transports.File({ filename: LOG_FILE })], | ||
}); | ||
class BitbucketServer { | ||
import { z } from "zod"; | ||
import { randomUUID } from "crypto"; | ||
import { setupLogger } from "./utils/logger.js"; | ||
import { getBitbucketAPI } from "./services/bitbucket.js"; | ||
// Setup logger | ||
const logger = setupLogger(); | ||
// Bitbucket MCP Server class | ||
class BitbucketMcpServer { | ||
server; | ||
api; | ||
config; | ||
api = null; | ||
constructor() { | ||
this.server = new Server({ | ||
name: "bitbucket-server-mcp-server", | ||
version: API_VERSION, | ||
}, { | ||
capabilities: { | ||
tools: { | ||
create_pull_request: { | ||
description: "Create a new pull request", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
project: { | ||
type: "string", | ||
description: "Bitbucket project key", | ||
}, | ||
repository: { | ||
type: "string", | ||
description: "Repository slug", | ||
}, | ||
title: { type: "string", description: "PR title" }, | ||
description: { | ||
type: "string", | ||
description: "PR description", | ||
}, | ||
sourceBranch: { | ||
type: "string", | ||
description: "Source branch name", | ||
}, | ||
targetBranch: { | ||
type: "string", | ||
description: "Target branch name", | ||
}, | ||
reviewers: { | ||
type: "array", | ||
items: { type: "string" }, | ||
description: "List of reviewer usernames", | ||
}, | ||
}, | ||
required: [ | ||
"repository", | ||
"title", | ||
"sourceBranch", | ||
"targetBranch", | ||
], | ||
// Initialize the MCP server | ||
this.server = new McpServer({ | ||
name: "bitbucket-mcp", | ||
version: "1.0.0", | ||
}); | ||
// Add server error handling | ||
logger.on("error", (error) => { | ||
logger.error("Server error", { error }); | ||
}); | ||
this.setupTools(); | ||
} | ||
setupTools() { | ||
// Register bitbucket repository tools | ||
this.server.tool("listRepositories", { | ||
workspace: z.string().optional().describe("Bitbucket workspace name"), | ||
limit: z | ||
.number() | ||
.optional() | ||
.describe("Maximum number of repositories to return"), | ||
}, async ({ workspace, limit }, extra) => { | ||
logger.info("Listing Bitbucket repositories", { workspace, limit }); | ||
try { | ||
const bitbucketAPI = await getBitbucketAPI(); | ||
const repositories = await bitbucketAPI.listRepositories(workspace, limit); | ||
return { | ||
content: [ | ||
{ | ||
type: "text", | ||
text: `Found ${repositories.length} repositories${workspace ? ` in workspace ${workspace}` : ""}`, | ||
}, | ||
}, | ||
get_pull_request: { | ||
description: "Get pull request details", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
project: { | ||
type: "string", | ||
description: "Bitbucket project key", | ||
}, | ||
repository: { | ||
type: "string", | ||
description: "Repository slug", | ||
}, | ||
prId: { type: "number", description: "Pull request ID" }, | ||
}, | ||
required: ["repository", "prId"], | ||
{ | ||
type: "text", | ||
text: JSON.stringify(repositories, null, 2), | ||
}, | ||
}, | ||
merge_pull_request: { | ||
description: "Merge a pull request", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
project: { | ||
type: "string", | ||
description: "Bitbucket project key", | ||
}, | ||
repository: { | ||
type: "string", | ||
description: "Repository slug", | ||
}, | ||
prId: { type: "number", description: "Pull request ID" }, | ||
message: { | ||
type: "string", | ||
description: "Merge commit message", | ||
}, | ||
strategy: { | ||
type: "string", | ||
enum: Object.values(MergeStrategy), | ||
description: "Merge strategy to use", | ||
}, | ||
}, | ||
required: ["repository", "prId"], | ||
], | ||
}; | ||
} | ||
catch (error) { | ||
logger.error("Error listing repositories", { error }); | ||
return { | ||
content: [ | ||
{ | ||
type: "text", | ||
text: `Error listing repositories: ${error instanceof Error ? error.message : String(error)}`, | ||
}, | ||
}, | ||
decline_pull_request: { | ||
description: "Decline a pull request", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
project: { | ||
type: "string", | ||
description: "Bitbucket project key", | ||
}, | ||
repository: { | ||
type: "string", | ||
description: "Repository slug", | ||
}, | ||
prId: { type: "number", description: "Pull request ID" }, | ||
message: { | ||
type: "string", | ||
description: "Reason for declining", | ||
}, | ||
}, | ||
required: ["repository", "prId"], | ||
], | ||
}; | ||
} | ||
}); | ||
this.server.tool("getRepository", { | ||
workspace: z.string().describe("Bitbucket workspace name"), | ||
repo_slug: z.string().describe("Repository slug"), | ||
}, async ({ workspace, repo_slug }, extra) => { | ||
logger.info("Getting Bitbucket repository info", { | ||
workspace, | ||
repo_slug, | ||
}); | ||
try { | ||
const bitbucketAPI = await getBitbucketAPI(); | ||
const repository = await bitbucketAPI.getRepository(workspace, repo_slug); | ||
return { | ||
content: [ | ||
{ | ||
type: "text", | ||
text: `Repository: ${repository.name}`, | ||
}, | ||
}, | ||
add_comment: { | ||
description: "Add a comment to a pull request", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
project: { | ||
type: "string", | ||
description: "Bitbucket project key", | ||
}, | ||
repository: { | ||
type: "string", | ||
description: "Repository slug", | ||
}, | ||
prId: { type: "number", description: "Pull request ID" }, | ||
text: { type: "string", description: "Comment text" }, | ||
parentId: { | ||
type: "number", | ||
description: "Parent comment ID for replies", | ||
}, | ||
}, | ||
required: ["repository", "prId", "text"], | ||
{ | ||
type: "text", | ||
text: JSON.stringify(repository, null, 2), | ||
}, | ||
}, | ||
get_diff: { | ||
description: "Get pull request diff", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
project: { | ||
type: "string", | ||
description: "Bitbucket project key", | ||
}, | ||
repository: { | ||
type: "string", | ||
description: "Repository slug", | ||
}, | ||
prId: { type: "number", description: "Pull request ID" }, | ||
contextLines: { | ||
type: "number", | ||
description: "Number of context lines", | ||
}, | ||
}, | ||
required: ["repository", "prId"], | ||
], | ||
}; | ||
} | ||
catch (error) { | ||
logger.error("Error getting repository", { | ||
error, | ||
workspace, | ||
repo_slug, | ||
}); | ||
return { | ||
content: [ | ||
{ | ||
type: "text", | ||
text: `Error getting repository: ${error instanceof Error ? error.message : String(error)}`, | ||
}, | ||
}, | ||
get_reviews: { | ||
description: "Get pull request reviews", | ||
inputSchema: { | ||
type: "object", | ||
properties: { | ||
project: { | ||
type: "string", | ||
description: "Bitbucket project key", | ||
}, | ||
repository: { | ||
type: "string", | ||
description: "Repository slug", | ||
}, | ||
prId: { type: "number", description: "Pull request ID" }, | ||
}, | ||
required: ["repository", "prId"], | ||
}, | ||
}, | ||
}, | ||
}, | ||
], | ||
}; | ||
} | ||
}); | ||
// Configuration from environment variables | ||
this.config = this.loadConfig(); | ||
// Configuration of Axios instance | ||
this.api = axios.create({ | ||
baseURL: `${this.config.baseUrl}${API_PATH}`, | ||
headers: this.config.token | ||
? { Authorization: `Bearer ${this.config.token}` } | ||
: {}, | ||
auth: this.config.username && this.config.password | ||
? { username: this.config.username, password: this.config.password } | ||
: undefined, | ||
}); | ||
this.setupToolHandlers(); | ||
this.server.onerror = (error) => { | ||
logger.error("[MCP Error]", error); | ||
}; | ||
} | ||
/** | ||
* Loads configuration from environment variables | ||
* @returns BitbucketConfig configuration object | ||
* @throws Error if required environment variables are not set | ||
*/ | ||
loadConfig() { | ||
const config = { | ||
baseUrl: process.env.BITBUCKET_URL ?? "", | ||
token: process.env.BITBUCKET_TOKEN, | ||
username: process.env.BITBUCKET_USERNAME, | ||
password: process.env.BITBUCKET_PASSWORD, | ||
defaultProject: process.env.BITBUCKET_DEFAULT_PROJECT, | ||
}; | ||
if (!config.baseUrl) { | ||
throw new Error("BITBUCKET_URL is required"); | ||
} | ||
if (!config.token && !(config.username && config.password)) { | ||
throw new Error("Either BITBUCKET_TOKEN or BITBUCKET_USERNAME/PASSWORD is required"); | ||
} | ||
return config; | ||
} | ||
/** | ||
* Type guard for PullRequestInput | ||
*/ | ||
isPullRequestInput(args) { | ||
const input = args; | ||
return (typeof args === "object" && | ||
args !== null && | ||
typeof input.project === "string" && | ||
typeof input.repository === "string" && | ||
typeof input.title === "string" && | ||
typeof input.sourceBranch === "string" && | ||
typeof input.targetBranch === "string" && | ||
(input.description === undefined || | ||
typeof input.description === "string") && | ||
(input.reviewers === undefined || Array.isArray(input.reviewers))); | ||
} | ||
/** | ||
* Sets up tool handlers for the MCP server | ||
*/ | ||
setupToolHandlers() { | ||
this.server.setRequestHandler(CallToolRequestSchema, async (request) => { | ||
this.server.tool("getPullRequests", { | ||
workspace: z.string().describe("Bitbucket workspace name"), | ||
repo_slug: z.string().describe("Repository slug"), | ||
state: z | ||
.enum(["OPEN", "MERGED", "DECLINED", "SUPERSEDED"]) | ||
.optional() | ||
.describe("Pull request state"), | ||
limit: z | ||
.number() | ||
.optional() | ||
.describe("Maximum number of pull requests to return"), | ||
}, async ({ workspace, repo_slug, state, limit }, extra) => { | ||
logger.info("Getting Bitbucket pull requests", { | ||
workspace, | ||
repo_slug, | ||
state, | ||
limit, | ||
}); | ||
try { | ||
logger.info(`Called tool: ${request.params.name}`, { | ||
arguments: request.params.arguments, | ||
}); | ||
const args = request.params.arguments ?? {}; | ||
const pullRequestParams = this.extractPullRequestParams(args); | ||
switch (request.params.name) { | ||
case "create_pull_request": | ||
if (!this.isPullRequestInput(args)) { | ||
throw new McpError(ErrorCode.InvalidParams, "Invalid pull request input parameters"); | ||
} | ||
return await this.createPullRequest(args); | ||
case "get_pull_request": | ||
return await this.getPullRequest(pullRequestParams); | ||
case "merge_pull_request": | ||
return await this.mergePullRequest(pullRequestParams, { | ||
message: args.message, | ||
strategy: args.strategy, | ||
}); | ||
case "decline_pull_request": | ||
return await this.declinePullRequest(pullRequestParams, args.message); | ||
case "add_comment": | ||
return await this.addComment(pullRequestParams, { | ||
text: args.text, | ||
parentId: args.parentId, | ||
}); | ||
case "get_diff": | ||
return await this.getDiff(pullRequestParams, args.contextLines); | ||
case "get_reviews": | ||
return await this.getReviews(pullRequestParams); | ||
default: | ||
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); | ||
} | ||
const bitbucketAPI = await getBitbucketAPI(); | ||
const pullRequests = await bitbucketAPI.getPullRequests(workspace, repo_slug, state, limit); | ||
return { | ||
content: [ | ||
{ | ||
type: "text", | ||
text: `Found ${pullRequests.length} pull requests${state ? ` with state ${state}` : ""}`, | ||
}, | ||
{ | ||
type: "text", | ||
text: JSON.stringify(pullRequests, null, 2), | ||
}, | ||
], | ||
}; | ||
} | ||
catch (error) { | ||
logger.error("Tool execution error", { error }); | ||
return this.handleError(error); | ||
logger.error("Error getting pull requests", { | ||
error, | ||
workspace, | ||
repo_slug, | ||
}); | ||
return { | ||
content: [ | ||
{ | ||
type: "text", | ||
text: `Error getting pull requests: ${error instanceof Error ? error.message : String(error)}`, | ||
}, | ||
], | ||
}; | ||
} | ||
}); | ||
} | ||
/** | ||
* Extracts pull request parameters from the arguments | ||
*/ | ||
extractPullRequestParams(args) { | ||
const pullRequestParams = { | ||
project: args.project ?? this.config.defaultProject ?? "", | ||
repository: args.repository, | ||
prId: args.prId, | ||
}; | ||
if (!pullRequestParams.project) { | ||
throw new McpError(ErrorCode.InvalidParams, "Project must be provided either as a parameter or through BITBUCKET_DEFAULT_PROJECT environment variable"); | ||
} | ||
return pullRequestParams; | ||
// Method to get the McpServer instance | ||
getServer() { | ||
return this.server; | ||
} | ||
/** | ||
* Handles errors and converts them to McpError format | ||
*/ | ||
handleError(error) { | ||
if (error instanceof McpError) { | ||
throw error; | ||
} | ||
// Create Bitbucket MCP Server instance | ||
const bitbucketMcpServer = new BitbucketMcpServer(); | ||
const server = bitbucketMcpServer.getServer(); | ||
// Express server setup | ||
const app = express(); | ||
app.use(express.json()); | ||
// Initialize standalone mode if requested | ||
if (process.env.STANDALONE === "true") { | ||
logger.info("Running in standalone mode with stdio transport"); | ||
const stdioTransport = new StdioServerTransport(); | ||
server.connect(stdioTransport).catch((error) => { | ||
logger.error("Error connecting stdio transport", { error }); | ||
process.exit(1); | ||
}); | ||
} | ||
else { | ||
// HTTP server mode | ||
// MCP endpoint | ||
app.all("/mcp", (req, res) => { | ||
logger.info("Received MCP request", { | ||
method: req.method, | ||
path: req.path, | ||
accept: req.headers.accept, | ||
contentType: req.headers["content-type"], | ||
}); | ||
try { | ||
// Create transport with a session ID generator | ||
const transport = new StreamableHTTPServerTransport({ | ||
sessionIdGenerator: () => randomUUID(), | ||
}); | ||
// Connect the transport to the server (must be done before handling requests) | ||
server.connect(transport); | ||
// Handle the request (this passes it to the connected server) | ||
transport.handleRequest(req, res, req.body); | ||
logger.info("Transport connected to server"); | ||
} | ||
if (axios.isAxiosError(error)) { | ||
const axiosError = error; | ||
const statusCode = axiosError.response?.status ?? 500; | ||
const errorMessage = axiosError.response?.data?.message ?? axiosError.message; | ||
throw new McpError(ErrorCode.InternalError, `Bitbucket API error (${statusCode}): ${errorMessage}`); | ||
catch (error) { | ||
logger.error("Error handling MCP request", { error }); | ||
// Only send response if headers haven't been sent yet | ||
if (!res.headersSent) { | ||
res.status(500).json({ | ||
error: "Internal server error", | ||
message: error instanceof Error ? error.message : String(error), | ||
}); | ||
} | ||
} | ||
throw new McpError(ErrorCode.InternalError, `Unknown error: ${error instanceof Error ? error.message : String(error)}`); | ||
} | ||
/** | ||
* Creates a new pull request | ||
*/ | ||
async createPullRequest(input) { | ||
const payload = { | ||
title: input.title, | ||
description: input.description, | ||
fromRef: { | ||
id: `refs/heads/${input.sourceBranch}`, | ||
repository: { | ||
slug: input.repository, | ||
project: { key: input.project }, | ||
}, | ||
}, | ||
toRef: { | ||
id: `refs/heads/${input.targetBranch}`, | ||
repository: { | ||
slug: input.repository, | ||
project: { key: input.project }, | ||
}, | ||
}, | ||
reviewers: input.reviewers?.map((username) => ({ | ||
user: { name: username }, | ||
})), | ||
}; | ||
const response = await this.api.post(`/projects/${input.project}/repos/${input.repository}/pull-requests`, payload); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
/** | ||
* Gets pull request details | ||
*/ | ||
async getPullRequest(params) { | ||
const { project, repository, prId } = params; | ||
const response = await this.api.get(`/projects/${project}/repos/${repository}/pull-requests/${prId}`); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
/** | ||
* Merges a pull request | ||
*/ | ||
async mergePullRequest(params, options = {}) { | ||
const { project, repository, prId } = params; | ||
const { message, strategy = MergeStrategy.MERGE_COMMIT } = options; | ||
const response = await this.api.post(`/projects/${project}/repos/${repository}/pull-requests/${prId}/merge`, { | ||
version: -1, | ||
message, | ||
strategy, | ||
}); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
/** | ||
* Declines a pull request | ||
*/ | ||
async declinePullRequest(params, message) { | ||
const { project, repository, prId } = params; | ||
const response = await this.api.post(`/projects/${project}/repos/${repository}/pull-requests/${prId}/decline`, { | ||
version: -1, | ||
message, | ||
}); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
/** | ||
* Adds a comment to a pull request | ||
*/ | ||
async addComment(params, options) { | ||
const { project, repository, prId } = params; | ||
const { text, parentId } = options; | ||
const response = await this.api.post(`/projects/${project}/repos/${repository}/pull-requests/${prId}/comments`, { | ||
text, | ||
parent: parentId ? { id: parentId } : undefined, | ||
}); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }], | ||
}; | ||
} | ||
/** | ||
* Gets the diff for a pull request | ||
*/ | ||
async getDiff(params, contextLines = 10) { | ||
const { project, repository, prId } = params; | ||
const response = await this.api.get(`/projects/${project}/repos/${repository}/pull-requests/${prId}/diff`, { | ||
params: { contextLines }, | ||
headers: { Accept: "text/plain" }, | ||
}); | ||
return { | ||
content: [{ type: "text", text: response.data }], | ||
}; | ||
} | ||
/** | ||
* Gets the reviews for a pull request | ||
*/ | ||
async getReviews(params) { | ||
const { project, repository, prId } = params; | ||
const response = await this.api.get(`/projects/${project}/repos/${repository}/pull-requests/${prId}/activities`); | ||
const reviews = response.data.values.filter((activity) => activity.action === ReviewAction.APPROVED || | ||
activity.action === ReviewAction.REVIEWED); | ||
return { | ||
content: [{ type: "text", text: JSON.stringify(reviews, null, 2) }], | ||
}; | ||
} | ||
/** | ||
* Starts the server | ||
*/ | ||
async run() { | ||
const transport = new StdioServerTransport(); | ||
await this.server.connect(transport); | ||
logger.info("Bitbucket MCP server running on stdio"); | ||
} | ||
}); | ||
// Health endpoint | ||
app.get("/health", (req, res) => { | ||
res.status(200).json({ status: "ok" }); | ||
}); | ||
// Start server | ||
const PORT = process.env.PORT || 3000; | ||
app.listen(PORT, () => { | ||
logger.info(`Bitbucket MCP server listening on port ${PORT}`); | ||
}); | ||
} | ||
const server = new BitbucketServer(); | ||
server.run().catch((error) => { | ||
logger.error("Server error", error); | ||
process.exit(1); | ||
}); | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "bitbucket-mcp", | ||
"version": "1.2.0", | ||
"version": "2.0.0", | ||
"description": "Model Context Protocol (MCP) server for Bitbucket Cloud and Server API integration", | ||
@@ -19,4 +19,10 @@ "type": "module", | ||
"standalone": "STANDALONE=true node dist/index.js", | ||
"dev": "tsc -w", | ||
"dev": "tsx watch src/index.ts", | ||
"prepublishOnly": "npm run build", | ||
"version": "git add -A src", | ||
"postversion": "git push && git push --tags", | ||
"publish:patch": "npm version patch && npm publish", | ||
"publish:minor": "npm version minor && npm publish", | ||
"publish:major": "npm version major && npm publish", | ||
"release": "npm run publish:patch", | ||
"test": "jest", | ||
@@ -33,8 +39,3 @@ "lint": "eslint src/**/*.ts", | ||
"ai", | ||
"llm", | ||
"claude", | ||
"anthropic", | ||
"gpt", | ||
"openai", | ||
"mistral" | ||
"llm" | ||
], | ||
@@ -52,14 +53,18 @@ "author": "Bitbucket MCP Team", | ||
"dependencies": { | ||
"@modelcontextprotocol/sdk": "^1.1.1", | ||
"@modelcontextprotocol/sdk": "^1.10.2", | ||
"axios": "^1.6.5", | ||
"winston": "^3.11.0" | ||
"express": "^4.19.2", | ||
"winston": "^3.11.0", | ||
"zod": "^3.22.4" | ||
}, | ||
"devDependencies": { | ||
"@types/express": "^4.17.21", | ||
"@types/jest": "^29.5.11", | ||
"@types/node": "^22.10.7", | ||
"@typescript-eslint/eslint-plugin": "^6.19.0", | ||
"@typescript-eslint/parser": "^6.19.0", | ||
"@types/node": "^20.11.24", | ||
"@typescript-eslint/eslint-plugin": "^7.0.2", | ||
"@typescript-eslint/parser": "^7.0.2", | ||
"eslint": "^8.56.0", | ||
"jest": "^29.7.0", | ||
"ts-jest": "^29.1.1", | ||
"tsx": "^4.7.1", | ||
"typescript": "^5.3.3" | ||
@@ -66,0 +71,0 @@ }, |
217
README.md
# Bitbucket MCP | ||
Model Context Protocol (MCP) server for Bitbucket Cloud and Server API integration. This package enables AI assistants to interact with Bitbucket repositories, pull requests, and other features through the MCP protocol. | ||
A Model Context Protocol (MCP) server for integrating with Bitbucket Cloud and Server APIs. This MCP server allows AI assistants to interact with Bitbucket repositories, pull requests, and other resources. | ||
## Features | ||
## Overview | ||
- **Bitbucket Cloud API Integration**: Access the full Bitbucket Cloud REST API | ||
- **Bitbucket Server Support**: Compatible with Bitbucket Server instances | ||
- **Repository Management**: List, view, and manage repositories | ||
- **Pull Request Operations**: Create, view, merge, and comment on PRs | ||
- **Permission Management**: Query and manage repository permissions | ||
- **Webhook Management**: List and manage webhooks | ||
This server implements the [Model Context Protocol](https://modelcontextprotocol.io/) standard to provide AI assistants with access to Bitbucket data and operations. It includes tools for: | ||
## Installation | ||
- Listing and retrieving repositories | ||
- Managing pull requests | ||
- Working with repository content | ||
```bash | ||
# Install globally | ||
npm install -g bitbucket-mcp | ||
## Getting Started | ||
# Or run directly with npx | ||
npx bitbucket-mcp | ||
``` | ||
### Prerequisites | ||
## Quick Start | ||
- Node.js 18 or higher | ||
- A Bitbucket Cloud account or Bitbucket Server instance | ||
- Optional: Bitbucket API token for authenticated requests | ||
1. Set up your environment variables: | ||
### Installation | ||
```bash | ||
# Required for all operations | ||
export BITBUCKET_URL="https://your-bitbucket-server-url" # For Server API | ||
export BITBUCKET_CLOUD_URL="https://api.bitbucket.org/2.0" # For Cloud API | ||
# Clone the repository | ||
git clone https://github.com/yourusername/bitbucket-mcp.git | ||
cd bitbucket-mcp | ||
# Authentication (choose one method) | ||
export BITBUCKET_TOKEN="your_access_token" | ||
# OR | ||
export BITBUCKET_USERNAME="your_username" | ||
export BITBUCKET_APP_PASSWORD="your_app_password" | ||
# Install dependencies | ||
npm install | ||
# Optional | ||
export BITBUCKET_DEFAULT_PROJECT="your_default_project" # For Server API | ||
# Build the project | ||
npm run build | ||
``` | ||
2. Run the MCP server: | ||
### Configuration | ||
Configure the server using environment variables: | ||
```bash | ||
npx bitbucket-mcp | ||
# Bitbucket API token (optional, but recommended) | ||
export BITBUCKET_TOKEN=your_token_here | ||
# For custom Bitbucket Server installations | ||
export BITBUCKET_API_URL=https://your-bitbucket-server.com/api | ||
``` | ||
## Supported API Operations | ||
### Running the Server | ||
### Bitbucket Cloud Repository API | ||
```bash | ||
# Start the server | ||
npm start | ||
- `list_public_repositories` - List all public repositories | ||
- `list_workspace_repositories` - List repositories in a workspace | ||
- `get_repository` - Get a specific repository | ||
- `list_repository_forks` - List forks of a repository | ||
- `list_repository_webhooks` - List webhooks for a repository | ||
- `get_repository_webhook` - Get a specific webhook | ||
- `get_repository_settings_inheritance` - Get repository settings inheritance | ||
- `list_repository_group_permissions` - List group permissions | ||
- `get_repository_group_permission` - Get specific group permission | ||
- `list_repository_user_permissions` - List user permissions | ||
- `get_repository_user_permission` - Get specific user permission | ||
- `list_repository_watchers` - List repository watchers | ||
- `list_user_repository_permissions` - List permissions for the current user | ||
# Or run in development mode | ||
npm run dev | ||
### Bitbucket Server Pull Request API | ||
# Use standalone mode for direct stdio communication | ||
npm run standalone | ||
``` | ||
- `create_pull_request` - Create a new pull request | ||
- `get_pull_request` - Get pull request details | ||
- `merge_pull_request` - Merge a pull request | ||
- `decline_pull_request` - Decline a pull request | ||
- `add_comment` - Add a comment to a pull request | ||
- `get_diff` - Get pull request diff | ||
- `get_reviews` - Get pull request reviews | ||
## Available Tools | ||
## Integration with AI Assistants | ||
This MCP server provides tools for interacting with Bitbucket repositories and pull requests. For a comprehensive list of all available tools with detailed documentation, please see [TOOLS.md](TOOLS.md). | ||
This MCP server is designed to be used with AI assistants that support the Model Context Protocol: | ||
Some of the available tools include: | ||
### Using with Claude | ||
- **listRepositories**: List repositories in a workspace | ||
- **getRepository**: Get details for a specific repository | ||
- **getPullRequests**: Get pull requests for a repository | ||
1. Install the Claude desktop app | ||
2. Add this MCP server to Claude: | ||
## Integration with MCP Clients | ||
```json | ||
{ | ||
"mcpServers": { | ||
"bitbucket": { | ||
"command": "npx", | ||
"args": ["-y", "bitbucket-mcp"], | ||
"env": { | ||
"BITBUCKET_URL": "https://your-bitbucket-server.com", | ||
"BITBUCKET_CLOUD_URL": "https://api.bitbucket.org/2.0", | ||
"BITBUCKET_TOKEN": "your-access-token" | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
This server can be integrated with any MCP client by connecting to the server endpoint: | ||
### Using with other MCP clients | ||
- HTTP endpoint: `http://localhost:3000/mcp` | ||
- When running in standalone mode, communication happens over stdio | ||
Any client that supports the MCP protocol can communicate with this server over standard input/output. | ||
## Development | ||
## API Examples | ||
### Project Structure | ||
### List repositories in a workspace | ||
```javascript | ||
// Request | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"method": "mcp.call_tool", | ||
"params": { | ||
"name": "list_workspace_repositories", | ||
"arguments": { | ||
"workspace": "your_workspace" | ||
} | ||
} | ||
} | ||
// Response | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 1, | ||
"result": { | ||
"content": [ | ||
{ | ||
"type": "text", | ||
"text": "{ \"values\": [...], \"pagelen\": 10, ... }" | ||
} | ||
] | ||
} | ||
} | ||
``` | ||
### Create a pull request | ||
```javascript | ||
// Request | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": 2, | ||
"method": "mcp.call_tool", | ||
"params": { | ||
"name": "create_pull_request", | ||
"arguments": { | ||
"project": "PROJECT", | ||
"repository": "repo-name", | ||
"title": "Feature: Add new functionality", | ||
"description": "This PR adds the new feature X", | ||
"sourceBranch": "feature/new-feature", | ||
"targetBranch": "main" | ||
} | ||
} | ||
} | ||
bitbucket-mcp/ | ||
├── src/ | ||
│ ├── api/ # API-related code | ||
│ ├── services/ # Service implementations | ||
│ ├── types/ # TypeScript type definitions | ||
│ ├── utils/ # Utility functions | ||
│ └── index.ts # Entry point | ||
├── package.json | ||
└── tsconfig.json | ||
``` | ||
## Development | ||
### Adding New Tools | ||
```bash | ||
# Clone the repository | ||
git clone https://github.com/yourusername/bitbucket-mcp.git | ||
cd bitbucket-mcp | ||
To add a new tool, modify the `src/index.ts` file and add your tool definition: | ||
# Install dependencies | ||
npm install | ||
# Build | ||
npm run build | ||
# Run locally | ||
npm start | ||
```typescript | ||
const newTool = server.tool( | ||
"newToolName", | ||
{ | ||
param1: z.string(), | ||
param2: z.number().optional(), | ||
}, | ||
async ({ param1, param2 }) => { | ||
// Implementation goes here | ||
return { | ||
content: [{ type: "text", text: "Result" }], | ||
}; | ||
} | ||
); | ||
``` | ||
## Logging | ||
The server logs all operations to `bitbucket.log` using Winston for debugging and monitoring purposes. | ||
## License | ||
MIT | ||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
15
150%0
-100%535
15.55%34328
-31.14%5
66.67%10
25%116
-37.3%6
20%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added