node-autotask 0.43.0 → 0.44.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/CHANGELOG.md +21 -0
- package/MCP_TOOL_CATALOG.json +27 -8
- package/MCP_TOOL_MANIFEST.md +138 -137
- package/N8N_MCP_MANIFEST.json +17 -17
- package/N8N_MCP_MANIFEST.md +18 -18
- package/README.md +6 -19
- package/capabilities.json +1815 -1432
- package/dist/{base-CVx2VMSj.d.cts → base-CftidiyZ.d.cts} +2 -1
- package/dist/{base-CVx2VMSj.d.ts → base-CftidiyZ.d.ts} +2 -1
- package/dist/capabilities.cjs +2 -2
- package/dist/capabilities.d.cts +2 -1
- package/dist/capabilities.d.ts +2 -1
- package/dist/capabilities.js +1 -1
- package/dist/{chunk-TH7B5H4P.cjs → chunk-6WLGHKIU.cjs} +285 -285
- package/dist/{chunk-KTEDPS4X.js → chunk-7MCKJTSA.js} +2 -2
- package/dist/{chunk-5VHSLJMR.js → chunk-A6LHCJQP.js} +30 -9
- package/dist/{chunk-4EFXIE53.js → chunk-BKTOMNT2.js} +2 -2
- package/dist/{chunk-VFLAMMIJ.cjs → chunk-EMYDC5SG.cjs} +151 -120
- package/dist/{chunk-SK3GYRNA.cjs → chunk-KWQSGWY5.cjs} +143 -143
- package/dist/{chunk-Q23JULNN.js → chunk-LYI5FRPX.js} +55 -24
- package/dist/{chunk-JY2BWYXY.js → chunk-N6IWPGHB.js} +1814 -1431
- package/dist/{chunk-5BDUMDAQ.cjs → chunk-PH3ESYAF.cjs} +1814 -1431
- package/dist/{chunk-MJTKK3QG.cjs → chunk-SN4BMQ2N.cjs} +31 -10
- package/dist/{index-C7rMJ4IV.d.ts → index-D8FtzUGP.d.ts} +54 -41
- package/dist/{index-C3KFMnRT.d.cts → index-DgBA1JMH.d.cts} +131 -130
- package/dist/{index-C3KFMnRT.d.ts → index-DgBA1JMH.d.ts} +131 -130
- package/dist/{index-Cm_Xpt7X.d.cts → index-YbEk0awP.d.cts} +54 -41
- package/dist/index.cjs +23 -23
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +5 -5
- package/dist/operations/index.cjs +337 -244
- package/dist/operations/index.d.cts +53 -45
- package/dist/operations/index.d.ts +53 -45
- package/dist/operations/index.js +121 -28
- package/dist/pool.cjs +9 -9
- package/dist/pool.d.cts +4 -3
- package/dist/pool.d.ts +4 -3
- package/dist/pool.js +5 -5
- package/dist/resources/index.cjs +5 -5
- package/dist/resources/index.d.cts +2 -2
- package/dist/resources/index.d.ts +2 -2
- package/dist/resources/index.js +4 -4
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/untrusted.cjs +2 -2
- package/dist/untrusted.d.cts +14 -5
- package/dist/untrusted.d.ts +14 -5
- package/dist/untrusted.js +1 -1
- package/docs/API.md +126 -37
- package/examples/mcp/schemas.ts +2 -0
- package/examples/mcp/tools-read.ts +3 -2
- package/package.json +10 -9
package/docs/API.md
CHANGED
|
@@ -685,6 +685,22 @@ carry paging parameters.
|
|
|
685
685
|
when the limit or the guard truncated the walk and more records exist past
|
|
686
686
|
the stop point; `pageDetails.count` is never surfaced (it is the current
|
|
687
687
|
page's record count — `<resource>.count` is the total).
|
|
688
|
+
- **`walk: { maxRows: N }` — an explicit export cap (issue #350).**
|
|
689
|
+
`N` is a positive integer and replaces the total `limit` cap, so
|
|
690
|
+
`{ walk: { maxRows: 10_000 } }` can return 10,000 rows. `limit` still
|
|
691
|
+
validates as 1..2500 (default 500) and only sizes pages in this mode:
|
|
692
|
+
`min(500, limit)`. `truncated` and `hasMore` are true when the final
|
|
693
|
+
page was sliced, a next cursor remains, or the 1000-page guard stopped
|
|
694
|
+
the walk, even below `maxRows`; an exhausted exact-cap result reports
|
|
695
|
+
false. No extra page is fetched to decide completeness. With no `walk`,
|
|
696
|
+
the ordinary cap and result shape remain unchanged (`truncated` is
|
|
697
|
+
omitted). `walk` and `latest` cannot be combined. Through `invoke`, the
|
|
698
|
+
unscoped-walk guard estimates the explicit `maxRows` cap independently
|
|
699
|
+
of `limit`; narrow the filter or pass `acknowledgeUnscoped: true` when
|
|
700
|
+
a large unscoped walk is intended. When names are enabled (per call or
|
|
701
|
+
by client labels), explicit walks apply the existing label budget per
|
|
702
|
+
fetched vendor page, matching the iterator; invoke does not label the
|
|
703
|
+
combined result again. Ordinary invoked queries keep their per-call budget.
|
|
688
704
|
- **`latest: N` (issue #59) — the N highest-`id` rows without caller paging.**
|
|
689
705
|
The vendor has NO sort parameter (the vendored spec's `QueryModel` is
|
|
690
706
|
exactly `maxRecords` / `includeFields` / `filter`), so `latest` is a
|
|
@@ -780,23 +796,28 @@ Vendor filter structure (swagger `Filter`, recursive):
|
|
|
780
796
|
`project.<field>` is resolved before the query: the parent entity is read
|
|
781
797
|
(`IncludeFields: ['id']`, at most 300 rows) with the relation's conditions,
|
|
782
798
|
and the leaves become `<relation>ID in [ids]`. Supported by `queryBounded`
|
|
783
|
-
(and the invoke `query` op), `count
|
|
784
|
-
`query()`/`queryAll()` walks
|
|
799
|
+
(and the invoke `query` op), `count`, `operations.aggregate`, and the raw
|
|
800
|
+
`query()`/`queryAll()` walks.
|
|
785
801
|
- Inside an `and` (and at the top level) all leaves of one relation share ONE
|
|
786
802
|
parent read; inside an `or` each relation leaf is its own read. Direct
|
|
787
803
|
conditions are untouched and may be mixed freely.
|
|
788
804
|
- No parent match: an `and` matches nothing; an `or` drops that branch. When
|
|
789
805
|
the whole filter matches nothing the child call is not issued: `queryBounded`
|
|
790
806
|
returns `{ items: [], fetched: 0, hasMore: false, pagesWalked: 0,
|
|
791
|
-
relationNote }
|
|
807
|
+
relationNote }` (plus `truncated: false` for an explicit `walk`),
|
|
808
|
+
`count` returns `0`, aggregate group/sum carries
|
|
792
809
|
`relationNote`.
|
|
793
810
|
- More than 300 matching parents: typed `AutotaskConfigError`, child call not
|
|
794
811
|
issued (a capped parent set would be vendor-ordered, so partial totals would
|
|
795
812
|
be biased). Narrow the relation conditions (e.g. a ticket date or status).
|
|
796
813
|
- Pre-request (zero wire): unknown relation, a parent field outside the
|
|
797
|
-
parent's vendored field set
|
|
798
|
-
|
|
814
|
+
parent's vendored field set and multi-hop (`ticket.company.x`) are
|
|
815
|
+
refused. The invoke field-name check skips relation
|
|
799
816
|
leaves (they are checked against the parent instead).
|
|
817
|
+
- Any dotted `udf: true` field is refused before metadata, parent queries,
|
|
818
|
+
scope counts or child requests, including self prefixes and unknown
|
|
819
|
+
prefixes (issue #351). Use an undotted tenant UDF name with `udf: true`;
|
|
820
|
+
`fieldCheck: false` does not bypass this rule.
|
|
800
821
|
|
|
801
822
|
```ts
|
|
802
823
|
// company 123's time in October, hours per ticket: 2 calls (tickets ids, then the walk)
|
|
@@ -829,11 +850,20 @@ Vendor filter structure (swagger `Filter`, recursive):
|
|
|
829
850
|
the cap). A GET path exists (`{ via: 'get' }`) and the SDK measures the
|
|
830
851
|
serialized GET URL — over 2048 it throws `QueryTooLongError` (never a
|
|
831
852
|
silent degradation).
|
|
832
|
-
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
853
|
+
- **Own-field aliases (issue #352):** a single self prefix — the resource
|
|
854
|
+
key, or a relation name targeting that resource — is stripped from
|
|
855
|
+
ordinary filter fields, `includeFields`, and aggregate `groupBy` / `sum`.
|
|
856
|
+
On tickets, `ticket.title` and `tickets.title` mean `title`; on time
|
|
857
|
+
entries, `time_entries.hoursWorked` means `hoursWorked`, while
|
|
858
|
+
`ticket.companyID` remains a parent relation filter. Aggregate date
|
|
859
|
+
suffixes are preserved (`time_entries.dateWorked@month` becomes
|
|
860
|
+
`dateWorked@month`), and result keys use the canonical unprefixed names.
|
|
861
|
+
Foreign and multi-hop names are never converted into own-field aliases.
|
|
862
|
+
Projection normalization applies on the first page and every cursor
|
|
863
|
+
continuation, without mutating the caller's input.
|
|
864
|
+
- `includeFields` selects vendor columns after own-field normalization; the
|
|
865
|
+
SDK always appends `id` when omitted (the vendor omits `id` unless it is
|
|
866
|
+
selected, and callers need it to act on the row — issue #59).
|
|
837
867
|
|
|
838
868
|
## 6. Mutation safety
|
|
839
869
|
|
|
@@ -962,7 +992,48 @@ candidates and the error code when a resource could not resolve (empty
|
|
|
962
992
|
the primary text field, exact match only; with `resolution.scanOnMiss` the
|
|
963
993
|
bounded client-scan re-check (and its truncation cap) applies per resource. Returns
|
|
964
994
|
`{ identifier, results: [{ resource, value, resolutionCost, scanned,
|
|
965
|
-
scanTruncated, candidates, error? }], matches }`.
|
|
995
|
+
scanTruncated, candidates, error? }], matches, matched }`.
|
|
996
|
+
|
|
997
|
+
`match` defaults to `'exact'`. With `match: 'auto'`, every requested
|
|
998
|
+
resource finishes its exact lookup before a contains search can start. Only
|
|
999
|
+
a clean miss everywhere permits the contains sweep; an exact hit,
|
|
1000
|
+
ambiguity, truncated scan, unsupported resolver or lookup error suppresses
|
|
1001
|
+
it. Numeric ids and digit strings never fall back to contains. Auth,
|
|
1002
|
+
rate-limit, server, network, timeout and policy faults propagate.
|
|
1003
|
+
|
|
1004
|
+
The contains sweep defaults to the client’s `resolution.maxScanRecords`,
|
|
1005
|
+
capped at 500. Optional `limit` must be an integer 1..500 and can only
|
|
1006
|
+
narrow that configured cap; invalid values refuse before lookup wire.
|
|
1007
|
+
Exact lookup sizes are unchanged. A singleton below the cap resolves; multiple matches
|
|
1008
|
+
report candidate ids, and a cap-full result reports `scanTruncated: true`
|
|
1009
|
+
and `RESOLUTION_TRUNCATED`, even if the cap is one. `matched` reports
|
|
1010
|
+
`'exact'` or `'contains'`; only the latter includes the full sweep in
|
|
1011
|
+
`contains`, including an empty sweep.
|
|
1012
|
+
|
|
1013
|
+
**`resolveStaff(client, identifier, opts?)`** looks up Autotask resources
|
|
1014
|
+
by exact email, userName or name. Text matches require `isActive: true`
|
|
1015
|
+
and exclude API users by default, using tenant picklist labels on
|
|
1016
|
+
`licenseType` / `userType` rather than fixed numeric values. Numeric ids
|
|
1017
|
+
fetch directly and bypass active/API-user exclusions. `emailOnly: true`
|
|
1018
|
+
treats even digit strings as email; `includeApiUsers: true` includes API
|
|
1019
|
+
users in text matches. Options form a closed plain-object bag: flags must be
|
|
1020
|
+
booleans when present and limit must be an integer 1..10. Unknown keys, nulls
|
|
1021
|
+
and malformed options refuse with CONFIG_ERROR before metadata or lookup wire.
|
|
1022
|
+
|
|
1023
|
+
Returns `{ identifier, lookup: 'id' | 'text', matches: [{ record, apiUser?,
|
|
1024
|
+
licenseTypeLabel? }], matchedCount, truncated, metadataState }`. `limit`
|
|
1025
|
+
is 1..10 (default 10); the text lookup reads at most 50 records.
|
|
1026
|
+
`matchedCount` is after exclusions and before the candidate limit, and is
|
|
1027
|
+
a lower bound when truncated. A live cursor or a candidate-limit cut sets
|
|
1028
|
+
`truncated: true`; a truncated singleton never proves uniqueness. Text
|
|
1029
|
+
misses return an empty `matches`; a numeric miss propagates `NOT_FOUND`.
|
|
1030
|
+
`metadataState` is `ok` when API-user labels were found, `none` when labels
|
|
1031
|
+
exist without an API label, `absent` when no labels are exposed, or `error`
|
|
1032
|
+
when metadata could not be read. Lookup continues if metadata is
|
|
1033
|
+
unavailable, and API-user classification is omitted when unproven. Text
|
|
1034
|
+
records honor the existing client `labels` setting through the shared row-label
|
|
1035
|
+
pass; optional `names` reports incomplete label coverage. Numeric gets keep
|
|
1036
|
+
their ordinary record shape.
|
|
966
1037
|
|
|
967
1038
|
### 7.3 `invoke(client, operation, args?, opts?)` — the write governor
|
|
968
1039
|
|
|
@@ -989,7 +1060,8 @@ the mode pinned (§7.4). The governor rules (pinned):
|
|
|
989
1060
|
`queryBounded`** (server-side pagination): ONE invoke call returns a
|
|
990
1061
|
bounded, complete-enough result — the vendor cursor walk happens inside
|
|
991
1062
|
the SDK; the caller passes `limit` as the TOTAL cap (default 500, max
|
|
992
|
-
2500)
|
|
1063
|
+
2500), or an independent `walk.maxRows` export cap (section 4), and never
|
|
1064
|
+
owns a stream. `queryAll` keeps its raw behavior (the
|
|
993
1065
|
unbounded walk; the catalog text steers the caller away for normal work);
|
|
994
1066
|
- a query's `includeFields` NAMES and its `filter` condition FIELD NAMES are
|
|
995
1067
|
validated **pre-request** (issues #119b + #125): the authority is the
|
|
@@ -1059,25 +1131,9 @@ be cross-mode-refused.
|
|
|
1059
1131
|
Pinned by the full-surface suite (`test/operations/invoke/modes.test.ts`:
|
|
1060
1132
|
all 1281 ops × 3 modes, zero wire).
|
|
1061
1133
|
|
|
1062
|
-
|
|
1063
|
-
whose MCP annotation is `readOnlyHint !== true` — today exactly **267
|
|
1064
|
-
|
|
1065
|
-
`effect=destructive`, every one of them has a dedicated tool) +
|
|
1066
|
-
`autotask_write` + `autotask_delete`. The remaining **635 tools** (the 622 dedicated read tools + the 10 core read tools + `autotask_read` +
|
|
1067
|
-
`autotask_catalog` + `autotask_describe`) all carry `readOnlyHint: true`
|
|
1068
|
-
and are safe ungated: every op they can route is `effect=read` (zero
|
|
1069
|
-
mutating requests — pinned by the test inventory, §6 of the split design).
|
|
1070
|
-
Refined class for the highest-risk ops: the **57 tools with
|
|
1071
|
-
`destructiveHint: true`** (56 dedicated delete tools + `autotask_delete`)
|
|
1072
|
-
additionally require `confirm === <operation key>` even inside
|
|
1073
|
-
`autotask_delete`, and the 3 approval-gated writes
|
|
1074
|
-
(`configuration_items.moveConfigurationItem`, `contacts.moveToCompany`,
|
|
1075
|
-
`resources.transferOwnership`) require the same confirm inside
|
|
1076
|
-
`autotask_write`. Migration: `autotask_invoke` is REPLACED by
|
|
1077
|
-
`autotask_read`/`autotask_write`/`autotask_delete` — update harness config
|
|
1078
|
-
(approve the 267 above) and remove any `autotask_invoke` entry. The rule is
|
|
1079
|
-
derivable from the registry `effect` column (and the mode tools' fixed
|
|
1080
|
-
annotations) — it never needs a hand-maintained name list.
|
|
1134
|
+
<!-- BEGIN GENERATED:approval-policy -->
|
|
1135
|
+
**Harness approval policy (recommended).** Require approval on every tool whose MCP annotation is `readOnlyHint !== true` — today exactly **267 tools**: the 265 dedicated write/delete tools (209 `effect=write` + 56 `effect=destructive`, every one of them has a dedicated tool) + `autotask_write` + `autotask_delete`. The remaining **636 tools** (the 623 dedicated read tools + the 10 core read tools + `autotask_read` + `autotask_catalog` + `autotask_describe`) all carry `readOnlyHint: true` and are safe ungated: every op they can route is `effect=read` (zero mutating requests). Refined class for the highest-risk ops: the **57 tools with `destructiveHint: true`** (56 dedicated delete tools + `autotask_delete`) additionally require `confirm === <operation key>` even inside `autotask_delete`. The approval-gated writes (`configuration_items.moveConfigurationItem`, `contacts.moveToCompany`, `resources.transferOwnership`) require the same confirm inside `autotask_write`. Migration: `autotask_invoke` is REPLACED by `autotask_read`/`autotask_write`/`autotask_delete` — update harness config and remove any `autotask_invoke` entry. The rule is derivable from the registry `effect` column and the mode tools’ fixed annotations; it never needs a hand-maintained name list.
|
|
1136
|
+
<!-- END GENERATED:approval-policy -->
|
|
1081
1137
|
|
|
1082
1138
|
### 7.5 `recovery(client, input, opts?)` — the one-call recovery/cleanup op (issue #9)
|
|
1083
1139
|
|
|
@@ -1282,10 +1338,12 @@ wire):
|
|
|
1282
1338
|
vendor casing via `operations.fieldCatalog`). An empty array / absent = the
|
|
1283
1339
|
unfiltered call (the vendor-accepted no-filter shape rides the wire as
|
|
1284
1340
|
`filter: []`; that is also the blind-predicate's stamp case).
|
|
1285
|
-
- **`groupBy` / `sum`** =
|
|
1341
|
+
- **`groupBy` / `sum`** = vendor field names, accepting the single own-field
|
|
1342
|
+
prefix aliases of section 5 (normalized before validation and folding;
|
|
1343
|
+
result keys are unprefixed). Names are validated against the
|
|
1286
1344
|
resource's vendored field set pre-request — a hallucinated field is a shape
|
|
1287
1345
|
error, not a silent fold-miss; the complete problem list in one typed error,
|
|
1288
|
-
zero wire
|
|
1346
|
+
zero wire. Non-numeric/absent `sum` values contribute 0 (documented).
|
|
1289
1347
|
- **`top`** (default 25, clamped 1..100) keeps the top-N groups (descending
|
|
1290
1348
|
count; the deterministic group-key order breaks ties). **`limit`** (default
|
|
1291
1349
|
2500 = the house `BOUNDED_QUERY_MAX_LIMIT`, clamped down to it) is the
|
|
@@ -1709,7 +1767,7 @@ const lob = await revenueRollup(client, { tenantWide: true, dateFrom: '2025-07-0
|
|
|
1709
1767
|
## 8. Resource surface
|
|
1710
1768
|
|
|
1711
1769
|
<!-- BEGIN GENERATED:api-per-entity -->
|
|
1712
|
-
Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page guarded), `queryBounded` (server-side pagination — one call walks the cursor until `limit`
|
|
1770
|
+
Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page guarded), `queryBounded` (server-side pagination — one call walks the cursor until the total cap: `limit` (default 500 / max 2500), or independent `walk.maxRows` for exports (pages stay `min(500, limit)`); `hasMore` reports incomplete walks, and explicit walks also carry `truncated`; the MCP `query` surface, routed by `invoke`), `queryAll` (collect → `T[]` — the unbounded raw walk), `count`, `get` (id → record; `item: null` → `NotFoundError`), `create` / `update` / `delete` (where the vendor defines them — mutations take a `MutationOpts` bag: `{ dryRun, lastModifiedDate?, maxRetries? }` (issue #105: `maxRetries` overrides the configured retry budget for every request the mutation issues; `0` = exactly one send per request)), plus the helper floor where the plan declares it: `resolve` (all 121), `search` (40 — bounded contains on the resource text field), `getContext` (5 workflow resources), `findBy<Field>` (3), `createIfNotExists` (14). Entity metadata on every resource: `getEntityInfo`, `getFieldInfo`, `getUdfInfo` (UDF-capable 16). Parent-scoped children take `(parentId, id)` on the CRUD methods and require `parentId` on `createIfNotExists` (the scoped create POSTs the scoped collection; the dedup query stays global); `query`/`count`/`resolve`/`search` stay global (narrow with a `parentField` filter). Scoped `update` PATCHes the collection (the vendor keys the record by the id in the body).
|
|
1713
1771
|
|
|
1714
1772
|
| resource key | apiEntity | kind | tier | parent | standard ops present | special ops | mcpTool |
|
|
1715
1773
|
|--------------|-----------|------|------|--------|----------------------|-------------|---------|
|
|
@@ -2678,7 +2736,8 @@ metadata is (the `autotask_describe` channel, `capabilities.json`,
|
|
|
2678
2736
|
`dateWorked` window (`from`/`to` are `YYYY-MM-DD` date strings — the S1c
|
|
2679
2737
|
guard forbids Date positionals), one bounded read (`limit` 1-500, default
|
|
2680
2738
|
500 — the hard cap, issue #128: a larger window needs `time_entries.query`
|
|
2681
|
-
with the same `dateWorked` filter
|
|
2739
|
+
with the same `dateWorked` filter and a suitable `limit` or explicit
|
|
2740
|
+
`walk.maxRows` cap, section 4) returning
|
|
2682
2741
|
`{ items, postedCount, unpostedCount, window, nextCursor, unconfirmed, truncated }`; the counts come
|
|
2683
2742
|
from the billing-item join (any billing item referencing the entry), `opts.posted` narrows client-side after the join
|
|
2684
2743
|
(true = posted only, false = unposted only, omitted = both); the counts still split every entry read (before the narrowing), so `items.length` can differ from their sum and `ticketID` /
|
|
@@ -3381,8 +3440,9 @@ additive, and mirrors the family:
|
|
|
3381
3440
|
- **Shape and wire** — each op is exactly ONE bounded query (`MaxRecords`
|
|
3382
3441
|
1-500, default 500 — the hard cap, issue #128: a call without `limit` takes
|
|
3383
3442
|
the first 500 rows in id order; a window with more than 500 matching rows
|
|
3384
|
-
needs the bounded query op with the same date-window filter
|
|
3385
|
-
walk cap; the `itemDate` /
|
|
3443
|
+
needs the bounded query op with the same date-window filter and a suitable
|
|
3444
|
+
`limit` or explicit `walk.maxRows` cap (section 4); the `itemDate` /
|
|
3445
|
+
`invoiceDateTime` `gte`/`lte` filter rides the
|
|
3386
3446
|
wire), and the result is the plain `items[]` array — no counts
|
|
3387
3447
|
(the time-entry family shape).
|
|
3388
3448
|
|
|
@@ -4011,6 +4071,35 @@ truncates first, then wraps) — so the explicit `… [+N chars truncated]`
|
|
|
4011
4071
|
marker survives INSIDE the wrapped value and the closing marker is never
|
|
4012
4072
|
cut off.
|
|
4013
4073
|
|
|
4074
|
+
**`sanitiseResult(client, result, operation)`** exposes that field projection
|
|
4075
|
+
as a zero-wire operations helper. Pass the registry key of the read that
|
|
4076
|
+
produced the result, for example `tickets.query`; an unknown key is a typed
|
|
4077
|
+
`CONFIG_ERROR`. It walks plain objects and arrays, wrapping strings in the
|
|
4078
|
+
operation's `metadata.untrustedFields`, including strings in arrays under
|
|
4079
|
+
those fields. `userDefinedFields[].value` strings are fenced even when the
|
|
4080
|
+
operation has no field stamp. Deeply nested containers are capped at 64
|
|
4081
|
+
levels with `[DEPTH]`.
|
|
4082
|
+
Inputs are never mutated, wrapping is idempotent, and non-string values
|
|
4083
|
+
and `[REDACTED]` pass through. SDK read primitives still return raw values.
|
|
4084
|
+
|
|
4085
|
+
This helper supplies field/UDF fencing. The host retains text cleaning,
|
|
4086
|
+
free-text caps and trusted formatting exemptions; apply those policies in
|
|
4087
|
+
the host before projection where needed. The bounded-query `noteChars`
|
|
4088
|
+
option remains a separate SDK cap applied before wrapping.
|
|
4089
|
+
|
|
4090
|
+
For hosts that need to clean or cap text before wrapping, the
|
|
4091
|
+
`node-autotask/untrusted` subpath exports
|
|
4092
|
+
`deepMarkUntrusted(value, fields, projector?)` and the callback type
|
|
4093
|
+
`UntrustedStringProjector = (text: string, target: object, key: string | number) => string`.
|
|
4094
|
+
The optional callback runs only for marked string values, including UDF
|
|
4095
|
+
values and strings in named arrays. `target` is the copied output
|
|
4096
|
+
container; `key` is its property name or array index, assigned after the
|
|
4097
|
+
callback returns. A host can record provenance in a `WeakMap` keyed by
|
|
4098
|
+
`target` and return the raw text, then clean, cap and wrap those marked
|
|
4099
|
+
values later. Containers exposed to the callback remain copies even if
|
|
4100
|
+
the callback returns unchanged text. Unmarked strings and non-string
|
|
4101
|
+
values do not invoke it; omitting the callback keeps the default wrapping.
|
|
4102
|
+
|
|
4014
4103
|
## 13. Doctor preflight (issue #2)
|
|
4015
4104
|
|
|
4016
4105
|
`runDoctor(client)` (section 1.10) runs the bounded, sequential preflight
|
package/examples/mcp/schemas.ts
CHANGED
|
@@ -160,6 +160,8 @@ export function hitsOutput(): z.ZodType {
|
|
|
160
160
|
/** `resolveAny`: one outcome per requested resource; misses do not throw. */
|
|
161
161
|
export function resolveAnyOutput(): z.ZodType {
|
|
162
162
|
return z.object({
|
|
163
|
+
matched: z.enum(['exact', 'contains']),
|
|
164
|
+
contains: hitsOutput().optional(),
|
|
163
165
|
identifier: z.union([z.string(), z.number()]),
|
|
164
166
|
results: z.array(
|
|
165
167
|
crossDataField('Per-resource outcome: value (compact or null), resolutionCost, scanned, candidates/error on a miss.'),
|
|
@@ -84,7 +84,7 @@ export function registerReadTools({ server, client, dry, untrustedWrap }: ToolCo
|
|
|
84
84
|
// R3 — find a record it cannot name.
|
|
85
85
|
registerCoreRead(
|
|
86
86
|
coreSpec('autotask_resolve_any')!,
|
|
87
|
-
z.object({ identifier: IDENTIFIER, resources: RESOURCES }),
|
|
87
|
+
z.object({ identifier: IDENTIFIER, resources: RESOURCES, match: z.enum(['exact', 'auto']).optional(), limit: z.number().int().min(1).max(500).optional() }),
|
|
88
88
|
resolveAnyOutput,
|
|
89
89
|
(v) => `${(v as { matches: string[] }).matches.length} resource(s) matched the identifier.`,
|
|
90
90
|
);
|
|
@@ -163,6 +163,7 @@ export function projectUntrustedResult(opId: string, value: unknown, fields: rea
|
|
|
163
163
|
case 'operations.resolveAny':
|
|
164
164
|
return {
|
|
165
165
|
...v,
|
|
166
|
+
...(v.contains === undefined ? {} : { contains: projectUntrustedResult('operations.searchAcrossResources', v.contains, fields) }),
|
|
166
167
|
results: (Array.isArray(v.results) ? v.results : []).map((row) =>
|
|
167
168
|
row !== null && typeof row === 'object' && !Array.isArray(row) && 'value' in row
|
|
168
169
|
? { ...(row as Record<string, unknown>), value: deepMarkUntrusted((row as Record<string, unknown>).value, fields) }
|
|
@@ -212,7 +213,7 @@ export async function coreReadCall(client: AutotaskClient, opId: string, args: R
|
|
|
212
213
|
case 'threshold_information.get':
|
|
213
214
|
return { model: await client.thresholdInformation.get() };
|
|
214
215
|
case 'operations.resolveAny':
|
|
215
|
-
return resolveAny(client, args.identifier as number | string, { resources: args.resources as string[] | undefined });
|
|
216
|
+
return resolveAny(client, args.identifier as number | string, { resources: args.resources as string[] | undefined, match: args.match as 'exact' | 'auto' | undefined, limit: args.limit as number | undefined });
|
|
216
217
|
case 'operations.searchAcrossResources':
|
|
217
218
|
return searchAcrossResources(client, args.query as string, {
|
|
218
219
|
resources: args.resources as string[] | undefined,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-autotask",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "Fully-typed TypeScript SDK for the Autotask (Datto) PSA REST API. Zero runtime dependencies. Built for building MCP servers and integrations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -161,18 +161,19 @@
|
|
|
161
161
|
},
|
|
162
162
|
"homepage": "https://github.com/msoukhomlinov/node-autotask#readme",
|
|
163
163
|
"devDependencies": {
|
|
164
|
-
"@eslint/js": "^
|
|
164
|
+
"@eslint/js": "^10.0.1",
|
|
165
165
|
"@modelcontextprotocol/server": "^2.2.0",
|
|
166
|
-
"@types/node": "^
|
|
167
|
-
"@vitest/coverage-v8": "^
|
|
168
|
-
"eslint": "^
|
|
169
|
-
"globals": "^
|
|
166
|
+
"@types/node": "^26.6.6",
|
|
167
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
168
|
+
"eslint": "^10.13.0",
|
|
169
|
+
"globals": "^17.13.0",
|
|
170
170
|
"rimraf": "^6.0.1",
|
|
171
|
-
"tsup": "^
|
|
171
|
+
"tsup": "^9.0.0",
|
|
172
172
|
"tsx": "^4.23.15",
|
|
173
|
-
"typescript": "^
|
|
173
|
+
"typescript": "^6.0.3",
|
|
174
174
|
"typescript-eslint": "^8.18.0",
|
|
175
|
-
"
|
|
175
|
+
"vite": "^8.3.5",
|
|
176
|
+
"vitest": "^5.0.5",
|
|
176
177
|
"zod": "^4.4.3"
|
|
177
178
|
},
|
|
178
179
|
"overrides": {
|