iit-mcp-autotask 0.13.1 → 0.15.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/README.md +9 -2
- package/dist/tools/dispatch.js +16 -2
- package/dist/tools/meta.js +9 -2
- package/dist/tools/schemas.d.ts +20 -5
- package/dist/tools/schemas.js +39 -6
- package/dist/tools/server-version.d.ts +1 -1
- package/dist/tools/server-version.js +1 -1
- package/dist/tools/server.d.ts +1 -1
- package/package.json +1 -1
- package/server.json +1 -1
- package/skills/filter-semantics.md +1 -1
package/README.md
CHANGED
|
@@ -40,11 +40,18 @@ its contract, `autotask_read` runs it.
|
|
|
40
40
|
`include_api_users: true`; `by: "email"` narrows to the email field; a numeric identifier is looked up
|
|
41
41
|
as a Resource id); compact rows carry `apiUser` and the licence label when the tenant's labels tell it, never an email. Every match carries `ref`, its human reference. A sweep returns compact matches (`resource`, `ref`, `id`, the human fields) unless `expand: true` (the full record under `record`); `limit` caps the TOTAL matches across resources (resource order, then the SDK's order) and a contains sweep reports `truncated` and per-resource `counts {returned, found}`.
|
|
42
42
|
- `autotask_query` is the bounded filtered list over `resource`: `tickets`, `time_entries`,
|
|
43
|
-
`service_calls`, `contracts`, `time_off_requests` (limit default 50, max 2500).
|
|
43
|
+
`service_calls`, `contracts`, `time_off_requests` (limit default 50, max 2500). The served filter
|
|
44
|
+
schema declares each condition as `{field, op, value}` strings, so a client that builds one type
|
|
45
|
+
per array column writes `"29"`, `"true"`, `"2026-10-22"`; numbers, `in` arrays and and/or groups are
|
|
46
|
+
still accepted and passed through unchanged.
|
|
44
47
|
- `autotask_read` runs any read by exact key, or up to 10 at once (`items`). Unbilled work
|
|
45
48
|
(`companies.unbilledWork`), contract profitability (`contracts.profitability`), aggregates
|
|
46
49
|
(`operations.aggregate`) and SLA health (`tickets.slaHealthCheck`) run here; `autotask_guide`
|
|
47
|
-
holds their recipes.
|
|
50
|
+
holds their recipes. Its inputs are generic (`operation`, `args`, `items`): no operation-specific
|
|
51
|
+
fields, because form-filling clients (Copilot Studio) fill every optional input they are shown. A
|
|
52
|
+
client that cannot send a mixed-type array asks for a time entry window over a resource with
|
|
53
|
+
`autotask_query` `time_entries` (`resourceID` eq, `dateWorked` gte/lte, every value a string: the
|
|
54
|
+
served filter schema types `value` as a string). `autotask_read` and `autotask_fetch_many` answer ONE envelope (the next minor,
|
|
48
55
|
breaking): `data = { results: Slot[], ok, failed, budget?, budgetWarning? }`, an array in input
|
|
49
56
|
order even for a single read (`data.results[0].result`). A slot is `{ ok: true, operation, result,
|
|
50
57
|
windowNote? }` or `{ ok: false, operation, error }`; fetch_many slots also echo `resource` and `id`
|
package/dist/tools/dispatch.js
CHANGED
|
@@ -65,11 +65,19 @@ export class NeedsInputError extends Error {
|
|
|
65
65
|
export async function runTool(ctx, input, rawArgs, extra, work) {
|
|
66
66
|
return stamp(ctx.correlationId, await frame(ctx, input, rawArgs, extra, work));
|
|
67
67
|
}
|
|
68
|
+
/** `raw` without the keys `ignored` names (a copy; `raw` itself unchanged), or `raw` as is when there are none. */
|
|
69
|
+
function dropKeys(raw, ignored) {
|
|
70
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
71
|
+
return raw;
|
|
72
|
+
const keys = ignored(raw);
|
|
73
|
+
return keys.length === 0 ? raw : Object.fromEntries(Object.entries(raw).filter(([k]) => !keys.includes(k)));
|
|
74
|
+
}
|
|
68
75
|
async function frame(ctx, input, rawArgs, extra, work) {
|
|
69
|
-
const
|
|
76
|
+
const raw = dropKeys(rawArgs ?? {}, input.ignored);
|
|
77
|
+
const closed = closedArgsError(raw, input.validKeys, ctx.correlationId);
|
|
70
78
|
if (closed !== null)
|
|
71
79
|
return closed;
|
|
72
|
-
const parsed = input.schema.safeParse(
|
|
80
|
+
const parsed = input.schema.safeParse(raw);
|
|
73
81
|
if (!parsed.success) {
|
|
74
82
|
const issue = parsed.error.issues[0];
|
|
75
83
|
const field = issue === undefined ? undefined : issue.path.map(String).join('.');
|
|
@@ -198,6 +206,12 @@ export async function readOneItem(ctx, operation, args, signal) {
|
|
|
198
206
|
// No params bag at all (args omitted / []): the same bounded default (#83); the SDK's own bag-less form is unbounded.
|
|
199
207
|
if (operation.endsWith('.query') && cleanArgs[0] === undefined)
|
|
200
208
|
cleanArgs[0] = { limit: QUERY_LIMIT.parse(undefined) };
|
|
209
|
+
const given = cleanArgs[0];
|
|
210
|
+
// #173: a digit-string limit (a client that writes every value as text, e.g. Copilot Studio's
|
|
211
|
+
// limit: "5") is that number; the SDK still range-checks it.
|
|
212
|
+
if (given !== null && typeof given === 'object' && !Array.isArray(given) && typeof given.limit === 'string' && /^\d{1,9}$/.test(given.limit)) {
|
|
213
|
+
cleanArgs[0] = { ...given, limit: Number(given.limit) };
|
|
214
|
+
}
|
|
201
215
|
const bag = cleanArgs[0];
|
|
202
216
|
if (bag !== null && typeof bag === 'object' && !Array.isArray(bag)) {
|
|
203
217
|
assertFilterShape(bag.filter, operation);
|
package/dist/tools/meta.js
CHANGED
|
@@ -24,7 +24,7 @@ import { crossResourceUntrusted, honestReadError, markResult, ok, typedErrorPayl
|
|
|
24
24
|
import { bm25Rank } from './rank.js';
|
|
25
25
|
import { playbooksFor } from './guide.js';
|
|
26
26
|
import { SERVER_NAME, SERVER_VERSION } from './server-version.js';
|
|
27
|
-
import { CATALOG_INPUT, DESCRIBE_INPUT, COMPANY_FULL_PROFILE_INPUT, CONFIGURATION_ITEM_PROFILE_INPUT, CONTACT_FULL_PROFILE_INPUT, CONTRACT_FULL_PROFILE_INPUT, FETCH_MANY_INPUT, INVOICE_GET_INPUT, PROJECT_GET_CONTEXT_INPUT, QUERY_INPUT, QUOTE_GET_INPUT, RESOLVE_INPUT, RESOURCE_WORKLOAD_INPUT, SERVICE_CALL_FULL_PROFILE_INPUT, TICKET_FULL_CONTEXT_INPUT, QUERY_LIMIT, READ_INPUT, TENANT_STATUS_INPUT, TENANT_STATUS_PARTS, closedInput, } from './schemas.js';
|
|
27
|
+
import { CATALOG_INPUT, DESCRIBE_INPUT, COMPANY_FULL_PROFILE_INPUT, CONFIGURATION_ITEM_PROFILE_INPUT, CONTACT_FULL_PROFILE_INPUT, CONTRACT_FULL_PROFILE_INPUT, FETCH_MANY_INPUT, INVOICE_GET_INPUT, PROJECT_GET_CONTEXT_INPUT, QUERY_INPUT, QUOTE_GET_INPUT, RESOLVE_INPUT, RESOURCE_WORKLOAD_INPUT, SERVICE_CALL_FULL_PROFILE_INPUT, TICKET_FULL_CONTEXT_INPUT, QUERY_LIMIT, READ_INPUT, TENANT_STATUS_INPUT, TENANT_STATUS_PARTS, readIgnoredKeys, closedInput, } from './schemas.js';
|
|
28
28
|
import { refusalReason } from '../policy.js';
|
|
29
29
|
/** Search text per catalog row (key words + purpose), built once. */
|
|
30
30
|
const ROW_TEXT = CATALOG.catalog.map((r) => `${r.operation} ${r.operation.replace(/[._]/g, ' ')} ${r.purpose ?? ''}`);
|
|
@@ -250,6 +250,13 @@ async function guidanceFor(ctx, rec, reachable) {
|
|
|
250
250
|
example: `autotask_read {operation:"${rec.id}", args:[{filter:[{field:"id",op:"gt",value:0}],limit:10}]}`,
|
|
251
251
|
};
|
|
252
252
|
}
|
|
253
|
+
if (rec.id === 'time_entries.dateWorkedWindow' || rec.id === 'time_entries.postedWithin') {
|
|
254
|
+
// #173: the positional [from, to, opts] mixes types; the same window as one uniform-string filter
|
|
255
|
+
return {
|
|
256
|
+
exampleArgs: ['2026-10-20', '2026-10-22', { resourceID: 123 }],
|
|
257
|
+
sameWindowAsQuery: 'autotask_query {resource:"time_entries", filter:[{field:"resourceID",op:"eq",value:"123"},{field:"dateWorked",op:"gte",value:"2026-10-20"},{field:"dateWorked",op:"lte",value:"2026-10-22"}], limit:500}: the same rows up to limit (the window caps at 500; dateWorked is a date, both days inclusive), without the posted counts, for a client that cannot send a mixed-type array.',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
253
260
|
if (op === 'moveToTicket') {
|
|
254
261
|
// the move ops (#107): zero wire, a pointer to the governed path
|
|
255
262
|
return {
|
|
@@ -311,7 +318,7 @@ async function describeRecord(ctx, operation, viaTool) {
|
|
|
311
318
|
export function buildRead(ctx) {
|
|
312
319
|
return {
|
|
313
320
|
title: 'Run Autotask read operations by exact key',
|
|
314
|
-
input: closedInput(READ_INPUT),
|
|
321
|
+
input: closedInput(READ_INPUT, readIgnoredKeys),
|
|
315
322
|
run: async (raw, signal) => {
|
|
316
323
|
const args = raw;
|
|
317
324
|
if ((args.operation === undefined) === (args.items === undefined)) {
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -48,14 +48,18 @@ export interface ClosedInput {
|
|
|
48
48
|
input: StandardSchemaWithJSON<Record<string, unknown>, Record<string, unknown>>;
|
|
49
49
|
schema: z.ZodType;
|
|
50
50
|
validKeys: readonly string[];
|
|
51
|
+
/** The raw top-level keys to drop before the closed-key check (none for most tools). */
|
|
52
|
+
ignored: (raw: Record<string, unknown>) => readonly string[];
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* Wrap a zod object schema so its top level is CLOSED and the SDK never rejects it:
|
|
54
56
|
* - the ADVERTISED wire schema is the object's JSON Schema with `additionalProperties: false`;
|
|
55
57
|
* - `validate` returns the raw arguments unchanged — the call frame runs the closed-key check and
|
|
56
58
|
* the zod parse, so every refusal is an envelope (M1), never the SDK's plain-text error.
|
|
59
|
+
* `ignored(raw)` names raw keys dropped before the closed-key check (never advertised, never named
|
|
60
|
+
* as valid keys).
|
|
57
61
|
*/
|
|
58
|
-
export declare function closedInput(schema: z.ZodType): ClosedInput;
|
|
62
|
+
export declare function closedInput(schema: z.ZodType, ignored?: ClosedInput['ignored']): ClosedInput;
|
|
59
63
|
/**
|
|
60
64
|
* The envelope as served: every field typed only. The `status` / `next` values are listed once,
|
|
61
65
|
* in the server instructions (that is the byte budget: the zod form with enums and bounds costs
|
|
@@ -210,13 +214,24 @@ export declare const DESCRIBE_INPUT: z.ZodObject<{
|
|
|
210
214
|
resource: z.ZodOptional<z.ZodString>;
|
|
211
215
|
}, z.core.$strip>;
|
|
212
216
|
export declare const READ_MAX_ITEMS = 10;
|
|
217
|
+
/**
|
|
218
|
+
* 0.15.0: autotask_read carries no operation-specific inputs. The six flat window inputs of 0.14.x
|
|
219
|
+
* are gone from the served schema (a form-filling client fills every optional it is shown, e.g. a
|
|
220
|
+
* stray limit: 5 on a resources.query), but a client still holding the 0.14.x tool list may send
|
|
221
|
+
* them. On any other read they are dropped (they never meant anything there). Beside a window key
|
|
222
|
+
* (top level or in items) a set one is NOT dropped, so the closed-key check refuses it naming the
|
|
223
|
+
* key: dropping a resourceID or ticketID there would silently widen the window. null or '' is unset.
|
|
224
|
+
* ponytail: drop this list once no 0.14.x tool list can be cached (a later minor).
|
|
225
|
+
*/
|
|
226
|
+
export declare const READ_RETIRED_KEYS: readonly ["from", "to", "posted", "limit", "ticketID", "resourceID"];
|
|
227
|
+
export declare function readIgnoredKeys(raw: Record<string, unknown>): readonly string[];
|
|
213
228
|
export declare const READ_INPUT: z.ZodObject<{
|
|
214
|
-
operation: z.ZodOptional<z.ZodString>;
|
|
215
|
-
args: z.ZodDefault<z.ZodArray<z.ZodUnknown
|
|
216
|
-
items: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
229
|
+
operation: z.ZodPreprocess<z.ZodOptional<z.ZodString>, unknown>;
|
|
230
|
+
args: z.ZodPreprocess<z.ZodDefault<z.ZodArray<z.ZodUnknown>>, unknown>;
|
|
231
|
+
items: z.ZodPreprocess<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
217
232
|
operation: z.ZodString;
|
|
218
233
|
args: z.ZodDefault<z.ZodArray<z.ZodUnknown>>;
|
|
219
|
-
}, z.core.$strict
|
|
234
|
+
}, z.core.$strict>>>, unknown>;
|
|
220
235
|
}, z.core.$strip>;
|
|
221
236
|
export declare const RESOLVE_INPUT: z.ZodObject<{
|
|
222
237
|
identifier: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
|
package/dist/tools/schemas.js
CHANGED
|
@@ -65,8 +65,18 @@ const COMPANY_PROFILE_PART_LIMIT_LOCATIONS = z
|
|
|
65
65
|
* The JSON filter (pass-through for WELL-FORMED shapes: the condition grammar — 13 operators +
|
|
66
66
|
* and/or — is validated by the SDK pre-request; unsupported TOP-LEVEL shapes are refused by this
|
|
67
67
|
* server pre-request, see {@link assertFilterShape}).
|
|
68
|
+
*
|
|
69
|
+
* #173: the ADVERTISED schema types a condition's `value` as a string, so a client that builds one
|
|
70
|
+
* column type per array (Copilot Studio's Table) gets a uniform string column instead of inferring
|
|
71
|
+
* one from mixed values; the runtime stays pass-through (numbers, booleans, in/notIn arrays and
|
|
72
|
+
* and/or groups are accepted unchanged, nothing is converted). Only field/op/value are advertised:
|
|
73
|
+
* a form-filling client fills every property it is shown, and a filled `udf` or `items` would change
|
|
74
|
+
* the query.
|
|
68
75
|
*/
|
|
69
|
-
const FILTER = z
|
|
76
|
+
const FILTER = z
|
|
77
|
+
.unknown()
|
|
78
|
+
.meta({ type: 'array', items: { type: 'object', properties: { field: { type: 'string' }, op: { type: 'string' }, value: { type: 'string' } } } })
|
|
79
|
+
.describe('Conditions {field, op, value} (grammar: server instructions); write every value as a string: "29", "true", "2026-10-22" (numbers also work; in/notIn take an array, and/or groups take items). Field names are pre-validated against the field catalog: an unknown name is a CONFIG_ERROR with the field-catalog pointer, zero QUERY wire (a cold field cache makes one vendor metadata GET first); one udf:true condition per query is exempt.');
|
|
70
80
|
/**
|
|
71
81
|
* The pre-dispatch filter-shape gate (#18): accepts the two top-level shapes the pinned SDK
|
|
72
82
|
* understands — a conditions array, or a params bag whose `filter` field is a conditions array
|
|
@@ -100,8 +110,10 @@ export function assertEnrichApplicable(resource, keys, operation) {
|
|
|
100
110
|
* - the ADVERTISED wire schema is the object's JSON Schema with `additionalProperties: false`;
|
|
101
111
|
* - `validate` returns the raw arguments unchanged — the call frame runs the closed-key check and
|
|
102
112
|
* the zod parse, so every refusal is an envelope (M1), never the SDK's plain-text error.
|
|
113
|
+
* `ignored(raw)` names raw keys dropped before the closed-key check (never advertised, never named
|
|
114
|
+
* as valid keys).
|
|
103
115
|
*/
|
|
104
|
-
export function closedInput(schema) {
|
|
116
|
+
export function closedInput(schema, ignored = () => []) {
|
|
105
117
|
const advertised = z.toJSONSchema(schema, { target: 'draft-2020-12', io: 'input' });
|
|
106
118
|
const validKeys = Object.keys(advertised.properties ?? {});
|
|
107
119
|
const input = {
|
|
@@ -119,7 +131,7 @@ export function closedInput(schema) {
|
|
|
119
131
|
},
|
|
120
132
|
},
|
|
121
133
|
};
|
|
122
|
-
return { input, schema, validKeys };
|
|
134
|
+
return { input, schema, validKeys, ignored };
|
|
123
135
|
}
|
|
124
136
|
// ---------------------------------------------------------------------------
|
|
125
137
|
// The served output schema: the compact result envelope (one per tool)
|
|
@@ -203,10 +215,31 @@ export const DESCRIBE_INPUT = z.object({
|
|
|
203
215
|
});
|
|
204
216
|
const READ_ITEM = z.strictObject({ operation: OP_KEY.describe('Exact read key.'), args: ARGS });
|
|
205
217
|
export const READ_MAX_ITEMS = 10;
|
|
218
|
+
/**
|
|
219
|
+
* An autotask_read input sent as null or '' is absent: form-filling clients (Copilot Studio) can
|
|
220
|
+
* send every unset optional that way. The advertised schema stays the plain type (no null union).
|
|
221
|
+
*/
|
|
222
|
+
const unset = (schema) => z.preprocess((v) => (v === null || v === '' ? undefined : v), schema);
|
|
223
|
+
/**
|
|
224
|
+
* 0.15.0: autotask_read carries no operation-specific inputs. The six flat window inputs of 0.14.x
|
|
225
|
+
* are gone from the served schema (a form-filling client fills every optional it is shown, e.g. a
|
|
226
|
+
* stray limit: 5 on a resources.query), but a client still holding the 0.14.x tool list may send
|
|
227
|
+
* them. On any other read they are dropped (they never meant anything there). Beside a window key
|
|
228
|
+
* (top level or in items) a set one is NOT dropped, so the closed-key check refuses it naming the
|
|
229
|
+
* key: dropping a resourceID or ticketID there would silently widen the window. null or '' is unset.
|
|
230
|
+
* ponytail: drop this list once no 0.14.x tool list can be cached (a later minor).
|
|
231
|
+
*/
|
|
232
|
+
export const READ_RETIRED_KEYS = ['from', 'to', 'posted', 'limit', 'ticketID', 'resourceID'];
|
|
233
|
+
const WINDOW_OPS = ['time_entries.dateWorkedWindow', 'time_entries.postedWithin'];
|
|
234
|
+
export function readIgnoredKeys(raw) {
|
|
235
|
+
const items = Array.isArray(raw.items) ? raw.items : [];
|
|
236
|
+
const window = WINDOW_OPS.includes(raw.operation) || items.some((i) => typeof i === 'object' && i !== null && WINDOW_OPS.includes(i.operation));
|
|
237
|
+
return READ_RETIRED_KEYS.filter((k) => k in raw && (!window || raw[k] === null || raw[k] === ''));
|
|
238
|
+
}
|
|
206
239
|
export const READ_INPUT = z.object({
|
|
207
|
-
operation: OP_KEY.optional().describe('Exact read key, e.g. tickets.get (autotask_catalog finds it).'),
|
|
208
|
-
args: ARGS,
|
|
209
|
-
items: z.array(READ_ITEM).min(1).max(READ_MAX_ITEMS).optional().describe('Up to 10 reads in one call, each { operation, args }; use instead of operation.'),
|
|
240
|
+
operation: unset(OP_KEY.optional()).describe('Exact read key, e.g. tickets.get (autotask_catalog finds it).'),
|
|
241
|
+
args: unset(ARGS),
|
|
242
|
+
items: unset(z.array(READ_ITEM).min(1).max(READ_MAX_ITEMS).optional()).describe('Up to 10 reads in one call, each { operation, args }; use instead of operation.'),
|
|
210
243
|
});
|
|
211
244
|
export const RESOLVE_INPUT = z.object({
|
|
212
245
|
identifier: z.union([z.number().int().nonnegative(), z.string().min(1).max(200)]).describe('Numeric id, name, ticket number, serial or email. resources: numeric identifiers are looked up as Resource ids.'),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** The wire identity of this server: a leaf module (no imports) so any tool can read it without an import cycle. */
|
|
2
2
|
export const SERVER_NAME = 'iit-mcp-autotask';
|
|
3
|
-
export const SERVER_VERSION = '0.
|
|
3
|
+
export const SERVER_VERSION = '0.15.0';
|
|
4
4
|
//# sourceMappingURL=server-version.js.map
|
package/dist/tools/server.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export declare function serverInstructions(config: Pick<Config, 'AUTOTASK_PROFIL
|
|
|
60
60
|
/** The full identity stamp (the `/metadata` build block — the era pair, FR-2). */
|
|
61
61
|
export declare const SERVER_IDENTITY: {
|
|
62
62
|
readonly name: "iit-mcp-autotask";
|
|
63
|
-
readonly version: "0.
|
|
63
|
+
readonly version: "0.15.0";
|
|
64
64
|
readonly mcpProtocolBaseline: "2026-07-28";
|
|
65
65
|
readonly legacyProtocolFallback: "2025-11-25";
|
|
66
66
|
};
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -28,7 +28,7 @@ No inputs: this is a reference page about the filter behaviour of autotask_query
|
|
|
28
28
|
3. tickets.search is a vendor contains on the TITLE only: no free-text search, no matched-field reporting, and vendor contains is looser than a literal substring (case/token matching can return MORE hits than a local title-substring check). The per-field contains of tickets.query is the precise tool.
|
|
29
29
|
4. Unassigned FKs are not uniform. tickets store 0; time_entries store NULL. Unallocated time needs notExist (ticketID eq 0 finds nothing); assignedResourceID eq 0 and notExist differ.
|
|
30
30
|
5. Field names are vendor casing, and an unknown field name now fails pre-dispatch as a typed CONFIG_ERROR with the field-catalog pointer, with zero query wire (0.20.0 pre-validation), on all five autotask_query operations (tickets.query, time_entries.query, service_calls.query, contracts.query, time_off_requests.query). One udf: true condition per query is exempt from the name check. List names for the queried resource with the scoped call `autotask_read {operation:"operations.fieldCatalog", args:[{resources:["<resource>"]}]}` (add `fields: [names]` to project; a scoped call also returns picklist labels by default on this server). Never the bare `operations.fieldCatalog`: it serves the whole five-resource catalog (~85 KB) and has no row for time_entries, service_calls, contracts or time_off_requests. Do not guess names. The answer is `data.results[0].result`.
|
|
31
|
-
6. Some clients (Copilot Studio) fail locally, before any request, when
|
|
31
|
+
6. Some clients (Copilot Studio) fail locally, before any request, when one array mixes value types (a number beside a string in one filter, or dateWorkedWindow args: strings plus an object). The autotask_query filter declares value as a string, so write every value as a string ("<resource id>", "2026-10-22", "true"); numbers still work for clients that send them. For a time entry window over a resource or ticket, query time entries instead of dateWorkedWindow: `autotask_query {resource:"time_entries", filter:[{field:"resourceID",op:"eq",value:"<resource id>"},{field:"dateWorked",op:"gte",value:"2026-10-20"},{field:"dateWorked",op:"lte",value:"2026-10-22"}], limit:500}`: the window's rows up to limit (dateWorkedWindow caps at 500; the query defaults to 50), both days inclusive because dateWorked is a date, without the posted counts (derive them from postedDate where the zone serves it). A bare window with no narrowing is `autotask_read {operation:"time_entries.dateWorkedWindow", args:["2026-10-20","2026-10-22"]}` (all strings). The server and SDK pass filter values through unchanged; string dates, ids and booleans are confirmed on resources and time_entries, other fields are not verified.
|
|
32
32
|
|
|
33
33
|
## Guardrails
|
|
34
34
|
|