node-autotask 0.39.4 → 0.40.0
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/ARCHITECTURE.md +6 -2
- package/CHANGELOG.md +26 -0
- package/MCP_TOOL_CATALOG.json +3 -3
- package/MCP_TOOL_MANIFEST.md +5 -5
- package/N8N_MCP_MANIFEST.json +2 -2
- package/N8N_MCP_MANIFEST.md +3 -3
- package/README.md +28 -36
- package/capabilities.json +1315 -1320
- package/dist/base-CVx2VMSj.d.cts +103 -0
- package/dist/base-CVx2VMSj.d.ts +103 -0
- package/dist/capabilities.cjs +2 -2
- package/dist/capabilities.js +1 -1
- package/dist/{chunk-H4S4KSXX.cjs → chunk-E5W24BZB.cjs} +143 -143
- package/dist/{chunk-DJCRQLQE.cjs → chunk-EUGSF3U4.cjs} +287 -292
- package/dist/{chunk-VR6RYXTD.cjs → chunk-EVDVESQM.cjs} +1314 -1319
- package/dist/{chunk-SGKGNJEI.cjs → chunk-FEYVLIHD.cjs} +764 -1726
- package/dist/{chunk-JBQ55HYG.js → chunk-MTFRSD7P.js} +2 -2
- package/dist/{chunk-DBLXPY4B.js → chunk-O32L6GS5.js} +4 -9
- package/dist/{chunk-GHRB46YN.js → chunk-VDVBXXJW.js} +566 -1528
- package/dist/{chunk-GDSCYY2G.js → chunk-Y6YJYKBZ.js} +1314 -1319
- package/dist/{index-DkZVRMeW.d.ts → index-BKp8_P2L.d.cts} +125 -806
- package/dist/{index-DkZVRMeW.d.cts → index-BKp8_P2L.d.ts} +125 -806
- package/dist/index.cjs +22 -50
- package/dist/index.d.cts +103 -102
- package/dist/index.d.ts +103 -102
- package/dist/index.js +18 -46
- package/dist/operations/index.cjs +178 -178
- package/dist/operations/index.d.cts +5 -4
- package/dist/operations/index.d.ts +5 -4
- package/dist/operations/index.js +3 -3
- package/dist/pool.cjs +17 -60
- package/dist/pool.d.cts +5 -13
- package/dist/pool.d.ts +5 -13
- package/dist/pool.js +13 -56
- package/dist/resources/index.cjs +4 -4
- package/dist/resources/index.d.cts +1 -1
- package/dist/resources/index.d.ts +1 -1
- package/dist/resources/index.js +3 -3
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/docs/API.md +130 -319
- package/docs/N8N-ADAPTER.md +7 -7
- package/examples/mcp-server-http.ts +18 -18
- package/package.json +1 -1
package/ARCHITECTURE.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
> **Historical document.** This is the frozen v0.1 design and build-plan record. It is
|
|
2
|
+
> not current: the code, `capabilities.plan.json`, `CLAUDE.md` and the `README.md` are the
|
|
3
|
+
> source of truth. Counts and plans below describe v0.1 and are not maintained.
|
|
4
|
+
|
|
1
5
|
# node-autotask — Architecture
|
|
2
6
|
|
|
3
7
|
> Source of truth for the `node-autotask` TypeScript SDK. Every later stage (implementer,
|
|
@@ -1230,7 +1234,7 @@ What it ships instead:
|
|
|
1230
1234
|
mutating tools gain the `dry_run` affordance) + the curation delta file. Silent
|
|
1231
1235
|
divergence between the projection and the curation is a defect; the delta file is
|
|
1232
1236
|
reviewed at Ship.
|
|
1233
|
-
- **Progressive disclosure is mandatory** — the projected surface is
|
|
1237
|
+
- **Progressive disclosure is mandatory** — the projected surface is the tool set in `MCP_TOOL_MANIFEST.md`
|
|
1234
1238
|
(`scripts/project-mcp-tools.mjs --check`), of which 1173 catalog rows (core 15) are
|
|
1235
1239
|
generated (`scripts/build-tool-catalog.mjs --check`), and 815 are dedicated
|
|
1236
1240
|
`autotask_*` tool names (5 META + 810 registry-backed dedicated; ≫ the ~10-tools
|
|
@@ -1249,7 +1253,7 @@ What it ships instead:
|
|
|
1249
1253
|
> `autotask_read` (effect=read only — zero mutating requests); writes through
|
|
1250
1254
|
> `autotask_write` (dry-run first); destructive ops through `autotask_delete`
|
|
1251
1255
|
> (confirm required); cross-mode calls are refused pre-request.
|
|
1252
|
-
That yields 15 always-on tools against the
|
|
1256
|
+
That yields 15 always-on tools against the full projected tool surface — the catalog row for every
|
|
1253
1257
|
non-core operation (name, purpose, `preferredWhen`, `backingOperation`, effect,
|
|
1254
1258
|
dry-run/confirmation flags, required args) is generated, so an unexposed capability stays
|
|
1255
1259
|
**discoverable**, not indistinguishable from absent. Hosts with provider-native
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.40.0] - 2026-10-30
|
|
11
|
+
|
|
12
|
+
The circuit breaker is removed and replaced by a shared per-zone maintenance gate (#299, #285). The SDK's caches merge into one `TtlCache` with one `cache` config block (#286, #263). The capability plan changed (test titles and descriptions for the cache merge): this release needs a minor bump.
|
|
13
|
+
|
|
14
|
+
### Breaking
|
|
15
|
+
- **#299: the circuit breaker is deleted.** Removed: the `circuitBreaker` config block, `CircuitBreakerError` and the `CIRCUIT_BREAKER_OPEN` code, `CircuitBreakerStore` / `CircuitBreakerState`, `InMemoryCircuitBreakerStore`, `defaultCircuitBreakerStore`, `resetDefaultCircuitBreakerStore`, `buildBreakerKey`, `isBreakerFailure` and the `DEFAULT_CIRCUIT_BREAKER_*` constants. The breaker was off by default and had no consumer; retries, the 429 cooldown, the hourly budget and the semaphore stay. Its one real job, the weekly maintenance window, moves to the gate below.
|
|
16
|
+
- **#285: the cooldown store is no longer pluggable.** Removed: `cooldown.store`, `CooldownStore`, `InMemoryCooldownStore`, `defaultCooldownStore`, `resetDefaultCooldownStore`, `buildTenantKey`, `cooldownDurationMs` and the `DEFAULT_COOLDOWN_*` root exports. The gate keeps its behaviour and its `enabled` / `maxMs` / `defaultMs` settings; its state is one in-process map.
|
|
17
|
+
- **#285: `createClientPool` options `idleTtlMs` and `factory` are removed**, together with the cache-settings mismatch warning. The pool is a plain LRU of real clients keyed by credentials; `maxSize` and `sharedReadCache` stay.
|
|
18
|
+
- **#285: the maintenance-page signature is narrower.** A page is a maintenance page when the 256-byte head of a non-JSON body contains `maintenance`, `outage`, `downtime` or `down for`. `service unavailable` and `temporarily unavailable` no longer count: a generic 503 page keeps the ordinary 5xx retry leg (GET / idempotent-flagged) and never opens the zone gate, and an HTML one is stamped `maintenancePatternMiss`.
|
|
19
|
+
- **#285: a maintenance page no longer takes the generic retry leg.** With `maintenanceMaxWaitMs: 0` a maintenance page surfaces at once instead of being retried `maxRetries` times.
|
|
20
|
+
- **#286: one `cache` config block.** `cache: { ttlMs?, fieldTtlMs?, store? } | false` now covers every cache: `ttlMs` (default 5 min) for single-record reads and resource names, `fieldTtlMs` (default 5 min) for entity field metadata, `false` turns everything off. Removed: the `fieldCache` and `resourceNameCache` blocks (both are refused with a pointer to the new shape; `fieldCache: { ttlMs }` becomes `cache: { fieldTtlMs }`, and the old all-off trio becomes `cache: false`), `cache.perResourceTtlMs` and the `maxEntities` / `maxEntries` bounds (kept at their old defaults).
|
|
21
|
+
- **#286: cache classes and constants.** `ReadCache`, `FieldCache`, `ResourceNameCache` and `LatestCountCache` are replaced by one exported `TtlCache` (`cache.store` now takes a `TtlCache`). Removed exports: `ReadCache`, `DEFAULT_RESOURCE_NAME_CACHE_TTL_MS`. `HttpClient.resourceNameCache` and `HttpClient.latestCountCache` are gone; `readCache` and `fieldCache` are `TtlCache` instances (field keys are `fieldKey(resource, kind)`, a drift drops `invalidate('<resource>:')`).
|
|
22
|
+
- **#286: the generated per-resource TTL table `src/cache-ttl-tables.ts` is deleted** (with `DEFAULT_PER_RESOURCE_TTL_MS`, `RESOURCE_KEY_BY_PATH` and its gate clause). The reference tables (`REFERENCE_ENTITIES`: billing codes, countries, departments, phases, roles, services) live `REFERENCE_TTL_MS` (1 h); every other record lives `cache.ttlMs`.
|
|
23
|
+
- **#286: resource names live in the read cache.** `operations.expandResourceNames` and the composite name siblings read the `/V1.0/Resources/{id}` record entry and its not-found marker. Any write through the client now clears cached names (before, only a `resources` write did), a not-found marker lives the read TTL instead of 60 s, and concurrent expansions of the same id no longer share one in-flight read.
|
|
24
|
+
- **#286: `latest: N` count probes are never cached.** The 30 s count-probe cache (#127) and its #139 / #157 special cases are deleted; every `latest` call pays its own count probes and sees current totals.
|
|
25
|
+
- **#285: a call that receives the maintenance page now throws `MaintenanceError`** (`code: 'MAINTENANCE'`, `httpStatus` set, `details.since`, `details.maintenanceDetected`, the body snippet) instead of `UnexpectedContentTypeError` / `ServerError` / `NotFoundError` with `details.maintenanceDetected`. This covers the wait being off, the wait budget running out, another call owning the probe slot, and an unflagged write answered at a non-2xx status (`retryable: false`, verify-first action). Consumers map one code for the whole window.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **#285: shared per-zone maintenance gate (always on).** The first call that sees the vendor maintenance page opens a process-wide gate for that zone. While it is open, other calls fail at once with the new `MaintenanceError` (`code: 'MAINTENANCE'`, `notSent: true`, `retryable: true`, `retryAfter`, `details.since`) without a send. One probe call per `maintenancePollMs` goes upstream (single-flight), and the first non-maintenance response closes the gate. `maintenanceMaxWaitMs` / `maintenancePollMs` keep their meaning; `0` fails fast and the gate still opens. Detection is the page signature only: a non-matching HTML page (a wrong `baseUrl`) never opens the gate, and a window that answers with refused connections is not detected.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **#263: a write flagged `idempotent: true` still clears the read cache** (fixed in 0.39.3, carried to the merged cache). `idempotent` governs retry only; only GETs and the `/query`, `/query/count` and `/query/next` read POSTs skip invalidation. A regression test reproduces the report (a `client.http.request` PUT with `idempotent: true`, then `get`, returns the post-write record).
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- `src/http/breaker-budget.ts` is split into `src/http/gates.ts` (cooldown + maintenance gate) and `src/http/limits.ts` (hourly budget, semaphore); `HttpClient` loses its breaker wrapper methods.
|
|
35
|
+
|
|
10
36
|
## [0.39.4] - pending
|
|
11
37
|
|
|
12
38
|
Patch: label path fix (#306). No capability-plan change.
|
package/MCP_TOOL_CATALOG.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"generatedBy": "scripts/build-tool-catalog.mjs",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
4
|
-
"planHash": "
|
|
3
|
+
"generatedAt": "2026-10-30T12:23:35.562Z",
|
|
4
|
+
"planHash": "eaa2bea2d3107fffc8b10fb9efef5475e457595d585d790871bec7c221802672",
|
|
5
5
|
"metaTools": [
|
|
6
6
|
{
|
|
7
7
|
"name": "autotask_catalog",
|
|
@@ -23389,7 +23389,7 @@
|
|
|
23389
23389
|
"operation": "operations.expandResourceNames",
|
|
23390
23390
|
"core": false,
|
|
23391
23391
|
"tool": "autotask_expand_resource_names",
|
|
23392
|
-
"purpose": "Resolve resource ids to display names in one opt-in call: per-id resources.get reads (never an id-in query) with blind-marked rows for ids the API user cannot resolve
|
|
23392
|
+
"purpose": "Resolve resource ids to display names in one opt-in call: per-id resources.get reads (never an id-in query) with blind-marked rows for ids the API user cannot resolve served from the read cache (each distinct id fetched at most once per cache lifetime).",
|
|
23393
23393
|
"preferredWhen": "For name-joining resource ids that other tables expose without names (time_entries / billing_items / tickets resourceID) — fetchByIds for the full records; the bounded query op for filtered resource sets (NOT for lookups on a table that may be blind to the API user — that is exactly when per-id reads are required).",
|
|
23394
23394
|
"backingOperation": "operations.expandResourceNames",
|
|
23395
23395
|
"effect": "read",
|
package/MCP_TOOL_MANIFEST.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MCP Tool Manifest — node-autotask
|
|
2
2
|
|
|
3
|
-
<!-- GENERATED by scripts/project-mcp-tools.mjs at 2026-10-
|
|
3
|
+
<!-- GENERATED by scripts/project-mcp-tools.mjs at 2026-10-30T12:23:35.418Z — do not hand-edit. -->
|
|
4
4
|
|
|
5
5
|
Mechanical MCP projection of the capability registry (generator-design section 7, ARCHITECTURE section 19.5).
|
|
6
6
|
Tool names are the plan `mcpTool` columns (entity-first `autotask_<entity>_<verb>`); descriptions are the
|
|
@@ -8,7 +8,7 @@ registry `purpose` + a mechanical bounds/flags sentence; annotations derive from
|
|
|
8
8
|
Curation edits live ONLY in `MCP_TOOL_OVERRIDES.json` (records `{ tool, field, newValue, reason }`).
|
|
9
9
|
|
|
10
10
|
Registry: 1281 operations; projected tools: 897 (one per registry op with a non-null `mcpTool`);
|
|
11
|
-
not projected (catalog/describe material): 384. planHash: `
|
|
11
|
+
not projected (catalog/describe material): 384. planHash: `eaa2bea2d3107fff…`
|
|
12
12
|
|
|
13
13
|
## Always-on core tier (CORE_RULE — computed, never a hand list)
|
|
14
14
|
|
|
@@ -835,7 +835,7 @@ operation key). An op with no tool of its own still exists as a catalog row in `
|
|
|
835
835
|
| `autotask_aggregate` | `operations.aggregate` | helper | read | read | Fold any query surface into groups/sums/totals in one bounded call — or count it with the vendor count endpoint when no folding is needed — with blind-surface and truncation honesty (caps documented: the walk scans at most 2500 rows and top keeps at most 100 groups — a truncated aggregate is a PARTIAL answer, never a tenant-wide total); date fields bucket via the closed groupBy suffix <field>@month | @week | @year (ISO labels, UTC-anchored). Use it for totals, sums, counts and hours per ticket, resource, company, contract or month (group by any field), e.g. time entries hours grouped by ticket and resource | {"operation":{"type":"string — the query operation to fold, exactly <planResourceKey>.query (a plan resource WITH a query row; a special op, a .count row, or an unknown resource is refused naming autotask_catalog)"},"filter":{"type":"object[] — the filter conditions (the query-bag filter grammar: 13 operators, caps 500 OR-conditions / 1 UDF / 8000 chars; field names in vendor casing via operations.fieldCatalog; an empty array = the unfiltered call — the blind-predicate stamp case)"},"opts":{"type":"object (optional) — { groupBy?: string[] (vendor field names), sum?: string[] (numeric vendor fields), top?: number (default 25, clamped 1..100 — the group-row cap), limit?: number (total-record cap for the walk modes, default 2500 = the house BOUNDED_QUERY_MAX_LIMIT — the hard max), names?: boolean (default true — group keys gain label siblings, e.g. key.ticketIDName, key.statusLabel — null, never missing, when the field is keyed with a null value, a 0 id or an unresolved value (a picklist 0 is labelled when the picklist defines it); false = raw ids) } — NEITHER groupBy nor sum = COUNT mode (one vendor /query/count call — no walk)"}} (required: operation, filter) | compact AggregateResult |
|
|
836
836
|
| `autotask_blind_surfaces` | `operations.blindSurfaces` | helper | read | read | Answer 'can this tenant/see this table at all?' in one ZERO-wire call: the plan-pinned blind-surface inventory over the 116 query-able surfaces — which read ops are affected per resource, the blind class, the unfiltered row count the discovery sweep observed, the authority op, and the proof level — with the data-full set, the artifact counts, and the remedies. | {"opts":{"type":"object? (the optional filter bag: { resource?: string (the plan resource key — narrows the row set; unknown key → RESOURCE_UNKNOWN), kind?: \u0027unowned-blind\u0027 \| \u0027fully-blind\u0027 \| \u0027unfiltered-blind\u0027 \| \u0027unknown\u0027 (narrows to one blind class; unknown kind → CONFIG_ERROR naming the valid classes) }) — the ZERO-wire served 116-surface inventory"}} (required: none) | compact BlindSurfaceInventory |
|
|
837
837
|
| `autotask_create_guidance` | `operations.createGuidance` | helper | read | read | Answer 'what does the vendor ACTUALLY require to create a record here?' in one ZERO-wire call: the 92 campaign-decoded create entities — the live-proven required-field prefix, the structured blocker (the exact wall class), the exact vendor message text, the remedy, and the delete boundary — instead of rediscovering it one failed write at a time. | {"opts":{"type":"object? (the optional filter bag: { resources?: string[] (the plan resource keys — narrows the row set; unknown key → RESOURCE_UNKNOWN), kind?: CreateBlockerKind (narrows to one blocker kind; unknown kind → CONFIG_ERROR naming the valid kinds) }) — the ZERO-wire served zone-fact catalog (one row per campaign-decoded create entity)"}} (required: none) | compact CreateGuidanceReport |
|
|
838
|
-
| `autotask_expand_resource_names` | `operations.expandResourceNames` | helper | read | read | Resolve resource ids to display names in one opt-in call: per-id resources.get reads (never an id-in query) with blind-marked rows for ids the API user cannot resolve
|
|
838
|
+
| `autotask_expand_resource_names` | `operations.expandResourceNames` | helper | read | read | Resolve resource ids to display names in one opt-in call: per-id resources.get reads (never an id-in query) with blind-marked rows for ids the API user cannot resolve served from the read cache (each distinct id fetched at most once per cache lifetime). | {"ids":{"type":"(number \| string)[] — vendor numeric resource ids (safe integers, or the canonical integer string for ids above 2^53); up to 500 ids (over → pre-request refusal, zero wire; maxIds may lower the cap, never raise it); non-numeric string ids refused pointing at operations.resolveAny. One call = per-id resources.get reads ONLY (never an id-in query — the resources table can be blind to count/query while per-id reads work, and an id-in lookup would silently return nothing). Each distinct id is fetched at most once per call (de-duplicated) and, with the read cache on (the default), at most once per cache lifetime (the vendor\u0027s true not-found is cached as a blind marker — a repeat miss costs no wire; a failure is never cached). Per-row results { id, name (firstName [middleName] lastName, single spaces; null when the vendor returns no name part — a data gap, distinct from blind), blind (true when the per-id read cannot resolve — the table-invisible case — id kept, no error, no throw), error? (a transient per-id fault; one fault never aborts the rest) }. Result: { results, resolved, blind, failed }."}} (required: ids) | compact ExpandResourceNamesResult |
|
|
839
839
|
| `autotask_fetch_by_ids` | `operations.fetchByIds` | helper | read | read | Fetch up to 500 single records by id across mixed resources in one call (query-first chunked id-in reads with a per-id get fallback), with per-item typed results. | {"items":{"type":"object[] — the items list [{ resource: string (plan key, e.g. tickets), id: number \| string (vendor numeric id — a safe integer, or the canonical integer string for ids above 2^53), fields?: string[] (exact vendor field names — the fetched row projected best-effort to the item\u0027s fields, zero extra wire) }]; up to 500 items (over → pre-request refusal); numeric ids + canonical integer strings ONLY (non-numeric string identifiers → operations.resolveAny); mixed resources; query-first (per-resource 250-id id-in chunks, single page each) with a per-id get fallback for every query-missed id (blind-surface-safe); per-item typed results (one NOT_FOUND or fault never aborts the rest)"}} (required: items) | compact FetchManyResult |
|
|
840
840
|
| `autotask_fetch_many` | `operations.fetchMany` | helper | read | read | When to call: batch up to 20 single-record GETs across MIXED resources in one call (instead of N sequential autotask_read calls): items [{ resource, id, fields? }] with per-item typed results (NOT_FOUND / SERVER_ERROR per item — never all-or-nothing). Refusals are pre-request (zero wire): string ids point at operations.resolveAny (name → id is its job); parent-scoped children (e.g. ticket_notes) need a parentId; unknown `fields` keys come back as the complete problem list (fields = exact vendor field names — client-side projection, zero extra wire); >20 items → CONFIG_ERROR (use <resource>.query for bulk). Sequential GETs (one wire per item); read op — no dry run. | {"items":{"type":"object[] — the items list [{ resource: string (plan key, e.g. tickets), id: number \| string (vendor numeric id — a safe integer, or the canonical integer string for ids above 2^53), fields?: string[] (exact vendor field names — client-side projection, zero extra wire) }]; max 20 items (over → pre-request refusal); numeric ids + canonical integer strings ONLY (non-numeric string identifiers → operations.resolveAny); per-item typed partial failure (never all-or-nothing)"},"opts":{"type":"object (optional) — { maxItems?: number (may lower the 20-item cap, never raise it), names?: boolean (default true — found records gain label siblings, e.g. statusLabel, contractIDName — null, never missing, when the field is keyed with a null value, a 0 id or an unresolved value (a picklist 0 is labelled when the picklist defines it); false = raw records), liveFields?: boolean (OPT-IN live field validation, issue #66 — when true the requested fields validate against the tenant\u0027s LIVE entity field metadata: a warm fieldCache costs zero wire, a cold run is one entityInformation/fields GET per distinct resource in the batch; a resource whose metadata is unavailable falls back to the vendored-spec set for THAT resource (never throws, never blocks the batch); when available the live set is authoritative — a field it lacks is refused even if the spec lists it, and a live field the spec lacks (e.g. time_entries postedDate) is accepted; field discovery: operations.fieldCatalog) }"}} (required: items) | compact FetchManyResult |
|
|
841
841
|
| `autotask_field_catalog` | `operations.fieldCatalog` | helper | read | read | Fetch the LIVE vendor field catalog for the named resources in one bounded read call: which fields exist, which are required, types, maxLength, read-only, queryable, picklist-ness — the on-demand refresh for the point-in-time pins (operations.createGuidance, the zoneFacts) when the vendor has moved since they were verified. The wire cost (resources × include) is refused pre-request above the cap, never truncated; with the opt-in fieldCache configured, repeat reads inside the TTL cost no wire. It reports picklist-ness as a flag (isPickList) and picklist field NAMES (picklistFields); with the opt-in labels: true the projected picklist rows carry the vendor's exposed picklist VALUES (labels next to each value), and without it the op serves no VALUES. A picklist VALUE the vendor does not expose to this API user is not resolvable from this catalog. | {"opts":{"type":"object? (the call bag: { resources?: string[] (plan keys; omitted → the 5 workflow resources — never the full set; unknown key → RESOURCE_UNKNOWN carrying the count + the autotask_catalog pointer), include?: (\u0027info\u0027\|\u0027fields\u0027\|\u0027udf\u0027)[] (what to fetch per resource; default [\u0027fields\u0027]), fresh?: boolean (default false — bypass + refresh the opt-in fieldCache entries), fields?: string[] (post-projection filter, fields-kind rows only: names absent from a resource\u0027s fields catalog → that row\u0027s unmatchedFields — reported, never an error; info/udf rows unaffected), labels?: boolean (opt-in: the projected picklist rows carry the vendor\u0027s exposed picklist VALUES — the raw vendor entries as-is; default false = no VALUES on the rows; zero extra wire), compact?: boolean (default false; true = each field row is only { name, type, isPickList, isReference, udf } — a live tickets result is ~11 KB compact against ~20 KB full and ~137 KB full with labels: true; picklistValues ride a compact row only with labels: true AND a fields filter; recommended for MCP/LLM callers), maxBytes?: number (integer >= 4096; default none; caps the UTF-8 JSON size: over it, field rows are dropped from the end and the result carries truncated: true + truncation { reason: \u0027maxBytes\u0027, maxBytes, fullBytes, omitted: [{ resource, kind, count }], note } — always valid JSON, never cut mid-document and never over the cap; the row name lists are trimmed only when the field rows alone cannot fit; a result whose envelope alone cannot fit (many resources, a small cap) is refused CONFIG_ERROR) }) — bounded live field metadata (the wire cost is refused pre-request above the cap, never truncated; per-resource failures recorded, never aborting). It reports field SHAPE as FLAGS and NAMES (picklist-ness via isPickList, picklist field names via picklistFields); with the opt-in labels: true, picklist rows carry the vendor\u0027s exposed picklist VALUES (labels next to each value), and without it the op serves no VALUES; a picklist VALUE the vendor does not expose to this API user (W-A4) is not resolvable from it"}} (required: none) | compact FieldCatalogResult |
|
|
@@ -1013,8 +1013,8 @@ the projection projects what the registry has and reports the gap so the Toolsmi
|
|
|
1013
1013
|
|
|
1014
1014
|
## Meta
|
|
1015
1015
|
|
|
1016
|
-
- generatedAt: 2026-10-
|
|
1017
|
-
- planHash:
|
|
1016
|
+
- generatedAt: 2026-10-30T12:23:35.418Z
|
|
1017
|
+
- planHash: eaa2bea2d3107fffc8b10fb9efef5475e457595d585d790871bec7c221802672
|
|
1018
1018
|
- registry records: 1281
|
|
1019
1019
|
- projected tools: 897
|
|
1020
1020
|
- core tier: 15
|
package/N8N_MCP_MANIFEST.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "node-autotask n8n consumer mcp projection v1",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-30T12:23:35.238Z",
|
|
4
4
|
"generator": "scripts/project-n8n-mcp.mjs",
|
|
5
5
|
"inputs": {
|
|
6
6
|
"capabilities": "capabilities.json (the capability registry — the single source of truth)",
|
|
@@ -3893,6 +3893,6 @@
|
|
|
3893
3893
|
"approvalGatedWrites": 2,
|
|
3894
3894
|
"destructiveExcluded": 4
|
|
3895
3895
|
},
|
|
3896
|
-
"capabilitiesHash": "
|
|
3896
|
+
"capabilitiesHash": "2cda495a540075f6713256fe3bf939c25a0ef63991eb82199ec9c43ef01939b4",
|
|
3897
3897
|
"curationHash": "e19117ce73e38d9aded3be7e7f077a4e185d2e10294622b76c17d67f63cf9e3b"
|
|
3898
3898
|
}
|
package/N8N_MCP_MANIFEST.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# N8N MCP Manifest — node-autotask (consumer projection)
|
|
2
2
|
|
|
3
|
-
<!-- GENERATED by scripts/project-n8n-mcp.mjs at 2026-10-
|
|
3
|
+
<!-- GENERATED by scripts/project-n8n-mcp.mjs at 2026-10-30T12:23:35.238Z — do not hand-edit. -->
|
|
4
4
|
|
|
5
5
|
The n8n CONSUMER projection of the capability registry: the canonical source for what the
|
|
6
6
|
n8n-side MCP tools must expose. The SDK is canonical — this is a projection of it, never a
|
|
@@ -289,6 +289,6 @@ only via a recorded change to `n8n-mcp.curation.json`.
|
|
|
289
289
|
|
|
290
290
|
## Meta
|
|
291
291
|
|
|
292
|
-
- generatedAt: 2026-10-
|
|
293
|
-
- capabilitiesHash:
|
|
292
|
+
- generatedAt: 2026-10-30T12:23:35.238Z
|
|
293
|
+
- capabilitiesHash: 2cda495a540075f6713256fe3bf939c25a0ef63991eb82199ec9c43ef01939b4
|
|
294
294
|
- curationHash: e19117ce73e38d9aded3be7e7f077a4e185d2e10294622b76c17d67f63cf9e3b
|
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ Fully-typed TypeScript SDK for the Autotask (Datto) PSA REST API. Zero runtime
|
|
|
4
4
|
dependencies. Built for building MCP servers and integrations.
|
|
5
5
|
|
|
6
6
|
The SDK is also the **deterministic execution layer for agents** (ARCHITECTURE
|
|
7
|
-
section 19): a generated capability registry (
|
|
8
|
-
|
|
7
|
+
section 19): a generated capability registry (one record per operation, across the entity
|
|
8
|
+
registry in `capabilities.json`), a mechanical MCP substrate (registry → manifest → overrides → core tier +
|
|
9
9
|
catalog meta-tool), and a write governor (dry-run-first, destructive
|
|
10
10
|
confirmation). The SDK boundary also redacts response field values — the
|
|
11
11
|
secret fields (`password`, `secretKey`, `secret`) always, personal and
|
|
@@ -219,29 +219,23 @@ The reference MCP servers (`examples/mcp-server.ts`,
|
|
|
219
219
|
(0 disables retries). Unset = the profile's value; an invalid value stops
|
|
220
220
|
startup with an `AutotaskConfigError` naming the variable.
|
|
221
221
|
|
|
222
|
-
##
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
zone
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
the
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
local refusal. On a shared host (one host, many credential tuples) one
|
|
240
|
-
tenant's outage opens the circuit for the others — the documented
|
|
241
|
-
consequence of the host-only key. `thresholdInformation.get()` bypasses
|
|
242
|
-
the refusal (its outcome is still recorded); a custom `CircuitBreakerStore`
|
|
243
|
-
and failure predicate are injectable (details:
|
|
244
|
-
[`docs/API.md`](./docs/API.md) section 3.2.2).
|
|
222
|
+
## Maintenance gate (issue #285)
|
|
223
|
+
|
|
224
|
+
Autotask takes each zone down for a weekly maintenance window, during which the
|
|
225
|
+
edge serves an HTML maintenance page. The first call that sees that page opens a
|
|
226
|
+
process-wide gate for the zone (always on). While it is open, every other call on
|
|
227
|
+
the zone fails at once with `MaintenanceError` (`code: 'MAINTENANCE'`,
|
|
228
|
+
`notSent: true`, `retryAfter` = seconds to the next probe, `details.since`), with
|
|
229
|
+
zero upstream calls. One call per `maintenancePollMs` (default 30 s) goes upstream
|
|
230
|
+
as the probe, and the first non-maintenance response closes the gate. The call
|
|
231
|
+
that saw the page waits the window out for up to `maintenanceMaxWaitMs` (default
|
|
232
|
+
1 h); `maintenanceMaxWaitMs: 0` fails fast instead (the gate still opens). A
|
|
233
|
+
call that receives the page and does not wait it out throws the same
|
|
234
|
+
`MaintenanceError` (with `httpStatus` set, no `notSent`), so consumers map one
|
|
235
|
+
code. Only the
|
|
236
|
+
page's text is recognised: a non-matching HTML page (for example a wrong
|
|
237
|
+
`baseUrl`) never opens the gate. Details: [`docs/API.md`](./docs/API.md)
|
|
238
|
+
section 3.2.2.
|
|
245
239
|
|
|
246
240
|
## Subpaths
|
|
247
241
|
|
|
@@ -265,7 +259,7 @@ import { markUntrusted, stripUntrustedDeep } from 'node-autotask/untrusted';
|
|
|
265
259
|
// tuple (multi-tenant gateways: one client per tenant — limiter state and
|
|
266
260
|
// read cache persist across a tenant's requests, never across tenants)
|
|
267
261
|
import { createClientPool } from 'node-autotask/pool';
|
|
268
|
-
// createClientPool({ maxSize = 128,
|
|
262
|
+
// createClientPool({ maxSize = 128, sharedReadCache? })
|
|
269
263
|
// -> { get(config), delete(config), clear(), size }
|
|
270
264
|
|
|
271
265
|
// Errors (the class → code → status hierarchy)
|
|
@@ -284,9 +278,8 @@ The SDK ships the mechanical substrate, not a server (no MCP imports, no zod):
|
|
|
284
278
|
`staleCheck` hash-pins). Generated from `capabilities.plan.json` (the single source
|
|
285
279
|
of truth) — never hand-edited.
|
|
286
280
|
2. **Manifest** — [`MCP_TOOL_MANIFEST.md`](./MCP_TOOL_MANIFEST.md): the mechanical
|
|
287
|
-
projection (`npm run mcp:project`) —
|
|
288
|
-
(`autotask_<entity>_<verb
|
|
289
|
-
809), descriptions = registry purpose + a mechanical
|
|
281
|
+
projection (`npm run mcp:project`) — entity-first tool names
|
|
282
|
+
(`autotask_<entity>_<verb>`, plus the five META tools), descriptions = registry purpose + a mechanical
|
|
290
283
|
bounds/flags sentence, `dry_run` affordance on mutating tools, `confirm` on
|
|
291
284
|
destructive ones; the plan-derived `untrustedFields` read tools additionally
|
|
292
285
|
carry the untrusted-content sentence + the field list as the `x-untrusted`
|
|
@@ -448,7 +441,8 @@ The SDK ships the mechanical substrate, not a server (no MCP imports, no zod):
|
|
|
448
441
|
that the MCP servers wrap as untrusted host text; a maintenance notice is waited
|
|
449
442
|
out for up to `maintenanceMaxWaitMs` (default 1 h; `MAINTENANCE_WINDOW_MAX_MS`
|
|
450
443
|
= 4 h, the observed ceiling) for reads and writes; the same wait recognises the
|
|
451
|
-
page at ANY status — `2xx`/`4xx`/`5xx`, issue #114 —
|
|
444
|
+
page at ANY status — `2xx`/`4xx`/`5xx`, issue #114 — and the page opens the
|
|
445
|
+
zone's maintenance gate (see "Maintenance gate" above) — but a `4xx`/`5xx` page is
|
|
452
446
|
waited out only for calls safe to re-send (a `GET` or an explicitly
|
|
453
447
|
`idempotent`-flagged call): an unflagged mutation makes ONE send and says the
|
|
454
448
|
write may have taken effect (`details.maintenanceDetected`); a non-matching
|
|
@@ -512,7 +506,7 @@ Semantics, pinned by `test/resources/collection-update-merge.test.ts` (mock fetc
|
|
|
512
506
|
|
|
513
507
|
[`docs/N8N-ADAPTER.md`](./docs/N8N-ADAPTER.md) proves the thin-importer thesis: how
|
|
514
508
|
`n8n-nodes-autotask` v2.29.4 keeps zero Autotask knowledge after the port — the
|
|
515
|
-
generated
|
|
509
|
+
generated operation mapping (node dir → node ops → SDK methods), the global-op
|
|
516
510
|
mapping (searchFilter/tool/aiHelper/apiThreshold), the swagger-gap table (87
|
|
517
511
|
node-parity paths the vendored swagger lacks, all UNVERIFIED-live this run), and the
|
|
518
512
|
5-line rewrite recipe.
|
|
@@ -531,7 +525,7 @@ node-parity paths the vendored swagger lacks, all UNVERIFIED-live this run), and
|
|
|
531
525
|
| `npm run capabilities:check` | `scripts/check-capabilities.mjs` — the batch gate (missing/incomplete/stale records → exit 1) |
|
|
532
526
|
| `npm run public-surface` | `scripts/public-surface.mjs` — capture (`--write`) / verify the public API surface baseline; default mode fails on unannounced removals, **and** checks the BUILT surface: every tsup entry has a matching `exports` key (and vice versa), every declared target was emitted and loads (ESM+CJS), and no runtime dependency leaks into `dist` |
|
|
533
527
|
| `npm run live-smoke` | `scripts/live-smoke.mjs` — read-only live smoke: 5 vendor probes (5-6 wire calls worst case: 4 GET + 1 bounded query that may walk a 2nd, empty page) + a zero-network shape guard; `--dry` prints the plan with no calls (DRY-RUN-ONLY verdict, exit 0); live via `AUTOTASK_*` env — missing/partial credentials hard-fail exit 2 |
|
|
534
|
-
| `npm run live-coverage` | `scripts/live-coverage.mjs` — the live coverage sweep over all
|
|
528
|
+
| `npm run live-coverage` | `scripts/live-coverage.mjs` — the live coverage sweep over all registry ops (`--dry`: full call plan, no calls; live: bounded reads for real, mutations `dryRun: true` only — zero mutating requests, every governor-gated row (the 54 delete dry-runs included) routed through invoke with `confirm`; budget <= 1227 plan-honest / <= 1382 wire-honest worst case) |
|
|
535
529
|
| `npm run mcp:project` | `scripts/project-mcp-tools.mjs` — mechanical MCP projection → `MCP_TOOL_MANIFEST.md` (`:check` verifies) |
|
|
536
530
|
| `npm run n8n:project` / `n8n:project:check` | `scripts/project-n8n-mcp.mjs` — the n8n consumer projection from the SAME registry → `N8N_MCP_MANIFEST.json`/`.md` (`--check` rebuilds and diffs; the gate is wired into `verify`) |
|
|
537
531
|
| `npm run catalog:build` / `catalog:check` | `scripts/build-tool-catalog.mjs` — the catalog meta-tool row data → `MCP_TOOL_CATALOG.json` + mechanical CORE profile (`--check` verifies drift) |
|
|
@@ -573,8 +567,7 @@ without credentials; a green exit is never a false pass.
|
|
|
573
567
|
|
|
574
568
|
## Testing
|
|
575
569
|
|
|
576
|
-
Strict TDD against mocked HTTP (no live calls in CI):
|
|
577
|
-
this build. Every registry record pins the plan + source + test file hashes
|
|
570
|
+
Strict TDD against mocked HTTP (no live calls in CI): see `npm test` for the current suite. Every registry record pins the plan + source + test file hashes
|
|
578
571
|
(`staleCheck`), and `capabilities:check` re-derives them at the gate. Coverage
|
|
579
572
|
thresholds: 90/90/80/90 (lines/functions/branches/statements). The public API
|
|
580
573
|
surface baseline (`test/__fixtures__/public-surface.json`) proves "additive only":
|
|
@@ -602,8 +595,7 @@ suites are the release gate, the live scripts are the ground-truth check.
|
|
|
602
595
|
env rename to the official Autotask terms, the swagger-required
|
|
603
596
|
`zoneInformation` `user` param, and the always-sent query `filter` (node
|
|
604
597
|
parity) — all live-verified against AU03/ww29.
|
|
605
|
-
- **v0.1.0** — the node's
|
|
606
|
-
(121 entities + 7 integration endpoints), 1167 registry operations, the agent
|
|
598
|
+
- **v0.1.0** — the node's MVP entity set and integration endpoints, the agent
|
|
607
599
|
execution layer (helper floor, bounded scans, mutation safety), the MCP substrate
|
|
608
600
|
(registry → manifest → overrides → core tier + catalog), and the docs surface.
|
|
609
601
|
- **Full 3003-operation spec** — the vendored swagger's complete op set (3003
|