@tugudush/bitbucket-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +202 -0
- package/build/api.d.ts +19 -0
- package/build/api.d.ts.map +1 -0
- package/build/api.js +105 -0
- package/build/api.js.map +1 -0
- package/build/config.d.ts +46 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +80 -0
- package/build/config.js.map +1 -0
- package/build/errors.d.ts +33 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +100 -0
- package/build/errors.js.map +1 -0
- package/build/index-new.d.ts +3 -0
- package/build/index-new.d.ts.map +1 -0
- package/build/index-new.js +43 -0
- package/build/index-new.js.map +1 -0
- package/build/index-old.d.ts +3 -0
- package/build/index-old.d.ts.map +1 -0
- package/build/index-old.js +1028 -0
- package/build/index-old.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +43 -0
- package/build/index.js.map +1 -0
- package/build/schemas.d.ts +287 -0
- package/build/schemas.d.ts.map +1 -0
- package/build/schemas.js +208 -0
- package/build/schemas.js.map +1 -0
- package/build/tools.d.ts +13 -0
- package/build/tools.d.ts.map +1 -0
- package/build/tools.js +710 -0
- package/build/tools.js.map +1 -0
- package/build/types.d.ts +180 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +5 -0
- package/build/types.js.map +1 -0
- package/package.json +70 -0
package/build/tools.js
ADDED
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
2
|
+
import { BitbucketApiError } from './errors.js';
|
|
3
|
+
import { GetRepositorySchema, ListRepositoriesSchema, ListWorkspacesSchema, GetPullRequestsSchema, GetPullRequestSchema, GetPullRequestCommentsSchema, GetPullRequestActivitySchema, GetIssuesSchema, GetIssueSchema, GetCommitsSchema, GetBranchesSchema, GetFileContentSchema, BrowseRepositorySchema, GetUserSchema, GetCurrentUserSchema, SearchRepositoriesSchema, SearchCodeSchema, GetWorkspaceSchema, API_CONSTANTS, } from './schemas.js';
|
|
4
|
+
import { makeRequest, buildApiUrl, addQueryParams } from './api.js';
|
|
5
|
+
/**
|
|
6
|
+
* Tool definitions for the Bitbucket MCP server
|
|
7
|
+
*/
|
|
8
|
+
export function getToolDefinitions() {
|
|
9
|
+
return [
|
|
10
|
+
{
|
|
11
|
+
name: 'bb_get_repository',
|
|
12
|
+
description: 'Get detailed information about a specific repository',
|
|
13
|
+
inputSchema: zodToJsonSchema(GetRepositorySchema),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'bb_list_repositories',
|
|
17
|
+
description: 'List repositories in a workspace',
|
|
18
|
+
inputSchema: zodToJsonSchema(ListRepositoriesSchema),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'bb_list_workspaces',
|
|
22
|
+
description: 'List all accessible workspaces for discovery and exploration',
|
|
23
|
+
inputSchema: zodToJsonSchema(ListWorkspacesSchema),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'bb_get_pull_requests',
|
|
27
|
+
description: 'Get pull requests for a repository',
|
|
28
|
+
inputSchema: zodToJsonSchema(GetPullRequestsSchema),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'bb_get_pull_request',
|
|
32
|
+
description: 'Get detailed information about a specific pull request',
|
|
33
|
+
inputSchema: zodToJsonSchema(GetPullRequestSchema),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'bb_get_pull_request_comments',
|
|
37
|
+
description: 'Get comments for a specific pull request',
|
|
38
|
+
inputSchema: zodToJsonSchema(GetPullRequestCommentsSchema),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'bb_get_pull_request_activity',
|
|
42
|
+
description: 'Get activity (reviews, approvals, comments) for a specific pull request',
|
|
43
|
+
inputSchema: zodToJsonSchema(GetPullRequestActivitySchema),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'bb_get_issues',
|
|
47
|
+
description: 'Get issues for a repository',
|
|
48
|
+
inputSchema: zodToJsonSchema(GetIssuesSchema),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'bb_get_issue',
|
|
52
|
+
description: 'Get detailed information about a specific issue',
|
|
53
|
+
inputSchema: zodToJsonSchema(GetIssueSchema),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'bb_get_commits',
|
|
57
|
+
description: 'Get commits for a repository branch',
|
|
58
|
+
inputSchema: zodToJsonSchema(GetCommitsSchema),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'bb_get_branches',
|
|
62
|
+
description: 'Get branches for a repository',
|
|
63
|
+
inputSchema: zodToJsonSchema(GetBranchesSchema),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'bb_get_file_content',
|
|
67
|
+
description: 'Get the content of a file from a repository with pagination support',
|
|
68
|
+
inputSchema: zodToJsonSchema(GetFileContentSchema),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'bb_browse_repository',
|
|
72
|
+
description: 'Browse files and directories in a repository to explore structure',
|
|
73
|
+
inputSchema: zodToJsonSchema(BrowseRepositorySchema),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'bb_get_user',
|
|
77
|
+
description: 'Get information about a Bitbucket user. If no username is provided, returns information about the authenticated user.',
|
|
78
|
+
inputSchema: zodToJsonSchema(GetUserSchema),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'bb_get_current_user',
|
|
82
|
+
description: 'Get information about the currently authenticated user.',
|
|
83
|
+
inputSchema: zodToJsonSchema(GetCurrentUserSchema),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'bb_search_repositories',
|
|
87
|
+
description: 'Search for repositories within a workspace by name or description.',
|
|
88
|
+
inputSchema: zodToJsonSchema(SearchRepositoriesSchema),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'bb_search_code',
|
|
92
|
+
description: 'Search for code content within a workspace. Supports filtering by repository, language, and file extension.',
|
|
93
|
+
inputSchema: zodToJsonSchema(SearchCodeSchema),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'bb_get_workspace',
|
|
97
|
+
description: 'Get information about a workspace',
|
|
98
|
+
inputSchema: zodToJsonSchema(GetWorkspaceSchema),
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
export async function handleToolCall(request) {
|
|
103
|
+
const { name, arguments: args } = request.params;
|
|
104
|
+
try {
|
|
105
|
+
switch (name) {
|
|
106
|
+
case 'bb_get_repository': {
|
|
107
|
+
const parsed = GetRepositorySchema.parse(args);
|
|
108
|
+
const url = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}`);
|
|
109
|
+
const data = await makeRequest(url);
|
|
110
|
+
return {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: 'text',
|
|
114
|
+
text: `Repository: ${data.full_name}\n` +
|
|
115
|
+
`Description: ${data.description || 'No description'}\n` +
|
|
116
|
+
`Language: ${data.language || 'Not specified'}\n` +
|
|
117
|
+
`Private: ${data.is_private}\n` +
|
|
118
|
+
`Created: ${data.created_on}\n` +
|
|
119
|
+
`Updated: ${data.updated_on}\n` +
|
|
120
|
+
`Size: ${data.size ? `${data.size} bytes` : 'Unknown'}\n` +
|
|
121
|
+
`Forks: ${data.forks_count || 0}\n` +
|
|
122
|
+
`Watchers: ${data.watchers_count || 0}\n` +
|
|
123
|
+
`Website: ${data.website || 'None'}`,
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
case 'bb_list_repositories': {
|
|
129
|
+
const parsed = ListRepositoriesSchema.parse(args);
|
|
130
|
+
const params = {
|
|
131
|
+
page: parsed.page,
|
|
132
|
+
pagelen: parsed.pagelen,
|
|
133
|
+
};
|
|
134
|
+
const url = addQueryParams(buildApiUrl(`/repositories/${parsed.workspace}`), params);
|
|
135
|
+
const data = await makeRequest(url);
|
|
136
|
+
const repoList = data.values
|
|
137
|
+
.map((repo) => `- ${repo.full_name} (${repo.language || 'Unknown'})\n` +
|
|
138
|
+
` ${repo.description || 'No description'}\n` +
|
|
139
|
+
` Private: ${repo.is_private}, Updated: ${repo.updated_on}`)
|
|
140
|
+
.join('\n\n');
|
|
141
|
+
return {
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: 'text',
|
|
145
|
+
text: `Repositories in ${parsed.workspace} (${data.size} total):\n\n${repoList}`,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
case 'bb_list_workspaces': {
|
|
151
|
+
const parsed = ListWorkspacesSchema.parse(args);
|
|
152
|
+
const params = {
|
|
153
|
+
page: parsed.page,
|
|
154
|
+
pagelen: parsed.pagelen,
|
|
155
|
+
};
|
|
156
|
+
const url = addQueryParams(buildApiUrl('/workspaces'), params);
|
|
157
|
+
const data = await makeRequest(url);
|
|
158
|
+
const workspaceList = data.values
|
|
159
|
+
.map((workspace) => `- ${workspace.slug} (${workspace.name})\n` +
|
|
160
|
+
` Type: ${workspace.type}\n` +
|
|
161
|
+
` Created: ${workspace.created_on || 'Unknown'}`)
|
|
162
|
+
.join('\n\n');
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: 'text',
|
|
167
|
+
text: `Accessible workspaces (${data.size} total):\n\n${workspaceList}`,
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
case 'bb_get_pull_requests': {
|
|
173
|
+
const parsed = GetPullRequestsSchema.parse(args);
|
|
174
|
+
const params = {
|
|
175
|
+
state: parsed.state,
|
|
176
|
+
page: parsed.page,
|
|
177
|
+
pagelen: parsed.pagelen,
|
|
178
|
+
};
|
|
179
|
+
const url = addQueryParams(buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/pullrequests`), params);
|
|
180
|
+
const data = await makeRequest(url);
|
|
181
|
+
const prList = data.values
|
|
182
|
+
.map((pr) => `- #${pr.id}: ${pr.title}\n` +
|
|
183
|
+
` Author: ${pr.author.display_name}\n` +
|
|
184
|
+
` State: ${pr.state}\n` +
|
|
185
|
+
` Created: ${pr.created_on}\n` +
|
|
186
|
+
` Source: ${pr.source.branch.name} → ${pr.destination.branch.name}`)
|
|
187
|
+
.join('\n\n');
|
|
188
|
+
return {
|
|
189
|
+
content: [
|
|
190
|
+
{
|
|
191
|
+
type: 'text',
|
|
192
|
+
text: `Pull requests for ${parsed.workspace}/${parsed.repo_slug} (${data.size} total):\n\n${prList}`,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
case 'bb_get_pull_request': {
|
|
198
|
+
const parsed = GetPullRequestSchema.parse(args);
|
|
199
|
+
const url = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/pullrequests/${parsed.pull_request_id}`);
|
|
200
|
+
const data = await makeRequest(url);
|
|
201
|
+
return {
|
|
202
|
+
content: [
|
|
203
|
+
{
|
|
204
|
+
type: 'text',
|
|
205
|
+
text: `Pull Request #${data.id}: ${data.title}\n` +
|
|
206
|
+
`Author: ${data.author.display_name}\n` +
|
|
207
|
+
`State: ${data.state}\n` +
|
|
208
|
+
`Created: ${data.created_on}\n` +
|
|
209
|
+
`Updated: ${data.updated_on}\n` +
|
|
210
|
+
`Source: ${data.source.branch.name} → ${data.destination.branch.name}\n` +
|
|
211
|
+
`Description:\n${data.description || 'No description'}\n` +
|
|
212
|
+
`Reviewers: ${data.reviewers?.map(r => r.display_name).join(', ') || 'None'}`,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
case 'bb_get_pull_request_comments': {
|
|
218
|
+
const parsed = GetPullRequestCommentsSchema.parse(args);
|
|
219
|
+
const params = {
|
|
220
|
+
page: parsed.page,
|
|
221
|
+
pagelen: parsed.pagelen,
|
|
222
|
+
};
|
|
223
|
+
const url = addQueryParams(buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/pullrequests/${parsed.pull_request_id}/comments`), params);
|
|
224
|
+
const data = await makeRequest(url);
|
|
225
|
+
const commentList = data.values
|
|
226
|
+
.map((comment) => `- ${comment.user.display_name} (${comment.created_on}):\n` +
|
|
227
|
+
` ${comment.content?.raw || 'No content'}\n` +
|
|
228
|
+
(comment.inline
|
|
229
|
+
? ` File: ${comment.inline.path}, Line: ${comment.inline.to || comment.inline.from}`
|
|
230
|
+
: ''))
|
|
231
|
+
.join('\n\n');
|
|
232
|
+
return {
|
|
233
|
+
content: [
|
|
234
|
+
{
|
|
235
|
+
type: 'text',
|
|
236
|
+
text: `Comments for PR #${parsed.pull_request_id} (${data.size} total):\n\n${commentList}`,
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
case 'bb_get_pull_request_activity': {
|
|
242
|
+
const parsed = GetPullRequestActivitySchema.parse(args);
|
|
243
|
+
const params = {
|
|
244
|
+
page: parsed.page,
|
|
245
|
+
pagelen: parsed.pagelen,
|
|
246
|
+
};
|
|
247
|
+
const url = addQueryParams(buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/pullrequests/${parsed.pull_request_id}/activity`), params);
|
|
248
|
+
const data = await makeRequest(url);
|
|
249
|
+
const activityList = data.values
|
|
250
|
+
.map((activity) => {
|
|
251
|
+
// Handle cases where user might be undefined/null (system activities)
|
|
252
|
+
const userName = activity.user?.display_name || 'System';
|
|
253
|
+
const actionDate = activity.created_on || activity.update?.date || 'Unknown date';
|
|
254
|
+
const action = activity.action || 'Activity';
|
|
255
|
+
let activityText = `- ${userName} (${actionDate}):\n` + ` Action: ${action}`;
|
|
256
|
+
// Add comment if present
|
|
257
|
+
if (activity.comment) {
|
|
258
|
+
activityText += `\n Comment: ${activity.comment.content?.raw || 'No content'}`;
|
|
259
|
+
}
|
|
260
|
+
// Add approval if present
|
|
261
|
+
if (activity.approval) {
|
|
262
|
+
activityText += `\n Approval: ${activity.approval.state || 'Unknown state'}`;
|
|
263
|
+
}
|
|
264
|
+
// Add update if present
|
|
265
|
+
if (activity.update) {
|
|
266
|
+
const updateAuthor = activity.update.author?.display_name || 'Unknown';
|
|
267
|
+
activityText += `\n Update: ${activity.update.state || 'Updated'} by ${updateAuthor}`;
|
|
268
|
+
if (activity.update.title) {
|
|
269
|
+
activityText += `\n Title changed to: ${activity.update.title}`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return activityText;
|
|
273
|
+
})
|
|
274
|
+
.join('\n\n');
|
|
275
|
+
return {
|
|
276
|
+
content: [
|
|
277
|
+
{
|
|
278
|
+
type: 'text',
|
|
279
|
+
text: `Activity for PR #${parsed.pull_request_id} (${data.size} total):\n\n${activityList}`,
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
case 'bb_get_issues': {
|
|
285
|
+
const parsed = GetIssuesSchema.parse(args);
|
|
286
|
+
const params = {
|
|
287
|
+
state: parsed.state,
|
|
288
|
+
kind: parsed.kind,
|
|
289
|
+
page: parsed.page,
|
|
290
|
+
pagelen: parsed.pagelen,
|
|
291
|
+
};
|
|
292
|
+
const url = addQueryParams(buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/issues`), params);
|
|
293
|
+
const data = await makeRequest(url);
|
|
294
|
+
const issueList = data.values
|
|
295
|
+
.map((issue) => `- #${issue.id}: ${issue.title}\n` +
|
|
296
|
+
` State: ${issue.state}\n` +
|
|
297
|
+
` Kind: ${issue.kind}\n` +
|
|
298
|
+
` Priority: ${issue.priority}\n` +
|
|
299
|
+
` Reporter: ${issue.reporter.display_name}\n` +
|
|
300
|
+
` Assignee: ${issue.assignee?.display_name || 'Unassigned'}\n` +
|
|
301
|
+
` Created: ${issue.created_on}`)
|
|
302
|
+
.join('\n\n');
|
|
303
|
+
return {
|
|
304
|
+
content: [
|
|
305
|
+
{
|
|
306
|
+
type: 'text',
|
|
307
|
+
text: `Issues for ${parsed.workspace}/${parsed.repo_slug} (${data.size} total):\n\n${issueList}`,
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
case 'bb_get_issue': {
|
|
313
|
+
const parsed = GetIssueSchema.parse(args);
|
|
314
|
+
const url = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/issues/${parsed.issue_id}`);
|
|
315
|
+
const data = await makeRequest(url);
|
|
316
|
+
return {
|
|
317
|
+
content: [
|
|
318
|
+
{
|
|
319
|
+
type: 'text',
|
|
320
|
+
text: `Issue #${data.id}: ${data.title}\n` +
|
|
321
|
+
`State: ${data.state}\n` +
|
|
322
|
+
`Kind: ${data.kind}\n` +
|
|
323
|
+
`Priority: ${data.priority}\n` +
|
|
324
|
+
`Reporter: ${data.reporter.display_name}\n` +
|
|
325
|
+
`Assignee: ${data.assignee?.display_name || 'Unassigned'}\n` +
|
|
326
|
+
`Created: ${data.created_on}\n` +
|
|
327
|
+
`Updated: ${data.updated_on}\n` +
|
|
328
|
+
`Content:\n${data.content?.raw || 'No content'}`,
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
case 'bb_get_commits': {
|
|
334
|
+
const parsed = GetCommitsSchema.parse(args);
|
|
335
|
+
const params = {
|
|
336
|
+
page: parsed.page,
|
|
337
|
+
pagelen: parsed.pagelen,
|
|
338
|
+
};
|
|
339
|
+
let url = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/commits`);
|
|
340
|
+
if (parsed.branch) {
|
|
341
|
+
url += `/${parsed.branch}`;
|
|
342
|
+
}
|
|
343
|
+
url = addQueryParams(url, params);
|
|
344
|
+
const data = await makeRequest(url);
|
|
345
|
+
const commitList = data.values
|
|
346
|
+
.map((commit) => `- ${commit.hash.substring(0, 8)}: ${commit.message.split('\n')[0]}\n` +
|
|
347
|
+
` Author: ${commit.author.user?.display_name || commit.author.raw}\n` +
|
|
348
|
+
` Date: ${commit.date}`)
|
|
349
|
+
.join('\n\n');
|
|
350
|
+
return {
|
|
351
|
+
content: [
|
|
352
|
+
{
|
|
353
|
+
type: 'text',
|
|
354
|
+
text: `Commits for ${parsed.workspace}/${parsed.repo_slug}${parsed.branch ? ` (${parsed.branch})` : ''} (${data.size} total):\n\n${commitList}`,
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
case 'bb_get_branches': {
|
|
360
|
+
const parsed = GetBranchesSchema.parse(args);
|
|
361
|
+
const params = {
|
|
362
|
+
page: parsed.page,
|
|
363
|
+
pagelen: parsed.pagelen,
|
|
364
|
+
};
|
|
365
|
+
const url = addQueryParams(buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/refs/branches`), params);
|
|
366
|
+
const data = await makeRequest(url);
|
|
367
|
+
const branchList = data.values
|
|
368
|
+
.map((branch) => `- ${branch.name}\n` +
|
|
369
|
+
` Last commit: ${branch.target.hash.substring(0, 8)}\n` +
|
|
370
|
+
` Date: ${branch.target.date}`)
|
|
371
|
+
.join('\n\n');
|
|
372
|
+
return {
|
|
373
|
+
content: [
|
|
374
|
+
{
|
|
375
|
+
type: 'text',
|
|
376
|
+
text: `Branches for ${parsed.workspace}/${parsed.repo_slug} (${data.size} total):\n\n${branchList}`,
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
case 'bb_get_file_content': {
|
|
382
|
+
const parsed = GetFileContentSchema.parse(args);
|
|
383
|
+
let ref = parsed.ref || 'HEAD';
|
|
384
|
+
// If no ref specified, fetch repository info to get default branch
|
|
385
|
+
if (ref === 'HEAD') {
|
|
386
|
+
try {
|
|
387
|
+
const repoUrl = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}`);
|
|
388
|
+
const repoData = await makeRequest(repoUrl);
|
|
389
|
+
ref = repoData.mainbranch?.name || 'main';
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// Fallback to 'main' if we can't get repository info
|
|
393
|
+
ref = 'main';
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Use the same robust approach as bb_browse_repository
|
|
397
|
+
// Get commit SHA first to handle branch names with slashes
|
|
398
|
+
let url;
|
|
399
|
+
try {
|
|
400
|
+
const branchUrl = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/refs/branches/${encodeURIComponent(ref)}`);
|
|
401
|
+
const branchData = await makeRequest(branchUrl);
|
|
402
|
+
const commitSha = branchData.target.hash;
|
|
403
|
+
// Use /src/{commit_sha}/{file_path} pattern
|
|
404
|
+
const encodedFilePath = parsed.file_path
|
|
405
|
+
.split('/')
|
|
406
|
+
.map(segment => encodeURIComponent(segment))
|
|
407
|
+
.join('/');
|
|
408
|
+
url = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/src/${commitSha}/${encodedFilePath}`);
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// If we can't get the commit SHA, fall back to trying the branch name directly
|
|
412
|
+
const encodedRef = encodeURIComponent(ref);
|
|
413
|
+
const encodedFilePath = parsed.file_path
|
|
414
|
+
.split('/')
|
|
415
|
+
.map(segment => encodeURIComponent(segment))
|
|
416
|
+
.join('/');
|
|
417
|
+
url = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/src/${encodedRef}/${encodedFilePath}`);
|
|
418
|
+
}
|
|
419
|
+
// Use a custom request for text content instead of makeRequest which expects JSON
|
|
420
|
+
const { loadConfig } = await import('./config.js');
|
|
421
|
+
const config = loadConfig();
|
|
422
|
+
const headers = {
|
|
423
|
+
Accept: 'text/plain',
|
|
424
|
+
'User-Agent': 'bitbucket-mcp-server/1.0.0',
|
|
425
|
+
};
|
|
426
|
+
// Add authentication if available
|
|
427
|
+
const apiToken = config.BITBUCKET_API_TOKEN;
|
|
428
|
+
const email = config.BITBUCKET_EMAIL;
|
|
429
|
+
if (apiToken && email) {
|
|
430
|
+
const auth = Buffer.from(`${email}:${apiToken}`).toString('base64');
|
|
431
|
+
headers.Authorization = `Basic ${auth}`;
|
|
432
|
+
}
|
|
433
|
+
const response = await fetch(url, { headers });
|
|
434
|
+
if (!response.ok) {
|
|
435
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
436
|
+
}
|
|
437
|
+
const content = await response.text();
|
|
438
|
+
// Handle pagination
|
|
439
|
+
const lines = content.split('\n');
|
|
440
|
+
const start = parsed.start ? Math.max(1, parsed.start) : 1;
|
|
441
|
+
const limit = parsed.limit
|
|
442
|
+
? Math.min(parsed.limit, API_CONSTANTS.MAX_FILE_LINES)
|
|
443
|
+
: API_CONSTANTS.DEFAULT_FILE_LINES;
|
|
444
|
+
const endLine = Math.min(start + limit - 1, lines.length);
|
|
445
|
+
const paginatedLines = lines.slice(start - 1, endLine);
|
|
446
|
+
return {
|
|
447
|
+
content: [
|
|
448
|
+
{
|
|
449
|
+
type: 'text',
|
|
450
|
+
text: `File: ${parsed.file_path} (lines ${start}-${endLine} of ${lines.length})\n` +
|
|
451
|
+
`Repository: ${parsed.workspace}/${parsed.repo_slug}\n` +
|
|
452
|
+
`Ref: ${ref}\n\n` +
|
|
453
|
+
paginatedLines
|
|
454
|
+
.map((line, index) => `${start + index}: ${line}`)
|
|
455
|
+
.join('\n'),
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
case 'bb_browse_repository': {
|
|
461
|
+
const parsed = BrowseRepositorySchema.parse(args);
|
|
462
|
+
let ref = parsed.ref;
|
|
463
|
+
// If no ref specified, fetch repository info to get default branch
|
|
464
|
+
if (!ref) {
|
|
465
|
+
try {
|
|
466
|
+
const repoUrl = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}`);
|
|
467
|
+
const repoData = await makeRequest(repoUrl);
|
|
468
|
+
ref = repoData.mainbranch?.name || 'main';
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
// Fallback to 'main' if we can't get repository info
|
|
472
|
+
ref = 'main';
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const path = parsed.path || '';
|
|
476
|
+
let url;
|
|
477
|
+
if (path) {
|
|
478
|
+
// For subdirectories, we need to get the commit SHA first
|
|
479
|
+
// because the /src/{ref}/{path} pattern doesn't work with branch names containing slashes
|
|
480
|
+
try {
|
|
481
|
+
const branchUrl = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/refs/branches/${encodeURIComponent(ref)}`);
|
|
482
|
+
const branchData = await makeRequest(branchUrl);
|
|
483
|
+
const commitSha = branchData.target.hash;
|
|
484
|
+
// Use /src/{commit_sha}/{path} pattern for subdirectories
|
|
485
|
+
const encodedPath = path
|
|
486
|
+
.split('/')
|
|
487
|
+
.map(segment => encodeURIComponent(segment))
|
|
488
|
+
.join('/');
|
|
489
|
+
url = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/src/${commitSha}/${encodedPath}`);
|
|
490
|
+
// Ensure trailing slash for directory browsing
|
|
491
|
+
if (!url.endsWith('/')) {
|
|
492
|
+
url += '/';
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
// If we can't get the commit SHA, fall back to trying the branch name directly
|
|
497
|
+
const encodedRef = encodeURIComponent(ref);
|
|
498
|
+
const encodedPath = path
|
|
499
|
+
.split('/')
|
|
500
|
+
.map(segment => encodeURIComponent(segment))
|
|
501
|
+
.join('/');
|
|
502
|
+
url = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/src/${encodedRef}/${encodedPath}`);
|
|
503
|
+
// Ensure trailing slash for directory browsing
|
|
504
|
+
if (!url.endsWith('/')) {
|
|
505
|
+
url += '/';
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
// For root directory, use /src?at={ref} pattern (works with branch names)
|
|
511
|
+
url = buildApiUrl(`/repositories/${parsed.workspace}/${parsed.repo_slug}/src`);
|
|
512
|
+
// Ensure trailing slash for directory browsing
|
|
513
|
+
if (!url.endsWith('/')) {
|
|
514
|
+
url += '/';
|
|
515
|
+
}
|
|
516
|
+
url += `?at=${encodeURIComponent(ref)}`;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
const data = await makeRequest(url);
|
|
520
|
+
const limit = parsed.limit
|
|
521
|
+
? Math.min(parsed.limit, API_CONSTANTS.MAX_BROWSE_ITEMS)
|
|
522
|
+
: API_CONSTANTS.DEFAULT_BROWSE_ITEMS;
|
|
523
|
+
const items = data.values.slice(0, limit);
|
|
524
|
+
const itemList = items
|
|
525
|
+
.map(item => {
|
|
526
|
+
const isDir = item.type === 'commit_directory';
|
|
527
|
+
const icon = isDir ? '📁' : '📄';
|
|
528
|
+
const size = item.size ? ` (${item.size} bytes)` : '';
|
|
529
|
+
return `${icon} ${item.path}${size}`;
|
|
530
|
+
})
|
|
531
|
+
.join('\n');
|
|
532
|
+
return {
|
|
533
|
+
content: [
|
|
534
|
+
{
|
|
535
|
+
type: 'text',
|
|
536
|
+
text: `Repository: ${parsed.workspace}/${parsed.repo_slug}\n` +
|
|
537
|
+
`Path: /${path}\n` +
|
|
538
|
+
`Ref: ${ref}\n` +
|
|
539
|
+
`Items (${items.length} of ${data.size || data.values.length} total):\n\n${itemList}`,
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
if (error instanceof BitbucketApiError && error.status === 404) {
|
|
546
|
+
// Enhanced error message for branch/commit not found
|
|
547
|
+
throw new BitbucketApiError(404, 'Not Found', `Branch, tag, or commit '${ref}' not found in repository ${parsed.workspace}/${parsed.repo_slug}`, `Try specifying a different branch with the 'ref' parameter. Common branch names are 'main', 'master', or 'develop'. Use bb_get_branches to list available branches.`);
|
|
548
|
+
}
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
case 'bb_get_user': {
|
|
553
|
+
const parsed = GetUserSchema.parse(args);
|
|
554
|
+
// Bitbucket API v2.0 only supports getting current user info
|
|
555
|
+
// The /users/{username} endpoint doesn't exist
|
|
556
|
+
if (parsed.username) {
|
|
557
|
+
throw new Error(`Getting user info by username is not supported by Bitbucket API v2.0. Use bb_get_current_user for current user or bb_get_workspace for workspace info.`);
|
|
558
|
+
}
|
|
559
|
+
const url = buildApiUrl('/user');
|
|
560
|
+
const data = await makeRequest(url);
|
|
561
|
+
return {
|
|
562
|
+
content: [
|
|
563
|
+
{
|
|
564
|
+
type: 'text',
|
|
565
|
+
text: `User: ${data.display_name} (@${data.username})\n` +
|
|
566
|
+
`Account ID: ${data.account_id}\n` +
|
|
567
|
+
`Type: ${data.type}\n` +
|
|
568
|
+
`Website: ${data.website || 'None'}\n` +
|
|
569
|
+
`Location: ${data.location || 'Not specified'}\n` +
|
|
570
|
+
`Created: ${data.created_on}`,
|
|
571
|
+
},
|
|
572
|
+
],
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
case 'bb_get_current_user': {
|
|
576
|
+
const url = buildApiUrl('/user');
|
|
577
|
+
const data = await makeRequest(url);
|
|
578
|
+
return {
|
|
579
|
+
content: [
|
|
580
|
+
{
|
|
581
|
+
type: 'text',
|
|
582
|
+
text: `Current User: ${data.display_name} (@${data.username})\n` +
|
|
583
|
+
`Account ID: ${data.account_id}\n` +
|
|
584
|
+
`Type: ${data.type}\n` +
|
|
585
|
+
`Website: ${data.website || 'None'}\n` +
|
|
586
|
+
`Location: ${data.location || 'Not specified'}\n` +
|
|
587
|
+
`Created: ${data.created_on}`,
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
case 'bb_search_repositories': {
|
|
593
|
+
const parsed = SearchRepositoriesSchema.parse(args);
|
|
594
|
+
// Bitbucket API v2.0 doesn't have workspace-scoped repository search
|
|
595
|
+
// Instead, list all repositories in workspace and filter client-side
|
|
596
|
+
const params = {
|
|
597
|
+
page: parsed.page,
|
|
598
|
+
pagelen: parsed.pagelen,
|
|
599
|
+
};
|
|
600
|
+
const url = addQueryParams(buildApiUrl(`/repositories/${parsed.workspace}`), params);
|
|
601
|
+
const data = await makeRequest(url);
|
|
602
|
+
// Filter repositories based on search query
|
|
603
|
+
const searchLower = parsed.query.toLowerCase();
|
|
604
|
+
const filteredRepos = data.values.filter((repo) => {
|
|
605
|
+
const nameMatch = repo.name.toLowerCase().includes(searchLower);
|
|
606
|
+
const descMatch = repo.description?.toLowerCase().includes(searchLower) || false;
|
|
607
|
+
const fullNameMatch = repo.full_name
|
|
608
|
+
.toLowerCase()
|
|
609
|
+
.includes(searchLower);
|
|
610
|
+
return nameMatch || descMatch || fullNameMatch;
|
|
611
|
+
});
|
|
612
|
+
const repoList = filteredRepos
|
|
613
|
+
.map((repo) => `- ${repo.full_name} (${repo.language || 'Unknown'})\n` +
|
|
614
|
+
` ${repo.description || 'No description'}\n` +
|
|
615
|
+
` Private: ${repo.is_private}, Updated: ${repo.updated_on}`)
|
|
616
|
+
.join('\n\n');
|
|
617
|
+
return {
|
|
618
|
+
content: [
|
|
619
|
+
{
|
|
620
|
+
type: 'text',
|
|
621
|
+
text: `Search results for "${parsed.query}" in ${parsed.workspace} (${filteredRepos.length} of ${data.values.length} total):\n\n${repoList || 'No repositories found matching the search query.'}`,
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
case 'bb_search_code': {
|
|
627
|
+
const parsed = SearchCodeSchema.parse(args);
|
|
628
|
+
const params = {
|
|
629
|
+
search_query: parsed.search_query,
|
|
630
|
+
page: parsed.page,
|
|
631
|
+
pagelen: parsed.pagelen,
|
|
632
|
+
};
|
|
633
|
+
if (parsed.repo_slug) {
|
|
634
|
+
params.search_query += ` repo:${parsed.repo_slug}`;
|
|
635
|
+
}
|
|
636
|
+
if (parsed.language) {
|
|
637
|
+
params.search_query += ` language:${parsed.language}`;
|
|
638
|
+
}
|
|
639
|
+
if (parsed.extension) {
|
|
640
|
+
params.search_query += ` extension:${parsed.extension}`;
|
|
641
|
+
}
|
|
642
|
+
const url = addQueryParams(buildApiUrl(`/workspaces/${parsed.workspace}/search/code`), params);
|
|
643
|
+
const data = await makeRequest(url);
|
|
644
|
+
const resultList = data.values
|
|
645
|
+
.map(result => {
|
|
646
|
+
const matchCount = result.content_match_count;
|
|
647
|
+
const filePath = result.file.path;
|
|
648
|
+
const matches = result.content_matches
|
|
649
|
+
.map(match => match.lines
|
|
650
|
+
.map(line => {
|
|
651
|
+
const lineText = line.segments
|
|
652
|
+
.map(seg => (seg.match ? `**${seg.text}**` : seg.text))
|
|
653
|
+
.join('');
|
|
654
|
+
return ` Line ${line.line}: ${lineText}`;
|
|
655
|
+
})
|
|
656
|
+
.join('\n'))
|
|
657
|
+
.join('\n');
|
|
658
|
+
return `📄 ${filePath} (${matchCount} matches)\n${matches}`;
|
|
659
|
+
})
|
|
660
|
+
.join('\n\n');
|
|
661
|
+
return {
|
|
662
|
+
content: [
|
|
663
|
+
{
|
|
664
|
+
type: 'text',
|
|
665
|
+
text: `Code search results for "${parsed.search_query}" in ${parsed.workspace} (${data.values.length} results):\n\n${resultList}`,
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
case 'bb_get_workspace': {
|
|
671
|
+
const parsed = GetWorkspaceSchema.parse(args);
|
|
672
|
+
const url = buildApiUrl(`/workspaces/${parsed.workspace}`);
|
|
673
|
+
const data = await makeRequest(url);
|
|
674
|
+
return {
|
|
675
|
+
content: [
|
|
676
|
+
{
|
|
677
|
+
type: 'text',
|
|
678
|
+
text: `Workspace: ${data.name} (${data.slug})\n` +
|
|
679
|
+
`Type: ${data.type}\n` +
|
|
680
|
+
`UUID: ${data.uuid || 'Not available'}\n` +
|
|
681
|
+
`Created: ${data.created_on || 'Unknown'}`,
|
|
682
|
+
},
|
|
683
|
+
],
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
default:
|
|
687
|
+
return {
|
|
688
|
+
content: [
|
|
689
|
+
{
|
|
690
|
+
type: 'text',
|
|
691
|
+
text: `Unknown tool: ${name}`,
|
|
692
|
+
},
|
|
693
|
+
],
|
|
694
|
+
isError: true,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
catch (error) {
|
|
699
|
+
return {
|
|
700
|
+
content: [
|
|
701
|
+
{
|
|
702
|
+
type: 'text',
|
|
703
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
704
|
+
},
|
|
705
|
+
],
|
|
706
|
+
isError: true,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
//# sourceMappingURL=tools.js.map
|