iit-mcp-autotask 0.4.2 → 0.4.3
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 +1 -1
- package/capabilities.json +2 -2
- package/dist/tools/catalog-data.d.ts +1 -1
- package/dist/tools/catalog-data.js +2 -2
- package/dist/tools/composites.js +36 -5
- package/dist/tools/schemas.d.ts +5 -1
- package/dist/tools/schemas.js +7 -1
- package/dist/tools/server.d.ts +2 -2
- package/dist/tools/server.js +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ miss never fails the call (typed `absent`); only a missing root record fails the
|
|
|
32
32
|
| C — dedicated workflow (23) | `autotask_ticket_query` · `autotask_ticket_full_context` · `autotask_ticket_sla_health_check` · `autotask_ticket_find_by_ticket_number` · `autotask_company_full_profile` · `autotask_timeEntry_query` · `autotask_timeEntry_create` · `autotask_quote_get` · `autotask_invoice_get` · `autotask_invoice_markup` · `autotask_configurationItem_profile` · `autotask_configurationItems_find_by_serial` · `autotask_contact_full_profile` · `autotask_contact_find_by_email` · `autotask_project_get_context` · `autotask_serviceCall_query` · `autotask_serviceCall_full_profile` · `autotask_contract_full_profile` · `autotask_contract_query` · `autotask_resource_workload` · `autotask_timeOffRequest_query` · `autotask_company_unbilled_work` · `autotask_aggregate` (names verbatim from the SDK manifest where they exist — case inconsistency included) | Bounded filtered lists, one-call composites, natural-key lookups, the single dedicated write |
|
|
33
33
|
|
|
34
34
|
Machine-readable catalogue: [`capabilities.json`](capabilities.json) · server metadata: [`server.json`](server.json) ·
|
|
35
|
-
per-workflow mapping: [`docs/capability-matrix.md`](docs/capability-matrix.md) · full design: [`docs/architecture.md`](docs/architecture.md).
|
|
35
|
+
per-workflow mapping: [`docs/capability-matrix.md`](docs/capability-matrix.md) · full design: [`docs/architecture.md`](docs/architecture.md) · contract P&L manual recipe (the `autotask_contract_profitability` gap until it ships): [`docs/contract-economics.md`](docs/contract-economics.md).
|
|
36
36
|
|
|
37
37
|
### The 5-META generic layer
|
|
38
38
|
|
package/capabilities.json
CHANGED
|
@@ -539,12 +539,12 @@
|
|
|
539
539
|
},
|
|
540
540
|
{
|
|
541
541
|
"id": "autotask.contract.full_profile",
|
|
542
|
-
"description": "One contract + its services
|
|
542
|
+
"description": "One contract + its services + billing rules + recent charges (50 newest) + service units in one call: parts degrade to typed absent, never a failure; the contract itself missing → NOT_FOUND. Deeper machinery (bundles, adjustments, exclusions): autotask_read; contract list: autotask_contract_query.",
|
|
543
543
|
"mcpPrimitive": "tool",
|
|
544
544
|
"toolName": "autotask_contract_full_profile",
|
|
545
545
|
"type": "read",
|
|
546
546
|
"layer": "C",
|
|
547
|
-
"backingOperation": "contracts.get + contract_services.query + contract_billing_rules.query",
|
|
547
|
+
"backingOperation": "contracts.get + contract_services.query + contract_billing_rules.query + contract_charges.query + contract_service_units.query",
|
|
548
548
|
"annotations": {
|
|
549
549
|
"readOnly": true,
|
|
550
550
|
"idempotent": true,
|
|
@@ -144,7 +144,7 @@ export declare function cacheOptionsForPreset(preset: 'on' | 'off', fieldCacheTt
|
|
|
144
144
|
* fails the boot (or the CI test) instead of quietly shipping a tool surface that no longer
|
|
145
145
|
* describes the SDK behind it.
|
|
146
146
|
*/
|
|
147
|
-
export declare const BUILT_AGAINST_PLAN_HASH = "
|
|
147
|
+
export declare const BUILT_AGAINST_PLAN_HASH = "6547153895b65c577ef59ced6c06361d0763d1299d6048531b8ffa94f77821d3";
|
|
148
148
|
/**
|
|
149
149
|
* Fail fast when the installed SDK's plan has moved out from under this surface.
|
|
150
150
|
* Called at boot (main.ts) and safe to call repeatedly (pure comparison).
|
|
@@ -273,7 +273,7 @@ export const TOOL_DESCRIPTIONS = {
|
|
|
273
273
|
autotask_project_get_context: `One project with its primary context: project + company + opportunity + lead resource. Project phase/task lists do NOT exist on this surface (no such vendor operations) — do not attempt them; charges/notes ride the generic layer (autotask_read). The project record alone: autotask_project_resolve.`,
|
|
274
274
|
autotask_serviceCall_query: `Query ServiceCall records with a JSON filter — the on-site visit queue. Full profile (linked tickets, tasks, assignments): autotask_serviceCall_full_profile. Assignment writes ride autotask_write. Bounded: limit default 50, max 2500. per-call options: see the server instructions.`,
|
|
275
275
|
autotask_serviceCall_full_profile: `One service call + its linked tickets + its tasks (incl. resource assignments). Parts degrade to typed absent (no tasks → NO_TASKS on the assignments part); the service call itself missing → NOT_FOUND. Prefer this before on-site-visit work (assignment writes ride autotask_write).`,
|
|
276
|
-
autotask_contract_full_profile: `One contract + its services
|
|
276
|
+
autotask_contract_full_profile: `One contract + its services + billing rules + recent charges (50 newest) + service units in one call: parts degrade to typed absent, never a failure; the contract itself missing → NOT_FOUND. Deeper machinery (bundles, adjustments, exclusions): autotask_read; contract list: autotask_contract_query.`,
|
|
277
277
|
autotask_contract_query: `Query Contract records with a JSON filter — e.g. active contracts for a company (the SLA & recurring-service view). One contract with its services/units/billing rules: autotask_contract_full_profile. Bounded: limit default 50, max 2500. per-call options: see the server instructions.`,
|
|
278
278
|
autotask_resource_workload: `One resource's availability picture: open assigned tickets + service-call assignments + time-off balances + pending time-off requests. Parts degrade to typed absent; resource missing → NOT_FOUND. Approve/reject: autotask_write. The request list: autotask_timeOffRequest_query. Sensitive.`,
|
|
279
279
|
autotask_timeOffRequest_query: `Query TimeOffRequest records with a JSON filter. Approve/reject ride autotask_write (time_off_requests.approve / .reject — dry-run-first; reject takes a reason). Availability: autotask_resource_workload. Bounded: limit default 50, max 2500. per-call options: see the server instructions.`,
|
|
@@ -339,7 +339,7 @@ export function cacheOptionsForPreset(preset, fieldCacheTtlMs) {
|
|
|
339
339
|
* fails the boot (or the CI test) instead of quietly shipping a tool surface that no longer
|
|
340
340
|
* describes the SDK behind it.
|
|
341
341
|
*/
|
|
342
|
-
export const BUILT_AGAINST_PLAN_HASH = '
|
|
342
|
+
export const BUILT_AGAINST_PLAN_HASH = '6547153895b65c577ef59ced6c06361d0763d1299d6048531b8ffa94f77821d3';
|
|
343
343
|
/**
|
|
344
344
|
* Fail fast when the installed SDK's plan has moved out from under this surface.
|
|
345
345
|
* Called at boot (main.ts) and safe to call repeatedly (pure comparison).
|
package/dist/tools/composites.js
CHANGED
|
@@ -13,7 +13,14 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Parts run as bounded fan-outs over ONE pooled tenant client: the client's own concurrency
|
|
15
15
|
* guard (the agent profile) bounds the in-flight vendor requests; the part list is small and
|
|
16
|
-
* static per tool.
|
|
16
|
+
* static per tool. Wire budget per call: the contract profile is the widest — 1 root read + 4
|
|
17
|
+
* parts in one parallel fan-out (contract_services.query / contract_billing_rules.query /
|
|
18
|
+
* contract_charges.query / contract_service_units.query, each limit 50 → one page), EXCEPT the
|
|
19
|
+
* charges part, which rides the SDK's count-anchored `latest` 50 (the vendor has no sort
|
|
20
|
+
* parameter; newest-first = id-newest, tracking the vendor's createDate/datePurchased) — 1 count
|
|
21
|
+
* + at most 6 page POSTs while the matching set is <= 2500 rows (the SDK's full-set mode); above
|
|
22
|
+
* that the SDK spends its own id-threshold count-probe budget before a <= 6-page walk, and a
|
|
23
|
+
* blind surface takes the plain bounded walk. Common case: at most 11 vendor requests per call.
|
|
17
24
|
*/
|
|
18
25
|
import { NotFoundError } from 'node-autotask';
|
|
19
26
|
import { TICKET_FULL_CONTEXT_INPUT, COMPANY_FULL_PROFILE_INPUT, CONFIGURATION_ITEM_PROFILE_INPUT, PROJECT_GET_CONTEXT_INPUT, QUOTE_GET_INPUT, INVOICE_GET_INPUT, INVOICE_MARKUP_INPUT, CONTACT_FULL_PROFILE_INPUT, SERVICE_CALL_FULL_PROFILE_INPUT, CONTRACT_FULL_PROFILE_INPUT, RESOURCE_WORKLOAD_INPUT, UNBILLED_WORK_INPUT, AGGREGATE_INPUT, COMPOSITE_OUTPUT, resultOutput, aggregateOutput, unbilledWorkOutput, closedInput, } from './schemas.js';
|
|
@@ -25,6 +32,11 @@ import { assertSecretReadPermitted, assertWritePermitted } from '../policy.js';
|
|
|
25
32
|
const TICKET_STATUS_CLOSED = 5;
|
|
26
33
|
/** The cap on a fan-out `in` filter (the task-id join between two parts). */
|
|
27
34
|
const IN_FILTER_CAP = 100;
|
|
35
|
+
/** The charges part's recent-N cap (issue #49): the N newest contract charges, newest first —
|
|
36
|
+
* served through the SDK's count-anchored `latest` (the vendor has no sort parameter). */
|
|
37
|
+
const CHARGES_RECENT_CAP = 50;
|
|
38
|
+
/** The units part's row cap (issue #49). */
|
|
39
|
+
const SERVICE_UNITS_CAP = 50;
|
|
28
40
|
/** Run one part read: a miss/fault degrades to `{ value: null, absent: code }`, never a throw. */
|
|
29
41
|
async function part(ctx, op, args, signal) {
|
|
30
42
|
try {
|
|
@@ -370,12 +382,13 @@ function registerServiceCallFullProfile(server, ctx) {
|
|
|
370
382
|
},
|
|
371
383
|
});
|
|
372
384
|
}
|
|
373
|
-
// ---- autotask_contract_full_profile: the contract, its services, its billing rules
|
|
385
|
+
// ---- autotask_contract_full_profile: the contract, its services, its billing rules,
|
|
386
|
+
// ---- its recent charges (issue #49) and its service units (issue #49) ----
|
|
374
387
|
function registerContractFullProfile(server, ctx) {
|
|
375
388
|
const contractInput = closedInput(CONTRACT_FULL_PROFILE_INPUT);
|
|
376
389
|
registerServerComposite(ctx, server, {
|
|
377
390
|
tool: 'autotask_contract_full_profile',
|
|
378
|
-
title: 'Get a contract with its services and
|
|
391
|
+
title: 'Get a contract with its services, billing rules, recent charges and service units',
|
|
379
392
|
description: ctx.description('autotask_contract_full_profile', 'contracts.get'),
|
|
380
393
|
input: contractInput,
|
|
381
394
|
rootOp: 'contracts.get',
|
|
@@ -383,16 +396,34 @@ function registerContractFullProfile(server, ctx) {
|
|
|
383
396
|
const id = args.id;
|
|
384
397
|
// The declared per-part limits (QUERY_LIMIT: 1-2500, default 50 — schema-validated at the
|
|
385
398
|
// protocol layer); each part reads its own declared argument, not one shared phantom key.
|
|
399
|
+
// The two issue #49 parts carry NO input knobs (the closed bag is unchanged): the charges
|
|
400
|
+
// part is the FIXED recent-N and the units part the fixed row cap.
|
|
386
401
|
const serviceLimit = args.service_limit ?? 50;
|
|
387
402
|
const billingRuleLimit = args.billing_rule_limit ?? 50;
|
|
388
403
|
const root = await c.ops.invokeRead(c.client, 'contracts.get', [id], undefined, signal);
|
|
389
404
|
if (root === undefined)
|
|
390
405
|
return { root: null, rootOp: 'contracts.get', parts: {} };
|
|
391
|
-
const [services, billingRules] = await Promise.all([
|
|
406
|
+
const [services, billingRules, charges, units] = await Promise.all([
|
|
392
407
|
part(c, 'contract_services.query', [{ filter: [{ field: 'contractID', op: 'eq', value: id }], limit: serviceLimit }], signal),
|
|
393
408
|
part(c, 'contract_billing_rules.query', [{ filter: [{ field: 'contractID', op: 'eq', value: id }], limit: billingRuleLimit }], signal),
|
|
409
|
+
// The recent charges (issue #49): the vendor has NO sort parameter — the SDK's
|
|
410
|
+
// count-anchored `latest` (the N highest-id rows, id-descending) is the only ordering
|
|
411
|
+
// channel; the id order tracks the vendor's createDate/datePurchased, so newest-first
|
|
412
|
+
// is id-newest-first. limit === latest is the legal coexistence (limit >= latest) and
|
|
413
|
+
// keeps the walk window explicit (1 count + at most 6 page POSTs).
|
|
414
|
+
part(c, 'contract_charges.query', [{ filter: [{ field: 'contractID', op: 'eq', value: id }], limit: CHARGES_RECENT_CAP, latest: CHARGES_RECENT_CAP }], signal),
|
|
415
|
+
part(c, 'contract_service_units.query', [{ filter: [{ field: 'contractID', op: 'eq', value: id }], limit: SERVICE_UNITS_CAP }], signal),
|
|
394
416
|
]);
|
|
395
|
-
return {
|
|
417
|
+
return {
|
|
418
|
+
root,
|
|
419
|
+
rootOp: 'contracts.get',
|
|
420
|
+
parts: {
|
|
421
|
+
services: ['contract_services.query', services],
|
|
422
|
+
billing_rules: ['contract_billing_rules.query', billingRules],
|
|
423
|
+
charges: ['contract_charges.query', charges],
|
|
424
|
+
units: ['contract_service_units.query', units],
|
|
425
|
+
},
|
|
426
|
+
};
|
|
396
427
|
},
|
|
397
428
|
});
|
|
398
429
|
}
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -385,7 +385,11 @@ export declare function queryEnvelope(fields?: readonly string[]): z.ZodType;
|
|
|
385
385
|
/** The five bounded query tools' output: the typed envelope under `result` (mirrors `resultOutput`). */
|
|
386
386
|
export declare function queryEnvelopeOutput(fields?: readonly string[]): z.ZodType;
|
|
387
387
|
/** A composite answer (SDK or server-built): the root record + one entry per part; a part miss
|
|
388
|
-
* is `null + absent`, never a tool failure — only a missing ROOT fails the composite.
|
|
388
|
+
* is `null + absent`, never a tool failure — only a missing ROOT fails the composite.
|
|
389
|
+
* The part names are tool-specific (this shared envelope stays a record): the contract
|
|
390
|
+
* profile serves `services` / `billing_rules` / `charges` (the recent-50 via the SDK's
|
|
391
|
+
* count-anchored `latest`, newest first — issue #49) / `units` (issue #49), every one riding
|
|
392
|
+
* the same value/absent envelope. */
|
|
389
393
|
export declare const COMPOSITE_OUTPUT: z.ZodObject<{
|
|
390
394
|
root: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
391
395
|
parts: z.ZodRecord<z.ZodString, z.ZodObject<{
|
package/dist/tools/schemas.js
CHANGED
|
@@ -408,6 +408,8 @@ export const CONTRACT_FULL_PROFILE_INPUT = z.object({
|
|
|
408
408
|
id: ID.describe('The numeric contract id.'),
|
|
409
409
|
service_limit: QUERY_LIMIT,
|
|
410
410
|
billing_rule_limit: QUERY_LIMIT,
|
|
411
|
+
// issue #49: the two new parts (charges — the FIXED recent-50 via the SDK's `latest`; units —
|
|
412
|
+
// the fixed 50-row cap) carry NO input keys: the closed bag above is unchanged.
|
|
411
413
|
});
|
|
412
414
|
export const RESOURCE_WORKLOAD_INPUT = z.object({
|
|
413
415
|
id: ID.describe('The numeric resource id.'),
|
|
@@ -588,7 +590,11 @@ export function queryEnvelopeOutput(fields = []) {
|
|
|
588
590
|
return z.object({ result: queryEnvelope(fields) });
|
|
589
591
|
}
|
|
590
592
|
/** A composite answer (SDK or server-built): the root record + one entry per part; a part miss
|
|
591
|
-
* is `null + absent`, never a tool failure — only a missing ROOT fails the composite.
|
|
593
|
+
* is `null + absent`, never a tool failure — only a missing ROOT fails the composite.
|
|
594
|
+
* The part names are tool-specific (this shared envelope stays a record): the contract
|
|
595
|
+
* profile serves `services` / `billing_rules` / `charges` (the recent-50 via the SDK's
|
|
596
|
+
* count-anchored `latest`, newest first — issue #49) / `units` (issue #49), every one riding
|
|
597
|
+
* the same value/absent envelope. */
|
|
592
598
|
export const COMPOSITE_OUTPUT = z.object({
|
|
593
599
|
root: dataField('The root record (a missing root fails the call with the typed vendor error).'),
|
|
594
600
|
parts: z
|
package/dist/tools/server.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { Config } from '../config.js';
|
|
|
24
24
|
import type { Logger } from '../logger.js';
|
|
25
25
|
/** The wire identity of this server (the initialize answer + the `/metadata` payload). */
|
|
26
26
|
export declare const SERVER_NAME = "iit-mcp-autotask";
|
|
27
|
-
export declare const SERVER_VERSION = "0.4.
|
|
27
|
+
export declare const SERVER_VERSION = "0.4.3";
|
|
28
28
|
export declare const SERVER_TITLE = "Autotask MCP Server";
|
|
29
29
|
/**
|
|
30
30
|
* The MCP protocol era this server advertises (the `/metadata` stamp, both levels).
|
|
@@ -62,7 +62,7 @@ export declare const SERVER_INSTRUCTIONS: string;
|
|
|
62
62
|
/** The full identity stamp (the `/metadata` build block — the era pair, FR-2). */
|
|
63
63
|
export declare const SERVER_IDENTITY: {
|
|
64
64
|
readonly name: "iit-mcp-autotask";
|
|
65
|
-
readonly version: "0.4.
|
|
65
|
+
readonly version: "0.4.3";
|
|
66
66
|
readonly mcpProtocolBaseline: "2026-07-28";
|
|
67
67
|
readonly legacyProtocolFallback: "2025-11-25";
|
|
68
68
|
};
|
package/dist/tools/server.js
CHANGED
|
@@ -27,7 +27,7 @@ import { UNTRUSTED_NOTE } from './catalog-data.js';
|
|
|
27
27
|
import { registerTools } from './register.js';
|
|
28
28
|
/** The wire identity of this server (the initialize answer + the `/metadata` payload). */
|
|
29
29
|
export const SERVER_NAME = 'iit-mcp-autotask';
|
|
30
|
-
export const SERVER_VERSION = '0.4.
|
|
30
|
+
export const SERVER_VERSION = '0.4.3';
|
|
31
31
|
export const SERVER_TITLE = 'Autotask MCP Server';
|
|
32
32
|
/**
|
|
33
33
|
* The MCP protocol era this server advertises (the `/metadata` stamp, both levels).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iit-mcp-autotask",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "MCP server for the Autotask (Datto) PSA domain, built on the node-autotask SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Max Soukhomlinov",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@modelcontextprotocol/server": "^2.2.0",
|
|
50
|
-
"node-autotask": "0.
|
|
50
|
+
"node-autotask": "0.11.0",
|
|
51
51
|
"zod": "^4.4.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
package/server.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iit-mcp-autotask",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "MCP server for the Autotask (Datto) PSA API, powered by the node-autotask SDK",
|
|
5
5
|
"mcpProtocolBaseline": "2026-07-28",
|
|
6
6
|
"legacyProtocolFallback": "2025-11-25",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"capabilitiesManifest": "capabilities.json",
|
|
30
30
|
"sdk": {
|
|
31
31
|
"package": "node-autotask",
|
|
32
|
-
"versionRange": "0.
|
|
32
|
+
"versionRange": "0.11.0"
|
|
33
33
|
},
|
|
34
34
|
"vendorExtensions": false
|
|
35
35
|
}
|