@adrata/adrata-mcp 1.0.19 → 1.0.40
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 +3 -0
- package/access/tiers.js +27 -0
- package/api-bridge.js +6 -0
- package/package.json +3 -3
- package/security.js +167 -14
- package/server.js +24 -1
- package/server.json +2 -2
- package/skills/qa-the-card/SKILL.md +16 -3
- package/skills/ship-the-card/SKILL.md +48 -39
- package/tool-annotations.js +17 -0
- package/tools/provisioning/onboarding-tools.js +730 -0
- package/tools/source-control/connection-tools.js +117 -1
- package/tools/work-board-tools.js +449 -39
- package/tools/work-hub/criteria-quality.js +254 -5
- package/tools/work-hub/field-changes.js +25 -0
- package/toolsets/prospecting.js +194 -3
- package/toolsets/revenue/always-loaded.js +2 -2
package/README.md
CHANGED
|
@@ -268,6 +268,9 @@ All free tools plus:
|
|
|
268
268
|
| `research_person` | Composite: deep person research |
|
|
269
269
|
| `discover_prospects` | Composite: find new prospects |
|
|
270
270
|
| `get_next_contacts` | Composite: next best contacts to reach |
|
|
271
|
+
| `commit_lead_to_outbound` | Governed: commit a lead above the /targets line — Pipeline's Outbound stage |
|
|
272
|
+
| `release_lead_from_outbound` | Governed: take a lead back out of Outbound |
|
|
273
|
+
| `list_outbound_leads` | Who is committed to Outbound right now |
|
|
271
274
|
| `get_competitive_intel` | Composite: competitive analysis |
|
|
272
275
|
| `get_meeting_brief` | Composite: pre-meeting intelligence |
|
|
273
276
|
| `get_deal_coaching` | Composite: deal coaching insights |
|
package/access/tiers.js
CHANGED
|
@@ -27,6 +27,12 @@ export const TOOL_TIERS = {
|
|
|
27
27
|
// or reach a second workspace from the CLI at all.
|
|
28
28
|
list_workspaces: TIERS.ENTERPRISE,
|
|
29
29
|
switch_workspace: TIERS.ENTERPRISE,
|
|
30
|
+
// Provisioning a customer's workspace: reading who is seated, and seating
|
|
31
|
+
// somebody with the onboarding link that reaches them. Enterprise because it
|
|
32
|
+
// is workspace administration -- the API separately requires the connected
|
|
33
|
+
// user to be a workspace admin and answers 403 otherwise.
|
|
34
|
+
list_workspace_seats: TIERS.ENTERPRISE,
|
|
35
|
+
create_onboarding_link: TIERS.ENTERPRISE,
|
|
30
36
|
adrata_api_catalog: TIERS.FREE,
|
|
31
37
|
paper_app_audit: TIERS.FREE,
|
|
32
38
|
adrata_desktop_app_audit: TIERS.FREE,
|
|
@@ -220,6 +226,7 @@ export const TOOL_TIERS = {
|
|
|
220
226
|
list_work_boards: TIERS.ENTERPRISE,
|
|
221
227
|
list_source_control_connections: TIERS.ENTERPRISE,
|
|
222
228
|
get_source_control_connection_events: TIERS.ENTERPRISE,
|
|
229
|
+
bind_repository_to_board: TIERS.ENTERPRISE,
|
|
223
230
|
get_work_item_delivery_evidence: TIERS.ENTERPRISE,
|
|
224
231
|
audit_work_hub: TIERS.ENTERPRISE,
|
|
225
232
|
list_work_item_acceptance_criteria: TIERS.ENTERPRISE,
|
|
@@ -235,6 +242,7 @@ export const TOOL_TIERS = {
|
|
|
235
242
|
record_work_item_qa_failure_and_release: TIERS.ENTERPRISE,
|
|
236
243
|
requeue_work_item_qa_after_fix: TIERS.ENTERPRISE,
|
|
237
244
|
get_work_item_history: TIERS.ENTERPRISE,
|
|
245
|
+
get_work_item_field_changes: TIERS.ENTERPRISE,
|
|
238
246
|
get_work_item_comments: TIERS.ENTERPRISE,
|
|
239
247
|
get_work_board_rollup: TIERS.ENTERPRISE,
|
|
240
248
|
list_work_board_rollups: TIERS.ENTERPRISE,
|
|
@@ -243,6 +251,8 @@ export const TOOL_TIERS = {
|
|
|
243
251
|
attach_work_item_qa_evidence: TIERS.ENTERPRISE,
|
|
244
252
|
verify_work_item_qa_evidence_playback: TIERS.ENTERPRISE,
|
|
245
253
|
move_work_item: TIERS.ENTERPRISE,
|
|
254
|
+
preview_work_item_acceptance_reaffirmation: TIERS.ENTERPRISE,
|
|
255
|
+
reaffirm_work_item_current_acceptance: TIERS.ENTERPRISE,
|
|
246
256
|
transfer_work_item_between_boards: TIERS.ENTERPRISE,
|
|
247
257
|
set_work_item_tag: TIERS.ENTERPRISE,
|
|
248
258
|
block_work_item: TIERS.ENTERPRISE,
|
|
@@ -258,6 +268,11 @@ export const TOOL_TIERS = {
|
|
|
258
268
|
unsatisfy_work_item_acceptance_criterion: TIERS.ENTERPRISE,
|
|
259
269
|
comment_on_work_item: TIERS.ENTERPRISE,
|
|
260
270
|
flag_work_item: TIERS.ENTERPRISE,
|
|
271
|
+
// Retiring a card and bringing it back. Same reasoning as every board write:
|
|
272
|
+
// these change a real workspace record.
|
|
273
|
+
archive_work_item: TIERS.ENTERPRISE,
|
|
274
|
+
delete_work_item: TIERS.ENTERPRISE,
|
|
275
|
+
unarchive_work_item: TIERS.ENTERPRISE,
|
|
261
276
|
// The containers above the cards, and the "add this to the roadmap" verb.
|
|
262
277
|
// Same reasoning as every board tool: scopes are real workspace records.
|
|
263
278
|
list_work_scopes: TIERS.ENTERPRISE,
|
|
@@ -411,6 +426,18 @@ export const TOOL_TIERS = {
|
|
|
411
426
|
// get_speedrun_list already mapped above
|
|
412
427
|
discover_prospects: TIERS.PRO,
|
|
413
428
|
get_next_contacts: TIERS.PRO,
|
|
429
|
+
// The Outbound trio, registered in `toolsets/prospecting.js` beside `get_next_contacts` and
|
|
430
|
+
// named in its header alongside it. They arrived on the desktop branch while this tier map
|
|
431
|
+
// grew on main, so neither side could see the gap: the registry had three tools this table
|
|
432
|
+
// did not, and `getToolTier` falls back to ENTERPRISE for anything unlisted -- which would
|
|
433
|
+
// have quietly put a PRO workspace's own Outbound moves behind the top tier rather than
|
|
434
|
+
// erroring. The exhaustiveness test is what refuses the fallback, and it is right to.
|
|
435
|
+
//
|
|
436
|
+
// PRO, matching the toolset they ship in: committing a lead to Outbound IS the pipeline move
|
|
437
|
+
// for a person, so it belongs with the prospecting reads that lead to it, not above them.
|
|
438
|
+
commit_lead_to_outbound: TIERS.PRO,
|
|
439
|
+
list_outbound_leads: TIERS.PRO,
|
|
440
|
+
release_lead_from_outbound: TIERS.PRO,
|
|
414
441
|
|
|
415
442
|
// --- COMPOSITE TOOLSETS: intelligence (pro tier) ---
|
|
416
443
|
get_competitive_intel: TIERS.PRO,
|
package/api-bridge.js
CHANGED
|
@@ -298,6 +298,12 @@ const PATH_WRITE_SCOPES = [
|
|
|
298
298
|
['/api/v1/contacts', 'write:people'],
|
|
299
299
|
['/api/v1/people', 'write:people'],
|
|
300
300
|
['/api/v1/leads', 'write:people'],
|
|
301
|
+
// Lead lists — including the marker-owned Outbound list that /targets and
|
|
302
|
+
// Pipeline share — are gated by the API on read:speedrun for reads AND
|
|
303
|
+
// writes (scope_guard/mod.rs: `path.starts_with("/lead-lists")`). Naming it
|
|
304
|
+
// here turns the preview's "unknown (no mapping for this path)" into the
|
|
305
|
+
// scope the server actually checks.
|
|
306
|
+
['/api/v1/lead-lists', 'read:speedrun'],
|
|
301
307
|
['/api/v1/agent-tasks', 'write:tasks'],
|
|
302
308
|
['/api/v1/tasks', 'write:tasks'],
|
|
303
309
|
['/api/v1/actions', 'write:actions'],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adrata/adrata-mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Adrata MCP Server \u2014 connect Claude Code, Codex, Gemini, Cursor, and other AI tools to your CRM.
|
|
3
|
+
"version": "1.0.40",
|
|
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",
|
|
7
7
|
"bin": {
|
|
@@ -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 tool-annotations.test.js toolsets.test.js toolsets/communications.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/criteria-quality.test.js tools/roadmap-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/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/security.js
CHANGED
|
@@ -143,18 +143,169 @@ export function rateLimitResponse(retryAfterMs) {
|
|
|
143
143
|
// 2. Input Validation
|
|
144
144
|
// ---------------------------------------------------------------------------
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Length caps for TOP-LEVEL string tool arguments, by argument name.
|
|
148
|
+
*
|
|
149
|
+
* `applySecurityLayer` wraps every tool this server registers, so these caps
|
|
150
|
+
* are the FIRST thing any argument meets — before Zod, before the handler,
|
|
151
|
+
* before the API. Until 2026-09-05 they TRUNCATED, silently, and the tool then
|
|
152
|
+
* returned success: a card flag ended mid-word at exactly 1000 characters
|
|
153
|
+
* ("Do not substitute API SHA or bounce the card merely t") and the
|
|
154
|
+
* prohibition was gone for every subsequent reader including its author.
|
|
155
|
+
*
|
|
156
|
+
* Every comparable cap in the Rust API refuses instead — comments 2000,
|
|
157
|
+
* criterion parts 600, engineering-proof fields 600, reviewer model 120,
|
|
158
|
+
* evidence summary 600 — several enforced twice, once in the handler and
|
|
159
|
+
* again as a database CHECK. So every caller already handles a refusal. This
|
|
160
|
+
* layer was the only one that cut, and it ran first, which is why the cut was
|
|
161
|
+
* invisible from both ends.
|
|
162
|
+
*/
|
|
163
|
+
const DEFAULT_STRING_CAP = 1000;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The API's own cap on a card comment, and on every free-text `reason` that
|
|
167
|
+
* routes through `validate_body`: flag, block, dependency, sharing, QA-flow
|
|
168
|
+
* note. Rust calls it `MAX_COMMENT_BODY_CHARS`
|
|
169
|
+
* (`code/api/crates/routes-gtm/src/work_boards/mod.rs`).
|
|
170
|
+
*
|
|
171
|
+
* `security.cap-contract.test.js` reads that constant out of the Rust
|
|
172
|
+
* source and fails if this number ever drops below it, because THAT is the
|
|
173
|
+
* divergence this whole change is about: a cap here that sits below the cap
|
|
174
|
+
* downstream cuts a value the API would have accepted, and the API never gets
|
|
175
|
+
* to refuse it.
|
|
176
|
+
*/
|
|
177
|
+
const API_COMMENT_BODY_CAP = 2000;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Sanity ceiling for an argument that carries a document or a data file rather
|
|
181
|
+
* than prose (a CSV to import, a knowledge file's markdown, a paper's HTML).
|
|
182
|
+
* Deliberately far above any product rule: the point is to bound the transport,
|
|
183
|
+
* and to let the API be the layer that says how big a document may be.
|
|
184
|
+
*/
|
|
185
|
+
const BULK_PAYLOAD_CAP = 1_000_000;
|
|
186
|
+
|
|
187
|
+
const STRING_CAPS_BY_KEY = {
|
|
188
|
+
// Long-form prose the API caps far lower anyway (a comment body is refused
|
|
189
|
+
// at 2000). 10000 here is a sanity ceiling, not the product rule.
|
|
190
|
+
body: 10000,
|
|
191
|
+
message: 10000,
|
|
192
|
+
description: 10000,
|
|
193
|
+
|
|
194
|
+
// The reason family. These were the DANGEROUS truncations and the reason
|
|
195
|
+
// this cap exists as its own class:
|
|
196
|
+
//
|
|
197
|
+
// flag_work_item.reason API cap 2000 — a 1000-cut value was
|
|
198
|
+
// ACCEPTED truncated. This is the flag
|
|
199
|
+
// defect: a prohibition ended mid-word at
|
|
200
|
+
// exactly 1000 characters and read as
|
|
201
|
+
// complete to every later reader.
|
|
202
|
+
// block_work_item.reason API cap 2000 — same shape.
|
|
203
|
+
// move_work_item.reason NO API cap at all, so nothing downstream
|
|
204
|
+
// transfer_...between_boards could ever catch the cut. Permanently and
|
|
205
|
+
// silently shortened to 1000.
|
|
206
|
+
// every X-Adrata-Reason header NO API cap anywhere in Rust
|
|
207
|
+
// (api-bridge.js) (presence-only). Same silence.
|
|
208
|
+
//
|
|
209
|
+
// Raising these to the API's own 2000 is what keeps this change from
|
|
210
|
+
// trading a silent cut for a refusal of honest input: a 1400-character
|
|
211
|
+
// reason is legitimate, the API accepts it, and until now this layer cut it.
|
|
212
|
+
reason: API_COMMENT_BODY_CAP,
|
|
213
|
+
|
|
214
|
+
// `auditReason` is the same field family under a different name: the shared
|
|
215
|
+
// `moneyWrite` envelope (server.js) spreads it into EVERY money write, so it
|
|
216
|
+
// was the one reason-shaped argument the first version of this table missed.
|
|
217
|
+
auditReason: API_COMMENT_BODY_CAP,
|
|
218
|
+
|
|
219
|
+
// `record_work_item_qa_failure_and_release.details` is composed into a flag
|
|
220
|
+
// reason (`"QA criterion failure (<id>): " + details`) and so lands under
|
|
221
|
+
// the same 2000 API cap. A 1000-character cut plus that ~14-character prefix
|
|
222
|
+
// stayed comfortably under 2000, which is exactly why the "~1,014 characters,
|
|
223
|
+
// 200 OK" measurement was silent. Known rough edge: a `details` near 2000
|
|
224
|
+
// composes to over 2000 and the API refuses the composed value with its own
|
|
225
|
+
// message. That is a refusal rather than a silence, which is the property
|
|
226
|
+
// this change is about.
|
|
227
|
+
details: API_COMMENT_BODY_CAP,
|
|
228
|
+
|
|
229
|
+
// Search terms. A query longer than this is a paste, not a search.
|
|
230
|
+
query: 500,
|
|
231
|
+
search: 500,
|
|
232
|
+
|
|
233
|
+
// --- Payloads, not prose ---
|
|
234
|
+
//
|
|
235
|
+
// These carry a document or a data file, and at the old 1000 default they
|
|
236
|
+
// were the WORST instances of the truncation: `bulk_import`/`manage_data`
|
|
237
|
+
// took a CSV — any real one is past 1000 in its first few rows — cut it to
|
|
238
|
+
// 1000, imported the fragment, and reported success. A partial import that
|
|
239
|
+
// announces itself as complete is harder to notice than a lost sentence.
|
|
240
|
+
//
|
|
241
|
+
// The ceiling here is a transport sanity bound, NOT a product rule. This
|
|
242
|
+
// layer has no business deciding how large a knowledge file may be; the API
|
|
243
|
+
// owns that, and it can refuse. What this layer must never again do is
|
|
244
|
+
// decide it silently.
|
|
245
|
+
csvData: BULK_PAYLOAD_CAP,
|
|
246
|
+
content: BULK_PAYLOAD_CAP,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/** The cap that applies to a top-level string argument named `key`. */
|
|
250
|
+
export function maxLengthForKey(key) {
|
|
251
|
+
return Object.prototype.hasOwnProperty.call(STRING_CAPS_BY_KEY, key)
|
|
252
|
+
? STRING_CAPS_BY_KEY[key]
|
|
253
|
+
: DEFAULT_STRING_CAP;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The refusal text. It names the actual length, the cap, and how much to cut,
|
|
258
|
+
* because the caller is usually an agent that can shorten and retry — and it
|
|
259
|
+
* says plainly that nothing was written, since the whole point of the defect
|
|
260
|
+
* this replaces was a caller believing a truncated write had landed.
|
|
261
|
+
*/
|
|
262
|
+
export function overLengthMessage(field, length, maxLength) {
|
|
263
|
+
return `Parameter "${field}" is ${length} characters and the cap is ${maxLength} — `
|
|
264
|
+
+ `remove ${length - maxLength}. Nothing was written: the value was refused whole `
|
|
265
|
+
+ `rather than cut, so shorten it and call again.`;
|
|
266
|
+
}
|
|
267
|
+
|
|
146
268
|
/**
|
|
147
269
|
* Common validation schemas for tool parameters.
|
|
148
270
|
* These wrap and tighten the existing Zod schemas.
|
|
149
271
|
*/
|
|
150
272
|
export const validators = {
|
|
151
|
-
/**
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Strip control characters, and REFUSE a string longer than `maxLength`.
|
|
275
|
+
*
|
|
276
|
+
* Returns `{ value, error }` rather than a bare string on purpose. Until
|
|
277
|
+
* 2026-09-05 this ended `return cleaned.slice(0, maxLength)` — the value was
|
|
278
|
+
* cut, `validateToolInput` still reported `valid: true`, and the write went
|
|
279
|
+
* on to return 200. The object shape means a caller cannot use the result as
|
|
280
|
+
* a string without noticing, so the refusal cannot be dropped the way the
|
|
281
|
+
* silent slice was.
|
|
282
|
+
*
|
|
283
|
+
* Length is counted in CODE POINTS (`[...cleaned].length`), matching the
|
|
284
|
+
* Rust API's `chars().count()` downstream rather than UTF-16 code units, so
|
|
285
|
+
* a value this layer accepts is not one the next layer measures differently.
|
|
286
|
+
*/
|
|
287
|
+
sanitizeString(value, maxLength = DEFAULT_STRING_CAP, field = 'value') {
|
|
288
|
+
if (typeof value !== 'string') return { value, error: null };
|
|
289
|
+
// Vertical tab (0x0B) and form feed (0x0C) ARE whitespace: they separate
|
|
290
|
+
// words. Deleting them outright fuses the tokens either side into one —
|
|
291
|
+
// "evidence<VT>01M1RAMY" becomes "evidence01M1RAMY" — and that is worse to
|
|
292
|
+
// read than a truncation, because the sentence still looks complete, so a
|
|
293
|
+
// reader has no tell at all. They collapse to a space. Every other control
|
|
294
|
+
// character in the class is not a separator, so removing it stays correct.
|
|
295
|
+
//
|
|
296
|
+
// Tab (0x09), newline (0x0A) and carriage return (0x0D) sit outside both
|
|
297
|
+
// classes and pass through untouched, as they always have.
|
|
298
|
+
const cleaned = value
|
|
299
|
+
// eslint-disable-next-line no-control-regex
|
|
300
|
+
.replace(/[\x0B\x0C]/g, ' ')
|
|
301
|
+
// eslint-disable-next-line no-control-regex
|
|
302
|
+
.replace(/[\x00-\x08\x0E-\x1F\x7F]/g, '');
|
|
303
|
+
// Measured AFTER stripping: control characters must not eat the budget.
|
|
304
|
+
const length = [...cleaned].length;
|
|
305
|
+
if (length > maxLength) {
|
|
306
|
+
return { value: cleaned, error: overLengthMessage(field, length, maxLength) };
|
|
307
|
+
}
|
|
308
|
+
return { value: cleaned, error: null };
|
|
158
309
|
},
|
|
159
310
|
|
|
160
311
|
/** Validate that a string doesn't contain obvious injection patterns. */
|
|
@@ -223,13 +374,15 @@ export function validateToolInput(toolName, args) {
|
|
|
223
374
|
continue;
|
|
224
375
|
}
|
|
225
376
|
|
|
226
|
-
// Sanitize string values
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
377
|
+
// Sanitize string values. An over-long value is REFUSED, not cut:
|
|
378
|
+
// see the cap table above for why this layer used to truncate silently.
|
|
379
|
+
const { value: cleaned, error: lengthError } =
|
|
380
|
+
validators.sanitizeString(value, maxLengthForKey(key), key);
|
|
381
|
+
if (lengthError) {
|
|
382
|
+
errors.push(lengthError);
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
sanitized[key] = cleaned;
|
|
233
386
|
|
|
234
387
|
// Validate specific field types
|
|
235
388
|
if (key === 'id' || key.endsWith('Id')) {
|
package/server.js
CHANGED
|
@@ -55,6 +55,7 @@ import { registerEmailTools } from './tools/email-tools.js';
|
|
|
55
55
|
import { registerWorkBoardTools } from './tools/work-board-tools.js';
|
|
56
56
|
import { registerSourceControlTools } from './tools/source-control/connection-tools.js';
|
|
57
57
|
import { registerRoadmapTools } from './tools/roadmap-tools.js';
|
|
58
|
+
import { registerProvisioningTools } from './tools/provisioning/onboarding-tools.js';
|
|
58
59
|
import { registerPaperTools } from './tools/paper-tools.js';
|
|
59
60
|
import { register as registerAlwaysLoadedTools } from './toolsets/revenue/always-loaded.js';
|
|
60
61
|
import { register as registerExtensibilityTools } from './toolsets/extensibility.js';
|
|
@@ -2394,7 +2395,29 @@ registerWorkBoardTools(server, {
|
|
|
2394
2395
|
getGrantedScope: () => loadTokens()?.scope,
|
|
2395
2396
|
});
|
|
2396
2397
|
|
|
2397
|
-
registerSourceControlTools(server, {
|
|
2398
|
+
registerSourceControlTools(server, {
|
|
2399
|
+
z,
|
|
2400
|
+
api,
|
|
2401
|
+
ok,
|
|
2402
|
+
validateApiBridgeRequest,
|
|
2403
|
+
buildMutationHeaders,
|
|
2404
|
+
getGrantedScope: () => loadTokens()?.scope,
|
|
2405
|
+
});
|
|
2406
|
+
|
|
2407
|
+
// Seating a customer's people and minting the onboarding link an AE hands them.
|
|
2408
|
+
// The first path from MCP that can actually finish: `invite_user` through
|
|
2409
|
+
// `adrata_ai_tool_execute` refuses every machine principal at the confirmation
|
|
2410
|
+
// step (ai_crm_tools/mod.rs:427), so the bridge could preview a seat forever and
|
|
2411
|
+
// create none. See tools/provisioning.js for why this is one tool and not two.
|
|
2412
|
+
registerProvisioningTools(server, {
|
|
2413
|
+
z,
|
|
2414
|
+
api,
|
|
2415
|
+
ok,
|
|
2416
|
+
validateApiBridgeRequest,
|
|
2417
|
+
buildMutationHeaders,
|
|
2418
|
+
getGrantedScope: () => loadTokens()?.scope,
|
|
2419
|
+
getWorkspaceId: () => loadTokens()?.workspaceId ?? AUTH?.workspaceId ?? null,
|
|
2420
|
+
});
|
|
2398
2421
|
|
|
2399
2422
|
// The containers above the cards, and the "add this to the roadmap" verb
|
|
2400
2423
|
// (company/decisions/2026-08-06-spoq-roadmap-sync.md).
|
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.40",
|
|
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.40",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|
|
21
21
|
},
|
|
@@ -13,7 +13,14 @@ The workflow semantics are fixed by
|
|
|
13
13
|
`company/decisions/2026-08-29-two-gate-ai-qa-human-production.md`; the required
|
|
14
14
|
evidence loop is fixed by
|
|
15
15
|
`company/decisions/2026-08-29-recorded-staging-qa-standard.md`. This skill is
|
|
16
|
-
their operating procedure.
|
|
16
|
+
their operating procedure. Read the current amendment at
|
|
17
|
+
`company/decisions/2026-09-04-complete-quality-gates-and-instant-interactions.md`
|
|
18
|
+
and the card contract at
|
|
19
|
+
`company/playbooks/engineering/card-readiness-and-evidence-contract.md`.
|
|
20
|
+
Every required criterion/configuration must pass: failed, blocked, skipped,
|
|
21
|
+
missing, stale, unknown, or flaky results block completion. Do not use a human
|
|
22
|
+
acknowledgement or legacy unknown-author exception to clear an incomplete gate.
|
|
23
|
+
No visible UI changes are authorized by the quality audit.
|
|
17
24
|
|
|
18
25
|
## Establish the real queue
|
|
19
26
|
|
|
@@ -171,8 +178,14 @@ does not establish that the screen is visually correct.
|
|
|
171
178
|
`ADRATA_MCP_CONFIG_DIR`. It opens the exact deployed product bundle through a
|
|
172
179
|
one-use, read-only loopback session: upstream reads keep the QA OAuth identity,
|
|
173
180
|
writes fail locally, and private media capabilities remain process-private.
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
It defaults to the canonical deployment on both halves — the canonical API for
|
|
182
|
+
the card and `https://adrata.com` for the bundle — and serves that origin's own
|
|
183
|
+
response policy, so whether a recording plays is decided by the deployed
|
|
184
|
+
`media-src` rather than by no policy at all. Record the returned session
|
|
185
|
+
fingerprint, the deployed app build, AND the `Deployed security policy served
|
|
186
|
+
by this bridge` line in the playback evidence, then close the bridge. If that
|
|
187
|
+
line is the WARNING instead, the bridge sent no policy and the playback it
|
|
188
|
+
showed says nothing about the deployed one.
|
|
176
189
|
|
|
177
190
|
## Manage cards as outcomes, not bug counters
|
|
178
191
|
|
|
@@ -36,22 +36,34 @@ implementation-and-submit path below.
|
|
|
36
36
|
available here, and the reviewer's comment is usually the one thing that
|
|
37
37
|
would have stopped you.
|
|
38
38
|
|
|
39
|
-
**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
**Read the flag's reason and `blocksClaim` separately.** A flag preserves a
|
|
40
|
+
finding; `blocksClaim` says whether an agent can take the work. An ordinary
|
|
41
|
+
failed test, fixture repair or deploy prerequisite that an agent can resolve
|
|
42
|
+
stays claimable with `blocksClaim: false`. Keep the finding visible while
|
|
43
|
+
repairing it. Urgency still comes from the tag, not the flag.
|
|
44
|
+
|
|
45
|
+
Use `blocksClaim: true` only for an explicit prerequisite agents cannot satisfy,
|
|
46
|
+
such as owner authorization, vendor consent or a product decision. Read its
|
|
47
|
+
comments and take another workable card until that prerequisite is satisfied.
|
|
48
|
+
When the field is absent, inspect the deployed API and tool contract: older
|
|
49
|
+
versions treat every flag as blocking. Do not clear an unresolved finding to
|
|
50
|
+
work around an old claim gate; record the release dependency instead.
|
|
51
|
+
|
|
52
|
+
When the finding is resolved, use `flag_work_item` (`flagged: false`) with the
|
|
53
|
+
`expectedFlagGeneration` captured for that finding and a concrete resolution
|
|
54
|
+
reason. A stale-generation refusal means a newer finding must be read and
|
|
55
|
+
resolved; substituting its generation without doing that loses the newer
|
|
56
|
+
failure. If the generation is absent, do not invent one or claim the older
|
|
57
|
+
client supplied concurrent-update protection. Record the release/client
|
|
58
|
+
prerequisite. For a deployed QA repair, use `requeue_work_item_qa_after_fix` with the
|
|
59
|
+
actual failed/deployed builds and `repairedFlagGeneration`, after proving the
|
|
60
|
+
fix's relevant blobs are deployed. Clearing a flag is not a QA pass.
|
|
61
|
+
|
|
62
|
+
If a missing repro, expected behavior or scope decision prevents work, record
|
|
63
|
+
what is missing and the next action on the card. Set a blocking flag only when
|
|
64
|
+
the prerequisite needs someone outside the agent work loop. Do not invent the
|
|
65
|
+
missing half, and do not turn an ordinary engineering failure into an owner
|
|
66
|
+
decision.
|
|
55
67
|
|
|
56
68
|
## Claim it, in the same move, FIRST
|
|
57
69
|
|
|
@@ -77,8 +89,10 @@ Three reasons, and the third is the real one:
|
|
|
77
89
|
Reuse the same `idempotencyKey` if the call fails and you retry. The server
|
|
78
90
|
replays rather than appending a second transition.
|
|
79
91
|
|
|
80
|
-
**If the claim is refused**,
|
|
81
|
-
|
|
92
|
+
**If the claim is refused**, read its actual reason. Another worker, missing
|
|
93
|
+
scope, readiness requirements or an older deployed gate can each refuse a claim.
|
|
94
|
+
Resolve an agent-repairable prerequisite or pick another workable card; do not
|
|
95
|
+
treat every refusal as proof of ownership. `force: true` exists — it takes the card over and demands a reason
|
|
82
96
|
saying why — but it is for a card whose owner is genuinely gone, not for working
|
|
83
97
|
around a refusal. Taking someone's card because it was convenient is the one move
|
|
84
98
|
on this board that costs trust permanently. If you use it, say so in your report,
|
|
@@ -114,27 +128,22 @@ Two things to carry back to the card:
|
|
|
114
128
|
session), so the next person at aligning can ask you what you meant; what it
|
|
115
129
|
must not arrive with is a list of outcomes nobody can check.
|
|
116
130
|
|
|
117
|
-
##
|
|
118
|
-
|
|
119
|
-
Before
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
improve the count.
|
|
134
|
-
|
|
135
|
-
If a tick turns out to be wrong, `unsatisfy_work_item_acceptance_criterion`
|
|
136
|
-
takes it off. It clears the note with it, so if the evidence is worth keeping,
|
|
137
|
-
put it on the card with `comment_on_work_item` first.
|
|
131
|
+
## Record builder readiness
|
|
132
|
+
|
|
133
|
+
Before QA1 entry, record the exact deployed build, implementation and test
|
|
134
|
+
blobs, executed checks, failures and replay instructions in the builder
|
|
135
|
+
handoff. Leave acceptance criteria **unmet and unverified** for independent QA,
|
|
136
|
+
as required by `AGENTS.md` and the recorded staging QA standard. Readiness means
|
|
137
|
+
deployed, testable and free of known card-specific failure; a missing fixture,
|
|
138
|
+
failing relevant check or unexplained runtime failure keeps the card in its
|
|
139
|
+
build column.
|
|
140
|
+
|
|
141
|
+
The builder releases its build claim at handoff and does not consume a QA-pass
|
|
142
|
+
lease or credential. A fresh independent reviewer follows `qa-the-card`; a
|
|
143
|
+
fixer never certifies its own repair. If an earlier unsupported tick needs to
|
|
144
|
+
be cleared, preserve its evidence in a comment before using
|
|
145
|
+
`unsatisfy_work_item_acceptance_criterion` through the governed current-stage
|
|
146
|
+
claim procedure.
|
|
138
147
|
|
|
139
148
|
## Submit it
|
|
140
149
|
|
package/tool-annotations.js
CHANGED
|
@@ -38,6 +38,13 @@ const DOMAIN_OVERRIDES = {
|
|
|
38
38
|
who_am_i: 'workspace',
|
|
39
39
|
get_current_user: 'workspace',
|
|
40
40
|
|
|
41
|
+
// Provisioning: seating a user and minting the onboarding link that reaches
|
|
42
|
+
// them. Categorised 'workspace' rather than given a category of their own —
|
|
43
|
+
// these are workspace administration, and the profile that can administer a
|
|
44
|
+
// workspace is the one that should see them.
|
|
45
|
+
list_workspace_seats: 'workspace',
|
|
46
|
+
create_onboarding_link: 'workspace',
|
|
47
|
+
|
|
41
48
|
// Governed API bridge.
|
|
42
49
|
// Broad backwards-compatible escape hatches stay on the unprofiled Adrata
|
|
43
50
|
// server. Product profiles use the namespace-bound capability bridge below.
|
|
@@ -241,10 +248,20 @@ const IDEMPOTENT_WRITES = new Set([
|
|
|
241
248
|
// comment, criterion, or card.
|
|
242
249
|
'move_work_item', 'transfer_work_item_between_boards', 'set_work_board_column_wip_limit', 'set_work_item_tag', 'set_work_item_kind',
|
|
243
250
|
'create_work_item', 'comment_on_work_item', 'flag_work_item',
|
|
251
|
+
// Retiring an already-retired card is REFUSED with a 409 rather than
|
|
252
|
+
// replayed, so the reason of the first retirement cannot be silently
|
|
253
|
+
// overwritten — which makes a retry with the same key land on the same state.
|
|
254
|
+
// The restore is a plain clear of the same five columns.
|
|
255
|
+
'archive_work_item', 'unarchive_work_item',
|
|
244
256
|
// A repeat block is the same edge (the pair is unique), and a repeat
|
|
245
257
|
// unblock deletes a row that is already gone. Neither compounds.
|
|
246
258
|
'block_work_item', 'unblock_work_item',
|
|
247
259
|
'add_work_item_acceptance_criterion',
|
|
260
|
+
// (delete_work_item needs no entry here either: DESTRUCTIVE_PATTERN matches
|
|
261
|
+
// the `delete_` prefix, so a host confirmation-gates it on its NAME. That is
|
|
262
|
+
// the whole reason the destructive branch is a separate tool rather than a
|
|
263
|
+
// `kind` argument on archive_work_item — a switch inside a safe-sounding name
|
|
264
|
+
// would be gated by nothing.)
|
|
248
265
|
// Reclassifying is a PUT of the whole routing decision, not an append:
|
|
249
266
|
// replaying the same key rewrites the criterion to the same route and the
|
|
250
267
|
// server's receipt table records one change, never a second one.
|