@adrata/adrata-mcp 1.0.42 → 1.0.43
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/access/tiers.js +7 -0
- package/api-bridge.js +3 -0
- package/package.json +2 -2
- package/server.js +13 -0
- package/server.json +2 -2
- package/tool-annotations.js +9 -0
- package/tools/launches/work-launch-tools.js +286 -0
package/access/tiers.js
CHANGED
|
@@ -247,6 +247,13 @@ export const TOOL_TIERS = {
|
|
|
247
247
|
get_work_board_rollup: TIERS.ENTERPRISE,
|
|
248
248
|
list_work_board_rollups: TIERS.ENTERPRISE,
|
|
249
249
|
set_work_board_archived: TIERS.ENTERPRISE,
|
|
250
|
+
// Launches: a lens over the boards (tools/work-launch-tools.js).
|
|
251
|
+
list_work_launches: TIERS.ENTERPRISE,
|
|
252
|
+
get_work_launch: TIERS.ENTERPRISE,
|
|
253
|
+
create_work_launch: TIERS.ENTERPRISE,
|
|
254
|
+
update_work_launch: TIERS.ENTERPRISE,
|
|
255
|
+
add_work_item_to_launch: TIERS.ENTERPRISE,
|
|
256
|
+
remove_work_item_from_launch: TIERS.ENTERPRISE,
|
|
250
257
|
set_work_board_column_wip_limit: TIERS.ENTERPRISE,
|
|
251
258
|
attach_work_item_qa_evidence: TIERS.ENTERPRISE,
|
|
252
259
|
verify_work_item_qa_evidence_playback: TIERS.ENTERPRISE,
|
package/api-bridge.js
CHANGED
|
@@ -211,6 +211,8 @@ const ALLOWED_PREFIXES = [
|
|
|
211
211
|
'/api/v1/work-board-rollups',
|
|
212
212
|
'/api/v1/work-boards',
|
|
213
213
|
'/api/v1/work-items',
|
|
214
|
+
// A launch is a lens over the boards; its writes are board writes.
|
|
215
|
+
'/api/v1/work-launches',
|
|
214
216
|
// The containers above the cards (initiative / epic / feature) and the
|
|
215
217
|
// "add this to the roadmap" verb. Creating an epic proposal is a governed
|
|
216
218
|
// write like any other; the strategic commitment itself still moves through
|
|
@@ -343,6 +345,7 @@ const PATH_WRITE_SCOPES = [
|
|
|
343
345
|
['/api/v1/work-boards', 'write:tasks'],
|
|
344
346
|
['/api/v1/work-items', 'write:tasks'],
|
|
345
347
|
['/api/v1/work-board-rollups', 'write:tasks'],
|
|
348
|
+
['/api/v1/work-launches', 'write:tasks'],
|
|
346
349
|
];
|
|
347
350
|
|
|
348
351
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adrata/adrata-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.43",
|
|
4
4
|
"description": "Adrata MCP Server \u2014 connect Claude Code, Codex, Gemini, Cursor, and other AI tools to your CRM. About 275 tools registered at startup for companies, people, deals, actions, buyer groups, warm intros, webhooks and intelligence, plus 65 more behind eight named toolsets you load with enable_toolset.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node server.js",
|
|
12
|
-
"test": "node --test analytics.test.js server.test.js api-bridge.test.js http/edge-block.test.js http/rate-limit.test.js audit-flush.test.js buyer-group-writes.test.js note-writes.test.js mcp-spec.test.js packaging.test.js product-profile.test.js security.test.js security.cap-contract.test.js tool-annotations.test.js toolsets.test.js toolsets/communications.test.js toolsets/prospecting.test.js access/auth.test.js access/oauth-callback.test.js access/oauth-session.test.js access/oauth-capabilities.test.js access/oauth-scope-grantability.test.js scripts/local-dev-server.test.js tools/competitive-coverage.test.js tools/describe-count.test.js tools/email-tools.test.js tools/scheduling.test.js tools/work-board-tools.test.js tools/work-hub/audit.test.js tools/work-hub/field-changes.test.js tools/work-hub/criteria-quality.test.js tools/roadmap-tools.test.js tools/provisioning/onboarding-tools.test.js tools/source-control/connection-tools.test.js governance/money.test.js http/rate-limit-policy.test.js"
|
|
12
|
+
"test": "node --test analytics.test.js server.test.js api-bridge.test.js http/edge-block.test.js http/rate-limit.test.js audit-flush.test.js buyer-group-writes.test.js note-writes.test.js mcp-spec.test.js packaging.test.js product-profile.test.js security.test.js security.cap-contract.test.js tool-annotations.test.js toolsets.test.js toolsets/communications.test.js toolsets/prospecting.test.js access/auth.test.js access/oauth-callback.test.js access/oauth-session.test.js access/oauth-capabilities.test.js access/oauth-scope-grantability.test.js scripts/local-dev-server.test.js tools/competitive-coverage.test.js tools/describe-count.test.js tools/email-tools.test.js tools/scheduling.test.js tools/work-board-tools.test.js tools/launches/work-launch-tools.test.js tools/work-hub/audit.test.js tools/work-hub/field-changes.test.js tools/work-hub/criteria-quality.test.js tools/roadmap-tools.test.js tools/provisioning/onboarding-tools.test.js tools/source-control/connection-tools.test.js governance/money.test.js http/rate-limit-policy.test.js"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"mcp",
|
package/server.js
CHANGED
|
@@ -53,6 +53,7 @@ import { connectWorkspace, disconnectWorkspace, getConnectionStatus, getValidTok
|
|
|
53
53
|
import { registerEnterpriseTools } from './tools/enterprise-tools.js';
|
|
54
54
|
import { registerEmailTools } from './tools/email-tools.js';
|
|
55
55
|
import { registerWorkBoardTools } from './tools/work-board-tools.js';
|
|
56
|
+
import { registerWorkLaunchTools } from './tools/launches/work-launch-tools.js';
|
|
56
57
|
import { registerSourceControlTools } from './tools/source-control/connection-tools.js';
|
|
57
58
|
import { registerRoadmapTools } from './tools/roadmap-tools.js';
|
|
58
59
|
import { registerProvisioningTools } from './tools/provisioning/onboarding-tools.js';
|
|
@@ -2395,6 +2396,18 @@ registerWorkBoardTools(server, {
|
|
|
2395
2396
|
getGrantedScope: () => loadTokens()?.scope,
|
|
2396
2397
|
});
|
|
2397
2398
|
|
|
2399
|
+
// ===== LAUNCH TOOLS =====
|
|
2400
|
+
// A launch is a lens over the boards — a named, dated set a card can be on
|
|
2401
|
+
// without leaving its board. Same governed contract; see work-launch-tools.js.
|
|
2402
|
+
registerWorkLaunchTools(server, {
|
|
2403
|
+
z,
|
|
2404
|
+
api,
|
|
2405
|
+
ok,
|
|
2406
|
+
validateApiBridgeRequest,
|
|
2407
|
+
buildMutationHeaders,
|
|
2408
|
+
getGrantedScope: () => loadTokens()?.scope,
|
|
2409
|
+
});
|
|
2410
|
+
|
|
2398
2411
|
registerSourceControlTools(server, {
|
|
2399
2412
|
z,
|
|
2400
2413
|
api,
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "com.adrata/adrata-mcp",
|
|
4
4
|
"description": "Adrata revenue-intelligence MCP server: companies, people, opportunities, actions, buyer groups, enrichment, email, and workspace operations for AI agents.",
|
|
5
5
|
"status": "active",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.43",
|
|
7
7
|
"websiteUrl": "https://adrata.com/developers",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/adrata/adrata",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"registryType": "npm",
|
|
16
16
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
17
17
|
"identifier": "@adrata/adrata-mcp",
|
|
18
|
-
"version": "1.0.
|
|
18
|
+
"version": "1.0.43",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|
|
21
21
|
},
|
package/tool-annotations.js
CHANGED
|
@@ -123,6 +123,12 @@ const DOMAIN_OVERRIDES = {
|
|
|
123
123
|
add_to_roadmap: 'board',
|
|
124
124
|
audit_work_hub: 'board',
|
|
125
125
|
set_work_board_archived: 'board',
|
|
126
|
+
list_work_launches: 'board',
|
|
127
|
+
get_work_launch: 'board',
|
|
128
|
+
create_work_launch: 'board',
|
|
129
|
+
update_work_launch: 'board',
|
|
130
|
+
add_work_item_to_launch: 'board',
|
|
131
|
+
remove_work_item_from_launch: 'board',
|
|
126
132
|
list_forms: 'forms',
|
|
127
133
|
get_form: 'forms',
|
|
128
134
|
create_form: 'forms',
|
|
@@ -253,6 +259,9 @@ const IDEMPOTENT_WRITES = new Set([
|
|
|
253
259
|
// overwritten — which makes a retry with the same key land on the same state.
|
|
254
260
|
// The restore is a plain clear of the same five columns.
|
|
255
261
|
'archive_work_item', 'unarchive_work_item',
|
|
262
|
+
// A launch membership is an upsert keyed on (launch, card): replaying the
|
|
263
|
+
// same add re-ranks the same row; a repeat remove deletes a row already gone.
|
|
264
|
+
'create_work_launch', 'update_work_launch', 'add_work_item_to_launch', 'remove_work_item_from_launch',
|
|
256
265
|
// A repeat block is the same edge (the pair is unique), and a repeat
|
|
257
266
|
// unblock deletes a row that is already gone. Neither compounds.
|
|
258
267
|
'block_work_item', 'unblock_work_item',
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch tools for the Adrata MCP server — a named, dated set of cards that
|
|
3
|
+
* must ship together, read as a LENS over the boards.
|
|
4
|
+
*
|
|
5
|
+
* The design: `spoq/epics/backlog/launches-are-lenses/EPIC.md`. The short form
|
|
6
|
+
* an agent needs before touching these:
|
|
7
|
+
*
|
|
8
|
+
* - A launch is NOT a board. A card stays on its own board, in its own
|
|
9
|
+
* column, under its own epic; membership here changes none of that and
|
|
10
|
+
* none of the board's instruments. Do not transfer a card to "put it on a
|
|
11
|
+
* launch" — that is the model these tools exist to replace.
|
|
12
|
+
* - A card can be on MANY launches. Priority (p0 | p1 | p2) is per launch and
|
|
13
|
+
* lives on the membership, because the card's one tag column is spoken
|
|
14
|
+
* for (on the Adrata board it is `severity`, and a P0 written there would
|
|
15
|
+
* overwrite `critical`).
|
|
16
|
+
* - Membership is a CLAIM with provenance — who decided, how sure, and why —
|
|
17
|
+
* on the same terms as `set_work_item_tag`. An agent that decides a card
|
|
18
|
+
* belongs on a launch is `model` and must give a confidence.
|
|
19
|
+
* - Readiness is COUNTS, derived on read, never a percentage and never
|
|
20
|
+
* stored (hierarchy Decision 4).
|
|
21
|
+
*
|
|
22
|
+
* "Get all the P0s done" is `get_work_launch` with `priority: "p0"`: the cards
|
|
23
|
+
* come back ranked and each one says why it is there.
|
|
24
|
+
*
|
|
25
|
+
* Writes follow the same governed contract as every write on this server:
|
|
26
|
+
* dryRun defaults to TRUE; a live write needs dryRun:false AND approved:true
|
|
27
|
+
* AND a reason AND an idempotencyKey.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const GOVERNED_NOTE =
|
|
31
|
+
' Governed write: previews by default. A live write requires dryRun:false plus approved:true, a reason, and an idempotencyKey (reuse the SAME key on retry — adding a card that is already on the launch re-ranks it rather than duplicating it).';
|
|
32
|
+
|
|
33
|
+
const PRIORITIES = ['p0', 'p1', 'p2'];
|
|
34
|
+
const EFFORTS = ['quick', 'standard', 'deep'];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Narrow a launch's cards by priority. Pure and exported so the filter rule —
|
|
38
|
+
* the one thing a "give me the P0s" call can silently get wrong — is testable
|
|
39
|
+
* without a server.
|
|
40
|
+
*
|
|
41
|
+
* `unranked` selects cards on the launch with NO priority, which is a real
|
|
42
|
+
* state ("on the launch, not yet ranked") and must be reachable, on the same
|
|
43
|
+
* reasoning the scope control keeps `Unassigned` and `Untyped` selectable.
|
|
44
|
+
*/
|
|
45
|
+
export function filterLaunchCards(cards, priority, effort) {
|
|
46
|
+
let list = Array.isArray(cards) ? cards : [];
|
|
47
|
+
if (priority === 'unranked') list = list.filter((card) => !card?.launchPriority);
|
|
48
|
+
else if (priority) list = list.filter((card) => card?.launchPriority === priority);
|
|
49
|
+
if (effort === 'unjudged') list = list.filter((card) => !card?.launchEffort);
|
|
50
|
+
else if (effort) list = list.filter((card) => card?.launchEffort === effort);
|
|
51
|
+
return list;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Register the launch tools.
|
|
56
|
+
*
|
|
57
|
+
* @param {McpServer} server
|
|
58
|
+
* @param {object} deps - { z, api, ok, validateApiBridgeRequest, buildMutationHeaders, getGrantedScope }
|
|
59
|
+
*/
|
|
60
|
+
export function registerWorkLaunchTools(
|
|
61
|
+
server,
|
|
62
|
+
{ z, api, ok, validateApiBridgeRequest, buildMutationHeaders, getGrantedScope = () => undefined }
|
|
63
|
+
) {
|
|
64
|
+
|
|
65
|
+
server.tool(
|
|
66
|
+
'list_work_launches',
|
|
67
|
+
'List the launches in the workspace — each a named, dated set of cards that must ship together, with its readiness as COUNTS (cards, p0/p1/p2/unranked, and how many sit in each stage). A launch is a lens over the boards, not a board: its cards stay on their own boards. Start here; get_work_launch needs an id from this list.',
|
|
68
|
+
{},
|
|
69
|
+
async () => {
|
|
70
|
+
const data = await api('GET', '/api/v1/work-launches');
|
|
71
|
+
const launches = data?.data || [];
|
|
72
|
+
return ok({ count: launches.length, launches });
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
server.tool(
|
|
77
|
+
'get_work_launch',
|
|
78
|
+
'Read one launch whole: its readiness counts and every member card the caller can see, p0 first. Each card carries its home board and column, its own tag, the ids it is blockedBy, and its membership claim — launchPriority, launchEffort (quick | standard | deep: a routing signal, not an estimate), launchReason, launchSource, launchConfidence. Pass priority to narrow: "p0" is the answer to "get all the P0s done"; "unranked" finds cards nobody has ranked. Pass effort to pick the quick wins for a fast lane, or the deep ones for a heavier model and a higher effort tier.',
|
|
79
|
+
{
|
|
80
|
+
launchId: z.string().describe('Launch id from list_work_launches.'),
|
|
81
|
+
priority: z
|
|
82
|
+
.enum([...PRIORITIES, 'unranked'])
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('Narrow the cards to one priority band, or to the unranked ones. Omit for all.'),
|
|
85
|
+
effort: z
|
|
86
|
+
.enum([...EFFORTS, 'unjudged'])
|
|
87
|
+
.optional()
|
|
88
|
+
.describe('Narrow to quick wins, standard work, deep exploration, or the cards nobody has judged. Omit for all.'),
|
|
89
|
+
},
|
|
90
|
+
async (args) => {
|
|
91
|
+
const data = await api('GET', `/api/v1/work-launches/${encodeURIComponent(args.launchId)}`);
|
|
92
|
+
const launch = data?.data;
|
|
93
|
+
if (!launch) return ok({ launch: null });
|
|
94
|
+
const { cards: all = [], ...rest } = launch;
|
|
95
|
+
const cards = filterLaunchCards(all, args.priority, args.effort);
|
|
96
|
+
return ok({
|
|
97
|
+
launch: rest,
|
|
98
|
+
filter: args.priority ?? null,
|
|
99
|
+
effort: args.effort ?? null,
|
|
100
|
+
count: cards.length,
|
|
101
|
+
of: all.length,
|
|
102
|
+
cards,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
server.tool(
|
|
108
|
+
'create_work_launch',
|
|
109
|
+
`Create a launch: a named, dated set of cards that must ship together. It owns no columns and no cards of its own — add cards with add_work_item_to_launch and they stay exactly where they are on their boards. Check list_work_launches first; two launches with one intent is a list nobody trusts.${GOVERNED_NOTE}`,
|
|
110
|
+
{
|
|
111
|
+
name: z.string().describe('e.g. "Adrata September Launch". Named for the outcome, not the month alone.'),
|
|
112
|
+
description: z
|
|
113
|
+
.string()
|
|
114
|
+
.optional()
|
|
115
|
+
.describe('Who it is for and what "live" means — the sentence a reader needs to judge whether a card belongs.'),
|
|
116
|
+
targetOn: z.string().optional().describe('The day it must be live, YYYY-MM-DD. A date, never an hour.'),
|
|
117
|
+
dryRun: z.boolean().optional().describe('Defaults to true. Set false for a live write.'),
|
|
118
|
+
approved: z.boolean().optional().describe('Required true for a live write.'),
|
|
119
|
+
reason: z.string().optional().describe('Required for a live write: the audit reason.'),
|
|
120
|
+
idempotencyKey: z.string().optional().describe('Required for a live write. Reuse on retry.'),
|
|
121
|
+
},
|
|
122
|
+
async (args) => {
|
|
123
|
+
const path = '/api/v1/work-launches';
|
|
124
|
+
const preview = validateApiBridgeRequest({
|
|
125
|
+
method: 'POST',
|
|
126
|
+
path,
|
|
127
|
+
dryRun: args.dryRun,
|
|
128
|
+
approved: args.approved,
|
|
129
|
+
reason: args.reason,
|
|
130
|
+
idempotencyKey: args.idempotencyKey,
|
|
131
|
+
grantedScope: getGrantedScope(),
|
|
132
|
+
});
|
|
133
|
+
if (preview?.dryRun) {
|
|
134
|
+
return ok({
|
|
135
|
+
...preview,
|
|
136
|
+
wouldCreate: { name: args.name, description: args.description, targetOn: args.targetOn },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const data = await api('POST', path, {
|
|
140
|
+
body: { name: args.name, description: args.description, targetOn: args.targetOn },
|
|
141
|
+
headers: buildMutationHeaders(args),
|
|
142
|
+
});
|
|
143
|
+
return ok({ created: true, launch: data?.data });
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
server.tool(
|
|
148
|
+
'update_work_launch',
|
|
149
|
+
`Rename a launch, move its target day, set its lifecycle (open | shipped | abandoned), or archive it. Status is the LIFECYCLE, not progress — progress is derived from the member cards and cannot be set. Archiving hides the launch from lists and from every card's launch chips; nothing is deleted.${GOVERNED_NOTE}`,
|
|
150
|
+
{
|
|
151
|
+
launchId: z.string().describe('Launch id from list_work_launches.'),
|
|
152
|
+
name: z.string().optional(),
|
|
153
|
+
description: z.string().optional(),
|
|
154
|
+
targetOn: z.string().optional().describe('YYYY-MM-DD, or "" to clear the date.'),
|
|
155
|
+
status: z.enum(['open', 'shipped', 'abandoned']).optional(),
|
|
156
|
+
archived: z.boolean().optional(),
|
|
157
|
+
dryRun: z.boolean().optional().describe('Defaults to true. Set false for a live write.'),
|
|
158
|
+
approved: z.boolean().optional().describe('Required true for a live write.'),
|
|
159
|
+
reason: z.string().optional().describe('Required for a live write: the audit reason.'),
|
|
160
|
+
idempotencyKey: z.string().optional().describe('Required for a live write. Reuse on retry.'),
|
|
161
|
+
},
|
|
162
|
+
async (args) => {
|
|
163
|
+
const path = `/api/v1/work-launches/${encodeURIComponent(args.launchId)}`;
|
|
164
|
+
const preview = validateApiBridgeRequest({
|
|
165
|
+
method: 'PATCH',
|
|
166
|
+
path,
|
|
167
|
+
dryRun: args.dryRun,
|
|
168
|
+
approved: args.approved,
|
|
169
|
+
reason: args.reason,
|
|
170
|
+
idempotencyKey: args.idempotencyKey,
|
|
171
|
+
grantedScope: getGrantedScope(),
|
|
172
|
+
});
|
|
173
|
+
const body = {
|
|
174
|
+
name: args.name,
|
|
175
|
+
description: args.description,
|
|
176
|
+
targetOn: args.targetOn,
|
|
177
|
+
status: args.status,
|
|
178
|
+
isArchived: args.archived,
|
|
179
|
+
};
|
|
180
|
+
if (preview?.dryRun) return ok({ ...preview, wouldUpdate: body });
|
|
181
|
+
const data = await api('PATCH', path, { body, headers: buildMutationHeaders(args) });
|
|
182
|
+
return ok({ updated: true, launch: data?.data });
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
server.tool(
|
|
187
|
+
'add_work_item_to_launch',
|
|
188
|
+
`Put a card on a launch, with a priority and the reason it belongs there. The card does NOT move: it keeps its board, column, tag and epic — a launch is a lens. Adding a card that is already on the launch re-ranks it and replaces its reason and provenance. source is who decided: an agent classifying a card is "model" and MUST give a confidence; claiming "human" would launder a guess as a person's judgement.${GOVERNED_NOTE}`,
|
|
189
|
+
{
|
|
190
|
+
launchId: z.string().describe('Launch id from list_work_launches.'),
|
|
191
|
+
itemId: z.string().describe('Card id.'),
|
|
192
|
+
priority: z
|
|
193
|
+
.enum(PRIORITIES)
|
|
194
|
+
.optional()
|
|
195
|
+
.describe(
|
|
196
|
+
'p0 = cannot go live without it, or the first hour lies; p1 = the first week is not indispensable without it; p2 = launch quality that can trail by a week or two. Omit to add unranked.'
|
|
197
|
+
),
|
|
198
|
+
effort: z
|
|
199
|
+
.enum(EFFORTS)
|
|
200
|
+
.optional()
|
|
201
|
+
.describe(
|
|
202
|
+
'quick = a known fix in a known place, hours not days; standard = ordinary card work; deep = needs exploration or a design (a heavier model / higher effort tier). A routing signal, never an estimate. Omit to leave unjudged.'
|
|
203
|
+
),
|
|
204
|
+
membershipReason: z
|
|
205
|
+
.string()
|
|
206
|
+
.describe('Why this card is on the launch, in a sentence. Shown on the launch beside the card.'),
|
|
207
|
+
source: z.enum(['human', 'model', 'rules']).describe('Who decided membership.'),
|
|
208
|
+
confidence: z.number().min(0).max(1).optional().describe('0-1. REQUIRED when source is "model".'),
|
|
209
|
+
dryRun: z.boolean().optional().describe('Defaults to true. Set false for a live write.'),
|
|
210
|
+
approved: z.boolean().optional().describe('Required true for a live write.'),
|
|
211
|
+
reason: z.string().optional().describe('Required for a live write: the audit reason.'),
|
|
212
|
+
idempotencyKey: z.string().optional().describe('Required for a live write. Reuse on retry.'),
|
|
213
|
+
},
|
|
214
|
+
async (args) => {
|
|
215
|
+
const path = `/api/v1/work-launches/${encodeURIComponent(args.launchId)}/items`;
|
|
216
|
+
const preview = validateApiBridgeRequest({
|
|
217
|
+
method: 'POST',
|
|
218
|
+
path,
|
|
219
|
+
dryRun: args.dryRun,
|
|
220
|
+
approved: args.approved,
|
|
221
|
+
reason: args.reason,
|
|
222
|
+
idempotencyKey: args.idempotencyKey,
|
|
223
|
+
grantedScope: getGrantedScope(),
|
|
224
|
+
});
|
|
225
|
+
const body = {
|
|
226
|
+
itemId: args.itemId,
|
|
227
|
+
priority: args.priority,
|
|
228
|
+
effort: args.effort,
|
|
229
|
+
reason: args.membershipReason,
|
|
230
|
+
source: args.source,
|
|
231
|
+
confidence: args.confidence,
|
|
232
|
+
};
|
|
233
|
+
if (preview?.dryRun) return ok({ ...preview, wouldAdd: body });
|
|
234
|
+
const data = await api('POST', path, { body, headers: buildMutationHeaders(args) });
|
|
235
|
+
const launch = data?.data;
|
|
236
|
+
return ok({
|
|
237
|
+
added: true,
|
|
238
|
+
launchId: args.launchId,
|
|
239
|
+
itemId: args.itemId,
|
|
240
|
+
priority: args.priority ?? null,
|
|
241
|
+
effort: args.effort ?? null,
|
|
242
|
+
readiness: launch?.readiness,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
server.tool(
|
|
248
|
+
'remove_work_item_from_launch',
|
|
249
|
+
`Take a card off a launch. The card itself is untouched — it stays on its board in its column. Use this when a card was added in error or the launch no longer needs it; to say a card is DONE, move it on its board instead and the launch's readiness will show it.${GOVERNED_NOTE}`,
|
|
250
|
+
{
|
|
251
|
+
launchId: z.string().describe('Launch id from list_work_launches.'),
|
|
252
|
+
itemId: z.string().describe('Card id.'),
|
|
253
|
+
dryRun: z.boolean().optional().describe('Defaults to true. Set false for a live write.'),
|
|
254
|
+
approved: z.boolean().optional().describe('Required true for a live write.'),
|
|
255
|
+
reason: z.string().optional().describe('Required for a live write: the audit reason.'),
|
|
256
|
+
idempotencyKey: z.string().optional().describe('Required for a live write. Reuse on retry.'),
|
|
257
|
+
},
|
|
258
|
+
async (args) => {
|
|
259
|
+
const path = `/api/v1/work-launches/${encodeURIComponent(args.launchId)}/items/${encodeURIComponent(args.itemId)}`;
|
|
260
|
+
const preview = validateApiBridgeRequest({
|
|
261
|
+
method: 'DELETE',
|
|
262
|
+
path,
|
|
263
|
+
dryRun: args.dryRun,
|
|
264
|
+
approved: args.approved,
|
|
265
|
+
reason: args.reason,
|
|
266
|
+
idempotencyKey: args.idempotencyKey,
|
|
267
|
+
grantedScope: getGrantedScope(),
|
|
268
|
+
});
|
|
269
|
+
if (preview?.dryRun) {
|
|
270
|
+
return ok({ ...preview, wouldRemove: { launchId: args.launchId, itemId: args.itemId } });
|
|
271
|
+
}
|
|
272
|
+
const data = await api('DELETE', path, { headers: buildMutationHeaders(args) });
|
|
273
|
+
return ok({ removed: true, launchId: args.launchId, itemId: args.itemId, readiness: data?.data?.readiness });
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Tool names registered here, for the tier map and the toolset manifest. */
|
|
279
|
+
export const WORK_LAUNCH_TOOL_NAMES = [
|
|
280
|
+
'list_work_launches',
|
|
281
|
+
'get_work_launch',
|
|
282
|
+
'create_work_launch',
|
|
283
|
+
'update_work_launch',
|
|
284
|
+
'add_work_item_to_launch',
|
|
285
|
+
'remove_work_item_from_launch',
|
|
286
|
+
];
|