wolfpack-mcp 1.0.115 → 1.0.117
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/README.md +12 -0
- package/dist/client.js +25 -4
- package/dist/index.js +100 -5
- package/dist/toolCatalogue.js +197 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -285,6 +285,8 @@ Get a wiki page by ID or slug.
|
|
|
285
285
|
|
|
286
286
|
- `page_id` (required): UUID or slug (e.g., "getting-started")
|
|
287
287
|
|
|
288
|
+
Returns a `version` alongside the content. Quote it back as `expected_version` when you save.
|
|
289
|
+
|
|
288
290
|
#### `create_wiki_page`
|
|
289
291
|
|
|
290
292
|
Create a new wiki page.
|
|
@@ -298,8 +300,18 @@ Create a new wiki page.
|
|
|
298
300
|
Update an existing wiki page.
|
|
299
301
|
|
|
300
302
|
- `page_id` (required): The page UUID
|
|
303
|
+
- `expected_version` (required): The `version` `get_wiki_page` returned
|
|
301
304
|
- `title`, `content` (optional)
|
|
302
305
|
|
|
306
|
+
**Read, then write.** `content` replaces the whole document, and wiki pages are edited by
|
|
307
|
+
people at the same time as an agent, so a save made from a copy read earlier would throw away
|
|
308
|
+
everything written since — which is what happened before #2681. `expected_version` is therefore
|
|
309
|
+
required, and a save quoting a version somebody has moved past is refused with 409: read the
|
|
310
|
+
page again, re-apply the change to what it says now, and save with the new version. Never
|
|
311
|
+
re-send the copy you were holding.
|
|
312
|
+
|
|
313
|
+
`create_wiki_page` is the exception — a page being created has no prior version.
|
|
314
|
+
|
|
303
315
|
### Journal Entries
|
|
304
316
|
|
|
305
317
|
#### `list_journal_entries`
|
package/dist/client.js
CHANGED
|
@@ -196,9 +196,9 @@ export class WolfpackClient {
|
|
|
196
196
|
throw error;
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
-
async updateWorkProgress(workItemId,
|
|
199
|
+
async updateWorkProgress(workItemId, body, teamSlug) {
|
|
200
200
|
try {
|
|
201
|
-
return await this.api.put(this.withTeamSlug(`/work-items/${workItemId}/progress`, teamSlug),
|
|
201
|
+
return await this.api.put(this.withTeamSlug(`/work-items/${workItemId}/progress`, teamSlug), body);
|
|
202
202
|
}
|
|
203
203
|
catch (error) {
|
|
204
204
|
if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
|
|
@@ -389,6 +389,20 @@ export class WolfpackClient {
|
|
|
389
389
|
async updateIssue(issueId, data, teamSlug) {
|
|
390
390
|
return this.api.patch(this.withTeamSlug(`/issues/${issueId}`, teamSlug), data);
|
|
391
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* An entity's change timeline (#2684). `project_slug` is only needed to resolve a refId,
|
|
394
|
+
* so `withTeamSlug` supplies the configured project when the caller names none.
|
|
395
|
+
*/
|
|
396
|
+
async getTimeline(entityType, entityId, options) {
|
|
397
|
+
const params = new URLSearchParams();
|
|
398
|
+
if (options?.since)
|
|
399
|
+
params.append('since', options.since);
|
|
400
|
+
if (options?.limit !== undefined)
|
|
401
|
+
params.append('limit', options.limit.toString());
|
|
402
|
+
const query = params.toString();
|
|
403
|
+
const path = `/timeline/${encodeURIComponent(entityType)}/${encodeURIComponent(entityId)}`;
|
|
404
|
+
return this.api.get(this.withTeamSlug(`${path}${query ? `?${query}` : ''}`, options?.teamSlug));
|
|
405
|
+
}
|
|
392
406
|
// Wiki Page methods
|
|
393
407
|
async listWikiPages(options) {
|
|
394
408
|
const params = new URLSearchParams();
|
|
@@ -420,9 +434,16 @@ export class WolfpackClient {
|
|
|
420
434
|
}
|
|
421
435
|
return this.fetchAllPages('/wiki-pages', params);
|
|
422
436
|
}
|
|
423
|
-
async getWikiPage(pageId) {
|
|
437
|
+
async getWikiPage(pageId, view) {
|
|
438
|
+
const params = new URLSearchParams();
|
|
439
|
+
if (view?.mode)
|
|
440
|
+
params.append('mode', view.mode);
|
|
441
|
+
// Repeated rather than joined: the route takes an array, and a heading can contain
|
|
442
|
+
// whatever punctuation a separator would have claimed.
|
|
443
|
+
view?.section?.forEach((heading) => params.append('section', heading));
|
|
444
|
+
const query = params.toString();
|
|
424
445
|
try {
|
|
425
|
-
return await this.api.get(`/wiki-pages/${encodeURIComponent(pageId)}`);
|
|
446
|
+
return await this.api.get(`/wiki-pages/${encodeURIComponent(pageId)}${query ? `?${query}` : ''}`);
|
|
426
447
|
}
|
|
427
448
|
catch (error) {
|
|
428
449
|
if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
|
package/dist/index.js
CHANGED
|
@@ -110,13 +110,29 @@ const GetWorkItemSchema = z.object({
|
|
|
110
110
|
work_item_id: refIdString().describe('The refId (number) of the work item'),
|
|
111
111
|
project_slug: z.string().optional().describe('Project slug (required when looking up by refId)'),
|
|
112
112
|
});
|
|
113
|
+
/**
|
|
114
|
+
* One targeted edit. Loose on purpose: which fields a given `type` needs is the backend's rule,
|
|
115
|
+
* said once in `contentEditService`, and a second copy here would be the thing that drifts
|
|
116
|
+
* (#2679). The refusals it raises are written to be read by whoever sent the edit.
|
|
117
|
+
*/
|
|
118
|
+
const ContentEditSchema = z.object({
|
|
119
|
+
type: z.string().describe('replace, replace_section, append_to_section, …'),
|
|
120
|
+
old: z.string().optional().describe('replace: the text to find, which must occur exactly once'),
|
|
121
|
+
new: z.string().optional().describe('replace: what to put in its place'),
|
|
122
|
+
section: z.array(z.string()).optional().describe('Section operations: the heading path'),
|
|
123
|
+
content: z.string().optional().describe('Section operations: the markdown to put there'),
|
|
124
|
+
});
|
|
113
125
|
const UpdateWorkProgressSchema = z.object({
|
|
114
126
|
work_item_id: refIdString().describe('The ID of the work item'),
|
|
115
|
-
description: z.string().describe('Updated description/notes for the work item'),
|
|
127
|
+
description: z.string().optional().describe('Updated description/notes for the work item'),
|
|
116
128
|
project_slug: z
|
|
117
129
|
.string()
|
|
118
130
|
.optional()
|
|
119
131
|
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
132
|
+
edits: z
|
|
133
|
+
.array(ContentEditSchema)
|
|
134
|
+
.optional()
|
|
135
|
+
.describe('Targeted edits to apply to `description` instead of sending it in full'),
|
|
120
136
|
});
|
|
121
137
|
/** The risk vocabulary, as the backend's `VALID_RISKS` holds it. */
|
|
122
138
|
const VALID_RISKS = ['High', 'Medium', 'Low'];
|
|
@@ -200,6 +216,10 @@ const UpdateWorkItemSchema = z.object({
|
|
|
200
216
|
.string()
|
|
201
217
|
.optional()
|
|
202
218
|
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
219
|
+
edits: z
|
|
220
|
+
.array(ContentEditSchema)
|
|
221
|
+
.optional()
|
|
222
|
+
.describe('Targeted edits to apply to `description` instead of sending it in full'),
|
|
203
223
|
});
|
|
204
224
|
const PullWorkItemSchema = z.object({
|
|
205
225
|
work_item_id: refIdString().describe('The ID of the work item to pull from backlog'),
|
|
@@ -335,6 +355,10 @@ const UpdateRadarItemSchema = z.object({
|
|
|
335
355
|
.string()
|
|
336
356
|
.optional()
|
|
337
357
|
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
358
|
+
edits: z
|
|
359
|
+
.array(ContentEditSchema)
|
|
360
|
+
.optional()
|
|
361
|
+
.describe('Targeted edits to apply to `description` instead of sending it in full'),
|
|
338
362
|
});
|
|
339
363
|
// Issue schemas
|
|
340
364
|
const ListIssuesSchema = z.object({
|
|
@@ -453,6 +477,10 @@ const UpdateIssueSchema = z.object({
|
|
|
453
477
|
.string()
|
|
454
478
|
.optional()
|
|
455
479
|
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
480
|
+
edits: z
|
|
481
|
+
.array(ContentEditSchema)
|
|
482
|
+
.optional()
|
|
483
|
+
.describe('Targeted edits to apply to `description` instead of sending it in full'),
|
|
456
484
|
});
|
|
457
485
|
// Search schema
|
|
458
486
|
const SearchSchema = z.object({
|
|
@@ -487,6 +515,24 @@ const ListWikiPagesSchema = z.object({
|
|
|
487
515
|
});
|
|
488
516
|
const GetWikiPageSchema = z.object({
|
|
489
517
|
page_id: z.string().describe('The page slug'),
|
|
518
|
+
mode: z
|
|
519
|
+
.enum(['full', 'outline'])
|
|
520
|
+
.optional()
|
|
521
|
+
.describe('"outline" returns the heading tree and no content'),
|
|
522
|
+
section: z
|
|
523
|
+
.array(z.string())
|
|
524
|
+
.optional()
|
|
525
|
+
.describe('Read one section by heading path, outermost first'),
|
|
526
|
+
});
|
|
527
|
+
// Timeline schema (#2684). `entity_type` is left a plain string: the backend refuses an
|
|
528
|
+
// unknown one with a message naming the types that have a timeline, which tells the caller
|
|
529
|
+
// more than a parser error would.
|
|
530
|
+
const GetTimelineSchema = z.object({
|
|
531
|
+
entity_type: z.string().describe('The kind of content whose history you want'),
|
|
532
|
+
entity_id: z.string().describe('The page slug, or the refId for everything else'),
|
|
533
|
+
project_slug: z.string().optional().describe('Project slug, needed to resolve a refId'),
|
|
534
|
+
since: z.coerce.string().optional().describe('Only changes at or after this moment (ISO 8601)'),
|
|
535
|
+
limit: z.number().optional().describe('Maximum number of changes to return'),
|
|
490
536
|
});
|
|
491
537
|
const CreateWikiPageSchema = z.object({
|
|
492
538
|
project_slug: z
|
|
@@ -507,7 +553,18 @@ const CreateWikiPageSchema = z.object({
|
|
|
507
553
|
const UpdateWikiPageSchema = z.object({
|
|
508
554
|
page_id: z.string().describe('The page slug'),
|
|
509
555
|
title: z.string().optional().describe('Updated title'),
|
|
510
|
-
content: z.string().optional().describe('Updated content (markdown)'),
|
|
556
|
+
content: z.string().optional().describe('Updated content (markdown) — replaces the whole page'),
|
|
557
|
+
edits: z
|
|
558
|
+
.array(ContentEditSchema)
|
|
559
|
+
.optional()
|
|
560
|
+
.describe('Targeted edits to apply instead of sending the whole page'),
|
|
561
|
+
// Optional here, required by the server for everything but a `replace` edit: a missing
|
|
562
|
+
// version is refused by the backend with a message saying how to get one, which is more use
|
|
563
|
+
// than a parser error (#2681).
|
|
564
|
+
expected_version: z
|
|
565
|
+
.string()
|
|
566
|
+
.optional()
|
|
567
|
+
.describe('The `version` from the read this write is based on'),
|
|
511
568
|
});
|
|
512
569
|
// Journal Entry schemas
|
|
513
570
|
const ListJournalEntriesSchema = z.object({
|
|
@@ -544,6 +601,10 @@ const UpdateJournalEntrySchema = z.object({
|
|
|
544
601
|
.string()
|
|
545
602
|
.optional()
|
|
546
603
|
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
604
|
+
edits: z
|
|
605
|
+
.array(ContentEditSchema)
|
|
606
|
+
.optional()
|
|
607
|
+
.describe('Targeted edits to apply to `content` instead of sending it in full'),
|
|
547
608
|
});
|
|
548
609
|
// Idea schemas (the Idea Factory)
|
|
549
610
|
const PROJECT_SLUG_DESCRIPTION = 'Project slug (required for multi-project users, use list_projects to get slugs)';
|
|
@@ -571,6 +632,10 @@ const UpdateIdeaSchema = z.object({
|
|
|
571
632
|
content: z.string().optional().describe('Updated content (markdown)'),
|
|
572
633
|
stage: z.string().optional().describe('New stage: spark, exploring, building, shipped or parked'),
|
|
573
634
|
project_slug: z.string().optional().describe(PROJECT_SLUG_DESCRIPTION),
|
|
635
|
+
edits: z
|
|
636
|
+
.array(ContentEditSchema)
|
|
637
|
+
.optional()
|
|
638
|
+
.describe('Targeted edits to apply to `content` instead of sending it in full'),
|
|
574
639
|
});
|
|
575
640
|
const BoostIdeaSchema = z.object({
|
|
576
641
|
idea_id: refIdString().describe('The idea refId (number)'),
|
|
@@ -926,6 +991,21 @@ class WolfpackMCPServer {
|
|
|
926
991
|
content: [{ type: 'text', text }],
|
|
927
992
|
};
|
|
928
993
|
}
|
|
994
|
+
// Timeline handler (#2684)
|
|
995
|
+
case 'get_timeline': {
|
|
996
|
+
const parsed = GetTimelineSchema.parse(args);
|
|
997
|
+
const timeline = await this.client.getTimeline(parsed.entity_type, parsed.entity_id, {
|
|
998
|
+
teamSlug: parsed.project_slug || this.client.getProjectSlug() || undefined,
|
|
999
|
+
since: parsed.since,
|
|
1000
|
+
limit: parsed.limit,
|
|
1001
|
+
});
|
|
1002
|
+
const text = timeline.changes.length === 0
|
|
1003
|
+
? `No changes recorded${parsed.since ? ` since ${parsed.since}` : ''} for ${parsed.entity_type} ${parsed.entity_id}.`
|
|
1004
|
+
: JSON.stringify(stripUuids(timeline), null, 2);
|
|
1005
|
+
return {
|
|
1006
|
+
content: [{ type: 'text', text }],
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
929
1009
|
// Work Item handlers
|
|
930
1010
|
case 'list_work_items': {
|
|
931
1011
|
const parsed = ListWorkItemsSchema.parse(args);
|
|
@@ -983,7 +1063,7 @@ class WolfpackMCPServer {
|
|
|
983
1063
|
case 'update_work_progress': {
|
|
984
1064
|
const parsed = UpdateWorkProgressSchema.parse(args);
|
|
985
1065
|
const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
|
|
986
|
-
const workItem = await this.client.updateWorkProgress(parsed.work_item_id, parsed.description, teamSlug);
|
|
1066
|
+
const workItem = await this.client.updateWorkProgress(parsed.work_item_id, { description: parsed.description, edits: parsed.edits }, teamSlug);
|
|
987
1067
|
if (workItem) {
|
|
988
1068
|
// Writing a description is not a claim that work has started: it is also how a
|
|
989
1069
|
// plan is added before pickup and how the record is tidied after handover, so
|
|
@@ -1046,6 +1126,8 @@ class WolfpackMCPServer {
|
|
|
1046
1126
|
fields.tags = parsed.tags;
|
|
1047
1127
|
if (parsed.closing_comment !== undefined)
|
|
1048
1128
|
fields.closingComment = parsed.closing_comment;
|
|
1129
|
+
if (parsed.edits !== undefined)
|
|
1130
|
+
fields.edits = parsed.edits;
|
|
1049
1131
|
const workItem = await this.client.updateWorkItem(parsed.work_item_id, fields, teamSlug);
|
|
1050
1132
|
if (workItem) {
|
|
1051
1133
|
// Build a summary of what changed
|
|
@@ -1300,6 +1382,7 @@ class WolfpackMCPServer {
|
|
|
1300
1382
|
description: parsed.description,
|
|
1301
1383
|
notes: parsed.notes,
|
|
1302
1384
|
stage: parsed.stage,
|
|
1385
|
+
edits: parsed.edits,
|
|
1303
1386
|
}, teamSlug);
|
|
1304
1387
|
return {
|
|
1305
1388
|
content: [
|
|
@@ -1419,6 +1502,7 @@ class WolfpackMCPServer {
|
|
|
1419
1502
|
expectedBehavior: parsed.expected_behavior,
|
|
1420
1503
|
actualBehavior: parsed.actual_behavior,
|
|
1421
1504
|
tags: parsed.tags,
|
|
1505
|
+
edits: parsed.edits,
|
|
1422
1506
|
}, teamSlug);
|
|
1423
1507
|
return {
|
|
1424
1508
|
content: [
|
|
@@ -1450,7 +1534,10 @@ class WolfpackMCPServer {
|
|
|
1450
1534
|
}
|
|
1451
1535
|
case 'get_wiki_page': {
|
|
1452
1536
|
const parsed = GetWikiPageSchema.parse(args);
|
|
1453
|
-
const page = await this.client.getWikiPage(parsed.page_id
|
|
1537
|
+
const page = await this.client.getWikiPage(parsed.page_id, {
|
|
1538
|
+
mode: parsed.mode,
|
|
1539
|
+
section: parsed.section,
|
|
1540
|
+
});
|
|
1454
1541
|
if (page) {
|
|
1455
1542
|
return {
|
|
1456
1543
|
content: [{ type: 'text', text: JSON.stringify(stripUuids(page), null, 2) }],
|
|
@@ -1482,6 +1569,8 @@ class WolfpackMCPServer {
|
|
|
1482
1569
|
const page = await this.client.updateWikiPage(parsed.page_id, {
|
|
1483
1570
|
title: parsed.title,
|
|
1484
1571
|
content: parsed.content,
|
|
1572
|
+
edits: parsed.edits,
|
|
1573
|
+
expectedVersion: parsed.expected_version,
|
|
1485
1574
|
});
|
|
1486
1575
|
return {
|
|
1487
1576
|
content: [
|
|
@@ -1545,6 +1634,7 @@ class WolfpackMCPServer {
|
|
|
1545
1634
|
const entry = await this.client.updateJournalEntry(parsed.entry_id, {
|
|
1546
1635
|
title: parsed.title,
|
|
1547
1636
|
content: parsed.content,
|
|
1637
|
+
edits: parsed.edits,
|
|
1548
1638
|
}, teamSlug);
|
|
1549
1639
|
return {
|
|
1550
1640
|
content: [
|
|
@@ -1598,7 +1688,12 @@ class WolfpackMCPServer {
|
|
|
1598
1688
|
}
|
|
1599
1689
|
case 'update_idea': {
|
|
1600
1690
|
const parsed = UpdateIdeaSchema.parse(args);
|
|
1601
|
-
const idea = await this.client.updateIdea(parsed.idea_id, {
|
|
1691
|
+
const idea = await this.client.updateIdea(parsed.idea_id, {
|
|
1692
|
+
title: parsed.title,
|
|
1693
|
+
content: parsed.content,
|
|
1694
|
+
stage: parsed.stage,
|
|
1695
|
+
edits: parsed.edits,
|
|
1696
|
+
}, parsed.project_slug || this.client.getProjectSlug() || undefined);
|
|
1602
1697
|
return {
|
|
1603
1698
|
content: [
|
|
1604
1699
|
{
|
package/dist/toolCatalogue.js
CHANGED
|
@@ -50,6 +50,80 @@ const CONTENT_LINKING_HELP = 'CROSS-REFERENCES: In any markdown content, you can
|
|
|
50
50
|
'#j123 or #journal-123 for journal entries, #c123 or #case-123 for cases, #p123 or #proc-123 for procedures. ' +
|
|
51
51
|
'Use @username to mention team members (triggers notifications). ' +
|
|
52
52
|
'Standard markdown links also work: [link text](/project/{slug}/work-items/123).';
|
|
53
|
+
/**
|
|
54
|
+
* How to change part of a stored body instead of sending it all back (#2679).
|
|
55
|
+
*
|
|
56
|
+
* Said once because seven update tools carry it, and an agent that learned the contract on one
|
|
57
|
+
* of them has to find the same contract on the next.
|
|
58
|
+
*/
|
|
59
|
+
const EDITS_HELP = 'SURGICAL EDITS: prefer `edits` to sending `content` in full. It costs a fraction of the ' +
|
|
60
|
+
'tokens, and it cannot corrupt the parts of the document you did not mean to touch. ' +
|
|
61
|
+
'Edits apply in order, all or nothing: if any one of them fails, nothing is written. ' +
|
|
62
|
+
'`{"type": "replace", "old": "...", "new": "..."}` swaps one exact run of text, which must ' +
|
|
63
|
+
'occur exactly once — quote enough of the surrounding lines to make it unique. Whitespace and ' +
|
|
64
|
+
'indentation are compared exactly. A failed match tells you how many times it matched and ' +
|
|
65
|
+
'shows the closest lines, so you can correct the quote without re-reading the document.';
|
|
66
|
+
/**
|
|
67
|
+
* The section operations, offered only where the body is markdown and carries a version.
|
|
68
|
+
*
|
|
69
|
+
* `delete_section` removes part of a document; it is not a delete tool in the sense this
|
|
70
|
+
* package's CLAUDE.md forbids, which is about destroying an entity. The same removal has always
|
|
71
|
+
* been expressible by sending a shorter `content`, and nothing here reaches an entity the
|
|
72
|
+
* caller could not already edit.
|
|
73
|
+
*/
|
|
74
|
+
const SECTION_EDITS_HELP = 'Markdown sections can also be addressed by heading path, which is stable where a line number ' +
|
|
75
|
+
'is not: `replace_section`, `append_to_section`, `insert_before_section`, ' +
|
|
76
|
+
'`insert_after_section` and `delete_section` take `section` (e.g. ["Deployment", "Rollback"]) ' +
|
|
77
|
+
'and, except for the delete, `content`. They act on the heading and everything nested under ' +
|
|
78
|
+
'it. Because they quote no text of their own they REQUIRE `expected_version` from the read ' +
|
|
79
|
+
'they are based on. Call get_wiki_page with mode="outline" to see the heading paths.';
|
|
80
|
+
const REPLACE_ONLY_EDIT_TYPES = ['replace'];
|
|
81
|
+
const ALL_EDIT_TYPES = [
|
|
82
|
+
'replace',
|
|
83
|
+
'replace_section',
|
|
84
|
+
'append_to_section',
|
|
85
|
+
'insert_before_section',
|
|
86
|
+
'insert_after_section',
|
|
87
|
+
'delete_section',
|
|
88
|
+
];
|
|
89
|
+
/** The `edits` property, with the section operations only where they are supported. */
|
|
90
|
+
function editsSchema(sections) {
|
|
91
|
+
return {
|
|
92
|
+
type: 'array',
|
|
93
|
+
description: sections
|
|
94
|
+
? 'Targeted edits to apply to the stored content instead of replacing it. Mutually exclusive with `content`.'
|
|
95
|
+
: 'Targeted edits to apply to the stored content instead of replacing it. Mutually exclusive with `content`. This content type takes `replace` only.',
|
|
96
|
+
items: {
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: {
|
|
99
|
+
type: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
enum: sections ? ALL_EDIT_TYPES : REPLACE_ONLY_EDIT_TYPES,
|
|
102
|
+
description: 'Which operation to apply.',
|
|
103
|
+
},
|
|
104
|
+
old: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: '`replace`: the text to find. Must occur exactly once. Whitespace is exact.',
|
|
107
|
+
},
|
|
108
|
+
new: { type: 'string', description: '`replace`: what to put in its place.' },
|
|
109
|
+
...(sections
|
|
110
|
+
? {
|
|
111
|
+
section: {
|
|
112
|
+
type: 'array',
|
|
113
|
+
items: { type: 'string' },
|
|
114
|
+
description: 'Section operations: the heading path, outermost first. The trailing headings alone are enough while they are unambiguous.',
|
|
115
|
+
},
|
|
116
|
+
content: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
description: 'Section operations other than `delete_section`: the markdown to put there.',
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
: {}),
|
|
122
|
+
},
|
|
123
|
+
required: ['type'],
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
53
127
|
/**
|
|
54
128
|
* The risk vocabulary, said the same way on every tool that carries it (#2633).
|
|
55
129
|
*
|
|
@@ -147,6 +221,65 @@ const SHARED_TOOLS = [
|
|
|
147
221
|
required: ['query'],
|
|
148
222
|
},
|
|
149
223
|
},
|
|
224
|
+
// Timeline tool (#2684)
|
|
225
|
+
{
|
|
226
|
+
name: 'get_timeline',
|
|
227
|
+
// Takes the read scope of whatever it is asked about — `mcp:wiki:read` for a page,
|
|
228
|
+
// `mcp:work_items:read` for a work item — and checks it per entity type, so it
|
|
229
|
+
// declares none of its own. Reading a page's history is reading the page.
|
|
230
|
+
permission: null,
|
|
231
|
+
description: 'Read what has changed on one piece of content, newest change first: who changed it, when, ' +
|
|
232
|
+
'and what the change was. Every kind of content with a timeline is read through this one tool. ' +
|
|
233
|
+
'WHAT YOU GET BACK: each change carries a `summary`, and the fields it touched split two ways — ' +
|
|
234
|
+
'`fieldChanges` (short values, as `from` and `to`) and `diffs` (long or multi-line values, as a ' +
|
|
235
|
+
'unified `diff -u` patch of the lines that changed). A change with neither, such as an approval ' +
|
|
236
|
+
'or a comment, is described by its `summary` alone. ' +
|
|
237
|
+
'SINCE IT WAS LAST APPROVED: approvals are changes on the same timeline, so read the timeline, ' +
|
|
238
|
+
'find the newest change whose `category` is "approval", and call again with `since` set to its ' +
|
|
239
|
+
'`timestamp` — what comes back is everything that has happened to the content since it was signed off. ' +
|
|
240
|
+
'PERMISSIONS: this needs the same read permission as reading the content itself (mcp:wiki:read for a ' +
|
|
241
|
+
'page, mcp:work_items:read for a work item, and so on) and nothing further. Each timeline is that ' +
|
|
242
|
+
"one thing's own history: a case's timeline does not include the work items raised from it, so read " +
|
|
243
|
+
'those with entity_type "work_item" and the refId of the item you want.',
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: 'object',
|
|
246
|
+
properties: {
|
|
247
|
+
entity_type: {
|
|
248
|
+
type: 'string',
|
|
249
|
+
enum: [
|
|
250
|
+
'wiki_page',
|
|
251
|
+
'work_item',
|
|
252
|
+
'issue',
|
|
253
|
+
'radar_item',
|
|
254
|
+
'journal_entry',
|
|
255
|
+
'procedure',
|
|
256
|
+
'case',
|
|
257
|
+
],
|
|
258
|
+
description: 'The kind of content whose history you want',
|
|
259
|
+
},
|
|
260
|
+
entity_id: {
|
|
261
|
+
type: 'string',
|
|
262
|
+
description: 'The reference you already hold: the slug for a wiki page, the refId (number) for ' +
|
|
263
|
+
'everything else — a work item\'s "123", an issue\'s "45".',
|
|
264
|
+
},
|
|
265
|
+
project_slug: {
|
|
266
|
+
type: 'string',
|
|
267
|
+
description: 'Project slug; required with a refId when you belong to more than one project ' +
|
|
268
|
+
'(use list_projects to get slugs)',
|
|
269
|
+
},
|
|
270
|
+
since: {
|
|
271
|
+
type: 'string',
|
|
272
|
+
description: 'Only changes at or after this moment (ISO 8601, e.g. "2026-10-07T09:00:00Z"). Pass an ' +
|
|
273
|
+
"approval change's `timestamp` to see what has changed since the content was approved.",
|
|
274
|
+
},
|
|
275
|
+
limit: {
|
|
276
|
+
type: 'number',
|
|
277
|
+
description: 'Maximum number of changes to return; defaults to 20',
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
required: ['entity_type', 'entity_id'],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
150
283
|
// Work Item tools
|
|
151
284
|
{
|
|
152
285
|
name: 'list_work_items',
|
|
@@ -282,7 +415,8 @@ const SHARED_TOOLS = [
|
|
|
282
415
|
'STATUS: This tool never changes status — editing the description is also how a plan is added before pickup and how the record is tidied after handover (#2634). The response names the status it left the item in. Moving an item is always your own call: pull_work_item to start it, update_work_item to set "doing" when you pick it back up, and "review" with a completion comment when it is done. ' +
|
|
283
416
|
'CRITICAL: NEVER overwrite or remove the original description text. Preserve all existing content exactly as-is. Append your plan below a "---" separator. You may only modify content you previously added (your plan section). ' +
|
|
284
417
|
'BEST PRACTICE: Append a plan with markdown checkboxes (- [ ] task). Check off completed tasks (- [x] task) as you progress. Include sections for: Plan (checklist), Approach (strategy), and Notes (discoveries/decisions). ' +
|
|
285
|
-
CONTENT_LINKING_HELP
|
|
418
|
+
CONTENT_LINKING_HELP +
|
|
419
|
+
EDITS_HELP,
|
|
286
420
|
inputSchema: {
|
|
287
421
|
type: 'object',
|
|
288
422
|
properties: {
|
|
@@ -295,8 +429,9 @@ const SHARED_TOOLS = [
|
|
|
295
429
|
type: 'string',
|
|
296
430
|
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
297
431
|
},
|
|
432
|
+
edits: editsSchema(false),
|
|
298
433
|
},
|
|
299
|
-
required: ['work_item_id'
|
|
434
|
+
required: ['work_item_id'],
|
|
300
435
|
},
|
|
301
436
|
},
|
|
302
437
|
{
|
|
@@ -315,7 +450,8 @@ const SHARED_TOOLS = [
|
|
|
315
450
|
'what makes the board show who is working on what. ' +
|
|
316
451
|
'When moving to "review", add a completion comment via create_work_item_comment. When moving to "blocked", add a comment explaining the blocker. ' +
|
|
317
452
|
'When you take an item back out of "review" to rework it, set it to "doing" first — an item being changed is not an item waiting to be reviewed. ' +
|
|
318
|
-
'closing_comment is only accepted together with status "closed" — never use it for progress or completion notes.'
|
|
453
|
+
'closing_comment is only accepted together with status "closed" — never use it for progress or completion notes.' +
|
|
454
|
+
EDITS_HELP,
|
|
319
455
|
inputSchema: {
|
|
320
456
|
type: 'object',
|
|
321
457
|
properties: {
|
|
@@ -404,6 +540,7 @@ const SHARED_TOOLS = [
|
|
|
404
540
|
type: 'string',
|
|
405
541
|
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
406
542
|
},
|
|
543
|
+
edits: editsSchema(false),
|
|
407
544
|
},
|
|
408
545
|
required: ['work_item_id'],
|
|
409
546
|
},
|
|
@@ -720,7 +857,8 @@ const SHARED_TOOLS = [
|
|
|
720
857
|
name: 'update_radar_item',
|
|
721
858
|
permission: 'mcp:radar:update',
|
|
722
859
|
description: 'Update an existing radar/initiative item. Requires mcp:radar:update permission. ' +
|
|
723
|
-
CONTENT_LINKING_HELP
|
|
860
|
+
CONTENT_LINKING_HELP +
|
|
861
|
+
EDITS_HELP,
|
|
724
862
|
inputSchema: {
|
|
725
863
|
type: 'object',
|
|
726
864
|
properties: {
|
|
@@ -743,6 +881,7 @@ const SHARED_TOOLS = [
|
|
|
743
881
|
type: 'string',
|
|
744
882
|
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
745
883
|
},
|
|
884
|
+
edits: editsSchema(false),
|
|
746
885
|
},
|
|
747
886
|
required: ['item_id'],
|
|
748
887
|
},
|
|
@@ -951,7 +1090,8 @@ const SHARED_TOOLS = [
|
|
|
951
1090
|
'empty array clears it. Tagging an issue that is not assigned to you needs ' +
|
|
952
1091
|
'mcp:issues:tag_all, and that scope allows a tags-only update — send any other ' +
|
|
953
1092
|
'field alongside and the usual assignment rule applies. ' +
|
|
954
|
-
CONTENT_LINKING_HELP
|
|
1093
|
+
CONTENT_LINKING_HELP +
|
|
1094
|
+
EDITS_HELP,
|
|
955
1095
|
inputSchema: {
|
|
956
1096
|
type: 'object',
|
|
957
1097
|
properties: {
|
|
@@ -999,6 +1139,7 @@ const SHARED_TOOLS = [
|
|
|
999
1139
|
type: 'string',
|
|
1000
1140
|
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
1001
1141
|
},
|
|
1142
|
+
edits: editsSchema(false),
|
|
1002
1143
|
},
|
|
1003
1144
|
required: ['issue_id'],
|
|
1004
1145
|
},
|
|
@@ -1036,6 +1177,12 @@ const SHARED_TOOLS = [
|
|
|
1036
1177
|
name: 'get_wiki_page',
|
|
1037
1178
|
permission: 'mcp:wiki:read',
|
|
1038
1179
|
description: 'Get a specific wiki/documentation page by slug (URL path). Returns full content in markdown. ' +
|
|
1180
|
+
'LONG PAGES: pass mode="outline" first to see the heading tree and the size of each section, ' +
|
|
1181
|
+
'then section=["Heading"] to read just the one you need — far cheaper than reading the page. ' +
|
|
1182
|
+
'VERSION: every response carries a `version`. Pass it to update_wiki_page as expected_version when you save, ' +
|
|
1183
|
+
'and read the page again — taking a fresh version — if the save is refused because somebody changed it. ' +
|
|
1184
|
+
'APPROVALS: `approvals` lists who has signed the page off as it currently reads, with the date each ' +
|
|
1185
|
+
'approved. It is empty if nobody has, and an approval the page has been edited past is not listed. ' +
|
|
1039
1186
|
'IMAGES: Image references in the content (e.g. ``) can be viewed using the download_image tool.',
|
|
1040
1187
|
inputSchema: {
|
|
1041
1188
|
type: 'object',
|
|
@@ -1044,6 +1191,20 @@ const SHARED_TOOLS = [
|
|
|
1044
1191
|
type: 'string',
|
|
1045
1192
|
description: 'The page slug (URL path like "getting-started")',
|
|
1046
1193
|
},
|
|
1194
|
+
mode: {
|
|
1195
|
+
type: 'string',
|
|
1196
|
+
enum: ['full', 'outline'],
|
|
1197
|
+
description: '"full" (default) returns the page content. "outline" returns the heading tree — ' +
|
|
1198
|
+
'heading path, level, line range and size — and no content.',
|
|
1199
|
+
},
|
|
1200
|
+
section: {
|
|
1201
|
+
type: 'array',
|
|
1202
|
+
items: { type: 'string' },
|
|
1203
|
+
description: 'Read one section instead of the page. A heading path, outermost first, e.g. ' +
|
|
1204
|
+
'["Deployment", "Rollback"]. The trailing headings alone are enough while they are ' +
|
|
1205
|
+
'unambiguous, so ["Rollback"] usually works. Returns the heading and everything ' +
|
|
1206
|
+
'nested under it.',
|
|
1207
|
+
},
|
|
1047
1208
|
},
|
|
1048
1209
|
required: ['page_id'],
|
|
1049
1210
|
},
|
|
@@ -1076,7 +1237,17 @@ const SHARED_TOOLS = [
|
|
|
1076
1237
|
{
|
|
1077
1238
|
name: 'update_wiki_page',
|
|
1078
1239
|
permission: 'mcp:wiki:update',
|
|
1079
|
-
description: 'Update an existing wiki page. Requires mcp:wiki:update permission. ' +
|
|
1240
|
+
description: 'Update an existing wiki page. Requires mcp:wiki:update permission. ' +
|
|
1241
|
+
EDITS_HELP +
|
|
1242
|
+
' ' +
|
|
1243
|
+
SECTION_EDITS_HELP +
|
|
1244
|
+
' READ BEFORE YOU WRITE: wiki pages are edited by people at the same time as you. A `content` write ' +
|
|
1245
|
+
'replaces the whole document, so a save made from a copy you read earlier would throw away everything ' +
|
|
1246
|
+
'written since — call get_wiki_page first and pass the `version` it returned as expected_version, ' +
|
|
1247
|
+
'which is required for `content` and for a section operation. ' +
|
|
1248
|
+
'If the save is refused because the page changed, do NOT send the same content again: ' +
|
|
1249
|
+
'call get_wiki_page, re-apply your change to what the page says now, and save with the new version. ' +
|
|
1250
|
+
CONTENT_LINKING_HELP,
|
|
1080
1251
|
// No tag_ids and no project_slug: `PATCH /api/mcp/wiki-pages/{pageId}` has
|
|
1081
1252
|
// neither. The body model is `additionalProperties: false`, so a call
|
|
1082
1253
|
// carrying tags was refused outright rather than ignored, and the page
|
|
@@ -1090,9 +1261,22 @@ const SHARED_TOOLS = [
|
|
|
1090
1261
|
title: { type: 'string', description: 'Updated title' },
|
|
1091
1262
|
content: {
|
|
1092
1263
|
type: 'string',
|
|
1093
|
-
description: 'Updated content (markdown)',
|
|
1264
|
+
description: 'Updated content (markdown). Replaces the whole page — prefer `edits` for anything smaller.',
|
|
1265
|
+
},
|
|
1266
|
+
edits: editsSchema(true),
|
|
1267
|
+
expected_version: {
|
|
1268
|
+
type: 'string',
|
|
1269
|
+
description: 'The `version` from the get_wiki_page read this write is based on. The save is ' +
|
|
1270
|
+
'refused with a conflict if the page changed since, naming who changed it — re-read ' +
|
|
1271
|
+
'and re-apply, so that their edit is not thrown away. Required for `content`, and ' +
|
|
1272
|
+
'for `edits` containing a section operation; a `replace` edit quotes the exact text ' +
|
|
1273
|
+
'it rewrites, which is a stronger precondition over the only region it touches.',
|
|
1094
1274
|
},
|
|
1095
1275
|
},
|
|
1276
|
+
// `expected_version` is required for everything except a `replace` edit, which is the one
|
|
1277
|
+
// request that quotes its own precondition — so the rule is stated on the field and
|
|
1278
|
+
// enforced by the route (#2679 / #2681) rather than written as a schema the caller has to
|
|
1279
|
+
// satisfy before the refusal can explain itself.
|
|
1096
1280
|
required: ['page_id'],
|
|
1097
1281
|
},
|
|
1098
1282
|
},
|
|
@@ -1167,7 +1351,8 @@ const SHARED_TOOLS = [
|
|
|
1167
1351
|
name: 'update_journal_entry',
|
|
1168
1352
|
permission: 'mcp:journal:update',
|
|
1169
1353
|
description: 'Update an existing journal entry. Requires mcp:journal:update permission. ' +
|
|
1170
|
-
CONTENT_LINKING_HELP
|
|
1354
|
+
CONTENT_LINKING_HELP +
|
|
1355
|
+
EDITS_HELP,
|
|
1171
1356
|
inputSchema: {
|
|
1172
1357
|
type: 'object',
|
|
1173
1358
|
properties: {
|
|
@@ -1184,6 +1369,7 @@ const SHARED_TOOLS = [
|
|
|
1184
1369
|
type: 'string',
|
|
1185
1370
|
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
1186
1371
|
},
|
|
1372
|
+
edits: editsSchema(false),
|
|
1187
1373
|
},
|
|
1188
1374
|
required: ['entry_id'],
|
|
1189
1375
|
},
|
|
@@ -1266,7 +1452,8 @@ const SHARED_TOOLS = [
|
|
|
1266
1452
|
description: 'Update an idea, or move it to another stage. The stages are "spark" (just raised), ' +
|
|
1267
1453
|
'"exploring", "building", "shipped" and "parked" — moving an idea along is how the project ' +
|
|
1268
1454
|
'sees what became of it. Requires mcp:ideas:update permission. ' +
|
|
1269
|
-
CONTENT_LINKING_HELP
|
|
1455
|
+
CONTENT_LINKING_HELP +
|
|
1456
|
+
EDITS_HELP,
|
|
1270
1457
|
inputSchema: {
|
|
1271
1458
|
type: 'object',
|
|
1272
1459
|
properties: {
|
|
@@ -1281,6 +1468,7 @@ const SHARED_TOOLS = [
|
|
|
1281
1468
|
type: 'string',
|
|
1282
1469
|
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
1283
1470
|
},
|
|
1471
|
+
edits: editsSchema(false),
|
|
1284
1472
|
},
|
|
1285
1473
|
required: ['idea_id'],
|
|
1286
1474
|
},
|