capsulemcp 1.6.0 → 1.6.2
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 +2 -1
- package/dist/http.js +586 -557
- package/dist/index.js +586 -557
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -286,11 +286,22 @@ async function doFetch(url, options) {
|
|
|
286
286
|
"Rate limit exceeded after one retry. Please slow down your requests."
|
|
287
287
|
);
|
|
288
288
|
}
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
return { ...retried, startedAt, method, url, retriedAfter429: true };
|
|
290
|
+
}
|
|
291
|
+
return { ...first, startedAt, method, url, retriedAfter429: false };
|
|
292
|
+
}
|
|
293
|
+
async function consumeBody(start, body) {
|
|
294
|
+
try {
|
|
295
|
+
return await body();
|
|
296
|
+
} finally {
|
|
297
|
+
emitCapsuleRequest(
|
|
298
|
+
start.method,
|
|
299
|
+
start.url,
|
|
300
|
+
start.res,
|
|
301
|
+
Date.now() - start.startedAt,
|
|
302
|
+
start.retriedAfter429
|
|
303
|
+
);
|
|
291
304
|
}
|
|
292
|
-
emitCapsuleRequest(method, url, first.res, Date.now() - startedAt, false);
|
|
293
|
-
return first;
|
|
294
305
|
}
|
|
295
306
|
function emitCapsuleRequest(method, url, res, durationMs, retriedAfter429) {
|
|
296
307
|
let path = "";
|
|
@@ -340,13 +351,15 @@ function buildUrl(path, params) {
|
|
|
340
351
|
async function capsuleGet(path, params) {
|
|
341
352
|
const token = getToken();
|
|
342
353
|
const url = buildUrl(path, params);
|
|
343
|
-
const
|
|
354
|
+
const start = await doFetch(url, { headers: baseHeaders(token) });
|
|
344
355
|
try {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
356
|
+
return await consumeBody(start, async () => {
|
|
357
|
+
const data = await handleResponse(start.res);
|
|
358
|
+
const nextPage = parseNextPage(start.res.headers.get("Link"));
|
|
359
|
+
return { data, nextPage };
|
|
360
|
+
});
|
|
348
361
|
} finally {
|
|
349
|
-
cleanup();
|
|
362
|
+
start.cleanup();
|
|
350
363
|
}
|
|
351
364
|
}
|
|
352
365
|
async function capsuleGetCached(path, params) {
|
|
@@ -381,123 +394,130 @@ async function capsulePost(path, body) {
|
|
|
381
394
|
if (isReadOnly()) throw new CapsuleReadOnlyError("POST");
|
|
382
395
|
const token = getToken();
|
|
383
396
|
const url = buildUrl(path);
|
|
384
|
-
const
|
|
397
|
+
const start = await doFetch(url, {
|
|
385
398
|
method: "POST",
|
|
386
399
|
headers: { ...baseHeaders(token), "Content-Type": "application/json" },
|
|
387
400
|
body: JSON.stringify(body)
|
|
388
401
|
});
|
|
389
402
|
try {
|
|
390
|
-
return await handleResponse(res);
|
|
403
|
+
return await consumeBody(start, () => handleResponse(start.res));
|
|
391
404
|
} finally {
|
|
392
|
-
cleanup();
|
|
405
|
+
start.cleanup();
|
|
393
406
|
}
|
|
394
407
|
}
|
|
395
408
|
async function capsulePostNoContent(path) {
|
|
396
409
|
if (isReadOnly()) throw new CapsuleReadOnlyError("POST");
|
|
397
410
|
const token = getToken();
|
|
398
411
|
const url = buildUrl(path);
|
|
399
|
-
const
|
|
412
|
+
const start = await doFetch(url, {
|
|
400
413
|
method: "POST",
|
|
401
414
|
headers: baseHeaders(token)
|
|
402
415
|
});
|
|
403
416
|
try {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
417
|
+
await consumeBody(start, async () => {
|
|
418
|
+
if (start.res.status === 204) return;
|
|
419
|
+
await throwForStatus(start.res);
|
|
420
|
+
await mapAbort(start.res.text());
|
|
421
|
+
});
|
|
407
422
|
} finally {
|
|
408
|
-
cleanup();
|
|
423
|
+
start.cleanup();
|
|
409
424
|
}
|
|
410
425
|
}
|
|
411
426
|
async function capsuleSearch(path, body, params) {
|
|
412
427
|
const token = getToken();
|
|
413
428
|
const url = buildUrl(path, params);
|
|
414
|
-
const
|
|
429
|
+
const start = await doFetch(url, {
|
|
415
430
|
method: "POST",
|
|
416
431
|
headers: { ...baseHeaders(token), "Content-Type": "application/json" },
|
|
417
432
|
body: JSON.stringify(body)
|
|
418
433
|
});
|
|
419
434
|
try {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
435
|
+
return await consumeBody(start, async () => {
|
|
436
|
+
const data = await handleResponse(start.res);
|
|
437
|
+
const nextPage = parseNextPage(start.res.headers.get("Link"));
|
|
438
|
+
return { data, nextPage };
|
|
439
|
+
});
|
|
423
440
|
} finally {
|
|
424
|
-
cleanup();
|
|
441
|
+
start.cleanup();
|
|
425
442
|
}
|
|
426
443
|
}
|
|
427
444
|
async function capsulePut(path, body) {
|
|
428
445
|
if (isReadOnly()) throw new CapsuleReadOnlyError("PUT");
|
|
429
446
|
const token = getToken();
|
|
430
447
|
const url = buildUrl(path);
|
|
431
|
-
const
|
|
448
|
+
const start = await doFetch(url, {
|
|
432
449
|
method: "PUT",
|
|
433
450
|
headers: { ...baseHeaders(token), "Content-Type": "application/json" },
|
|
434
451
|
body: JSON.stringify(body)
|
|
435
452
|
});
|
|
436
453
|
try {
|
|
437
|
-
return await handleResponse(res);
|
|
454
|
+
return await consumeBody(start, () => handleResponse(start.res));
|
|
438
455
|
} finally {
|
|
439
|
-
cleanup();
|
|
456
|
+
start.cleanup();
|
|
440
457
|
}
|
|
441
458
|
}
|
|
442
459
|
async function capsuleGetBinary(path, maxBytes) {
|
|
443
460
|
const token = getToken();
|
|
444
461
|
const url = buildUrl(path);
|
|
445
|
-
const
|
|
462
|
+
const start = await doFetch(url, { headers: baseHeaders(token) });
|
|
446
463
|
try {
|
|
447
|
-
await
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
buffer: Buffer.alloc(0),
|
|
457
|
-
truncated: true,
|
|
458
|
-
sizeBytes: declaredBytes
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
if (maxBytes !== void 0 && res.body) {
|
|
462
|
-
const reader = res.body.getReader();
|
|
463
|
-
const chunks = [];
|
|
464
|
-
let total = 0;
|
|
465
|
-
let truncated = false;
|
|
466
|
-
while (true) {
|
|
467
|
-
const { done, value } = await mapAbort(reader.read());
|
|
468
|
-
if (done) break;
|
|
469
|
-
total += value.byteLength;
|
|
470
|
-
if (total > maxBytes) {
|
|
471
|
-
truncated = true;
|
|
472
|
-
await reader.cancel().catch(() => {
|
|
473
|
-
});
|
|
474
|
-
break;
|
|
475
|
-
}
|
|
476
|
-
chunks.push(value);
|
|
477
|
-
}
|
|
478
|
-
if (truncated) {
|
|
464
|
+
return await consumeBody(start, async () => {
|
|
465
|
+
const res = start.res;
|
|
466
|
+
await throwForStatus(res);
|
|
467
|
+
const contentType = res.headers.get("Content-Type") ?? "application/octet-stream";
|
|
468
|
+
const declared = res.headers.get("Content-Length");
|
|
469
|
+
const declaredBytes = declared ? Number(declared) : NaN;
|
|
470
|
+
if (maxBytes !== void 0 && Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {
|
|
471
|
+
if (res.body) await res.body.cancel().catch(() => {
|
|
472
|
+
});
|
|
479
473
|
return {
|
|
480
474
|
contentType,
|
|
481
475
|
buffer: Buffer.alloc(0),
|
|
482
476
|
truncated: true,
|
|
483
|
-
sizeBytes:
|
|
477
|
+
sizeBytes: declaredBytes
|
|
484
478
|
};
|
|
485
479
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
480
|
+
if (maxBytes !== void 0 && res.body) {
|
|
481
|
+
const reader = res.body.getReader();
|
|
482
|
+
const chunks = [];
|
|
483
|
+
let total = 0;
|
|
484
|
+
let truncated = false;
|
|
485
|
+
while (true) {
|
|
486
|
+
const { done, value } = await mapAbort(reader.read());
|
|
487
|
+
if (done) break;
|
|
488
|
+
total += value.byteLength;
|
|
489
|
+
if (total > maxBytes) {
|
|
490
|
+
truncated = true;
|
|
491
|
+
await reader.cancel().catch(() => {
|
|
492
|
+
});
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
chunks.push(value);
|
|
496
|
+
}
|
|
497
|
+
if (truncated) {
|
|
498
|
+
return {
|
|
499
|
+
contentType,
|
|
500
|
+
buffer: Buffer.alloc(0),
|
|
501
|
+
truncated: true,
|
|
502
|
+
sizeBytes: total
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const buffer2 = Buffer.concat(chunks.map((c) => Buffer.from(c)));
|
|
506
|
+
return { contentType, buffer: buffer2, sizeBytes: buffer2.length };
|
|
507
|
+
}
|
|
508
|
+
const arrayBuffer = await mapAbort(res.arrayBuffer());
|
|
509
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
510
|
+
return { contentType, buffer, sizeBytes: buffer.length };
|
|
511
|
+
});
|
|
492
512
|
} finally {
|
|
493
|
-
cleanup();
|
|
513
|
+
start.cleanup();
|
|
494
514
|
}
|
|
495
515
|
}
|
|
496
516
|
async function capsulePostBinary(path, body, contentType, filename) {
|
|
497
517
|
if (isReadOnly()) throw new CapsuleReadOnlyError("POST");
|
|
498
518
|
const token = getToken();
|
|
499
519
|
const url = buildUrl(path);
|
|
500
|
-
const
|
|
520
|
+
const start = await doFetch(url, {
|
|
501
521
|
method: "POST",
|
|
502
522
|
headers: {
|
|
503
523
|
...baseHeaders(token),
|
|
@@ -508,25 +528,27 @@ async function capsulePostBinary(path, body, contentType, filename) {
|
|
|
508
528
|
body
|
|
509
529
|
});
|
|
510
530
|
try {
|
|
511
|
-
return await handleResponse(res);
|
|
531
|
+
return await consumeBody(start, () => handleResponse(start.res));
|
|
512
532
|
} finally {
|
|
513
|
-
cleanup();
|
|
533
|
+
start.cleanup();
|
|
514
534
|
}
|
|
515
535
|
}
|
|
516
536
|
async function capsuleDelete(path) {
|
|
517
537
|
if (isReadOnly()) throw new CapsuleReadOnlyError("DELETE");
|
|
518
538
|
const token = getToken();
|
|
519
539
|
const url = buildUrl(path);
|
|
520
|
-
const
|
|
540
|
+
const start = await doFetch(url, {
|
|
521
541
|
method: "DELETE",
|
|
522
542
|
headers: baseHeaders(token)
|
|
523
543
|
});
|
|
524
544
|
try {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
545
|
+
await consumeBody(start, async () => {
|
|
546
|
+
if (start.res.status === 204) return;
|
|
547
|
+
await throwForStatus(start.res);
|
|
548
|
+
await mapAbort(start.res.text());
|
|
549
|
+
});
|
|
528
550
|
} finally {
|
|
529
|
-
cleanup();
|
|
551
|
+
start.cleanup();
|
|
530
552
|
}
|
|
531
553
|
}
|
|
532
554
|
|
|
@@ -849,18 +871,19 @@ function registerToolTask(server2, name, description, schema, handler) {
|
|
|
849
871
|
}
|
|
850
872
|
|
|
851
873
|
// src/tools/parties.ts
|
|
852
|
-
import { z as
|
|
874
|
+
import { z as z6 } from "zod";
|
|
853
875
|
|
|
854
|
-
// src/tools/
|
|
855
|
-
|
|
856
|
-
|
|
876
|
+
// src/tools/body-helpers.ts
|
|
877
|
+
function setRef(body, key, id) {
|
|
878
|
+
if (id) body[key] = { id };
|
|
879
|
+
}
|
|
880
|
+
function setNullableRef(body, key, id) {
|
|
881
|
+
if (id === null) body[key] = null;
|
|
882
|
+
else if (id !== void 0) body[key] = { id };
|
|
883
|
+
}
|
|
857
884
|
|
|
858
|
-
// src/tools/
|
|
885
|
+
// src/tools/define-batch.ts
|
|
859
886
|
import { z } from "zod";
|
|
860
|
-
var CONFIRM_REQUIRED_MESSAGE = "confirm: true is required to perform this destructive operation (set the parameter explicitly to acknowledge the destructive intent)";
|
|
861
|
-
function confirmFlag() {
|
|
862
|
-
return z.literal(true, { error: () => CONFIRM_REQUIRED_MESSAGE });
|
|
863
|
-
}
|
|
864
887
|
|
|
865
888
|
// src/capsule/batch.ts
|
|
866
889
|
function chunk(arr, size) {
|
|
@@ -954,6 +977,39 @@ function topFailureReasons(results, n) {
|
|
|
954
977
|
return Array.from(counts.values()).sort((a, b) => b.count - a.count).slice(0, n);
|
|
955
978
|
}
|
|
956
979
|
|
|
980
|
+
// src/tools/define-batch.ts
|
|
981
|
+
function defineBatch(args) {
|
|
982
|
+
const schema = z.object({
|
|
983
|
+
items: z.array(args.itemSchema).min(1).max(50).describe(args.itemDescription)
|
|
984
|
+
});
|
|
985
|
+
async function handler(input, opts = {}) {
|
|
986
|
+
return batchExecute(args.toolName, input.items, args.itemHandler, opts);
|
|
987
|
+
}
|
|
988
|
+
return { schema, handler };
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// src/tools/descriptions.ts
|
|
992
|
+
var EMBED_TAGS_FIELDS_DESCRIPTION = "Comma-separated embeds, e.g. 'tags,fields'";
|
|
993
|
+
var EMBED_ATTACHMENTS_PARTICIPANTS_DESCRIPTION = "Comma-separated embeds, e.g. 'attachments,participants'";
|
|
994
|
+
|
|
995
|
+
// src/tools/define-delete.ts
|
|
996
|
+
import { z as z4 } from "zod";
|
|
997
|
+
|
|
998
|
+
// src/tools/confirm-flag.ts
|
|
999
|
+
import { z as z2 } from "zod";
|
|
1000
|
+
var CONFIRM_REQUIRED_MESSAGE = "confirm: true is required to perform this destructive operation (set the parameter explicitly to acknowledge the destructive intent)";
|
|
1001
|
+
function confirmFlag() {
|
|
1002
|
+
return z2.literal(true, { error: () => CONFIRM_REQUIRED_MESSAGE });
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// src/tools/shared-schemas.ts
|
|
1006
|
+
import { z as z3 } from "zod";
|
|
1007
|
+
var positiveId = z3.preprocess((input) => {
|
|
1008
|
+
if (typeof input !== "string") return input;
|
|
1009
|
+
const trimmed = input.trim();
|
|
1010
|
+
return /^\d+$/.test(trimmed) ? Number(trimmed) : input;
|
|
1011
|
+
}, z3.number().int().positive());
|
|
1012
|
+
|
|
957
1013
|
// src/capsule/idempotent.ts
|
|
958
1014
|
var isCapsule404 = (err) => err instanceof CapsuleApiError && err.status === 404;
|
|
959
1015
|
var isCapsuleTagNotFound = (err) => err instanceof CapsuleApiError && err.status === 422 && /tag not found/i.test(err.message);
|
|
@@ -976,13 +1032,33 @@ async function idempotentWithResult(op, success, alreadyDone, isAlreadyDoneError
|
|
|
976
1032
|
}
|
|
977
1033
|
}
|
|
978
1034
|
|
|
1035
|
+
// src/tools/define-delete.ts
|
|
1036
|
+
function defineDelete(args) {
|
|
1037
|
+
const { toolName, pathPrefix, confirmHint, idDescription } = args;
|
|
1038
|
+
const schema = z4.object({
|
|
1039
|
+
id: idDescription ? positiveId.describe(idDescription) : positiveId,
|
|
1040
|
+
confirm: confirmFlag().describe(confirmHint)
|
|
1041
|
+
});
|
|
1042
|
+
async function handler(input) {
|
|
1043
|
+
if (input.confirm !== true) {
|
|
1044
|
+
throw new Error(`${toolName} requires confirm: true`);
|
|
1045
|
+
}
|
|
1046
|
+
return idempotent(
|
|
1047
|
+
() => capsuleDelete(`${pathPrefix}/${input.id}`),
|
|
1048
|
+
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1049
|
+
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
return { schema, handler };
|
|
1053
|
+
}
|
|
1054
|
+
|
|
979
1055
|
// src/tools/custom-field-helpers.ts
|
|
980
|
-
import { z as
|
|
981
|
-
var CustomFieldWriteSchema =
|
|
982
|
-
definitionId:
|
|
1056
|
+
import { z as z5 } from "zod";
|
|
1057
|
+
var CustomFieldWriteSchema = z5.object({
|
|
1058
|
+
definitionId: positiveId.describe(
|
|
983
1059
|
"The custom-field definition id from list_custom_fields. Identifies which field on the entity to set."
|
|
984
1060
|
),
|
|
985
|
-
value:
|
|
1061
|
+
value: z5.union([z5.string(), z5.number(), z5.boolean(), z5.null()]).describe(
|
|
986
1062
|
"The new value. String for TEXT / DATE / LIST / LARGE_TEXT / LINK fields, number for NUMBER fields, boolean for BOOLEAN fields. Clearing: pass null for TEXT / NUMBER / DATE / LIST (Capsule removes the row). BOOLEAN does NOT accept null (Capsule returns 422 'invalid type for field'); use `value: false` instead. Note BOOLEAN fields are observably **two-state**: a row exists with `value: true`, or no row exists. Setting `value: false` removes the row entirely \u2014 readers should treat absent BOOLEAN rows as equivalent to false. Tri-state BOOLEAN semantics (true / false / unknown) are not achievable through Capsule's API. Audit-log noise: sending value=null on a field that's already empty/cleared is accepted by Capsule but still bumps the parent entity's `updatedAt`. Read the current value via embed='fields' first if `updatedAt` is being used as a 'last meaningful change' signal. NUMBER quirks: Capsule stores numerics correctly but the read-back via embed=fields returns them as STRINGS (e.g. value=3 reads as '3'); callers comparing values must coerce. TEXT quirks: value='' has the same observable effect as value=null (row removed); empty-string and never-set are indistinguishable."
|
|
987
1063
|
)
|
|
988
1064
|
});
|
|
@@ -998,24 +1074,24 @@ function mapFieldsForBody(fields) {
|
|
|
998
1074
|
}
|
|
999
1075
|
|
|
1000
1076
|
// src/tools/parties.ts
|
|
1001
|
-
var EmailAddressSchema =
|
|
1002
|
-
address:
|
|
1003
|
-
type:
|
|
1077
|
+
var EmailAddressSchema = z6.object({
|
|
1078
|
+
address: z6.string().email(),
|
|
1079
|
+
type: z6.string().optional()
|
|
1004
1080
|
});
|
|
1005
|
-
var PhoneNumberSchema =
|
|
1081
|
+
var PhoneNumberSchema = z6.object({
|
|
1006
1082
|
// Capsule rejects empty strings with `phoneNumber.number: number is
|
|
1007
1083
|
// required`. Enforce at the schema layer to catch typos pre-call,
|
|
1008
1084
|
// matching how EmailAddressSchema's address field behaves.
|
|
1009
|
-
number:
|
|
1010
|
-
type:
|
|
1085
|
+
number: z6.string().min(1),
|
|
1086
|
+
type: z6.string().optional()
|
|
1011
1087
|
});
|
|
1012
1088
|
var CountryDescription = "Country name. Capsule validates this against a small canonical-English-name dictionary; inputs not in the dictionary are REJECTED with 422 'address.country: unknown country' (NOT silently passed through or normalised). Probed examples \u2014 accepted: `United States`, `United Kingdom`, `Czechia`, `Germany`. Aliased: `USA \u2192 United States`. Rejected: `United States of America`, `Czech Republic` (use `Czechia`), `UK`/`Britain` (use `United Kingdom`), `Deutschland` (use `Germany`). Empty string is accepted and stored as `null` \u2014 a de-facto 'clear' shape. To discover an accepted name, read an existing party that already has the country set.";
|
|
1013
|
-
var AddressSchema =
|
|
1014
|
-
street:
|
|
1015
|
-
city:
|
|
1016
|
-
state:
|
|
1017
|
-
country:
|
|
1018
|
-
zip:
|
|
1089
|
+
var AddressSchema = z6.object({
|
|
1090
|
+
street: z6.string().optional(),
|
|
1091
|
+
city: z6.string().optional(),
|
|
1092
|
+
state: z6.string().optional(),
|
|
1093
|
+
country: z6.string().optional().describe(CountryDescription),
|
|
1094
|
+
zip: z6.string().optional()
|
|
1019
1095
|
});
|
|
1020
1096
|
function validateWebsiteAddress(data, ctx) {
|
|
1021
1097
|
const isUrlService = data.service === void 0 || data.service === "URL";
|
|
@@ -1038,7 +1114,7 @@ function validateWebsiteAddress(data, ctx) {
|
|
|
1038
1114
|
});
|
|
1039
1115
|
}
|
|
1040
1116
|
}
|
|
1041
|
-
var WebsiteServiceEnum =
|
|
1117
|
+
var WebsiteServiceEnum = z6.enum([
|
|
1042
1118
|
"URL",
|
|
1043
1119
|
"SKYPE",
|
|
1044
1120
|
"TWITTER",
|
|
@@ -1057,19 +1133,19 @@ var WebsiteServiceEnum = z3.enum([
|
|
|
1057
1133
|
"BLUESKY",
|
|
1058
1134
|
"SNAPCHAT"
|
|
1059
1135
|
]);
|
|
1060
|
-
var WebsiteSchema =
|
|
1061
|
-
address:
|
|
1136
|
+
var WebsiteSchema = z6.object({
|
|
1137
|
+
address: z6.string().min(1).describe(
|
|
1062
1138
|
"The website address. A URL when service='URL', or a handle (e.g. '@acmeco') for social services like 'TWITTER', 'INSTAGRAM'. Capsule names this field `address` regardless of service type."
|
|
1063
1139
|
),
|
|
1064
1140
|
service: WebsiteServiceEnum.optional().describe(
|
|
1065
1141
|
"Service type. One of: URL, SKYPE, TWITTER, LINKED_IN, FACEBOOK, XING, FEED, GOOGLE_PLUS, FLICKR, GITHUB, YOUTUBE, INSTAGRAM, PINTEREST, TIKTOK, THREADS, BLUESKY, SNAPCHAT. Defaults to 'URL' if omitted."
|
|
1066
1142
|
)
|
|
1067
1143
|
}).superRefine(validateWebsiteAddress);
|
|
1068
|
-
var searchPartiesSchema =
|
|
1069
|
-
q:
|
|
1070
|
-
embed:
|
|
1071
|
-
page:
|
|
1072
|
-
perPage:
|
|
1144
|
+
var searchPartiesSchema = z6.object({
|
|
1145
|
+
q: z6.string().optional().describe("Free-text search query"),
|
|
1146
|
+
embed: z6.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
1147
|
+
page: z6.number().int().positive().optional().default(1),
|
|
1148
|
+
perPage: z6.number().int().min(1).max(100).optional().default(25)
|
|
1073
1149
|
});
|
|
1074
1150
|
async function searchParties(input) {
|
|
1075
1151
|
const path = input.q ? "/parties/search" : "/parties";
|
|
@@ -1081,9 +1157,9 @@ async function searchParties(input) {
|
|
|
1081
1157
|
});
|
|
1082
1158
|
return { ...data, nextPage };
|
|
1083
1159
|
}
|
|
1084
|
-
var getPartySchema =
|
|
1085
|
-
id:
|
|
1086
|
-
embed:
|
|
1160
|
+
var getPartySchema = z6.object({
|
|
1161
|
+
id: positiveId.describe("Party ID"),
|
|
1162
|
+
embed: z6.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1087
1163
|
});
|
|
1088
1164
|
async function getParty(input) {
|
|
1089
1165
|
const { data } = await capsuleGet(`/parties/${input.id}`, {
|
|
@@ -1091,11 +1167,11 @@ async function getParty(input) {
|
|
|
1091
1167
|
});
|
|
1092
1168
|
return data;
|
|
1093
1169
|
}
|
|
1094
|
-
var getPartiesSchema =
|
|
1095
|
-
ids:
|
|
1170
|
+
var getPartiesSchema = z6.object({
|
|
1171
|
+
ids: z6.array(positiveId).min(1).max(50).describe(
|
|
1096
1172
|
"Array of party IDs (1\u201350). Capsule's native batch-fetch endpoint caps at 10 per request; the connector transparently splits larger sets into 10-id chunks and fans out the Capsule calls in parallel. Result shape is identical regardless of input size."
|
|
1097
1173
|
),
|
|
1098
|
-
embed:
|
|
1174
|
+
embed: z6.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1099
1175
|
});
|
|
1100
1176
|
async function getParties(input) {
|
|
1101
1177
|
const { ids, embed } = input;
|
|
@@ -1113,10 +1189,10 @@ async function getParties(input) {
|
|
|
1113
1189
|
);
|
|
1114
1190
|
return { parties: responses.flatMap((r) => r.data.parties) };
|
|
1115
1191
|
}
|
|
1116
|
-
var listPartyOpportunitiesSchema =
|
|
1117
|
-
partyId:
|
|
1118
|
-
page:
|
|
1119
|
-
perPage:
|
|
1192
|
+
var listPartyOpportunitiesSchema = z6.object({
|
|
1193
|
+
partyId: positiveId,
|
|
1194
|
+
page: z6.number().int().positive().optional().default(1),
|
|
1195
|
+
perPage: z6.number().int().min(1).max(100).optional().default(25)
|
|
1120
1196
|
});
|
|
1121
1197
|
async function listPartyOpportunities(input) {
|
|
1122
1198
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -1125,10 +1201,10 @@ async function listPartyOpportunities(input) {
|
|
|
1125
1201
|
);
|
|
1126
1202
|
return { ...data, nextPage };
|
|
1127
1203
|
}
|
|
1128
|
-
var listPartyProjectsSchema =
|
|
1129
|
-
partyId:
|
|
1130
|
-
page:
|
|
1131
|
-
perPage:
|
|
1204
|
+
var listPartyProjectsSchema = z6.object({
|
|
1205
|
+
partyId: positiveId,
|
|
1206
|
+
page: z6.number().int().positive().optional().default(1),
|
|
1207
|
+
perPage: z6.number().int().min(1).max(100).optional().default(25)
|
|
1132
1208
|
});
|
|
1133
1209
|
async function listPartyProjects(input) {
|
|
1134
1210
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -1138,50 +1214,50 @@ async function listPartyProjects(input) {
|
|
|
1138
1214
|
return { ...data, nextPage };
|
|
1139
1215
|
}
|
|
1140
1216
|
var PartyWriteBaseSchema = {
|
|
1141
|
-
about:
|
|
1142
|
-
emailAddresses:
|
|
1217
|
+
about: z6.string().optional(),
|
|
1218
|
+
emailAddresses: z6.array(EmailAddressSchema).optional().describe(
|
|
1143
1219
|
"APPEND-ONLY: items are merged into the existing list, never replaced. For atomic add/remove/replace use add_party_email_address and remove_party_email_address_by_id. Passing `[]` here is a silent no-op (does not clear the list and does not advance updatedAt)."
|
|
1144
1220
|
),
|
|
1145
|
-
phoneNumbers:
|
|
1221
|
+
phoneNumbers: z6.array(PhoneNumberSchema).optional().describe(
|
|
1146
1222
|
"APPEND-ONLY: items are merged into the existing list, never replaced. For atomic add/remove/replace use add_party_phone_number and remove_party_phone_number_by_id."
|
|
1147
1223
|
),
|
|
1148
|
-
addresses:
|
|
1224
|
+
addresses: z6.array(AddressSchema).optional().describe(
|
|
1149
1225
|
"APPEND-ONLY: items are merged into the existing list, never replaced. For atomic add/remove/replace use add_party_address and remove_party_address_by_id. The `country` field is mapped through Capsule's country dictionary \u2014 see `add_party_address.country` for the dictionary edges (small canonical-English-name list; inputs not in the dictionary are REJECTED with 422, not silently dropped)."
|
|
1150
1226
|
),
|
|
1151
|
-
websites:
|
|
1227
|
+
websites: z6.array(WebsiteSchema).optional().describe(
|
|
1152
1228
|
"APPEND-ONLY: items are merged into the existing list, never replaced. For atomic add/remove/replace use add_party_website and remove_party_website_by_id."
|
|
1153
1229
|
),
|
|
1154
|
-
ownerId:
|
|
1230
|
+
ownerId: positiveId.optional().describe(
|
|
1155
1231
|
"Assign to user ID. On create_party, defaults to the API-token owner when omitted. Once set, this connector cannot clear the owner back to null \u2014 use Capsule's web UI for that. Discover IDs via list_users."
|
|
1156
1232
|
)
|
|
1157
1233
|
};
|
|
1158
|
-
var createPartySchema =
|
|
1159
|
-
type:
|
|
1234
|
+
var createPartySchema = z6.object({
|
|
1235
|
+
type: z6.enum(["person", "organisation"]),
|
|
1160
1236
|
// person
|
|
1161
|
-
firstName:
|
|
1162
|
-
lastName:
|
|
1163
|
-
title:
|
|
1164
|
-
jobTitle:
|
|
1165
|
-
organisationId:
|
|
1237
|
+
firstName: z6.string().optional(),
|
|
1238
|
+
lastName: z6.string().optional(),
|
|
1239
|
+
title: z6.string().optional(),
|
|
1240
|
+
jobTitle: z6.string().optional(),
|
|
1241
|
+
organisationId: positiveId.optional().describe("Link person to an existing organisation ID"),
|
|
1166
1242
|
// organisation
|
|
1167
|
-
name:
|
|
1243
|
+
name: z6.string().optional(),
|
|
1168
1244
|
...PartyWriteBaseSchema
|
|
1169
1245
|
});
|
|
1170
1246
|
async function createParty(input) {
|
|
1171
1247
|
const { ownerId, organisationId, ...rest } = input;
|
|
1172
1248
|
const body = { ...rest };
|
|
1173
|
-
|
|
1174
|
-
|
|
1249
|
+
setRef(body, "owner", ownerId);
|
|
1250
|
+
setRef(body, "organisation", organisationId);
|
|
1175
1251
|
return capsulePost("/parties", { party: body });
|
|
1176
1252
|
}
|
|
1177
|
-
var updatePartySchema =
|
|
1178
|
-
id:
|
|
1179
|
-
firstName:
|
|
1180
|
-
lastName:
|
|
1181
|
-
title:
|
|
1182
|
-
jobTitle:
|
|
1183
|
-
name:
|
|
1184
|
-
fields:
|
|
1253
|
+
var updatePartySchema = z6.object({
|
|
1254
|
+
id: positiveId,
|
|
1255
|
+
firstName: z6.string().optional(),
|
|
1256
|
+
lastName: z6.string().optional(),
|
|
1257
|
+
title: z6.string().optional(),
|
|
1258
|
+
jobTitle: z6.string().optional(),
|
|
1259
|
+
name: z6.string().optional(),
|
|
1260
|
+
fields: z6.array(CustomFieldWriteSchema).optional().describe(fieldsArrayDescriptor("get_party")),
|
|
1185
1261
|
...PartyWriteBaseSchema
|
|
1186
1262
|
});
|
|
1187
1263
|
async function updateParty(input) {
|
|
@@ -1190,39 +1266,26 @@ async function updateParty(input) {
|
|
|
1190
1266
|
for (const [k, v] of Object.entries(rest)) {
|
|
1191
1267
|
if (v !== void 0) body[k] = v;
|
|
1192
1268
|
}
|
|
1193
|
-
|
|
1269
|
+
setRef(body, "owner", ownerId);
|
|
1194
1270
|
const mappedFields = mapFieldsForBody(fields);
|
|
1195
1271
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1196
1272
|
return capsulePut(`/parties/${id}`, { party: body });
|
|
1197
1273
|
}
|
|
1198
|
-
var batchUpdatePartySchema =
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
)
|
|
1274
|
+
var { schema: batchUpdatePartySchema, handler: batchUpdateParty } = defineBatch({
|
|
1275
|
+
toolName: "batch_update_party",
|
|
1276
|
+
itemSchema: updatePartySchema,
|
|
1277
|
+
itemDescription: "Array of 1\u201350 update_party inputs. Each item is the same shape as a single update_party call \u2014 id is required, every other field is optional. Capped at 50 so a single tool call can't burn an outsized share of Capsule's hourly per-token rate budget (~4000 req/h).",
|
|
1278
|
+
itemHandler: updateParty
|
|
1202
1279
|
});
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
id: z3.number().int().positive(),
|
|
1208
|
-
confirm: confirmFlag().describe(
|
|
1209
|
-
"Must be set to true. Deletes the party AND all linked notes, tasks, opportunities, and projects (kases). Deleting an ORGANISATION does NOT delete people linked to it via organisationId \u2014 their `organisation` field is silently cleared to null and they survive as standalone records. Irreversible."
|
|
1210
|
-
)
|
|
1280
|
+
var { schema: deletePartySchema, handler: deleteParty } = defineDelete({
|
|
1281
|
+
toolName: "delete_party",
|
|
1282
|
+
pathPrefix: "/parties",
|
|
1283
|
+
confirmHint: "Must be set to true. Deletes the party AND all linked notes, tasks, opportunities, and projects (kases). Deleting an ORGANISATION does NOT delete people linked to it via organisationId \u2014 their `organisation` field is silently cleared to null and they survive as standalone records. Irreversible."
|
|
1211
1284
|
});
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
return idempotent(
|
|
1217
|
-
() => capsuleDelete(`/parties/${input.id}`),
|
|
1218
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1219
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1220
|
-
);
|
|
1221
|
-
}
|
|
1222
|
-
var addPartyEmailAddressSchema = z3.object({
|
|
1223
|
-
partyId: z3.number().int().positive(),
|
|
1224
|
-
address: z3.string().email(),
|
|
1225
|
-
type: z3.string().optional().describe("Free-form label, e.g. 'Work', 'Home'.")
|
|
1285
|
+
var addPartyEmailAddressSchema = z6.object({
|
|
1286
|
+
partyId: positiveId,
|
|
1287
|
+
address: z6.string().email(),
|
|
1288
|
+
type: z6.string().optional().describe("Free-form label, e.g. 'Work', 'Home'.")
|
|
1226
1289
|
});
|
|
1227
1290
|
async function addPartyEmailAddress(input) {
|
|
1228
1291
|
const { partyId, address, type } = input;
|
|
@@ -1232,9 +1295,9 @@ async function addPartyEmailAddress(input) {
|
|
|
1232
1295
|
party: { emailAddresses: [item] }
|
|
1233
1296
|
});
|
|
1234
1297
|
}
|
|
1235
|
-
var removePartyEmailAddressByIdSchema =
|
|
1236
|
-
partyId:
|
|
1237
|
-
emailAddressId:
|
|
1298
|
+
var removePartyEmailAddressByIdSchema = z6.object({
|
|
1299
|
+
partyId: positiveId,
|
|
1300
|
+
emailAddressId: positiveId.describe(
|
|
1238
1301
|
"Capsule's id for the email-address row. Read it from get_party (each entry in emailAddresses carries an id)."
|
|
1239
1302
|
)
|
|
1240
1303
|
});
|
|
@@ -1254,10 +1317,10 @@ async function removePartyEmailAddressById(input) {
|
|
|
1254
1317
|
() => ({ removed: true, alreadyRemoved: true, partyId, emailAddressId })
|
|
1255
1318
|
);
|
|
1256
1319
|
}
|
|
1257
|
-
var addPartyPhoneNumberSchema =
|
|
1258
|
-
partyId:
|
|
1259
|
-
number:
|
|
1260
|
-
type:
|
|
1320
|
+
var addPartyPhoneNumberSchema = z6.object({
|
|
1321
|
+
partyId: positiveId,
|
|
1322
|
+
number: z6.string().min(1),
|
|
1323
|
+
type: z6.string().optional().describe("Free-form label, e.g. 'Work', 'Mobile'.")
|
|
1261
1324
|
});
|
|
1262
1325
|
async function addPartyPhoneNumber(input) {
|
|
1263
1326
|
const { partyId, number, type } = input;
|
|
@@ -1267,9 +1330,9 @@ async function addPartyPhoneNumber(input) {
|
|
|
1267
1330
|
party: { phoneNumbers: [item] }
|
|
1268
1331
|
});
|
|
1269
1332
|
}
|
|
1270
|
-
var removePartyPhoneNumberByIdSchema =
|
|
1271
|
-
partyId:
|
|
1272
|
-
phoneNumberId:
|
|
1333
|
+
var removePartyPhoneNumberByIdSchema = z6.object({
|
|
1334
|
+
partyId: positiveId,
|
|
1335
|
+
phoneNumberId: positiveId.describe(
|
|
1273
1336
|
"Capsule's id for the phone-number row. Read it from get_party (each entry in phoneNumbers carries an id)."
|
|
1274
1337
|
)
|
|
1275
1338
|
});
|
|
@@ -1289,14 +1352,14 @@ async function removePartyPhoneNumberById(input) {
|
|
|
1289
1352
|
() => ({ removed: true, alreadyRemoved: true, partyId, phoneNumberId })
|
|
1290
1353
|
);
|
|
1291
1354
|
}
|
|
1292
|
-
var addPartyAddressSchema =
|
|
1293
|
-
partyId:
|
|
1294
|
-
street:
|
|
1295
|
-
city:
|
|
1296
|
-
state:
|
|
1297
|
-
country:
|
|
1298
|
-
zip:
|
|
1299
|
-
type:
|
|
1355
|
+
var addPartyAddressSchema = z6.object({
|
|
1356
|
+
partyId: positiveId,
|
|
1357
|
+
street: z6.string().optional(),
|
|
1358
|
+
city: z6.string().optional(),
|
|
1359
|
+
state: z6.string().optional(),
|
|
1360
|
+
country: z6.string().optional().describe(CountryDescription),
|
|
1361
|
+
zip: z6.string().optional(),
|
|
1362
|
+
type: z6.string().optional().describe("Free-form label, e.g. 'Office', 'Home'.")
|
|
1300
1363
|
});
|
|
1301
1364
|
async function addPartyAddress(input) {
|
|
1302
1365
|
const { partyId, ...rest } = input;
|
|
@@ -1308,9 +1371,9 @@ async function addPartyAddress(input) {
|
|
|
1308
1371
|
party: { addresses: [item] }
|
|
1309
1372
|
});
|
|
1310
1373
|
}
|
|
1311
|
-
var removePartyAddressByIdSchema =
|
|
1312
|
-
partyId:
|
|
1313
|
-
addressId:
|
|
1374
|
+
var removePartyAddressByIdSchema = z6.object({
|
|
1375
|
+
partyId: positiveId,
|
|
1376
|
+
addressId: positiveId.describe(
|
|
1314
1377
|
"Capsule's id for the address row. Read it from get_party (each entry in addresses carries an id)."
|
|
1315
1378
|
)
|
|
1316
1379
|
});
|
|
@@ -1330,9 +1393,9 @@ async function removePartyAddressById(input) {
|
|
|
1330
1393
|
() => ({ removed: true, alreadyRemoved: true, partyId, addressId })
|
|
1331
1394
|
);
|
|
1332
1395
|
}
|
|
1333
|
-
var addPartyWebsiteSchema =
|
|
1334
|
-
partyId:
|
|
1335
|
-
address:
|
|
1396
|
+
var addPartyWebsiteSchema = z6.object({
|
|
1397
|
+
partyId: positiveId,
|
|
1398
|
+
address: z6.string().min(1).describe(
|
|
1336
1399
|
"The website address. A URL when service='URL', or a handle (e.g. '@acmeco') for social services."
|
|
1337
1400
|
),
|
|
1338
1401
|
service: WebsiteServiceEnum.optional().describe("Defaults to 'URL' if omitted.")
|
|
@@ -1345,9 +1408,9 @@ async function addPartyWebsite(input) {
|
|
|
1345
1408
|
party: { websites: [item] }
|
|
1346
1409
|
});
|
|
1347
1410
|
}
|
|
1348
|
-
var removePartyWebsiteByIdSchema =
|
|
1349
|
-
partyId:
|
|
1350
|
-
websiteId:
|
|
1411
|
+
var removePartyWebsiteByIdSchema = z6.object({
|
|
1412
|
+
partyId: positiveId,
|
|
1413
|
+
websiteId: positiveId.describe(
|
|
1351
1414
|
"Capsule's id for the website row. Read it from get_party (each entry in websites carries an id)."
|
|
1352
1415
|
)
|
|
1353
1416
|
});
|
|
@@ -1369,20 +1432,32 @@ async function removePartyWebsiteById(input) {
|
|
|
1369
1432
|
}
|
|
1370
1433
|
|
|
1371
1434
|
// src/tools/opportunities.ts
|
|
1372
|
-
import { z as
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1435
|
+
import { z as z7 } from "zod";
|
|
1436
|
+
|
|
1437
|
+
// src/tools/preserve-refs.ts
|
|
1438
|
+
async function readEntityRefs(path, responseKey) {
|
|
1439
|
+
const { data } = await capsuleGet(path);
|
|
1440
|
+
const entity = data[responseKey];
|
|
1441
|
+
return {
|
|
1442
|
+
teamId: entity?.team?.id ?? void 0,
|
|
1443
|
+
stageId: entity?.stage?.id ?? void 0
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// src/tools/opportunities.ts
|
|
1448
|
+
var OpportunityValueSchema = z7.object({
|
|
1449
|
+
amount: z7.number().nonnegative(),
|
|
1450
|
+
currency: z7.string({
|
|
1376
1451
|
error: (iss) => iss.code === "invalid_type" && iss.input === void 0 ? "currency is required when amount is set (3-letter ISO 4217 code, e.g. 'USD', 'EUR', 'GBP')" : void 0
|
|
1377
1452
|
}).length(3).describe(
|
|
1378
1453
|
"ISO 4217 currency code (3 letters), e.g. 'GBP', 'USD', 'EUR'. Required when amount is set."
|
|
1379
1454
|
)
|
|
1380
1455
|
});
|
|
1381
|
-
var searchOpportunitiesSchema =
|
|
1382
|
-
q:
|
|
1383
|
-
embed:
|
|
1384
|
-
page:
|
|
1385
|
-
perPage:
|
|
1456
|
+
var searchOpportunitiesSchema = z7.object({
|
|
1457
|
+
q: z7.string().optional().describe("Free-text search query"),
|
|
1458
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
1459
|
+
page: z7.number().int().positive().optional().default(1),
|
|
1460
|
+
perPage: z7.number().int().min(1).max(100).optional().default(25)
|
|
1386
1461
|
});
|
|
1387
1462
|
async function searchOpportunities(input) {
|
|
1388
1463
|
const path = input.q ? "/opportunities/search" : "/opportunities";
|
|
@@ -1394,9 +1469,9 @@ async function searchOpportunities(input) {
|
|
|
1394
1469
|
});
|
|
1395
1470
|
return { ...data, nextPage };
|
|
1396
1471
|
}
|
|
1397
|
-
var getOpportunitySchema =
|
|
1398
|
-
id:
|
|
1399
|
-
embed:
|
|
1472
|
+
var getOpportunitySchema = z7.object({
|
|
1473
|
+
id: positiveId,
|
|
1474
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1400
1475
|
});
|
|
1401
1476
|
async function getOpportunity(input) {
|
|
1402
1477
|
const { data } = await capsuleGet(`/opportunities/${input.id}`, {
|
|
@@ -1404,11 +1479,11 @@ async function getOpportunity(input) {
|
|
|
1404
1479
|
});
|
|
1405
1480
|
return data;
|
|
1406
1481
|
}
|
|
1407
|
-
var getOpportunitiesSchema =
|
|
1408
|
-
ids:
|
|
1482
|
+
var getOpportunitiesSchema = z7.object({
|
|
1483
|
+
ids: z7.array(positiveId).min(1).max(50).describe(
|
|
1409
1484
|
"Array of opportunity IDs (1\u201350). Capsule's native batch-fetch endpoint caps at 10 per request; the connector transparently splits larger sets into 10-id chunks and fans out the Capsule calls in parallel."
|
|
1410
1485
|
),
|
|
1411
|
-
embed:
|
|
1486
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1412
1487
|
});
|
|
1413
1488
|
async function getOpportunities(input) {
|
|
1414
1489
|
const { ids, embed } = input;
|
|
@@ -1429,102 +1504,96 @@ async function getOpportunities(input) {
|
|
|
1429
1504
|
);
|
|
1430
1505
|
return { opportunities: responses.flatMap((r) => r.data.opportunities) };
|
|
1431
1506
|
}
|
|
1432
|
-
var createOpportunitySchema =
|
|
1433
|
-
name:
|
|
1434
|
-
partyId:
|
|
1435
|
-
milestoneId:
|
|
1436
|
-
"ID of the pipeline milestone to place this opportunity at. The milestone implicitly determines the pipeline \u2014 there is no separate pipelineId parameter. Discover via list_pipelines / list_milestones."
|
|
1507
|
+
var createOpportunitySchema = z7.object({
|
|
1508
|
+
name: z7.string().min(1),
|
|
1509
|
+
partyId: positiveId.describe("ID of the party this opportunity belongs to"),
|
|
1510
|
+
milestoneId: positiveId.describe(
|
|
1511
|
+
"ID of the pipeline milestone to place this opportunity at. The milestone implicitly determines the pipeline \u2014 there is no separate pipelineId parameter. Discover via list_pipelines / list_milestones. NOTE: some Capsule tenants configure **pipeline / milestone-reached automation rules** that mutate `owner` and/or `team` immediately after creation \u2014 e.g. an 'Assign to a Team' action that fires on entry to a specific milestone and has been observed to clear `owner` as an automation side-effect. If you observe a newly-created opp landing with `owner: null` despite passing `ownerId`, the cause is almost certainly a milestone automation on the destination pipeline rather than the connector. Documented workaround: follow `create_opportunity` with an immediate `batch_update_opportunity({items: [{id, ownerId, teamId}]})` carrying both fields \u2014 PUT does not re-fire milestone-reached triggers, so the owner sticks."
|
|
1437
1512
|
),
|
|
1438
|
-
description:
|
|
1513
|
+
description: z7.string().optional(),
|
|
1439
1514
|
value: OpportunityValueSchema.optional(),
|
|
1440
|
-
expectedCloseOn:
|
|
1441
|
-
probability:
|
|
1442
|
-
ownerId:
|
|
1443
|
-
"Assign to user ID. Defaults to the API-token owner when omitted \u2014 note that opportunities do NOT inherit owner from the linked party, even though one might expect it. Once set, this connector cannot clear the owner back to null (use Capsule's web UI). Discover IDs via list_users."
|
|
1515
|
+
expectedCloseOn: z7.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD"),
|
|
1516
|
+
probability: z7.number().int().min(0).max(100).optional(),
|
|
1517
|
+
ownerId: positiveId.optional().describe(
|
|
1518
|
+
"Assign to user ID. Defaults to the API-token owner when omitted \u2014 note that opportunities do NOT inherit owner from the linked party, even though one might expect it. Once set, this connector cannot clear the owner back to null (use Capsule's web UI). Discover IDs via list_users. WARNING: tenant pipeline / milestone-reached automation can mutate this field post-create \u2014 see the `milestoneId` description for details and the chained-PUT workaround."
|
|
1519
|
+
),
|
|
1520
|
+
teamId: positiveId.optional().describe(
|
|
1521
|
+
"Assign to team ID (discover via list_teams). Independent from `ownerId` \u2014 setting one does NOT clear the other on create. Three ownership shapes are valid: owner alone, team alone, or owner+team (the owner must be a member of the team; users can belong to multiple teams \u2014 422 'owner is not a member of the team' otherwise)."
|
|
1444
1522
|
)
|
|
1445
1523
|
});
|
|
1446
1524
|
async function createOpportunity(input) {
|
|
1447
|
-
const { partyId, milestoneId, ownerId, ...rest } = input;
|
|
1525
|
+
const { partyId, milestoneId, ownerId, teamId, ...rest } = input;
|
|
1448
1526
|
const body = {
|
|
1449
1527
|
...rest,
|
|
1450
1528
|
party: { id: partyId },
|
|
1451
1529
|
milestone: { id: milestoneId }
|
|
1452
1530
|
};
|
|
1453
|
-
|
|
1531
|
+
setRef(body, "owner", ownerId);
|
|
1532
|
+
setRef(body, "team", teamId);
|
|
1454
1533
|
return capsulePost("/opportunities", { opportunity: body });
|
|
1455
1534
|
}
|
|
1456
|
-
var updateOpportunitySchema =
|
|
1457
|
-
id:
|
|
1458
|
-
name:
|
|
1459
|
-
milestoneId:
|
|
1460
|
-
"Move the opportunity to this milestone. Side effects depend on the target: closing milestones (Won/Lost) auto-set `closedOn` to today and `probability` to the milestone default (100/0), preserving `lastOpenMilestone` as the previous open stage; moving back to an open milestone clears `closedOn` and re-applies the milestone's default probability (Won/Lost is reversible \u2014 no separate reopen tool). WARNING: Capsule does NOT validate that the new milestone belongs to the opportunity's current pipeline. Passing a milestoneId from a different pipeline silently relocates the opportunity across pipelines, and `lastOpenMilestone` may then reference a milestone in the previous pipeline. Verify against the opportunity's current pipeline (read the opp first, list its pipeline's milestones via list_milestones) before passing a cross-pipeline id."
|
|
1535
|
+
var updateOpportunitySchema = z7.object({
|
|
1536
|
+
id: positiveId,
|
|
1537
|
+
name: z7.string().min(1).optional(),
|
|
1538
|
+
milestoneId: positiveId.optional().describe(
|
|
1539
|
+
"Move the opportunity to this milestone. Side effects depend on the target: closing milestones (Won/Lost) auto-set `closedOn` to today and `probability` to the milestone default (100/0), preserving `lastOpenMilestone` as the previous open stage; moving back to an open milestone clears `closedOn` and re-applies the milestone's default probability (Won/Lost is reversible \u2014 no separate reopen tool). WARNING: Capsule does NOT validate that the new milestone belongs to the opportunity's current pipeline. Passing a milestoneId from a different pipeline silently relocates the opportunity across pipelines, and `lastOpenMilestone` may then reference a milestone in the previous pipeline. Verify against the opportunity's current pipeline (read the opp first, list its pipeline's milestones via list_milestones) before passing a cross-pipeline id. NOTE: changing `milestoneId` can fire **pipeline / milestone-reached automations** that mutate `owner` / `team` on the destination milestone (same shape as `create_opportunity` \u2014 see its `milestoneId` description for the owner-clearing automation caveat). If a milestone-change-and-owner-set in the same call lands with `owner: null`, follow up with a second `update_opportunity` (or `batch_update_opportunity`) carrying both `ownerId` and `teamId` \u2014 milestone-reached triggers only fire on the transition, so a subsequent PUT preserves your values."
|
|
1461
1540
|
),
|
|
1462
|
-
description:
|
|
1541
|
+
description: z7.string().optional(),
|
|
1463
1542
|
value: OpportunityValueSchema.optional(),
|
|
1464
|
-
expectedCloseOn:
|
|
1465
|
-
probability:
|
|
1543
|
+
expectedCloseOn: z7.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
1544
|
+
probability: z7.number().int().min(0).max(100).optional().describe(
|
|
1466
1545
|
"Win probability 0\u2013100. On an open milestone this overrides the milestone's default probability. CANNOT be set in the same call as a closing milestone (Won/Lost) \u2014 Capsule processes the milestone change first, the opportunity becomes closed, then the probability update is rejected as edit-on-closed-opp with 422 'probability can be updated only for open opportunity'. To close an opportunity, leave probability out of the call: it auto-snaps to 100% (Won) or 0% (Lost)."
|
|
1467
1546
|
),
|
|
1468
|
-
lostReasonId:
|
|
1547
|
+
lostReasonId: positiveId.optional().describe(
|
|
1469
1548
|
"Reason the opportunity was lost. Only meaningful when transitioning to a Lost milestone \u2014 Capsule silently drops it for other milestones. Without this set, a connector-driven Lost-close leaves `lostReason: null`. Discover IDs via list_lostreasons."
|
|
1470
1549
|
),
|
|
1471
|
-
ownerId:
|
|
1472
|
-
"Reassign owner to user ID. Once set, this connector cannot clear an owner back to null \u2014 use Capsule's web UI for that."
|
|
1550
|
+
ownerId: positiveId.optional().describe(
|
|
1551
|
+
"Reassign owner to user ID. Once set, this connector cannot clear an owner back to null \u2014 use Capsule's web UI for that. When you supply `ownerId` and omit `teamId`, the connector fetches the opportunity's current team and includes it in the PUT body to preserve it across the owner change. Without this defensive read, Capsule's PUT would clear the existing team (see NOTES-ON-CAPSULE-API.md \xA727 \u2014 same asymmetric semantic as /kases). Supply `teamId` explicitly on the same call to change the team instead."
|
|
1552
|
+
),
|
|
1553
|
+
teamId: positiveId.nullable().optional().describe(
|
|
1554
|
+
"Reassign team: pass a team ID (discover via list_teams) to set, or `null` to unassign. Capsule preserves the existing owner across a team change (server-side), so `update_opportunity { teamId }` alone is safe \u2014 the owner is carried through. Owner must be a member of the new team or Capsule returns 422 'owner is not a member of the team'. Independent from `ownerId` \u2014 setting `teamId` does NOT clear the owner."
|
|
1473
1555
|
),
|
|
1474
|
-
fields:
|
|
1556
|
+
fields: z7.array(CustomFieldWriteSchema).optional().describe(fieldsArrayDescriptor("get_opportunity"))
|
|
1475
1557
|
});
|
|
1476
1558
|
async function updateOpportunity(input) {
|
|
1477
|
-
const { id, milestoneId, ownerId, lostReasonId, fields, ...rest } = input;
|
|
1559
|
+
const { id, milestoneId, ownerId, teamId, lostReasonId, fields, ...rest } = input;
|
|
1478
1560
|
const body = {};
|
|
1479
1561
|
for (const [k, v] of Object.entries(rest)) {
|
|
1480
1562
|
if (v !== void 0) body[k] = v;
|
|
1481
1563
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
if (
|
|
1564
|
+
setRef(body, "milestone", milestoneId);
|
|
1565
|
+
let resolvedTeamId = teamId;
|
|
1566
|
+
if (ownerId !== void 0 && teamId === void 0) {
|
|
1567
|
+
({ teamId: resolvedTeamId } = await readEntityRefs(`/opportunities/${id}`, "opportunity"));
|
|
1568
|
+
}
|
|
1569
|
+
setRef(body, "owner", ownerId);
|
|
1570
|
+
setNullableRef(body, "team", resolvedTeamId);
|
|
1571
|
+
setRef(body, "lostReason", lostReasonId);
|
|
1485
1572
|
const mappedFields = mapFieldsForBody(fields);
|
|
1486
1573
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1487
1574
|
return capsulePut(`/opportunities/${id}`, {
|
|
1488
1575
|
opportunity: body
|
|
1489
1576
|
});
|
|
1490
1577
|
}
|
|
1491
|
-
var batchUpdateOpportunitySchema =
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1578
|
+
var { schema: batchUpdateOpportunitySchema, handler: batchUpdateOpportunity } = defineBatch({
|
|
1579
|
+
toolName: "batch_update_opportunity",
|
|
1580
|
+
itemSchema: updateOpportunitySchema,
|
|
1581
|
+
itemDescription: "Array of 1\u201350 update_opportunity inputs. Each item is the same shape as a single update_opportunity call \u2014 id is required, every other field is optional. Capped at 50 so a single tool call can't burn an outsized share of Capsule's hourly per-token rate budget.",
|
|
1582
|
+
itemHandler: updateOpportunity
|
|
1495
1583
|
});
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
(item) => updateOpportunity(item),
|
|
1501
|
-
opts
|
|
1502
|
-
);
|
|
1503
|
-
}
|
|
1504
|
-
var deleteOpportunitySchema = z4.object({
|
|
1505
|
-
id: z4.number().int().positive(),
|
|
1506
|
-
confirm: confirmFlag().describe(
|
|
1507
|
-
"Must be set to true. Permanently deletes the opportunity. Irreversible."
|
|
1508
|
-
)
|
|
1584
|
+
var { schema: deleteOpportunitySchema, handler: deleteOpportunity } = defineDelete({
|
|
1585
|
+
toolName: "delete_opportunity",
|
|
1586
|
+
pathPrefix: "/opportunities",
|
|
1587
|
+
confirmHint: "Must be set to true. Permanently deletes the opportunity. Irreversible."
|
|
1509
1588
|
});
|
|
1510
|
-
async function deleteOpportunity(input) {
|
|
1511
|
-
if (input.confirm !== true) {
|
|
1512
|
-
throw new Error("delete_opportunity requires confirm: true");
|
|
1513
|
-
}
|
|
1514
|
-
return idempotent(
|
|
1515
|
-
() => capsuleDelete(`/opportunities/${input.id}`),
|
|
1516
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1517
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1518
|
-
);
|
|
1519
|
-
}
|
|
1520
1589
|
|
|
1521
1590
|
// src/tools/projects.ts
|
|
1522
|
-
import { z as
|
|
1523
|
-
var listProjectsSchema =
|
|
1524
|
-
status:
|
|
1525
|
-
embed:
|
|
1526
|
-
page:
|
|
1527
|
-
perPage:
|
|
1591
|
+
import { z as z8 } from "zod";
|
|
1592
|
+
var listProjectsSchema = z8.object({
|
|
1593
|
+
status: z8.enum(["OPEN", "CLOSED"]).optional(),
|
|
1594
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
1595
|
+
page: z8.number().int().positive().optional().default(1),
|
|
1596
|
+
perPage: z8.number().int().min(1).max(100).optional().default(25)
|
|
1528
1597
|
});
|
|
1529
1598
|
async function listProjects(input) {
|
|
1530
1599
|
const { data, nextPage } = await capsuleGet("/kases", {
|
|
@@ -1535,9 +1604,9 @@ async function listProjects(input) {
|
|
|
1535
1604
|
});
|
|
1536
1605
|
return { ...data, nextPage };
|
|
1537
1606
|
}
|
|
1538
|
-
var getProjectSchema =
|
|
1539
|
-
id:
|
|
1540
|
-
embed:
|
|
1607
|
+
var getProjectSchema = z8.object({
|
|
1608
|
+
id: positiveId,
|
|
1609
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1541
1610
|
});
|
|
1542
1611
|
async function getProject(input) {
|
|
1543
1612
|
const { data } = await capsuleGet(`/kases/${input.id}`, {
|
|
@@ -1545,11 +1614,11 @@ async function getProject(input) {
|
|
|
1545
1614
|
});
|
|
1546
1615
|
return data;
|
|
1547
1616
|
}
|
|
1548
|
-
var getProjectsSchema =
|
|
1549
|
-
ids:
|
|
1617
|
+
var getProjectsSchema = z8.object({
|
|
1618
|
+
ids: z8.array(positiveId).min(1).max(50).describe(
|
|
1550
1619
|
"Array of project IDs (1\u201350). Capsule's native batch-fetch endpoint caps at 10 per request; the connector transparently splits larger sets into 10-id chunks and fans out the Capsule calls in parallel."
|
|
1551
1620
|
),
|
|
1552
|
-
embed:
|
|
1621
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1553
1622
|
});
|
|
1554
1623
|
async function getProjects(input) {
|
|
1555
1624
|
const { ids, embed } = input;
|
|
@@ -1567,21 +1636,21 @@ async function getProjects(input) {
|
|
|
1567
1636
|
);
|
|
1568
1637
|
return { kases: responses.flatMap((r) => r.data.kases) };
|
|
1569
1638
|
}
|
|
1570
|
-
var createProjectSchema =
|
|
1571
|
-
name:
|
|
1572
|
-
partyId:
|
|
1573
|
-
description:
|
|
1574
|
-
status:
|
|
1575
|
-
ownerId:
|
|
1639
|
+
var createProjectSchema = z8.object({
|
|
1640
|
+
name: z8.string().min(1),
|
|
1641
|
+
partyId: positiveId.describe("ID of the party linked to this project"),
|
|
1642
|
+
description: z8.string().optional(),
|
|
1643
|
+
status: z8.enum(["OPEN", "CLOSED"]).optional().describe("Defaults to OPEN when omitted."),
|
|
1644
|
+
ownerId: positiveId.optional().describe(
|
|
1576
1645
|
"Assign to user ID. Defaults to the API-token owner when omitted, same as create_party / create_opportunity / create_task. NOTE: some Capsule tenants configure board-level **automation rules** that mutate `owner` (and `team`) on project creation \u2014 e.g. an automation that clears `owner` when a project enters a particular board. If you observe a project landing with unexpected `owner: null` after a create_project with `ownerId`, check the target board's automation configuration. Capsule's API itself does not drop `ownerId` when `stageId` is also supplied."
|
|
1577
1646
|
),
|
|
1578
|
-
teamId:
|
|
1647
|
+
teamId: positiveId.optional().describe(
|
|
1579
1648
|
"Assign to team ID (discover via list_teams). Capsule projects must always have at least one of {owner, team} set \u2014 Capsule returns 422 'owner or team is required' otherwise. Three ownership shapes are valid: owner alone, team alone, or owner+team (the user must be a member of the team \u2014 users can belong to multiple teams; 422 'owner is not a member of the team' otherwise). Tenant-specific board automations may set the team field on project creation (e.g. 'when project enters board X, set team to T'). If you observe a team set despite omitting `teamId`, check the target board's automation rules."
|
|
1580
1649
|
),
|
|
1581
|
-
stageId:
|
|
1650
|
+
stageId: positiveId.optional().describe(
|
|
1582
1651
|
"Stage (board column) to place the project on. Discover IDs via list_stages \u2014 each stage belongs to one Board, so picking a stageId implicitly picks the board. If omitted, the project is created with no stage assignment (and won't appear on any board). NOTE: tenant-specific board automation rules may run on project creation and mutate `owner` / `team` fields. See `create_project.ownerId` / `create_project.teamId` for the automation caveat. Capsule's create endpoint itself preserves the `ownerId` / `teamId` you supply \u2014 any clearing you observe traces to board automations, not the API."
|
|
1583
1652
|
),
|
|
1584
|
-
expectedCloseOn:
|
|
1653
|
+
expectedCloseOn: z8.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD")
|
|
1585
1654
|
});
|
|
1586
1655
|
async function createProject(input) {
|
|
1587
1656
|
const { partyId, ownerId, teamId, status, stageId, ...rest } = input;
|
|
@@ -1590,27 +1659,27 @@ async function createProject(input) {
|
|
|
1590
1659
|
status: status ?? "OPEN",
|
|
1591
1660
|
party: { id: partyId }
|
|
1592
1661
|
};
|
|
1593
|
-
|
|
1594
|
-
|
|
1662
|
+
setRef(body, "owner", ownerId);
|
|
1663
|
+
setRef(body, "team", teamId);
|
|
1595
1664
|
if (stageId) body["stage"] = stageId;
|
|
1596
1665
|
return capsulePost("/kases", { kase: body });
|
|
1597
1666
|
}
|
|
1598
|
-
var updateProjectSchema =
|
|
1599
|
-
id:
|
|
1600
|
-
name:
|
|
1601
|
-
description:
|
|
1602
|
-
status:
|
|
1603
|
-
ownerId:
|
|
1667
|
+
var updateProjectSchema = z8.object({
|
|
1668
|
+
id: positiveId,
|
|
1669
|
+
name: z8.string().min(1).optional(),
|
|
1670
|
+
description: z8.string().optional(),
|
|
1671
|
+
status: z8.enum(["OPEN", "CLOSED"]).optional(),
|
|
1672
|
+
ownerId: positiveId.nullable().optional().describe(
|
|
1604
1673
|
"Reassign owner: pass a user ID to set, or `null` to unassign (matches the 'Unassign' option in Capsule's web UI). When you supply `ownerId` and omit `teamId` and/or `stageId`, the connector fetches the project's current omitted fields and includes them in the PUT body \u2014 this preserves them across the owner change (without it, Capsule's PUT would clear team; stage carry is defensive against the symmetric clear). Supply `teamId` and/or `stageId` explicitly on the same call to change them instead. `teamId: null` clears the team as part of an owner change. Constraints (Capsule enforces, 422 on violation): owner must be a member of the team if both are set; a project must always have at least one of {owner, team} set (cannot clear both)."
|
|
1605
1674
|
),
|
|
1606
|
-
teamId:
|
|
1675
|
+
teamId: positiveId.nullable().optional().describe(
|
|
1607
1676
|
"Reassign team: pass a team ID (discover via list_teams) to set, or `null` to unassign. Capsule preserves the existing owner across a team change (server-side), so `update_project { teamId }` alone is safe \u2014 the owner is carried through. Owner must be a member of the new team or Capsule returns 422 'owner is not a member of the team'. A project must always have at least one of {owner, team} set \u2014 `teamId: null` on a project whose owner is already null returns 422 'owner or team is required'."
|
|
1608
1677
|
),
|
|
1609
|
-
stageId:
|
|
1678
|
+
stageId: positiveId.optional().describe(
|
|
1610
1679
|
"Move the project to this stage (board column). Discover IDs via list_stages. Owner and team are preserved across stage-only updates (Capsule's PUT semantic). WARNING (cross-board): Capsule does NOT validate that the new stage belongs to the project's current board \u2014 passing a stageId from a different board silently relocates the project across boards. Team and other board-derived defaults are NOT updated to match the new board. Verify against the project's current board (read the project first, list its board's stages) before passing a cross-board id."
|
|
1611
1680
|
),
|
|
1612
|
-
expectedCloseOn:
|
|
1613
|
-
fields:
|
|
1681
|
+
expectedCloseOn: z8.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD"),
|
|
1682
|
+
fields: z8.array(CustomFieldWriteSchema).optional().describe(
|
|
1614
1683
|
fieldsArrayDescriptor("get_project") + " Project-specific: setting a field whose definition lives under a 'data tag' populates the row's internal tagId but does NOT auto-add the data tag to the project's tags array \u2014 use add_tag explicitly if you want it visible via embed=tags."
|
|
1615
1684
|
)
|
|
1616
1685
|
});
|
|
@@ -1623,55 +1692,38 @@ async function updateProject(input) {
|
|
|
1623
1692
|
let resolvedTeamId = teamId;
|
|
1624
1693
|
let resolvedStageId = stageId;
|
|
1625
1694
|
if (ownerId !== void 0 && (teamId === void 0 || stageId === void 0)) {
|
|
1626
|
-
const
|
|
1627
|
-
if (teamId === void 0)
|
|
1628
|
-
|
|
1629
|
-
}
|
|
1630
|
-
if (stageId === void 0) {
|
|
1631
|
-
resolvedStageId = data.kase?.stage?.id ?? void 0;
|
|
1632
|
-
}
|
|
1695
|
+
const current = await readEntityRefs(`/kases/${id}`, "kase");
|
|
1696
|
+
if (teamId === void 0) resolvedTeamId = current.teamId;
|
|
1697
|
+
if (stageId === void 0) resolvedStageId = current.stageId;
|
|
1633
1698
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
if (resolvedTeamId === null) body["team"] = null;
|
|
1637
|
-
else if (resolvedTeamId !== void 0) body["team"] = { id: resolvedTeamId };
|
|
1699
|
+
setNullableRef(body, "owner", ownerId);
|
|
1700
|
+
setNullableRef(body, "team", resolvedTeamId);
|
|
1638
1701
|
if (resolvedStageId) body["stage"] = resolvedStageId;
|
|
1639
1702
|
const mappedFields = mapFieldsForBody(fields);
|
|
1640
1703
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1641
1704
|
return capsulePut(`/kases/${id}`, { kase: body });
|
|
1642
1705
|
}
|
|
1643
|
-
var deleteProjectSchema =
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
)
|
|
1706
|
+
var { schema: deleteProjectSchema, handler: deleteProject } = defineDelete({
|
|
1707
|
+
toolName: "delete_project",
|
|
1708
|
+
pathPrefix: "/kases",
|
|
1709
|
+
confirmHint: "Must be set to true. Permanently deletes the project (case). Consider update_project status='CLOSED' instead. Irreversible."
|
|
1648
1710
|
});
|
|
1649
|
-
async function deleteProject(input) {
|
|
1650
|
-
if (input.confirm !== true) {
|
|
1651
|
-
throw new Error("delete_project requires confirm: true");
|
|
1652
|
-
}
|
|
1653
|
-
return idempotent(
|
|
1654
|
-
() => capsuleDelete(`/kases/${input.id}`),
|
|
1655
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1656
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1657
|
-
);
|
|
1658
|
-
}
|
|
1659
1711
|
|
|
1660
1712
|
// src/tools/tasks.ts
|
|
1661
|
-
import { z as
|
|
1662
|
-
var listTasksSchema =
|
|
1713
|
+
import { z as z9 } from "zod";
|
|
1714
|
+
var listTasksSchema = z9.object({
|
|
1663
1715
|
// Note: Capsule has a third internal status `PENDING` (a task that's
|
|
1664
1716
|
// part of an active track but not yet "open"), but it can only be
|
|
1665
1717
|
// reached via track machinery — it is NOT directly settable by
|
|
1666
1718
|
// /tasks PUT, and a list filter for it returns the same as OPEN
|
|
1667
1719
|
// anyway. We expose only the two values that are actually filterable
|
|
1668
1720
|
// by the v2 API.
|
|
1669
|
-
status:
|
|
1721
|
+
status: z9.enum(["OPEN", "COMPLETED"]).optional().describe(
|
|
1670
1722
|
"Defaults to OPEN when omitted. Pass COMPLETED to filter to completed tasks, or 'OPEN' explicitly."
|
|
1671
1723
|
),
|
|
1672
|
-
ownerId:
|
|
1673
|
-
page:
|
|
1674
|
-
perPage:
|
|
1724
|
+
ownerId: positiveId.optional().describe("Filter to tasks owned by this user ID"),
|
|
1725
|
+
page: z9.number().int().positive().optional().default(1),
|
|
1726
|
+
perPage: z9.number().int().min(1).max(100).optional().default(25)
|
|
1675
1727
|
});
|
|
1676
1728
|
async function listTasks(input) {
|
|
1677
1729
|
const { data, nextPage } = await capsuleGet("/tasks", {
|
|
@@ -1685,15 +1737,15 @@ async function listTasks(input) {
|
|
|
1685
1737
|
});
|
|
1686
1738
|
return { ...data, nextPage };
|
|
1687
1739
|
}
|
|
1688
|
-
var getTaskSchema =
|
|
1689
|
-
id:
|
|
1740
|
+
var getTaskSchema = z9.object({
|
|
1741
|
+
id: positiveId.describe("Task ID")
|
|
1690
1742
|
});
|
|
1691
1743
|
async function getTask(input) {
|
|
1692
1744
|
const { data } = await capsuleGet(`/tasks/${input.id}`);
|
|
1693
1745
|
return data;
|
|
1694
1746
|
}
|
|
1695
|
-
var getTasksSchema =
|
|
1696
|
-
ids:
|
|
1747
|
+
var getTasksSchema = z9.object({
|
|
1748
|
+
ids: z9.array(positiveId).min(1).max(50).describe(
|
|
1697
1749
|
"Array of task IDs (1\u201350). Capsule's native batch-fetch endpoint caps at 10 per request; the connector transparently splits larger sets into 10-id chunks and fans out the Capsule calls in parallel."
|
|
1698
1750
|
)
|
|
1699
1751
|
});
|
|
@@ -1709,17 +1761,17 @@ async function getTasks(input) {
|
|
|
1709
1761
|
);
|
|
1710
1762
|
return { tasks: responses.flatMap((r) => r.data.tasks) };
|
|
1711
1763
|
}
|
|
1712
|
-
var createTaskSchema =
|
|
1713
|
-
description:
|
|
1714
|
-
dueOn:
|
|
1715
|
-
dueTime:
|
|
1716
|
-
detail:
|
|
1717
|
-
ownerId:
|
|
1764
|
+
var createTaskSchema = z9.object({
|
|
1765
|
+
description: z9.string().min(1),
|
|
1766
|
+
dueOn: z9.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("YYYY-MM-DD"),
|
|
1767
|
+
dueTime: z9.string().regex(/^\d{2}:\d{2}$/).optional().describe("HH:MM in user's timezone"),
|
|
1768
|
+
detail: z9.string().optional(),
|
|
1769
|
+
ownerId: positiveId.optional().describe(
|
|
1718
1770
|
"Assign to user ID. Defaults to the API-token owner when omitted. Once set, this connector cannot clear the owner back to null \u2014 use Capsule's web UI for that."
|
|
1719
1771
|
),
|
|
1720
|
-
partyId:
|
|
1721
|
-
opportunityId:
|
|
1722
|
-
projectId:
|
|
1772
|
+
partyId: positiveId.optional().describe("Link task to a party (mutually exclusive with opportunityId/projectId)"),
|
|
1773
|
+
opportunityId: positiveId.optional().describe("Link task to an opportunity (mutually exclusive with partyId/projectId)"),
|
|
1774
|
+
projectId: positiveId.optional().describe("Link task to a project (mutually exclusive with partyId/opportunityId)")
|
|
1723
1775
|
});
|
|
1724
1776
|
async function createTask(input) {
|
|
1725
1777
|
const linked = [input.partyId, input.opportunityId, input.projectId].filter(Boolean);
|
|
@@ -1728,25 +1780,25 @@ async function createTask(input) {
|
|
|
1728
1780
|
}
|
|
1729
1781
|
const { ownerId, partyId, opportunityId, projectId, ...rest } = input;
|
|
1730
1782
|
const body = { ...rest };
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1783
|
+
setRef(body, "owner", ownerId);
|
|
1784
|
+
setRef(body, "party", partyId);
|
|
1785
|
+
setRef(body, "opportunity", opportunityId);
|
|
1786
|
+
setRef(body, "kase", projectId);
|
|
1735
1787
|
return capsulePost("/tasks", { task: body });
|
|
1736
1788
|
}
|
|
1737
|
-
var updateTaskSchema =
|
|
1738
|
-
id:
|
|
1739
|
-
description:
|
|
1740
|
-
dueOn:
|
|
1741
|
-
dueTime:
|
|
1742
|
-
detail:
|
|
1789
|
+
var updateTaskSchema = z9.object({
|
|
1790
|
+
id: positiveId,
|
|
1791
|
+
description: z9.string().min(1).optional(),
|
|
1792
|
+
dueOn: z9.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD"),
|
|
1793
|
+
dueTime: z9.string().regex(/^\d{2}:\d{2}$/).optional().describe("HH:MM in user's timezone"),
|
|
1794
|
+
detail: z9.string().optional(),
|
|
1743
1795
|
// Capsule rejects direct sets of `PENDING` (which is a track-machinery
|
|
1744
1796
|
// internal state) with 422 "cannot set task status to PENDING".
|
|
1745
1797
|
// Only OPEN and COMPLETED are settable here.
|
|
1746
|
-
status:
|
|
1798
|
+
status: z9.enum(["OPEN", "COMPLETED"]).optional().describe(
|
|
1747
1799
|
"Set to OPEN or COMPLETED. (PENDING exists internally for track-driven tasks but cannot be set directly via this tool \u2014 Capsule rejects it.) Setting status: OPEN on an already-open task is a true no-op (does not advance updatedAt)."
|
|
1748
1800
|
),
|
|
1749
|
-
ownerId:
|
|
1801
|
+
ownerId: positiveId.optional().describe(
|
|
1750
1802
|
"Reassign owner to user ID. Once set, this connector cannot clear an owner back to null \u2014 use Capsule's web UI for that."
|
|
1751
1803
|
)
|
|
1752
1804
|
});
|
|
@@ -1756,51 +1808,40 @@ async function updateTask(input) {
|
|
|
1756
1808
|
for (const [k, v] of Object.entries(rest)) {
|
|
1757
1809
|
if (v !== void 0) body[k] = v;
|
|
1758
1810
|
}
|
|
1759
|
-
|
|
1811
|
+
setRef(body, "owner", ownerId);
|
|
1760
1812
|
return capsulePut(`/tasks/${id}`, { task: body });
|
|
1761
1813
|
}
|
|
1762
|
-
var completeTaskSchema =
|
|
1763
|
-
id:
|
|
1814
|
+
var completeTaskSchema = z9.object({
|
|
1815
|
+
id: positiveId
|
|
1764
1816
|
});
|
|
1765
1817
|
async function completeTask(input) {
|
|
1766
1818
|
return capsulePut(`/tasks/${input.id}`, {
|
|
1767
1819
|
task: { status: "COMPLETED" }
|
|
1768
1820
|
});
|
|
1769
1821
|
}
|
|
1770
|
-
var batchCompleteTaskSchema =
|
|
1771
|
-
ids:
|
|
1822
|
+
var batchCompleteTaskSchema = z9.object({
|
|
1823
|
+
ids: z9.array(positiveId).min(1).max(50).describe(
|
|
1772
1824
|
"Array of 1\u201350 task ids to mark COMPLETED in parallel. Each id resolves to one PUT /tasks/{id}; failures (e.g. 404 for a deleted task) surface per-item in the result array, the rest still complete. Capped at 50."
|
|
1773
1825
|
)
|
|
1774
1826
|
});
|
|
1775
1827
|
async function batchCompleteTask(input, opts = {}) {
|
|
1776
1828
|
return batchExecute("batch_complete_task", input.ids, (id) => completeTask({ id }), opts);
|
|
1777
1829
|
}
|
|
1778
|
-
var deleteTaskSchema =
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
)
|
|
1830
|
+
var { schema: deleteTaskSchema, handler: deleteTask } = defineDelete({
|
|
1831
|
+
toolName: "delete_task",
|
|
1832
|
+
pathPrefix: "/tasks",
|
|
1833
|
+
confirmHint: "Must be set to true. Permanently deletes the task. To mark done without losing history use complete_task. Irreversible."
|
|
1783
1834
|
});
|
|
1784
|
-
async function deleteTask(input) {
|
|
1785
|
-
if (input.confirm !== true) {
|
|
1786
|
-
throw new Error("delete_task requires confirm: true");
|
|
1787
|
-
}
|
|
1788
|
-
return idempotent(
|
|
1789
|
-
() => capsuleDelete(`/tasks/${input.id}`),
|
|
1790
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1791
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1792
|
-
);
|
|
1793
|
-
}
|
|
1794
1835
|
|
|
1795
1836
|
// src/tools/entries.ts
|
|
1796
|
-
import { z as
|
|
1837
|
+
import { z as z10 } from "zod";
|
|
1797
1838
|
var listEntriesPagination = {
|
|
1798
|
-
page:
|
|
1799
|
-
perPage:
|
|
1800
|
-
embed:
|
|
1839
|
+
page: z10.number().int().positive().optional().default(1),
|
|
1840
|
+
perPage: z10.number().int().min(1).max(100).optional().default(25),
|
|
1841
|
+
embed: z10.string().optional().describe(EMBED_ATTACHMENTS_PARTICIPANTS_DESCRIPTION)
|
|
1801
1842
|
};
|
|
1802
|
-
var listPartyEntriesSchema =
|
|
1803
|
-
partyId:
|
|
1843
|
+
var listPartyEntriesSchema = z10.object({
|
|
1844
|
+
partyId: positiveId,
|
|
1804
1845
|
...listEntriesPagination
|
|
1805
1846
|
});
|
|
1806
1847
|
async function listPartyEntries(input) {
|
|
@@ -1810,8 +1851,8 @@ async function listPartyEntries(input) {
|
|
|
1810
1851
|
);
|
|
1811
1852
|
return { ...data, nextPage };
|
|
1812
1853
|
}
|
|
1813
|
-
var listOpportunityEntriesSchema =
|
|
1814
|
-
opportunityId:
|
|
1854
|
+
var listOpportunityEntriesSchema = z10.object({
|
|
1855
|
+
opportunityId: positiveId,
|
|
1815
1856
|
...listEntriesPagination
|
|
1816
1857
|
});
|
|
1817
1858
|
async function listOpportunityEntries(input) {
|
|
@@ -1821,8 +1862,8 @@ async function listOpportunityEntries(input) {
|
|
|
1821
1862
|
);
|
|
1822
1863
|
return { ...data, nextPage };
|
|
1823
1864
|
}
|
|
1824
|
-
var listProjectEntriesSchema =
|
|
1825
|
-
projectId:
|
|
1865
|
+
var listProjectEntriesSchema = z10.object({
|
|
1866
|
+
projectId: positiveId,
|
|
1826
1867
|
...listEntriesPagination
|
|
1827
1868
|
});
|
|
1828
1869
|
async function listProjectEntries(input) {
|
|
@@ -1832,9 +1873,9 @@ async function listProjectEntries(input) {
|
|
|
1832
1873
|
);
|
|
1833
1874
|
return { ...data, nextPage };
|
|
1834
1875
|
}
|
|
1835
|
-
var getEntrySchema =
|
|
1836
|
-
id:
|
|
1837
|
-
embed:
|
|
1876
|
+
var getEntrySchema = z10.object({
|
|
1877
|
+
id: positiveId,
|
|
1878
|
+
embed: z10.string().optional().describe(EMBED_ATTACHMENTS_PARTICIPANTS_DESCRIPTION)
|
|
1838
1879
|
});
|
|
1839
1880
|
async function getEntry(input) {
|
|
1840
1881
|
const { data } = await capsuleGet(`/entries/${input.id}`, {
|
|
@@ -1842,7 +1883,7 @@ async function getEntry(input) {
|
|
|
1842
1883
|
});
|
|
1843
1884
|
return data;
|
|
1844
1885
|
}
|
|
1845
|
-
var listEntriesSchema =
|
|
1886
|
+
var listEntriesSchema = z10.object({
|
|
1846
1887
|
...listEntriesPagination
|
|
1847
1888
|
});
|
|
1848
1889
|
async function listEntries(input) {
|
|
@@ -1853,14 +1894,14 @@ async function listEntries(input) {
|
|
|
1853
1894
|
});
|
|
1854
1895
|
return { ...data, nextPage };
|
|
1855
1896
|
}
|
|
1856
|
-
var addNoteSchema =
|
|
1857
|
-
content:
|
|
1897
|
+
var addNoteSchema = z10.object({
|
|
1898
|
+
content: z10.string().min(1).describe(
|
|
1858
1899
|
"Note body text. Stored verbatim and treated as MARKDOWN \u2014 Capsule's web UI renders the markdown when displaying. Pass markdown source ('# Heading', '**bold**', '- bullet'), not HTML."
|
|
1859
1900
|
),
|
|
1860
|
-
partyId:
|
|
1861
|
-
opportunityId:
|
|
1862
|
-
projectId:
|
|
1863
|
-
entryAt:
|
|
1901
|
+
partyId: positiveId.optional().describe("Link note to a party (mutually exclusive with opportunityId/projectId)"),
|
|
1902
|
+
opportunityId: positiveId.optional().describe("Link note to an opportunity (mutually exclusive with partyId/projectId)"),
|
|
1903
|
+
projectId: positiveId.optional().describe("Link note to a project (mutually exclusive with partyId/opportunityId)"),
|
|
1904
|
+
entryAt: z10.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/).optional().describe(
|
|
1864
1905
|
"ISO-8601 timestamp for when this note actually happened (e.g. '2024-03-15T14:30:00Z'). Defaults to now. Use this for backdating historical notes when migrating from another system. `entryAt` is preserved across subsequent update_entry calls; only `updatedAt` advances on edits. Note attribution flows to the API-token owner \u2014 there is no way to record a note as authored by a different user via this connector (a `creatorId` parameter would enable audit-attribution spoofing on shared-connector deployments, so it is intentionally not exposed)."
|
|
1865
1906
|
)
|
|
1866
1907
|
});
|
|
@@ -1871,18 +1912,18 @@ async function addNote(input) {
|
|
|
1871
1912
|
throw new Error("Provide exactly one of partyId, opportunityId, or projectId");
|
|
1872
1913
|
}
|
|
1873
1914
|
const body = { type: "note", content };
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1915
|
+
setRef(body, "party", partyId);
|
|
1916
|
+
setRef(body, "opportunity", opportunityId);
|
|
1917
|
+
setRef(body, "kase", projectId);
|
|
1877
1918
|
if (entryAt !== void 0) body["entryAt"] = entryAt;
|
|
1878
1919
|
return capsulePost("/entries", { entry: body });
|
|
1879
1920
|
}
|
|
1880
|
-
var updateEntrySchema =
|
|
1881
|
-
id:
|
|
1882
|
-
content:
|
|
1921
|
+
var updateEntrySchema = z10.object({
|
|
1922
|
+
id: positiveId.describe("Entry ID to update"),
|
|
1923
|
+
content: z10.string().min(1).optional().describe(
|
|
1883
1924
|
"New body text for the entry. For notes, this is the markdown content; for emails, the body. Provide only if you want to change it."
|
|
1884
1925
|
),
|
|
1885
|
-
subject:
|
|
1926
|
+
subject: z10.string().optional().describe(
|
|
1886
1927
|
"New subject line. Mostly meaningful on email-type entries; on plain notes Capsule accepts the call (HTTP 200) but **does not store the subject and does not advance `updatedAt`** \u2014 a true no-op for inapplicable fields. `entryAt` (when the note was authored) is preserved across edits; `updatedAt` advances only when an applicable field actually changes. To sort/filter by 'when did this happen', use `entryAt`; for 'last touched', use `updatedAt`."
|
|
1887
1928
|
)
|
|
1888
1929
|
});
|
|
@@ -1896,30 +1937,20 @@ async function updateEntry(input) {
|
|
|
1896
1937
|
}
|
|
1897
1938
|
return capsulePut(`/entries/${id}`, { entry: body });
|
|
1898
1939
|
}
|
|
1899
|
-
var deleteEntrySchema =
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
)
|
|
1940
|
+
var { schema: deleteEntrySchema, handler: deleteEntry } = defineDelete({
|
|
1941
|
+
toolName: "delete_entry",
|
|
1942
|
+
pathPrefix: "/entries",
|
|
1943
|
+
confirmHint: "Must be set to true. Permanently deletes the entry \u2014 use this to remove a note from a party/opportunity/project. Irreversible.",
|
|
1944
|
+
idDescription: "Entry (note/email/task-record) ID"
|
|
1904
1945
|
});
|
|
1905
|
-
async function deleteEntry(input) {
|
|
1906
|
-
if (input.confirm !== true) {
|
|
1907
|
-
throw new Error("delete_entry requires confirm: true");
|
|
1908
|
-
}
|
|
1909
|
-
return idempotent(
|
|
1910
|
-
() => capsuleDelete(`/entries/${input.id}`),
|
|
1911
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1912
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1913
|
-
);
|
|
1914
|
-
}
|
|
1915
1946
|
|
|
1916
1947
|
// src/tools/pipelines.ts
|
|
1917
|
-
import { z as
|
|
1948
|
+
import { z as z11 } from "zod";
|
|
1918
1949
|
var paginationFields = {
|
|
1919
|
-
page:
|
|
1920
|
-
perPage:
|
|
1950
|
+
page: z11.number().int().positive().optional(),
|
|
1951
|
+
perPage: z11.number().int().min(1).max(100).optional()
|
|
1921
1952
|
};
|
|
1922
|
-
var listPipelinesSchema =
|
|
1953
|
+
var listPipelinesSchema = z11.object({ ...paginationFields });
|
|
1923
1954
|
async function listPipelines(input) {
|
|
1924
1955
|
const { data, nextPage } = await capsuleGetCached("/pipelines", {
|
|
1925
1956
|
page: input.page ?? 1,
|
|
@@ -1927,8 +1958,8 @@ async function listPipelines(input) {
|
|
|
1927
1958
|
});
|
|
1928
1959
|
return { ...data, nextPage };
|
|
1929
1960
|
}
|
|
1930
|
-
var listMilestonesSchema =
|
|
1931
|
-
pipelineId:
|
|
1961
|
+
var listMilestonesSchema = z11.object({
|
|
1962
|
+
pipelineId: positiveId,
|
|
1932
1963
|
...paginationFields
|
|
1933
1964
|
});
|
|
1934
1965
|
async function listMilestones(input) {
|
|
@@ -1940,12 +1971,12 @@ async function listMilestones(input) {
|
|
|
1940
1971
|
}
|
|
1941
1972
|
|
|
1942
1973
|
// src/tools/boards.ts
|
|
1943
|
-
import { z as
|
|
1974
|
+
import { z as z12 } from "zod";
|
|
1944
1975
|
var paginationFields2 = {
|
|
1945
|
-
page:
|
|
1946
|
-
perPage:
|
|
1976
|
+
page: z12.number().int().positive().optional(),
|
|
1977
|
+
perPage: z12.number().int().min(1).max(100).optional()
|
|
1947
1978
|
};
|
|
1948
|
-
var listBoardsSchema =
|
|
1979
|
+
var listBoardsSchema = z12.object({ ...paginationFields2 });
|
|
1949
1980
|
async function listBoards(input) {
|
|
1950
1981
|
const { data, nextPage } = await capsuleGetCached("/boards", {
|
|
1951
1982
|
page: input.page ?? 1,
|
|
@@ -1953,8 +1984,8 @@ async function listBoards(input) {
|
|
|
1953
1984
|
});
|
|
1954
1985
|
return { ...data, nextPage };
|
|
1955
1986
|
}
|
|
1956
|
-
var listStagesSchema =
|
|
1957
|
-
boardId:
|
|
1987
|
+
var listStagesSchema = z12.object({
|
|
1988
|
+
boardId: positiveId.optional().describe(
|
|
1958
1989
|
"Optional. If provided, returns only the stages defined on that specific board (uses /boards/{id}/stages). Omit to get all stages across all boards in one call."
|
|
1959
1990
|
),
|
|
1960
1991
|
...paginationFields2
|
|
@@ -1969,7 +2000,7 @@ async function listStages(input) {
|
|
|
1969
2000
|
}
|
|
1970
2001
|
|
|
1971
2002
|
// src/tools/tags.ts
|
|
1972
|
-
import { z as
|
|
2003
|
+
import { z as z13 } from "zod";
|
|
1973
2004
|
var TAG_LIST_PATH = {
|
|
1974
2005
|
parties: "/parties/tags",
|
|
1975
2006
|
opportunities: "/opportunities/tags",
|
|
@@ -1980,11 +2011,11 @@ var ENTITY_TO_WRAPPER = {
|
|
|
1980
2011
|
opportunities: "opportunity",
|
|
1981
2012
|
kases: "kase"
|
|
1982
2013
|
};
|
|
1983
|
-
var TagEntity =
|
|
1984
|
-
var listTagsSchema =
|
|
1985
|
-
entity:
|
|
1986
|
-
page:
|
|
1987
|
-
perPage:
|
|
2014
|
+
var TagEntity = z13.enum(["parties", "opportunities", "kases"]).describe("Which entity type. Use 'kases' for projects (Capsule's legacy path name).");
|
|
2015
|
+
var listTagsSchema = z13.object({
|
|
2016
|
+
entity: z13.enum(["parties", "opportunities", "kases"]).describe("The resource type to list tags for"),
|
|
2017
|
+
page: z13.number().int().positive().optional(),
|
|
2018
|
+
perPage: z13.number().int().min(1).max(100).optional()
|
|
1988
2019
|
});
|
|
1989
2020
|
async function listTags(input) {
|
|
1990
2021
|
const path = TAG_LIST_PATH[input.entity];
|
|
@@ -1994,10 +2025,10 @@ async function listTags(input) {
|
|
|
1994
2025
|
});
|
|
1995
2026
|
return { ...data, nextPage };
|
|
1996
2027
|
}
|
|
1997
|
-
var addTagSchema =
|
|
2028
|
+
var addTagSchema = z13.object({
|
|
1998
2029
|
entity: TagEntity,
|
|
1999
|
-
entityId:
|
|
2000
|
-
tagName:
|
|
2030
|
+
entityId: positiveId.describe("The party/opportunity/kase id."),
|
|
2031
|
+
tagName: z13.string().min(1).describe(
|
|
2001
2032
|
"Name of the tag to attach. Capsule resolves by name: if a tag with this name already exists in the tenant it is attached to the entity; if not, Capsule creates the tag and attaches it. Names are tenant-global. Capsule matches case-INSENSITIVELY when resolving (so 'VIP' and 'vip' attach the same tag), preserving the canonical casing from whichever variant was created first. To ensure consistent casing in your tag list, call list_tags first and reuse the exact name from there. Idempotent \u2014 re-attaching an already-attached tag is harmless."
|
|
2002
2033
|
)
|
|
2003
2034
|
});
|
|
@@ -2010,10 +2041,10 @@ async function addTag(input) {
|
|
|
2010
2041
|
invalidateByPrefix(TAG_LIST_PATH[entity], "add_tag");
|
|
2011
2042
|
return result;
|
|
2012
2043
|
}
|
|
2013
|
-
var removeTagByIdSchema =
|
|
2044
|
+
var removeTagByIdSchema = z13.object({
|
|
2014
2045
|
entity: TagEntity,
|
|
2015
|
-
entityId:
|
|
2016
|
-
tagId:
|
|
2046
|
+
entityId: positiveId.describe("The party/opportunity/kase id."),
|
|
2047
|
+
tagId: positiveId.describe(
|
|
2017
2048
|
"The tag's id. Read via get_party / get_opportunity / get_project with embed='tags' \u2014 each tag entry in the response has an `id` field. list_tags returns the same ids for the same tags, so either source works; reading via embed first is the safer pattern because it confirms the tag is actually attached to this entity before you try to remove it (otherwise Capsule returns 422 'tag not found to delete'). Removing detaches the tag from this entity only; the tag definition itself persists in the tenant for other entities that share it."
|
|
2018
2049
|
)
|
|
2019
2050
|
});
|
|
@@ -2041,28 +2072,24 @@ async function removeTagById(input) {
|
|
|
2041
2072
|
invalidateByPrefix(TAG_LIST_PATH[entity], "remove_tag_by_id");
|
|
2042
2073
|
return result;
|
|
2043
2074
|
}
|
|
2044
|
-
var batchAddTagSchema =
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2075
|
+
var { schema: batchAddTagSchema, handler: batchAddTag } = defineBatch({
|
|
2076
|
+
toolName: "batch_add_tag",
|
|
2077
|
+
itemSchema: addTagSchema,
|
|
2078
|
+
itemDescription: "Array of 1\u201350 add_tag inputs. Useful for mass-tagging \u2014 e.g. 'tag these 20 contacts as RSAC26'. Each item is the same shape as a single add_tag call. The list_tags cache is invalidated for each affected entity type. Capped at 50.",
|
|
2079
|
+
itemHandler: addTag
|
|
2048
2080
|
});
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
"Array of 1\u201350 remove_tag_by_id inputs. Each item is the same shape as a single remove_tag_by_id call. Detaches the tag from each specified entity; the tag definition itself persists in the tenant. Capped at 50."
|
|
2055
|
-
)
|
|
2081
|
+
var { schema: batchRemoveTagByIdSchema, handler: batchRemoveTagById } = defineBatch({
|
|
2082
|
+
toolName: "batch_remove_tag_by_id",
|
|
2083
|
+
itemSchema: removeTagByIdSchema,
|
|
2084
|
+
itemDescription: "Array of 1\u201350 remove_tag_by_id inputs. Each item is the same shape as a single remove_tag_by_id call. Detaches the tag from each specified entity; the tag definition itself persists in the tenant. Capped at 50.",
|
|
2085
|
+
itemHandler: removeTagById
|
|
2056
2086
|
});
|
|
2057
|
-
async function batchRemoveTagById(input, opts = {}) {
|
|
2058
|
-
return batchExecute("batch_remove_tag_by_id", input.items, (item) => removeTagById(item), opts);
|
|
2059
|
-
}
|
|
2060
2087
|
|
|
2061
2088
|
// src/tools/users.ts
|
|
2062
|
-
import { z as
|
|
2063
|
-
var listUsersSchema =
|
|
2064
|
-
page:
|
|
2065
|
-
perPage:
|
|
2089
|
+
import { z as z14 } from "zod";
|
|
2090
|
+
var listUsersSchema = z14.object({
|
|
2091
|
+
page: z14.number().int().positive().optional(),
|
|
2092
|
+
perPage: z14.number().int().min(1).max(100).optional()
|
|
2066
2093
|
});
|
|
2067
2094
|
async function listUsers(input) {
|
|
2068
2095
|
const { data, nextPage } = await capsuleGetCached("/users", {
|
|
@@ -2071,32 +2098,32 @@ async function listUsers(input) {
|
|
|
2071
2098
|
});
|
|
2072
2099
|
return { ...data, nextPage };
|
|
2073
2100
|
}
|
|
2074
|
-
var getCurrentUserSchema =
|
|
2101
|
+
var getCurrentUserSchema = z14.object({});
|
|
2075
2102
|
async function getCurrentUser(_input) {
|
|
2076
2103
|
const { data } = await capsuleGet("/users/current");
|
|
2077
2104
|
return data;
|
|
2078
2105
|
}
|
|
2079
2106
|
|
|
2080
2107
|
// src/tools/filters.ts
|
|
2081
|
-
import { z as
|
|
2082
|
-
var FilterConditionSchema =
|
|
2083
|
-
field:
|
|
2108
|
+
import { z as z15 } from "zod";
|
|
2109
|
+
var FilterConditionSchema = z15.object({
|
|
2110
|
+
field: z15.string().describe(
|
|
2084
2111
|
"The Capsule filter-side field name (these differ from response field names \u2014 e.g. response.createdAt is filter-side 'addedOn', response.lastContactedAt is filter-side 'lastContactedOn'). Common: 'addedOn' (date created), 'updatedOn' (date last modified), 'lastContactedOn' (parties only), 'name', 'tag', 'owner', 'team', 'type' (parties: person|organisation), 'milestone' (opportunities), 'status' (opp/project: OPEN|CLOSED), 'closedOn' (opp/project), 'expectedCloseOn' (opp/project), 'hasTags', 'hasEmailAddress' (parties), 'isOpen', 'isStale' (opportunities), 'custom:{fieldId}'. Full per-entity list: https://developer.capsulecrm.com/v2/reference/filters"
|
|
2085
2112
|
),
|
|
2086
|
-
operator:
|
|
2113
|
+
operator: z15.string().describe(
|
|
2087
2114
|
"The filter operator. Common: 'is', 'is not' (use value=null to test for null), 'contains', 'does not contain', 'is greater than', 'is less than', 'is within last' (date fields, value=integer days), 'is more than' (date fields, value=integer days ago), 'starts with', 'ends with'. Operator validity depends on the field's type."
|
|
2088
2115
|
),
|
|
2089
|
-
value:
|
|
2116
|
+
value: z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]).describe(
|
|
2090
2117
|
"The value to compare against. For 'is within last' on date fields, pass an integer number of days. For tag filters, pass the tag name (string) or tag id (number). For 'is not' null tests, pass null literally."
|
|
2091
2118
|
)
|
|
2092
2119
|
});
|
|
2093
|
-
var FilterInputSchema =
|
|
2094
|
-
conditions:
|
|
2120
|
+
var FilterInputSchema = z15.object({
|
|
2121
|
+
conditions: z15.array(FilterConditionSchema).min(1).describe(
|
|
2095
2122
|
"Array of filter conditions. All conditions are ANDed together. To get newest records, use a date condition like {field: 'addedOn', operator: 'is within last', value: 7} and pick the highest-id row from the result (Capsule IDs are monotonic)."
|
|
2096
2123
|
),
|
|
2097
|
-
embed:
|
|
2098
|
-
page:
|
|
2099
|
-
perPage:
|
|
2124
|
+
embed: z15.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2125
|
+
page: z15.number().int().positive().optional().default(1),
|
|
2126
|
+
perPage: z15.number().int().min(1).max(100).optional().default(25)
|
|
2100
2127
|
});
|
|
2101
2128
|
async function runFilter(entityPath, input) {
|
|
2102
2129
|
const { data, nextPage } = await capsuleSearch(
|
|
@@ -2127,12 +2154,12 @@ async function filterProjects(input) {
|
|
|
2127
2154
|
}
|
|
2128
2155
|
|
|
2129
2156
|
// src/tools/metadata.ts
|
|
2130
|
-
import { z as
|
|
2157
|
+
import { z as z16 } from "zod";
|
|
2131
2158
|
var paginationFields3 = {
|
|
2132
|
-
page:
|
|
2133
|
-
perPage:
|
|
2159
|
+
page: z16.number().int().positive().optional(),
|
|
2160
|
+
perPage: z16.number().int().min(1).max(100).optional().describe("Page size, max 100. Defaults to 100 for reference data.")
|
|
2134
2161
|
};
|
|
2135
|
-
var listTeamsSchema =
|
|
2162
|
+
var listTeamsSchema = z16.object({ ...paginationFields3 });
|
|
2136
2163
|
async function listTeams(input) {
|
|
2137
2164
|
const { data, nextPage } = await capsuleGetCached("/teams", {
|
|
2138
2165
|
page: input.page ?? 1,
|
|
@@ -2140,7 +2167,7 @@ async function listTeams(input) {
|
|
|
2140
2167
|
});
|
|
2141
2168
|
return { ...data, nextPage };
|
|
2142
2169
|
}
|
|
2143
|
-
var listLostReasonsSchema =
|
|
2170
|
+
var listLostReasonsSchema = z16.object({ ...paginationFields3 });
|
|
2144
2171
|
async function listLostReasons(input) {
|
|
2145
2172
|
const { data, nextPage } = await capsuleGetCached("/lostreasons", {
|
|
2146
2173
|
page: input.page ?? 1,
|
|
@@ -2148,7 +2175,7 @@ async function listLostReasons(input) {
|
|
|
2148
2175
|
});
|
|
2149
2176
|
return { ...data, nextPage };
|
|
2150
2177
|
}
|
|
2151
|
-
var listActivityTypesSchema =
|
|
2178
|
+
var listActivityTypesSchema = z16.object({ ...paginationFields3 });
|
|
2152
2179
|
async function listActivityTypes(input) {
|
|
2153
2180
|
const { data, nextPage } = await capsuleGetCached(
|
|
2154
2181
|
"/activitytypes",
|
|
@@ -2159,12 +2186,12 @@ async function listActivityTypes(input) {
|
|
|
2159
2186
|
);
|
|
2160
2187
|
return { ...data, nextPage };
|
|
2161
2188
|
}
|
|
2162
|
-
var getSiteSchema =
|
|
2189
|
+
var getSiteSchema = z16.object({});
|
|
2163
2190
|
async function getSite(_input) {
|
|
2164
2191
|
const { data } = await capsuleGetCached("/site");
|
|
2165
2192
|
return data;
|
|
2166
2193
|
}
|
|
2167
|
-
var listTrackDefinitionsSchema =
|
|
2194
|
+
var listTrackDefinitionsSchema = z16.object({ ...paginationFields3 });
|
|
2168
2195
|
async function listTrackDefinitions(input) {
|
|
2169
2196
|
const { data, nextPage } = await capsuleGetCached(
|
|
2170
2197
|
"/trackdefinitions",
|
|
@@ -2172,7 +2199,7 @@ async function listTrackDefinitions(input) {
|
|
|
2172
2199
|
);
|
|
2173
2200
|
return { ...data, nextPage };
|
|
2174
2201
|
}
|
|
2175
|
-
var listCategoriesSchema =
|
|
2202
|
+
var listCategoriesSchema = z16.object({ ...paginationFields3 });
|
|
2176
2203
|
async function listCategories(input) {
|
|
2177
2204
|
const { data, nextPage } = await capsuleGetCached("/categories", {
|
|
2178
2205
|
page: input.page ?? 1,
|
|
@@ -2180,7 +2207,7 @@ async function listCategories(input) {
|
|
|
2180
2207
|
});
|
|
2181
2208
|
return { ...data, nextPage };
|
|
2182
2209
|
}
|
|
2183
|
-
var listGoalsSchema =
|
|
2210
|
+
var listGoalsSchema = z16.object({ ...paginationFields3 });
|
|
2184
2211
|
async function listGoals(input) {
|
|
2185
2212
|
const { data, nextPage } = await capsuleGetCached("/goals", {
|
|
2186
2213
|
page: input.page ?? 1,
|
|
@@ -2190,14 +2217,14 @@ async function listGoals(input) {
|
|
|
2190
2217
|
}
|
|
2191
2218
|
|
|
2192
2219
|
// src/tools/audit.ts
|
|
2193
|
-
import { z as
|
|
2194
|
-
var listEmployeesSchema =
|
|
2195
|
-
partyId:
|
|
2220
|
+
import { z as z17 } from "zod";
|
|
2221
|
+
var listEmployeesSchema = z17.object({
|
|
2222
|
+
partyId: positiveId.describe(
|
|
2196
2223
|
"The organisation's party id. Returns the people whose `organisation` field links to this party."
|
|
2197
2224
|
),
|
|
2198
|
-
page:
|
|
2199
|
-
perPage:
|
|
2200
|
-
embed:
|
|
2225
|
+
page: z17.number().int().positive().optional().default(1),
|
|
2226
|
+
perPage: z17.number().int().min(1).max(100).optional().default(25),
|
|
2227
|
+
embed: z17.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
2201
2228
|
});
|
|
2202
2229
|
async function listEmployees(input) {
|
|
2203
2230
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2206,15 +2233,15 @@ async function listEmployees(input) {
|
|
|
2206
2233
|
);
|
|
2207
2234
|
return { ...data, nextPage };
|
|
2208
2235
|
}
|
|
2209
|
-
var DeletedSinceSchema =
|
|
2236
|
+
var DeletedSinceSchema = z17.string().describe(
|
|
2210
2237
|
"REQUIRED. ISO-8601 timestamp; only deletions on or after this point are returned. Example: '2026-01-01T00:00:00Z'."
|
|
2211
2238
|
);
|
|
2212
2239
|
var DeletedPagination = {
|
|
2213
2240
|
since: DeletedSinceSchema,
|
|
2214
|
-
page:
|
|
2215
|
-
perPage:
|
|
2241
|
+
page: z17.number().int().positive().optional().default(1),
|
|
2242
|
+
perPage: z17.number().int().min(1).max(100).optional().default(25)
|
|
2216
2243
|
};
|
|
2217
|
-
var listDeletedPartiesSchema =
|
|
2244
|
+
var listDeletedPartiesSchema = z17.object(DeletedPagination);
|
|
2218
2245
|
async function listDeletedParties(input) {
|
|
2219
2246
|
const { data, nextPage } = await capsuleGet("/parties/deleted", {
|
|
2220
2247
|
since: input.since,
|
|
@@ -2223,7 +2250,7 @@ async function listDeletedParties(input) {
|
|
|
2223
2250
|
});
|
|
2224
2251
|
return { ...data, nextPage };
|
|
2225
2252
|
}
|
|
2226
|
-
var listDeletedOpportunitiesSchema =
|
|
2253
|
+
var listDeletedOpportunitiesSchema = z17.object(DeletedPagination);
|
|
2227
2254
|
async function listDeletedOpportunities(input) {
|
|
2228
2255
|
const { data, nextPage } = await capsuleGet("/opportunities/deleted", {
|
|
2229
2256
|
since: input.since,
|
|
@@ -2232,7 +2259,7 @@ async function listDeletedOpportunities(input) {
|
|
|
2232
2259
|
});
|
|
2233
2260
|
return { ...data, nextPage };
|
|
2234
2261
|
}
|
|
2235
|
-
var listDeletedProjectsSchema =
|
|
2262
|
+
var listDeletedProjectsSchema = z17.object(DeletedPagination);
|
|
2236
2263
|
async function listDeletedProjects(input) {
|
|
2237
2264
|
const { data, nextPage } = await capsuleGet("/kases/deleted", {
|
|
2238
2265
|
since: input.since,
|
|
@@ -2243,14 +2270,14 @@ async function listDeletedProjects(input) {
|
|
|
2243
2270
|
}
|
|
2244
2271
|
|
|
2245
2272
|
// src/tools/relationships.ts
|
|
2246
|
-
import { z as
|
|
2247
|
-
var RelationshipEntity =
|
|
2248
|
-
var listAdditionalPartiesSchema =
|
|
2273
|
+
import { z as z18 } from "zod";
|
|
2274
|
+
var RelationshipEntity = z18.enum(["opportunities", "kases"]).describe("Which entity has the additional-party links. Use 'kases' for projects.");
|
|
2275
|
+
var listAdditionalPartiesSchema = z18.object({
|
|
2249
2276
|
entity: RelationshipEntity,
|
|
2250
|
-
entityId:
|
|
2251
|
-
embed:
|
|
2252
|
-
page:
|
|
2253
|
-
perPage:
|
|
2277
|
+
entityId: positiveId.describe("ID of the opportunity or project."),
|
|
2278
|
+
embed: z18.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2279
|
+
page: z18.number().int().positive().optional().default(1),
|
|
2280
|
+
perPage: z18.number().int().min(1).max(100).optional().default(25)
|
|
2254
2281
|
});
|
|
2255
2282
|
async function listAdditionalParties(input) {
|
|
2256
2283
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2259,10 +2286,12 @@ async function listAdditionalParties(input) {
|
|
|
2259
2286
|
);
|
|
2260
2287
|
return { ...data, nextPage };
|
|
2261
2288
|
}
|
|
2262
|
-
var addAdditionalPartySchema =
|
|
2289
|
+
var addAdditionalPartySchema = z18.object({
|
|
2263
2290
|
entity: RelationshipEntity,
|
|
2264
|
-
entityId:
|
|
2265
|
-
partyId:
|
|
2291
|
+
entityId: positiveId,
|
|
2292
|
+
partyId: positiveId.describe(
|
|
2293
|
+
"ID of the party (person or organisation) to link as an additional party."
|
|
2294
|
+
)
|
|
2266
2295
|
});
|
|
2267
2296
|
async function addAdditionalParty(input) {
|
|
2268
2297
|
try {
|
|
@@ -2290,10 +2319,10 @@ async function addAdditionalParty(input) {
|
|
|
2290
2319
|
throw err;
|
|
2291
2320
|
}
|
|
2292
2321
|
}
|
|
2293
|
-
var removeAdditionalPartySchema =
|
|
2322
|
+
var removeAdditionalPartySchema = z18.object({
|
|
2294
2323
|
entity: RelationshipEntity,
|
|
2295
|
-
entityId:
|
|
2296
|
-
partyId:
|
|
2324
|
+
entityId: positiveId,
|
|
2325
|
+
partyId: positiveId,
|
|
2297
2326
|
confirm: confirmFlag().describe(
|
|
2298
2327
|
"Must be set to true. Removes the link between the entity and the additional party. The party itself is not deleted. Reversible by re-adding the link."
|
|
2299
2328
|
)
|
|
@@ -2320,11 +2349,11 @@ async function removeAdditionalParty(input) {
|
|
|
2320
2349
|
})
|
|
2321
2350
|
);
|
|
2322
2351
|
}
|
|
2323
|
-
var listAssociatedProjectsSchema =
|
|
2324
|
-
opportunityId:
|
|
2325
|
-
embed:
|
|
2326
|
-
page:
|
|
2327
|
-
perPage:
|
|
2352
|
+
var listAssociatedProjectsSchema = z18.object({
|
|
2353
|
+
opportunityId: positiveId,
|
|
2354
|
+
embed: z18.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2355
|
+
page: z18.number().int().positive().optional().default(1),
|
|
2356
|
+
perPage: z18.number().int().min(1).max(100).optional().default(25)
|
|
2328
2357
|
});
|
|
2329
2358
|
async function listAssociatedProjects(input) {
|
|
2330
2359
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2335,9 +2364,9 @@ async function listAssociatedProjects(input) {
|
|
|
2335
2364
|
}
|
|
2336
2365
|
|
|
2337
2366
|
// src/tools/custom-fields.ts
|
|
2338
|
-
import { z as
|
|
2339
|
-
var CustomFieldEntity =
|
|
2340
|
-
var listCustomFieldsSchema =
|
|
2367
|
+
import { z as z19 } from "zod";
|
|
2368
|
+
var CustomFieldEntity = z19.enum(["parties", "opportunities", "kases"]).describe("Which entity type's custom field schema to inspect. Use 'kases' for projects.");
|
|
2369
|
+
var listCustomFieldsSchema = z19.object({
|
|
2341
2370
|
entity: CustomFieldEntity
|
|
2342
2371
|
});
|
|
2343
2372
|
async function listCustomFields(input) {
|
|
@@ -2346,9 +2375,9 @@ async function listCustomFields(input) {
|
|
|
2346
2375
|
);
|
|
2347
2376
|
return data;
|
|
2348
2377
|
}
|
|
2349
|
-
var getCustomFieldSchema =
|
|
2378
|
+
var getCustomFieldSchema = z19.object({
|
|
2350
2379
|
entity: CustomFieldEntity,
|
|
2351
|
-
fieldId:
|
|
2380
|
+
fieldId: positiveId.describe("Custom field definition id.")
|
|
2352
2381
|
});
|
|
2353
2382
|
async function getCustomField(input) {
|
|
2354
2383
|
const { data } = await capsuleGetCached(
|
|
@@ -2358,11 +2387,11 @@ async function getCustomField(input) {
|
|
|
2358
2387
|
}
|
|
2359
2388
|
|
|
2360
2389
|
// src/tools/tracks.ts
|
|
2361
|
-
import { z as
|
|
2362
|
-
var TrackEntity =
|
|
2363
|
-
var listEntityTracksSchema =
|
|
2390
|
+
import { z as z20 } from "zod";
|
|
2391
|
+
var TrackEntity = z20.enum(["parties", "opportunities", "kases"]).describe("Use 'kases' for projects.");
|
|
2392
|
+
var listEntityTracksSchema = z20.object({
|
|
2364
2393
|
entity: TrackEntity,
|
|
2365
|
-
entityId:
|
|
2394
|
+
entityId: positiveId
|
|
2366
2395
|
});
|
|
2367
2396
|
async function listEntityTracks(input) {
|
|
2368
2397
|
const { data } = await capsuleGet(
|
|
@@ -2370,20 +2399,20 @@ async function listEntityTracks(input) {
|
|
|
2370
2399
|
);
|
|
2371
2400
|
return data;
|
|
2372
2401
|
}
|
|
2373
|
-
var showTrackSchema =
|
|
2374
|
-
trackId:
|
|
2402
|
+
var showTrackSchema = z20.object({
|
|
2403
|
+
trackId: positiveId
|
|
2375
2404
|
});
|
|
2376
2405
|
async function showTrack(input) {
|
|
2377
2406
|
const { data } = await capsuleGet(`/tracks/${input.trackId}`);
|
|
2378
2407
|
return data;
|
|
2379
2408
|
}
|
|
2380
|
-
var applyTrackSchema =
|
|
2381
|
-
entity:
|
|
2382
|
-
entityId:
|
|
2383
|
-
trackDefinitionId:
|
|
2409
|
+
var applyTrackSchema = z20.object({
|
|
2410
|
+
entity: z20.enum(["opportunities", "kases"]).describe("Which entity to apply the track to. Use 'kases' for projects."),
|
|
2411
|
+
entityId: positiveId,
|
|
2412
|
+
trackDefinitionId: positiveId.describe(
|
|
2384
2413
|
"The trackDefinition to apply (from list_track_definitions). Auto-creates task definitions on the target entity per the track's rules."
|
|
2385
2414
|
),
|
|
2386
|
-
startDate:
|
|
2415
|
+
startDate: z20.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe(
|
|
2387
2416
|
"Optional ISO-8601 date (YYYY-MM-DD) the track should start from \u2014 drives task due-date calculations (each task's `dueOn` is computed as startDate + the track-definition's `daysAfter` offset). Defaults to today if omitted. Useful for scheduling a renewal-queue track against a future contract end-date, or backfilling tracks for historical projects."
|
|
2388
2417
|
)
|
|
2389
2418
|
});
|
|
@@ -2396,9 +2425,9 @@ async function applyTrack(input) {
|
|
|
2396
2425
|
if (input.startDate !== void 0) track["trackDateOn"] = input.startDate;
|
|
2397
2426
|
return capsulePost("/tracks", { track });
|
|
2398
2427
|
}
|
|
2399
|
-
var updateTrackSchema =
|
|
2400
|
-
trackId:
|
|
2401
|
-
fields:
|
|
2428
|
+
var updateTrackSchema = z20.object({
|
|
2429
|
+
trackId: positiveId,
|
|
2430
|
+
fields: z20.record(z20.string(), z20.unknown()).describe(
|
|
2402
2431
|
"Object of fields to update on the track. Capsule's PUT semantics are partial \u2014 only the fields you provide are changed. Common: { complete: true } to mark a track completed. Capsule rejects unknown keys; consult Capsule's docs for the full updatable set."
|
|
2403
2432
|
)
|
|
2404
2433
|
});
|
|
@@ -2410,8 +2439,8 @@ async function updateTrack(input) {
|
|
|
2410
2439
|
track: input.fields
|
|
2411
2440
|
});
|
|
2412
2441
|
}
|
|
2413
|
-
var removeTrackSchema =
|
|
2414
|
-
trackId:
|
|
2442
|
+
var removeTrackSchema = z20.object({
|
|
2443
|
+
trackId: positiveId,
|
|
2415
2444
|
confirm: confirmFlag().describe(
|
|
2416
2445
|
"Must be set to true. Removes the track instance from its entity. **Capsule also deletes the auto-tasks the track created when it was applied** \u2014 they go with the track and become unreachable (404 on GET /tasks/{id}, gone from list_tasks on the parent entity). If you need any of those tasks to outlive the track, copy their content into fresh tasks (or use the web UI) before calling remove_track."
|
|
2417
2446
|
)
|
|
@@ -2428,13 +2457,13 @@ async function removeTrack(input) {
|
|
|
2428
2457
|
}
|
|
2429
2458
|
|
|
2430
2459
|
// src/tools/attachments.ts
|
|
2431
|
-
import { z as
|
|
2460
|
+
import { z as z21 } from "zod";
|
|
2432
2461
|
var DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024;
|
|
2433
2462
|
var HARD_MAX_SIZE_BYTES = 25 * 1024 * 1024;
|
|
2434
2463
|
var HARD_MAX_BASE64_CHARS = Math.ceil(HARD_MAX_SIZE_BYTES / 3) * 4;
|
|
2435
|
-
var getAttachmentSchema =
|
|
2436
|
-
id:
|
|
2437
|
-
maxSizeBytes:
|
|
2464
|
+
var getAttachmentSchema = z21.object({
|
|
2465
|
+
id: positiveId.describe("Attachment ID."),
|
|
2466
|
+
maxSizeBytes: z21.number().int().positive().max(HARD_MAX_SIZE_BYTES).optional().describe(
|
|
2438
2467
|
`Refuse to return content over this size (default ${DEFAULT_MAX_SIZE_BYTES} bytes \u2248 5MB; max ${HARD_MAX_SIZE_BYTES} bytes \u2248 25MB). Files exceeding the cap return metadata only with a 'truncated: true' flag.`
|
|
2439
2468
|
)
|
|
2440
2469
|
});
|
|
@@ -2449,22 +2478,22 @@ async function getAttachment(input) {
|
|
|
2449
2478
|
}
|
|
2450
2479
|
return { contentType, buffer, sizeBytes };
|
|
2451
2480
|
}
|
|
2452
|
-
var uploadAttachmentSchema =
|
|
2453
|
-
filename:
|
|
2481
|
+
var uploadAttachmentSchema = z21.object({
|
|
2482
|
+
filename: z21.string().min(1).describe(
|
|
2454
2483
|
"Filename Capsule should record (e.g. 'contract.pdf'). Capsule does NOT validate consistency between filename, contentType, and the actual bytes \u2014 a typo in either is accepted and the file is stored as labelled."
|
|
2455
2484
|
),
|
|
2456
|
-
contentType:
|
|
2485
|
+
contentType: z21.string().min(1).describe(
|
|
2457
2486
|
"MIME type of the file (e.g. 'application/pdf', 'image/png', 'text/plain'). Trusted by Capsule verbatim; not cross-checked against `filename` or the actual bytes."
|
|
2458
2487
|
),
|
|
2459
|
-
dataBase64:
|
|
2488
|
+
dataBase64: z21.string().min(1).max(HARD_MAX_BASE64_CHARS).describe(
|
|
2460
2489
|
"File contents, base64-encoded. Decoded server-side and uploaded as the request body. Maximum 25 MB per attachment (Capsule's documented limit); the connector rejects oversized base64 before uploading. The inbound HTTP body limit is ~35 MB which leaves room for the base64 expansion of a 25 MB binary."
|
|
2461
2490
|
),
|
|
2462
|
-
content:
|
|
2491
|
+
content: z21.string().optional().describe(
|
|
2463
2492
|
"Body text for the note that will hold the attachment. Defaults to '[attachment]' if omitted."
|
|
2464
2493
|
),
|
|
2465
|
-
partyId:
|
|
2466
|
-
opportunityId:
|
|
2467
|
-
projectId:
|
|
2494
|
+
partyId: positiveId.optional().describe("Link the new note to a party (mutually exclusive with opportunityId / projectId)."),
|
|
2495
|
+
opportunityId: positiveId.optional(),
|
|
2496
|
+
projectId: positiveId.optional()
|
|
2468
2497
|
});
|
|
2469
2498
|
function isValidBase64(s) {
|
|
2470
2499
|
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(s)) return false;
|
|
@@ -2514,23 +2543,23 @@ async function uploadAttachment(input) {
|
|
|
2514
2543
|
}
|
|
2515
2544
|
|
|
2516
2545
|
// src/tools/saved-filters.ts
|
|
2517
|
-
import { z as
|
|
2518
|
-
var EntitySchema =
|
|
2546
|
+
import { z as z22 } from "zod";
|
|
2547
|
+
var EntitySchema = z22.enum(["parties", "opportunities", "kases"]).describe(
|
|
2519
2548
|
"Which entity type the filter operates over. Use 'kases' for projects (Capsule's legacy name)."
|
|
2520
2549
|
);
|
|
2521
|
-
var listSavedFiltersSchema =
|
|
2550
|
+
var listSavedFiltersSchema = z22.object({
|
|
2522
2551
|
entity: EntitySchema
|
|
2523
2552
|
});
|
|
2524
2553
|
async function listSavedFilters(input) {
|
|
2525
2554
|
const { data } = await capsuleGetCached(`/${input.entity}/filters`);
|
|
2526
2555
|
return data;
|
|
2527
2556
|
}
|
|
2528
|
-
var runSavedFilterSchema =
|
|
2557
|
+
var runSavedFilterSchema = z22.object({
|
|
2529
2558
|
entity: EntitySchema,
|
|
2530
|
-
id:
|
|
2531
|
-
embed:
|
|
2532
|
-
page:
|
|
2533
|
-
perPage:
|
|
2559
|
+
id: positiveId.describe("The saved filter id (from list_saved_filters)."),
|
|
2560
|
+
embed: z22.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2561
|
+
page: z22.number().int().positive().optional().default(1),
|
|
2562
|
+
perPage: z22.number().int().min(1).max(100).optional().default(25)
|
|
2534
2563
|
});
|
|
2535
2564
|
async function runSavedFilter(input) {
|
|
2536
2565
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2548,7 +2577,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
2548
2577
|
const server2 = new McpServer(
|
|
2549
2578
|
{
|
|
2550
2579
|
name: "capsulemcp",
|
|
2551
|
-
version: "1.6.
|
|
2580
|
+
version: "1.6.2",
|
|
2552
2581
|
description: "Read and (optionally) modify Capsule CRM data \u2014 parties, opportunities, projects, tasks, timeline entries, pipelines, tags.",
|
|
2553
2582
|
websiteUrl: "https://github.com/soil-dev/capsulemcp",
|
|
2554
2583
|
icons: ICONS
|
|
@@ -3133,7 +3162,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
3133
3162
|
registerTool(
|
|
3134
3163
|
server2,
|
|
3135
3164
|
"list_teams",
|
|
3136
|
-
"List all teams configured in the Capsule account. Useful as input for filter_* queries that scope by team, and for reporting. LIMITATION: returns team identity only (id, name, description, timestamps). Capsule's v2 API does not expose team\u2194user membership through any endpoint \u2014 `GET /teams/{id}/users` 404s, `embed=users` is silently ignored, and `GET /users/{id}` doesn't include a `teams` field. To determine whether a given user belongs to a given team, either check Capsule's web UI Team Membership page, or probe via `update_project { ownerId: U, teamId: T }` and read the response \u2014 422 'owner is not a member of the team' means U \u2209 T.",
|
|
3165
|
+
"List all teams configured in the Capsule account. Useful as input for filter_* queries that scope by team, and for reporting. LIMITATION: returns team identity only (id, name, description, timestamps). Capsule's v2 API does not expose team\u2194user membership through any endpoint \u2014 `GET /teams/{id}/users` 404s, `embed=users` is silently ignored, and `GET /users/{id}` doesn't include a `teams` field. To determine whether a given user belongs to a given team, either check Capsule's web UI Team Membership page, or probe via `update_project { ownerId: U, teamId: T }` / `batch_update_opportunity { items: [{ id: <any opp>, ownerId: U, teamId: T }] }` and read the response \u2014 422 'owner is not a member of the team' means U \u2209 T. Both probe paths apply the same membership constraint server-side.",
|
|
3137
3166
|
listTeamsSchema,
|
|
3138
3167
|
listTeams
|
|
3139
3168
|
);
|