@zhanglc77/bitbucket-mcp-server
Version:
MCP server for Bitbucket API integration - supports both Cloud and Server
149 lines • 7.56 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
// TODO: Read version from package.json when not in test environment
const version = '1.0.12';
import { BitbucketApiClient } from './utils/api-client.js';
import { PullRequestHandlers } from './handlers/pull-request-handlers.js';
import { BranchHandlers } from './handlers/branch-handlers.js';
import { ReviewHandlers } from './handlers/review-handlers.js';
import { FileHandlers } from './handlers/file-handlers.js';
import { SearchHandlers } from './handlers/search-handlers.js';
import { ResourceHandlers } from './handlers/resource-handlers.js';
import { toolDefinitions } from './tools/definitions.js';
// Get environment variables
const BITBUCKET_USERNAME = process.env.BITBUCKET_USERNAME;
const BITBUCKET_APP_PASSWORD = process.env.BITBUCKET_APP_PASSWORD;
const BITBUCKET_TOKEN = process.env.BITBUCKET_TOKEN; // For Bitbucket Server
const BITBUCKET_BASE_URL = process.env.BITBUCKET_BASE_URL || 'https://api.bitbucket.org/2.0';
// Check for either app password (Cloud) or token (Server)
if (!BITBUCKET_USERNAME || (!BITBUCKET_APP_PASSWORD && !BITBUCKET_TOKEN)) {
console.error('Error: BITBUCKET_USERNAME and either BITBUCKET_APP_PASSWORD (for Cloud) or BITBUCKET_TOKEN (for Server) are required');
console.error('Please set these in your MCP settings configuration');
process.exit(1);
}
export class BitbucketMCPServer {
server;
apiClient;
pullRequestHandlers;
branchHandlers;
reviewHandlers;
fileHandlers;
searchHandlers;
resourceHandlers;
constructor() {
this.server = new Server({
name: 'bitbucket-mcp-server',
version: version,
}, {
capabilities: {
tools: {},
resources: {},
},
});
// Initialize API client
this.apiClient = new BitbucketApiClient(BITBUCKET_BASE_URL, BITBUCKET_USERNAME, BITBUCKET_APP_PASSWORD, BITBUCKET_TOKEN);
// Initialize handlers
this.pullRequestHandlers = new PullRequestHandlers(this.apiClient, BITBUCKET_BASE_URL, BITBUCKET_USERNAME);
this.branchHandlers = new BranchHandlers(this.apiClient, BITBUCKET_BASE_URL);
this.reviewHandlers = new ReviewHandlers(this.apiClient, BITBUCKET_USERNAME);
this.fileHandlers = new FileHandlers(this.apiClient, BITBUCKET_BASE_URL);
this.searchHandlers = new SearchHandlers(this.apiClient, BITBUCKET_BASE_URL);
this.resourceHandlers = new ResourceHandlers();
this.setupToolHandlers();
this.setupResourceHandlers();
// Error handling
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
setupToolHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: toolDefinitions,
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
// Pull Request tools
case 'get_pull_request':
return this.pullRequestHandlers.handleGetPullRequest(request.params.arguments);
case 'list_pull_requests':
return this.pullRequestHandlers.handleListPullRequests(request.params.arguments);
case 'create_pull_request':
return this.pullRequestHandlers.handleCreatePullRequest(request.params.arguments);
case 'update_pull_request':
return this.pullRequestHandlers.handleUpdatePullRequest(request.params.arguments);
case 'add_comment':
return this.pullRequestHandlers.handleAddComment(request.params.arguments);
case 'merge_pull_request':
return this.pullRequestHandlers.handleMergePullRequest(request.params.arguments);
case 'list_pr_commits':
return this.pullRequestHandlers.handleListPrCommits(request.params.arguments);
// Branch tools
case 'list_branches':
return this.branchHandlers.handleListBranches(request.params.arguments);
case 'delete_branch':
return this.branchHandlers.handleDeleteBranch(request.params.arguments);
case 'get_branch':
return this.branchHandlers.handleGetBranch(request.params.arguments);
case 'list_branch_commits':
return this.branchHandlers.handleListBranchCommits(request.params.arguments);
// Code Review tools
case 'get_pull_request_diff':
return this.reviewHandlers.handleGetPullRequestDiff(request.params.arguments);
case 'approve_pull_request':
return this.reviewHandlers.handleApprovePullRequest(request.params.arguments);
case 'unapprove_pull_request':
return this.reviewHandlers.handleUnapprovePullRequest(request.params.arguments);
case 'request_changes':
return this.reviewHandlers.handleRequestChanges(request.params.arguments);
case 'remove_requested_changes':
return this.reviewHandlers.handleRemoveRequestedChanges(request.params.arguments);
// File tools
case 'list_directory_content':
return this.fileHandlers.handleListDirectoryContent(request.params.arguments);
case 'get_file_content':
return this.fileHandlers.handleGetFileContent(request.params.arguments);
// Search tools
case 'search_code':
return this.searchHandlers.handleSearchCode(request.params.arguments);
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
}
});
}
setupResourceHandlers() {
// List available resources and templates
this.server.setRequestHandler(ListResourcesRequestSchema, async () => this.resourceHandlers.listResources());
// Read resource content
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => this.resourceHandlers.readResource(request.params.uri));
}
// Public methods for testing
getCapabilities() {
return {
tools: {},
resources: {}
};
}
async listTools() {
return { tools: toolDefinitions };
}
async listResources() {
return this.resourceHandlers.listResources();
}
async readResource(uri) {
return this.resourceHandlers.readResource(uri);
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error(`Bitbucket MCP server running on stdio (${this.apiClient.getIsServer() ? 'Server' : 'Cloud'} mode)`);
}
}
const server = new BitbucketMCPServer();
server.run().catch(console.error);
//# sourceMappingURL=index.js.map