bamboo-mcp-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +366 -0
- package/dist/bamboo-client.d.ts +71 -0
- package/dist/bamboo-client.js +363 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +44 -0
- package/dist/tools/branches.d.ts +3 -0
- package/dist/tools/branches.js +65 -0
- package/dist/tools/builds.d.ts +3 -0
- package/dist/tools/builds.js +221 -0
- package/dist/tools/deployments.d.ts +3 -0
- package/dist/tools/deployments.js +147 -0
- package/dist/tools/plans.d.ts +3 -0
- package/dist/tools/plans.js +152 -0
- package/dist/tools/projects.d.ts +3 -0
- package/dist/tools/projects.js +64 -0
- package/dist/tools/queue.d.ts +3 -0
- package/dist/tools/queue.js +55 -0
- package/dist/tools/server.d.ts +3 -0
- package/dist/tools/server.js +52 -0
- package/dist/types.d.ts +238 -0
- package/dist/types.js +2 -0
- package/package.json +52 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { ProxyAgent, fetch as undiciFetch } from 'undici';
|
|
2
|
+
export class BambooClient {
|
|
3
|
+
baseUrl;
|
|
4
|
+
token;
|
|
5
|
+
proxyAgent;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
8
|
+
this.token = config.token;
|
|
9
|
+
if (config.proxyUrl) {
|
|
10
|
+
this.proxyAgent = new ProxyAgent(config.proxyUrl);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async request(endpoint, options = {}) {
|
|
14
|
+
const url = `${this.baseUrl}/rest/api/latest${endpoint}`;
|
|
15
|
+
const headers = {
|
|
16
|
+
'Authorization': `Bearer ${this.token}`,
|
|
17
|
+
'Accept': 'application/json',
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
...(options.headers || {}),
|
|
20
|
+
};
|
|
21
|
+
const fetchOptions = {
|
|
22
|
+
...options,
|
|
23
|
+
headers,
|
|
24
|
+
};
|
|
25
|
+
// Add proxy agent if configured
|
|
26
|
+
if (this.proxyAgent) {
|
|
27
|
+
fetchOptions.dispatcher = this.proxyAgent;
|
|
28
|
+
}
|
|
29
|
+
const response = await undiciFetch(url, fetchOptions);
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
const errorText = await response.text();
|
|
32
|
+
throw new Error(`Bamboo API error (${response.status}): ${errorText}`);
|
|
33
|
+
}
|
|
34
|
+
// Handle empty responses
|
|
35
|
+
const text = await response.text();
|
|
36
|
+
if (!text) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
return JSON.parse(text);
|
|
40
|
+
}
|
|
41
|
+
// Server endpoints
|
|
42
|
+
async getServerInfo() {
|
|
43
|
+
return this.request('/info');
|
|
44
|
+
}
|
|
45
|
+
async healthCheck() {
|
|
46
|
+
return this.request('/server');
|
|
47
|
+
}
|
|
48
|
+
// Project endpoints
|
|
49
|
+
async listProjects(params) {
|
|
50
|
+
const searchParams = new URLSearchParams();
|
|
51
|
+
if (params?.expand)
|
|
52
|
+
searchParams.set('expand', params.expand);
|
|
53
|
+
if (params?.startIndex)
|
|
54
|
+
searchParams.set('start-index', String(params.startIndex));
|
|
55
|
+
if (params?.maxResults)
|
|
56
|
+
searchParams.set('max-result', String(params.maxResults));
|
|
57
|
+
const query = searchParams.toString();
|
|
58
|
+
return this.request(`/project${query ? `?${query}` : ''}`);
|
|
59
|
+
}
|
|
60
|
+
async getProject(projectKey, expand) {
|
|
61
|
+
const query = expand ? `?expand=${expand}` : '';
|
|
62
|
+
return this.request(`/project/${projectKey}${query}`);
|
|
63
|
+
}
|
|
64
|
+
// Plan endpoints
|
|
65
|
+
async listPlans(params) {
|
|
66
|
+
const searchParams = new URLSearchParams();
|
|
67
|
+
if (params?.expand)
|
|
68
|
+
searchParams.set('expand', params.expand);
|
|
69
|
+
if (params?.startIndex)
|
|
70
|
+
searchParams.set('start-index', String(params.startIndex));
|
|
71
|
+
if (params?.maxResults)
|
|
72
|
+
searchParams.set('max-result', String(params.maxResults));
|
|
73
|
+
const query = searchParams.toString();
|
|
74
|
+
return this.request(`/plan${query ? `?${query}` : ''}`);
|
|
75
|
+
}
|
|
76
|
+
async getPlan(planKey, expand) {
|
|
77
|
+
const query = expand ? `?expand=${expand}` : '';
|
|
78
|
+
return this.request(`/plan/${planKey}${query}`);
|
|
79
|
+
}
|
|
80
|
+
async searchPlans(name, params) {
|
|
81
|
+
const searchParams = new URLSearchParams();
|
|
82
|
+
searchParams.set('searchTerm', name);
|
|
83
|
+
if (params?.fuzzy !== undefined)
|
|
84
|
+
searchParams.set('fuzzy', String(params.fuzzy));
|
|
85
|
+
if (params?.startIndex)
|
|
86
|
+
searchParams.set('start-index', String(params.startIndex));
|
|
87
|
+
if (params?.maxResults)
|
|
88
|
+
searchParams.set('max-result', String(params.maxResults));
|
|
89
|
+
return this.request(`/search/plans?${searchParams.toString()}`);
|
|
90
|
+
}
|
|
91
|
+
async enablePlan(planKey) {
|
|
92
|
+
return this.request(`/plan/${planKey}/enable`, { method: 'POST' });
|
|
93
|
+
}
|
|
94
|
+
async disablePlan(planKey) {
|
|
95
|
+
return this.request(`/plan/${planKey}/enable`, { method: 'DELETE' });
|
|
96
|
+
}
|
|
97
|
+
// Branch endpoints
|
|
98
|
+
async listPlanBranches(planKey, params) {
|
|
99
|
+
const searchParams = new URLSearchParams();
|
|
100
|
+
if (params?.enabledOnly !== undefined)
|
|
101
|
+
searchParams.set('enabledOnly', String(params.enabledOnly));
|
|
102
|
+
if (params?.startIndex)
|
|
103
|
+
searchParams.set('start-index', String(params.startIndex));
|
|
104
|
+
if (params?.maxResults)
|
|
105
|
+
searchParams.set('max-result', String(params.maxResults));
|
|
106
|
+
const query = searchParams.toString();
|
|
107
|
+
return this.request(`/plan/${planKey}/branch${query ? `?${query}` : ''}`);
|
|
108
|
+
}
|
|
109
|
+
async getPlanBranch(planKey, branchName) {
|
|
110
|
+
return this.request(`/plan/${planKey}/branch/${encodeURIComponent(branchName)}`);
|
|
111
|
+
}
|
|
112
|
+
// Build endpoints
|
|
113
|
+
async triggerBuild(planKey, params) {
|
|
114
|
+
const searchParams = new URLSearchParams();
|
|
115
|
+
if (params?.stage)
|
|
116
|
+
searchParams.set('stage', params.stage);
|
|
117
|
+
if (params?.executeAllStages !== undefined) {
|
|
118
|
+
searchParams.set('executeAllStages', String(params.executeAllStages));
|
|
119
|
+
}
|
|
120
|
+
if (params?.customRevision)
|
|
121
|
+
searchParams.set('customRevision', params.customRevision);
|
|
122
|
+
// Add bamboo variables
|
|
123
|
+
if (params?.variables) {
|
|
124
|
+
for (const [key, value] of Object.entries(params.variables)) {
|
|
125
|
+
searchParams.set(`bamboo.variable.${key}`, value);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const query = searchParams.toString();
|
|
129
|
+
return this.request(`/queue/${planKey}${query ? `?${query}` : ''}`, { method: 'POST' });
|
|
130
|
+
}
|
|
131
|
+
async stopBuild(planKey) {
|
|
132
|
+
// To stop a build, we need to DELETE the job keys (not the plan key)
|
|
133
|
+
// First, check if the plan is currently building
|
|
134
|
+
const plan = await this.request(`/plan/${planKey}`);
|
|
135
|
+
if (!plan.isBuilding) {
|
|
136
|
+
return {
|
|
137
|
+
message: `Plan ${planKey} is not currently building`,
|
|
138
|
+
success: false
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Get the list of results including in-progress builds
|
|
142
|
+
const results = await this.request(`/result/${planKey}?includeAllStates=true&max-result=5`);
|
|
143
|
+
// Find the in-progress build
|
|
144
|
+
const runningBuild = results.results?.result?.find(r => r.lifeCycleState === 'InProgress');
|
|
145
|
+
if (!runningBuild) {
|
|
146
|
+
return {
|
|
147
|
+
message: `No running build found for plan ${planKey}`,
|
|
148
|
+
success: false
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Get the running build with job details
|
|
152
|
+
const buildResult = await this.request(`/result/${runningBuild.key}?expand=stages.stage.results.result`);
|
|
153
|
+
// Find all jobs and stop them
|
|
154
|
+
const stoppedJobs = [];
|
|
155
|
+
const failedJobs = [];
|
|
156
|
+
if (buildResult.stages?.stage) {
|
|
157
|
+
for (const stage of buildResult.stages.stage) {
|
|
158
|
+
if (stage.results?.result) {
|
|
159
|
+
for (const job of stage.results.result) {
|
|
160
|
+
if (job.lifeCycleState === 'InProgress' || job.lifeCycleState === 'Pending' || job.lifeCycleState === 'Queued') {
|
|
161
|
+
try {
|
|
162
|
+
await this.request(`/queue/${job.key}`, { method: 'DELETE' });
|
|
163
|
+
stoppedJobs.push(job.key);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
failedJobs.push(job.key);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
message: stoppedJobs.length > 0
|
|
175
|
+
? `Stopped ${stoppedJobs.length} job(s) for build ${runningBuild.key}`
|
|
176
|
+
: `No running jobs found to stop for build ${runningBuild.key}`,
|
|
177
|
+
buildKey: runningBuild.key,
|
|
178
|
+
stoppedJobs,
|
|
179
|
+
failedJobs,
|
|
180
|
+
success: stoppedJobs.length > 0
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
async getBuildResult(buildKey, expand) {
|
|
184
|
+
const query = expand ? `?expand=${expand}` : '';
|
|
185
|
+
return this.request(`/result/${buildKey}${query}`);
|
|
186
|
+
}
|
|
187
|
+
async getLatestBuildResult(planKey, expand) {
|
|
188
|
+
const query = expand ? `?expand=${expand}` : '';
|
|
189
|
+
return this.request(`/result/${planKey}/latest${query}`);
|
|
190
|
+
}
|
|
191
|
+
async listBuildResults(params) {
|
|
192
|
+
const searchParams = new URLSearchParams();
|
|
193
|
+
if (params?.buildState)
|
|
194
|
+
searchParams.set('buildstate', params.buildState);
|
|
195
|
+
if (params?.startIndex)
|
|
196
|
+
searchParams.set('start-index', String(params.startIndex));
|
|
197
|
+
if (params?.maxResults)
|
|
198
|
+
searchParams.set('max-result', String(params.maxResults));
|
|
199
|
+
if (params?.expand)
|
|
200
|
+
searchParams.set('expand', params.expand);
|
|
201
|
+
if (params?.includeAllStates)
|
|
202
|
+
searchParams.set('includeAllStates', 'true');
|
|
203
|
+
let endpoint = '/result';
|
|
204
|
+
if (params?.projectKey && params?.planKey) {
|
|
205
|
+
endpoint = `/result/${params.projectKey}-${params.planKey}`;
|
|
206
|
+
}
|
|
207
|
+
else if (params?.projectKey) {
|
|
208
|
+
endpoint = `/result/${params.projectKey}`;
|
|
209
|
+
}
|
|
210
|
+
const query = searchParams.toString();
|
|
211
|
+
return this.request(`${endpoint}${query ? `?${query}` : ''}`);
|
|
212
|
+
}
|
|
213
|
+
async getBuildLogs(buildKey, jobKey) {
|
|
214
|
+
// Build logs are obtained by getting the job result with logFiles expand
|
|
215
|
+
// The buildKey should be a job result key (e.g., PROJ-PLAN-JOB1-123)
|
|
216
|
+
// If only a plan result key is provided (e.g., PROJ-PLAN-123), we need to find the job keys first
|
|
217
|
+
if (jobKey) {
|
|
218
|
+
// If job key provided, construct the full job result key
|
|
219
|
+
const jobResultKey = `${buildKey.replace(/-\d+$/, '')}-${jobKey}-${buildKey.split('-').pop()}`;
|
|
220
|
+
const result = await this.request(`/result/${jobResultKey}?expand=logFiles`);
|
|
221
|
+
return {
|
|
222
|
+
buildKey: jobResultKey,
|
|
223
|
+
logFiles: result.logFiles || [],
|
|
224
|
+
message: result.logFiles?.length
|
|
225
|
+
? 'Log files are available via the URLs below. These require browser authentication to download.'
|
|
226
|
+
: 'No log files found for this build.'
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// Try to get the build result with stages to find job results
|
|
230
|
+
const result = await this.request(`/result/${buildKey}?expand=stages.stage.results.result`);
|
|
231
|
+
// Extract log files from all jobs
|
|
232
|
+
const allLogs = [];
|
|
233
|
+
if (result.stages?.stage) {
|
|
234
|
+
for (const stage of result.stages.stage) {
|
|
235
|
+
if (stage.results?.result) {
|
|
236
|
+
for (const job of stage.results.result) {
|
|
237
|
+
if (job.logFiles && job.logFiles.length > 0) {
|
|
238
|
+
allLogs.push({
|
|
239
|
+
jobKey: job.key,
|
|
240
|
+
logFiles: job.logFiles
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
buildKey,
|
|
249
|
+
jobs: allLogs,
|
|
250
|
+
message: allLogs.length
|
|
251
|
+
? 'Log files are available via the URLs below. These require browser authentication to download.'
|
|
252
|
+
: 'No log files found for this build.'
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
async getBuildResultWithLogs(buildKey, params) {
|
|
256
|
+
// First, try to get the result with logEntries (works for job-level keys)
|
|
257
|
+
const maxResult = params?.maxLogLines || 100;
|
|
258
|
+
const result = await this.request(`/result/${buildKey}?expand=logEntries,stages.stage.results.result&max-result=${maxResult}`);
|
|
259
|
+
// If logEntries exists and has entries, this is a job result - return directly
|
|
260
|
+
if (result.logEntries?.logEntry && result.logEntries.logEntry.length > 0) {
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
// This is a plan result - fetch logs from all jobs
|
|
264
|
+
const jobLogs = [];
|
|
265
|
+
if (result.stages?.stage) {
|
|
266
|
+
for (const stage of result.stages.stage) {
|
|
267
|
+
if (stage.results?.result) {
|
|
268
|
+
for (const job of stage.results.result) {
|
|
269
|
+
// Fetch logs for each job
|
|
270
|
+
const jobResult = await this.request(`/result/${job.key}?expand=logEntries&max-result=${maxResult}`);
|
|
271
|
+
jobLogs.push({
|
|
272
|
+
jobKey: job.key,
|
|
273
|
+
jobName: job.key.split('-').slice(-2, -1)[0], // Extract job name from key
|
|
274
|
+
buildState: jobResult.buildState,
|
|
275
|
+
logEntries: {
|
|
276
|
+
size: jobResult.logEntries?.size || 0,
|
|
277
|
+
logs: (jobResult.logEntries?.logEntry || []).map(entry => ({
|
|
278
|
+
date: entry.formattedDate,
|
|
279
|
+
log: entry.unstyledLog
|
|
280
|
+
}))
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
key: result.key,
|
|
289
|
+
buildState: result.buildState,
|
|
290
|
+
lifeCycleState: result.lifeCycleState,
|
|
291
|
+
isJobResult: false,
|
|
292
|
+
jobs: jobLogs,
|
|
293
|
+
totalLogEntries: jobLogs.reduce((sum, job) => sum + job.logEntries.size, 0)
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
// Queue endpoints
|
|
297
|
+
async getBuildQueue(expand) {
|
|
298
|
+
const query = expand ? `?expand=${expand}` : '?expand=queuedBuilds';
|
|
299
|
+
return this.request(`/queue${query}`);
|
|
300
|
+
}
|
|
301
|
+
async getDeploymentQueue() {
|
|
302
|
+
// Note: The deployment queue endpoint is not available in all Bamboo versions
|
|
303
|
+
// We'll try to get it from the deployment dashboard instead
|
|
304
|
+
try {
|
|
305
|
+
// Try the standard endpoint first
|
|
306
|
+
return await this.request('/deploy/queue?expand=queuedDeployments');
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// If that fails, return info about checking deployment results instead
|
|
310
|
+
return {
|
|
311
|
+
message: 'Deployment queue endpoint not available in this Bamboo version. Use bamboo_get_deployment_results to check deployment status for specific environments.',
|
|
312
|
+
available: false
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Deployment endpoints
|
|
317
|
+
async listDeploymentProjects() {
|
|
318
|
+
return this.request('/deploy/project/all');
|
|
319
|
+
}
|
|
320
|
+
async getDeploymentProject(projectId) {
|
|
321
|
+
return this.request(`/deploy/project/${projectId}`);
|
|
322
|
+
}
|
|
323
|
+
async triggerDeployment(versionId, environmentId) {
|
|
324
|
+
return this.request(`/queue/deployment?versionId=${versionId}&environmentId=${environmentId}`, { method: 'POST' });
|
|
325
|
+
}
|
|
326
|
+
async getDeploymentResults(environmentId, params) {
|
|
327
|
+
const searchParams = new URLSearchParams();
|
|
328
|
+
if (params?.startIndex)
|
|
329
|
+
searchParams.set('start-index', String(params.startIndex));
|
|
330
|
+
if (params?.maxResults)
|
|
331
|
+
searchParams.set('max-result', String(params.maxResults));
|
|
332
|
+
const query = searchParams.toString();
|
|
333
|
+
return this.request(`/deploy/environment/${environmentId}/results${query ? `?${query}` : ''}`);
|
|
334
|
+
}
|
|
335
|
+
async getDeploymentResult(deploymentResultId, params) {
|
|
336
|
+
const searchParams = new URLSearchParams();
|
|
337
|
+
if (params?.includeLogs) {
|
|
338
|
+
searchParams.set('includeLogs', 'true');
|
|
339
|
+
if (params?.maxLogLines) {
|
|
340
|
+
searchParams.set('max-result', String(params.maxLogLines));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const query = searchParams.toString();
|
|
344
|
+
return this.request(`/deploy/result/${deploymentResultId}${query ? `?${query}` : ''}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// Factory function to create client from environment variables
|
|
348
|
+
export function createBambooClientFromEnv() {
|
|
349
|
+
const baseUrl = process.env.BAMBOO_URL;
|
|
350
|
+
const token = process.env.BAMBOO_TOKEN;
|
|
351
|
+
const proxyUrl = process.env.BAMBOO_PROXY;
|
|
352
|
+
if (!baseUrl) {
|
|
353
|
+
throw new Error('BAMBOO_URL environment variable is required');
|
|
354
|
+
}
|
|
355
|
+
if (!token) {
|
|
356
|
+
throw new Error('BAMBOO_TOKEN environment variable is required');
|
|
357
|
+
}
|
|
358
|
+
return new BambooClient({
|
|
359
|
+
baseUrl,
|
|
360
|
+
token,
|
|
361
|
+
proxyUrl,
|
|
362
|
+
});
|
|
363
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { createBambooClientFromEnv } from './bamboo-client.js';
|
|
5
|
+
import { registerServerTools } from './tools/server.js';
|
|
6
|
+
import { registerProjectTools } from './tools/projects.js';
|
|
7
|
+
import { registerPlanTools } from './tools/plans.js';
|
|
8
|
+
import { registerBranchTools } from './tools/branches.js';
|
|
9
|
+
import { registerBuildTools } from './tools/builds.js';
|
|
10
|
+
import { registerQueueTools } from './tools/queue.js';
|
|
11
|
+
import { registerDeploymentTools } from './tools/deployments.js';
|
|
12
|
+
async function main() {
|
|
13
|
+
// Create the Bamboo client from environment variables
|
|
14
|
+
const client = createBambooClientFromEnv();
|
|
15
|
+
// Create the MCP server
|
|
16
|
+
const server = new McpServer({
|
|
17
|
+
name: 'bamboo-mcp-server',
|
|
18
|
+
version: '1.0.0',
|
|
19
|
+
});
|
|
20
|
+
// Register all tools
|
|
21
|
+
registerServerTools(server, client);
|
|
22
|
+
registerProjectTools(server, client);
|
|
23
|
+
registerPlanTools(server, client);
|
|
24
|
+
registerBranchTools(server, client);
|
|
25
|
+
registerBuildTools(server, client);
|
|
26
|
+
registerQueueTools(server, client);
|
|
27
|
+
registerDeploymentTools(server, client);
|
|
28
|
+
// Connect via stdio transport
|
|
29
|
+
const transport = new StdioServerTransport();
|
|
30
|
+
await server.connect(transport);
|
|
31
|
+
// Handle graceful shutdown
|
|
32
|
+
process.on('SIGINT', async () => {
|
|
33
|
+
await server.close();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
});
|
|
36
|
+
process.on('SIGTERM', async () => {
|
|
37
|
+
await server.close();
|
|
38
|
+
process.exit(0);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
main().catch((error) => {
|
|
42
|
+
console.error('Fatal error:', error);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerBranchTools(server, client) {
|
|
3
|
+
// List plan branches
|
|
4
|
+
server.tool('bamboo_list_plan_branches', 'List all branches for a Bamboo build plan', {
|
|
5
|
+
plan_key: z.string().describe('The plan key (e.g., "PROJ-PLAN")'),
|
|
6
|
+
enabled_only: z.boolean().optional().describe('Only return enabled branches'),
|
|
7
|
+
start_index: z.number().optional().describe('Starting index for pagination (default: 0)'),
|
|
8
|
+
max_results: z.number().optional().describe('Maximum number of results to return (default: 25)'),
|
|
9
|
+
}, async ({ plan_key, enabled_only, start_index, max_results }) => {
|
|
10
|
+
try {
|
|
11
|
+
const branches = await client.listPlanBranches(plan_key, {
|
|
12
|
+
enabledOnly: enabled_only,
|
|
13
|
+
startIndex: start_index,
|
|
14
|
+
maxResults: max_results,
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: 'text',
|
|
20
|
+
text: JSON.stringify(branches, null, 2),
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
isError: true,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// Get plan branch
|
|
38
|
+
server.tool('bamboo_get_plan_branch', 'Get details of a specific plan branch', {
|
|
39
|
+
plan_key: z.string().describe('The plan key (e.g., "PROJ-PLAN")'),
|
|
40
|
+
branch_name: z.string().describe('The branch name'),
|
|
41
|
+
}, async ({ plan_key, branch_name }) => {
|
|
42
|
+
try {
|
|
43
|
+
const branch = await client.getPlanBranch(plan_key, branch_name);
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: 'text',
|
|
48
|
+
text: JSON.stringify(branch, null, 2),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
content: [
|
|
56
|
+
{
|
|
57
|
+
type: 'text',
|
|
58
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
isError: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|