mcp-server-bitbucket 0.11.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/dist/index.js ADDED
@@ -0,0 +1,2976 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListToolsRequestSchema,
9
+ ListResourcesRequestSchema,
10
+ ReadResourceRequestSchema,
11
+ ListPromptsRequestSchema,
12
+ GetPromptRequestSchema
13
+ } from "@modelcontextprotocol/sdk/types.js";
14
+
15
+ // src/settings.ts
16
+ import { z } from "zod";
17
+ var settingsSchema = z.object({
18
+ bitbucketWorkspace: z.string().min(1, "BITBUCKET_WORKSPACE is required"),
19
+ bitbucketEmail: z.string().min(1, "BITBUCKET_EMAIL is required"),
20
+ bitbucketApiToken: z.string().min(1, "BITBUCKET_API_TOKEN is required"),
21
+ apiTimeout: z.number().min(1).max(300).default(30),
22
+ maxRetries: z.number().min(0).max(10).default(3),
23
+ outputFormat: z.enum(["json", "toon"]).default("json")
24
+ });
25
+ var cachedSettings = null;
26
+ function getSettings() {
27
+ if (cachedSettings) {
28
+ return cachedSettings;
29
+ }
30
+ const rawSettings = {
31
+ bitbucketWorkspace: process.env.BITBUCKET_WORKSPACE || "",
32
+ bitbucketEmail: process.env.BITBUCKET_EMAIL || "",
33
+ bitbucketApiToken: process.env.BITBUCKET_API_TOKEN || "",
34
+ apiTimeout: parseInt(process.env.API_TIMEOUT || "30", 10),
35
+ maxRetries: parseInt(process.env.MAX_RETRIES || "3", 10),
36
+ outputFormat: process.env.OUTPUT_FORMAT || "json"
37
+ };
38
+ const result = settingsSchema.safeParse(rawSettings);
39
+ if (!result.success) {
40
+ const errors = result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
41
+ throw new Error(`Configuration error: ${errors}`);
42
+ }
43
+ cachedSettings = result.data;
44
+ return cachedSettings;
45
+ }
46
+
47
+ // src/client.ts
48
+ import axios from "axios";
49
+
50
+ // src/utils.ts
51
+ function ensureUuidBraces(uuid) {
52
+ if (!uuid) return uuid;
53
+ if (uuid.startsWith("{") && uuid.endsWith("}")) {
54
+ return uuid;
55
+ }
56
+ return `{${uuid}}`;
57
+ }
58
+ function truncateHash(hash) {
59
+ if (!hash) return "";
60
+ return hash.substring(0, 7);
61
+ }
62
+ function sanitizeSearchTerm(term) {
63
+ return term.replace(/["\\]/g, "").trim();
64
+ }
65
+ function validateLimit(limit, maxLimit = 100) {
66
+ if (limit < 1) return 1;
67
+ if (limit > maxLimit) return maxLimit;
68
+ return limit;
69
+ }
70
+ function notFoundResponse(type, identifier) {
71
+ return {
72
+ error: `${type} '${identifier}' not found`,
73
+ found: false
74
+ };
75
+ }
76
+ function sleep(ms) {
77
+ return new Promise((resolve) => setTimeout(resolve, ms));
78
+ }
79
+
80
+ // src/client.ts
81
+ var BitbucketError = class extends Error {
82
+ constructor(message, statusCode, method, path) {
83
+ super(message);
84
+ this.statusCode = statusCode;
85
+ this.method = method;
86
+ this.path = path;
87
+ this.name = "BitbucketError";
88
+ }
89
+ };
90
+ var BitbucketClient = class _BitbucketClient {
91
+ static BASE_URL = "https://api.bitbucket.org/2.0";
92
+ static INITIAL_BACKOFF = 1e3;
93
+ // ms
94
+ workspace;
95
+ client;
96
+ maxRetries;
97
+ constructor() {
98
+ const settings = getSettings();
99
+ this.workspace = settings.bitbucketWorkspace;
100
+ this.maxRetries = settings.maxRetries;
101
+ this.client = axios.create({
102
+ baseURL: _BitbucketClient.BASE_URL,
103
+ timeout: settings.apiTimeout * 1e3,
104
+ auth: {
105
+ username: settings.bitbucketEmail,
106
+ password: settings.bitbucketApiToken
107
+ },
108
+ headers: {
109
+ "Content-Type": "application/json"
110
+ }
111
+ });
112
+ }
113
+ /**
114
+ * Build repository endpoint path
115
+ */
116
+ repoPath(repoSlug, ...parts) {
117
+ const base = `repositories/${this.workspace}/${repoSlug}`;
118
+ return parts.length > 0 ? `${base}/${parts.join("/")}` : base;
119
+ }
120
+ /**
121
+ * Make an API request with retry logic for rate limiting
122
+ */
123
+ async request(method, path, data, params) {
124
+ let backoff = _BitbucketClient.INITIAL_BACKOFF;
125
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
126
+ try {
127
+ const response = await this.client.request({
128
+ method,
129
+ url: path,
130
+ data,
131
+ params
132
+ });
133
+ return response.data;
134
+ } catch (error) {
135
+ if (axios.isAxiosError(error)) {
136
+ const axiosError = error;
137
+ if (axiosError.response?.status === 404) {
138
+ return null;
139
+ }
140
+ if (axiosError.response?.status === 429) {
141
+ if (attempt < this.maxRetries) {
142
+ const retryAfter = axiosError.response.headers["retry-after"];
143
+ const waitTime = retryAfter ? parseInt(retryAfter, 10) * 1e3 : backoff;
144
+ await sleep(waitTime);
145
+ backoff *= 2;
146
+ continue;
147
+ }
148
+ throw new BitbucketError(
149
+ `Rate limited after ${this.maxRetries} retries`,
150
+ 429,
151
+ method,
152
+ path
153
+ );
154
+ }
155
+ const statusCode = axiosError.response?.status;
156
+ const errorText = JSON.stringify(axiosError.response?.data || axiosError.message).substring(0, 500);
157
+ throw new BitbucketError(
158
+ `API error ${statusCode}: ${errorText}`,
159
+ statusCode,
160
+ method,
161
+ path
162
+ );
163
+ }
164
+ throw error;
165
+ }
166
+ }
167
+ throw new BitbucketError(`Unexpected error in request`, void 0, method, path);
168
+ }
169
+ /**
170
+ * Make a request that returns plain text
171
+ */
172
+ async requestText(path) {
173
+ let backoff = _BitbucketClient.INITIAL_BACKOFF;
174
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
175
+ try {
176
+ const response = await this.client.get(path, {
177
+ responseType: "text"
178
+ });
179
+ return response.data;
180
+ } catch (error) {
181
+ if (axios.isAxiosError(error)) {
182
+ if (error.response?.status === 404) {
183
+ return null;
184
+ }
185
+ if (error.response?.status === 429) {
186
+ if (attempt < this.maxRetries) {
187
+ const retryAfter = error.response.headers["retry-after"];
188
+ const waitTime = retryAfter ? parseInt(retryAfter, 10) * 1e3 : backoff;
189
+ await sleep(waitTime);
190
+ backoff *= 2;
191
+ continue;
192
+ }
193
+ }
194
+ throw new BitbucketError(`Request failed: ${error.response?.status}`);
195
+ }
196
+ throw error;
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ /**
202
+ * Helper for paginated list endpoints
203
+ */
204
+ async paginatedList(endpoint, options = {}) {
205
+ const { limit = 50, maxPage = 100, ...extraParams } = options;
206
+ const params = {
207
+ pagelen: Math.min(limit, maxPage),
208
+ ...extraParams
209
+ };
210
+ Object.keys(params).forEach((key) => {
211
+ if (params[key] === void 0) {
212
+ delete params[key];
213
+ }
214
+ });
215
+ const result = await this.request("GET", endpoint, void 0, params);
216
+ return result?.values || [];
217
+ }
218
+ // ==================== REPOSITORIES ====================
219
+ async getRepository(repoSlug) {
220
+ return this.request("GET", this.repoPath(repoSlug));
221
+ }
222
+ async createRepository(repoSlug, options = {}) {
223
+ const payload = {
224
+ scm: "git",
225
+ is_private: options.isPrivate ?? true
226
+ };
227
+ if (options.projectKey) {
228
+ payload.project = { key: options.projectKey };
229
+ }
230
+ if (options.description) {
231
+ payload.description = options.description;
232
+ }
233
+ const result = await this.request("POST", this.repoPath(repoSlug), payload);
234
+ if (!result) {
235
+ throw new BitbucketError(`Failed to create repository: ${repoSlug}`);
236
+ }
237
+ return result;
238
+ }
239
+ async deleteRepository(repoSlug) {
240
+ await this.request("DELETE", this.repoPath(repoSlug));
241
+ }
242
+ async listRepositories(options = {}) {
243
+ const params = {
244
+ pagelen: Math.min(options.limit || 50, 100)
245
+ };
246
+ const qParts = [];
247
+ if (options.projectKey) {
248
+ qParts.push(`project.key="${options.projectKey}"`);
249
+ }
250
+ if (options.query) {
251
+ qParts.push(options.query);
252
+ }
253
+ if (qParts.length > 0) {
254
+ params.q = qParts.join(" AND ");
255
+ }
256
+ const result = await this.request(
257
+ "GET",
258
+ `repositories/${this.workspace}`,
259
+ void 0,
260
+ params
261
+ );
262
+ return result?.values || [];
263
+ }
264
+ async updateRepository(repoSlug, options) {
265
+ const payload = {};
266
+ if (options.projectKey !== void 0) {
267
+ payload.project = { key: options.projectKey };
268
+ }
269
+ if (options.isPrivate !== void 0) {
270
+ payload.is_private = options.isPrivate;
271
+ }
272
+ if (options.description !== void 0) {
273
+ payload.description = options.description;
274
+ }
275
+ if (options.name !== void 0) {
276
+ payload.name = options.name;
277
+ }
278
+ if (Object.keys(payload).length === 0) {
279
+ throw new BitbucketError("No fields to update");
280
+ }
281
+ const result = await this.request("PUT", this.repoPath(repoSlug), payload);
282
+ if (!result) {
283
+ throw new BitbucketError(`Failed to update repository: ${repoSlug}`);
284
+ }
285
+ return result;
286
+ }
287
+ // ==================== PULL REQUESTS ====================
288
+ async createPullRequest(repoSlug, options) {
289
+ const payload = {
290
+ title: options.title,
291
+ source: { branch: { name: options.sourceBranch } },
292
+ destination: { branch: { name: options.destinationBranch || "main" } },
293
+ close_source_branch: options.closeSourceBranch ?? true
294
+ };
295
+ if (options.description) {
296
+ payload.description = options.description;
297
+ }
298
+ if (options.reviewers && options.reviewers.length > 0) {
299
+ payload.reviewers = options.reviewers.map(
300
+ (r) => r.startsWith("{") ? { uuid: r } : { account_id: r }
301
+ );
302
+ }
303
+ const result = await this.request(
304
+ "POST",
305
+ this.repoPath(repoSlug, "pullrequests"),
306
+ payload
307
+ );
308
+ if (!result) {
309
+ throw new BitbucketError(`Failed to create PR: ${options.sourceBranch} -> ${options.destinationBranch || "main"}`);
310
+ }
311
+ return result;
312
+ }
313
+ async getPullRequest(repoSlug, prId) {
314
+ return this.request("GET", this.repoPath(repoSlug, "pullrequests", String(prId)));
315
+ }
316
+ async listPullRequests(repoSlug, options = {}) {
317
+ return this.paginatedList(this.repoPath(repoSlug, "pullrequests"), {
318
+ limit: options.limit || 50,
319
+ maxPage: 50,
320
+ state: options.state || "OPEN"
321
+ });
322
+ }
323
+ async mergePullRequest(repoSlug, prId, options = {}) {
324
+ const payload = {
325
+ type: options.mergeStrategy || "merge_commit",
326
+ close_source_branch: options.closeSourceBranch ?? true
327
+ };
328
+ if (options.message) {
329
+ payload.message = options.message;
330
+ }
331
+ const result = await this.request(
332
+ "POST",
333
+ this.repoPath(repoSlug, "pullrequests", String(prId), "merge"),
334
+ payload
335
+ );
336
+ if (!result) {
337
+ throw new BitbucketError(`Failed to merge PR #${prId}`);
338
+ }
339
+ return result;
340
+ }
341
+ async listPrComments(repoSlug, prId, options = {}) {
342
+ return this.paginatedList(
343
+ this.repoPath(repoSlug, "pullrequests", String(prId), "comments"),
344
+ { limit: options.limit || 50 }
345
+ );
346
+ }
347
+ async addPrComment(repoSlug, prId, content, inline) {
348
+ const payload = {
349
+ content: { raw: content }
350
+ };
351
+ if (inline) {
352
+ payload.inline = inline;
353
+ }
354
+ const result = await this.request(
355
+ "POST",
356
+ this.repoPath(repoSlug, "pullrequests", String(prId), "comments"),
357
+ payload
358
+ );
359
+ if (!result) {
360
+ throw new BitbucketError(`Failed to add comment to PR #${prId}`);
361
+ }
362
+ return result;
363
+ }
364
+ async approvePr(repoSlug, prId) {
365
+ const result = await this.request(
366
+ "POST",
367
+ this.repoPath(repoSlug, "pullrequests", String(prId), "approve")
368
+ );
369
+ if (!result) {
370
+ throw new BitbucketError(`Failed to approve PR #${prId}`);
371
+ }
372
+ return result;
373
+ }
374
+ async unapprovePr(repoSlug, prId) {
375
+ await this.request("DELETE", this.repoPath(repoSlug, "pullrequests", String(prId), "approve"));
376
+ }
377
+ async requestChangesPr(repoSlug, prId) {
378
+ const result = await this.request(
379
+ "POST",
380
+ this.repoPath(repoSlug, "pullrequests", String(prId), "request-changes")
381
+ );
382
+ if (!result) {
383
+ throw new BitbucketError(`Failed to request changes on PR #${prId}`);
384
+ }
385
+ return result;
386
+ }
387
+ async declinePr(repoSlug, prId) {
388
+ const result = await this.request(
389
+ "POST",
390
+ this.repoPath(repoSlug, "pullrequests", String(prId), "decline")
391
+ );
392
+ if (!result) {
393
+ throw new BitbucketError(`Failed to decline PR #${prId}`);
394
+ }
395
+ return result;
396
+ }
397
+ async getPrDiff(repoSlug, prId) {
398
+ return await this.requestText(this.repoPath(repoSlug, "pullrequests", String(prId), "diff")) || "";
399
+ }
400
+ // ==================== PIPELINES ====================
401
+ /**
402
+ * Build the pipeline target object based on options.
403
+ * Supports branch triggers, commit triggers, and custom pipelines.
404
+ */
405
+ buildPipelineTarget(options) {
406
+ if (options.branch && options.commit) {
407
+ throw new BitbucketError("Cannot specify both branch and commit - they are mutually exclusive");
408
+ }
409
+ if (options.commit) {
410
+ const target2 = {
411
+ type: "pipeline_commit_target",
412
+ commit: { hash: options.commit }
413
+ };
414
+ if (options.customPipeline) {
415
+ target2.selector = {
416
+ type: "custom",
417
+ pattern: options.customPipeline
418
+ };
419
+ }
420
+ return target2;
421
+ }
422
+ const target = {
423
+ type: "pipeline_ref_target",
424
+ ref_type: "branch",
425
+ ref_name: options.branch || "main"
426
+ };
427
+ if (options.customPipeline) {
428
+ target.selector = {
429
+ type: "custom",
430
+ pattern: options.customPipeline
431
+ };
432
+ }
433
+ return target;
434
+ }
435
+ /**
436
+ * Normalize pipeline variables to the array format expected by the API.
437
+ * Supports both array format (with secured flag) and simple object format.
438
+ */
439
+ normalizePipelineVariables(variables) {
440
+ if (!variables) {
441
+ return void 0;
442
+ }
443
+ if (Array.isArray(variables)) {
444
+ return variables.map((v) => ({
445
+ key: v.key,
446
+ value: v.value,
447
+ ...v.secured !== void 0 && { secured: v.secured }
448
+ }));
449
+ }
450
+ return Object.entries(variables).map(([key, value]) => ({
451
+ key,
452
+ value
453
+ }));
454
+ }
455
+ async triggerPipeline(repoSlug, options = {}) {
456
+ const payload = {
457
+ target: this.buildPipelineTarget(options)
458
+ };
459
+ const normalizedVariables = this.normalizePipelineVariables(options.variables);
460
+ if (normalizedVariables && normalizedVariables.length > 0) {
461
+ payload.variables = normalizedVariables;
462
+ }
463
+ const result = await this.request(
464
+ "POST",
465
+ `${this.repoPath(repoSlug, "pipelines")}/`,
466
+ payload
467
+ );
468
+ const targetDesc = options.commit ? `commit ${options.commit}` : options.branch || "main";
469
+ const pipelineDesc = options.customPipeline ? `custom:${options.customPipeline}` : "default";
470
+ if (!result) {
471
+ throw new BitbucketError(`Failed to trigger ${pipelineDesc} pipeline on ${targetDesc}`);
472
+ }
473
+ return result;
474
+ }
475
+ async getPipeline(repoSlug, pipelineUuid) {
476
+ return this.request("GET", this.repoPath(repoSlug, "pipelines", ensureUuidBraces(pipelineUuid)));
477
+ }
478
+ async listPipelines(repoSlug, options = {}) {
479
+ return this.paginatedList(`${this.repoPath(repoSlug, "pipelines")}/`, {
480
+ limit: options.limit || 10,
481
+ sort: "-created_on"
482
+ });
483
+ }
484
+ async getPipelineSteps(repoSlug, pipelineUuid) {
485
+ return this.paginatedList(
486
+ `${this.repoPath(repoSlug, "pipelines", ensureUuidBraces(pipelineUuid), "steps")}/`
487
+ );
488
+ }
489
+ async getPipelineLogs(repoSlug, pipelineUuid, stepUuid) {
490
+ const path = this.repoPath(
491
+ repoSlug,
492
+ "pipelines",
493
+ ensureUuidBraces(pipelineUuid),
494
+ "steps",
495
+ ensureUuidBraces(stepUuid),
496
+ "log"
497
+ );
498
+ return await this.requestText(path) || "";
499
+ }
500
+ async stopPipeline(repoSlug, pipelineUuid) {
501
+ await this.request(
502
+ "POST",
503
+ this.repoPath(repoSlug, "pipelines", ensureUuidBraces(pipelineUuid), "stopPipeline")
504
+ );
505
+ const result = await this.getPipeline(repoSlug, pipelineUuid);
506
+ return result || { uuid: pipelineUuid, state: { name: "STOPPED" } };
507
+ }
508
+ // ==================== PIPELINE VARIABLES ====================
509
+ async listPipelineVariables(repoSlug, options = {}) {
510
+ return this.paginatedList(
511
+ this.repoPath(repoSlug, "pipelines_config", "variables"),
512
+ { limit: options.limit || 50 }
513
+ );
514
+ }
515
+ async getPipelineVariable(repoSlug, variableUuid) {
516
+ return this.request(
517
+ "GET",
518
+ this.repoPath(repoSlug, "pipelines_config", "variables", ensureUuidBraces(variableUuid))
519
+ );
520
+ }
521
+ async createPipelineVariable(repoSlug, key, value, secured = false) {
522
+ const result = await this.request(
523
+ "POST",
524
+ `${this.repoPath(repoSlug, "pipelines_config", "variables")}/`,
525
+ { key, value, secured }
526
+ );
527
+ if (!result) {
528
+ throw new BitbucketError("Failed to create pipeline variable");
529
+ }
530
+ return result;
531
+ }
532
+ async updatePipelineVariable(repoSlug, variableUuid, value) {
533
+ const result = await this.request(
534
+ "PUT",
535
+ this.repoPath(repoSlug, "pipelines_config", "variables", ensureUuidBraces(variableUuid)),
536
+ { value }
537
+ );
538
+ if (!result) {
539
+ throw new BitbucketError("Failed to update pipeline variable");
540
+ }
541
+ return result;
542
+ }
543
+ async deletePipelineVariable(repoSlug, variableUuid) {
544
+ await this.request(
545
+ "DELETE",
546
+ this.repoPath(repoSlug, "pipelines_config", "variables", ensureUuidBraces(variableUuid))
547
+ );
548
+ }
549
+ // ==================== BRANCHES ====================
550
+ async listBranches(repoSlug, options = {}) {
551
+ return this.paginatedList(this.repoPath(repoSlug, "refs", "branches"), {
552
+ limit: options.limit || 50
553
+ });
554
+ }
555
+ async getBranch(repoSlug, branchName) {
556
+ return this.request("GET", this.repoPath(repoSlug, "refs", "branches", branchName));
557
+ }
558
+ // ==================== COMMITS ====================
559
+ async listCommits(repoSlug, options = {}) {
560
+ return this.paginatedList(this.repoPath(repoSlug, "commits"), {
561
+ limit: options.limit || 20,
562
+ include: options.branch,
563
+ path: options.path
564
+ });
565
+ }
566
+ async getCommit(repoSlug, commit) {
567
+ return this.request("GET", this.repoPath(repoSlug, "commit", commit));
568
+ }
569
+ async compareCommits(repoSlug, base, head) {
570
+ return this.request("GET", this.repoPath(repoSlug, "diffstat", `${base}..${head}`));
571
+ }
572
+ async getCommitStatuses(repoSlug, commit, options = {}) {
573
+ return this.paginatedList(this.repoPath(repoSlug, "commit", commit, "statuses"), {
574
+ limit: options.limit || 20
575
+ });
576
+ }
577
+ async createCommitStatus(repoSlug, commit, options) {
578
+ const payload = {
579
+ state: options.state,
580
+ key: options.key
581
+ };
582
+ if (options.url) payload.url = options.url;
583
+ if (options.name) payload.name = options.name;
584
+ if (options.description) payload.description = options.description;
585
+ const result = await this.request(
586
+ "POST",
587
+ this.repoPath(repoSlug, "commit", commit, "statuses", "build"),
588
+ payload
589
+ );
590
+ if (!result) {
591
+ throw new BitbucketError(`Failed to create status for commit ${commit}`);
592
+ }
593
+ return result;
594
+ }
595
+ // ==================== PROJECTS ====================
596
+ async listProjects(options = {}) {
597
+ return this.paginatedList(`workspaces/${this.workspace}/projects`, {
598
+ limit: options.limit || 50
599
+ });
600
+ }
601
+ async getProject(projectKey) {
602
+ return this.request("GET", `workspaces/${this.workspace}/projects/${projectKey}`);
603
+ }
604
+ // ==================== DEPLOYMENTS ====================
605
+ async listEnvironments(repoSlug, options = {}) {
606
+ return this.paginatedList(this.repoPath(repoSlug, "environments"), {
607
+ limit: options.limit || 20
608
+ });
609
+ }
610
+ async getEnvironment(repoSlug, environmentUuid) {
611
+ return this.request(
612
+ "GET",
613
+ this.repoPath(repoSlug, "environments", ensureUuidBraces(environmentUuid))
614
+ );
615
+ }
616
+ async listDeploymentHistory(repoSlug, environmentUuid, options = {}) {
617
+ return this.paginatedList(this.repoPath(repoSlug, "deployments"), {
618
+ limit: options.limit || 20,
619
+ environment: ensureUuidBraces(environmentUuid),
620
+ sort: "-state.started_on"
621
+ });
622
+ }
623
+ // ==================== WEBHOOKS ====================
624
+ async listWebhooks(repoSlug, options = {}) {
625
+ return this.paginatedList(this.repoPath(repoSlug, "hooks"), {
626
+ limit: options.limit || 50
627
+ });
628
+ }
629
+ async createWebhook(repoSlug, options) {
630
+ const payload = {
631
+ url: options.url,
632
+ events: options.events,
633
+ active: options.active ?? true
634
+ };
635
+ if (options.description) {
636
+ payload.description = options.description;
637
+ }
638
+ const result = await this.request(
639
+ "POST",
640
+ this.repoPath(repoSlug, "hooks"),
641
+ payload
642
+ );
643
+ if (!result) {
644
+ throw new BitbucketError("Failed to create webhook");
645
+ }
646
+ return result;
647
+ }
648
+ async getWebhook(repoSlug, webhookUid) {
649
+ return this.request("GET", this.repoPath(repoSlug, "hooks", ensureUuidBraces(webhookUid)));
650
+ }
651
+ async deleteWebhook(repoSlug, webhookUid) {
652
+ await this.request("DELETE", this.repoPath(repoSlug, "hooks", ensureUuidBraces(webhookUid)));
653
+ }
654
+ // ==================== TAGS ====================
655
+ async listTags(repoSlug, options = {}) {
656
+ return this.paginatedList(this.repoPath(repoSlug, "refs", "tags"), {
657
+ limit: options.limit || 50,
658
+ sort: "-target.date"
659
+ });
660
+ }
661
+ async createTag(repoSlug, name, target, message) {
662
+ const payload = {
663
+ name,
664
+ target: { hash: target }
665
+ };
666
+ if (message) {
667
+ payload.message = message;
668
+ }
669
+ const result = await this.request(
670
+ "POST",
671
+ this.repoPath(repoSlug, "refs", "tags"),
672
+ payload
673
+ );
674
+ if (!result) {
675
+ throw new BitbucketError(`Failed to create tag ${name}`);
676
+ }
677
+ return result;
678
+ }
679
+ async deleteTag(repoSlug, tagName) {
680
+ await this.request("DELETE", this.repoPath(repoSlug, "refs", "tags", tagName));
681
+ }
682
+ // ==================== BRANCH RESTRICTIONS ====================
683
+ async listBranchRestrictions(repoSlug, options = {}) {
684
+ return this.paginatedList(this.repoPath(repoSlug, "branch-restrictions"), {
685
+ limit: options.limit || 50
686
+ });
687
+ }
688
+ async createBranchRestriction(repoSlug, options) {
689
+ const payload = {
690
+ kind: options.kind,
691
+ branch_match_kind: options.branchMatchKind || "glob"
692
+ };
693
+ if (options.branchMatchKind === "glob" && options.pattern) {
694
+ payload.pattern = options.pattern;
695
+ }
696
+ if (options.branchMatchKind === "branching_model" && options.branchType) {
697
+ payload.branch_type = options.branchType;
698
+ }
699
+ if (options.value !== void 0) {
700
+ payload.value = options.value;
701
+ }
702
+ const result = await this.request(
703
+ "POST",
704
+ this.repoPath(repoSlug, "branch-restrictions"),
705
+ payload
706
+ );
707
+ if (!result) {
708
+ throw new BitbucketError(`Failed to create branch restriction ${options.kind}`);
709
+ }
710
+ return result;
711
+ }
712
+ async deleteBranchRestriction(repoSlug, restrictionId) {
713
+ await this.request(
714
+ "DELETE",
715
+ this.repoPath(repoSlug, "branch-restrictions", String(restrictionId))
716
+ );
717
+ }
718
+ // ==================== SOURCE ====================
719
+ async getFileContent(repoSlug, path, ref = "main") {
720
+ return this.requestText(this.repoPath(repoSlug, "src", ref, path));
721
+ }
722
+ async listDirectory(repoSlug, path = "", options = {}) {
723
+ const endpoint = path ? this.repoPath(repoSlug, "src", options.ref || "main", path) : this.repoPath(repoSlug, "src", options.ref || "main");
724
+ return this.paginatedList(endpoint, { limit: options.limit || 100 });
725
+ }
726
+ // ==================== PERMISSIONS ====================
727
+ async listUserPermissions(repoSlug, options = {}) {
728
+ return this.paginatedList(this.repoPath(repoSlug, "permissions-config", "users"), {
729
+ limit: options.limit || 50
730
+ });
731
+ }
732
+ async getUserPermission(repoSlug, selectedUser) {
733
+ return this.request(
734
+ "GET",
735
+ this.repoPath(repoSlug, "permissions-config", "users", selectedUser)
736
+ );
737
+ }
738
+ async updateUserPermission(repoSlug, selectedUser, permission) {
739
+ const result = await this.request(
740
+ "PUT",
741
+ this.repoPath(repoSlug, "permissions-config", "users", selectedUser),
742
+ { permission }
743
+ );
744
+ if (!result) {
745
+ throw new BitbucketError(`Failed to update permission for user ${selectedUser}`);
746
+ }
747
+ return result;
748
+ }
749
+ async deleteUserPermission(repoSlug, selectedUser) {
750
+ await this.request(
751
+ "DELETE",
752
+ this.repoPath(repoSlug, "permissions-config", "users", selectedUser)
753
+ );
754
+ }
755
+ async listGroupPermissions(repoSlug, options = {}) {
756
+ return this.paginatedList(this.repoPath(repoSlug, "permissions-config", "groups"), {
757
+ limit: options.limit || 50
758
+ });
759
+ }
760
+ async getGroupPermission(repoSlug, groupSlug) {
761
+ return this.request(
762
+ "GET",
763
+ this.repoPath(repoSlug, "permissions-config", "groups", groupSlug)
764
+ );
765
+ }
766
+ async updateGroupPermission(repoSlug, groupSlug, permission) {
767
+ const result = await this.request(
768
+ "PUT",
769
+ this.repoPath(repoSlug, "permissions-config", "groups", groupSlug),
770
+ { permission }
771
+ );
772
+ if (!result) {
773
+ throw new BitbucketError(`Failed to update permission for group ${groupSlug}`);
774
+ }
775
+ return result;
776
+ }
777
+ async deleteGroupPermission(repoSlug, groupSlug) {
778
+ await this.request(
779
+ "DELETE",
780
+ this.repoPath(repoSlug, "permissions-config", "groups", groupSlug)
781
+ );
782
+ }
783
+ // ==================== UTILITIES ====================
784
+ extractPrUrl(pr) {
785
+ return pr.links?.html?.href || "";
786
+ }
787
+ extractCloneUrls(repo) {
788
+ const urls = {};
789
+ for (const link of repo.links?.clone || []) {
790
+ const name = (link.name || "").toLowerCase();
791
+ if (name === "https" || name === "ssh") {
792
+ urls[name] = link.href || "";
793
+ }
794
+ }
795
+ urls.html = repo.links?.html?.href || "";
796
+ return urls;
797
+ }
798
+ };
799
+ var clientInstance = null;
800
+ function getClient() {
801
+ if (!clientInstance) {
802
+ clientInstance = new BitbucketClient();
803
+ }
804
+ return clientInstance;
805
+ }
806
+
807
+ // src/tools/repositories.ts
808
+ var definitions = [
809
+ {
810
+ name: "get_repository",
811
+ description: "Get information about a Bitbucket repository.",
812
+ inputSchema: {
813
+ type: "object",
814
+ properties: {
815
+ repo_slug: {
816
+ type: "string",
817
+ description: 'Repository slug (e.g., "anzsic_classifier")'
818
+ }
819
+ },
820
+ required: ["repo_slug"]
821
+ }
822
+ },
823
+ {
824
+ name: "create_repository",
825
+ description: "Create a new Bitbucket repository.",
826
+ inputSchema: {
827
+ type: "object",
828
+ properties: {
829
+ repo_slug: {
830
+ type: "string",
831
+ description: "Repository slug (lowercase, no spaces)"
832
+ },
833
+ project_key: {
834
+ type: "string",
835
+ description: "Project key to create repo under (optional)"
836
+ },
837
+ is_private: {
838
+ type: "boolean",
839
+ description: "Whether repository is private (default: true)",
840
+ default: true
841
+ },
842
+ description: {
843
+ type: "string",
844
+ description: "Repository description",
845
+ default: ""
846
+ }
847
+ },
848
+ required: ["repo_slug"]
849
+ }
850
+ },
851
+ {
852
+ name: "delete_repository",
853
+ description: "Delete a Bitbucket repository. WARNING: This action is irreversible!",
854
+ inputSchema: {
855
+ type: "object",
856
+ properties: {
857
+ repo_slug: {
858
+ type: "string",
859
+ description: "Repository slug to delete"
860
+ }
861
+ },
862
+ required: ["repo_slug"]
863
+ }
864
+ },
865
+ {
866
+ name: "list_repositories",
867
+ description: "List and search repositories in the workspace.",
868
+ inputSchema: {
869
+ type: "object",
870
+ properties: {
871
+ project_key: {
872
+ type: "string",
873
+ description: "Filter by project key (optional)"
874
+ },
875
+ search: {
876
+ type: "string",
877
+ description: "Simple search term for repository name (optional). Uses fuzzy matching."
878
+ },
879
+ query: {
880
+ type: "string",
881
+ description: 'Advanced Bitbucket query syntax (optional). Examples: name ~ "api", is_private = false'
882
+ },
883
+ limit: {
884
+ type: "number",
885
+ description: "Maximum number of results (default: 50, max: 100)",
886
+ default: 50
887
+ }
888
+ },
889
+ required: []
890
+ }
891
+ },
892
+ {
893
+ name: "update_repository",
894
+ description: "Update repository settings (project, visibility, description, name).",
895
+ inputSchema: {
896
+ type: "object",
897
+ properties: {
898
+ repo_slug: {
899
+ type: "string",
900
+ description: "Repository slug"
901
+ },
902
+ project_key: {
903
+ type: "string",
904
+ description: "Move to different project (optional)"
905
+ },
906
+ is_private: {
907
+ type: "boolean",
908
+ description: "Change visibility (optional)"
909
+ },
910
+ description: {
911
+ type: "string",
912
+ description: "Update description (optional)"
913
+ },
914
+ name: {
915
+ type: "string",
916
+ description: "Rename repository (optional)"
917
+ }
918
+ },
919
+ required: ["repo_slug"]
920
+ }
921
+ }
922
+ ];
923
+ var handlers = {
924
+ get_repository: async (args) => {
925
+ const client = getClient();
926
+ const result = await client.getRepository(args.repo_slug);
927
+ if (!result) {
928
+ return notFoundResponse("Repository", args.repo_slug);
929
+ }
930
+ return {
931
+ name: result.name,
932
+ full_name: result.full_name,
933
+ private: result.is_private,
934
+ project: result.project?.key,
935
+ description: result.description || "",
936
+ main_branch: result.mainbranch?.name,
937
+ clone_urls: client.extractCloneUrls(result),
938
+ created: result.created_on,
939
+ updated: result.updated_on
940
+ };
941
+ },
942
+ create_repository: async (args) => {
943
+ const client = getClient();
944
+ const result = await client.createRepository(args.repo_slug, {
945
+ projectKey: args.project_key,
946
+ isPrivate: args.is_private,
947
+ description: args.description
948
+ });
949
+ return {
950
+ name: result.name,
951
+ full_name: result.full_name,
952
+ clone_urls: client.extractCloneUrls(result)
953
+ };
954
+ },
955
+ delete_repository: async (args) => {
956
+ const client = getClient();
957
+ await client.deleteRepository(args.repo_slug);
958
+ return {};
959
+ },
960
+ list_repositories: async (args) => {
961
+ const client = getClient();
962
+ let effectiveQuery = args.query;
963
+ if (args.search && !args.query) {
964
+ const safeSearch = sanitizeSearchTerm(args.search);
965
+ effectiveQuery = `name ~ "${safeSearch}"`;
966
+ }
967
+ const repos = await client.listRepositories({
968
+ projectKey: args.project_key,
969
+ query: effectiveQuery,
970
+ limit: validateLimit(args.limit || 50)
971
+ });
972
+ return {
973
+ repositories: repos.map((r) => ({
974
+ name: r.name,
975
+ full_name: r.full_name,
976
+ private: r.is_private,
977
+ project: r.project?.key,
978
+ description: r.description || ""
979
+ }))
980
+ };
981
+ },
982
+ update_repository: async (args) => {
983
+ const client = getClient();
984
+ const result = await client.updateRepository(args.repo_slug, {
985
+ projectKey: args.project_key,
986
+ isPrivate: args.is_private,
987
+ description: args.description,
988
+ name: args.name
989
+ });
990
+ return {
991
+ name: result.name,
992
+ full_name: result.full_name,
993
+ project: result.project?.key,
994
+ private: result.is_private,
995
+ description: result.description || ""
996
+ };
997
+ }
998
+ };
999
+
1000
+ // src/types.ts
1001
+ var PRState = /* @__PURE__ */ ((PRState2) => {
1002
+ PRState2["OPEN"] = "OPEN";
1003
+ PRState2["MERGED"] = "MERGED";
1004
+ PRState2["DECLINED"] = "DECLINED";
1005
+ PRState2["SUPERSEDED"] = "SUPERSEDED";
1006
+ return PRState2;
1007
+ })(PRState || {});
1008
+ var MergeStrategy = /* @__PURE__ */ ((MergeStrategy2) => {
1009
+ MergeStrategy2["MERGE_COMMIT"] = "merge_commit";
1010
+ MergeStrategy2["SQUASH"] = "squash";
1011
+ MergeStrategy2["FAST_FORWARD"] = "fast_forward";
1012
+ return MergeStrategy2;
1013
+ })(MergeStrategy || {});
1014
+ var CommitStatusState = /* @__PURE__ */ ((CommitStatusState2) => {
1015
+ CommitStatusState2["SUCCESSFUL"] = "SUCCESSFUL";
1016
+ CommitStatusState2["FAILED"] = "FAILED";
1017
+ CommitStatusState2["INPROGRESS"] = "INPROGRESS";
1018
+ CommitStatusState2["STOPPED"] = "STOPPED";
1019
+ return CommitStatusState2;
1020
+ })(CommitStatusState || {});
1021
+
1022
+ // src/tools/pull-requests.ts
1023
+ var definitions2 = [
1024
+ {
1025
+ name: "create_pull_request",
1026
+ description: "Create a pull request in a Bitbucket repository.",
1027
+ inputSchema: {
1028
+ type: "object",
1029
+ properties: {
1030
+ repo_slug: { type: "string", description: "Repository slug" },
1031
+ title: { type: "string", description: "PR title" },
1032
+ source_branch: { type: "string", description: "Source branch name" },
1033
+ destination_branch: { type: "string", description: "Target branch (default: main)", default: "main" },
1034
+ description: { type: "string", description: "PR description in markdown", default: "" },
1035
+ close_source_branch: { type: "boolean", description: "Delete source branch after merge", default: true }
1036
+ },
1037
+ required: ["repo_slug", "title", "source_branch"]
1038
+ }
1039
+ },
1040
+ {
1041
+ name: "get_pull_request",
1042
+ description: "Get information about a pull request.",
1043
+ inputSchema: {
1044
+ type: "object",
1045
+ properties: {
1046
+ repo_slug: { type: "string", description: "Repository slug" },
1047
+ pr_id: { type: "number", description: "Pull request ID" }
1048
+ },
1049
+ required: ["repo_slug", "pr_id"]
1050
+ }
1051
+ },
1052
+ {
1053
+ name: "list_pull_requests",
1054
+ description: "List pull requests in a repository.",
1055
+ inputSchema: {
1056
+ type: "object",
1057
+ properties: {
1058
+ repo_slug: { type: "string", description: "Repository slug" },
1059
+ state: { type: "string", description: "Filter by state: OPEN, MERGED, DECLINED, SUPERSEDED", default: "OPEN" },
1060
+ limit: { type: "number", description: "Maximum results (default: 20, max: 100)", default: 20 }
1061
+ },
1062
+ required: ["repo_slug"]
1063
+ }
1064
+ },
1065
+ {
1066
+ name: "merge_pull_request",
1067
+ description: "Merge a pull request.",
1068
+ inputSchema: {
1069
+ type: "object",
1070
+ properties: {
1071
+ repo_slug: { type: "string", description: "Repository slug" },
1072
+ pr_id: { type: "number", description: "Pull request ID" },
1073
+ merge_strategy: { type: "string", description: "merge_commit, squash, or fast_forward", default: "merge_commit" },
1074
+ close_source_branch: { type: "boolean", description: "Delete source branch after merge", default: true },
1075
+ message: { type: "string", description: "Optional merge commit message" }
1076
+ },
1077
+ required: ["repo_slug", "pr_id"]
1078
+ }
1079
+ },
1080
+ {
1081
+ name: "list_pr_comments",
1082
+ description: "List comments on a pull request.",
1083
+ inputSchema: {
1084
+ type: "object",
1085
+ properties: {
1086
+ repo_slug: { type: "string", description: "Repository slug" },
1087
+ pr_id: { type: "number", description: "Pull request ID" },
1088
+ limit: { type: "number", description: "Maximum results (default: 50)", default: 50 }
1089
+ },
1090
+ required: ["repo_slug", "pr_id"]
1091
+ }
1092
+ },
1093
+ {
1094
+ name: "add_pr_comment",
1095
+ description: "Add a comment to a pull request. Can add general or inline comments.",
1096
+ inputSchema: {
1097
+ type: "object",
1098
+ properties: {
1099
+ repo_slug: { type: "string", description: "Repository slug" },
1100
+ pr_id: { type: "number", description: "Pull request ID" },
1101
+ content: { type: "string", description: "Comment content (markdown supported)" },
1102
+ file_path: { type: "string", description: "File path for inline comment (optional)" },
1103
+ line: { type: "number", description: "Line number for inline comment (optional, requires file_path)" }
1104
+ },
1105
+ required: ["repo_slug", "pr_id", "content"]
1106
+ }
1107
+ },
1108
+ {
1109
+ name: "approve_pr",
1110
+ description: "Approve a pull request.",
1111
+ inputSchema: {
1112
+ type: "object",
1113
+ properties: {
1114
+ repo_slug: { type: "string", description: "Repository slug" },
1115
+ pr_id: { type: "number", description: "Pull request ID" }
1116
+ },
1117
+ required: ["repo_slug", "pr_id"]
1118
+ }
1119
+ },
1120
+ {
1121
+ name: "unapprove_pr",
1122
+ description: "Remove your approval from a pull request.",
1123
+ inputSchema: {
1124
+ type: "object",
1125
+ properties: {
1126
+ repo_slug: { type: "string", description: "Repository slug" },
1127
+ pr_id: { type: "number", description: "Pull request ID" }
1128
+ },
1129
+ required: ["repo_slug", "pr_id"]
1130
+ }
1131
+ },
1132
+ {
1133
+ name: "request_changes_pr",
1134
+ description: "Request changes on a pull request.",
1135
+ inputSchema: {
1136
+ type: "object",
1137
+ properties: {
1138
+ repo_slug: { type: "string", description: "Repository slug" },
1139
+ pr_id: { type: "number", description: "Pull request ID" }
1140
+ },
1141
+ required: ["repo_slug", "pr_id"]
1142
+ }
1143
+ },
1144
+ {
1145
+ name: "decline_pr",
1146
+ description: "Decline (close without merging) a pull request.",
1147
+ inputSchema: {
1148
+ type: "object",
1149
+ properties: {
1150
+ repo_slug: { type: "string", description: "Repository slug" },
1151
+ pr_id: { type: "number", description: "Pull request ID" }
1152
+ },
1153
+ required: ["repo_slug", "pr_id"]
1154
+ }
1155
+ },
1156
+ {
1157
+ name: "get_pr_diff",
1158
+ description: "Get the diff of a pull request.",
1159
+ inputSchema: {
1160
+ type: "object",
1161
+ properties: {
1162
+ repo_slug: { type: "string", description: "Repository slug" },
1163
+ pr_id: { type: "number", description: "Pull request ID" }
1164
+ },
1165
+ required: ["repo_slug", "pr_id"]
1166
+ }
1167
+ }
1168
+ ];
1169
+ var handlers2 = {
1170
+ create_pull_request: async (args) => {
1171
+ const client = getClient();
1172
+ const result = await client.createPullRequest(args.repo_slug, {
1173
+ title: args.title,
1174
+ sourceBranch: args.source_branch,
1175
+ destinationBranch: args.destination_branch || "main",
1176
+ description: args.description,
1177
+ closeSourceBranch: args.close_source_branch ?? true
1178
+ });
1179
+ return {
1180
+ id: result.id,
1181
+ title: result.title,
1182
+ state: result.state,
1183
+ url: client.extractPrUrl(result)
1184
+ };
1185
+ },
1186
+ get_pull_request: async (args) => {
1187
+ const client = getClient();
1188
+ const result = await client.getPullRequest(args.repo_slug, args.pr_id);
1189
+ if (!result) {
1190
+ return notFoundResponse("PR", `#${args.pr_id}`);
1191
+ }
1192
+ return {
1193
+ id: result.id,
1194
+ title: result.title,
1195
+ description: result.description,
1196
+ state: result.state,
1197
+ author: result.author?.display_name,
1198
+ source_branch: result.source?.branch?.name,
1199
+ destination_branch: result.destination?.branch?.name,
1200
+ reviewers: result.reviewers?.map((r) => r.display_name) || [],
1201
+ url: client.extractPrUrl(result),
1202
+ created: result.created_on,
1203
+ updated: result.updated_on
1204
+ };
1205
+ },
1206
+ list_pull_requests: async (args) => {
1207
+ const client = getClient();
1208
+ const state = (args.state || "OPEN").toUpperCase();
1209
+ const validState = Object.values(PRState).includes(state) ? state : "OPEN";
1210
+ const prs = await client.listPullRequests(args.repo_slug, {
1211
+ state: validState,
1212
+ limit: validateLimit(args.limit || 20)
1213
+ });
1214
+ return {
1215
+ pull_requests: prs.map((pr) => ({
1216
+ id: pr.id,
1217
+ title: pr.title,
1218
+ state: pr.state,
1219
+ author: pr.author?.display_name,
1220
+ source_branch: pr.source?.branch?.name,
1221
+ destination_branch: pr.destination?.branch?.name,
1222
+ url: client.extractPrUrl(pr)
1223
+ }))
1224
+ };
1225
+ },
1226
+ merge_pull_request: async (args) => {
1227
+ const client = getClient();
1228
+ const strategy = (args.merge_strategy || "merge_commit").toLowerCase();
1229
+ const validStrategy = Object.values(MergeStrategy).includes(strategy) ? strategy : "merge_commit";
1230
+ const result = await client.mergePullRequest(args.repo_slug, args.pr_id, {
1231
+ mergeStrategy: validStrategy,
1232
+ closeSourceBranch: args.close_source_branch ?? true,
1233
+ message: args.message
1234
+ });
1235
+ return {
1236
+ id: result.id,
1237
+ state: result.state,
1238
+ merge_commit: result.merge_commit?.hash,
1239
+ url: client.extractPrUrl(result)
1240
+ };
1241
+ },
1242
+ list_pr_comments: async (args) => {
1243
+ const client = getClient();
1244
+ const comments = await client.listPrComments(args.repo_slug, args.pr_id, {
1245
+ limit: validateLimit(args.limit || 50)
1246
+ });
1247
+ return {
1248
+ pr_id: args.pr_id,
1249
+ comments: comments.map((c) => ({
1250
+ id: c.id,
1251
+ content: c.content?.raw || "",
1252
+ author: c.user?.display_name,
1253
+ created: c.created_on,
1254
+ inline: c.inline ? { path: c.inline.path, line: c.inline.to } : void 0
1255
+ }))
1256
+ };
1257
+ },
1258
+ add_pr_comment: async (args) => {
1259
+ const client = getClient();
1260
+ let inline;
1261
+ if (args.file_path && args.line) {
1262
+ inline = { path: args.file_path, to: args.line };
1263
+ }
1264
+ const result = await client.addPrComment(
1265
+ args.repo_slug,
1266
+ args.pr_id,
1267
+ args.content,
1268
+ inline
1269
+ );
1270
+ return {
1271
+ id: result.id,
1272
+ content: result.content?.raw || "",
1273
+ inline
1274
+ };
1275
+ },
1276
+ approve_pr: async (args) => {
1277
+ const client = getClient();
1278
+ const result = await client.approvePr(args.repo_slug, args.pr_id);
1279
+ return {
1280
+ pr_id: args.pr_id,
1281
+ approved_by: result.user?.display_name
1282
+ };
1283
+ },
1284
+ unapprove_pr: async (args) => {
1285
+ const client = getClient();
1286
+ await client.unapprovePr(args.repo_slug, args.pr_id);
1287
+ return { pr_id: args.pr_id };
1288
+ },
1289
+ request_changes_pr: async (args) => {
1290
+ const client = getClient();
1291
+ const result = await client.requestChangesPr(args.repo_slug, args.pr_id);
1292
+ return {
1293
+ pr_id: args.pr_id,
1294
+ requested_by: result.user?.display_name
1295
+ };
1296
+ },
1297
+ decline_pr: async (args) => {
1298
+ const client = getClient();
1299
+ const result = await client.declinePr(args.repo_slug, args.pr_id);
1300
+ return {
1301
+ pr_id: args.pr_id,
1302
+ state: result.state
1303
+ };
1304
+ },
1305
+ get_pr_diff: async (args) => {
1306
+ const client = getClient();
1307
+ const diff = await client.getPrDiff(args.repo_slug, args.pr_id);
1308
+ if (!diff) {
1309
+ return { error: `PR #${args.pr_id} not found or has no diff` };
1310
+ }
1311
+ const maxLength = 5e4;
1312
+ const truncated = diff.length > maxLength;
1313
+ return {
1314
+ pr_id: args.pr_id,
1315
+ diff: truncated ? diff.substring(0, maxLength) : diff,
1316
+ truncated,
1317
+ total_length: diff.length
1318
+ };
1319
+ }
1320
+ };
1321
+
1322
+ // src/tools/pipelines.ts
1323
+ var definitions3 = [
1324
+ {
1325
+ name: "trigger_pipeline",
1326
+ description: 'Trigger a pipeline run. Supports custom pipelines (from "custom:" section in bitbucket-pipelines.yml) and commit-based triggers.',
1327
+ inputSchema: {
1328
+ type: "object",
1329
+ properties: {
1330
+ repo_slug: { type: "string", description: "Repository slug" },
1331
+ branch: { type: "string", description: "Branch to run pipeline on (default: main). Mutually exclusive with commit.", default: "main" },
1332
+ commit: { type: "string", description: "Commit hash to run pipeline on. Mutually exclusive with branch." },
1333
+ custom_pipeline: { type: "string", description: 'Name of custom pipeline from "custom:" section (e.g., "deploy-staging", "dry-run")' },
1334
+ variables: {
1335
+ type: "array",
1336
+ description: "Pipeline variables. Can be array of {key, value, secured?} or simple {key: value} object for backwards compatibility.",
1337
+ items: {
1338
+ type: "object",
1339
+ properties: {
1340
+ key: { type: "string", description: "Variable name" },
1341
+ value: { type: "string", description: "Variable value" },
1342
+ secured: { type: "boolean", description: "Whether to mark as secured (encrypted)", default: false }
1343
+ },
1344
+ required: ["key", "value"]
1345
+ }
1346
+ }
1347
+ },
1348
+ required: ["repo_slug"]
1349
+ }
1350
+ },
1351
+ {
1352
+ name: "get_pipeline",
1353
+ description: "Get status of a pipeline run.",
1354
+ inputSchema: {
1355
+ type: "object",
1356
+ properties: {
1357
+ repo_slug: { type: "string", description: "Repository slug" },
1358
+ pipeline_uuid: { type: "string", description: "Pipeline UUID" }
1359
+ },
1360
+ required: ["repo_slug", "pipeline_uuid"]
1361
+ }
1362
+ },
1363
+ {
1364
+ name: "list_pipelines",
1365
+ description: "List recent pipeline runs for a repository.",
1366
+ inputSchema: {
1367
+ type: "object",
1368
+ properties: {
1369
+ repo_slug: { type: "string", description: "Repository slug" },
1370
+ limit: { type: "number", description: "Maximum results (default: 10)", default: 10 }
1371
+ },
1372
+ required: ["repo_slug"]
1373
+ }
1374
+ },
1375
+ {
1376
+ name: "get_pipeline_logs",
1377
+ description: "Get logs for a pipeline run. If step_uuid is not provided, returns list of steps.",
1378
+ inputSchema: {
1379
+ type: "object",
1380
+ properties: {
1381
+ repo_slug: { type: "string", description: "Repository slug" },
1382
+ pipeline_uuid: { type: "string", description: "Pipeline UUID" },
1383
+ step_uuid: { type: "string", description: "Step UUID (optional, get from steps list first)" }
1384
+ },
1385
+ required: ["repo_slug", "pipeline_uuid"]
1386
+ }
1387
+ },
1388
+ {
1389
+ name: "stop_pipeline",
1390
+ description: "Stop a running pipeline.",
1391
+ inputSchema: {
1392
+ type: "object",
1393
+ properties: {
1394
+ repo_slug: { type: "string", description: "Repository slug" },
1395
+ pipeline_uuid: { type: "string", description: "Pipeline UUID" }
1396
+ },
1397
+ required: ["repo_slug", "pipeline_uuid"]
1398
+ }
1399
+ },
1400
+ {
1401
+ name: "list_pipeline_variables",
1402
+ description: "List pipeline variables for a repository.",
1403
+ inputSchema: {
1404
+ type: "object",
1405
+ properties: {
1406
+ repo_slug: { type: "string", description: "Repository slug" },
1407
+ limit: { type: "number", description: "Maximum results (default: 50)", default: 50 }
1408
+ },
1409
+ required: ["repo_slug"]
1410
+ }
1411
+ },
1412
+ {
1413
+ name: "get_pipeline_variable",
1414
+ description: "Get details about a specific pipeline variable.",
1415
+ inputSchema: {
1416
+ type: "object",
1417
+ properties: {
1418
+ repo_slug: { type: "string", description: "Repository slug" },
1419
+ variable_uuid: { type: "string", description: "Variable UUID" }
1420
+ },
1421
+ required: ["repo_slug", "variable_uuid"]
1422
+ }
1423
+ },
1424
+ {
1425
+ name: "create_pipeline_variable",
1426
+ description: "Create a pipeline variable.",
1427
+ inputSchema: {
1428
+ type: "object",
1429
+ properties: {
1430
+ repo_slug: { type: "string", description: "Repository slug" },
1431
+ key: { type: "string", description: "Variable name" },
1432
+ value: { type: "string", description: "Variable value" },
1433
+ secured: { type: "boolean", description: "Encrypt the value (secured variables cannot be read back)", default: false }
1434
+ },
1435
+ required: ["repo_slug", "key", "value"]
1436
+ }
1437
+ },
1438
+ {
1439
+ name: "update_pipeline_variable",
1440
+ description: "Update a pipeline variable's value.",
1441
+ inputSchema: {
1442
+ type: "object",
1443
+ properties: {
1444
+ repo_slug: { type: "string", description: "Repository slug" },
1445
+ variable_uuid: { type: "string", description: "Variable UUID" },
1446
+ value: { type: "string", description: "New variable value" }
1447
+ },
1448
+ required: ["repo_slug", "variable_uuid", "value"]
1449
+ }
1450
+ },
1451
+ {
1452
+ name: "delete_pipeline_variable",
1453
+ description: "Delete a pipeline variable.",
1454
+ inputSchema: {
1455
+ type: "object",
1456
+ properties: {
1457
+ repo_slug: { type: "string", description: "Repository slug" },
1458
+ variable_uuid: { type: "string", description: "Variable UUID" }
1459
+ },
1460
+ required: ["repo_slug", "variable_uuid"]
1461
+ }
1462
+ }
1463
+ ];
1464
+ var handlers3 = {
1465
+ trigger_pipeline: async (args) => {
1466
+ const client = getClient();
1467
+ const result = await client.triggerPipeline(args.repo_slug, {
1468
+ branch: args.branch,
1469
+ commit: args.commit,
1470
+ customPipeline: args.custom_pipeline,
1471
+ variables: args.variables
1472
+ });
1473
+ return {
1474
+ uuid: result.uuid,
1475
+ build_number: result.build_number,
1476
+ state: result.state?.name
1477
+ };
1478
+ },
1479
+ get_pipeline: async (args) => {
1480
+ const client = getClient();
1481
+ const result = await client.getPipeline(args.repo_slug, args.pipeline_uuid);
1482
+ if (!result) {
1483
+ return notFoundResponse("Pipeline", args.pipeline_uuid);
1484
+ }
1485
+ return {
1486
+ uuid: result.uuid,
1487
+ build_number: result.build_number,
1488
+ state: result.state?.name,
1489
+ result: result.state?.result?.name,
1490
+ branch: result.target?.ref_name,
1491
+ created: result.created_on,
1492
+ completed: result.completed_on,
1493
+ duration: result.duration_in_seconds
1494
+ };
1495
+ },
1496
+ list_pipelines: async (args) => {
1497
+ const client = getClient();
1498
+ const pipelines = await client.listPipelines(args.repo_slug, {
1499
+ limit: validateLimit(args.limit || 10)
1500
+ });
1501
+ return {
1502
+ pipelines: pipelines.map((p) => ({
1503
+ uuid: p.uuid,
1504
+ build_number: p.build_number,
1505
+ state: p.state?.name,
1506
+ result: p.state?.result?.name,
1507
+ branch: p.target?.ref_name,
1508
+ created: p.created_on
1509
+ }))
1510
+ };
1511
+ },
1512
+ get_pipeline_logs: async (args) => {
1513
+ const client = getClient();
1514
+ const pipelineUuid = args.pipeline_uuid;
1515
+ const stepUuid = args.step_uuid;
1516
+ if (!stepUuid) {
1517
+ const steps = await client.getPipelineSteps(args.repo_slug, pipelineUuid);
1518
+ return {
1519
+ message: "Provide step_uuid to get logs for a specific step",
1520
+ steps: steps.map((s) => ({
1521
+ uuid: s.uuid,
1522
+ name: s.name,
1523
+ state: s.state?.name,
1524
+ result: s.state?.result?.name,
1525
+ duration: s.duration_in_seconds
1526
+ }))
1527
+ };
1528
+ }
1529
+ const logs = await client.getPipelineLogs(args.repo_slug, pipelineUuid, stepUuid);
1530
+ return {
1531
+ step_uuid: stepUuid,
1532
+ logs: logs || "(no logs available)"
1533
+ };
1534
+ },
1535
+ stop_pipeline: async (args) => {
1536
+ const client = getClient();
1537
+ const result = await client.stopPipeline(args.repo_slug, args.pipeline_uuid);
1538
+ return {
1539
+ uuid: result.uuid,
1540
+ state: result.state?.name
1541
+ };
1542
+ },
1543
+ list_pipeline_variables: async (args) => {
1544
+ const client = getClient();
1545
+ const variables = await client.listPipelineVariables(args.repo_slug, {
1546
+ limit: validateLimit(args.limit || 50)
1547
+ });
1548
+ return {
1549
+ variables: variables.map((v) => ({
1550
+ uuid: v.uuid,
1551
+ key: v.key,
1552
+ secured: v.secured,
1553
+ value: v.secured ? void 0 : v.value
1554
+ }))
1555
+ };
1556
+ },
1557
+ get_pipeline_variable: async (args) => {
1558
+ const client = getClient();
1559
+ const result = await client.getPipelineVariable(args.repo_slug, args.variable_uuid);
1560
+ if (!result) {
1561
+ return notFoundResponse("Pipeline variable", args.variable_uuid);
1562
+ }
1563
+ return {
1564
+ uuid: result.uuid,
1565
+ key: result.key,
1566
+ secured: result.secured,
1567
+ value: result.secured ? void 0 : result.value
1568
+ };
1569
+ },
1570
+ create_pipeline_variable: async (args) => {
1571
+ const client = getClient();
1572
+ const result = await client.createPipelineVariable(
1573
+ args.repo_slug,
1574
+ args.key,
1575
+ args.value,
1576
+ args.secured ?? false
1577
+ );
1578
+ return {
1579
+ uuid: result.uuid,
1580
+ key: result.key,
1581
+ secured: result.secured
1582
+ };
1583
+ },
1584
+ update_pipeline_variable: async (args) => {
1585
+ const client = getClient();
1586
+ const result = await client.updatePipelineVariable(
1587
+ args.repo_slug,
1588
+ args.variable_uuid,
1589
+ args.value
1590
+ );
1591
+ return {
1592
+ uuid: result.uuid,
1593
+ key: result.key,
1594
+ secured: result.secured
1595
+ };
1596
+ },
1597
+ delete_pipeline_variable: async (args) => {
1598
+ const client = getClient();
1599
+ await client.deletePipelineVariable(args.repo_slug, args.variable_uuid);
1600
+ return {};
1601
+ }
1602
+ };
1603
+
1604
+ // src/tools/branches.ts
1605
+ var definitions4 = [
1606
+ {
1607
+ name: "list_branches",
1608
+ description: "List branches in a repository.",
1609
+ inputSchema: {
1610
+ type: "object",
1611
+ properties: {
1612
+ repo_slug: { type: "string", description: "Repository slug" },
1613
+ limit: { type: "number", description: "Maximum results (default: 50)", default: 50 }
1614
+ },
1615
+ required: ["repo_slug"]
1616
+ }
1617
+ },
1618
+ {
1619
+ name: "get_branch",
1620
+ description: "Get information about a specific branch.",
1621
+ inputSchema: {
1622
+ type: "object",
1623
+ properties: {
1624
+ repo_slug: { type: "string", description: "Repository slug" },
1625
+ branch_name: { type: "string", description: "Branch name" }
1626
+ },
1627
+ required: ["repo_slug", "branch_name"]
1628
+ }
1629
+ }
1630
+ ];
1631
+ var handlers4 = {
1632
+ list_branches: async (args) => {
1633
+ const client = getClient();
1634
+ const branches = await client.listBranches(args.repo_slug, {
1635
+ limit: validateLimit(args.limit || 50)
1636
+ });
1637
+ return {
1638
+ branches: branches.map((b) => ({
1639
+ name: b.name,
1640
+ commit: b.target?.hash?.substring(0, 7),
1641
+ message: b.target?.message,
1642
+ date: b.target?.date
1643
+ }))
1644
+ };
1645
+ },
1646
+ get_branch: async (args) => {
1647
+ const client = getClient();
1648
+ const result = await client.getBranch(args.repo_slug, args.branch_name);
1649
+ if (!result) {
1650
+ return notFoundResponse("Branch", args.branch_name);
1651
+ }
1652
+ return {
1653
+ name: result.name,
1654
+ latest_commit: {
1655
+ hash: result.target?.hash,
1656
+ message: result.target?.message || "",
1657
+ author: result.target?.author?.raw,
1658
+ date: result.target?.date
1659
+ }
1660
+ };
1661
+ }
1662
+ };
1663
+
1664
+ // src/tools/commits.ts
1665
+ var definitions5 = [
1666
+ {
1667
+ name: "list_commits",
1668
+ description: "List commits in a repository.",
1669
+ inputSchema: {
1670
+ type: "object",
1671
+ properties: {
1672
+ repo_slug: { type: "string", description: "Repository slug" },
1673
+ branch: { type: "string", description: "Filter by branch name (optional)" },
1674
+ path: { type: "string", description: "Filter by file path - only commits that modified this path (optional)" },
1675
+ limit: { type: "number", description: "Maximum results (default: 20)", default: 20 }
1676
+ },
1677
+ required: ["repo_slug"]
1678
+ }
1679
+ },
1680
+ {
1681
+ name: "get_commit",
1682
+ description: "Get detailed information about a specific commit.",
1683
+ inputSchema: {
1684
+ type: "object",
1685
+ properties: {
1686
+ repo_slug: { type: "string", description: "Repository slug" },
1687
+ commit: { type: "string", description: "Commit hash (full or short)" }
1688
+ },
1689
+ required: ["repo_slug", "commit"]
1690
+ }
1691
+ },
1692
+ {
1693
+ name: "compare_commits",
1694
+ description: "Compare two commits or branches and see files changed.",
1695
+ inputSchema: {
1696
+ type: "object",
1697
+ properties: {
1698
+ repo_slug: { type: "string", description: "Repository slug" },
1699
+ base: { type: "string", description: "Base commit hash or branch name" },
1700
+ head: { type: "string", description: "Head commit hash or branch name" }
1701
+ },
1702
+ required: ["repo_slug", "base", "head"]
1703
+ }
1704
+ },
1705
+ {
1706
+ name: "get_commit_statuses",
1707
+ description: "Get build/CI statuses for a commit.",
1708
+ inputSchema: {
1709
+ type: "object",
1710
+ properties: {
1711
+ repo_slug: { type: "string", description: "Repository slug" },
1712
+ commit: { type: "string", description: "Commit hash" },
1713
+ limit: { type: "number", description: "Maximum results (default: 20)", default: 20 }
1714
+ },
1715
+ required: ["repo_slug", "commit"]
1716
+ }
1717
+ },
1718
+ {
1719
+ name: "create_commit_status",
1720
+ description: "Create a build status for a commit. Use this to report CI/CD status from external systems.",
1721
+ inputSchema: {
1722
+ type: "object",
1723
+ properties: {
1724
+ repo_slug: { type: "string", description: "Repository slug" },
1725
+ commit: { type: "string", description: "Commit hash" },
1726
+ state: { type: "string", description: "Status state: SUCCESSFUL, FAILED, INPROGRESS, STOPPED" },
1727
+ key: { type: "string", description: "Unique identifier for this status" },
1728
+ url: { type: "string", description: "URL to the build details (optional)" },
1729
+ name: { type: "string", description: "Display name for the status (optional)" },
1730
+ description: { type: "string", description: "Status description (optional)" }
1731
+ },
1732
+ required: ["repo_slug", "commit", "state", "key"]
1733
+ }
1734
+ }
1735
+ ];
1736
+ var handlers5 = {
1737
+ list_commits: async (args) => {
1738
+ const client = getClient();
1739
+ const commits = await client.listCommits(args.repo_slug, {
1740
+ branch: args.branch,
1741
+ path: args.path,
1742
+ limit: validateLimit(args.limit || 20)
1743
+ });
1744
+ return {
1745
+ commits: commits.map((c) => ({
1746
+ hash: truncateHash(c.hash),
1747
+ message: c.message,
1748
+ author: c.author?.raw,
1749
+ date: c.date
1750
+ }))
1751
+ };
1752
+ },
1753
+ get_commit: async (args) => {
1754
+ const client = getClient();
1755
+ const result = await client.getCommit(args.repo_slug, args.commit);
1756
+ if (!result) {
1757
+ return notFoundResponse("Commit", args.commit);
1758
+ }
1759
+ return {
1760
+ hash: result.hash,
1761
+ message: result.message,
1762
+ author: result.author?.raw,
1763
+ date: result.date,
1764
+ parents: result.parents?.map((p) => truncateHash(p.hash))
1765
+ };
1766
+ },
1767
+ compare_commits: async (args) => {
1768
+ const client = getClient();
1769
+ const result = await client.compareCommits(
1770
+ args.repo_slug,
1771
+ args.base,
1772
+ args.head
1773
+ );
1774
+ if (!result) {
1775
+ return { error: `Could not compare ${args.base}..${args.head}` };
1776
+ }
1777
+ const files = result.values || [];
1778
+ return {
1779
+ files: files.slice(0, 50).map((f) => ({
1780
+ path: f.new?.path || f.old?.path,
1781
+ status: f.status,
1782
+ "+": f.lines_added || 0,
1783
+ "-": f.lines_removed || 0
1784
+ }))
1785
+ };
1786
+ },
1787
+ get_commit_statuses: async (args) => {
1788
+ const client = getClient();
1789
+ const statuses = await client.getCommitStatuses(args.repo_slug, args.commit, {
1790
+ limit: validateLimit(args.limit || 20)
1791
+ });
1792
+ return {
1793
+ commit: truncateHash(args.commit),
1794
+ statuses: statuses.map((s) => ({
1795
+ key: s.key,
1796
+ state: s.state,
1797
+ name: s.name,
1798
+ description: s.description,
1799
+ url: s.url,
1800
+ created: s.created_on
1801
+ }))
1802
+ };
1803
+ },
1804
+ create_commit_status: async (args) => {
1805
+ const state = args.state.toUpperCase();
1806
+ const validStates = Object.values(CommitStatusState);
1807
+ if (!validStates.includes(state)) {
1808
+ return {
1809
+ success: false,
1810
+ error: `Invalid state '${args.state}'. Must be one of: ${validStates.join(", ")}`
1811
+ };
1812
+ }
1813
+ const client = getClient();
1814
+ const result = await client.createCommitStatus(args.repo_slug, args.commit, {
1815
+ state,
1816
+ key: args.key,
1817
+ url: args.url,
1818
+ name: args.name,
1819
+ description: args.description
1820
+ });
1821
+ return {
1822
+ key: result.key,
1823
+ state: result.state,
1824
+ name: result.name,
1825
+ url: result.url
1826
+ };
1827
+ }
1828
+ };
1829
+
1830
+ // src/tools/deployments.ts
1831
+ var definitions6 = [
1832
+ {
1833
+ name: "list_environments",
1834
+ description: "List deployment environments for a repository.",
1835
+ inputSchema: {
1836
+ type: "object",
1837
+ properties: {
1838
+ repo_slug: { type: "string", description: "Repository slug" },
1839
+ limit: { type: "number", description: "Maximum results (default: 20)", default: 20 }
1840
+ },
1841
+ required: ["repo_slug"]
1842
+ }
1843
+ },
1844
+ {
1845
+ name: "get_environment",
1846
+ description: "Get details about a specific deployment environment.",
1847
+ inputSchema: {
1848
+ type: "object",
1849
+ properties: {
1850
+ repo_slug: { type: "string", description: "Repository slug" },
1851
+ environment_uuid: { type: "string", description: "Environment UUID" }
1852
+ },
1853
+ required: ["repo_slug", "environment_uuid"]
1854
+ }
1855
+ },
1856
+ {
1857
+ name: "list_deployment_history",
1858
+ description: "Get deployment history for a specific environment.",
1859
+ inputSchema: {
1860
+ type: "object",
1861
+ properties: {
1862
+ repo_slug: { type: "string", description: "Repository slug" },
1863
+ environment_uuid: { type: "string", description: "Environment UUID" },
1864
+ limit: { type: "number", description: "Maximum results (default: 20)", default: 20 }
1865
+ },
1866
+ required: ["repo_slug", "environment_uuid"]
1867
+ }
1868
+ }
1869
+ ];
1870
+ var handlers6 = {
1871
+ list_environments: async (args) => {
1872
+ const client = getClient();
1873
+ const environments = await client.listEnvironments(args.repo_slug, {
1874
+ limit: validateLimit(args.limit || 20)
1875
+ });
1876
+ return {
1877
+ environments: environments.map((e) => ({
1878
+ uuid: e.uuid,
1879
+ name: e.name,
1880
+ type: e.environment_type?.name,
1881
+ rank: e.rank
1882
+ }))
1883
+ };
1884
+ },
1885
+ get_environment: async (args) => {
1886
+ const client = getClient();
1887
+ const result = await client.getEnvironment(args.repo_slug, args.environment_uuid);
1888
+ if (!result) {
1889
+ return notFoundResponse("Environment", args.environment_uuid);
1890
+ }
1891
+ return {
1892
+ uuid: result.uuid,
1893
+ name: result.name,
1894
+ environment_type: result.environment_type?.name,
1895
+ rank: result.rank,
1896
+ restrictions: result.restrictions,
1897
+ lock: result.lock
1898
+ };
1899
+ },
1900
+ list_deployment_history: async (args) => {
1901
+ const client = getClient();
1902
+ const deployments = await client.listDeploymentHistory(
1903
+ args.repo_slug,
1904
+ args.environment_uuid,
1905
+ { limit: validateLimit(args.limit || 20) }
1906
+ );
1907
+ return {
1908
+ deployments: deployments.map((d) => ({
1909
+ uuid: d.uuid,
1910
+ state: d.state?.name,
1911
+ started: d.state?.started_on,
1912
+ completed: d.state?.completed_on,
1913
+ commit: d.release?.commit?.hash?.substring(0, 7),
1914
+ pipeline_uuid: d.release?.pipeline?.uuid
1915
+ }))
1916
+ };
1917
+ }
1918
+ };
1919
+
1920
+ // src/tools/webhooks.ts
1921
+ var definitions7 = [
1922
+ {
1923
+ name: "list_webhooks",
1924
+ description: "List webhooks configured for a repository.",
1925
+ inputSchema: {
1926
+ type: "object",
1927
+ properties: {
1928
+ repo_slug: { type: "string", description: "Repository slug" },
1929
+ limit: { type: "number", description: "Maximum results (default: 50)", default: 50 }
1930
+ },
1931
+ required: ["repo_slug"]
1932
+ }
1933
+ },
1934
+ {
1935
+ name: "create_webhook",
1936
+ description: "Create a webhook for a repository.",
1937
+ inputSchema: {
1938
+ type: "object",
1939
+ properties: {
1940
+ repo_slug: { type: "string", description: "Repository slug" },
1941
+ url: { type: "string", description: "URL to call when events occur" },
1942
+ events: {
1943
+ type: "array",
1944
+ items: { type: "string" },
1945
+ description: "List of events (e.g., repo:push, pullrequest:created, pullrequest:merged)"
1946
+ },
1947
+ description: { type: "string", description: "Webhook description (optional)", default: "" },
1948
+ active: { type: "boolean", description: "Whether webhook is active (default: true)", default: true }
1949
+ },
1950
+ required: ["repo_slug", "url", "events"]
1951
+ }
1952
+ },
1953
+ {
1954
+ name: "get_webhook",
1955
+ description: "Get details about a specific webhook.",
1956
+ inputSchema: {
1957
+ type: "object",
1958
+ properties: {
1959
+ repo_slug: { type: "string", description: "Repository slug" },
1960
+ webhook_uuid: { type: "string", description: "Webhook UUID" }
1961
+ },
1962
+ required: ["repo_slug", "webhook_uuid"]
1963
+ }
1964
+ },
1965
+ {
1966
+ name: "delete_webhook",
1967
+ description: "Delete a webhook.",
1968
+ inputSchema: {
1969
+ type: "object",
1970
+ properties: {
1971
+ repo_slug: { type: "string", description: "Repository slug" },
1972
+ webhook_uuid: { type: "string", description: "Webhook UUID" }
1973
+ },
1974
+ required: ["repo_slug", "webhook_uuid"]
1975
+ }
1976
+ }
1977
+ ];
1978
+ var handlers7 = {
1979
+ list_webhooks: async (args) => {
1980
+ const client = getClient();
1981
+ const webhooks = await client.listWebhooks(args.repo_slug, {
1982
+ limit: validateLimit(args.limit || 50)
1983
+ });
1984
+ return {
1985
+ webhooks: webhooks.map((w) => ({
1986
+ uuid: w.uuid,
1987
+ url: w.url,
1988
+ description: w.description,
1989
+ events: w.events,
1990
+ active: w.active
1991
+ }))
1992
+ };
1993
+ },
1994
+ create_webhook: async (args) => {
1995
+ const client = getClient();
1996
+ const result = await client.createWebhook(args.repo_slug, {
1997
+ url: args.url,
1998
+ events: args.events,
1999
+ description: args.description,
2000
+ active: args.active ?? true
2001
+ });
2002
+ return {
2003
+ uuid: result.uuid,
2004
+ url: result.url,
2005
+ events: result.events,
2006
+ active: result.active
2007
+ };
2008
+ },
2009
+ get_webhook: async (args) => {
2010
+ const client = getClient();
2011
+ const result = await client.getWebhook(args.repo_slug, args.webhook_uuid);
2012
+ if (!result) {
2013
+ return notFoundResponse("Webhook", args.webhook_uuid);
2014
+ }
2015
+ return {
2016
+ uuid: result.uuid,
2017
+ url: result.url,
2018
+ description: result.description,
2019
+ events: result.events,
2020
+ active: result.active
2021
+ };
2022
+ },
2023
+ delete_webhook: async (args) => {
2024
+ const client = getClient();
2025
+ await client.deleteWebhook(args.repo_slug, args.webhook_uuid);
2026
+ return {};
2027
+ }
2028
+ };
2029
+
2030
+ // src/tools/tags.ts
2031
+ var definitions8 = [
2032
+ {
2033
+ name: "list_tags",
2034
+ description: "List tags in a repository.",
2035
+ inputSchema: {
2036
+ type: "object",
2037
+ properties: {
2038
+ repo_slug: { type: "string", description: "Repository slug" },
2039
+ limit: { type: "number", description: "Maximum results (default: 50)", default: 50 }
2040
+ },
2041
+ required: ["repo_slug"]
2042
+ }
2043
+ },
2044
+ {
2045
+ name: "create_tag",
2046
+ description: "Create a new tag in a repository.",
2047
+ inputSchema: {
2048
+ type: "object",
2049
+ properties: {
2050
+ repo_slug: { type: "string", description: "Repository slug" },
2051
+ name: { type: "string", description: 'Tag name (e.g., "v1.0.0")' },
2052
+ target: { type: "string", description: "Commit hash or branch name to tag" },
2053
+ message: { type: "string", description: "Optional tag message (for annotated tags)", default: "" }
2054
+ },
2055
+ required: ["repo_slug", "name", "target"]
2056
+ }
2057
+ },
2058
+ {
2059
+ name: "delete_tag",
2060
+ description: "Delete a tag from a repository.",
2061
+ inputSchema: {
2062
+ type: "object",
2063
+ properties: {
2064
+ repo_slug: { type: "string", description: "Repository slug" },
2065
+ tag_name: { type: "string", description: "Tag name to delete" }
2066
+ },
2067
+ required: ["repo_slug", "tag_name"]
2068
+ }
2069
+ }
2070
+ ];
2071
+ var handlers8 = {
2072
+ list_tags: async (args) => {
2073
+ const client = getClient();
2074
+ const tags = await client.listTags(args.repo_slug, {
2075
+ limit: validateLimit(args.limit || 50)
2076
+ });
2077
+ return {
2078
+ tags: tags.map((t) => ({
2079
+ name: t.name,
2080
+ target: truncateHash(t.target?.hash),
2081
+ message: t.message,
2082
+ date: t.target?.date,
2083
+ tagger: t.tagger?.raw
2084
+ }))
2085
+ };
2086
+ },
2087
+ create_tag: async (args) => {
2088
+ const client = getClient();
2089
+ const result = await client.createTag(
2090
+ args.repo_slug,
2091
+ args.name,
2092
+ args.target,
2093
+ args.message || void 0
2094
+ );
2095
+ return {
2096
+ name: result.name,
2097
+ target: truncateHash(result.target?.hash),
2098
+ message: result.message || ""
2099
+ };
2100
+ },
2101
+ delete_tag: async (args) => {
2102
+ const client = getClient();
2103
+ await client.deleteTag(args.repo_slug, args.tag_name);
2104
+ return {};
2105
+ }
2106
+ };
2107
+
2108
+ // src/tools/restrictions.ts
2109
+ var definitions9 = [
2110
+ {
2111
+ name: "list_branch_restrictions",
2112
+ description: "List branch restrictions (protection rules) in a repository.",
2113
+ inputSchema: {
2114
+ type: "object",
2115
+ properties: {
2116
+ repo_slug: { type: "string", description: "Repository slug" },
2117
+ limit: { type: "number", description: "Maximum results (default: 50)", default: 50 }
2118
+ },
2119
+ required: ["repo_slug"]
2120
+ }
2121
+ },
2122
+ {
2123
+ name: "create_branch_restriction",
2124
+ description: "Create a branch restriction (protection rule).",
2125
+ inputSchema: {
2126
+ type: "object",
2127
+ properties: {
2128
+ repo_slug: { type: "string", description: "Repository slug" },
2129
+ kind: {
2130
+ type: "string",
2131
+ description: "Type of restriction: push, force, delete, restrict_merges, require_passing_builds_to_merge, require_approvals_to_merge, etc."
2132
+ },
2133
+ pattern: { type: "string", description: 'Branch pattern (e.g., "main", "release/*"). Required for glob match.', default: "" },
2134
+ branch_match_kind: { type: "string", description: 'How to match branches: "glob" or "branching_model"', default: "glob" },
2135
+ branch_type: { type: "string", description: "Branch type when using branching_model: development, production, feature, etc.", default: "" },
2136
+ value: { type: "number", description: "Numeric value (e.g., number of required approvals)", default: 0 }
2137
+ },
2138
+ required: ["repo_slug", "kind"]
2139
+ }
2140
+ },
2141
+ {
2142
+ name: "delete_branch_restriction",
2143
+ description: "Delete a branch restriction.",
2144
+ inputSchema: {
2145
+ type: "object",
2146
+ properties: {
2147
+ repo_slug: { type: "string", description: "Repository slug" },
2148
+ restriction_id: { type: "number", description: "Restriction ID" }
2149
+ },
2150
+ required: ["repo_slug", "restriction_id"]
2151
+ }
2152
+ }
2153
+ ];
2154
+ var handlers9 = {
2155
+ list_branch_restrictions: async (args) => {
2156
+ const client = getClient();
2157
+ const restrictions = await client.listBranchRestrictions(args.repo_slug, {
2158
+ limit: validateLimit(args.limit || 50)
2159
+ });
2160
+ return {
2161
+ restrictions: restrictions.map((r) => ({
2162
+ id: r.id,
2163
+ kind: r.kind,
2164
+ pattern: r.pattern,
2165
+ branch_match_kind: r.branch_match_kind,
2166
+ branch_type: r.branch_type,
2167
+ value: r.value
2168
+ }))
2169
+ };
2170
+ },
2171
+ create_branch_restriction: async (args) => {
2172
+ const client = getClient();
2173
+ const result = await client.createBranchRestriction(args.repo_slug, {
2174
+ kind: args.kind,
2175
+ pattern: args.pattern,
2176
+ branchMatchKind: args.branch_match_kind || "glob",
2177
+ branchType: args.branch_type || void 0,
2178
+ value: args.value || void 0
2179
+ });
2180
+ return {
2181
+ id: result.id,
2182
+ kind: result.kind
2183
+ };
2184
+ },
2185
+ delete_branch_restriction: async (args) => {
2186
+ const client = getClient();
2187
+ await client.deleteBranchRestriction(args.repo_slug, args.restriction_id);
2188
+ return {};
2189
+ }
2190
+ };
2191
+
2192
+ // src/tools/source.ts
2193
+ var definitions10 = [
2194
+ {
2195
+ name: "get_file_content",
2196
+ description: "Get the content of a file from a repository. Read file contents without cloning.",
2197
+ inputSchema: {
2198
+ type: "object",
2199
+ properties: {
2200
+ repo_slug: { type: "string", description: "Repository slug" },
2201
+ path: { type: "string", description: 'File path (e.g., "src/main.py", "README.md")' },
2202
+ ref: { type: "string", description: 'Branch, tag, or commit hash (default: "main")', default: "main" }
2203
+ },
2204
+ required: ["repo_slug", "path"]
2205
+ }
2206
+ },
2207
+ {
2208
+ name: "list_directory",
2209
+ description: "List contents of a directory in a repository. Browse repository structure without cloning.",
2210
+ inputSchema: {
2211
+ type: "object",
2212
+ properties: {
2213
+ repo_slug: { type: "string", description: "Repository slug" },
2214
+ path: { type: "string", description: "Directory path (empty string for root)", default: "" },
2215
+ ref: { type: "string", description: 'Branch, tag, or commit hash (default: "main")', default: "main" },
2216
+ limit: { type: "number", description: "Maximum entries (default: 100)", default: 100 }
2217
+ },
2218
+ required: ["repo_slug"]
2219
+ }
2220
+ }
2221
+ ];
2222
+ var handlers10 = {
2223
+ get_file_content: async (args) => {
2224
+ const client = getClient();
2225
+ const ref = args.ref || "main";
2226
+ const content = await client.getFileContent(args.repo_slug, args.path, ref);
2227
+ if (content === null) {
2228
+ return { error: `File '${args.path}' not found at ref '${ref}'` };
2229
+ }
2230
+ return {
2231
+ path: args.path,
2232
+ ref,
2233
+ content,
2234
+ size: content.length
2235
+ };
2236
+ },
2237
+ list_directory: async (args) => {
2238
+ const client = getClient();
2239
+ const ref = args.ref || "main";
2240
+ const path = args.path || "";
2241
+ const entries = await client.listDirectory(args.repo_slug, path, {
2242
+ ref,
2243
+ limit: validateLimit(args.limit || 100)
2244
+ });
2245
+ return {
2246
+ path: path || "/",
2247
+ ref,
2248
+ entries: entries.map((e) => ({
2249
+ path: e.path,
2250
+ type: e.type === "commit_directory" ? "directory" : "file",
2251
+ size: e.size
2252
+ }))
2253
+ };
2254
+ }
2255
+ };
2256
+
2257
+ // src/tools/permissions.ts
2258
+ var definitions11 = [
2259
+ // User permissions
2260
+ {
2261
+ name: "list_user_permissions",
2262
+ description: "List user permissions for a repository.",
2263
+ inputSchema: {
2264
+ type: "object",
2265
+ properties: {
2266
+ repo_slug: { type: "string", description: "Repository slug" },
2267
+ limit: { type: "number", description: "Maximum results (default: 50)", default: 50 }
2268
+ },
2269
+ required: ["repo_slug"]
2270
+ }
2271
+ },
2272
+ {
2273
+ name: "get_user_permission",
2274
+ description: "Get a specific user's permission for a repository.",
2275
+ inputSchema: {
2276
+ type: "object",
2277
+ properties: {
2278
+ repo_slug: { type: "string", description: "Repository slug" },
2279
+ selected_user: { type: "string", description: "User's account_id or UUID" }
2280
+ },
2281
+ required: ["repo_slug", "selected_user"]
2282
+ }
2283
+ },
2284
+ {
2285
+ name: "update_user_permission",
2286
+ description: "Update or add a user's permission for a repository.",
2287
+ inputSchema: {
2288
+ type: "object",
2289
+ properties: {
2290
+ repo_slug: { type: "string", description: "Repository slug" },
2291
+ selected_user: { type: "string", description: "User's account_id or UUID" },
2292
+ permission: { type: "string", description: 'Permission level: "read", "write", or "admin"' }
2293
+ },
2294
+ required: ["repo_slug", "selected_user", "permission"]
2295
+ }
2296
+ },
2297
+ {
2298
+ name: "delete_user_permission",
2299
+ description: "Remove a user's explicit permission from a repository.",
2300
+ inputSchema: {
2301
+ type: "object",
2302
+ properties: {
2303
+ repo_slug: { type: "string", description: "Repository slug" },
2304
+ selected_user: { type: "string", description: "User's account_id or UUID" }
2305
+ },
2306
+ required: ["repo_slug", "selected_user"]
2307
+ }
2308
+ },
2309
+ // Group permissions
2310
+ {
2311
+ name: "list_group_permissions",
2312
+ description: "List group permissions for a repository.",
2313
+ inputSchema: {
2314
+ type: "object",
2315
+ properties: {
2316
+ repo_slug: { type: "string", description: "Repository slug" },
2317
+ limit: { type: "number", description: "Maximum results (default: 50)", default: 50 }
2318
+ },
2319
+ required: ["repo_slug"]
2320
+ }
2321
+ },
2322
+ {
2323
+ name: "get_group_permission",
2324
+ description: "Get a specific group's permission for a repository.",
2325
+ inputSchema: {
2326
+ type: "object",
2327
+ properties: {
2328
+ repo_slug: { type: "string", description: "Repository slug" },
2329
+ group_slug: { type: "string", description: "Group slug" }
2330
+ },
2331
+ required: ["repo_slug", "group_slug"]
2332
+ }
2333
+ },
2334
+ {
2335
+ name: "update_group_permission",
2336
+ description: "Update or add a group's permission for a repository.",
2337
+ inputSchema: {
2338
+ type: "object",
2339
+ properties: {
2340
+ repo_slug: { type: "string", description: "Repository slug" },
2341
+ group_slug: { type: "string", description: "Group slug" },
2342
+ permission: { type: "string", description: 'Permission level: "read", "write", or "admin"' }
2343
+ },
2344
+ required: ["repo_slug", "group_slug", "permission"]
2345
+ }
2346
+ },
2347
+ {
2348
+ name: "delete_group_permission",
2349
+ description: "Remove a group's explicit permission from a repository.",
2350
+ inputSchema: {
2351
+ type: "object",
2352
+ properties: {
2353
+ repo_slug: { type: "string", description: "Repository slug" },
2354
+ group_slug: { type: "string", description: "Group slug" }
2355
+ },
2356
+ required: ["repo_slug", "group_slug"]
2357
+ }
2358
+ }
2359
+ ];
2360
+ var handlers11 = {
2361
+ list_user_permissions: async (args) => {
2362
+ const client = getClient();
2363
+ const permissions = await client.listUserPermissions(args.repo_slug, {
2364
+ limit: validateLimit(args.limit || 50)
2365
+ });
2366
+ return {
2367
+ users: permissions.map((p) => ({
2368
+ user: p.user?.display_name,
2369
+ account_id: p.user?.account_id,
2370
+ permission: p.permission
2371
+ }))
2372
+ };
2373
+ },
2374
+ get_user_permission: async (args) => {
2375
+ const client = getClient();
2376
+ const result = await client.getUserPermission(args.repo_slug, args.selected_user);
2377
+ if (!result) {
2378
+ return notFoundResponse("User permission", args.selected_user);
2379
+ }
2380
+ return {
2381
+ user: result.user?.display_name,
2382
+ account_id: result.user?.account_id,
2383
+ permission: result.permission
2384
+ };
2385
+ },
2386
+ update_user_permission: async (args) => {
2387
+ const client = getClient();
2388
+ const result = await client.updateUserPermission(
2389
+ args.repo_slug,
2390
+ args.selected_user,
2391
+ args.permission
2392
+ );
2393
+ return {
2394
+ user: result.user?.display_name,
2395
+ permission: result.permission
2396
+ };
2397
+ },
2398
+ delete_user_permission: async (args) => {
2399
+ const client = getClient();
2400
+ await client.deleteUserPermission(args.repo_slug, args.selected_user);
2401
+ return {};
2402
+ },
2403
+ list_group_permissions: async (args) => {
2404
+ const client = getClient();
2405
+ const permissions = await client.listGroupPermissions(args.repo_slug, {
2406
+ limit: validateLimit(args.limit || 50)
2407
+ });
2408
+ return {
2409
+ groups: permissions.map((p) => ({
2410
+ group: p.group?.name,
2411
+ slug: p.group?.slug,
2412
+ permission: p.permission
2413
+ }))
2414
+ };
2415
+ },
2416
+ get_group_permission: async (args) => {
2417
+ const client = getClient();
2418
+ const result = await client.getGroupPermission(args.repo_slug, args.group_slug);
2419
+ if (!result) {
2420
+ return notFoundResponse("Group permission", args.group_slug);
2421
+ }
2422
+ return {
2423
+ group: result.group?.name,
2424
+ slug: result.group?.slug,
2425
+ permission: result.permission
2426
+ };
2427
+ },
2428
+ update_group_permission: async (args) => {
2429
+ const client = getClient();
2430
+ const result = await client.updateGroupPermission(
2431
+ args.repo_slug,
2432
+ args.group_slug,
2433
+ args.permission
2434
+ );
2435
+ return {
2436
+ group: result.group?.name,
2437
+ permission: result.permission
2438
+ };
2439
+ },
2440
+ delete_group_permission: async (args) => {
2441
+ const client = getClient();
2442
+ await client.deleteGroupPermission(args.repo_slug, args.group_slug);
2443
+ return {};
2444
+ }
2445
+ };
2446
+
2447
+ // src/tools/projects.ts
2448
+ var definitions12 = [
2449
+ {
2450
+ name: "list_projects",
2451
+ description: "List projects in the workspace.",
2452
+ inputSchema: {
2453
+ type: "object",
2454
+ properties: {
2455
+ limit: { type: "number", description: "Maximum results (default: 50)", default: 50 }
2456
+ },
2457
+ required: []
2458
+ }
2459
+ },
2460
+ {
2461
+ name: "get_project",
2462
+ description: "Get information about a specific project.",
2463
+ inputSchema: {
2464
+ type: "object",
2465
+ properties: {
2466
+ project_key: { type: "string", description: 'Project key (e.g., "DS", "PROJ")' }
2467
+ },
2468
+ required: ["project_key"]
2469
+ }
2470
+ }
2471
+ ];
2472
+ var handlers12 = {
2473
+ list_projects: async (args) => {
2474
+ const client = getClient();
2475
+ const projects = await client.listProjects({
2476
+ limit: validateLimit(args.limit || 50)
2477
+ });
2478
+ return {
2479
+ projects: projects.map((p) => ({
2480
+ key: p.key,
2481
+ name: p.name,
2482
+ description: p.description
2483
+ }))
2484
+ };
2485
+ },
2486
+ get_project: async (args) => {
2487
+ const client = getClient();
2488
+ const result = await client.getProject(args.project_key);
2489
+ if (!result) {
2490
+ return notFoundResponse("Project", args.project_key);
2491
+ }
2492
+ return {
2493
+ key: result.key,
2494
+ name: result.name,
2495
+ description: result.description,
2496
+ uuid: result.uuid
2497
+ };
2498
+ }
2499
+ };
2500
+
2501
+ // src/tools/index.ts
2502
+ var toolDefinitions = [
2503
+ // Repository tools
2504
+ ...definitions,
2505
+ // Pull request tools
2506
+ ...definitions2,
2507
+ // Pipeline tools
2508
+ ...definitions3,
2509
+ // Branch tools
2510
+ ...definitions4,
2511
+ // Commit tools
2512
+ ...definitions5,
2513
+ // Deployment tools
2514
+ ...definitions6,
2515
+ // Webhook tools
2516
+ ...definitions7,
2517
+ // Tag tools
2518
+ ...definitions8,
2519
+ // Branch restriction tools
2520
+ ...definitions9,
2521
+ // Source browsing tools
2522
+ ...definitions10,
2523
+ // Permission tools
2524
+ ...definitions11,
2525
+ // Project tools
2526
+ ...definitions12
2527
+ ];
2528
+ async function handleToolCall(name, args) {
2529
+ if (name in handlers) {
2530
+ return await handlers[name](args);
2531
+ }
2532
+ if (name in handlers2) {
2533
+ return await handlers2[name](args);
2534
+ }
2535
+ if (name in handlers3) {
2536
+ return await handlers3[name](args);
2537
+ }
2538
+ if (name in handlers4) {
2539
+ return await handlers4[name](args);
2540
+ }
2541
+ if (name in handlers5) {
2542
+ return await handlers5[name](args);
2543
+ }
2544
+ if (name in handlers6) {
2545
+ return await handlers6[name](args);
2546
+ }
2547
+ if (name in handlers7) {
2548
+ return await handlers7[name](args);
2549
+ }
2550
+ if (name in handlers8) {
2551
+ return await handlers8[name](args);
2552
+ }
2553
+ if (name in handlers9) {
2554
+ return await handlers9[name](args);
2555
+ }
2556
+ if (name in handlers10) {
2557
+ return await handlers10[name](args);
2558
+ }
2559
+ if (name in handlers11) {
2560
+ return await handlers11[name](args);
2561
+ }
2562
+ if (name in handlers12) {
2563
+ return await handlers12[name](args);
2564
+ }
2565
+ throw new Error(`Unknown tool: ${name}`);
2566
+ }
2567
+
2568
+ // src/resources.ts
2569
+ var resourceDefinitions = [
2570
+ {
2571
+ uri: "bitbucket://repositories",
2572
+ name: "Repositories",
2573
+ description: "List all repositories in the workspace",
2574
+ mimeType: "text/markdown"
2575
+ },
2576
+ {
2577
+ uri: "bitbucket://repositories/{repo_slug}",
2578
+ name: "Repository Details",
2579
+ description: "Get detailed information about a specific repository",
2580
+ mimeType: "text/markdown"
2581
+ },
2582
+ {
2583
+ uri: "bitbucket://repositories/{repo_slug}/branches",
2584
+ name: "Repository Branches",
2585
+ description: "List branches in a repository",
2586
+ mimeType: "text/markdown"
2587
+ },
2588
+ {
2589
+ uri: "bitbucket://repositories/{repo_slug}/pull-requests",
2590
+ name: "Pull Requests",
2591
+ description: "List open pull requests in a repository",
2592
+ mimeType: "text/markdown"
2593
+ },
2594
+ {
2595
+ uri: "bitbucket://projects",
2596
+ name: "Projects",
2597
+ description: "List all projects in the workspace",
2598
+ mimeType: "text/markdown"
2599
+ }
2600
+ ];
2601
+ async function handleResourceRead(uri) {
2602
+ const client = getClient();
2603
+ if (uri === "bitbucket://repositories") {
2604
+ return await resourceRepositories(client);
2605
+ }
2606
+ if (uri === "bitbucket://projects") {
2607
+ return await resourceProjects(client);
2608
+ }
2609
+ const repoMatch = uri.match(/^bitbucket:\/\/repositories\/([^/]+)$/);
2610
+ if (repoMatch) {
2611
+ return await resourceRepository(client, repoMatch[1]);
2612
+ }
2613
+ const branchesMatch = uri.match(/^bitbucket:\/\/repositories\/([^/]+)\/branches$/);
2614
+ if (branchesMatch) {
2615
+ return await resourceBranches(client, branchesMatch[1]);
2616
+ }
2617
+ const prsMatch = uri.match(/^bitbucket:\/\/repositories\/([^/]+)\/pull-requests$/);
2618
+ if (prsMatch) {
2619
+ return await resourcePullRequests(client, prsMatch[1]);
2620
+ }
2621
+ throw new Error(`Unknown resource URI: ${uri}`);
2622
+ }
2623
+ async function resourceRepositories(client) {
2624
+ const repos = await client.listRepositories({ limit: 50 });
2625
+ const lines = [`# Repositories in ${client.workspace}`, ""];
2626
+ for (const r of repos) {
2627
+ const name = r.name || "unknown";
2628
+ const desc = (r.description || "").substring(0, 50) || "No description";
2629
+ const icon = r.is_private ? "\u{1F512}" : "\u{1F310}";
2630
+ lines.push(`- ${icon} **${name}**: ${desc}`);
2631
+ }
2632
+ return lines.join("\n");
2633
+ }
2634
+ async function resourceRepository(client, repoSlug) {
2635
+ const repo = await client.getRepository(repoSlug);
2636
+ if (!repo) {
2637
+ return `Repository '${repoSlug}' not found`;
2638
+ }
2639
+ const lines = [
2640
+ `# ${repo.name || repoSlug}`,
2641
+ "",
2642
+ `**Description**: ${repo.description || "No description"}`,
2643
+ `**Private**: ${repo.is_private ? "Yes" : "No"}`,
2644
+ `**Project**: ${repo.project?.name || "None"}`,
2645
+ `**Main branch**: ${repo.mainbranch?.name || "main"}`,
2646
+ "",
2647
+ "## Clone URLs"
2648
+ ];
2649
+ for (const clone of repo.links?.clone || []) {
2650
+ lines.push(`- ${clone.name}: \`${clone.href}\``);
2651
+ }
2652
+ return lines.join("\n");
2653
+ }
2654
+ async function resourceBranches(client, repoSlug) {
2655
+ const branches = await client.listBranches(repoSlug, { limit: 30 });
2656
+ const lines = [`# Branches in ${repoSlug}`, ""];
2657
+ for (const b of branches) {
2658
+ const name = b.name || "unknown";
2659
+ const commit = (b.target?.hash || "").substring(0, 7);
2660
+ lines.push(`- **${name}** (${commit})`);
2661
+ }
2662
+ return lines.join("\n");
2663
+ }
2664
+ async function resourcePullRequests(client, repoSlug) {
2665
+ const prs = await client.listPullRequests(repoSlug, { state: "OPEN", limit: 20 });
2666
+ const lines = [`# Open Pull Requests in ${repoSlug}`, ""];
2667
+ if (prs.length === 0) {
2668
+ lines.push("No open pull requests");
2669
+ }
2670
+ for (const pr of prs) {
2671
+ const prId = pr.id;
2672
+ const title = pr.title || "Untitled";
2673
+ const author = pr.author?.display_name || "Unknown";
2674
+ lines.push(`- **#${prId}**: ${title} (by ${author})`);
2675
+ }
2676
+ return lines.join("\n");
2677
+ }
2678
+ async function resourceProjects(client) {
2679
+ const projects = await client.listProjects({ limit: 50 });
2680
+ const lines = [`# Projects in ${client.workspace}`, ""];
2681
+ for (const p of projects) {
2682
+ const key = p.key || "?";
2683
+ const name = p.name || "Unknown";
2684
+ const desc = (p.description || "").substring(0, 40) || "No description";
2685
+ lines.push(`- **${key}** - ${name}: ${desc}`);
2686
+ }
2687
+ return lines.join("\n");
2688
+ }
2689
+
2690
+ // src/prompts.ts
2691
+ var promptDefinitions = [
2692
+ {
2693
+ name: "code_review",
2694
+ description: "Generate a code review prompt for a pull request",
2695
+ arguments: [
2696
+ {
2697
+ name: "repo_slug",
2698
+ description: "Repository slug",
2699
+ required: true
2700
+ },
2701
+ {
2702
+ name: "pr_id",
2703
+ description: "Pull request ID",
2704
+ required: true
2705
+ }
2706
+ ]
2707
+ },
2708
+ {
2709
+ name: "release_notes",
2710
+ description: "Generate release notes from commits between two refs",
2711
+ arguments: [
2712
+ {
2713
+ name: "repo_slug",
2714
+ description: "Repository slug",
2715
+ required: true
2716
+ },
2717
+ {
2718
+ name: "base_tag",
2719
+ description: 'Base tag or commit (e.g., "v1.0.0")',
2720
+ required: true
2721
+ },
2722
+ {
2723
+ name: "head",
2724
+ description: 'Head ref (default: "main")',
2725
+ required: false
2726
+ }
2727
+ ]
2728
+ },
2729
+ {
2730
+ name: "pipeline_debug",
2731
+ description: "Debug a failed pipeline",
2732
+ arguments: [
2733
+ {
2734
+ name: "repo_slug",
2735
+ description: "Repository slug",
2736
+ required: true
2737
+ }
2738
+ ]
2739
+ },
2740
+ {
2741
+ name: "repo_summary",
2742
+ description: "Get a comprehensive summary of a repository",
2743
+ arguments: [
2744
+ {
2745
+ name: "repo_slug",
2746
+ description: "Repository slug",
2747
+ required: true
2748
+ }
2749
+ ]
2750
+ }
2751
+ ];
2752
+ function handlePromptGet(name, args) {
2753
+ switch (name) {
2754
+ case "code_review":
2755
+ return promptCodeReview(args.repo_slug, args.pr_id);
2756
+ case "release_notes":
2757
+ return promptReleaseNotes(args.repo_slug, args.base_tag, args.head || "main");
2758
+ case "pipeline_debug":
2759
+ return promptPipelineDebug(args.repo_slug);
2760
+ case "repo_summary":
2761
+ return promptRepoSummary(args.repo_slug);
2762
+ default:
2763
+ throw new Error(`Unknown prompt: ${name}`);
2764
+ }
2765
+ }
2766
+ function promptCodeReview(repoSlug, prId) {
2767
+ const content = `Please review pull request #${prId} in repository '${repoSlug}'.
2768
+
2769
+ Use the following tools to gather information:
2770
+ 1. get_pull_request(repo_slug="${repoSlug}", pr_id=${prId}) - Get PR details
2771
+ 2. get_pr_diff(repo_slug="${repoSlug}", pr_id=${prId}) - Get the code changes
2772
+ 3. list_pr_comments(repo_slug="${repoSlug}", pr_id=${prId}) - See existing comments
2773
+
2774
+ Then provide a thorough code review covering:
2775
+ - Code quality and readability
2776
+ - Potential bugs or edge cases
2777
+ - Security concerns
2778
+ - Performance considerations
2779
+ - Suggestions for improvement
2780
+
2781
+ If you find issues, use add_pr_comment() to leave feedback on specific lines.`;
2782
+ return {
2783
+ messages: [
2784
+ {
2785
+ role: "user",
2786
+ content: {
2787
+ type: "text",
2788
+ text: content
2789
+ }
2790
+ }
2791
+ ]
2792
+ };
2793
+ }
2794
+ function promptReleaseNotes(repoSlug, baseTag, head) {
2795
+ const content = `Generate release notes for repository '${repoSlug}' comparing ${baseTag} to ${head}.
2796
+
2797
+ Use these tools:
2798
+ 1. compare_commits(repo_slug="${repoSlug}", base="${baseTag}", head="${head}") - See changed files
2799
+ 2. list_commits(repo_slug="${repoSlug}", branch="${head}", limit=50) - Get recent commits
2800
+
2801
+ Organize the release notes into sections:
2802
+ - **New Features**: New functionality added
2803
+ - **Bug Fixes**: Issues that were resolved
2804
+ - **Improvements**: Enhancements to existing features
2805
+ - **Breaking Changes**: Changes that require user action
2806
+
2807
+ Format as markdown suitable for a GitHub/Bitbucket release.`;
2808
+ return {
2809
+ messages: [
2810
+ {
2811
+ role: "user",
2812
+ content: {
2813
+ type: "text",
2814
+ text: content
2815
+ }
2816
+ }
2817
+ ]
2818
+ };
2819
+ }
2820
+ function promptPipelineDebug(repoSlug) {
2821
+ const content = `Help debug pipeline failures in repository '${repoSlug}'.
2822
+
2823
+ Use these tools:
2824
+ 1. list_pipelines(repo_slug="${repoSlug}", limit=5) - Get recent pipeline runs
2825
+ 2. get_pipeline(repo_slug="${repoSlug}", pipeline_uuid="<uuid>") - Get pipeline details
2826
+ 3. get_pipeline_logs(repo_slug="${repoSlug}", pipeline_uuid="<uuid>") - Get step list
2827
+ 4. get_pipeline_logs(repo_slug="${repoSlug}", pipeline_uuid="<uuid>", step_uuid="<step>") - Get logs
2828
+
2829
+ Analyze the failures and provide:
2830
+ - Root cause of the failure
2831
+ - Specific error messages
2832
+ - Recommended fixes
2833
+ - Commands to re-run the pipeline if appropriate`;
2834
+ return {
2835
+ messages: [
2836
+ {
2837
+ role: "user",
2838
+ content: {
2839
+ type: "text",
2840
+ text: content
2841
+ }
2842
+ }
2843
+ ]
2844
+ };
2845
+ }
2846
+ function promptRepoSummary(repoSlug) {
2847
+ const content = `Provide a comprehensive summary of repository '${repoSlug}'.
2848
+
2849
+ Gather information using:
2850
+ 1. get_repository(repo_slug="${repoSlug}") - Basic repo info
2851
+ 2. list_branches(repo_slug="${repoSlug}", limit=10) - Active branches
2852
+ 3. list_pull_requests(repo_slug="${repoSlug}", state="OPEN") - Open PRs
2853
+ 4. list_pipelines(repo_slug="${repoSlug}", limit=5) - Recent CI/CD status
2854
+ 5. list_commits(repo_slug="${repoSlug}", limit=10) - Recent activity
2855
+
2856
+ Summarize:
2857
+ - Repository description and purpose
2858
+ - Current development activity
2859
+ - Open pull requests needing attention
2860
+ - CI/CD health
2861
+ - Recent contributors`;
2862
+ return {
2863
+ messages: [
2864
+ {
2865
+ role: "user",
2866
+ content: {
2867
+ type: "text",
2868
+ text: content
2869
+ }
2870
+ }
2871
+ ]
2872
+ };
2873
+ }
2874
+
2875
+ // src/index.ts
2876
+ var VERSION = "0.10.0";
2877
+ function createServer() {
2878
+ const server = new Server(
2879
+ {
2880
+ name: "bitbucket",
2881
+ version: VERSION
2882
+ },
2883
+ {
2884
+ capabilities: {
2885
+ tools: {},
2886
+ resources: {},
2887
+ prompts: {}
2888
+ }
2889
+ }
2890
+ );
2891
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
2892
+ return {
2893
+ tools: toolDefinitions
2894
+ };
2895
+ });
2896
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2897
+ const { name, arguments: args } = request.params;
2898
+ try {
2899
+ const result = await handleToolCall(name, args || {});
2900
+ return {
2901
+ content: [
2902
+ {
2903
+ type: "text",
2904
+ text: JSON.stringify(result, null, 2)
2905
+ }
2906
+ ]
2907
+ };
2908
+ } catch (error) {
2909
+ const message = error instanceof Error ? error.message : "Unknown error";
2910
+ return {
2911
+ content: [
2912
+ {
2913
+ type: "text",
2914
+ text: JSON.stringify({ error: message }, null, 2)
2915
+ }
2916
+ ],
2917
+ isError: true
2918
+ };
2919
+ }
2920
+ });
2921
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
2922
+ return {
2923
+ resources: resourceDefinitions
2924
+ };
2925
+ });
2926
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2927
+ const { uri } = request.params;
2928
+ try {
2929
+ const content = await handleResourceRead(uri);
2930
+ return {
2931
+ contents: [
2932
+ {
2933
+ uri,
2934
+ mimeType: "text/markdown",
2935
+ text: content
2936
+ }
2937
+ ]
2938
+ };
2939
+ } catch (error) {
2940
+ const message = error instanceof Error ? error.message : "Unknown error";
2941
+ throw new Error(`Failed to read resource: ${message}`);
2942
+ }
2943
+ });
2944
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
2945
+ return {
2946
+ prompts: promptDefinitions
2947
+ };
2948
+ });
2949
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
2950
+ const { name, arguments: args } = request.params;
2951
+ try {
2952
+ const result = handlePromptGet(name, args || {});
2953
+ return result;
2954
+ } catch (error) {
2955
+ const message = error instanceof Error ? error.message : "Unknown error";
2956
+ throw new Error(`Failed to get prompt: ${message}`);
2957
+ }
2958
+ });
2959
+ return server;
2960
+ }
2961
+ async function main() {
2962
+ try {
2963
+ getSettings();
2964
+ } catch (error) {
2965
+ console.error("Configuration error:", error instanceof Error ? error.message : error);
2966
+ process.exit(1);
2967
+ }
2968
+ const server = createServer();
2969
+ const transport = new StdioServerTransport();
2970
+ await server.connect(transport);
2971
+ console.error(`Bitbucket MCP Server v${VERSION} started`);
2972
+ }
2973
+ main().catch((error) => {
2974
+ console.error("Fatal error:", error);
2975
+ process.exit(1);
2976
+ });