@stubbedev/atlassian-mcp 0.4.1 → 0.4.2
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 +22 -7
- package/dist/context.js +1 -0
- package/dist/index.js +16 -14
- package/dist/jira.js +10 -1
- package/package.json +1 -1
package/dist/bitbucket.js
CHANGED
|
@@ -63,18 +63,32 @@ function branchDisplayId(branch) {
|
|
|
63
63
|
function formatDate(ms) {
|
|
64
64
|
return new Date(ms).toISOString().slice(0, 10);
|
|
65
65
|
}
|
|
66
|
+
// Cap long free-text (e.g. PR descriptions) so one verbose PR does not flood
|
|
67
|
+
// the model's context. Returns the text untouched when within cap.
|
|
68
|
+
function capText(value, max) {
|
|
69
|
+
if (max <= 0 || value.length <= max)
|
|
70
|
+
return value;
|
|
71
|
+
const more = value.length - max;
|
|
72
|
+
return `${value.slice(0, max)}\n... (truncated, ${more} more chars — pass fullDescription=true for the rest)`;
|
|
73
|
+
}
|
|
66
74
|
function formatCommentThread(comment, indent = '', depth = 0) {
|
|
67
75
|
if (depth > 20)
|
|
68
76
|
return [`${indent}... (deeply nested replies omitted)`];
|
|
69
77
|
const author = comment.author?.displayName ?? comment.author?.name ?? 'Unknown';
|
|
70
78
|
const date = comment.createdDate ? ` (${formatDate(comment.createdDate)})` : '';
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
// Show only non-default flags: OPEN state, NORMAL severity and unresolved
|
|
80
|
+
// threads are the implied baseline, so badge only what deviates. Keeps the
|
|
81
|
+
// RESOLVED/BLOCKER signal while dropping repeated [OPEN/NORMAL thread=OPEN].
|
|
82
|
+
const flags = [];
|
|
83
|
+
if ((comment.state ?? 'OPEN') !== 'OPEN')
|
|
84
|
+
flags.push(comment.state);
|
|
85
|
+
if ((comment.severity ?? 'NORMAL') !== 'NORMAL')
|
|
86
|
+
flags.push(comment.severity);
|
|
87
|
+
if (comment.threadResolved === true)
|
|
88
|
+
flags.push('thread=RESOLVED');
|
|
89
|
+
const flagStr = flags.length > 0 ? ` [${flags.join('/')}]` : '';
|
|
76
90
|
const lines = [
|
|
77
|
-
`${indent}#${comment.id}
|
|
91
|
+
`${indent}#${comment.id}${flagStr} ${author}${date} (v${comment.version})`,
|
|
78
92
|
`${indent}${comment.text}`,
|
|
79
93
|
];
|
|
80
94
|
if (comment.comments && comment.comments.length > 0) {
|
|
@@ -529,6 +543,7 @@ export class BitbucketClient {
|
|
|
529
543
|
const includeComments = args.includeComments ?? true;
|
|
530
544
|
const includeDiff = args.includeDiff ?? false;
|
|
531
545
|
const includeBuildStatus = args.includeBuildStatus ?? true;
|
|
546
|
+
const descriptionCap = args.fullDescription ? 0 : args.descriptionMaxChars ?? 2000;
|
|
532
547
|
let prId = args.prId;
|
|
533
548
|
if (prId === undefined) {
|
|
534
549
|
const branch = args.fromBranch ?? safeExec('git rev-parse --abbrev-ref HEAD');
|
|
@@ -567,7 +582,7 @@ export class BitbucketClient {
|
|
|
567
582
|
url ? `URL: ${url}` : '',
|
|
568
583
|
'',
|
|
569
584
|
'Description:',
|
|
570
|
-
pr.description
|
|
585
|
+
pr.description ? capText(pr.description, descriptionCap) : '(no description)',
|
|
571
586
|
].filter((line) => line !== '');
|
|
572
587
|
sections.push(header.join('\n'));
|
|
573
588
|
if (includeBuildStatus && pr.fromRef.latestCommit) {
|
package/dist/context.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -204,7 +204,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
204
204
|
// ── Combined context (jira + bitbucket, or either alone) ─────────────
|
|
205
205
|
...(jira || bitbucket ? [{
|
|
206
206
|
name: 'get_dev_context',
|
|
207
|
-
description: 'Master entry point
|
|
207
|
+
description: 'Master entry point for "what am I working on / what\'s the status", and before any review or coding task. Returns: git branch + upstream state, Jira ticket overview (status, transitions, sprint, comments), open PR with reviewer approvals, and actionable next-step hints (create PR, merge, address blockers).',
|
|
208
208
|
inputSchema: {
|
|
209
209
|
type: 'object',
|
|
210
210
|
properties: {
|
|
@@ -216,7 +216,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
216
216
|
...(jira ? [
|
|
217
217
|
{
|
|
218
218
|
name: 'start_work',
|
|
219
|
-
description: 'Start working on a Jira ticket end-to-end: resolves the ticket (by key or free-text search with a picker when multiple match), creates a local branch with an auto-generated name, fetches the project README from Bitbucket so you have commit/PR conventions in context, and prints a
|
|
219
|
+
description: 'Start working on a Jira ticket end-to-end: resolves the ticket (by key or free-text search with a picker when multiple match), creates a local branch with an auto-generated name, fetches the project README from Bitbucket so you have commit/PR conventions in context, and prints a next-steps summary. If issueKey is omitted, provide query for free-text search.',
|
|
220
220
|
inputSchema: {
|
|
221
221
|
type: 'object',
|
|
222
222
|
properties: {
|
|
@@ -232,7 +232,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
232
232
|
},
|
|
233
233
|
{
|
|
234
234
|
name: 'jira_search',
|
|
235
|
-
description: 'Discover Jira resources
|
|
235
|
+
description: 'Discover Jira resources (tickets, projects, boards, sprints, versions, users). Set resource:\n• "issues" (default) — search by text, JQL, project, status, assignee, issue type, or mine=true for your queue\n• "projects" — list all projects and their keys\n• "issue_types" — valid types and statuses for a project\n• "boards" — list boards (pass project to filter by project key); use this to find the boardId before fetching sprints or board_overview\n• "sprints" — sprints for a board (pass boardId); if you don\'t know the boardId, first use resource=boards\n• "board_overview" — active/future sprints with their issues for a board (pass boardId); use when asked "what\'s in the sprint", "show me the board", or "what\'s everyone working on"\n• "versions" — list fix versions/releases for a project (pass project; optionally pass query to filter by name substring). If the version you need does not exist, create it yourself with `jira_version action=create` — do NOT ask the user to make it in the Jira UI.\n• "users" — find users by name/email (pass query)',
|
|
236
236
|
inputSchema: {
|
|
237
237
|
type: 'object',
|
|
238
238
|
properties: {
|
|
@@ -254,7 +254,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
254
254
|
},
|
|
255
255
|
{
|
|
256
256
|
name: 'jira_get',
|
|
257
|
-
description: 'Full details for one Jira issue: summary, description, status, assignee, sprint, available transitions, recent comments, and a list of attachments (filename, size, mime type, attachment ID).
|
|
257
|
+
description: 'Full details for one Jira issue: summary, description, status, assignee, sprint, available transitions, recent comments, and a list of attachments (filename, size, mime type, attachment ID). To view an attachment\'s contents (e.g. an image), call jira_get_attachment with the attachment ID surfaced here.',
|
|
258
258
|
inputSchema: {
|
|
259
259
|
type: 'object',
|
|
260
260
|
properties: {
|
|
@@ -264,13 +264,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
264
264
|
commentsStartAt: { type: 'number', description: 'Comment pagination offset (default 0)', default: 0 },
|
|
265
265
|
includeTransitions: { type: 'boolean', description: 'Include available transitions (default true)', default: true },
|
|
266
266
|
includeSprint: { type: 'boolean', description: 'Include sprint data (default true)', default: true },
|
|
267
|
+
fullDescription: { type: 'boolean', description: 'Return the full description even when long (default false — descriptions over ~2000 chars are truncated to save context)', default: false },
|
|
267
268
|
},
|
|
268
269
|
required: ['issueKey'],
|
|
269
270
|
},
|
|
270
271
|
},
|
|
271
272
|
{
|
|
272
273
|
name: 'jira_mutate',
|
|
273
|
-
description: `
|
|
274
|
+
description: `Create/update a ticket, transition status, assign, comment, link issues, or log work — bundles create/update/transition/comment/link/worklog in one call. ${JIRA_WIKI_MARKUP_HINT}`,
|
|
274
275
|
inputSchema: {
|
|
275
276
|
type: 'object',
|
|
276
277
|
properties: {
|
|
@@ -334,7 +335,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
334
335
|
},
|
|
335
336
|
{
|
|
336
337
|
name: 'jira_get_attachment',
|
|
337
|
-
description: 'Fetch a Jira attachment by ID and return its contents inline. Images are auto-resized
|
|
338
|
+
description: 'Fetch a Jira attachment by ID and return its contents inline. Images are auto-resized + re-encoded; text/JSON/XML return as text; videos and animated images (GIF/APNG/animated WebP) are decoded with ffmpeg into sampled frames (re-call with start/end/frames or mode=scenes to refine); audio returns as an audio block; PDFs return extracted text. Oversized/non-renderable files are saved to a temp file and the path returned. Use jira_get first to discover attachment IDs.',
|
|
338
339
|
inputSchema: {
|
|
339
340
|
type: 'object',
|
|
340
341
|
properties: {
|
|
@@ -353,7 +354,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
353
354
|
},
|
|
354
355
|
{
|
|
355
356
|
name: 'jira_comment',
|
|
356
|
-
description: `Add, update, or delete a comment on a Jira issue.
|
|
357
|
+
description: `Add, update, or delete a comment on a Jira issue. action defaults to "add". Can only edit/delete your own comments. ${JIRA_WIKI_MARKUP_HINT}`,
|
|
357
358
|
inputSchema: {
|
|
358
359
|
type: 'object',
|
|
359
360
|
properties: {
|
|
@@ -367,7 +368,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
367
368
|
},
|
|
368
369
|
{
|
|
369
370
|
name: 'jira_version',
|
|
370
|
-
description: 'Manage Jira fix versions (releases)
|
|
371
|
+
description: 'Manage Jira fix versions (releases): create, update, release, archive, delete. action defaults to "create". For create pass projectKey + name. For update/release/archive/delete pass id (look it up via jira_search resource=versions). "release" sets released=true and defaults releaseDate to today. Once a version exists you can set it on tickets via jira_mutate update.fixVersion.',
|
|
371
372
|
inputSchema: {
|
|
372
373
|
type: 'object',
|
|
373
374
|
properties: {
|
|
@@ -387,7 +388,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
387
388
|
] : []),
|
|
388
389
|
...(bitbucket ? [{
|
|
389
390
|
name: 'bitbucket_search',
|
|
390
|
-
description: 'Discover Bitbucket resources
|
|
391
|
+
description: 'Discover Bitbucket resources (PRs, repos, branches, users). 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.',
|
|
391
392
|
inputSchema: {
|
|
392
393
|
type: 'object',
|
|
393
394
|
properties: {
|
|
@@ -410,7 +411,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
410
411
|
},
|
|
411
412
|
{
|
|
412
413
|
name: 'bitbucket_get_pr',
|
|
413
|
-
description: 'Full details for one PR: metadata, commits, open comments, blockers, optional diff, and any attachments referenced from the description or comments (with attachment ID + filename).
|
|
414
|
+
description: 'Full details for one PR: metadata, commits, open comments, blockers, optional diff, and any attachments referenced from the description or comments (with attachment ID + filename). 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. To view an attachment\'s contents, call bitbucket_get_attachment with the surfaced attachment ID. 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.',
|
|
414
415
|
inputSchema: {
|
|
415
416
|
type: 'object',
|
|
416
417
|
properties: {
|
|
@@ -430,12 +431,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
430
431
|
commentsStart: { type: 'number', description: 'Comment pagination offset (default 0)', default: 0 },
|
|
431
432
|
commitsLimit: { type: 'number', description: 'Max commits (default 25)', default: 25 },
|
|
432
433
|
diffMaxChars: { type: 'number', description: 'Max diff chars when includeDiff=true (default 8000)', default: 8000 },
|
|
434
|
+
fullDescription: { type: 'boolean', description: 'Return the full PR description even when long (default false — descriptions over ~2000 chars are truncated to save context)', default: false },
|
|
433
435
|
},
|
|
434
436
|
},
|
|
435
437
|
},
|
|
436
438
|
{
|
|
437
439
|
name: 'bitbucket_mutate',
|
|
438
|
-
description: '
|
|
440
|
+
description: 'Create, update, approve/unapprove, mark needs_work, decline, or merge a PR — in one call. Auto-targets the open PR for the current branch when prId is omitted. needs_work sets your reviewer status to "Needs work" (Bitbucket Server\'s changes-requested signal); revert with action=unapprove.',
|
|
439
441
|
inputSchema: {
|
|
440
442
|
type: 'object',
|
|
441
443
|
properties: {
|
|
@@ -521,7 +523,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
521
523
|
},
|
|
522
524
|
{
|
|
523
525
|
name: 'bitbucket_get_attachment',
|
|
524
|
-
description: 'Fetch a Bitbucket repo attachment by ID and return its contents inline.
|
|
526
|
+
description: 'Fetch a Bitbucket repo attachment by ID and return its contents inline. Attachments are repo-scoped, referenced from PR descriptions/comments via attachment:<id> markdown; use bitbucket_get_pr first to surface IDs. Images are auto-resized + re-encoded; text/JSON/XML return as text; videos and animated images (GIF/APNG/animated WebP) are decoded with ffmpeg into sampled frames (re-call with start/end/frames or mode=scenes to refine); audio returns as an audio block; PDFs return extracted text. Oversized/non-renderable files are saved to a temp file and the path returned.',
|
|
525
527
|
inputSchema: {
|
|
526
528
|
type: 'object',
|
|
527
529
|
properties: {
|
|
@@ -544,7 +546,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
544
546
|
},
|
|
545
547
|
{
|
|
546
548
|
name: 'bitbucket_pr_tasks',
|
|
547
|
-
description: 'Manage PR tasks (checklist items)
|
|
549
|
+
description: 'Manage PR tasks (checklist items): list, create, resolve, reopen, delete. Tasks are distinct from comments — they appear as a checklist in the PR sidebar.',
|
|
548
550
|
inputSchema: {
|
|
549
551
|
type: 'object',
|
|
550
552
|
properties: {
|
|
@@ -565,7 +567,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
565
567
|
// ── Combined workflow ─────────────────────────────────────────────────
|
|
566
568
|
...(jira && bitbucket ? [{
|
|
567
569
|
name: 'complete_work',
|
|
568
|
-
description: 'Close the loop on a finished branch: merges the open PR and transitions the Jira ticket to Done (or a named transition).
|
|
570
|
+
description: 'Close the loop on a finished branch: merges the open PR and transitions the Jira ticket to Done (or a named transition). Mirrors start_work.',
|
|
569
571
|
inputSchema: {
|
|
570
572
|
type: 'object',
|
|
571
573
|
properties: {
|
package/dist/jira.js
CHANGED
|
@@ -10,6 +10,14 @@ const EMOJI_RE = /\p{Extended_Pictographic}/u;
|
|
|
10
10
|
function text(t) {
|
|
11
11
|
return { content: [{ type: 'text', text: t }] };
|
|
12
12
|
}
|
|
13
|
+
// Cap long free-text (e.g. issue descriptions) so a single verbose ticket does
|
|
14
|
+
// not flood the model's context. Returns the text untouched when within cap.
|
|
15
|
+
function capText(value, max) {
|
|
16
|
+
if (max <= 0 || value.length <= max)
|
|
17
|
+
return value;
|
|
18
|
+
const more = value.length - max;
|
|
19
|
+
return `${value.slice(0, max)}\n... (truncated, ${more} more chars — pass fullDescription=true for the rest)`;
|
|
20
|
+
}
|
|
13
21
|
function pagination(total, startAt, count) {
|
|
14
22
|
const end = startAt + count;
|
|
15
23
|
return total > end ? ` (showing ${startAt + 1}–${end} of ${total}, use startAt=${end} for next page)` : '';
|
|
@@ -456,6 +464,7 @@ export class JiraClient {
|
|
|
456
464
|
const includeComments = args.includeComments ?? true;
|
|
457
465
|
const includeTransitions = args.includeTransitions ?? true;
|
|
458
466
|
const includeSprint = args.includeSprint ?? true;
|
|
467
|
+
const descriptionCap = args.fullDescription ? 0 : args.descriptionMaxChars ?? 2000;
|
|
459
468
|
const commentsMaxResults = args.commentsMaxResults ?? 10;
|
|
460
469
|
const commentsStartAt = args.commentsStartAt ?? 0;
|
|
461
470
|
const baseFields = 'summary,description,status,assignee,priority,issuetype,labels,components,parent,fixVersions,issuelinks,subtasks,attachment';
|
|
@@ -517,7 +526,7 @@ export class JiraClient {
|
|
|
517
526
|
const names = (transitions?.transitions ?? []).map((t) => `${t.name} -> ${t.to.name}`);
|
|
518
527
|
lines.push(`Transitions: ${names.length > 0 ? names.join(', ') : '(none)'}`);
|
|
519
528
|
}
|
|
520
|
-
lines.push('', 'Description:', f.description
|
|
529
|
+
lines.push('', 'Description:', f.description ? capText(f.description, descriptionCap) : '(no description)');
|
|
521
530
|
if (f.attachment?.length) {
|
|
522
531
|
lines.push('', `Attachments: ${f.attachment.length}`);
|
|
523
532
|
for (const att of f.attachment) {
|