This commit is contained in:
2026-05-19 16:45:59 +02:00
parent 29496cb944
commit 992f25a9a2
16 changed files with 6222 additions and 116 deletions

347
src/index.ts Normal file
View File

@@ -0,0 +1,347 @@
#!/usr/bin/env node
/**
* Bitbucket Pull Request MCP Server
*
* Configuration:
* 1. Export BITBUCKET_MCP_TOKEN=<token> (environment variable)
* 2. Add to .env file in project root
* 3. Interactive prompt (development fallback)
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
ListToolsRequestSchema,
CallToolRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { BitbucketRouter } from './router.js';
const router = new BitbucketRouter();
/**
* MCP Server implementation for Bitbucket Pull Requests.
*/
class BitbucketMCPServer {
private server: Server;
constructor() {
this.server = new Server({
name: 'bitbucket-pullrequests',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
// Handle requests
this.setupRequestHandlers();
}
/**
* Setup MCP request handlers.
*/
private setupRequestHandlers(): void {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'validate_token',
description: 'Validate the Bitbucket authentication token',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'list_pull_requests',
description: 'List pull requests in a Bitbucket repository',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
state: { type: 'string', enum: ['open', 'closed', 'all'] },
author: { type: 'string' },
},
},
},
{
name: 'get_pull_request',
description: 'Get details of a specific pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
},
},
},
{
name: 'get_pull_request_activities',
description: 'Get activities/events for a pull request (opens, closes, comments, reviews, etc.)',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
limit: { type: 'integer', description: 'Max results per page (default 25)' },
start: { type: 'integer', description: 'Pagination start index' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_pull_request_changes',
description: 'Get files changed in a pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
limit: { type: 'integer', description: 'Max results per page (default 25)' },
start: { type: 'integer', description: 'Pagination start index' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_pull_request_comments',
description: 'Get all comments on a pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
limit: { type: 'integer', description: 'Max results per page (default 25)' },
start: { type: 'integer', description: 'Pagination start index' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_pull_request_comment',
description: 'Get a specific comment on a pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
commentId: { type: 'integer', description: 'The comment ID' },
},
required: ['workspace', 'repository', 'pullRequestId', 'commentId'],
},
},
{
name: 'get_pull_request_commits',
description: 'Get commits included in a pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
limit: { type: 'integer', description: 'Max results per page (default 25)' },
start: { type: 'integer', description: 'Pagination start index' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_pull_request_diff',
description: 'Get the diff of a pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
path: { type: 'string', description: 'Specific file path to get diff for' },
context: { type: 'integer', description: 'Number of context lines around changes' },
whitespace: { type: 'string', enum: ['ignore-all', 'ignore-changing', 'ignore-eol', 'show-all'] },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_pull_request_patch',
description: 'Get the raw patch file for a pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_pull_request_participants',
description: 'Get participants of a pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_pull_request_reviewers',
description: 'Get reviewers of a pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_pull_request_status',
description: 'Get status/state of a pull request (open, merged, declined)',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_pull_request_tasks',
description: 'Get tasks associated with a pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_pull_request_task_count',
description: 'Get count of tasks on a pull request',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
{
name: 'get_full_pull_request',
description: 'Get full detailed information about a pull request with all fields expanded',
inputSchema: {
type: 'object',
properties: {
workspace: { type: 'string' },
repository: { type: 'string' },
pullRequestId: { type: 'integer' },
},
required: ['workspace', 'repository', 'pullRequestId'],
},
},
],
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: params } = request.params;
try {
console.log(`Executing tool: ${name}`);
console.log('Parameters:', JSON.stringify(params));
const result = await router.executeTool(name, params as any);
if (!result.success) {
throw new Error(result.error || 'Tool execution failed');
}
return {
content: [
{
type: 'text',
text: JSON.stringify(result.data, null, 2),
},
],
};
} catch (error) {
console.error(`Tool error (${name}):`, error);
throw {
name: 'ToolExecutionError',
message: typeof error === 'string' ? error : (error as Error).message || 'Unknown error',
};
}
});
}
/**
* Start the MCP server.
*/
async start(): Promise<void> {
try {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.log('✅ Bitbucket MCP Server started');
console.log(' Name: bitbucket-pullrequests v1.0.0');
// Log token source for debugging
if (process.env.BITBUCKET_MCP_TOKEN) {
console.log(' Token source: environment variable');
} else {
console.log(' Token source: .env file or interactive prompt');
}
// Validate token on startup
try {
console.log('🔍 Validating Bitbucket token...');
const router = new BitbucketRouter();
const validation = await router.executeTool('validate_token', {});
if (validation.success) {
console.log('✅ Token validation successful');
} else {
console.error('❌ Token validation failed:', validation.error);
console.error(' Please check your BITBUCKET_MCP_TOKEN configuration');
console.error(' Required permissions: Repository read access');
}
} catch (error) {
console.error('❌ Token validation error:', error);
}
} catch (error) {
console.error('❌ Server startup failed:', error);
process.exit(1);
}
}
}
// Entry point
const server = new BitbucketMCPServer();
server.start().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});