feat: add list_branch_commits and list_pr_commits tools
- Add list_branch_commits tool with advanced filtering options: - Filter by date range (since/until) - Filter by author email/username - Include/exclude merge commits - Search in commit messages - Pagination support - Add list_pr_commits tool to list all commits in a pull request - Returns PR title along with commit list - Pagination support - Detailed commit information - Fix author filtering for Bitbucket Server with client-side filtering - Add TypeScript interfaces for commit types - Add formatter functions for consistent commit representation - Update documentation with comprehensive usage examples - Bump version to 0.10.0
This commit is contained in:
parent
e21f2dcfe8
commit
e5da36e515
10 changed files with 732 additions and 8 deletions
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.10.0] - 2025-07-03
|
||||
|
||||
### Added
|
||||
- **New `list_branch_commits` tool for retrieving commit history**:
|
||||
- List all commits in a specific branch with detailed information
|
||||
- Advanced filtering options:
|
||||
- `since` and `until` parameters for date range filtering (ISO date strings)
|
||||
- `author` parameter to filter by author email/username
|
||||
- `include_merge_commits` parameter to include/exclude merge commits (default: true)
|
||||
- `search` parameter to search in commit messages
|
||||
- Returns branch head information and paginated commit list
|
||||
- Each commit includes hash, message, author details, date, parents, and merge status
|
||||
- Supports both Bitbucket Server and Cloud APIs with appropriate parameter mapping
|
||||
- Useful for reviewing commit history, tracking changes, and analyzing branch activity
|
||||
|
||||
- **New `list_pr_commits` tool for pull request commits**:
|
||||
- List all commits that are part of a specific pull request
|
||||
- Returns PR title and paginated commit list
|
||||
- Simpler than branch commits - focused specifically on PR changes
|
||||
- Each commit includes same detailed information as branch commits
|
||||
- Supports pagination with `limit` and `start` parameters
|
||||
- Useful for reviewing all changes in a PR before merging
|
||||
|
||||
### Changed
|
||||
- Added new TypeScript interfaces for commit types:
|
||||
- `BitbucketServerCommit` and `BitbucketCloudCommit` for API responses
|
||||
- `FormattedCommit` for consistent commit representation
|
||||
- Added formatter functions `formatServerCommit` and `formatCloudCommit` for unified output
|
||||
- Enhanced type guards with `isListBranchCommitsArgs` and `isListPrCommitsArgs`
|
||||
|
||||
## [0.9.1] - 2025-01-27
|
||||
|
||||
### Fixed
|
||||
|
|
180
README.md
180
README.md
|
@ -16,12 +16,14 @@ An MCP (Model Context Protocol) server that provides tools for interacting with
|
|||
- `update_pull_request` - Update PR details (title, description, reviewers, destination branch)
|
||||
- `add_comment` - Add comments to pull requests (supports replies)
|
||||
- `merge_pull_request` - Merge pull requests with various strategies
|
||||
- `list_pr_commits` - List all commits that are part of a pull request
|
||||
- `delete_branch` - Delete branches after merge
|
||||
|
||||
#### Branch Management Tools
|
||||
- `list_branches` - List branches with filtering and pagination
|
||||
- `delete_branch` - Delete branches (with protection checks)
|
||||
- `get_branch` - Get detailed branch information including associated PRs
|
||||
- `list_branch_commits` - List commits in a branch with advanced filtering
|
||||
|
||||
#### File and Directory Tools
|
||||
- `list_directory_content` - List files and directories in a repository path
|
||||
|
@ -596,6 +598,184 @@ This tool is particularly useful for:
|
|||
- Understanding PR review status
|
||||
- Identifying stale branches
|
||||
|
||||
### List Branch Commits
|
||||
|
||||
Get all commits in a specific branch with advanced filtering options:
|
||||
|
||||
```typescript
|
||||
// Basic usage - get recent commits
|
||||
{
|
||||
"tool": "list_branch_commits",
|
||||
"arguments": {
|
||||
"workspace": "PROJ",
|
||||
"repository": "my-repo",
|
||||
"branch_name": "feature/new-feature",
|
||||
"limit": 50 // Optional (default: 25)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by date range
|
||||
{
|
||||
"tool": "list_branch_commits",
|
||||
"arguments": {
|
||||
"workspace": "PROJ",
|
||||
"repository": "my-repo",
|
||||
"branch_name": "main",
|
||||
"since": "2025-01-01T00:00:00Z", // ISO date string
|
||||
"until": "2025-01-15T23:59:59Z" // ISO date string
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by author
|
||||
{
|
||||
"tool": "list_branch_commits",
|
||||
"arguments": {
|
||||
"workspace": "PROJ",
|
||||
"repository": "my-repo",
|
||||
"branch_name": "develop",
|
||||
"author": "john.doe@company.com", // Email or username
|
||||
"limit": 100
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude merge commits
|
||||
{
|
||||
"tool": "list_branch_commits",
|
||||
"arguments": {
|
||||
"workspace": "PROJ",
|
||||
"repository": "my-repo",
|
||||
"branch_name": "release/v2.0",
|
||||
"include_merge_commits": false
|
||||
}
|
||||
}
|
||||
|
||||
// Search in commit messages
|
||||
{
|
||||
"tool": "list_branch_commits",
|
||||
"arguments": {
|
||||
"workspace": "PROJ",
|
||||
"repository": "my-repo",
|
||||
"branch_name": "main",
|
||||
"search": "bugfix", // Search in commit messages
|
||||
"limit": 50
|
||||
}
|
||||
}
|
||||
|
||||
// Combine multiple filters
|
||||
{
|
||||
"tool": "list_branch_commits",
|
||||
"arguments": {
|
||||
"workspace": "PROJ",
|
||||
"repository": "my-repo",
|
||||
"branch_name": "develop",
|
||||
"author": "jane.smith@company.com",
|
||||
"since": "2025-01-01T00:00:00Z",
|
||||
"include_merge_commits": false,
|
||||
"search": "feature",
|
||||
"limit": 100,
|
||||
"start": 0 // For pagination
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Filter Parameters:**
|
||||
- `since`: ISO date string - only show commits after this date
|
||||
- `until`: ISO date string - only show commits before this date
|
||||
- `author`: Filter by author email/username
|
||||
- `include_merge_commits`: Boolean to include/exclude merge commits (default: true)
|
||||
- `search`: Search for text in commit messages
|
||||
|
||||
Returns detailed commit information:
|
||||
```json
|
||||
{
|
||||
"branch_name": "feature/new-feature",
|
||||
"branch_head": "abc123def456", // Latest commit hash
|
||||
"commits": [
|
||||
{
|
||||
"hash": "abc123def456",
|
||||
"abbreviated_hash": "abc123d",
|
||||
"message": "Add new feature implementation",
|
||||
"author": {
|
||||
"name": "John Doe",
|
||||
"email": "john.doe@example.com"
|
||||
},
|
||||
"date": "2025-01-03T10:30:00Z",
|
||||
"parents": ["parent1hash", "parent2hash"],
|
||||
"is_merge_commit": false
|
||||
}
|
||||
// ... more commits
|
||||
],
|
||||
"total_count": 150,
|
||||
"start": 0,
|
||||
"limit": 25,
|
||||
"has_more": true,
|
||||
"next_start": 25,
|
||||
"filters_applied": {
|
||||
"author": "john.doe@example.com",
|
||||
"since": "2025-01-01",
|
||||
"include_merge_commits": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This tool is particularly useful for:
|
||||
- Reviewing commit history before releases
|
||||
- Finding commits by specific authors
|
||||
- Tracking changes within date ranges
|
||||
- Searching for specific features or fixes
|
||||
- Analyzing branch activity patterns
|
||||
|
||||
### List PR Commits
|
||||
|
||||
Get all commits that are part of a pull request:
|
||||
|
||||
```typescript
|
||||
{
|
||||
"tool": "list_pr_commits",
|
||||
"arguments": {
|
||||
"workspace": "PROJ",
|
||||
"repository": "my-repo",
|
||||
"pull_request_id": 123,
|
||||
"limit": 50, // Optional (default: 25)
|
||||
"start": 0 // Optional: for pagination
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Returns commit information for the PR:
|
||||
```json
|
||||
{
|
||||
"pull_request_id": 123,
|
||||
"pull_request_title": "Add awesome feature",
|
||||
"commits": [
|
||||
{
|
||||
"hash": "def456ghi789",
|
||||
"abbreviated_hash": "def456g",
|
||||
"message": "Initial implementation",
|
||||
"author": {
|
||||
"name": "Jane Smith",
|
||||
"email": "jane.smith@example.com"
|
||||
},
|
||||
"date": "2025-01-02T14:20:00Z",
|
||||
"parents": ["parent1hash"],
|
||||
"is_merge_commit": false
|
||||
}
|
||||
// ... more commits
|
||||
],
|
||||
"total_count": 5,
|
||||
"start": 0,
|
||||
"limit": 25,
|
||||
"has_more": false
|
||||
}
|
||||
```
|
||||
|
||||
This tool is particularly useful for:
|
||||
- Reviewing all changes in a PR before merging
|
||||
- Understanding the development history of a PR
|
||||
- Checking commit messages for quality
|
||||
- Verifying authorship of changes
|
||||
- Analyzing PR complexity by commit count
|
||||
|
||||
### Get Pull Request Diff
|
||||
|
||||
Get the diff/changes for a pull request with optional filtering capabilities:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@nexus2520/bitbucket-mcp-server",
|
||||
"version": "0.9.1",
|
||||
"version": "0.10.0",
|
||||
"description": "MCP server for Bitbucket API integration - supports both Cloud and Server",
|
||||
"type": "module",
|
||||
"main": "./build/index.js",
|
||||
|
|
|
@ -3,9 +3,17 @@ import { BitbucketApiClient } from '../utils/api-client.js';
|
|||
import {
|
||||
isListBranchesArgs,
|
||||
isDeleteBranchArgs,
|
||||
isGetBranchArgs
|
||||
isGetBranchArgs,
|
||||
isListBranchCommitsArgs
|
||||
} from '../types/guards.js';
|
||||
import { BitbucketServerBranch, BitbucketCloudBranch } from '../types/bitbucket.js';
|
||||
import {
|
||||
BitbucketServerBranch,
|
||||
BitbucketCloudBranch,
|
||||
BitbucketServerCommit,
|
||||
BitbucketCloudCommit,
|
||||
FormattedCommit
|
||||
} from '../types/bitbucket.js';
|
||||
import { formatServerCommit, formatCloudCommit } from '../utils/formatters.js';
|
||||
|
||||
export class BranchHandlers {
|
||||
constructor(
|
||||
|
@ -392,4 +400,188 @@ export class BranchHandlers {
|
|||
return this.apiClient.handleApiError(error, `getting branch '${branch_name}' in ${workspace}/${repository}`);
|
||||
}
|
||||
}
|
||||
|
||||
async handleListBranchCommits(args: any) {
|
||||
if (!isListBranchCommitsArgs(args)) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
'Invalid arguments for list_branch_commits'
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
workspace,
|
||||
repository,
|
||||
branch_name,
|
||||
limit = 25,
|
||||
start = 0,
|
||||
since,
|
||||
until,
|
||||
author,
|
||||
include_merge_commits = true,
|
||||
search
|
||||
} = args;
|
||||
|
||||
try {
|
||||
let apiPath: string;
|
||||
let params: any = {};
|
||||
let commits: FormattedCommit[] = [];
|
||||
let totalCount = 0;
|
||||
let nextPageStart: number | null = null;
|
||||
|
||||
if (this.apiClient.getIsServer()) {
|
||||
// Bitbucket Server API
|
||||
apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/commits`;
|
||||
params = {
|
||||
until: `refs/heads/${branch_name}`,
|
||||
limit,
|
||||
start,
|
||||
withCounts: true
|
||||
};
|
||||
|
||||
// Add filters
|
||||
if (since) {
|
||||
params.since = since;
|
||||
}
|
||||
if (!include_merge_commits) {
|
||||
params.merges = 'exclude';
|
||||
}
|
||||
|
||||
const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });
|
||||
|
||||
// Format commits
|
||||
commits = (response.values || []).map((commit: BitbucketServerCommit) => formatServerCommit(commit));
|
||||
|
||||
// Apply client-side filters for Server API
|
||||
if (author) {
|
||||
// Filter by author email or name
|
||||
commits = commits.filter(c =>
|
||||
c.author.email === author ||
|
||||
c.author.name === author ||
|
||||
c.author.email.toLowerCase() === author.toLowerCase() ||
|
||||
c.author.name.toLowerCase() === author.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by date if 'until' is provided (Server API doesn't support 'until' param directly)
|
||||
if (until) {
|
||||
const untilDate = new Date(until).getTime();
|
||||
commits = commits.filter(c => new Date(c.date).getTime() <= untilDate);
|
||||
}
|
||||
|
||||
// Filter by message search if provided
|
||||
if (search) {
|
||||
const searchLower = search.toLowerCase();
|
||||
commits = commits.filter(c => c.message.toLowerCase().includes(searchLower));
|
||||
}
|
||||
|
||||
// If we applied client-side filters, update the total count
|
||||
if (author || until || search) {
|
||||
totalCount = commits.length;
|
||||
// Can't determine if there are more results when filtering client-side
|
||||
nextPageStart = null;
|
||||
} else {
|
||||
totalCount = response.size || commits.length;
|
||||
if (!response.isLastPage && response.nextPageStart !== undefined) {
|
||||
nextPageStart = response.nextPageStart;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Bitbucket Cloud API
|
||||
apiPath = `/repositories/${workspace}/${repository}/commits/${encodeURIComponent(branch_name)}`;
|
||||
params = {
|
||||
pagelen: limit,
|
||||
page: Math.floor(start / limit) + 1
|
||||
};
|
||||
|
||||
// Build query string for filters
|
||||
const queryParts: string[] = [];
|
||||
if (author) {
|
||||
queryParts.push(`author.raw ~ "${author}"`);
|
||||
}
|
||||
if (!include_merge_commits) {
|
||||
// Cloud API doesn't have direct merge exclusion, we'll filter client-side
|
||||
}
|
||||
if (queryParts.length > 0) {
|
||||
params.q = queryParts.join(' AND ');
|
||||
}
|
||||
|
||||
const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });
|
||||
|
||||
// Format commits
|
||||
let cloudCommits = (response.values || []).map((commit: BitbucketCloudCommit) => formatCloudCommit(commit));
|
||||
|
||||
// Apply client-side filters
|
||||
if (!include_merge_commits) {
|
||||
cloudCommits = cloudCommits.filter((c: FormattedCommit) => !c.is_merge_commit);
|
||||
}
|
||||
if (since) {
|
||||
const sinceDate = new Date(since).getTime();
|
||||
cloudCommits = cloudCommits.filter((c: FormattedCommit) => new Date(c.date).getTime() >= sinceDate);
|
||||
}
|
||||
if (until) {
|
||||
const untilDate = new Date(until).getTime();
|
||||
cloudCommits = cloudCommits.filter((c: FormattedCommit) => new Date(c.date).getTime() <= untilDate);
|
||||
}
|
||||
if (search) {
|
||||
const searchLower = search.toLowerCase();
|
||||
cloudCommits = cloudCommits.filter((c: FormattedCommit) => c.message.toLowerCase().includes(searchLower));
|
||||
}
|
||||
|
||||
commits = cloudCommits;
|
||||
totalCount = response.size || commits.length;
|
||||
if (response.next) {
|
||||
nextPageStart = start + limit;
|
||||
}
|
||||
}
|
||||
|
||||
// Get branch head info
|
||||
let branchHead: string | null = null;
|
||||
try {
|
||||
if (this.apiClient.getIsServer()) {
|
||||
const branchesPath = `/rest/api/latest/projects/${workspace}/repos/${repository}/branches`;
|
||||
const branchesResponse = await this.apiClient.makeRequest<any>('get', branchesPath, undefined, {
|
||||
params: { filterText: branch_name, limit: 1 }
|
||||
});
|
||||
const branch = branchesResponse.values?.find((b: any) => b.displayId === branch_name);
|
||||
branchHead = branch?.latestCommit || null;
|
||||
} else {
|
||||
const branchPath = `/repositories/${workspace}/${repository}/refs/branches/${encodeURIComponent(branch_name)}`;
|
||||
const branch = await this.apiClient.makeRequest<any>('get', branchPath);
|
||||
branchHead = branch.target?.hash || null;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore error, branch head is optional
|
||||
}
|
||||
|
||||
// Build filters applied summary
|
||||
const filtersApplied: any = {};
|
||||
if (author) filtersApplied.author = author;
|
||||
if (since) filtersApplied.since = since;
|
||||
if (until) filtersApplied.until = until;
|
||||
if (include_merge_commits !== undefined) filtersApplied.include_merge_commits = include_merge_commits;
|
||||
if (search) filtersApplied.search = search;
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
branch_name,
|
||||
branch_head: branchHead,
|
||||
commits,
|
||||
total_count: totalCount,
|
||||
start,
|
||||
limit,
|
||||
has_more: nextPageStart !== null,
|
||||
next_start: nextPageStart,
|
||||
filters_applied: filtersApplied
|
||||
}, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return this.apiClient.handleApiError(error, `listing commits for branch '${branch_name}' in ${workspace}/${repository}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { BitbucketApiClient } from '../utils/api-client.js';
|
||||
import { formatServerResponse, formatCloudResponse } from '../utils/formatters.js';
|
||||
import { formatServerResponse, formatCloudResponse, formatServerCommit, formatCloudCommit } from '../utils/formatters.js';
|
||||
import { formatSuggestionComment } from '../utils/suggestion-formatter.js';
|
||||
import { DiffParser } from '../utils/diff-parser.js';
|
||||
import {
|
||||
|
@ -13,7 +13,10 @@ import {
|
|||
FormattedComment,
|
||||
FormattedFileChange,
|
||||
CodeMatch,
|
||||
MultipleMatchesError
|
||||
MultipleMatchesError,
|
||||
BitbucketServerCommit,
|
||||
BitbucketCloudCommit,
|
||||
FormattedCommit
|
||||
} from '../types/bitbucket.js';
|
||||
import {
|
||||
isGetPullRequestArgs,
|
||||
|
@ -21,7 +24,8 @@ import {
|
|||
isCreatePullRequestArgs,
|
||||
isUpdatePullRequestArgs,
|
||||
isAddCommentArgs,
|
||||
isMergePullRequestArgs
|
||||
isMergePullRequestArgs,
|
||||
isListPrCommitsArgs
|
||||
} from '../types/guards.js';
|
||||
|
||||
export class PullRequestHandlers {
|
||||
|
@ -1113,4 +1117,93 @@ export class PullRequestHandlers {
|
|||
private selectBestMatch(matches: CodeMatch[]): CodeMatch {
|
||||
return matches.sort((a, b) => b.confidence - a.confidence)[0];
|
||||
}
|
||||
|
||||
async handleListPrCommits(args: any) {
|
||||
if (!isListPrCommitsArgs(args)) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
'Invalid arguments for list_pr_commits'
|
||||
);
|
||||
}
|
||||
|
||||
const { workspace, repository, pull_request_id, limit = 25, start = 0 } = args;
|
||||
|
||||
try {
|
||||
// First get the PR details to include in response
|
||||
const prPath = this.apiClient.getIsServer()
|
||||
? `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}`
|
||||
: `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}`;
|
||||
|
||||
let prTitle = '';
|
||||
try {
|
||||
const pr = await this.apiClient.makeRequest<any>('get', prPath);
|
||||
prTitle = pr.title;
|
||||
} catch (e) {
|
||||
// Ignore error, PR title is optional
|
||||
}
|
||||
|
||||
let apiPath: string;
|
||||
let params: any = {};
|
||||
let commits: FormattedCommit[] = [];
|
||||
let totalCount = 0;
|
||||
let nextPageStart: number | null = null;
|
||||
|
||||
if (this.apiClient.getIsServer()) {
|
||||
// Bitbucket Server API
|
||||
apiPath = `/rest/api/1.0/projects/${workspace}/repos/${repository}/pull-requests/${pull_request_id}/commits`;
|
||||
params = {
|
||||
limit,
|
||||
start,
|
||||
withCounts: true
|
||||
};
|
||||
|
||||
const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });
|
||||
|
||||
// Format commits
|
||||
commits = (response.values || []).map((commit: BitbucketServerCommit) => formatServerCommit(commit));
|
||||
|
||||
totalCount = response.size || commits.length;
|
||||
if (!response.isLastPage && response.nextPageStart !== undefined) {
|
||||
nextPageStart = response.nextPageStart;
|
||||
}
|
||||
} else {
|
||||
// Bitbucket Cloud API
|
||||
apiPath = `/repositories/${workspace}/${repository}/pullrequests/${pull_request_id}/commits`;
|
||||
params = {
|
||||
pagelen: limit,
|
||||
page: Math.floor(start / limit) + 1
|
||||
};
|
||||
|
||||
const response = await this.apiClient.makeRequest<any>('get', apiPath, undefined, { params });
|
||||
|
||||
// Format commits
|
||||
commits = (response.values || []).map((commit: BitbucketCloudCommit) => formatCloudCommit(commit));
|
||||
|
||||
totalCount = response.size || commits.length;
|
||||
if (response.next) {
|
||||
nextPageStart = start + limit;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: JSON.stringify({
|
||||
pull_request_id,
|
||||
pull_request_title: prTitle,
|
||||
commits,
|
||||
total_count: totalCount,
|
||||
start,
|
||||
limit,
|
||||
has_more: nextPageStart !== null,
|
||||
next_start: nextPageStart
|
||||
}, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return this.apiClient.handleApiError(error, `listing commits for pull request ${pull_request_id} in ${workspace}/${repository}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class BitbucketMCPServer {
|
|||
this.server = new Server(
|
||||
{
|
||||
name: 'bitbucket-mcp-server',
|
||||
version: '0.9.1',
|
||||
version: '0.10.0',
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
|
@ -99,6 +99,8 @@ class BitbucketMCPServer {
|
|||
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':
|
||||
|
@ -107,6 +109,8 @@ class BitbucketMCPServer {
|
|||
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':
|
||||
|
|
|
@ -527,4 +527,84 @@ export const toolDefinitions = [
|
|||
required: ['workspace', 'repository', 'file_path'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'list_branch_commits',
|
||||
description: 'List commits in a branch with detailed information and filtering options',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
workspace: {
|
||||
type: 'string',
|
||||
description: 'Bitbucket workspace/project key (e.g., "PROJ")',
|
||||
},
|
||||
repository: {
|
||||
type: 'string',
|
||||
description: 'Repository slug (e.g., "my-repo")',
|
||||
},
|
||||
branch_name: {
|
||||
type: 'string',
|
||||
description: 'Branch name to get commits from',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Maximum number of commits to return (default: 25)',
|
||||
},
|
||||
start: {
|
||||
type: 'number',
|
||||
description: 'Start index for pagination (default: 0)',
|
||||
},
|
||||
since: {
|
||||
type: 'string',
|
||||
description: 'ISO date string - only show commits after this date (optional)',
|
||||
},
|
||||
until: {
|
||||
type: 'string',
|
||||
description: 'ISO date string - only show commits before this date (optional)',
|
||||
},
|
||||
author: {
|
||||
type: 'string',
|
||||
description: 'Filter by author email/username (optional)',
|
||||
},
|
||||
include_merge_commits: {
|
||||
type: 'boolean',
|
||||
description: 'Include merge commits in results (default: true)',
|
||||
},
|
||||
search: {
|
||||
type: 'string',
|
||||
description: 'Search for text in commit messages (optional)',
|
||||
},
|
||||
},
|
||||
required: ['workspace', 'repository', 'branch_name'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'list_pr_commits',
|
||||
description: 'List all commits that are part of a pull request',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
workspace: {
|
||||
type: 'string',
|
||||
description: 'Bitbucket workspace/project key (e.g., "PROJ")',
|
||||
},
|
||||
repository: {
|
||||
type: 'string',
|
||||
description: 'Repository slug (e.g., "my-repo")',
|
||||
},
|
||||
pull_request_id: {
|
||||
type: 'number',
|
||||
description: 'Pull request ID',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Maximum number of commits to return (default: 25)',
|
||||
},
|
||||
start: {
|
||||
type: 'number',
|
||||
description: 'Start index for pagination (default: 0)',
|
||||
},
|
||||
},
|
||||
required: ['workspace', 'repository', 'pull_request_id'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -384,3 +384,62 @@ export interface MultipleMatchesError {
|
|||
}>;
|
||||
suggestion: string;
|
||||
}
|
||||
|
||||
// Commit types
|
||||
export interface BitbucketServerCommit {
|
||||
id: string;
|
||||
displayId: string;
|
||||
message: string;
|
||||
author: {
|
||||
name: string;
|
||||
emailAddress: string;
|
||||
};
|
||||
authorTimestamp: number;
|
||||
committer?: {
|
||||
name: string;
|
||||
emailAddress: string;
|
||||
};
|
||||
committerTimestamp?: number;
|
||||
parents: Array<{
|
||||
id: string;
|
||||
displayId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface BitbucketCloudCommit {
|
||||
hash: string;
|
||||
message: string;
|
||||
author: {
|
||||
raw: string;
|
||||
user?: {
|
||||
display_name: string;
|
||||
account_id: string;
|
||||
};
|
||||
};
|
||||
date: string;
|
||||
parents: Array<{
|
||||
hash: string;
|
||||
type: string;
|
||||
}>;
|
||||
links?: {
|
||||
self: {
|
||||
href: string;
|
||||
};
|
||||
html: {
|
||||
href: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface FormattedCommit {
|
||||
hash: string;
|
||||
abbreviated_hash: string;
|
||||
message: string;
|
||||
author: {
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
date: string;
|
||||
parents: string[];
|
||||
is_merge_commit: boolean;
|
||||
}
|
||||
|
|
|
@ -261,3 +261,47 @@ export const isGetFileContentArgs = (
|
|||
(args.start_line === undefined || typeof args.start_line === 'number') &&
|
||||
(args.line_count === undefined || typeof args.line_count === 'number') &&
|
||||
(args.full_content === undefined || typeof args.full_content === 'boolean');
|
||||
|
||||
export const isListBranchCommitsArgs = (
|
||||
args: any
|
||||
): args is {
|
||||
workspace: string;
|
||||
repository: string;
|
||||
branch_name: string;
|
||||
limit?: number;
|
||||
start?: number;
|
||||
since?: string;
|
||||
until?: string;
|
||||
author?: string;
|
||||
include_merge_commits?: boolean;
|
||||
search?: string;
|
||||
} =>
|
||||
typeof args === 'object' &&
|
||||
args !== null &&
|
||||
typeof args.workspace === 'string' &&
|
||||
typeof args.repository === 'string' &&
|
||||
typeof args.branch_name === 'string' &&
|
||||
(args.limit === undefined || typeof args.limit === 'number') &&
|
||||
(args.start === undefined || typeof args.start === 'number') &&
|
||||
(args.since === undefined || typeof args.since === 'string') &&
|
||||
(args.until === undefined || typeof args.until === 'string') &&
|
||||
(args.author === undefined || typeof args.author === 'string') &&
|
||||
(args.include_merge_commits === undefined || typeof args.include_merge_commits === 'boolean') &&
|
||||
(args.search === undefined || typeof args.search === 'string');
|
||||
|
||||
export const isListPrCommitsArgs = (
|
||||
args: any
|
||||
): args is {
|
||||
workspace: string;
|
||||
repository: string;
|
||||
pull_request_id: number;
|
||||
limit?: number;
|
||||
start?: number;
|
||||
} =>
|
||||
typeof args === 'object' &&
|
||||
args !== null &&
|
||||
typeof args.workspace === 'string' &&
|
||||
typeof args.repository === 'string' &&
|
||||
typeof args.pull_request_id === 'number' &&
|
||||
(args.limit === undefined || typeof args.limit === 'number') &&
|
||||
(args.start === undefined || typeof args.start === 'number');
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { BitbucketServerPullRequest, BitbucketCloudPullRequest, MergeInfo } from '../types/bitbucket.js';
|
||||
import {
|
||||
BitbucketServerPullRequest,
|
||||
BitbucketCloudPullRequest,
|
||||
MergeInfo,
|
||||
BitbucketServerCommit,
|
||||
BitbucketCloudCommit,
|
||||
FormattedCommit
|
||||
} from '../types/bitbucket.js';
|
||||
|
||||
export function formatServerResponse(
|
||||
pr: BitbucketServerPullRequest,
|
||||
|
@ -74,3 +81,38 @@ export function formatCloudResponse(pr: BitbucketCloudPullRequest): any {
|
|||
close_source_branch: pr.close_source_branch,
|
||||
};
|
||||
}
|
||||
|
||||
export function formatServerCommit(commit: BitbucketServerCommit): FormattedCommit {
|
||||
return {
|
||||
hash: commit.id,
|
||||
abbreviated_hash: commit.displayId,
|
||||
message: commit.message,
|
||||
author: {
|
||||
name: commit.author.name,
|
||||
email: commit.author.emailAddress,
|
||||
},
|
||||
date: new Date(commit.authorTimestamp).toISOString(),
|
||||
parents: commit.parents.map(p => p.id),
|
||||
is_merge_commit: commit.parents.length > 1,
|
||||
};
|
||||
}
|
||||
|
||||
export function formatCloudCommit(commit: BitbucketCloudCommit): FormattedCommit {
|
||||
// Parse the author raw string which is in format "Name <email>"
|
||||
const authorMatch = commit.author.raw.match(/^(.+?)\s*<(.+?)>$/);
|
||||
const authorName = authorMatch ? authorMatch[1] : (commit.author.user?.display_name || commit.author.raw);
|
||||
const authorEmail = authorMatch ? authorMatch[2] : '';
|
||||
|
||||
return {
|
||||
hash: commit.hash,
|
||||
abbreviated_hash: commit.hash.substring(0, 7),
|
||||
message: commit.message,
|
||||
author: {
|
||||
name: authorName,
|
||||
email: authorEmail,
|
||||
},
|
||||
date: commit.date,
|
||||
parents: commit.parents.map(p => p.hash),
|
||||
is_merge_commit: commit.parents.length > 1,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue