capsulemcp 1.6.1 → 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 +1 -1
- package/dist/http.js +480 -487
- package/dist/index.js +480 -487
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -871,18 +871,19 @@ function registerToolTask(server2, name, description, schema, handler) {
|
|
|
871
871
|
}
|
|
872
872
|
|
|
873
873
|
// src/tools/parties.ts
|
|
874
|
-
import { z as
|
|
874
|
+
import { z as z6 } from "zod";
|
|
875
875
|
|
|
876
|
-
// src/tools/
|
|
877
|
-
|
|
878
|
-
|
|
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
|
+
}
|
|
879
884
|
|
|
880
|
-
// src/tools/
|
|
885
|
+
// src/tools/define-batch.ts
|
|
881
886
|
import { z } from "zod";
|
|
882
|
-
var CONFIRM_REQUIRED_MESSAGE = "confirm: true is required to perform this destructive operation (set the parameter explicitly to acknowledge the destructive intent)";
|
|
883
|
-
function confirmFlag() {
|
|
884
|
-
return z.literal(true, { error: () => CONFIRM_REQUIRED_MESSAGE });
|
|
885
|
-
}
|
|
886
887
|
|
|
887
888
|
// src/capsule/batch.ts
|
|
888
889
|
function chunk(arr, size) {
|
|
@@ -976,6 +977,39 @@ function topFailureReasons(results, n) {
|
|
|
976
977
|
return Array.from(counts.values()).sort((a, b) => b.count - a.count).slice(0, n);
|
|
977
978
|
}
|
|
978
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
|
+
|
|
979
1013
|
// src/capsule/idempotent.ts
|
|
980
1014
|
var isCapsule404 = (err) => err instanceof CapsuleApiError && err.status === 404;
|
|
981
1015
|
var isCapsuleTagNotFound = (err) => err instanceof CapsuleApiError && err.status === 422 && /tag not found/i.test(err.message);
|
|
@@ -998,13 +1032,33 @@ async function idempotentWithResult(op, success, alreadyDone, isAlreadyDoneError
|
|
|
998
1032
|
}
|
|
999
1033
|
}
|
|
1000
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
|
+
|
|
1001
1055
|
// src/tools/custom-field-helpers.ts
|
|
1002
|
-
import { z as
|
|
1003
|
-
var CustomFieldWriteSchema =
|
|
1004
|
-
definitionId:
|
|
1056
|
+
import { z as z5 } from "zod";
|
|
1057
|
+
var CustomFieldWriteSchema = z5.object({
|
|
1058
|
+
definitionId: positiveId.describe(
|
|
1005
1059
|
"The custom-field definition id from list_custom_fields. Identifies which field on the entity to set."
|
|
1006
1060
|
),
|
|
1007
|
-
value:
|
|
1061
|
+
value: z5.union([z5.string(), z5.number(), z5.boolean(), z5.null()]).describe(
|
|
1008
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."
|
|
1009
1063
|
)
|
|
1010
1064
|
});
|
|
@@ -1020,24 +1074,24 @@ function mapFieldsForBody(fields) {
|
|
|
1020
1074
|
}
|
|
1021
1075
|
|
|
1022
1076
|
// src/tools/parties.ts
|
|
1023
|
-
var EmailAddressSchema =
|
|
1024
|
-
address:
|
|
1025
|
-
type:
|
|
1077
|
+
var EmailAddressSchema = z6.object({
|
|
1078
|
+
address: z6.string().email(),
|
|
1079
|
+
type: z6.string().optional()
|
|
1026
1080
|
});
|
|
1027
|
-
var PhoneNumberSchema =
|
|
1081
|
+
var PhoneNumberSchema = z6.object({
|
|
1028
1082
|
// Capsule rejects empty strings with `phoneNumber.number: number is
|
|
1029
1083
|
// required`. Enforce at the schema layer to catch typos pre-call,
|
|
1030
1084
|
// matching how EmailAddressSchema's address field behaves.
|
|
1031
|
-
number:
|
|
1032
|
-
type:
|
|
1085
|
+
number: z6.string().min(1),
|
|
1086
|
+
type: z6.string().optional()
|
|
1033
1087
|
});
|
|
1034
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.";
|
|
1035
|
-
var AddressSchema =
|
|
1036
|
-
street:
|
|
1037
|
-
city:
|
|
1038
|
-
state:
|
|
1039
|
-
country:
|
|
1040
|
-
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()
|
|
1041
1095
|
});
|
|
1042
1096
|
function validateWebsiteAddress(data, ctx) {
|
|
1043
1097
|
const isUrlService = data.service === void 0 || data.service === "URL";
|
|
@@ -1060,7 +1114,7 @@ function validateWebsiteAddress(data, ctx) {
|
|
|
1060
1114
|
});
|
|
1061
1115
|
}
|
|
1062
1116
|
}
|
|
1063
|
-
var WebsiteServiceEnum =
|
|
1117
|
+
var WebsiteServiceEnum = z6.enum([
|
|
1064
1118
|
"URL",
|
|
1065
1119
|
"SKYPE",
|
|
1066
1120
|
"TWITTER",
|
|
@@ -1079,19 +1133,19 @@ var WebsiteServiceEnum = z3.enum([
|
|
|
1079
1133
|
"BLUESKY",
|
|
1080
1134
|
"SNAPCHAT"
|
|
1081
1135
|
]);
|
|
1082
|
-
var WebsiteSchema =
|
|
1083
|
-
address:
|
|
1136
|
+
var WebsiteSchema = z6.object({
|
|
1137
|
+
address: z6.string().min(1).describe(
|
|
1084
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."
|
|
1085
1139
|
),
|
|
1086
1140
|
service: WebsiteServiceEnum.optional().describe(
|
|
1087
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."
|
|
1088
1142
|
)
|
|
1089
1143
|
}).superRefine(validateWebsiteAddress);
|
|
1090
|
-
var searchPartiesSchema =
|
|
1091
|
-
q:
|
|
1092
|
-
embed:
|
|
1093
|
-
page:
|
|
1094
|
-
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)
|
|
1095
1149
|
});
|
|
1096
1150
|
async function searchParties(input) {
|
|
1097
1151
|
const path = input.q ? "/parties/search" : "/parties";
|
|
@@ -1103,9 +1157,9 @@ async function searchParties(input) {
|
|
|
1103
1157
|
});
|
|
1104
1158
|
return { ...data, nextPage };
|
|
1105
1159
|
}
|
|
1106
|
-
var getPartySchema =
|
|
1107
|
-
id:
|
|
1108
|
-
embed:
|
|
1160
|
+
var getPartySchema = z6.object({
|
|
1161
|
+
id: positiveId.describe("Party ID"),
|
|
1162
|
+
embed: z6.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1109
1163
|
});
|
|
1110
1164
|
async function getParty(input) {
|
|
1111
1165
|
const { data } = await capsuleGet(`/parties/${input.id}`, {
|
|
@@ -1113,11 +1167,11 @@ async function getParty(input) {
|
|
|
1113
1167
|
});
|
|
1114
1168
|
return data;
|
|
1115
1169
|
}
|
|
1116
|
-
var getPartiesSchema =
|
|
1117
|
-
ids:
|
|
1170
|
+
var getPartiesSchema = z6.object({
|
|
1171
|
+
ids: z6.array(positiveId).min(1).max(50).describe(
|
|
1118
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."
|
|
1119
1173
|
),
|
|
1120
|
-
embed:
|
|
1174
|
+
embed: z6.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1121
1175
|
});
|
|
1122
1176
|
async function getParties(input) {
|
|
1123
1177
|
const { ids, embed } = input;
|
|
@@ -1135,10 +1189,10 @@ async function getParties(input) {
|
|
|
1135
1189
|
);
|
|
1136
1190
|
return { parties: responses.flatMap((r) => r.data.parties) };
|
|
1137
1191
|
}
|
|
1138
|
-
var listPartyOpportunitiesSchema =
|
|
1139
|
-
partyId:
|
|
1140
|
-
page:
|
|
1141
|
-
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)
|
|
1142
1196
|
});
|
|
1143
1197
|
async function listPartyOpportunities(input) {
|
|
1144
1198
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -1147,10 +1201,10 @@ async function listPartyOpportunities(input) {
|
|
|
1147
1201
|
);
|
|
1148
1202
|
return { ...data, nextPage };
|
|
1149
1203
|
}
|
|
1150
|
-
var listPartyProjectsSchema =
|
|
1151
|
-
partyId:
|
|
1152
|
-
page:
|
|
1153
|
-
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)
|
|
1154
1208
|
});
|
|
1155
1209
|
async function listPartyProjects(input) {
|
|
1156
1210
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -1160,50 +1214,50 @@ async function listPartyProjects(input) {
|
|
|
1160
1214
|
return { ...data, nextPage };
|
|
1161
1215
|
}
|
|
1162
1216
|
var PartyWriteBaseSchema = {
|
|
1163
|
-
about:
|
|
1164
|
-
emailAddresses:
|
|
1217
|
+
about: z6.string().optional(),
|
|
1218
|
+
emailAddresses: z6.array(EmailAddressSchema).optional().describe(
|
|
1165
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)."
|
|
1166
1220
|
),
|
|
1167
|
-
phoneNumbers:
|
|
1221
|
+
phoneNumbers: z6.array(PhoneNumberSchema).optional().describe(
|
|
1168
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."
|
|
1169
1223
|
),
|
|
1170
|
-
addresses:
|
|
1224
|
+
addresses: z6.array(AddressSchema).optional().describe(
|
|
1171
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)."
|
|
1172
1226
|
),
|
|
1173
|
-
websites:
|
|
1227
|
+
websites: z6.array(WebsiteSchema).optional().describe(
|
|
1174
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."
|
|
1175
1229
|
),
|
|
1176
|
-
ownerId:
|
|
1230
|
+
ownerId: positiveId.optional().describe(
|
|
1177
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."
|
|
1178
1232
|
)
|
|
1179
1233
|
};
|
|
1180
|
-
var createPartySchema =
|
|
1181
|
-
type:
|
|
1234
|
+
var createPartySchema = z6.object({
|
|
1235
|
+
type: z6.enum(["person", "organisation"]),
|
|
1182
1236
|
// person
|
|
1183
|
-
firstName:
|
|
1184
|
-
lastName:
|
|
1185
|
-
title:
|
|
1186
|
-
jobTitle:
|
|
1187
|
-
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"),
|
|
1188
1242
|
// organisation
|
|
1189
|
-
name:
|
|
1243
|
+
name: z6.string().optional(),
|
|
1190
1244
|
...PartyWriteBaseSchema
|
|
1191
1245
|
});
|
|
1192
1246
|
async function createParty(input) {
|
|
1193
1247
|
const { ownerId, organisationId, ...rest } = input;
|
|
1194
1248
|
const body = { ...rest };
|
|
1195
|
-
|
|
1196
|
-
|
|
1249
|
+
setRef(body, "owner", ownerId);
|
|
1250
|
+
setRef(body, "organisation", organisationId);
|
|
1197
1251
|
return capsulePost("/parties", { party: body });
|
|
1198
1252
|
}
|
|
1199
|
-
var updatePartySchema =
|
|
1200
|
-
id:
|
|
1201
|
-
firstName:
|
|
1202
|
-
lastName:
|
|
1203
|
-
title:
|
|
1204
|
-
jobTitle:
|
|
1205
|
-
name:
|
|
1206
|
-
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")),
|
|
1207
1261
|
...PartyWriteBaseSchema
|
|
1208
1262
|
});
|
|
1209
1263
|
async function updateParty(input) {
|
|
@@ -1212,39 +1266,26 @@ async function updateParty(input) {
|
|
|
1212
1266
|
for (const [k, v] of Object.entries(rest)) {
|
|
1213
1267
|
if (v !== void 0) body[k] = v;
|
|
1214
1268
|
}
|
|
1215
|
-
|
|
1269
|
+
setRef(body, "owner", ownerId);
|
|
1216
1270
|
const mappedFields = mapFieldsForBody(fields);
|
|
1217
1271
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1218
1272
|
return capsulePut(`/parties/${id}`, { party: body });
|
|
1219
1273
|
}
|
|
1220
|
-
var batchUpdatePartySchema =
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
)
|
|
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
|
|
1224
1279
|
});
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
id: z3.number().int().positive(),
|
|
1230
|
-
confirm: confirmFlag().describe(
|
|
1231
|
-
"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."
|
|
1232
|
-
)
|
|
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."
|
|
1233
1284
|
});
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
return idempotent(
|
|
1239
|
-
() => capsuleDelete(`/parties/${input.id}`),
|
|
1240
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1241
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1242
|
-
);
|
|
1243
|
-
}
|
|
1244
|
-
var addPartyEmailAddressSchema = z3.object({
|
|
1245
|
-
partyId: z3.number().int().positive(),
|
|
1246
|
-
address: z3.string().email(),
|
|
1247
|
-
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'.")
|
|
1248
1289
|
});
|
|
1249
1290
|
async function addPartyEmailAddress(input) {
|
|
1250
1291
|
const { partyId, address, type } = input;
|
|
@@ -1254,9 +1295,9 @@ async function addPartyEmailAddress(input) {
|
|
|
1254
1295
|
party: { emailAddresses: [item] }
|
|
1255
1296
|
});
|
|
1256
1297
|
}
|
|
1257
|
-
var removePartyEmailAddressByIdSchema =
|
|
1258
|
-
partyId:
|
|
1259
|
-
emailAddressId:
|
|
1298
|
+
var removePartyEmailAddressByIdSchema = z6.object({
|
|
1299
|
+
partyId: positiveId,
|
|
1300
|
+
emailAddressId: positiveId.describe(
|
|
1260
1301
|
"Capsule's id for the email-address row. Read it from get_party (each entry in emailAddresses carries an id)."
|
|
1261
1302
|
)
|
|
1262
1303
|
});
|
|
@@ -1276,10 +1317,10 @@ async function removePartyEmailAddressById(input) {
|
|
|
1276
1317
|
() => ({ removed: true, alreadyRemoved: true, partyId, emailAddressId })
|
|
1277
1318
|
);
|
|
1278
1319
|
}
|
|
1279
|
-
var addPartyPhoneNumberSchema =
|
|
1280
|
-
partyId:
|
|
1281
|
-
number:
|
|
1282
|
-
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'.")
|
|
1283
1324
|
});
|
|
1284
1325
|
async function addPartyPhoneNumber(input) {
|
|
1285
1326
|
const { partyId, number, type } = input;
|
|
@@ -1289,9 +1330,9 @@ async function addPartyPhoneNumber(input) {
|
|
|
1289
1330
|
party: { phoneNumbers: [item] }
|
|
1290
1331
|
});
|
|
1291
1332
|
}
|
|
1292
|
-
var removePartyPhoneNumberByIdSchema =
|
|
1293
|
-
partyId:
|
|
1294
|
-
phoneNumberId:
|
|
1333
|
+
var removePartyPhoneNumberByIdSchema = z6.object({
|
|
1334
|
+
partyId: positiveId,
|
|
1335
|
+
phoneNumberId: positiveId.describe(
|
|
1295
1336
|
"Capsule's id for the phone-number row. Read it from get_party (each entry in phoneNumbers carries an id)."
|
|
1296
1337
|
)
|
|
1297
1338
|
});
|
|
@@ -1311,14 +1352,14 @@ async function removePartyPhoneNumberById(input) {
|
|
|
1311
1352
|
() => ({ removed: true, alreadyRemoved: true, partyId, phoneNumberId })
|
|
1312
1353
|
);
|
|
1313
1354
|
}
|
|
1314
|
-
var addPartyAddressSchema =
|
|
1315
|
-
partyId:
|
|
1316
|
-
street:
|
|
1317
|
-
city:
|
|
1318
|
-
state:
|
|
1319
|
-
country:
|
|
1320
|
-
zip:
|
|
1321
|
-
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'.")
|
|
1322
1363
|
});
|
|
1323
1364
|
async function addPartyAddress(input) {
|
|
1324
1365
|
const { partyId, ...rest } = input;
|
|
@@ -1330,9 +1371,9 @@ async function addPartyAddress(input) {
|
|
|
1330
1371
|
party: { addresses: [item] }
|
|
1331
1372
|
});
|
|
1332
1373
|
}
|
|
1333
|
-
var removePartyAddressByIdSchema =
|
|
1334
|
-
partyId:
|
|
1335
|
-
addressId:
|
|
1374
|
+
var removePartyAddressByIdSchema = z6.object({
|
|
1375
|
+
partyId: positiveId,
|
|
1376
|
+
addressId: positiveId.describe(
|
|
1336
1377
|
"Capsule's id for the address row. Read it from get_party (each entry in addresses carries an id)."
|
|
1337
1378
|
)
|
|
1338
1379
|
});
|
|
@@ -1352,9 +1393,9 @@ async function removePartyAddressById(input) {
|
|
|
1352
1393
|
() => ({ removed: true, alreadyRemoved: true, partyId, addressId })
|
|
1353
1394
|
);
|
|
1354
1395
|
}
|
|
1355
|
-
var addPartyWebsiteSchema =
|
|
1356
|
-
partyId:
|
|
1357
|
-
address:
|
|
1396
|
+
var addPartyWebsiteSchema = z6.object({
|
|
1397
|
+
partyId: positiveId,
|
|
1398
|
+
address: z6.string().min(1).describe(
|
|
1358
1399
|
"The website address. A URL when service='URL', or a handle (e.g. '@acmeco') for social services."
|
|
1359
1400
|
),
|
|
1360
1401
|
service: WebsiteServiceEnum.optional().describe("Defaults to 'URL' if omitted.")
|
|
@@ -1367,9 +1408,9 @@ async function addPartyWebsite(input) {
|
|
|
1367
1408
|
party: { websites: [item] }
|
|
1368
1409
|
});
|
|
1369
1410
|
}
|
|
1370
|
-
var removePartyWebsiteByIdSchema =
|
|
1371
|
-
partyId:
|
|
1372
|
-
websiteId:
|
|
1411
|
+
var removePartyWebsiteByIdSchema = z6.object({
|
|
1412
|
+
partyId: positiveId,
|
|
1413
|
+
websiteId: positiveId.describe(
|
|
1373
1414
|
"Capsule's id for the website row. Read it from get_party (each entry in websites carries an id)."
|
|
1374
1415
|
)
|
|
1375
1416
|
});
|
|
@@ -1391,20 +1432,32 @@ async function removePartyWebsiteById(input) {
|
|
|
1391
1432
|
}
|
|
1392
1433
|
|
|
1393
1434
|
// src/tools/opportunities.ts
|
|
1394
|
-
import { z as
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
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({
|
|
1398
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
|
|
1399
1452
|
}).length(3).describe(
|
|
1400
1453
|
"ISO 4217 currency code (3 letters), e.g. 'GBP', 'USD', 'EUR'. Required when amount is set."
|
|
1401
1454
|
)
|
|
1402
1455
|
});
|
|
1403
|
-
var searchOpportunitiesSchema =
|
|
1404
|
-
q:
|
|
1405
|
-
embed:
|
|
1406
|
-
page:
|
|
1407
|
-
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)
|
|
1408
1461
|
});
|
|
1409
1462
|
async function searchOpportunities(input) {
|
|
1410
1463
|
const path = input.q ? "/opportunities/search" : "/opportunities";
|
|
@@ -1416,9 +1469,9 @@ async function searchOpportunities(input) {
|
|
|
1416
1469
|
});
|
|
1417
1470
|
return { ...data, nextPage };
|
|
1418
1471
|
}
|
|
1419
|
-
var getOpportunitySchema =
|
|
1420
|
-
id:
|
|
1421
|
-
embed:
|
|
1472
|
+
var getOpportunitySchema = z7.object({
|
|
1473
|
+
id: positiveId,
|
|
1474
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1422
1475
|
});
|
|
1423
1476
|
async function getOpportunity(input) {
|
|
1424
1477
|
const { data } = await capsuleGet(`/opportunities/${input.id}`, {
|
|
@@ -1426,11 +1479,11 @@ async function getOpportunity(input) {
|
|
|
1426
1479
|
});
|
|
1427
1480
|
return data;
|
|
1428
1481
|
}
|
|
1429
|
-
var getOpportunitiesSchema =
|
|
1430
|
-
ids:
|
|
1482
|
+
var getOpportunitiesSchema = z7.object({
|
|
1483
|
+
ids: z7.array(positiveId).min(1).max(50).describe(
|
|
1431
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."
|
|
1432
1485
|
),
|
|
1433
|
-
embed:
|
|
1486
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1434
1487
|
});
|
|
1435
1488
|
async function getOpportunities(input) {
|
|
1436
1489
|
const { ids, embed } = input;
|
|
@@ -1451,20 +1504,20 @@ async function getOpportunities(input) {
|
|
|
1451
1504
|
);
|
|
1452
1505
|
return { opportunities: responses.flatMap((r) => r.data.opportunities) };
|
|
1453
1506
|
}
|
|
1454
|
-
var createOpportunitySchema =
|
|
1455
|
-
name:
|
|
1456
|
-
partyId:
|
|
1457
|
-
milestoneId:
|
|
1458
|
-
"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."
|
|
1459
1512
|
),
|
|
1460
|
-
description:
|
|
1513
|
+
description: z7.string().optional(),
|
|
1461
1514
|
value: OpportunityValueSchema.optional(),
|
|
1462
|
-
expectedCloseOn:
|
|
1463
|
-
probability:
|
|
1464
|
-
ownerId:
|
|
1465
|
-
"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."
|
|
1466
1519
|
),
|
|
1467
|
-
teamId:
|
|
1520
|
+
teamId: positiveId.optional().describe(
|
|
1468
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)."
|
|
1469
1522
|
)
|
|
1470
1523
|
});
|
|
@@ -1475,32 +1528,32 @@ async function createOpportunity(input) {
|
|
|
1475
1528
|
party: { id: partyId },
|
|
1476
1529
|
milestone: { id: milestoneId }
|
|
1477
1530
|
};
|
|
1478
|
-
|
|
1479
|
-
|
|
1531
|
+
setRef(body, "owner", ownerId);
|
|
1532
|
+
setRef(body, "team", teamId);
|
|
1480
1533
|
return capsulePost("/opportunities", { opportunity: body });
|
|
1481
1534
|
}
|
|
1482
|
-
var updateOpportunitySchema =
|
|
1483
|
-
id:
|
|
1484
|
-
name:
|
|
1485
|
-
milestoneId:
|
|
1486
|
-
"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."
|
|
1487
1540
|
),
|
|
1488
|
-
description:
|
|
1541
|
+
description: z7.string().optional(),
|
|
1489
1542
|
value: OpportunityValueSchema.optional(),
|
|
1490
|
-
expectedCloseOn:
|
|
1491
|
-
probability:
|
|
1543
|
+
expectedCloseOn: z7.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
1544
|
+
probability: z7.number().int().min(0).max(100).optional().describe(
|
|
1492
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)."
|
|
1493
1546
|
),
|
|
1494
|
-
lostReasonId:
|
|
1547
|
+
lostReasonId: positiveId.optional().describe(
|
|
1495
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."
|
|
1496
1549
|
),
|
|
1497
|
-
ownerId:
|
|
1550
|
+
ownerId: positiveId.optional().describe(
|
|
1498
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."
|
|
1499
1552
|
),
|
|
1500
|
-
teamId:
|
|
1553
|
+
teamId: positiveId.nullable().optional().describe(
|
|
1501
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."
|
|
1502
1555
|
),
|
|
1503
|
-
fields:
|
|
1556
|
+
fields: z7.array(CustomFieldWriteSchema).optional().describe(fieldsArrayDescriptor("get_opportunity"))
|
|
1504
1557
|
});
|
|
1505
1558
|
async function updateOpportunity(input) {
|
|
1506
1559
|
const { id, milestoneId, ownerId, teamId, lostReasonId, fields, ...rest } = input;
|
|
@@ -1508,59 +1561,39 @@ async function updateOpportunity(input) {
|
|
|
1508
1561
|
for (const [k, v] of Object.entries(rest)) {
|
|
1509
1562
|
if (v !== void 0) body[k] = v;
|
|
1510
1563
|
}
|
|
1511
|
-
|
|
1564
|
+
setRef(body, "milestone", milestoneId);
|
|
1512
1565
|
let resolvedTeamId = teamId;
|
|
1513
1566
|
if (ownerId !== void 0 && teamId === void 0) {
|
|
1514
|
-
|
|
1515
|
-
resolvedTeamId = data.opportunity?.team?.id ?? void 0;
|
|
1567
|
+
({ teamId: resolvedTeamId } = await readEntityRefs(`/opportunities/${id}`, "opportunity"));
|
|
1516
1568
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
if (lostReasonId) body["lostReason"] = { id: lostReasonId };
|
|
1569
|
+
setRef(body, "owner", ownerId);
|
|
1570
|
+
setNullableRef(body, "team", resolvedTeamId);
|
|
1571
|
+
setRef(body, "lostReason", lostReasonId);
|
|
1521
1572
|
const mappedFields = mapFieldsForBody(fields);
|
|
1522
1573
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1523
1574
|
return capsulePut(`/opportunities/${id}`, {
|
|
1524
1575
|
opportunity: body
|
|
1525
1576
|
});
|
|
1526
1577
|
}
|
|
1527
|
-
var batchUpdateOpportunitySchema =
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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
|
|
1531
1583
|
});
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
(item) => updateOpportunity(item),
|
|
1537
|
-
opts
|
|
1538
|
-
);
|
|
1539
|
-
}
|
|
1540
|
-
var deleteOpportunitySchema = z4.object({
|
|
1541
|
-
id: z4.number().int().positive(),
|
|
1542
|
-
confirm: confirmFlag().describe(
|
|
1543
|
-
"Must be set to true. Permanently deletes the opportunity. Irreversible."
|
|
1544
|
-
)
|
|
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."
|
|
1545
1588
|
});
|
|
1546
|
-
async function deleteOpportunity(input) {
|
|
1547
|
-
if (input.confirm !== true) {
|
|
1548
|
-
throw new Error("delete_opportunity requires confirm: true");
|
|
1549
|
-
}
|
|
1550
|
-
return idempotent(
|
|
1551
|
-
() => capsuleDelete(`/opportunities/${input.id}`),
|
|
1552
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1553
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1554
|
-
);
|
|
1555
|
-
}
|
|
1556
1589
|
|
|
1557
1590
|
// src/tools/projects.ts
|
|
1558
|
-
import { z as
|
|
1559
|
-
var listProjectsSchema =
|
|
1560
|
-
status:
|
|
1561
|
-
embed:
|
|
1562
|
-
page:
|
|
1563
|
-
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)
|
|
1564
1597
|
});
|
|
1565
1598
|
async function listProjects(input) {
|
|
1566
1599
|
const { data, nextPage } = await capsuleGet("/kases", {
|
|
@@ -1571,9 +1604,9 @@ async function listProjects(input) {
|
|
|
1571
1604
|
});
|
|
1572
1605
|
return { ...data, nextPage };
|
|
1573
1606
|
}
|
|
1574
|
-
var getProjectSchema =
|
|
1575
|
-
id:
|
|
1576
|
-
embed:
|
|
1607
|
+
var getProjectSchema = z8.object({
|
|
1608
|
+
id: positiveId,
|
|
1609
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1577
1610
|
});
|
|
1578
1611
|
async function getProject(input) {
|
|
1579
1612
|
const { data } = await capsuleGet(`/kases/${input.id}`, {
|
|
@@ -1581,11 +1614,11 @@ async function getProject(input) {
|
|
|
1581
1614
|
});
|
|
1582
1615
|
return data;
|
|
1583
1616
|
}
|
|
1584
|
-
var getProjectsSchema =
|
|
1585
|
-
ids:
|
|
1617
|
+
var getProjectsSchema = z8.object({
|
|
1618
|
+
ids: z8.array(positiveId).min(1).max(50).describe(
|
|
1586
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."
|
|
1587
1620
|
),
|
|
1588
|
-
embed:
|
|
1621
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1589
1622
|
});
|
|
1590
1623
|
async function getProjects(input) {
|
|
1591
1624
|
const { ids, embed } = input;
|
|
@@ -1603,21 +1636,21 @@ async function getProjects(input) {
|
|
|
1603
1636
|
);
|
|
1604
1637
|
return { kases: responses.flatMap((r) => r.data.kases) };
|
|
1605
1638
|
}
|
|
1606
|
-
var createProjectSchema =
|
|
1607
|
-
name:
|
|
1608
|
-
partyId:
|
|
1609
|
-
description:
|
|
1610
|
-
status:
|
|
1611
|
-
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(
|
|
1612
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."
|
|
1613
1646
|
),
|
|
1614
|
-
teamId:
|
|
1647
|
+
teamId: positiveId.optional().describe(
|
|
1615
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."
|
|
1616
1649
|
),
|
|
1617
|
-
stageId:
|
|
1650
|
+
stageId: positiveId.optional().describe(
|
|
1618
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."
|
|
1619
1652
|
),
|
|
1620
|
-
expectedCloseOn:
|
|
1653
|
+
expectedCloseOn: z8.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD")
|
|
1621
1654
|
});
|
|
1622
1655
|
async function createProject(input) {
|
|
1623
1656
|
const { partyId, ownerId, teamId, status, stageId, ...rest } = input;
|
|
@@ -1626,27 +1659,27 @@ async function createProject(input) {
|
|
|
1626
1659
|
status: status ?? "OPEN",
|
|
1627
1660
|
party: { id: partyId }
|
|
1628
1661
|
};
|
|
1629
|
-
|
|
1630
|
-
|
|
1662
|
+
setRef(body, "owner", ownerId);
|
|
1663
|
+
setRef(body, "team", teamId);
|
|
1631
1664
|
if (stageId) body["stage"] = stageId;
|
|
1632
1665
|
return capsulePost("/kases", { kase: body });
|
|
1633
1666
|
}
|
|
1634
|
-
var updateProjectSchema =
|
|
1635
|
-
id:
|
|
1636
|
-
name:
|
|
1637
|
-
description:
|
|
1638
|
-
status:
|
|
1639
|
-
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(
|
|
1640
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)."
|
|
1641
1674
|
),
|
|
1642
|
-
teamId:
|
|
1675
|
+
teamId: positiveId.nullable().optional().describe(
|
|
1643
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'."
|
|
1644
1677
|
),
|
|
1645
|
-
stageId:
|
|
1678
|
+
stageId: positiveId.optional().describe(
|
|
1646
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."
|
|
1647
1680
|
),
|
|
1648
|
-
expectedCloseOn:
|
|
1649
|
-
fields:
|
|
1681
|
+
expectedCloseOn: z8.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD"),
|
|
1682
|
+
fields: z8.array(CustomFieldWriteSchema).optional().describe(
|
|
1650
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."
|
|
1651
1684
|
)
|
|
1652
1685
|
});
|
|
@@ -1659,55 +1692,38 @@ async function updateProject(input) {
|
|
|
1659
1692
|
let resolvedTeamId = teamId;
|
|
1660
1693
|
let resolvedStageId = stageId;
|
|
1661
1694
|
if (ownerId !== void 0 && (teamId === void 0 || stageId === void 0)) {
|
|
1662
|
-
const
|
|
1663
|
-
if (teamId === void 0)
|
|
1664
|
-
|
|
1665
|
-
}
|
|
1666
|
-
if (stageId === void 0) {
|
|
1667
|
-
resolvedStageId = data.kase?.stage?.id ?? void 0;
|
|
1668
|
-
}
|
|
1695
|
+
const current = await readEntityRefs(`/kases/${id}`, "kase");
|
|
1696
|
+
if (teamId === void 0) resolvedTeamId = current.teamId;
|
|
1697
|
+
if (stageId === void 0) resolvedStageId = current.stageId;
|
|
1669
1698
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
if (resolvedTeamId === null) body["team"] = null;
|
|
1673
|
-
else if (resolvedTeamId !== void 0) body["team"] = { id: resolvedTeamId };
|
|
1699
|
+
setNullableRef(body, "owner", ownerId);
|
|
1700
|
+
setNullableRef(body, "team", resolvedTeamId);
|
|
1674
1701
|
if (resolvedStageId) body["stage"] = resolvedStageId;
|
|
1675
1702
|
const mappedFields = mapFieldsForBody(fields);
|
|
1676
1703
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1677
1704
|
return capsulePut(`/kases/${id}`, { kase: body });
|
|
1678
1705
|
}
|
|
1679
|
-
var deleteProjectSchema =
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
)
|
|
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."
|
|
1684
1710
|
});
|
|
1685
|
-
async function deleteProject(input) {
|
|
1686
|
-
if (input.confirm !== true) {
|
|
1687
|
-
throw new Error("delete_project requires confirm: true");
|
|
1688
|
-
}
|
|
1689
|
-
return idempotent(
|
|
1690
|
-
() => capsuleDelete(`/kases/${input.id}`),
|
|
1691
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1692
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1693
|
-
);
|
|
1694
|
-
}
|
|
1695
1711
|
|
|
1696
1712
|
// src/tools/tasks.ts
|
|
1697
|
-
import { z as
|
|
1698
|
-
var listTasksSchema =
|
|
1713
|
+
import { z as z9 } from "zod";
|
|
1714
|
+
var listTasksSchema = z9.object({
|
|
1699
1715
|
// Note: Capsule has a third internal status `PENDING` (a task that's
|
|
1700
1716
|
// part of an active track but not yet "open"), but it can only be
|
|
1701
1717
|
// reached via track machinery — it is NOT directly settable by
|
|
1702
1718
|
// /tasks PUT, and a list filter for it returns the same as OPEN
|
|
1703
1719
|
// anyway. We expose only the two values that are actually filterable
|
|
1704
1720
|
// by the v2 API.
|
|
1705
|
-
status:
|
|
1721
|
+
status: z9.enum(["OPEN", "COMPLETED"]).optional().describe(
|
|
1706
1722
|
"Defaults to OPEN when omitted. Pass COMPLETED to filter to completed tasks, or 'OPEN' explicitly."
|
|
1707
1723
|
),
|
|
1708
|
-
ownerId:
|
|
1709
|
-
page:
|
|
1710
|
-
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)
|
|
1711
1727
|
});
|
|
1712
1728
|
async function listTasks(input) {
|
|
1713
1729
|
const { data, nextPage } = await capsuleGet("/tasks", {
|
|
@@ -1721,15 +1737,15 @@ async function listTasks(input) {
|
|
|
1721
1737
|
});
|
|
1722
1738
|
return { ...data, nextPage };
|
|
1723
1739
|
}
|
|
1724
|
-
var getTaskSchema =
|
|
1725
|
-
id:
|
|
1740
|
+
var getTaskSchema = z9.object({
|
|
1741
|
+
id: positiveId.describe("Task ID")
|
|
1726
1742
|
});
|
|
1727
1743
|
async function getTask(input) {
|
|
1728
1744
|
const { data } = await capsuleGet(`/tasks/${input.id}`);
|
|
1729
1745
|
return data;
|
|
1730
1746
|
}
|
|
1731
|
-
var getTasksSchema =
|
|
1732
|
-
ids:
|
|
1747
|
+
var getTasksSchema = z9.object({
|
|
1748
|
+
ids: z9.array(positiveId).min(1).max(50).describe(
|
|
1733
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."
|
|
1734
1750
|
)
|
|
1735
1751
|
});
|
|
@@ -1745,17 +1761,17 @@ async function getTasks(input) {
|
|
|
1745
1761
|
);
|
|
1746
1762
|
return { tasks: responses.flatMap((r) => r.data.tasks) };
|
|
1747
1763
|
}
|
|
1748
|
-
var createTaskSchema =
|
|
1749
|
-
description:
|
|
1750
|
-
dueOn:
|
|
1751
|
-
dueTime:
|
|
1752
|
-
detail:
|
|
1753
|
-
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(
|
|
1754
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."
|
|
1755
1771
|
),
|
|
1756
|
-
partyId:
|
|
1757
|
-
opportunityId:
|
|
1758
|
-
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)")
|
|
1759
1775
|
});
|
|
1760
1776
|
async function createTask(input) {
|
|
1761
1777
|
const linked = [input.partyId, input.opportunityId, input.projectId].filter(Boolean);
|
|
@@ -1764,25 +1780,25 @@ async function createTask(input) {
|
|
|
1764
1780
|
}
|
|
1765
1781
|
const { ownerId, partyId, opportunityId, projectId, ...rest } = input;
|
|
1766
1782
|
const body = { ...rest };
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1783
|
+
setRef(body, "owner", ownerId);
|
|
1784
|
+
setRef(body, "party", partyId);
|
|
1785
|
+
setRef(body, "opportunity", opportunityId);
|
|
1786
|
+
setRef(body, "kase", projectId);
|
|
1771
1787
|
return capsulePost("/tasks", { task: body });
|
|
1772
1788
|
}
|
|
1773
|
-
var updateTaskSchema =
|
|
1774
|
-
id:
|
|
1775
|
-
description:
|
|
1776
|
-
dueOn:
|
|
1777
|
-
dueTime:
|
|
1778
|
-
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(),
|
|
1779
1795
|
// Capsule rejects direct sets of `PENDING` (which is a track-machinery
|
|
1780
1796
|
// internal state) with 422 "cannot set task status to PENDING".
|
|
1781
1797
|
// Only OPEN and COMPLETED are settable here.
|
|
1782
|
-
status:
|
|
1798
|
+
status: z9.enum(["OPEN", "COMPLETED"]).optional().describe(
|
|
1783
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)."
|
|
1784
1800
|
),
|
|
1785
|
-
ownerId:
|
|
1801
|
+
ownerId: positiveId.optional().describe(
|
|
1786
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."
|
|
1787
1803
|
)
|
|
1788
1804
|
});
|
|
@@ -1792,51 +1808,40 @@ async function updateTask(input) {
|
|
|
1792
1808
|
for (const [k, v] of Object.entries(rest)) {
|
|
1793
1809
|
if (v !== void 0) body[k] = v;
|
|
1794
1810
|
}
|
|
1795
|
-
|
|
1811
|
+
setRef(body, "owner", ownerId);
|
|
1796
1812
|
return capsulePut(`/tasks/${id}`, { task: body });
|
|
1797
1813
|
}
|
|
1798
|
-
var completeTaskSchema =
|
|
1799
|
-
id:
|
|
1814
|
+
var completeTaskSchema = z9.object({
|
|
1815
|
+
id: positiveId
|
|
1800
1816
|
});
|
|
1801
1817
|
async function completeTask(input) {
|
|
1802
1818
|
return capsulePut(`/tasks/${input.id}`, {
|
|
1803
1819
|
task: { status: "COMPLETED" }
|
|
1804
1820
|
});
|
|
1805
1821
|
}
|
|
1806
|
-
var batchCompleteTaskSchema =
|
|
1807
|
-
ids:
|
|
1822
|
+
var batchCompleteTaskSchema = z9.object({
|
|
1823
|
+
ids: z9.array(positiveId).min(1).max(50).describe(
|
|
1808
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."
|
|
1809
1825
|
)
|
|
1810
1826
|
});
|
|
1811
1827
|
async function batchCompleteTask(input, opts = {}) {
|
|
1812
1828
|
return batchExecute("batch_complete_task", input.ids, (id) => completeTask({ id }), opts);
|
|
1813
1829
|
}
|
|
1814
|
-
var deleteTaskSchema =
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
)
|
|
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."
|
|
1819
1834
|
});
|
|
1820
|
-
async function deleteTask(input) {
|
|
1821
|
-
if (input.confirm !== true) {
|
|
1822
|
-
throw new Error("delete_task requires confirm: true");
|
|
1823
|
-
}
|
|
1824
|
-
return idempotent(
|
|
1825
|
-
() => capsuleDelete(`/tasks/${input.id}`),
|
|
1826
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1827
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1828
|
-
);
|
|
1829
|
-
}
|
|
1830
1835
|
|
|
1831
1836
|
// src/tools/entries.ts
|
|
1832
|
-
import { z as
|
|
1837
|
+
import { z as z10 } from "zod";
|
|
1833
1838
|
var listEntriesPagination = {
|
|
1834
|
-
page:
|
|
1835
|
-
perPage:
|
|
1836
|
-
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)
|
|
1837
1842
|
};
|
|
1838
|
-
var listPartyEntriesSchema =
|
|
1839
|
-
partyId:
|
|
1843
|
+
var listPartyEntriesSchema = z10.object({
|
|
1844
|
+
partyId: positiveId,
|
|
1840
1845
|
...listEntriesPagination
|
|
1841
1846
|
});
|
|
1842
1847
|
async function listPartyEntries(input) {
|
|
@@ -1846,8 +1851,8 @@ async function listPartyEntries(input) {
|
|
|
1846
1851
|
);
|
|
1847
1852
|
return { ...data, nextPage };
|
|
1848
1853
|
}
|
|
1849
|
-
var listOpportunityEntriesSchema =
|
|
1850
|
-
opportunityId:
|
|
1854
|
+
var listOpportunityEntriesSchema = z10.object({
|
|
1855
|
+
opportunityId: positiveId,
|
|
1851
1856
|
...listEntriesPagination
|
|
1852
1857
|
});
|
|
1853
1858
|
async function listOpportunityEntries(input) {
|
|
@@ -1857,8 +1862,8 @@ async function listOpportunityEntries(input) {
|
|
|
1857
1862
|
);
|
|
1858
1863
|
return { ...data, nextPage };
|
|
1859
1864
|
}
|
|
1860
|
-
var listProjectEntriesSchema =
|
|
1861
|
-
projectId:
|
|
1865
|
+
var listProjectEntriesSchema = z10.object({
|
|
1866
|
+
projectId: positiveId,
|
|
1862
1867
|
...listEntriesPagination
|
|
1863
1868
|
});
|
|
1864
1869
|
async function listProjectEntries(input) {
|
|
@@ -1868,9 +1873,9 @@ async function listProjectEntries(input) {
|
|
|
1868
1873
|
);
|
|
1869
1874
|
return { ...data, nextPage };
|
|
1870
1875
|
}
|
|
1871
|
-
var getEntrySchema =
|
|
1872
|
-
id:
|
|
1873
|
-
embed:
|
|
1876
|
+
var getEntrySchema = z10.object({
|
|
1877
|
+
id: positiveId,
|
|
1878
|
+
embed: z10.string().optional().describe(EMBED_ATTACHMENTS_PARTICIPANTS_DESCRIPTION)
|
|
1874
1879
|
});
|
|
1875
1880
|
async function getEntry(input) {
|
|
1876
1881
|
const { data } = await capsuleGet(`/entries/${input.id}`, {
|
|
@@ -1878,7 +1883,7 @@ async function getEntry(input) {
|
|
|
1878
1883
|
});
|
|
1879
1884
|
return data;
|
|
1880
1885
|
}
|
|
1881
|
-
var listEntriesSchema =
|
|
1886
|
+
var listEntriesSchema = z10.object({
|
|
1882
1887
|
...listEntriesPagination
|
|
1883
1888
|
});
|
|
1884
1889
|
async function listEntries(input) {
|
|
@@ -1889,14 +1894,14 @@ async function listEntries(input) {
|
|
|
1889
1894
|
});
|
|
1890
1895
|
return { ...data, nextPage };
|
|
1891
1896
|
}
|
|
1892
|
-
var addNoteSchema =
|
|
1893
|
-
content:
|
|
1897
|
+
var addNoteSchema = z10.object({
|
|
1898
|
+
content: z10.string().min(1).describe(
|
|
1894
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."
|
|
1895
1900
|
),
|
|
1896
|
-
partyId:
|
|
1897
|
-
opportunityId:
|
|
1898
|
-
projectId:
|
|
1899
|
-
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(
|
|
1900
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)."
|
|
1901
1906
|
)
|
|
1902
1907
|
});
|
|
@@ -1907,18 +1912,18 @@ async function addNote(input) {
|
|
|
1907
1912
|
throw new Error("Provide exactly one of partyId, opportunityId, or projectId");
|
|
1908
1913
|
}
|
|
1909
1914
|
const body = { type: "note", content };
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1915
|
+
setRef(body, "party", partyId);
|
|
1916
|
+
setRef(body, "opportunity", opportunityId);
|
|
1917
|
+
setRef(body, "kase", projectId);
|
|
1913
1918
|
if (entryAt !== void 0) body["entryAt"] = entryAt;
|
|
1914
1919
|
return capsulePost("/entries", { entry: body });
|
|
1915
1920
|
}
|
|
1916
|
-
var updateEntrySchema =
|
|
1917
|
-
id:
|
|
1918
|
-
content:
|
|
1921
|
+
var updateEntrySchema = z10.object({
|
|
1922
|
+
id: positiveId.describe("Entry ID to update"),
|
|
1923
|
+
content: z10.string().min(1).optional().describe(
|
|
1919
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."
|
|
1920
1925
|
),
|
|
1921
|
-
subject:
|
|
1926
|
+
subject: z10.string().optional().describe(
|
|
1922
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`."
|
|
1923
1928
|
)
|
|
1924
1929
|
});
|
|
@@ -1932,30 +1937,20 @@ async function updateEntry(input) {
|
|
|
1932
1937
|
}
|
|
1933
1938
|
return capsulePut(`/entries/${id}`, { entry: body });
|
|
1934
1939
|
}
|
|
1935
|
-
var deleteEntrySchema =
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
)
|
|
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"
|
|
1940
1945
|
});
|
|
1941
|
-
async function deleteEntry(input) {
|
|
1942
|
-
if (input.confirm !== true) {
|
|
1943
|
-
throw new Error("delete_entry requires confirm: true");
|
|
1944
|
-
}
|
|
1945
|
-
return idempotent(
|
|
1946
|
-
() => capsuleDelete(`/entries/${input.id}`),
|
|
1947
|
-
() => ({ deleted: true, alreadyDeleted: false, id: input.id }),
|
|
1948
|
-
() => ({ deleted: true, alreadyDeleted: true, id: input.id })
|
|
1949
|
-
);
|
|
1950
|
-
}
|
|
1951
1946
|
|
|
1952
1947
|
// src/tools/pipelines.ts
|
|
1953
|
-
import { z as
|
|
1948
|
+
import { z as z11 } from "zod";
|
|
1954
1949
|
var paginationFields = {
|
|
1955
|
-
page:
|
|
1956
|
-
perPage:
|
|
1950
|
+
page: z11.number().int().positive().optional(),
|
|
1951
|
+
perPage: z11.number().int().min(1).max(100).optional()
|
|
1957
1952
|
};
|
|
1958
|
-
var listPipelinesSchema =
|
|
1953
|
+
var listPipelinesSchema = z11.object({ ...paginationFields });
|
|
1959
1954
|
async function listPipelines(input) {
|
|
1960
1955
|
const { data, nextPage } = await capsuleGetCached("/pipelines", {
|
|
1961
1956
|
page: input.page ?? 1,
|
|
@@ -1963,8 +1958,8 @@ async function listPipelines(input) {
|
|
|
1963
1958
|
});
|
|
1964
1959
|
return { ...data, nextPage };
|
|
1965
1960
|
}
|
|
1966
|
-
var listMilestonesSchema =
|
|
1967
|
-
pipelineId:
|
|
1961
|
+
var listMilestonesSchema = z11.object({
|
|
1962
|
+
pipelineId: positiveId,
|
|
1968
1963
|
...paginationFields
|
|
1969
1964
|
});
|
|
1970
1965
|
async function listMilestones(input) {
|
|
@@ -1976,12 +1971,12 @@ async function listMilestones(input) {
|
|
|
1976
1971
|
}
|
|
1977
1972
|
|
|
1978
1973
|
// src/tools/boards.ts
|
|
1979
|
-
import { z as
|
|
1974
|
+
import { z as z12 } from "zod";
|
|
1980
1975
|
var paginationFields2 = {
|
|
1981
|
-
page:
|
|
1982
|
-
perPage:
|
|
1976
|
+
page: z12.number().int().positive().optional(),
|
|
1977
|
+
perPage: z12.number().int().min(1).max(100).optional()
|
|
1983
1978
|
};
|
|
1984
|
-
var listBoardsSchema =
|
|
1979
|
+
var listBoardsSchema = z12.object({ ...paginationFields2 });
|
|
1985
1980
|
async function listBoards(input) {
|
|
1986
1981
|
const { data, nextPage } = await capsuleGetCached("/boards", {
|
|
1987
1982
|
page: input.page ?? 1,
|
|
@@ -1989,8 +1984,8 @@ async function listBoards(input) {
|
|
|
1989
1984
|
});
|
|
1990
1985
|
return { ...data, nextPage };
|
|
1991
1986
|
}
|
|
1992
|
-
var listStagesSchema =
|
|
1993
|
-
boardId:
|
|
1987
|
+
var listStagesSchema = z12.object({
|
|
1988
|
+
boardId: positiveId.optional().describe(
|
|
1994
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."
|
|
1995
1990
|
),
|
|
1996
1991
|
...paginationFields2
|
|
@@ -2005,7 +2000,7 @@ async function listStages(input) {
|
|
|
2005
2000
|
}
|
|
2006
2001
|
|
|
2007
2002
|
// src/tools/tags.ts
|
|
2008
|
-
import { z as
|
|
2003
|
+
import { z as z13 } from "zod";
|
|
2009
2004
|
var TAG_LIST_PATH = {
|
|
2010
2005
|
parties: "/parties/tags",
|
|
2011
2006
|
opportunities: "/opportunities/tags",
|
|
@@ -2016,11 +2011,11 @@ var ENTITY_TO_WRAPPER = {
|
|
|
2016
2011
|
opportunities: "opportunity",
|
|
2017
2012
|
kases: "kase"
|
|
2018
2013
|
};
|
|
2019
|
-
var TagEntity =
|
|
2020
|
-
var listTagsSchema =
|
|
2021
|
-
entity:
|
|
2022
|
-
page:
|
|
2023
|
-
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()
|
|
2024
2019
|
});
|
|
2025
2020
|
async function listTags(input) {
|
|
2026
2021
|
const path = TAG_LIST_PATH[input.entity];
|
|
@@ -2030,10 +2025,10 @@ async function listTags(input) {
|
|
|
2030
2025
|
});
|
|
2031
2026
|
return { ...data, nextPage };
|
|
2032
2027
|
}
|
|
2033
|
-
var addTagSchema =
|
|
2028
|
+
var addTagSchema = z13.object({
|
|
2034
2029
|
entity: TagEntity,
|
|
2035
|
-
entityId:
|
|
2036
|
-
tagName:
|
|
2030
|
+
entityId: positiveId.describe("The party/opportunity/kase id."),
|
|
2031
|
+
tagName: z13.string().min(1).describe(
|
|
2037
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."
|
|
2038
2033
|
)
|
|
2039
2034
|
});
|
|
@@ -2046,10 +2041,10 @@ async function addTag(input) {
|
|
|
2046
2041
|
invalidateByPrefix(TAG_LIST_PATH[entity], "add_tag");
|
|
2047
2042
|
return result;
|
|
2048
2043
|
}
|
|
2049
|
-
var removeTagByIdSchema =
|
|
2044
|
+
var removeTagByIdSchema = z13.object({
|
|
2050
2045
|
entity: TagEntity,
|
|
2051
|
-
entityId:
|
|
2052
|
-
tagId:
|
|
2046
|
+
entityId: positiveId.describe("The party/opportunity/kase id."),
|
|
2047
|
+
tagId: positiveId.describe(
|
|
2053
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."
|
|
2054
2049
|
)
|
|
2055
2050
|
});
|
|
@@ -2077,28 +2072,24 @@ async function removeTagById(input) {
|
|
|
2077
2072
|
invalidateByPrefix(TAG_LIST_PATH[entity], "remove_tag_by_id");
|
|
2078
2073
|
return result;
|
|
2079
2074
|
}
|
|
2080
|
-
var batchAddTagSchema =
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
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
|
|
2084
2080
|
});
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
"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."
|
|
2091
|
-
)
|
|
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
|
|
2092
2086
|
});
|
|
2093
|
-
async function batchRemoveTagById(input, opts = {}) {
|
|
2094
|
-
return batchExecute("batch_remove_tag_by_id", input.items, (item) => removeTagById(item), opts);
|
|
2095
|
-
}
|
|
2096
2087
|
|
|
2097
2088
|
// src/tools/users.ts
|
|
2098
|
-
import { z as
|
|
2099
|
-
var listUsersSchema =
|
|
2100
|
-
page:
|
|
2101
|
-
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()
|
|
2102
2093
|
});
|
|
2103
2094
|
async function listUsers(input) {
|
|
2104
2095
|
const { data, nextPage } = await capsuleGetCached("/users", {
|
|
@@ -2107,32 +2098,32 @@ async function listUsers(input) {
|
|
|
2107
2098
|
});
|
|
2108
2099
|
return { ...data, nextPage };
|
|
2109
2100
|
}
|
|
2110
|
-
var getCurrentUserSchema =
|
|
2101
|
+
var getCurrentUserSchema = z14.object({});
|
|
2111
2102
|
async function getCurrentUser(_input) {
|
|
2112
2103
|
const { data } = await capsuleGet("/users/current");
|
|
2113
2104
|
return data;
|
|
2114
2105
|
}
|
|
2115
2106
|
|
|
2116
2107
|
// src/tools/filters.ts
|
|
2117
|
-
import { z as
|
|
2118
|
-
var FilterConditionSchema =
|
|
2119
|
-
field:
|
|
2108
|
+
import { z as z15 } from "zod";
|
|
2109
|
+
var FilterConditionSchema = z15.object({
|
|
2110
|
+
field: z15.string().describe(
|
|
2120
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"
|
|
2121
2112
|
),
|
|
2122
|
-
operator:
|
|
2113
|
+
operator: z15.string().describe(
|
|
2123
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."
|
|
2124
2115
|
),
|
|
2125
|
-
value:
|
|
2116
|
+
value: z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]).describe(
|
|
2126
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."
|
|
2127
2118
|
)
|
|
2128
2119
|
});
|
|
2129
|
-
var FilterInputSchema =
|
|
2130
|
-
conditions:
|
|
2120
|
+
var FilterInputSchema = z15.object({
|
|
2121
|
+
conditions: z15.array(FilterConditionSchema).min(1).describe(
|
|
2131
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)."
|
|
2132
2123
|
),
|
|
2133
|
-
embed:
|
|
2134
|
-
page:
|
|
2135
|
-
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)
|
|
2136
2127
|
});
|
|
2137
2128
|
async function runFilter(entityPath, input) {
|
|
2138
2129
|
const { data, nextPage } = await capsuleSearch(
|
|
@@ -2163,12 +2154,12 @@ async function filterProjects(input) {
|
|
|
2163
2154
|
}
|
|
2164
2155
|
|
|
2165
2156
|
// src/tools/metadata.ts
|
|
2166
|
-
import { z as
|
|
2157
|
+
import { z as z16 } from "zod";
|
|
2167
2158
|
var paginationFields3 = {
|
|
2168
|
-
page:
|
|
2169
|
-
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.")
|
|
2170
2161
|
};
|
|
2171
|
-
var listTeamsSchema =
|
|
2162
|
+
var listTeamsSchema = z16.object({ ...paginationFields3 });
|
|
2172
2163
|
async function listTeams(input) {
|
|
2173
2164
|
const { data, nextPage } = await capsuleGetCached("/teams", {
|
|
2174
2165
|
page: input.page ?? 1,
|
|
@@ -2176,7 +2167,7 @@ async function listTeams(input) {
|
|
|
2176
2167
|
});
|
|
2177
2168
|
return { ...data, nextPage };
|
|
2178
2169
|
}
|
|
2179
|
-
var listLostReasonsSchema =
|
|
2170
|
+
var listLostReasonsSchema = z16.object({ ...paginationFields3 });
|
|
2180
2171
|
async function listLostReasons(input) {
|
|
2181
2172
|
const { data, nextPage } = await capsuleGetCached("/lostreasons", {
|
|
2182
2173
|
page: input.page ?? 1,
|
|
@@ -2184,7 +2175,7 @@ async function listLostReasons(input) {
|
|
|
2184
2175
|
});
|
|
2185
2176
|
return { ...data, nextPage };
|
|
2186
2177
|
}
|
|
2187
|
-
var listActivityTypesSchema =
|
|
2178
|
+
var listActivityTypesSchema = z16.object({ ...paginationFields3 });
|
|
2188
2179
|
async function listActivityTypes(input) {
|
|
2189
2180
|
const { data, nextPage } = await capsuleGetCached(
|
|
2190
2181
|
"/activitytypes",
|
|
@@ -2195,12 +2186,12 @@ async function listActivityTypes(input) {
|
|
|
2195
2186
|
);
|
|
2196
2187
|
return { ...data, nextPage };
|
|
2197
2188
|
}
|
|
2198
|
-
var getSiteSchema =
|
|
2189
|
+
var getSiteSchema = z16.object({});
|
|
2199
2190
|
async function getSite(_input) {
|
|
2200
2191
|
const { data } = await capsuleGetCached("/site");
|
|
2201
2192
|
return data;
|
|
2202
2193
|
}
|
|
2203
|
-
var listTrackDefinitionsSchema =
|
|
2194
|
+
var listTrackDefinitionsSchema = z16.object({ ...paginationFields3 });
|
|
2204
2195
|
async function listTrackDefinitions(input) {
|
|
2205
2196
|
const { data, nextPage } = await capsuleGetCached(
|
|
2206
2197
|
"/trackdefinitions",
|
|
@@ -2208,7 +2199,7 @@ async function listTrackDefinitions(input) {
|
|
|
2208
2199
|
);
|
|
2209
2200
|
return { ...data, nextPage };
|
|
2210
2201
|
}
|
|
2211
|
-
var listCategoriesSchema =
|
|
2202
|
+
var listCategoriesSchema = z16.object({ ...paginationFields3 });
|
|
2212
2203
|
async function listCategories(input) {
|
|
2213
2204
|
const { data, nextPage } = await capsuleGetCached("/categories", {
|
|
2214
2205
|
page: input.page ?? 1,
|
|
@@ -2216,7 +2207,7 @@ async function listCategories(input) {
|
|
|
2216
2207
|
});
|
|
2217
2208
|
return { ...data, nextPage };
|
|
2218
2209
|
}
|
|
2219
|
-
var listGoalsSchema =
|
|
2210
|
+
var listGoalsSchema = z16.object({ ...paginationFields3 });
|
|
2220
2211
|
async function listGoals(input) {
|
|
2221
2212
|
const { data, nextPage } = await capsuleGetCached("/goals", {
|
|
2222
2213
|
page: input.page ?? 1,
|
|
@@ -2226,14 +2217,14 @@ async function listGoals(input) {
|
|
|
2226
2217
|
}
|
|
2227
2218
|
|
|
2228
2219
|
// src/tools/audit.ts
|
|
2229
|
-
import { z as
|
|
2230
|
-
var listEmployeesSchema =
|
|
2231
|
-
partyId:
|
|
2220
|
+
import { z as z17 } from "zod";
|
|
2221
|
+
var listEmployeesSchema = z17.object({
|
|
2222
|
+
partyId: positiveId.describe(
|
|
2232
2223
|
"The organisation's party id. Returns the people whose `organisation` field links to this party."
|
|
2233
2224
|
),
|
|
2234
|
-
page:
|
|
2235
|
-
perPage:
|
|
2236
|
-
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)
|
|
2237
2228
|
});
|
|
2238
2229
|
async function listEmployees(input) {
|
|
2239
2230
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2242,15 +2233,15 @@ async function listEmployees(input) {
|
|
|
2242
2233
|
);
|
|
2243
2234
|
return { ...data, nextPage };
|
|
2244
2235
|
}
|
|
2245
|
-
var DeletedSinceSchema =
|
|
2236
|
+
var DeletedSinceSchema = z17.string().describe(
|
|
2246
2237
|
"REQUIRED. ISO-8601 timestamp; only deletions on or after this point are returned. Example: '2026-01-01T00:00:00Z'."
|
|
2247
2238
|
);
|
|
2248
2239
|
var DeletedPagination = {
|
|
2249
2240
|
since: DeletedSinceSchema,
|
|
2250
|
-
page:
|
|
2251
|
-
perPage:
|
|
2241
|
+
page: z17.number().int().positive().optional().default(1),
|
|
2242
|
+
perPage: z17.number().int().min(1).max(100).optional().default(25)
|
|
2252
2243
|
};
|
|
2253
|
-
var listDeletedPartiesSchema =
|
|
2244
|
+
var listDeletedPartiesSchema = z17.object(DeletedPagination);
|
|
2254
2245
|
async function listDeletedParties(input) {
|
|
2255
2246
|
const { data, nextPage } = await capsuleGet("/parties/deleted", {
|
|
2256
2247
|
since: input.since,
|
|
@@ -2259,7 +2250,7 @@ async function listDeletedParties(input) {
|
|
|
2259
2250
|
});
|
|
2260
2251
|
return { ...data, nextPage };
|
|
2261
2252
|
}
|
|
2262
|
-
var listDeletedOpportunitiesSchema =
|
|
2253
|
+
var listDeletedOpportunitiesSchema = z17.object(DeletedPagination);
|
|
2263
2254
|
async function listDeletedOpportunities(input) {
|
|
2264
2255
|
const { data, nextPage } = await capsuleGet("/opportunities/deleted", {
|
|
2265
2256
|
since: input.since,
|
|
@@ -2268,7 +2259,7 @@ async function listDeletedOpportunities(input) {
|
|
|
2268
2259
|
});
|
|
2269
2260
|
return { ...data, nextPage };
|
|
2270
2261
|
}
|
|
2271
|
-
var listDeletedProjectsSchema =
|
|
2262
|
+
var listDeletedProjectsSchema = z17.object(DeletedPagination);
|
|
2272
2263
|
async function listDeletedProjects(input) {
|
|
2273
2264
|
const { data, nextPage } = await capsuleGet("/kases/deleted", {
|
|
2274
2265
|
since: input.since,
|
|
@@ -2279,14 +2270,14 @@ async function listDeletedProjects(input) {
|
|
|
2279
2270
|
}
|
|
2280
2271
|
|
|
2281
2272
|
// src/tools/relationships.ts
|
|
2282
|
-
import { z as
|
|
2283
|
-
var RelationshipEntity =
|
|
2284
|
-
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({
|
|
2285
2276
|
entity: RelationshipEntity,
|
|
2286
|
-
entityId:
|
|
2287
|
-
embed:
|
|
2288
|
-
page:
|
|
2289
|
-
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)
|
|
2290
2281
|
});
|
|
2291
2282
|
async function listAdditionalParties(input) {
|
|
2292
2283
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2295,10 +2286,12 @@ async function listAdditionalParties(input) {
|
|
|
2295
2286
|
);
|
|
2296
2287
|
return { ...data, nextPage };
|
|
2297
2288
|
}
|
|
2298
|
-
var addAdditionalPartySchema =
|
|
2289
|
+
var addAdditionalPartySchema = z18.object({
|
|
2299
2290
|
entity: RelationshipEntity,
|
|
2300
|
-
entityId:
|
|
2301
|
-
partyId:
|
|
2291
|
+
entityId: positiveId,
|
|
2292
|
+
partyId: positiveId.describe(
|
|
2293
|
+
"ID of the party (person or organisation) to link as an additional party."
|
|
2294
|
+
)
|
|
2302
2295
|
});
|
|
2303
2296
|
async function addAdditionalParty(input) {
|
|
2304
2297
|
try {
|
|
@@ -2326,10 +2319,10 @@ async function addAdditionalParty(input) {
|
|
|
2326
2319
|
throw err;
|
|
2327
2320
|
}
|
|
2328
2321
|
}
|
|
2329
|
-
var removeAdditionalPartySchema =
|
|
2322
|
+
var removeAdditionalPartySchema = z18.object({
|
|
2330
2323
|
entity: RelationshipEntity,
|
|
2331
|
-
entityId:
|
|
2332
|
-
partyId:
|
|
2324
|
+
entityId: positiveId,
|
|
2325
|
+
partyId: positiveId,
|
|
2333
2326
|
confirm: confirmFlag().describe(
|
|
2334
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."
|
|
2335
2328
|
)
|
|
@@ -2356,11 +2349,11 @@ async function removeAdditionalParty(input) {
|
|
|
2356
2349
|
})
|
|
2357
2350
|
);
|
|
2358
2351
|
}
|
|
2359
|
-
var listAssociatedProjectsSchema =
|
|
2360
|
-
opportunityId:
|
|
2361
|
-
embed:
|
|
2362
|
-
page:
|
|
2363
|
-
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)
|
|
2364
2357
|
});
|
|
2365
2358
|
async function listAssociatedProjects(input) {
|
|
2366
2359
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2371,9 +2364,9 @@ async function listAssociatedProjects(input) {
|
|
|
2371
2364
|
}
|
|
2372
2365
|
|
|
2373
2366
|
// src/tools/custom-fields.ts
|
|
2374
|
-
import { z as
|
|
2375
|
-
var CustomFieldEntity =
|
|
2376
|
-
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({
|
|
2377
2370
|
entity: CustomFieldEntity
|
|
2378
2371
|
});
|
|
2379
2372
|
async function listCustomFields(input) {
|
|
@@ -2382,9 +2375,9 @@ async function listCustomFields(input) {
|
|
|
2382
2375
|
);
|
|
2383
2376
|
return data;
|
|
2384
2377
|
}
|
|
2385
|
-
var getCustomFieldSchema =
|
|
2378
|
+
var getCustomFieldSchema = z19.object({
|
|
2386
2379
|
entity: CustomFieldEntity,
|
|
2387
|
-
fieldId:
|
|
2380
|
+
fieldId: positiveId.describe("Custom field definition id.")
|
|
2388
2381
|
});
|
|
2389
2382
|
async function getCustomField(input) {
|
|
2390
2383
|
const { data } = await capsuleGetCached(
|
|
@@ -2394,11 +2387,11 @@ async function getCustomField(input) {
|
|
|
2394
2387
|
}
|
|
2395
2388
|
|
|
2396
2389
|
// src/tools/tracks.ts
|
|
2397
|
-
import { z as
|
|
2398
|
-
var TrackEntity =
|
|
2399
|
-
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({
|
|
2400
2393
|
entity: TrackEntity,
|
|
2401
|
-
entityId:
|
|
2394
|
+
entityId: positiveId
|
|
2402
2395
|
});
|
|
2403
2396
|
async function listEntityTracks(input) {
|
|
2404
2397
|
const { data } = await capsuleGet(
|
|
@@ -2406,20 +2399,20 @@ async function listEntityTracks(input) {
|
|
|
2406
2399
|
);
|
|
2407
2400
|
return data;
|
|
2408
2401
|
}
|
|
2409
|
-
var showTrackSchema =
|
|
2410
|
-
trackId:
|
|
2402
|
+
var showTrackSchema = z20.object({
|
|
2403
|
+
trackId: positiveId
|
|
2411
2404
|
});
|
|
2412
2405
|
async function showTrack(input) {
|
|
2413
2406
|
const { data } = await capsuleGet(`/tracks/${input.trackId}`);
|
|
2414
2407
|
return data;
|
|
2415
2408
|
}
|
|
2416
|
-
var applyTrackSchema =
|
|
2417
|
-
entity:
|
|
2418
|
-
entityId:
|
|
2419
|
-
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(
|
|
2420
2413
|
"The trackDefinition to apply (from list_track_definitions). Auto-creates task definitions on the target entity per the track's rules."
|
|
2421
2414
|
),
|
|
2422
|
-
startDate:
|
|
2415
|
+
startDate: z20.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe(
|
|
2423
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."
|
|
2424
2417
|
)
|
|
2425
2418
|
});
|
|
@@ -2432,9 +2425,9 @@ async function applyTrack(input) {
|
|
|
2432
2425
|
if (input.startDate !== void 0) track["trackDateOn"] = input.startDate;
|
|
2433
2426
|
return capsulePost("/tracks", { track });
|
|
2434
2427
|
}
|
|
2435
|
-
var updateTrackSchema =
|
|
2436
|
-
trackId:
|
|
2437
|
-
fields:
|
|
2428
|
+
var updateTrackSchema = z20.object({
|
|
2429
|
+
trackId: positiveId,
|
|
2430
|
+
fields: z20.record(z20.string(), z20.unknown()).describe(
|
|
2438
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."
|
|
2439
2432
|
)
|
|
2440
2433
|
});
|
|
@@ -2446,8 +2439,8 @@ async function updateTrack(input) {
|
|
|
2446
2439
|
track: input.fields
|
|
2447
2440
|
});
|
|
2448
2441
|
}
|
|
2449
|
-
var removeTrackSchema =
|
|
2450
|
-
trackId:
|
|
2442
|
+
var removeTrackSchema = z20.object({
|
|
2443
|
+
trackId: positiveId,
|
|
2451
2444
|
confirm: confirmFlag().describe(
|
|
2452
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."
|
|
2453
2446
|
)
|
|
@@ -2464,13 +2457,13 @@ async function removeTrack(input) {
|
|
|
2464
2457
|
}
|
|
2465
2458
|
|
|
2466
2459
|
// src/tools/attachments.ts
|
|
2467
|
-
import { z as
|
|
2460
|
+
import { z as z21 } from "zod";
|
|
2468
2461
|
var DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024;
|
|
2469
2462
|
var HARD_MAX_SIZE_BYTES = 25 * 1024 * 1024;
|
|
2470
2463
|
var HARD_MAX_BASE64_CHARS = Math.ceil(HARD_MAX_SIZE_BYTES / 3) * 4;
|
|
2471
|
-
var getAttachmentSchema =
|
|
2472
|
-
id:
|
|
2473
|
-
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(
|
|
2474
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.`
|
|
2475
2468
|
)
|
|
2476
2469
|
});
|
|
@@ -2485,22 +2478,22 @@ async function getAttachment(input) {
|
|
|
2485
2478
|
}
|
|
2486
2479
|
return { contentType, buffer, sizeBytes };
|
|
2487
2480
|
}
|
|
2488
|
-
var uploadAttachmentSchema =
|
|
2489
|
-
filename:
|
|
2481
|
+
var uploadAttachmentSchema = z21.object({
|
|
2482
|
+
filename: z21.string().min(1).describe(
|
|
2490
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."
|
|
2491
2484
|
),
|
|
2492
|
-
contentType:
|
|
2485
|
+
contentType: z21.string().min(1).describe(
|
|
2493
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."
|
|
2494
2487
|
),
|
|
2495
|
-
dataBase64:
|
|
2488
|
+
dataBase64: z21.string().min(1).max(HARD_MAX_BASE64_CHARS).describe(
|
|
2496
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."
|
|
2497
2490
|
),
|
|
2498
|
-
content:
|
|
2491
|
+
content: z21.string().optional().describe(
|
|
2499
2492
|
"Body text for the note that will hold the attachment. Defaults to '[attachment]' if omitted."
|
|
2500
2493
|
),
|
|
2501
|
-
partyId:
|
|
2502
|
-
opportunityId:
|
|
2503
|
-
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()
|
|
2504
2497
|
});
|
|
2505
2498
|
function isValidBase64(s) {
|
|
2506
2499
|
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(s)) return false;
|
|
@@ -2550,23 +2543,23 @@ async function uploadAttachment(input) {
|
|
|
2550
2543
|
}
|
|
2551
2544
|
|
|
2552
2545
|
// src/tools/saved-filters.ts
|
|
2553
|
-
import { z as
|
|
2554
|
-
var EntitySchema =
|
|
2546
|
+
import { z as z22 } from "zod";
|
|
2547
|
+
var EntitySchema = z22.enum(["parties", "opportunities", "kases"]).describe(
|
|
2555
2548
|
"Which entity type the filter operates over. Use 'kases' for projects (Capsule's legacy name)."
|
|
2556
2549
|
);
|
|
2557
|
-
var listSavedFiltersSchema =
|
|
2550
|
+
var listSavedFiltersSchema = z22.object({
|
|
2558
2551
|
entity: EntitySchema
|
|
2559
2552
|
});
|
|
2560
2553
|
async function listSavedFilters(input) {
|
|
2561
2554
|
const { data } = await capsuleGetCached(`/${input.entity}/filters`);
|
|
2562
2555
|
return data;
|
|
2563
2556
|
}
|
|
2564
|
-
var runSavedFilterSchema =
|
|
2557
|
+
var runSavedFilterSchema = z22.object({
|
|
2565
2558
|
entity: EntitySchema,
|
|
2566
|
-
id:
|
|
2567
|
-
embed:
|
|
2568
|
-
page:
|
|
2569
|
-
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)
|
|
2570
2563
|
});
|
|
2571
2564
|
async function runSavedFilter(input) {
|
|
2572
2565
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2584,7 +2577,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
2584
2577
|
const server2 = new McpServer(
|
|
2585
2578
|
{
|
|
2586
2579
|
name: "capsulemcp",
|
|
2587
|
-
version: "1.6.
|
|
2580
|
+
version: "1.6.2",
|
|
2588
2581
|
description: "Read and (optionally) modify Capsule CRM data \u2014 parties, opportunities, projects, tasks, timeline entries, pipelines, tags.",
|
|
2589
2582
|
websiteUrl: "https://github.com/soil-dev/capsulemcp",
|
|
2590
2583
|
icons: ICONS
|
|
@@ -3169,7 +3162,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
3169
3162
|
registerTool(
|
|
3170
3163
|
server2,
|
|
3171
3164
|
"list_teams",
|
|
3172
|
-
"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.",
|
|
3173
3166
|
listTeamsSchema,
|
|
3174
3167
|
listTeams
|
|
3175
3168
|
);
|