@stubbedev/atlassian-mcp 0.2.9 → 0.2.11
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/bitbucket.js +32 -1
- package/dist/index.js +9 -6
- package/dist/jira.js +25 -8
- package/package.json +1 -1
package/dist/bitbucket.js
CHANGED
|
@@ -340,6 +340,37 @@ export class BitbucketClient {
|
|
|
340
340
|
const lines = data.values.map((r, i) => `${start + i + 1}. ${r.project.key}/${r.slug} — ${r.name}`);
|
|
341
341
|
return text(`${data.values.length} repo(s)${pageHint(data)}:\n${lines.join('\n')}`);
|
|
342
342
|
}
|
|
343
|
+
async searchUsers(args) {
|
|
344
|
+
const params = new URLSearchParams();
|
|
345
|
+
if (args.query)
|
|
346
|
+
params.set('filter', args.query);
|
|
347
|
+
params.set('limit', String(args.limit ?? 25));
|
|
348
|
+
if (args.start)
|
|
349
|
+
params.set('start', String(args.start));
|
|
350
|
+
let path;
|
|
351
|
+
if (args.projectKey && args.repoSlug) {
|
|
352
|
+
path = `${this.rp(args.projectKey, args.repoSlug)}/permissions/users?${params}`;
|
|
353
|
+
}
|
|
354
|
+
else if (args.projectKey) {
|
|
355
|
+
path = `/projects/${encodeURIComponent(args.projectKey)}/permissions/users?${params}`;
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
path = `/users?${params}`;
|
|
359
|
+
}
|
|
360
|
+
const data = await this.request('GET', path);
|
|
361
|
+
if (!data || data.values.length === 0)
|
|
362
|
+
return text('No users found.');
|
|
363
|
+
const lines = data.values.map((entry, i) => {
|
|
364
|
+
const user = entry.user ?? entry;
|
|
365
|
+
const parts = [`${i + 1}. ${user.displayName} (${user.name})`];
|
|
366
|
+
if (user.emailAddress)
|
|
367
|
+
parts.push(`— ${user.emailAddress}`);
|
|
368
|
+
if (user.active === false)
|
|
369
|
+
parts.push('[inactive]');
|
|
370
|
+
return parts.join(' ');
|
|
371
|
+
});
|
|
372
|
+
return text(`${data.values.length} user(s)${pageHint(data)}:\n${lines.join('\n')}`);
|
|
373
|
+
}
|
|
343
374
|
async listPullRequests(args) {
|
|
344
375
|
const { projectKey, repoSlug } = this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
|
|
345
376
|
const { state = 'OPEN', fromBranch, text: searchText, limit = 25, start = 0 } = args;
|
|
@@ -972,7 +1003,7 @@ export class BitbucketClient {
|
|
|
972
1003
|
return text(`${data.values.length} task(s) on PR #${args.prId} (${open} open)${pageHint(data)}:\n${lines.join('\n')}`);
|
|
973
1004
|
}
|
|
974
1005
|
async mutatePrTask(args) {
|
|
975
|
-
|
|
1006
|
+
this.resolveProjectAndRepo(args.projectKey, args.repoSlug);
|
|
976
1007
|
if (args.action === 'create') {
|
|
977
1008
|
if (!args.text)
|
|
978
1009
|
throw new Error('text is required to create a task.');
|
package/dist/index.js
CHANGED
|
@@ -263,17 +263,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
263
263
|
] : []),
|
|
264
264
|
...(bitbucket ? [{
|
|
265
265
|
name: 'bitbucket_search',
|
|
266
|
-
description: 'Discover Bitbucket resources. Use when asked "what PRs are open?", "show me the repos", "find the PR for this branch", or "list branches". Set resource:\n• "pull_requests" (default) — list PRs by state/branch/text; mine=true for your inbox\n• "repos" — list repositories in a project\n• "branches" — list or filter branches in a repo',
|
|
266
|
+
description: 'Discover Bitbucket resources. Use when asked "what PRs are open?", "show me the repos", "find the PR for this branch", or "list branches". Set resource:\n• "pull_requests" (default) — list PRs by state/branch/text; mine=true for your inbox\n• "repos" — list repositories in a project\n• "branches" — list or filter branches in a repo\n• "users" — find users by name/email (pass query); add projectKey+repoSlug to restrict to users with repo access. ALWAYS use this to look up valid usernames before adding reviewers to a PR.',
|
|
267
267
|
inputSchema: {
|
|
268
268
|
type: 'object',
|
|
269
269
|
properties: {
|
|
270
|
-
resource: { type: 'string', enum: ['pull_requests', 'repos', 'branches'], description: 'What to search (default: pull_requests)' },
|
|
270
|
+
resource: { type: 'string', enum: ['pull_requests', 'repos', 'branches', 'users'], description: 'What to search (default: pull_requests)' },
|
|
271
271
|
mine: { type: 'boolean', description: 'Return your own PRs by role (resource=pull_requests only)' },
|
|
272
272
|
role: { type: 'string', enum: ['author', 'reviewer', 'participant'], description: 'Your role filter when mine=true' },
|
|
273
273
|
projectKey: { type: 'string', description: 'Bitbucket project code, e.g. "ENG"' },
|
|
274
274
|
project: { type: 'string', description: 'Alias for projectKey' },
|
|
275
275
|
repoSlug: { type: 'string', description: 'Repository slug' },
|
|
276
276
|
repo: { type: 'string', description: 'Alias for repoSlug' },
|
|
277
|
+
query: { type: 'string', description: 'Name or email filter (resource=users only)' },
|
|
277
278
|
state: { type: 'string', enum: ['OPEN', 'MERGED', 'DECLINED'], description: 'PR state filter (default OPEN)' },
|
|
278
279
|
fromBranch: { type: 'string', description: 'Filter PRs from this source branch' },
|
|
279
280
|
text: { type: 'string', description: 'Filter PRs by title/description text' },
|
|
@@ -285,7 +286,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
285
286
|
},
|
|
286
287
|
{
|
|
287
288
|
name: 'bitbucket_get_pr',
|
|
288
|
-
description: 'Full details for one PR: metadata, commits, open comments, blockers, and optional diff. Use when asked to "review this PR", "show me the review comments", "what\'s blocking the merge", or after get_dev_context surfaces a prId.
|
|
289
|
+
description: 'Full details for one PR: metadata, commits, open comments, blockers, and optional diff. Use when asked to "review this PR", "show me the review comments", "what\'s blocking the merge", or after get_dev_context surfaces a prId. IMPORTANT: The PR branch is often not the locally checked-out branch. Do NOT read files with local tools (Read, git_get_diff, etc.) for PR context — use bitbucket_get_file with the PR\'s source branch instead. The response includes a "Viewing as" line — if it says "you are the author", do NOT add review comments or a summary unless explicitly asked; just answer questions about the PR. If it says "you are a reviewer", default to posting inline comments for suggested changes and a final summary comment.',
|
|
289
290
|
inputSchema: {
|
|
290
291
|
type: 'object',
|
|
291
292
|
properties: {
|
|
@@ -330,7 +331,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
330
331
|
description: { type: 'string', description: 'PR description (optional)' },
|
|
331
332
|
fromBranch: { type: 'string', description: 'Source branch (defaults to current branch)' },
|
|
332
333
|
toBranch: { type: 'string', description: 'Target branch (default: master)' },
|
|
333
|
-
reviewers: { type: 'array', items: { type: 'string' }, description: 'Reviewer usernames
|
|
334
|
+
reviewers: { type: 'array', items: { type: 'string' }, description: 'Reviewer usernames. Use bitbucket_search resource=users to look up valid usernames before setting this.' },
|
|
334
335
|
},
|
|
335
336
|
required: ['title'],
|
|
336
337
|
},
|
|
@@ -340,7 +341,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
340
341
|
title: { type: 'string', description: 'Updated PR title (optional)' },
|
|
341
342
|
description: { type: 'string', description: 'Updated description, or empty string to clear (optional)' },
|
|
342
343
|
toBranch: { type: 'string', description: 'Updated target branch (optional)' },
|
|
343
|
-
reviewers: { type: 'array', items: { type: 'string' }, description: 'Updated reviewer usernames. Empty array clears reviewers.' },
|
|
344
|
+
reviewers: { type: 'array', items: { type: 'string' }, description: 'Updated reviewer usernames. Empty array clears reviewers. Use bitbucket_search resource=users to look up valid usernames before setting this.' },
|
|
344
345
|
},
|
|
345
346
|
},
|
|
346
347
|
},
|
|
@@ -377,7 +378,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
377
378
|
},
|
|
378
379
|
{
|
|
379
380
|
name: 'bitbucket_get_file',
|
|
380
|
-
description: 'Raw file content from Bitbucket at a branch, tag, or commit.
|
|
381
|
+
description: 'Raw file content from Bitbucket at a branch, tag, or commit. CRITICAL: if the PR branch being reviewed is NOT the currently checked-out local branch, ALL additional file context for that review MUST come from this tool — never from local Read, git_get_diff, or any tool that reads local disk. Pass the PR source branch as ref.',
|
|
381
382
|
inputSchema: {
|
|
382
383
|
type: 'object',
|
|
383
384
|
properties: {
|
|
@@ -611,6 +612,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
611
612
|
return await bitbucket.listRepos(a);
|
|
612
613
|
if (resource === 'branches')
|
|
613
614
|
return await bitbucket.getBranches(a);
|
|
615
|
+
if (resource === 'users')
|
|
616
|
+
return await bitbucket.searchUsers({ projectKey: a.projectKey, repoSlug: a.repoSlug, query: a.query, limit: a.limit, start: a.start });
|
|
614
617
|
// pull_requests (default)
|
|
615
618
|
if (a.mine)
|
|
616
619
|
return await bitbucket.myPrs({ limit: a.limit, start: a.start, role: a.role });
|
package/dist/jira.js
CHANGED
|
@@ -95,6 +95,7 @@ export class JiraClient {
|
|
|
95
95
|
headers;
|
|
96
96
|
currentUserCache;
|
|
97
97
|
projectsCache;
|
|
98
|
+
issueLinkingEnabled;
|
|
98
99
|
constructor(baseUrl, token) {
|
|
99
100
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
100
101
|
this.headers = {
|
|
@@ -147,6 +148,13 @@ export class JiraClient {
|
|
|
147
148
|
this.currentUserCache = me;
|
|
148
149
|
return me;
|
|
149
150
|
}
|
|
151
|
+
async getIssueLinkingEnabled() {
|
|
152
|
+
if (this.issueLinkingEnabled !== undefined)
|
|
153
|
+
return this.issueLinkingEnabled;
|
|
154
|
+
const config = await this.request('GET', '/configuration');
|
|
155
|
+
this.issueLinkingEnabled = config?.issueLinkingEnabled ?? false;
|
|
156
|
+
return this.issueLinkingEnabled;
|
|
157
|
+
}
|
|
150
158
|
async assertOwnComment(comment) {
|
|
151
159
|
const me = await this.getCurrentUser();
|
|
152
160
|
const commentAuthorName = this.normalizeIdentity(comment.author.name);
|
|
@@ -602,14 +610,20 @@ export class JiraClient {
|
|
|
602
610
|
await this.request('POST', `/issue/${encodeURIComponent(issueKey)}/comment`, { body: validateCommentBody(args.comment) });
|
|
603
611
|
actions.push('added comment');
|
|
604
612
|
}
|
|
613
|
+
const warnings = [];
|
|
605
614
|
if (args.link) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
615
|
+
if (!(await this.getIssueLinkingEnabled())) {
|
|
616
|
+
warnings.push(`issue linking is disabled in this Jira instance — add the link manually`);
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
const dir = args.link.direction ?? 'outward';
|
|
620
|
+
await this.request('POST', '/issueLink', {
|
|
621
|
+
type: { name: args.link.linkType },
|
|
622
|
+
outwardIssue: { key: dir === 'outward' ? issueKey : args.link.targetIssueKey },
|
|
623
|
+
inwardIssue: { key: dir === 'outward' ? args.link.targetIssueKey : issueKey },
|
|
624
|
+
});
|
|
625
|
+
actions.push(`linked ${args.link.linkType} → ${args.link.targetIssueKey}`);
|
|
626
|
+
}
|
|
613
627
|
}
|
|
614
628
|
if (args.worklog) {
|
|
615
629
|
const wBody = { timeSpent: args.worklog.timeSpent };
|
|
@@ -623,7 +637,10 @@ export class JiraClient {
|
|
|
623
637
|
if (actions.length === 0) {
|
|
624
638
|
return text('Nothing to mutate.');
|
|
625
639
|
}
|
|
626
|
-
|
|
640
|
+
const parts = [`Mutated ${issueKey}: ${actions.join(', ')}.`, this.issueUrl(issueKey)];
|
|
641
|
+
if (warnings.length)
|
|
642
|
+
parts.push(`Warnings: ${warnings.join('; ')}`);
|
|
643
|
+
return text(parts.join('\n'));
|
|
627
644
|
}
|
|
628
645
|
async getComments(args) {
|
|
629
646
|
const { issueKey, maxResults = 50, startAt = 0 } = args;
|