iit-mcp-autotask 0.38.2 → 0.38.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 +3 -1
- package/dist/tools/lists.js +6 -0
- package/dist/tools/schemas.d.ts +9 -8
- package/dist/tools/schemas.js +12 -10
- 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/README.md
CHANGED
|
@@ -47,7 +47,9 @@ examples; `full: true` serves the complete record, `summary: true` about 2 KB),
|
|
|
47
47
|
`time_off_requests`, `companies`, `billing_items`, ...; limit default 50, max 2500). With
|
|
48
48
|
`group_by`, `sum` or `count: true` it answers totals instead of rows (the SDK's
|
|
49
49
|
`operations.aggregate`; `top` 1-100 groups, `limit` up to 2,500 scanned rows; `hasMore` /
|
|
50
|
-
`groupsTruncated` mean partial).
|
|
50
|
+
`groupsTruncated` mean partial). `filter` is not schema-required (a form-filler dead-ends on a
|
|
51
|
+
required Table, #241) but a missing or empty one is refused (`INPUT_INVALID`, rows and totals);
|
|
52
|
+
`{field: "id", op: "exist"}` targets the whole entity. Records by id: `filter: [{field: "id", op: "in", value: "1,2,3"}]`
|
|
51
53
|
(`in` / `notIn` take an array or a comma string). Time entries have no `companyID`: the relation
|
|
52
54
|
filter `{field: "ticket.companyID", op: "eq", value: "<id>"}` (also `task.<field>`; `project.<field>`
|
|
53
55
|
where the entity has the FK) scopes them through one parent read of at most 300 ids (more is a typed
|
package/dist/tools/lists.js
CHANGED
|
@@ -51,6 +51,12 @@ export function buildQuery(ctx) {
|
|
|
51
51
|
if (rec === undefined || rec.effect !== 'read') {
|
|
52
52
|
throw new InputInvalidError(`query: "${args.resource}" has no ${operation} read in the registry (find the resource with autotask_catalog)`, 'resource');
|
|
53
53
|
}
|
|
54
|
+
// #241: filter is optional in the advertised schema (a form-filler dead-ends on a required Table), so a missing or
|
|
55
|
+
// empty one is refused here, rows and totals alike (unscoped totals answer the wrong scope), before any wire. An
|
|
56
|
+
// INPUT_INVALID error, as when the schema required it: a model retries it; needs_input reads as "stop" (rule 3).
|
|
57
|
+
if (args.filter === undefined || (Array.isArray(args.filter) && args.filter.length === 0)) {
|
|
58
|
+
throw new InputInvalidError('query: filter is missing or empty; call again with a scope condition such as {field: "companyID", op: "eq", value: "<id>"} (time_entries: ticket.companyID), or {field: "id", op: "exist"} for the whole entity', 'filter');
|
|
59
|
+
}
|
|
54
60
|
// #228: totals mode (group_by / sum / count) is the SDK's operations.aggregate over the same filter
|
|
55
61
|
if (args.group_by !== undefined || args.sum !== undefined || args.count === true) {
|
|
56
62
|
const clash = ['latest', 'include_fields', 'note_chars', 'via', 'format', 'output_path', 'acknowledge_unscoped', 'untrusted_wrapper'].find((k) => args[k] !== undefined);
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -40,16 +40,17 @@ export declare const COMPANY_PROFILE_PART_LIMIT_DEFAULT = 10;
|
|
|
40
40
|
* and/or groups are accepted unchanged). Only field/op/value are advertised: a form-filling client
|
|
41
41
|
* fills every property it is shown, and a filled `udf` or `items` would change the query.
|
|
42
42
|
* #231: the advertised condition is explicit for strict form-fillers (one prepared a required Table
|
|
43
|
-
* with no rows and sent nothing):
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* with no rows and sent nothing): `op` the 13-operator enum and an `examples` entry (a relation
|
|
44
|
+
* filter plus a UTC date bound). exist/notExist take value "", which {@link normalizeFilter} drops
|
|
45
|
+
* before dispatch. #241: `filter` and a condition's `value` are not required (Copilot Studio left the
|
|
46
|
+
* required Table unfilled and asked the user); the query handler refuses a missing or empty filter.
|
|
46
47
|
*/
|
|
47
48
|
export declare const FILTER_OPS: readonly ["eq", "noteq", "gt", "gte", "lt", "lte", "beginsWith", "endsWith", "contains", "exist", "notExist", "in", "notIn"];
|
|
48
49
|
/** #234: a `<self>.<field>` name on its own resource without the prefix; any other name unchanged. */
|
|
49
50
|
export declare function ownField(resource: string, name: string): string;
|
|
50
51
|
/**
|
|
51
52
|
* #231: drop the empty `value` a strict form-filler sends on exist/notExist (the advertised schema
|
|
52
|
-
*
|
|
53
|
+
* required one on every condition until #241). #234: a `<self>.<field>` condition on `resource` names the own
|
|
53
54
|
* field ({@link ownField}). Everything else passes through unchanged, groups and the bag form included.
|
|
54
55
|
*/
|
|
55
56
|
export declare function normalizeFilter(filter: unknown, resource?: string): unknown;
|
|
@@ -60,9 +61,9 @@ export declare function normalizeFilter(filter: unknown, resource?: string): unk
|
|
|
60
61
|
* the typed CONFIG_ERROR BEFORE any request is built (zero wire). node-autotask before 0.9.0
|
|
61
62
|
* silently DROPPED a bare object's conditions, so an unrefused bare object queried UNFILTERED;
|
|
62
63
|
* 0.9.0 refuses it too (#83), and this server's refusal stays the zero-wire pre-request
|
|
63
|
-
* guarantee. The `filter` key
|
|
64
|
-
*
|
|
65
|
-
*
|
|
64
|
+
* guarantee. The `filter` key is optional in `QUERY_INPUT` (#241): the query handler refuses a
|
|
65
|
+
* missing or empty one (INPUT_INVALID) before this validator runs; the `undefined` early return
|
|
66
|
+
* serves autotask_read, whose `<resource>.query` params bag may omit it.
|
|
66
67
|
*/
|
|
67
68
|
export declare function assertFilterShape(filter: unknown, operation: string): void;
|
|
68
69
|
/** A registered input: the StandardSchema the SDK sees, the zod object the frame parses, the key set. */
|
|
@@ -289,7 +290,7 @@ export declare const RESOLVE_INPUT: z.ZodObject<{
|
|
|
289
290
|
}, z.core.$strip>;
|
|
290
291
|
export declare const QUERY_INPUT: z.ZodObject<{
|
|
291
292
|
resource: z.ZodString;
|
|
292
|
-
filter: z.ZodUnknown
|
|
293
|
+
filter: z.ZodOptional<z.ZodUnknown>;
|
|
293
294
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
294
295
|
group_by: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
295
296
|
sum: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
package/dist/tools/schemas.js
CHANGED
|
@@ -72,21 +72,23 @@ const COMPANY_PROFILE_PART_LIMIT_LOCATIONS = z
|
|
|
72
72
|
* and/or groups are accepted unchanged). Only field/op/value are advertised: a form-filling client
|
|
73
73
|
* fills every property it is shown, and a filled `udf` or `items` would change the query.
|
|
74
74
|
* #231: the advertised condition is explicit for strict form-fillers (one prepared a required Table
|
|
75
|
-
* with no rows and sent nothing):
|
|
76
|
-
*
|
|
77
|
-
*
|
|
75
|
+
* with no rows and sent nothing): `op` the 13-operator enum and an `examples` entry (a relation
|
|
76
|
+
* filter plus a UTC date bound). exist/notExist take value "", which {@link normalizeFilter} drops
|
|
77
|
+
* before dispatch. #241: `filter` and a condition's `value` are not required (Copilot Studio left the
|
|
78
|
+
* required Table unfilled and asked the user); the query handler refuses a missing or empty filter.
|
|
78
79
|
*/
|
|
79
80
|
export const FILTER_OPS = ['eq', 'noteq', 'gt', 'gte', 'lt', 'lte', 'beginsWith', 'endsWith', 'contains', 'exist', 'notExist', 'in', 'notIn'];
|
|
80
81
|
const FILTER = z
|
|
81
82
|
.unknown()
|
|
83
|
+
.optional()
|
|
82
84
|
.meta({
|
|
83
85
|
type: 'array',
|
|
84
86
|
// op keeps type string beside the enum: a Table client types each column from `type` (#173)
|
|
85
|
-
items: { type: 'object', properties: { field: { type: 'string' }, op: { type: 'string', enum: FILTER_OPS }, value: { type: 'string' } }, required: ['field', 'op'
|
|
87
|
+
items: { type: 'object', properties: { field: { type: 'string' }, op: { type: 'string', enum: FILTER_OPS }, value: { type: 'string' } }, required: ['field', 'op'] },
|
|
86
88
|
// one filter copied verbatim by form-fillers: the ticket relation on time_entries, the own fields on tickets (#234)
|
|
87
89
|
examples: [[{ field: 'ticket.companyID', op: 'eq', value: '123' }, { field: 'ticket.createDate', op: 'gte', value: '2026-10-01T00:00:00Z' }]],
|
|
88
90
|
})
|
|
89
|
-
.describe('Conditions; write every value as a string ("29", "true"; exist/notExist: ""; in/notIn: "1,2,3"). Dates: UTC ISO-8601. An unknown field name (also group_by, sum) is a pre-query CONFIG_ERROR. ticket.<field> scopes time entries by their ticket; on tickets use the plain field.');
|
|
91
|
+
.describe('Needed (missing or [] is refused). Conditions; write every value as a string ("29", "true"; exist/notExist: ""; in/notIn: "1,2,3"). Dates: UTC ISO-8601. An unknown field name (also group_by, sum) is a pre-query CONFIG_ERROR. ticket.<field> scopes time entries by their ticket; on tickets use the plain field.');
|
|
90
92
|
/**
|
|
91
93
|
* #234: the SDK's relation targets. On the target resource itself `<self>.` can only mean an own
|
|
92
94
|
* field (none of them carries a `<self>ID`), so `ticket.companyID` on tickets is `companyID`; the SDK
|
|
@@ -100,7 +102,7 @@ export function ownField(resource, name) {
|
|
|
100
102
|
}
|
|
101
103
|
/**
|
|
102
104
|
* #231: drop the empty `value` a strict form-filler sends on exist/notExist (the advertised schema
|
|
103
|
-
*
|
|
105
|
+
* required one on every condition until #241). #234: a `<self>.<field>` condition on `resource` names the own
|
|
104
106
|
* field ({@link ownField}). Everything else passes through unchanged, groups and the bag form included.
|
|
105
107
|
*/
|
|
106
108
|
export function normalizeFilter(filter, resource = '') {
|
|
@@ -131,9 +133,9 @@ export function normalizeFilter(filter, resource = '') {
|
|
|
131
133
|
* the typed CONFIG_ERROR BEFORE any request is built (zero wire). node-autotask before 0.9.0
|
|
132
134
|
* silently DROPPED a bare object's conditions, so an unrefused bare object queried UNFILTERED;
|
|
133
135
|
* 0.9.0 refuses it too (#83), and this server's refusal stays the zero-wire pre-request
|
|
134
|
-
* guarantee. The `filter` key
|
|
135
|
-
*
|
|
136
|
-
*
|
|
136
|
+
* guarantee. The `filter` key is optional in `QUERY_INPUT` (#241): the query handler refuses a
|
|
137
|
+
* missing or empty one (INPUT_INVALID) before this validator runs; the `undefined` early return
|
|
138
|
+
* serves autotask_read, whose `<resource>.query` params bag may omit it.
|
|
137
139
|
*/
|
|
138
140
|
export function assertFilterShape(filter, operation) {
|
|
139
141
|
if (filter === undefined)
|
|
@@ -307,7 +309,7 @@ export const QUERY_INPUT = z.object({
|
|
|
307
309
|
count: z.boolean().optional().describe('true: count the matching rows, no rows returned.'),
|
|
308
310
|
top: z.number().int().min(1).max(100).optional().describe('Groups kept, 1-100 (default 25).'),
|
|
309
311
|
include_fields: z.array(z.string().min(1)).optional().describe('Exact vendor field names to return (id always included); an unknown name is a pre-query CONFIG_ERROR. Absent: tickets and time_entries return default columns, others the full row.'),
|
|
310
|
-
latest: z.number().int().min(1).max(2500).optional().describe('N newest records (highest id). filter is
|
|
312
|
+
latest: z.number().int().min(1).max(2500).optional().describe('N newest records (highest id). A filter is needed on this tool (a no-filter call is refused); to target the whole entity pass the tautology {field: id, op: exist}. Needs limit >= N (limit defaults to 50).'),
|
|
311
313
|
note_chars: z.number().int().min(1).optional().describe('Cap each free-text field at N characters.'),
|
|
312
314
|
via: z.enum(['post', 'get']).optional().describe('Vendor transport (get caps the URL at 2048 chars).'),
|
|
313
315
|
format: z.enum(['json', 'tsv']).optional().describe('tsv: the table as data.tsv, items omitted.'),
|
|
@@ -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.38.
|
|
3
|
+
export const SERVER_VERSION = '0.38.3';
|
|
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.38.
|
|
63
|
+
readonly version: "0.38.3";
|
|
64
64
|
readonly mcpProtocolBaseline: "2026-07-28";
|
|
65
65
|
readonly legacyProtocolFallback: "2025-11-25";
|
|
66
66
|
};
|
package/package.json
CHANGED
package/server.json
CHANGED