capsulemcp 1.6.1 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/http.js +518 -495
- package/dist/index.js +518 -495
- 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,91 +1214,82 @@ 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
|
-
|
|
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
|
+
organisationId: positiveId.nullable().optional().describe(
|
|
1261
|
+
"For PERSON parties: link to an organisation by id, or `null` to unlink (the person becomes an orphan / standalone record). Discover org IDs via search_parties / filter_parties with type=organisation. For ORGANISATION parties: silently ignored by Capsule's API \u2014 organisations don't have a parent organisation in the data model. Empirically verified in v1.6.3 wire-trace; no client-side type guard since the no-op is harmless."
|
|
1262
|
+
),
|
|
1263
|
+
fields: z6.array(CustomFieldWriteSchema).optional().describe(fieldsArrayDescriptor("get_party")),
|
|
1207
1264
|
...PartyWriteBaseSchema
|
|
1208
1265
|
});
|
|
1209
1266
|
async function updateParty(input) {
|
|
1210
|
-
const { id, ownerId, fields, ...rest } = input;
|
|
1267
|
+
const { id, ownerId, organisationId, fields, ...rest } = input;
|
|
1211
1268
|
const body = {};
|
|
1212
1269
|
for (const [k, v] of Object.entries(rest)) {
|
|
1213
1270
|
if (v !== void 0) body[k] = v;
|
|
1214
1271
|
}
|
|
1215
|
-
|
|
1272
|
+
setRef(body, "owner", ownerId);
|
|
1273
|
+
setNullableRef(body, "organisation", organisationId);
|
|
1216
1274
|
const mappedFields = mapFieldsForBody(fields);
|
|
1217
1275
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1218
1276
|
return capsulePut(`/parties/${id}`, { party: body });
|
|
1219
1277
|
}
|
|
1220
|
-
var batchUpdatePartySchema =
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
)
|
|
1278
|
+
var { schema: batchUpdatePartySchema, handler: batchUpdateParty } = defineBatch({
|
|
1279
|
+
toolName: "batch_update_party",
|
|
1280
|
+
itemSchema: updatePartySchema,
|
|
1281
|
+
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).",
|
|
1282
|
+
itemHandler: updateParty
|
|
1224
1283
|
});
|
|
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
|
-
)
|
|
1284
|
+
var { schema: deletePartySchema, handler: deleteParty } = defineDelete({
|
|
1285
|
+
toolName: "delete_party",
|
|
1286
|
+
pathPrefix: "/parties",
|
|
1287
|
+
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
1288
|
});
|
|
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'.")
|
|
1289
|
+
var addPartyEmailAddressSchema = z6.object({
|
|
1290
|
+
partyId: positiveId,
|
|
1291
|
+
address: z6.string().email(),
|
|
1292
|
+
type: z6.string().optional().describe("Free-form label, e.g. 'Work', 'Home'.")
|
|
1248
1293
|
});
|
|
1249
1294
|
async function addPartyEmailAddress(input) {
|
|
1250
1295
|
const { partyId, address, type } = input;
|
|
@@ -1254,9 +1299,9 @@ async function addPartyEmailAddress(input) {
|
|
|
1254
1299
|
party: { emailAddresses: [item] }
|
|
1255
1300
|
});
|
|
1256
1301
|
}
|
|
1257
|
-
var removePartyEmailAddressByIdSchema =
|
|
1258
|
-
partyId:
|
|
1259
|
-
emailAddressId:
|
|
1302
|
+
var removePartyEmailAddressByIdSchema = z6.object({
|
|
1303
|
+
partyId: positiveId,
|
|
1304
|
+
emailAddressId: positiveId.describe(
|
|
1260
1305
|
"Capsule's id for the email-address row. Read it from get_party (each entry in emailAddresses carries an id)."
|
|
1261
1306
|
)
|
|
1262
1307
|
});
|
|
@@ -1276,10 +1321,10 @@ async function removePartyEmailAddressById(input) {
|
|
|
1276
1321
|
() => ({ removed: true, alreadyRemoved: true, partyId, emailAddressId })
|
|
1277
1322
|
);
|
|
1278
1323
|
}
|
|
1279
|
-
var addPartyPhoneNumberSchema =
|
|
1280
|
-
partyId:
|
|
1281
|
-
number:
|
|
1282
|
-
type:
|
|
1324
|
+
var addPartyPhoneNumberSchema = z6.object({
|
|
1325
|
+
partyId: positiveId,
|
|
1326
|
+
number: z6.string().min(1),
|
|
1327
|
+
type: z6.string().optional().describe("Free-form label, e.g. 'Work', 'Mobile'.")
|
|
1283
1328
|
});
|
|
1284
1329
|
async function addPartyPhoneNumber(input) {
|
|
1285
1330
|
const { partyId, number, type } = input;
|
|
@@ -1289,9 +1334,9 @@ async function addPartyPhoneNumber(input) {
|
|
|
1289
1334
|
party: { phoneNumbers: [item] }
|
|
1290
1335
|
});
|
|
1291
1336
|
}
|
|
1292
|
-
var removePartyPhoneNumberByIdSchema =
|
|
1293
|
-
partyId:
|
|
1294
|
-
phoneNumberId:
|
|
1337
|
+
var removePartyPhoneNumberByIdSchema = z6.object({
|
|
1338
|
+
partyId: positiveId,
|
|
1339
|
+
phoneNumberId: positiveId.describe(
|
|
1295
1340
|
"Capsule's id for the phone-number row. Read it from get_party (each entry in phoneNumbers carries an id)."
|
|
1296
1341
|
)
|
|
1297
1342
|
});
|
|
@@ -1311,14 +1356,14 @@ async function removePartyPhoneNumberById(input) {
|
|
|
1311
1356
|
() => ({ removed: true, alreadyRemoved: true, partyId, phoneNumberId })
|
|
1312
1357
|
);
|
|
1313
1358
|
}
|
|
1314
|
-
var addPartyAddressSchema =
|
|
1315
|
-
partyId:
|
|
1316
|
-
street:
|
|
1317
|
-
city:
|
|
1318
|
-
state:
|
|
1319
|
-
country:
|
|
1320
|
-
zip:
|
|
1321
|
-
type:
|
|
1359
|
+
var addPartyAddressSchema = z6.object({
|
|
1360
|
+
partyId: positiveId,
|
|
1361
|
+
street: z6.string().optional(),
|
|
1362
|
+
city: z6.string().optional(),
|
|
1363
|
+
state: z6.string().optional(),
|
|
1364
|
+
country: z6.string().optional().describe(CountryDescription),
|
|
1365
|
+
zip: z6.string().optional(),
|
|
1366
|
+
type: z6.string().optional().describe("Free-form label, e.g. 'Office', 'Home'.")
|
|
1322
1367
|
});
|
|
1323
1368
|
async function addPartyAddress(input) {
|
|
1324
1369
|
const { partyId, ...rest } = input;
|
|
@@ -1330,9 +1375,9 @@ async function addPartyAddress(input) {
|
|
|
1330
1375
|
party: { addresses: [item] }
|
|
1331
1376
|
});
|
|
1332
1377
|
}
|
|
1333
|
-
var removePartyAddressByIdSchema =
|
|
1334
|
-
partyId:
|
|
1335
|
-
addressId:
|
|
1378
|
+
var removePartyAddressByIdSchema = z6.object({
|
|
1379
|
+
partyId: positiveId,
|
|
1380
|
+
addressId: positiveId.describe(
|
|
1336
1381
|
"Capsule's id for the address row. Read it from get_party (each entry in addresses carries an id)."
|
|
1337
1382
|
)
|
|
1338
1383
|
});
|
|
@@ -1352,9 +1397,9 @@ async function removePartyAddressById(input) {
|
|
|
1352
1397
|
() => ({ removed: true, alreadyRemoved: true, partyId, addressId })
|
|
1353
1398
|
);
|
|
1354
1399
|
}
|
|
1355
|
-
var addPartyWebsiteSchema =
|
|
1356
|
-
partyId:
|
|
1357
|
-
address:
|
|
1400
|
+
var addPartyWebsiteSchema = z6.object({
|
|
1401
|
+
partyId: positiveId,
|
|
1402
|
+
address: z6.string().min(1).describe(
|
|
1358
1403
|
"The website address. A URL when service='URL', or a handle (e.g. '@acmeco') for social services."
|
|
1359
1404
|
),
|
|
1360
1405
|
service: WebsiteServiceEnum.optional().describe("Defaults to 'URL' if omitted.")
|
|
@@ -1367,9 +1412,9 @@ async function addPartyWebsite(input) {
|
|
|
1367
1412
|
party: { websites: [item] }
|
|
1368
1413
|
});
|
|
1369
1414
|
}
|
|
1370
|
-
var removePartyWebsiteByIdSchema =
|
|
1371
|
-
partyId:
|
|
1372
|
-
websiteId:
|
|
1415
|
+
var removePartyWebsiteByIdSchema = z6.object({
|
|
1416
|
+
partyId: positiveId,
|
|
1417
|
+
websiteId: positiveId.describe(
|
|
1373
1418
|
"Capsule's id for the website row. Read it from get_party (each entry in websites carries an id)."
|
|
1374
1419
|
)
|
|
1375
1420
|
});
|
|
@@ -1391,20 +1436,32 @@ async function removePartyWebsiteById(input) {
|
|
|
1391
1436
|
}
|
|
1392
1437
|
|
|
1393
1438
|
// src/tools/opportunities.ts
|
|
1394
|
-
import { z as
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1439
|
+
import { z as z7 } from "zod";
|
|
1440
|
+
|
|
1441
|
+
// src/tools/preserve-refs.ts
|
|
1442
|
+
async function readEntityRefs(path, responseKey) {
|
|
1443
|
+
const { data } = await capsuleGet(path);
|
|
1444
|
+
const entity = data[responseKey];
|
|
1445
|
+
return {
|
|
1446
|
+
teamId: entity?.team?.id ?? void 0,
|
|
1447
|
+
stageId: entity?.stage?.id ?? void 0
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// src/tools/opportunities.ts
|
|
1452
|
+
var OpportunityValueSchema = z7.object({
|
|
1453
|
+
amount: z7.number().nonnegative(),
|
|
1454
|
+
currency: z7.string({
|
|
1398
1455
|
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
1456
|
}).length(3).describe(
|
|
1400
1457
|
"ISO 4217 currency code (3 letters), e.g. 'GBP', 'USD', 'EUR'. Required when amount is set."
|
|
1401
1458
|
)
|
|
1402
1459
|
});
|
|
1403
|
-
var searchOpportunitiesSchema =
|
|
1404
|
-
q:
|
|
1405
|
-
embed:
|
|
1406
|
-
page:
|
|
1407
|
-
perPage:
|
|
1460
|
+
var searchOpportunitiesSchema = z7.object({
|
|
1461
|
+
q: z7.string().optional().describe("Free-text search query"),
|
|
1462
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
1463
|
+
page: z7.number().int().positive().optional().default(1),
|
|
1464
|
+
perPage: z7.number().int().min(1).max(100).optional().default(25)
|
|
1408
1465
|
});
|
|
1409
1466
|
async function searchOpportunities(input) {
|
|
1410
1467
|
const path = input.q ? "/opportunities/search" : "/opportunities";
|
|
@@ -1416,9 +1473,9 @@ async function searchOpportunities(input) {
|
|
|
1416
1473
|
});
|
|
1417
1474
|
return { ...data, nextPage };
|
|
1418
1475
|
}
|
|
1419
|
-
var getOpportunitySchema =
|
|
1420
|
-
id:
|
|
1421
|
-
embed:
|
|
1476
|
+
var getOpportunitySchema = z7.object({
|
|
1477
|
+
id: positiveId,
|
|
1478
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1422
1479
|
});
|
|
1423
1480
|
async function getOpportunity(input) {
|
|
1424
1481
|
const { data } = await capsuleGet(`/opportunities/${input.id}`, {
|
|
@@ -1426,11 +1483,11 @@ async function getOpportunity(input) {
|
|
|
1426
1483
|
});
|
|
1427
1484
|
return data;
|
|
1428
1485
|
}
|
|
1429
|
-
var getOpportunitiesSchema =
|
|
1430
|
-
ids:
|
|
1486
|
+
var getOpportunitiesSchema = z7.object({
|
|
1487
|
+
ids: z7.array(positiveId).min(1).max(50).describe(
|
|
1431
1488
|
"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
1489
|
),
|
|
1433
|
-
embed:
|
|
1490
|
+
embed: z7.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1434
1491
|
});
|
|
1435
1492
|
async function getOpportunities(input) {
|
|
1436
1493
|
const { ids, embed } = input;
|
|
@@ -1451,20 +1508,20 @@ async function getOpportunities(input) {
|
|
|
1451
1508
|
);
|
|
1452
1509
|
return { opportunities: responses.flatMap((r) => r.data.opportunities) };
|
|
1453
1510
|
}
|
|
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."
|
|
1511
|
+
var createOpportunitySchema = z7.object({
|
|
1512
|
+
name: z7.string().min(1),
|
|
1513
|
+
partyId: positiveId.describe("ID of the party this opportunity belongs to"),
|
|
1514
|
+
milestoneId: positiveId.describe(
|
|
1515
|
+
"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
1516
|
),
|
|
1460
|
-
description:
|
|
1517
|
+
description: z7.string().optional(),
|
|
1461
1518
|
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."
|
|
1519
|
+
expectedCloseOn: z7.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD"),
|
|
1520
|
+
probability: z7.number().int().min(0).max(100).optional(),
|
|
1521
|
+
ownerId: positiveId.optional().describe(
|
|
1522
|
+
"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
1523
|
),
|
|
1467
|
-
teamId:
|
|
1524
|
+
teamId: positiveId.optional().describe(
|
|
1468
1525
|
"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
1526
|
)
|
|
1470
1527
|
});
|
|
@@ -1475,92 +1532,76 @@ async function createOpportunity(input) {
|
|
|
1475
1532
|
party: { id: partyId },
|
|
1476
1533
|
milestone: { id: milestoneId }
|
|
1477
1534
|
};
|
|
1478
|
-
|
|
1479
|
-
|
|
1535
|
+
setRef(body, "owner", ownerId);
|
|
1536
|
+
setRef(body, "team", teamId);
|
|
1480
1537
|
return capsulePost("/opportunities", { opportunity: body });
|
|
1481
1538
|
}
|
|
1482
|
-
var updateOpportunitySchema =
|
|
1483
|
-
id:
|
|
1484
|
-
name:
|
|
1485
|
-
|
|
1486
|
-
"
|
|
1539
|
+
var updateOpportunitySchema = z7.object({
|
|
1540
|
+
id: positiveId,
|
|
1541
|
+
name: z7.string().min(1).optional(),
|
|
1542
|
+
partyId: positiveId.optional().describe(
|
|
1543
|
+
"Reassign the opportunity to a different primary party. Capsule requires every opportunity to have a party \u2014 passing `null` is rejected with 422 'party is required' (use Capsule's web UI if you need to dissolve the link entirely). Discover ids via search_parties / filter_parties. No defensive read-modify-write needed: this connector verified empirically (v1.6.3 wire-trace) that `party` is a standalone PUT field on /opportunities and does not interact with the asymmetric owner/team semantic from NOTES-ON-CAPSULE-API.md \xA727."
|
|
1544
|
+
),
|
|
1545
|
+
milestoneId: positiveId.optional().describe(
|
|
1546
|
+
"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
1547
|
),
|
|
1488
|
-
description:
|
|
1548
|
+
description: z7.string().optional(),
|
|
1489
1549
|
value: OpportunityValueSchema.optional(),
|
|
1490
|
-
expectedCloseOn:
|
|
1491
|
-
probability:
|
|
1550
|
+
expectedCloseOn: z7.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
1551
|
+
probability: z7.number().int().min(0).max(100).optional().describe(
|
|
1492
1552
|
"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
1553
|
),
|
|
1494
|
-
lostReasonId:
|
|
1554
|
+
lostReasonId: positiveId.optional().describe(
|
|
1495
1555
|
"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
1556
|
),
|
|
1497
|
-
ownerId:
|
|
1557
|
+
ownerId: positiveId.optional().describe(
|
|
1498
1558
|
"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
1559
|
),
|
|
1500
|
-
teamId:
|
|
1560
|
+
teamId: positiveId.nullable().optional().describe(
|
|
1501
1561
|
"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
1562
|
),
|
|
1503
|
-
fields:
|
|
1563
|
+
fields: z7.array(CustomFieldWriteSchema).optional().describe(fieldsArrayDescriptor("get_opportunity"))
|
|
1504
1564
|
});
|
|
1505
1565
|
async function updateOpportunity(input) {
|
|
1506
|
-
const { id, milestoneId, ownerId, teamId, lostReasonId, fields, ...rest } = input;
|
|
1566
|
+
const { id, partyId, milestoneId, ownerId, teamId, lostReasonId, fields, ...rest } = input;
|
|
1507
1567
|
const body = {};
|
|
1508
1568
|
for (const [k, v] of Object.entries(rest)) {
|
|
1509
1569
|
if (v !== void 0) body[k] = v;
|
|
1510
1570
|
}
|
|
1511
|
-
|
|
1571
|
+
setRef(body, "party", partyId);
|
|
1572
|
+
setRef(body, "milestone", milestoneId);
|
|
1512
1573
|
let resolvedTeamId = teamId;
|
|
1513
1574
|
if (ownerId !== void 0 && teamId === void 0) {
|
|
1514
|
-
|
|
1515
|
-
resolvedTeamId = data.opportunity?.team?.id ?? void 0;
|
|
1575
|
+
({ teamId: resolvedTeamId } = await readEntityRefs(`/opportunities/${id}`, "opportunity"));
|
|
1516
1576
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
if (lostReasonId) body["lostReason"] = { id: lostReasonId };
|
|
1577
|
+
setRef(body, "owner", ownerId);
|
|
1578
|
+
setNullableRef(body, "team", resolvedTeamId);
|
|
1579
|
+
setRef(body, "lostReason", lostReasonId);
|
|
1521
1580
|
const mappedFields = mapFieldsForBody(fields);
|
|
1522
1581
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1523
1582
|
return capsulePut(`/opportunities/${id}`, {
|
|
1524
1583
|
opportunity: body
|
|
1525
1584
|
});
|
|
1526
1585
|
}
|
|
1527
|
-
var batchUpdateOpportunitySchema =
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1586
|
+
var { schema: batchUpdateOpportunitySchema, handler: batchUpdateOpportunity } = defineBatch({
|
|
1587
|
+
toolName: "batch_update_opportunity",
|
|
1588
|
+
itemSchema: updateOpportunitySchema,
|
|
1589
|
+
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.",
|
|
1590
|
+
itemHandler: updateOpportunity
|
|
1531
1591
|
});
|
|
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
|
-
)
|
|
1592
|
+
var { schema: deleteOpportunitySchema, handler: deleteOpportunity } = defineDelete({
|
|
1593
|
+
toolName: "delete_opportunity",
|
|
1594
|
+
pathPrefix: "/opportunities",
|
|
1595
|
+
confirmHint: "Must be set to true. Permanently deletes the opportunity. Irreversible."
|
|
1545
1596
|
});
|
|
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
1597
|
|
|
1557
1598
|
// src/tools/projects.ts
|
|
1558
|
-
import { z as
|
|
1559
|
-
var listProjectsSchema =
|
|
1560
|
-
status:
|
|
1561
|
-
embed:
|
|
1562
|
-
page:
|
|
1563
|
-
perPage:
|
|
1599
|
+
import { z as z8 } from "zod";
|
|
1600
|
+
var listProjectsSchema = z8.object({
|
|
1601
|
+
status: z8.enum(["OPEN", "CLOSED"]).optional(),
|
|
1602
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
1603
|
+
page: z8.number().int().positive().optional().default(1),
|
|
1604
|
+
perPage: z8.number().int().min(1).max(100).optional().default(25)
|
|
1564
1605
|
});
|
|
1565
1606
|
async function listProjects(input) {
|
|
1566
1607
|
const { data, nextPage } = await capsuleGet("/kases", {
|
|
@@ -1571,9 +1612,9 @@ async function listProjects(input) {
|
|
|
1571
1612
|
});
|
|
1572
1613
|
return { ...data, nextPage };
|
|
1573
1614
|
}
|
|
1574
|
-
var getProjectSchema =
|
|
1575
|
-
id:
|
|
1576
|
-
embed:
|
|
1615
|
+
var getProjectSchema = z8.object({
|
|
1616
|
+
id: positiveId,
|
|
1617
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1577
1618
|
});
|
|
1578
1619
|
async function getProject(input) {
|
|
1579
1620
|
const { data } = await capsuleGet(`/kases/${input.id}`, {
|
|
@@ -1581,11 +1622,11 @@ async function getProject(input) {
|
|
|
1581
1622
|
});
|
|
1582
1623
|
return data;
|
|
1583
1624
|
}
|
|
1584
|
-
var getProjectsSchema =
|
|
1585
|
-
ids:
|
|
1625
|
+
var getProjectsSchema = z8.object({
|
|
1626
|
+
ids: z8.array(positiveId).min(1).max(50).describe(
|
|
1586
1627
|
"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
1628
|
),
|
|
1588
|
-
embed:
|
|
1629
|
+
embed: z8.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
1589
1630
|
});
|
|
1590
1631
|
async function getProjects(input) {
|
|
1591
1632
|
const { ids, embed } = input;
|
|
@@ -1603,21 +1644,21 @@ async function getProjects(input) {
|
|
|
1603
1644
|
);
|
|
1604
1645
|
return { kases: responses.flatMap((r) => r.data.kases) };
|
|
1605
1646
|
}
|
|
1606
|
-
var createProjectSchema =
|
|
1607
|
-
name:
|
|
1608
|
-
partyId:
|
|
1609
|
-
description:
|
|
1610
|
-
status:
|
|
1611
|
-
ownerId:
|
|
1647
|
+
var createProjectSchema = z8.object({
|
|
1648
|
+
name: z8.string().min(1),
|
|
1649
|
+
partyId: positiveId.describe("ID of the party linked to this project"),
|
|
1650
|
+
description: z8.string().optional(),
|
|
1651
|
+
status: z8.enum(["OPEN", "CLOSED"]).optional().describe("Defaults to OPEN when omitted."),
|
|
1652
|
+
ownerId: positiveId.optional().describe(
|
|
1612
1653
|
"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
1654
|
),
|
|
1614
|
-
teamId:
|
|
1655
|
+
teamId: positiveId.optional().describe(
|
|
1615
1656
|
"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
1657
|
),
|
|
1617
|
-
stageId:
|
|
1658
|
+
stageId: positiveId.optional().describe(
|
|
1618
1659
|
"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
1660
|
),
|
|
1620
|
-
expectedCloseOn:
|
|
1661
|
+
expectedCloseOn: z8.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD")
|
|
1621
1662
|
});
|
|
1622
1663
|
async function createProject(input) {
|
|
1623
1664
|
const { partyId, ownerId, teamId, status, stageId, ...rest } = input;
|
|
@@ -1626,88 +1667,75 @@ async function createProject(input) {
|
|
|
1626
1667
|
status: status ?? "OPEN",
|
|
1627
1668
|
party: { id: partyId }
|
|
1628
1669
|
};
|
|
1629
|
-
|
|
1630
|
-
|
|
1670
|
+
setRef(body, "owner", ownerId);
|
|
1671
|
+
setRef(body, "team", teamId);
|
|
1631
1672
|
if (stageId) body["stage"] = stageId;
|
|
1632
1673
|
return capsulePost("/kases", { kase: body });
|
|
1633
1674
|
}
|
|
1634
|
-
var updateProjectSchema =
|
|
1635
|
-
id:
|
|
1636
|
-
name:
|
|
1637
|
-
description:
|
|
1638
|
-
status:
|
|
1639
|
-
|
|
1675
|
+
var updateProjectSchema = z8.object({
|
|
1676
|
+
id: positiveId,
|
|
1677
|
+
name: z8.string().min(1).optional(),
|
|
1678
|
+
description: z8.string().optional(),
|
|
1679
|
+
status: z8.enum(["OPEN", "CLOSED"]).optional(),
|
|
1680
|
+
partyId: positiveId.optional().describe(
|
|
1681
|
+
"Reassign the project to a different primary party. Capsule requires every project to have a party \u2014 passing `null` is rejected with 422 'party is required' (verified empirically in v1.6.3 wire-trace). Discover ids via search_parties / filter_parties."
|
|
1682
|
+
),
|
|
1683
|
+
ownerId: positiveId.nullable().optional().describe(
|
|
1640
1684
|
"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
1685
|
),
|
|
1642
|
-
teamId:
|
|
1686
|
+
teamId: positiveId.nullable().optional().describe(
|
|
1643
1687
|
"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
1688
|
),
|
|
1645
|
-
stageId:
|
|
1689
|
+
stageId: positiveId.optional().describe(
|
|
1646
1690
|
"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
1691
|
),
|
|
1648
|
-
expectedCloseOn:
|
|
1649
|
-
fields:
|
|
1692
|
+
expectedCloseOn: z8.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD"),
|
|
1693
|
+
fields: z8.array(CustomFieldWriteSchema).optional().describe(
|
|
1650
1694
|
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
1695
|
)
|
|
1652
1696
|
});
|
|
1653
1697
|
async function updateProject(input) {
|
|
1654
|
-
const { id, ownerId, teamId, stageId, fields, ...rest } = input;
|
|
1698
|
+
const { id, partyId, ownerId, teamId, stageId, fields, ...rest } = input;
|
|
1655
1699
|
const body = {};
|
|
1656
1700
|
for (const [k, v] of Object.entries(rest)) {
|
|
1657
1701
|
if (v !== void 0) body[k] = v;
|
|
1658
1702
|
}
|
|
1703
|
+
setRef(body, "party", partyId);
|
|
1659
1704
|
let resolvedTeamId = teamId;
|
|
1660
1705
|
let resolvedStageId = stageId;
|
|
1661
1706
|
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
|
-
}
|
|
1707
|
+
const current = await readEntityRefs(`/kases/${id}`, "kase");
|
|
1708
|
+
if (teamId === void 0) resolvedTeamId = current.teamId;
|
|
1709
|
+
if (stageId === void 0) resolvedStageId = current.stageId;
|
|
1669
1710
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
if (resolvedTeamId === null) body["team"] = null;
|
|
1673
|
-
else if (resolvedTeamId !== void 0) body["team"] = { id: resolvedTeamId };
|
|
1711
|
+
setNullableRef(body, "owner", ownerId);
|
|
1712
|
+
setNullableRef(body, "team", resolvedTeamId);
|
|
1674
1713
|
if (resolvedStageId) body["stage"] = resolvedStageId;
|
|
1675
1714
|
const mappedFields = mapFieldsForBody(fields);
|
|
1676
1715
|
if (mappedFields !== void 0) body["fields"] = mappedFields;
|
|
1677
1716
|
return capsulePut(`/kases/${id}`, { kase: body });
|
|
1678
1717
|
}
|
|
1679
|
-
var deleteProjectSchema =
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
)
|
|
1718
|
+
var { schema: deleteProjectSchema, handler: deleteProject } = defineDelete({
|
|
1719
|
+
toolName: "delete_project",
|
|
1720
|
+
pathPrefix: "/kases",
|
|
1721
|
+
confirmHint: "Must be set to true. Permanently deletes the project (case). Consider update_project status='CLOSED' instead. Irreversible."
|
|
1684
1722
|
});
|
|
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
1723
|
|
|
1696
1724
|
// src/tools/tasks.ts
|
|
1697
|
-
import { z as
|
|
1698
|
-
var listTasksSchema =
|
|
1725
|
+
import { z as z9 } from "zod";
|
|
1726
|
+
var listTasksSchema = z9.object({
|
|
1699
1727
|
// Note: Capsule has a third internal status `PENDING` (a task that's
|
|
1700
1728
|
// part of an active track but not yet "open"), but it can only be
|
|
1701
1729
|
// reached via track machinery — it is NOT directly settable by
|
|
1702
1730
|
// /tasks PUT, and a list filter for it returns the same as OPEN
|
|
1703
1731
|
// anyway. We expose only the two values that are actually filterable
|
|
1704
1732
|
// by the v2 API.
|
|
1705
|
-
status:
|
|
1733
|
+
status: z9.enum(["OPEN", "COMPLETED"]).optional().describe(
|
|
1706
1734
|
"Defaults to OPEN when omitted. Pass COMPLETED to filter to completed tasks, or 'OPEN' explicitly."
|
|
1707
1735
|
),
|
|
1708
|
-
ownerId:
|
|
1709
|
-
page:
|
|
1710
|
-
perPage:
|
|
1736
|
+
ownerId: positiveId.optional().describe("Filter to tasks owned by this user ID"),
|
|
1737
|
+
page: z9.number().int().positive().optional().default(1),
|
|
1738
|
+
perPage: z9.number().int().min(1).max(100).optional().default(25)
|
|
1711
1739
|
});
|
|
1712
1740
|
async function listTasks(input) {
|
|
1713
1741
|
const { data, nextPage } = await capsuleGet("/tasks", {
|
|
@@ -1721,15 +1749,15 @@ async function listTasks(input) {
|
|
|
1721
1749
|
});
|
|
1722
1750
|
return { ...data, nextPage };
|
|
1723
1751
|
}
|
|
1724
|
-
var getTaskSchema =
|
|
1725
|
-
id:
|
|
1752
|
+
var getTaskSchema = z9.object({
|
|
1753
|
+
id: positiveId.describe("Task ID")
|
|
1726
1754
|
});
|
|
1727
1755
|
async function getTask(input) {
|
|
1728
1756
|
const { data } = await capsuleGet(`/tasks/${input.id}`);
|
|
1729
1757
|
return data;
|
|
1730
1758
|
}
|
|
1731
|
-
var getTasksSchema =
|
|
1732
|
-
ids:
|
|
1759
|
+
var getTasksSchema = z9.object({
|
|
1760
|
+
ids: z9.array(positiveId).min(1).max(50).describe(
|
|
1733
1761
|
"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
1762
|
)
|
|
1735
1763
|
});
|
|
@@ -1745,17 +1773,17 @@ async function getTasks(input) {
|
|
|
1745
1773
|
);
|
|
1746
1774
|
return { tasks: responses.flatMap((r) => r.data.tasks) };
|
|
1747
1775
|
}
|
|
1748
|
-
var createTaskSchema =
|
|
1749
|
-
description:
|
|
1750
|
-
dueOn:
|
|
1751
|
-
dueTime:
|
|
1752
|
-
detail:
|
|
1753
|
-
ownerId:
|
|
1776
|
+
var createTaskSchema = z9.object({
|
|
1777
|
+
description: z9.string().min(1),
|
|
1778
|
+
dueOn: z9.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("YYYY-MM-DD"),
|
|
1779
|
+
dueTime: z9.string().regex(/^\d{2}:\d{2}$/).optional().describe("HH:MM in user's timezone"),
|
|
1780
|
+
detail: z9.string().optional(),
|
|
1781
|
+
ownerId: positiveId.optional().describe(
|
|
1754
1782
|
"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
1783
|
),
|
|
1756
|
-
partyId:
|
|
1757
|
-
opportunityId:
|
|
1758
|
-
projectId:
|
|
1784
|
+
partyId: positiveId.optional().describe("Link task to a party (mutually exclusive with opportunityId/projectId)"),
|
|
1785
|
+
opportunityId: positiveId.optional().describe("Link task to an opportunity (mutually exclusive with partyId/projectId)"),
|
|
1786
|
+
projectId: positiveId.optional().describe("Link task to a project (mutually exclusive with partyId/opportunityId)")
|
|
1759
1787
|
});
|
|
1760
1788
|
async function createTask(input) {
|
|
1761
1789
|
const linked = [input.partyId, input.opportunityId, input.projectId].filter(Boolean);
|
|
@@ -1764,79 +1792,86 @@ async function createTask(input) {
|
|
|
1764
1792
|
}
|
|
1765
1793
|
const { ownerId, partyId, opportunityId, projectId, ...rest } = input;
|
|
1766
1794
|
const body = { ...rest };
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1795
|
+
setRef(body, "owner", ownerId);
|
|
1796
|
+
setRef(body, "party", partyId);
|
|
1797
|
+
setRef(body, "opportunity", opportunityId);
|
|
1798
|
+
setRef(body, "kase", projectId);
|
|
1771
1799
|
return capsulePost("/tasks", { task: body });
|
|
1772
1800
|
}
|
|
1773
|
-
var updateTaskSchema =
|
|
1774
|
-
id:
|
|
1775
|
-
description:
|
|
1776
|
-
dueOn:
|
|
1777
|
-
dueTime:
|
|
1778
|
-
detail:
|
|
1801
|
+
var updateTaskSchema = z9.object({
|
|
1802
|
+
id: positiveId,
|
|
1803
|
+
description: z9.string().min(1).optional(),
|
|
1804
|
+
dueOn: z9.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("YYYY-MM-DD"),
|
|
1805
|
+
dueTime: z9.string().regex(/^\d{2}:\d{2}$/).optional().describe("HH:MM in user's timezone"),
|
|
1806
|
+
detail: z9.string().optional(),
|
|
1779
1807
|
// Capsule rejects direct sets of `PENDING` (which is a track-machinery
|
|
1780
1808
|
// internal state) with 422 "cannot set task status to PENDING".
|
|
1781
1809
|
// Only OPEN and COMPLETED are settable here.
|
|
1782
|
-
status:
|
|
1810
|
+
status: z9.enum(["OPEN", "COMPLETED"]).optional().describe(
|
|
1783
1811
|
"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
1812
|
),
|
|
1785
|
-
ownerId:
|
|
1813
|
+
ownerId: positiveId.optional().describe(
|
|
1786
1814
|
"Reassign owner to user ID. Once set, this connector cannot clear an owner back to null \u2014 use Capsule's web UI for that."
|
|
1815
|
+
),
|
|
1816
|
+
partyId: positiveId.nullable().optional().describe(
|
|
1817
|
+
"Re-link the task to a party by id, or `null` to orphan it. Mutually exclusive with `opportunityId` / `projectId` \u2014 Capsule enforces 'task can be related to at most one entity' server-side (422 if two parent-refs are set at once, verified in v1.6.3 wire-trace). To swap parent type atomically, pass the old one as `null` and the new one as an id in the same call."
|
|
1818
|
+
),
|
|
1819
|
+
opportunityId: positiveId.nullable().optional().describe(
|
|
1820
|
+
"Re-link the task to an opportunity by id, or `null` to orphan it. Mutually exclusive with `partyId` / `projectId` \u2014 see `partyId` for the XOR semantic."
|
|
1821
|
+
),
|
|
1822
|
+
projectId: positiveId.nullable().optional().describe(
|
|
1823
|
+
"Re-link the task to a project (kase) by id, or `null` to orphan it. Mutually exclusive with `partyId` / `opportunityId` \u2014 see `partyId` for the XOR semantic."
|
|
1787
1824
|
)
|
|
1788
1825
|
});
|
|
1789
1826
|
async function updateTask(input) {
|
|
1790
|
-
const { id, ownerId, ...rest } = input;
|
|
1827
|
+
const { id, ownerId, partyId, opportunityId, projectId, ...rest } = input;
|
|
1828
|
+
const setCount = [partyId, opportunityId, projectId].filter((v) => typeof v === "number").length;
|
|
1829
|
+
if (setCount > 1) {
|
|
1830
|
+
throw new Error(
|
|
1831
|
+
"update_task: provide at most one of partyId, opportunityId, or projectId (Capsule rejects multi-parent tasks with 422 'task can be related to at most one entity')"
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1791
1834
|
const body = {};
|
|
1792
1835
|
for (const [k, v] of Object.entries(rest)) {
|
|
1793
1836
|
if (v !== void 0) body[k] = v;
|
|
1794
1837
|
}
|
|
1795
|
-
|
|
1838
|
+
setRef(body, "owner", ownerId);
|
|
1839
|
+
setNullableRef(body, "party", partyId);
|
|
1840
|
+
setNullableRef(body, "opportunity", opportunityId);
|
|
1841
|
+
setNullableRef(body, "kase", projectId);
|
|
1796
1842
|
return capsulePut(`/tasks/${id}`, { task: body });
|
|
1797
1843
|
}
|
|
1798
|
-
var completeTaskSchema =
|
|
1799
|
-
id:
|
|
1844
|
+
var completeTaskSchema = z9.object({
|
|
1845
|
+
id: positiveId
|
|
1800
1846
|
});
|
|
1801
1847
|
async function completeTask(input) {
|
|
1802
1848
|
return capsulePut(`/tasks/${input.id}`, {
|
|
1803
1849
|
task: { status: "COMPLETED" }
|
|
1804
1850
|
});
|
|
1805
1851
|
}
|
|
1806
|
-
var batchCompleteTaskSchema =
|
|
1807
|
-
ids:
|
|
1852
|
+
var batchCompleteTaskSchema = z9.object({
|
|
1853
|
+
ids: z9.array(positiveId).min(1).max(50).describe(
|
|
1808
1854
|
"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
1855
|
)
|
|
1810
1856
|
});
|
|
1811
1857
|
async function batchCompleteTask(input, opts = {}) {
|
|
1812
1858
|
return batchExecute("batch_complete_task", input.ids, (id) => completeTask({ id }), opts);
|
|
1813
1859
|
}
|
|
1814
|
-
var deleteTaskSchema =
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
)
|
|
1860
|
+
var { schema: deleteTaskSchema, handler: deleteTask } = defineDelete({
|
|
1861
|
+
toolName: "delete_task",
|
|
1862
|
+
pathPrefix: "/tasks",
|
|
1863
|
+
confirmHint: "Must be set to true. Permanently deletes the task. To mark done without losing history use complete_task. Irreversible."
|
|
1819
1864
|
});
|
|
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
1865
|
|
|
1831
1866
|
// src/tools/entries.ts
|
|
1832
|
-
import { z as
|
|
1867
|
+
import { z as z10 } from "zod";
|
|
1833
1868
|
var listEntriesPagination = {
|
|
1834
|
-
page:
|
|
1835
|
-
perPage:
|
|
1836
|
-
embed:
|
|
1869
|
+
page: z10.number().int().positive().optional().default(1),
|
|
1870
|
+
perPage: z10.number().int().min(1).max(100).optional().default(25),
|
|
1871
|
+
embed: z10.string().optional().describe(EMBED_ATTACHMENTS_PARTICIPANTS_DESCRIPTION)
|
|
1837
1872
|
};
|
|
1838
|
-
var listPartyEntriesSchema =
|
|
1839
|
-
partyId:
|
|
1873
|
+
var listPartyEntriesSchema = z10.object({
|
|
1874
|
+
partyId: positiveId,
|
|
1840
1875
|
...listEntriesPagination
|
|
1841
1876
|
});
|
|
1842
1877
|
async function listPartyEntries(input) {
|
|
@@ -1846,8 +1881,8 @@ async function listPartyEntries(input) {
|
|
|
1846
1881
|
);
|
|
1847
1882
|
return { ...data, nextPage };
|
|
1848
1883
|
}
|
|
1849
|
-
var listOpportunityEntriesSchema =
|
|
1850
|
-
opportunityId:
|
|
1884
|
+
var listOpportunityEntriesSchema = z10.object({
|
|
1885
|
+
opportunityId: positiveId,
|
|
1851
1886
|
...listEntriesPagination
|
|
1852
1887
|
});
|
|
1853
1888
|
async function listOpportunityEntries(input) {
|
|
@@ -1857,8 +1892,8 @@ async function listOpportunityEntries(input) {
|
|
|
1857
1892
|
);
|
|
1858
1893
|
return { ...data, nextPage };
|
|
1859
1894
|
}
|
|
1860
|
-
var listProjectEntriesSchema =
|
|
1861
|
-
projectId:
|
|
1895
|
+
var listProjectEntriesSchema = z10.object({
|
|
1896
|
+
projectId: positiveId,
|
|
1862
1897
|
...listEntriesPagination
|
|
1863
1898
|
});
|
|
1864
1899
|
async function listProjectEntries(input) {
|
|
@@ -1868,9 +1903,9 @@ async function listProjectEntries(input) {
|
|
|
1868
1903
|
);
|
|
1869
1904
|
return { ...data, nextPage };
|
|
1870
1905
|
}
|
|
1871
|
-
var getEntrySchema =
|
|
1872
|
-
id:
|
|
1873
|
-
embed:
|
|
1906
|
+
var getEntrySchema = z10.object({
|
|
1907
|
+
id: positiveId,
|
|
1908
|
+
embed: z10.string().optional().describe(EMBED_ATTACHMENTS_PARTICIPANTS_DESCRIPTION)
|
|
1874
1909
|
});
|
|
1875
1910
|
async function getEntry(input) {
|
|
1876
1911
|
const { data } = await capsuleGet(`/entries/${input.id}`, {
|
|
@@ -1878,7 +1913,7 @@ async function getEntry(input) {
|
|
|
1878
1913
|
});
|
|
1879
1914
|
return data;
|
|
1880
1915
|
}
|
|
1881
|
-
var listEntriesSchema =
|
|
1916
|
+
var listEntriesSchema = z10.object({
|
|
1882
1917
|
...listEntriesPagination
|
|
1883
1918
|
});
|
|
1884
1919
|
async function listEntries(input) {
|
|
@@ -1889,14 +1924,14 @@ async function listEntries(input) {
|
|
|
1889
1924
|
});
|
|
1890
1925
|
return { ...data, nextPage };
|
|
1891
1926
|
}
|
|
1892
|
-
var addNoteSchema =
|
|
1893
|
-
content:
|
|
1927
|
+
var addNoteSchema = z10.object({
|
|
1928
|
+
content: z10.string().min(1).describe(
|
|
1894
1929
|
"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
1930
|
),
|
|
1896
|
-
partyId:
|
|
1897
|
-
opportunityId:
|
|
1898
|
-
projectId:
|
|
1899
|
-
entryAt:
|
|
1931
|
+
partyId: positiveId.optional().describe("Link note to a party (mutually exclusive with opportunityId/projectId)"),
|
|
1932
|
+
opportunityId: positiveId.optional().describe("Link note to an opportunity (mutually exclusive with partyId/projectId)"),
|
|
1933
|
+
projectId: positiveId.optional().describe("Link note to a project (mutually exclusive with partyId/opportunityId)"),
|
|
1934
|
+
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
1935
|
"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
1936
|
)
|
|
1902
1937
|
});
|
|
@@ -1907,18 +1942,18 @@ async function addNote(input) {
|
|
|
1907
1942
|
throw new Error("Provide exactly one of partyId, opportunityId, or projectId");
|
|
1908
1943
|
}
|
|
1909
1944
|
const body = { type: "note", content };
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1945
|
+
setRef(body, "party", partyId);
|
|
1946
|
+
setRef(body, "opportunity", opportunityId);
|
|
1947
|
+
setRef(body, "kase", projectId);
|
|
1913
1948
|
if (entryAt !== void 0) body["entryAt"] = entryAt;
|
|
1914
1949
|
return capsulePost("/entries", { entry: body });
|
|
1915
1950
|
}
|
|
1916
|
-
var updateEntrySchema =
|
|
1917
|
-
id:
|
|
1918
|
-
content:
|
|
1951
|
+
var updateEntrySchema = z10.object({
|
|
1952
|
+
id: positiveId.describe("Entry ID to update"),
|
|
1953
|
+
content: z10.string().min(1).optional().describe(
|
|
1919
1954
|
"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
1955
|
),
|
|
1921
|
-
subject:
|
|
1956
|
+
subject: z10.string().optional().describe(
|
|
1922
1957
|
"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
1958
|
)
|
|
1924
1959
|
});
|
|
@@ -1932,30 +1967,20 @@ async function updateEntry(input) {
|
|
|
1932
1967
|
}
|
|
1933
1968
|
return capsulePut(`/entries/${id}`, { entry: body });
|
|
1934
1969
|
}
|
|
1935
|
-
var deleteEntrySchema =
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
)
|
|
1970
|
+
var { schema: deleteEntrySchema, handler: deleteEntry } = defineDelete({
|
|
1971
|
+
toolName: "delete_entry",
|
|
1972
|
+
pathPrefix: "/entries",
|
|
1973
|
+
confirmHint: "Must be set to true. Permanently deletes the entry \u2014 use this to remove a note from a party/opportunity/project. Irreversible.",
|
|
1974
|
+
idDescription: "Entry (note/email/task-record) ID"
|
|
1940
1975
|
});
|
|
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
1976
|
|
|
1952
1977
|
// src/tools/pipelines.ts
|
|
1953
|
-
import { z as
|
|
1978
|
+
import { z as z11 } from "zod";
|
|
1954
1979
|
var paginationFields = {
|
|
1955
|
-
page:
|
|
1956
|
-
perPage:
|
|
1980
|
+
page: z11.number().int().positive().optional(),
|
|
1981
|
+
perPage: z11.number().int().min(1).max(100).optional()
|
|
1957
1982
|
};
|
|
1958
|
-
var listPipelinesSchema =
|
|
1983
|
+
var listPipelinesSchema = z11.object({ ...paginationFields });
|
|
1959
1984
|
async function listPipelines(input) {
|
|
1960
1985
|
const { data, nextPage } = await capsuleGetCached("/pipelines", {
|
|
1961
1986
|
page: input.page ?? 1,
|
|
@@ -1963,8 +1988,8 @@ async function listPipelines(input) {
|
|
|
1963
1988
|
});
|
|
1964
1989
|
return { ...data, nextPage };
|
|
1965
1990
|
}
|
|
1966
|
-
var listMilestonesSchema =
|
|
1967
|
-
pipelineId:
|
|
1991
|
+
var listMilestonesSchema = z11.object({
|
|
1992
|
+
pipelineId: positiveId,
|
|
1968
1993
|
...paginationFields
|
|
1969
1994
|
});
|
|
1970
1995
|
async function listMilestones(input) {
|
|
@@ -1976,12 +2001,12 @@ async function listMilestones(input) {
|
|
|
1976
2001
|
}
|
|
1977
2002
|
|
|
1978
2003
|
// src/tools/boards.ts
|
|
1979
|
-
import { z as
|
|
2004
|
+
import { z as z12 } from "zod";
|
|
1980
2005
|
var paginationFields2 = {
|
|
1981
|
-
page:
|
|
1982
|
-
perPage:
|
|
2006
|
+
page: z12.number().int().positive().optional(),
|
|
2007
|
+
perPage: z12.number().int().min(1).max(100).optional()
|
|
1983
2008
|
};
|
|
1984
|
-
var listBoardsSchema =
|
|
2009
|
+
var listBoardsSchema = z12.object({ ...paginationFields2 });
|
|
1985
2010
|
async function listBoards(input) {
|
|
1986
2011
|
const { data, nextPage } = await capsuleGetCached("/boards", {
|
|
1987
2012
|
page: input.page ?? 1,
|
|
@@ -1989,8 +2014,8 @@ async function listBoards(input) {
|
|
|
1989
2014
|
});
|
|
1990
2015
|
return { ...data, nextPage };
|
|
1991
2016
|
}
|
|
1992
|
-
var listStagesSchema =
|
|
1993
|
-
boardId:
|
|
2017
|
+
var listStagesSchema = z12.object({
|
|
2018
|
+
boardId: positiveId.optional().describe(
|
|
1994
2019
|
"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
2020
|
),
|
|
1996
2021
|
...paginationFields2
|
|
@@ -2005,7 +2030,7 @@ async function listStages(input) {
|
|
|
2005
2030
|
}
|
|
2006
2031
|
|
|
2007
2032
|
// src/tools/tags.ts
|
|
2008
|
-
import { z as
|
|
2033
|
+
import { z as z13 } from "zod";
|
|
2009
2034
|
var TAG_LIST_PATH = {
|
|
2010
2035
|
parties: "/parties/tags",
|
|
2011
2036
|
opportunities: "/opportunities/tags",
|
|
@@ -2016,11 +2041,11 @@ var ENTITY_TO_WRAPPER = {
|
|
|
2016
2041
|
opportunities: "opportunity",
|
|
2017
2042
|
kases: "kase"
|
|
2018
2043
|
};
|
|
2019
|
-
var TagEntity =
|
|
2020
|
-
var listTagsSchema =
|
|
2021
|
-
entity:
|
|
2022
|
-
page:
|
|
2023
|
-
perPage:
|
|
2044
|
+
var TagEntity = z13.enum(["parties", "opportunities", "kases"]).describe("Which entity type. Use 'kases' for projects (Capsule's legacy path name).");
|
|
2045
|
+
var listTagsSchema = z13.object({
|
|
2046
|
+
entity: z13.enum(["parties", "opportunities", "kases"]).describe("The resource type to list tags for"),
|
|
2047
|
+
page: z13.number().int().positive().optional(),
|
|
2048
|
+
perPage: z13.number().int().min(1).max(100).optional()
|
|
2024
2049
|
});
|
|
2025
2050
|
async function listTags(input) {
|
|
2026
2051
|
const path = TAG_LIST_PATH[input.entity];
|
|
@@ -2030,10 +2055,10 @@ async function listTags(input) {
|
|
|
2030
2055
|
});
|
|
2031
2056
|
return { ...data, nextPage };
|
|
2032
2057
|
}
|
|
2033
|
-
var addTagSchema =
|
|
2058
|
+
var addTagSchema = z13.object({
|
|
2034
2059
|
entity: TagEntity,
|
|
2035
|
-
entityId:
|
|
2036
|
-
tagName:
|
|
2060
|
+
entityId: positiveId.describe("The party/opportunity/kase id."),
|
|
2061
|
+
tagName: z13.string().min(1).describe(
|
|
2037
2062
|
"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
2063
|
)
|
|
2039
2064
|
});
|
|
@@ -2046,10 +2071,10 @@ async function addTag(input) {
|
|
|
2046
2071
|
invalidateByPrefix(TAG_LIST_PATH[entity], "add_tag");
|
|
2047
2072
|
return result;
|
|
2048
2073
|
}
|
|
2049
|
-
var removeTagByIdSchema =
|
|
2074
|
+
var removeTagByIdSchema = z13.object({
|
|
2050
2075
|
entity: TagEntity,
|
|
2051
|
-
entityId:
|
|
2052
|
-
tagId:
|
|
2076
|
+
entityId: positiveId.describe("The party/opportunity/kase id."),
|
|
2077
|
+
tagId: positiveId.describe(
|
|
2053
2078
|
"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
2079
|
)
|
|
2055
2080
|
});
|
|
@@ -2077,28 +2102,24 @@ async function removeTagById(input) {
|
|
|
2077
2102
|
invalidateByPrefix(TAG_LIST_PATH[entity], "remove_tag_by_id");
|
|
2078
2103
|
return result;
|
|
2079
2104
|
}
|
|
2080
|
-
var batchAddTagSchema =
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2105
|
+
var { schema: batchAddTagSchema, handler: batchAddTag } = defineBatch({
|
|
2106
|
+
toolName: "batch_add_tag",
|
|
2107
|
+
itemSchema: addTagSchema,
|
|
2108
|
+
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.",
|
|
2109
|
+
itemHandler: addTag
|
|
2084
2110
|
});
|
|
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
|
-
)
|
|
2111
|
+
var { schema: batchRemoveTagByIdSchema, handler: batchRemoveTagById } = defineBatch({
|
|
2112
|
+
toolName: "batch_remove_tag_by_id",
|
|
2113
|
+
itemSchema: removeTagByIdSchema,
|
|
2114
|
+
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.",
|
|
2115
|
+
itemHandler: removeTagById
|
|
2092
2116
|
});
|
|
2093
|
-
async function batchRemoveTagById(input, opts = {}) {
|
|
2094
|
-
return batchExecute("batch_remove_tag_by_id", input.items, (item) => removeTagById(item), opts);
|
|
2095
|
-
}
|
|
2096
2117
|
|
|
2097
2118
|
// src/tools/users.ts
|
|
2098
|
-
import { z as
|
|
2099
|
-
var listUsersSchema =
|
|
2100
|
-
page:
|
|
2101
|
-
perPage:
|
|
2119
|
+
import { z as z14 } from "zod";
|
|
2120
|
+
var listUsersSchema = z14.object({
|
|
2121
|
+
page: z14.number().int().positive().optional(),
|
|
2122
|
+
perPage: z14.number().int().min(1).max(100).optional()
|
|
2102
2123
|
});
|
|
2103
2124
|
async function listUsers(input) {
|
|
2104
2125
|
const { data, nextPage } = await capsuleGetCached("/users", {
|
|
@@ -2107,32 +2128,32 @@ async function listUsers(input) {
|
|
|
2107
2128
|
});
|
|
2108
2129
|
return { ...data, nextPage };
|
|
2109
2130
|
}
|
|
2110
|
-
var getCurrentUserSchema =
|
|
2131
|
+
var getCurrentUserSchema = z14.object({});
|
|
2111
2132
|
async function getCurrentUser(_input) {
|
|
2112
2133
|
const { data } = await capsuleGet("/users/current");
|
|
2113
2134
|
return data;
|
|
2114
2135
|
}
|
|
2115
2136
|
|
|
2116
2137
|
// src/tools/filters.ts
|
|
2117
|
-
import { z as
|
|
2118
|
-
var FilterConditionSchema =
|
|
2119
|
-
field:
|
|
2138
|
+
import { z as z15 } from "zod";
|
|
2139
|
+
var FilterConditionSchema = z15.object({
|
|
2140
|
+
field: z15.string().describe(
|
|
2120
2141
|
"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
2142
|
),
|
|
2122
|
-
operator:
|
|
2143
|
+
operator: z15.string().describe(
|
|
2123
2144
|
"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
2145
|
),
|
|
2125
|
-
value:
|
|
2146
|
+
value: z15.union([z15.string(), z15.number(), z15.boolean(), z15.null()]).describe(
|
|
2126
2147
|
"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
2148
|
)
|
|
2128
2149
|
});
|
|
2129
|
-
var FilterInputSchema =
|
|
2130
|
-
conditions:
|
|
2150
|
+
var FilterInputSchema = z15.object({
|
|
2151
|
+
conditions: z15.array(FilterConditionSchema).min(1).describe(
|
|
2131
2152
|
"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
2153
|
),
|
|
2133
|
-
embed:
|
|
2134
|
-
page:
|
|
2135
|
-
perPage:
|
|
2154
|
+
embed: z15.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2155
|
+
page: z15.number().int().positive().optional().default(1),
|
|
2156
|
+
perPage: z15.number().int().min(1).max(100).optional().default(25)
|
|
2136
2157
|
});
|
|
2137
2158
|
async function runFilter(entityPath, input) {
|
|
2138
2159
|
const { data, nextPage } = await capsuleSearch(
|
|
@@ -2163,12 +2184,12 @@ async function filterProjects(input) {
|
|
|
2163
2184
|
}
|
|
2164
2185
|
|
|
2165
2186
|
// src/tools/metadata.ts
|
|
2166
|
-
import { z as
|
|
2187
|
+
import { z as z16 } from "zod";
|
|
2167
2188
|
var paginationFields3 = {
|
|
2168
|
-
page:
|
|
2169
|
-
perPage:
|
|
2189
|
+
page: z16.number().int().positive().optional(),
|
|
2190
|
+
perPage: z16.number().int().min(1).max(100).optional().describe("Page size, max 100. Defaults to 100 for reference data.")
|
|
2170
2191
|
};
|
|
2171
|
-
var listTeamsSchema =
|
|
2192
|
+
var listTeamsSchema = z16.object({ ...paginationFields3 });
|
|
2172
2193
|
async function listTeams(input) {
|
|
2173
2194
|
const { data, nextPage } = await capsuleGetCached("/teams", {
|
|
2174
2195
|
page: input.page ?? 1,
|
|
@@ -2176,7 +2197,7 @@ async function listTeams(input) {
|
|
|
2176
2197
|
});
|
|
2177
2198
|
return { ...data, nextPage };
|
|
2178
2199
|
}
|
|
2179
|
-
var listLostReasonsSchema =
|
|
2200
|
+
var listLostReasonsSchema = z16.object({ ...paginationFields3 });
|
|
2180
2201
|
async function listLostReasons(input) {
|
|
2181
2202
|
const { data, nextPage } = await capsuleGetCached("/lostreasons", {
|
|
2182
2203
|
page: input.page ?? 1,
|
|
@@ -2184,7 +2205,7 @@ async function listLostReasons(input) {
|
|
|
2184
2205
|
});
|
|
2185
2206
|
return { ...data, nextPage };
|
|
2186
2207
|
}
|
|
2187
|
-
var listActivityTypesSchema =
|
|
2208
|
+
var listActivityTypesSchema = z16.object({ ...paginationFields3 });
|
|
2188
2209
|
async function listActivityTypes(input) {
|
|
2189
2210
|
const { data, nextPage } = await capsuleGetCached(
|
|
2190
2211
|
"/activitytypes",
|
|
@@ -2195,12 +2216,12 @@ async function listActivityTypes(input) {
|
|
|
2195
2216
|
);
|
|
2196
2217
|
return { ...data, nextPage };
|
|
2197
2218
|
}
|
|
2198
|
-
var getSiteSchema =
|
|
2219
|
+
var getSiteSchema = z16.object({});
|
|
2199
2220
|
async function getSite(_input) {
|
|
2200
2221
|
const { data } = await capsuleGetCached("/site");
|
|
2201
2222
|
return data;
|
|
2202
2223
|
}
|
|
2203
|
-
var listTrackDefinitionsSchema =
|
|
2224
|
+
var listTrackDefinitionsSchema = z16.object({ ...paginationFields3 });
|
|
2204
2225
|
async function listTrackDefinitions(input) {
|
|
2205
2226
|
const { data, nextPage } = await capsuleGetCached(
|
|
2206
2227
|
"/trackdefinitions",
|
|
@@ -2208,7 +2229,7 @@ async function listTrackDefinitions(input) {
|
|
|
2208
2229
|
);
|
|
2209
2230
|
return { ...data, nextPage };
|
|
2210
2231
|
}
|
|
2211
|
-
var listCategoriesSchema =
|
|
2232
|
+
var listCategoriesSchema = z16.object({ ...paginationFields3 });
|
|
2212
2233
|
async function listCategories(input) {
|
|
2213
2234
|
const { data, nextPage } = await capsuleGetCached("/categories", {
|
|
2214
2235
|
page: input.page ?? 1,
|
|
@@ -2216,7 +2237,7 @@ async function listCategories(input) {
|
|
|
2216
2237
|
});
|
|
2217
2238
|
return { ...data, nextPage };
|
|
2218
2239
|
}
|
|
2219
|
-
var listGoalsSchema =
|
|
2240
|
+
var listGoalsSchema = z16.object({ ...paginationFields3 });
|
|
2220
2241
|
async function listGoals(input) {
|
|
2221
2242
|
const { data, nextPage } = await capsuleGetCached("/goals", {
|
|
2222
2243
|
page: input.page ?? 1,
|
|
@@ -2226,14 +2247,14 @@ async function listGoals(input) {
|
|
|
2226
2247
|
}
|
|
2227
2248
|
|
|
2228
2249
|
// src/tools/audit.ts
|
|
2229
|
-
import { z as
|
|
2230
|
-
var listEmployeesSchema =
|
|
2231
|
-
partyId:
|
|
2250
|
+
import { z as z17 } from "zod";
|
|
2251
|
+
var listEmployeesSchema = z17.object({
|
|
2252
|
+
partyId: positiveId.describe(
|
|
2232
2253
|
"The organisation's party id. Returns the people whose `organisation` field links to this party."
|
|
2233
2254
|
),
|
|
2234
|
-
page:
|
|
2235
|
-
perPage:
|
|
2236
|
-
embed:
|
|
2255
|
+
page: z17.number().int().positive().optional().default(1),
|
|
2256
|
+
perPage: z17.number().int().min(1).max(100).optional().default(25),
|
|
2257
|
+
embed: z17.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION)
|
|
2237
2258
|
});
|
|
2238
2259
|
async function listEmployees(input) {
|
|
2239
2260
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2242,15 +2263,15 @@ async function listEmployees(input) {
|
|
|
2242
2263
|
);
|
|
2243
2264
|
return { ...data, nextPage };
|
|
2244
2265
|
}
|
|
2245
|
-
var DeletedSinceSchema =
|
|
2266
|
+
var DeletedSinceSchema = z17.string().describe(
|
|
2246
2267
|
"REQUIRED. ISO-8601 timestamp; only deletions on or after this point are returned. Example: '2026-01-01T00:00:00Z'."
|
|
2247
2268
|
);
|
|
2248
2269
|
var DeletedPagination = {
|
|
2249
2270
|
since: DeletedSinceSchema,
|
|
2250
|
-
page:
|
|
2251
|
-
perPage:
|
|
2271
|
+
page: z17.number().int().positive().optional().default(1),
|
|
2272
|
+
perPage: z17.number().int().min(1).max(100).optional().default(25)
|
|
2252
2273
|
};
|
|
2253
|
-
var listDeletedPartiesSchema =
|
|
2274
|
+
var listDeletedPartiesSchema = z17.object(DeletedPagination);
|
|
2254
2275
|
async function listDeletedParties(input) {
|
|
2255
2276
|
const { data, nextPage } = await capsuleGet("/parties/deleted", {
|
|
2256
2277
|
since: input.since,
|
|
@@ -2259,7 +2280,7 @@ async function listDeletedParties(input) {
|
|
|
2259
2280
|
});
|
|
2260
2281
|
return { ...data, nextPage };
|
|
2261
2282
|
}
|
|
2262
|
-
var listDeletedOpportunitiesSchema =
|
|
2283
|
+
var listDeletedOpportunitiesSchema = z17.object(DeletedPagination);
|
|
2263
2284
|
async function listDeletedOpportunities(input) {
|
|
2264
2285
|
const { data, nextPage } = await capsuleGet("/opportunities/deleted", {
|
|
2265
2286
|
since: input.since,
|
|
@@ -2268,7 +2289,7 @@ async function listDeletedOpportunities(input) {
|
|
|
2268
2289
|
});
|
|
2269
2290
|
return { ...data, nextPage };
|
|
2270
2291
|
}
|
|
2271
|
-
var listDeletedProjectsSchema =
|
|
2292
|
+
var listDeletedProjectsSchema = z17.object(DeletedPagination);
|
|
2272
2293
|
async function listDeletedProjects(input) {
|
|
2273
2294
|
const { data, nextPage } = await capsuleGet("/kases/deleted", {
|
|
2274
2295
|
since: input.since,
|
|
@@ -2279,14 +2300,14 @@ async function listDeletedProjects(input) {
|
|
|
2279
2300
|
}
|
|
2280
2301
|
|
|
2281
2302
|
// src/tools/relationships.ts
|
|
2282
|
-
import { z as
|
|
2283
|
-
var RelationshipEntity =
|
|
2284
|
-
var listAdditionalPartiesSchema =
|
|
2303
|
+
import { z as z18 } from "zod";
|
|
2304
|
+
var RelationshipEntity = z18.enum(["opportunities", "kases"]).describe("Which entity has the additional-party links. Use 'kases' for projects.");
|
|
2305
|
+
var listAdditionalPartiesSchema = z18.object({
|
|
2285
2306
|
entity: RelationshipEntity,
|
|
2286
|
-
entityId:
|
|
2287
|
-
embed:
|
|
2288
|
-
page:
|
|
2289
|
-
perPage:
|
|
2307
|
+
entityId: positiveId.describe("ID of the opportunity or project."),
|
|
2308
|
+
embed: z18.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2309
|
+
page: z18.number().int().positive().optional().default(1),
|
|
2310
|
+
perPage: z18.number().int().min(1).max(100).optional().default(25)
|
|
2290
2311
|
});
|
|
2291
2312
|
async function listAdditionalParties(input) {
|
|
2292
2313
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2295,10 +2316,12 @@ async function listAdditionalParties(input) {
|
|
|
2295
2316
|
);
|
|
2296
2317
|
return { ...data, nextPage };
|
|
2297
2318
|
}
|
|
2298
|
-
var addAdditionalPartySchema =
|
|
2319
|
+
var addAdditionalPartySchema = z18.object({
|
|
2299
2320
|
entity: RelationshipEntity,
|
|
2300
|
-
entityId:
|
|
2301
|
-
partyId:
|
|
2321
|
+
entityId: positiveId,
|
|
2322
|
+
partyId: positiveId.describe(
|
|
2323
|
+
"ID of the party (person or organisation) to link as an additional party."
|
|
2324
|
+
)
|
|
2302
2325
|
});
|
|
2303
2326
|
async function addAdditionalParty(input) {
|
|
2304
2327
|
try {
|
|
@@ -2326,10 +2349,10 @@ async function addAdditionalParty(input) {
|
|
|
2326
2349
|
throw err;
|
|
2327
2350
|
}
|
|
2328
2351
|
}
|
|
2329
|
-
var removeAdditionalPartySchema =
|
|
2352
|
+
var removeAdditionalPartySchema = z18.object({
|
|
2330
2353
|
entity: RelationshipEntity,
|
|
2331
|
-
entityId:
|
|
2332
|
-
partyId:
|
|
2354
|
+
entityId: positiveId,
|
|
2355
|
+
partyId: positiveId,
|
|
2333
2356
|
confirm: confirmFlag().describe(
|
|
2334
2357
|
"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
2358
|
)
|
|
@@ -2356,11 +2379,11 @@ async function removeAdditionalParty(input) {
|
|
|
2356
2379
|
})
|
|
2357
2380
|
);
|
|
2358
2381
|
}
|
|
2359
|
-
var listAssociatedProjectsSchema =
|
|
2360
|
-
opportunityId:
|
|
2361
|
-
embed:
|
|
2362
|
-
page:
|
|
2363
|
-
perPage:
|
|
2382
|
+
var listAssociatedProjectsSchema = z18.object({
|
|
2383
|
+
opportunityId: positiveId,
|
|
2384
|
+
embed: z18.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2385
|
+
page: z18.number().int().positive().optional().default(1),
|
|
2386
|
+
perPage: z18.number().int().min(1).max(100).optional().default(25)
|
|
2364
2387
|
});
|
|
2365
2388
|
async function listAssociatedProjects(input) {
|
|
2366
2389
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2371,9 +2394,9 @@ async function listAssociatedProjects(input) {
|
|
|
2371
2394
|
}
|
|
2372
2395
|
|
|
2373
2396
|
// src/tools/custom-fields.ts
|
|
2374
|
-
import { z as
|
|
2375
|
-
var CustomFieldEntity =
|
|
2376
|
-
var listCustomFieldsSchema =
|
|
2397
|
+
import { z as z19 } from "zod";
|
|
2398
|
+
var CustomFieldEntity = z19.enum(["parties", "opportunities", "kases"]).describe("Which entity type's custom field schema to inspect. Use 'kases' for projects.");
|
|
2399
|
+
var listCustomFieldsSchema = z19.object({
|
|
2377
2400
|
entity: CustomFieldEntity
|
|
2378
2401
|
});
|
|
2379
2402
|
async function listCustomFields(input) {
|
|
@@ -2382,9 +2405,9 @@ async function listCustomFields(input) {
|
|
|
2382
2405
|
);
|
|
2383
2406
|
return data;
|
|
2384
2407
|
}
|
|
2385
|
-
var getCustomFieldSchema =
|
|
2408
|
+
var getCustomFieldSchema = z19.object({
|
|
2386
2409
|
entity: CustomFieldEntity,
|
|
2387
|
-
fieldId:
|
|
2410
|
+
fieldId: positiveId.describe("Custom field definition id.")
|
|
2388
2411
|
});
|
|
2389
2412
|
async function getCustomField(input) {
|
|
2390
2413
|
const { data } = await capsuleGetCached(
|
|
@@ -2394,11 +2417,11 @@ async function getCustomField(input) {
|
|
|
2394
2417
|
}
|
|
2395
2418
|
|
|
2396
2419
|
// src/tools/tracks.ts
|
|
2397
|
-
import { z as
|
|
2398
|
-
var TrackEntity =
|
|
2399
|
-
var listEntityTracksSchema =
|
|
2420
|
+
import { z as z20 } from "zod";
|
|
2421
|
+
var TrackEntity = z20.enum(["parties", "opportunities", "kases"]).describe("Use 'kases' for projects.");
|
|
2422
|
+
var listEntityTracksSchema = z20.object({
|
|
2400
2423
|
entity: TrackEntity,
|
|
2401
|
-
entityId:
|
|
2424
|
+
entityId: positiveId
|
|
2402
2425
|
});
|
|
2403
2426
|
async function listEntityTracks(input) {
|
|
2404
2427
|
const { data } = await capsuleGet(
|
|
@@ -2406,20 +2429,20 @@ async function listEntityTracks(input) {
|
|
|
2406
2429
|
);
|
|
2407
2430
|
return data;
|
|
2408
2431
|
}
|
|
2409
|
-
var showTrackSchema =
|
|
2410
|
-
trackId:
|
|
2432
|
+
var showTrackSchema = z20.object({
|
|
2433
|
+
trackId: positiveId
|
|
2411
2434
|
});
|
|
2412
2435
|
async function showTrack(input) {
|
|
2413
2436
|
const { data } = await capsuleGet(`/tracks/${input.trackId}`);
|
|
2414
2437
|
return data;
|
|
2415
2438
|
}
|
|
2416
|
-
var applyTrackSchema =
|
|
2417
|
-
entity:
|
|
2418
|
-
entityId:
|
|
2419
|
-
trackDefinitionId:
|
|
2439
|
+
var applyTrackSchema = z20.object({
|
|
2440
|
+
entity: z20.enum(["opportunities", "kases"]).describe("Which entity to apply the track to. Use 'kases' for projects."),
|
|
2441
|
+
entityId: positiveId,
|
|
2442
|
+
trackDefinitionId: positiveId.describe(
|
|
2420
2443
|
"The trackDefinition to apply (from list_track_definitions). Auto-creates task definitions on the target entity per the track's rules."
|
|
2421
2444
|
),
|
|
2422
|
-
startDate:
|
|
2445
|
+
startDate: z20.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe(
|
|
2423
2446
|
"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
2447
|
)
|
|
2425
2448
|
});
|
|
@@ -2432,9 +2455,9 @@ async function applyTrack(input) {
|
|
|
2432
2455
|
if (input.startDate !== void 0) track["trackDateOn"] = input.startDate;
|
|
2433
2456
|
return capsulePost("/tracks", { track });
|
|
2434
2457
|
}
|
|
2435
|
-
var updateTrackSchema =
|
|
2436
|
-
trackId:
|
|
2437
|
-
fields:
|
|
2458
|
+
var updateTrackSchema = z20.object({
|
|
2459
|
+
trackId: positiveId,
|
|
2460
|
+
fields: z20.record(z20.string(), z20.unknown()).describe(
|
|
2438
2461
|
"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
2462
|
)
|
|
2440
2463
|
});
|
|
@@ -2446,8 +2469,8 @@ async function updateTrack(input) {
|
|
|
2446
2469
|
track: input.fields
|
|
2447
2470
|
});
|
|
2448
2471
|
}
|
|
2449
|
-
var removeTrackSchema =
|
|
2450
|
-
trackId:
|
|
2472
|
+
var removeTrackSchema = z20.object({
|
|
2473
|
+
trackId: positiveId,
|
|
2451
2474
|
confirm: confirmFlag().describe(
|
|
2452
2475
|
"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
2476
|
)
|
|
@@ -2464,13 +2487,13 @@ async function removeTrack(input) {
|
|
|
2464
2487
|
}
|
|
2465
2488
|
|
|
2466
2489
|
// src/tools/attachments.ts
|
|
2467
|
-
import { z as
|
|
2490
|
+
import { z as z21 } from "zod";
|
|
2468
2491
|
var DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024;
|
|
2469
2492
|
var HARD_MAX_SIZE_BYTES = 25 * 1024 * 1024;
|
|
2470
2493
|
var HARD_MAX_BASE64_CHARS = Math.ceil(HARD_MAX_SIZE_BYTES / 3) * 4;
|
|
2471
|
-
var getAttachmentSchema =
|
|
2472
|
-
id:
|
|
2473
|
-
maxSizeBytes:
|
|
2494
|
+
var getAttachmentSchema = z21.object({
|
|
2495
|
+
id: positiveId.describe("Attachment ID."),
|
|
2496
|
+
maxSizeBytes: z21.number().int().positive().max(HARD_MAX_SIZE_BYTES).optional().describe(
|
|
2474
2497
|
`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
2498
|
)
|
|
2476
2499
|
});
|
|
@@ -2485,22 +2508,22 @@ async function getAttachment(input) {
|
|
|
2485
2508
|
}
|
|
2486
2509
|
return { contentType, buffer, sizeBytes };
|
|
2487
2510
|
}
|
|
2488
|
-
var uploadAttachmentSchema =
|
|
2489
|
-
filename:
|
|
2511
|
+
var uploadAttachmentSchema = z21.object({
|
|
2512
|
+
filename: z21.string().min(1).describe(
|
|
2490
2513
|
"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
2514
|
),
|
|
2492
|
-
contentType:
|
|
2515
|
+
contentType: z21.string().min(1).describe(
|
|
2493
2516
|
"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
2517
|
),
|
|
2495
|
-
dataBase64:
|
|
2518
|
+
dataBase64: z21.string().min(1).max(HARD_MAX_BASE64_CHARS).describe(
|
|
2496
2519
|
"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
2520
|
),
|
|
2498
|
-
content:
|
|
2521
|
+
content: z21.string().optional().describe(
|
|
2499
2522
|
"Body text for the note that will hold the attachment. Defaults to '[attachment]' if omitted."
|
|
2500
2523
|
),
|
|
2501
|
-
partyId:
|
|
2502
|
-
opportunityId:
|
|
2503
|
-
projectId:
|
|
2524
|
+
partyId: positiveId.optional().describe("Link the new note to a party (mutually exclusive with opportunityId / projectId)."),
|
|
2525
|
+
opportunityId: positiveId.optional(),
|
|
2526
|
+
projectId: positiveId.optional()
|
|
2504
2527
|
});
|
|
2505
2528
|
function isValidBase64(s) {
|
|
2506
2529
|
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(s)) return false;
|
|
@@ -2550,23 +2573,23 @@ async function uploadAttachment(input) {
|
|
|
2550
2573
|
}
|
|
2551
2574
|
|
|
2552
2575
|
// src/tools/saved-filters.ts
|
|
2553
|
-
import { z as
|
|
2554
|
-
var EntitySchema =
|
|
2576
|
+
import { z as z22 } from "zod";
|
|
2577
|
+
var EntitySchema = z22.enum(["parties", "opportunities", "kases"]).describe(
|
|
2555
2578
|
"Which entity type the filter operates over. Use 'kases' for projects (Capsule's legacy name)."
|
|
2556
2579
|
);
|
|
2557
|
-
var listSavedFiltersSchema =
|
|
2580
|
+
var listSavedFiltersSchema = z22.object({
|
|
2558
2581
|
entity: EntitySchema
|
|
2559
2582
|
});
|
|
2560
2583
|
async function listSavedFilters(input) {
|
|
2561
2584
|
const { data } = await capsuleGetCached(`/${input.entity}/filters`);
|
|
2562
2585
|
return data;
|
|
2563
2586
|
}
|
|
2564
|
-
var runSavedFilterSchema =
|
|
2587
|
+
var runSavedFilterSchema = z22.object({
|
|
2565
2588
|
entity: EntitySchema,
|
|
2566
|
-
id:
|
|
2567
|
-
embed:
|
|
2568
|
-
page:
|
|
2569
|
-
perPage:
|
|
2589
|
+
id: positiveId.describe("The saved filter id (from list_saved_filters)."),
|
|
2590
|
+
embed: z22.string().optional().describe(EMBED_TAGS_FIELDS_DESCRIPTION),
|
|
2591
|
+
page: z22.number().int().positive().optional().default(1),
|
|
2592
|
+
perPage: z22.number().int().min(1).max(100).optional().default(25)
|
|
2570
2593
|
});
|
|
2571
2594
|
async function runSavedFilter(input) {
|
|
2572
2595
|
const { data, nextPage } = await capsuleGet(
|
|
@@ -2584,7 +2607,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
2584
2607
|
const server2 = new McpServer(
|
|
2585
2608
|
{
|
|
2586
2609
|
name: "capsulemcp",
|
|
2587
|
-
version: "1.6.
|
|
2610
|
+
version: "1.6.3",
|
|
2588
2611
|
description: "Read and (optionally) modify Capsule CRM data \u2014 parties, opportunities, projects, tasks, timeline entries, pipelines, tags.",
|
|
2589
2612
|
websiteUrl: "https://github.com/soil-dev/capsulemcp",
|
|
2590
2613
|
icons: ICONS
|
|
@@ -2687,7 +2710,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
2687
2710
|
registerTool(
|
|
2688
2711
|
server2,
|
|
2689
2712
|
"update_party",
|
|
2690
|
-
"Update top-level fields on an existing party (about, firstName/lastName/name/title/jobTitle, ownerId). Only the fields you provide are changed. Child arrays (emailAddresses / phoneNumbers / addresses / websites) on this tool are APPEND-ONLY: items are merged into the existing list, not replaced. For surgical changes \u2014 replacing one email, removing one phone number, fixing the type on one address \u2014 use the dedicated atomic tools: add_party_email_address / remove_party_email_address_by_id (and the phone/address/website equivalents).",
|
|
2713
|
+
"Update top-level fields on an existing party (about, firstName/lastName/name/title/jobTitle, ownerId, organisationId). For PERSON parties, organisationId links to an organisation or null unlinks the person from its organisation; for ORGANISATION parties Capsule silently ignores organisationId. Only the fields you provide are changed. Child arrays (emailAddresses / phoneNumbers / addresses / websites) on this tool are APPEND-ONLY: items are merged into the existing list, not replaced. For surgical changes \u2014 replacing one email, removing one phone number, fixing the type on one address \u2014 use the dedicated atomic tools: add_party_email_address / remove_party_email_address_by_id (and the phone/address/website equivalents).",
|
|
2691
2714
|
updatePartySchema,
|
|
2692
2715
|
updateParty
|
|
2693
2716
|
);
|
|
@@ -2822,7 +2845,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
2822
2845
|
registerTool(
|
|
2823
2846
|
server2,
|
|
2824
2847
|
"update_opportunity",
|
|
2825
|
-
"Update fields on an existing opportunity. Only the fields you provide are changed. Closed (Won/Lost) opportunities ARE editable \u2014 Capsule does not enforce closed-record immutability, so `value`, `description`, etc. can be changed on a Won opp without warning. If the workflow needs historical revenue numbers to be stable, enforce that caller-side.",
|
|
2848
|
+
"Update fields on an existing opportunity, including the parent-reference field `partyId` to reassign the opp to a different primary party. Only the fields you provide are changed. Closed (Won/Lost) opportunities ARE editable \u2014 Capsule does not enforce closed-record immutability, so `value`, `description`, etc. can be changed on a Won opp without warning. If the workflow needs historical revenue numbers to be stable, enforce that caller-side. Capsule requires every opportunity to have a party \u2014 passing `partyId: null` is rejected with 422 'party is required'.",
|
|
2826
2849
|
updateOpportunitySchema,
|
|
2827
2850
|
updateOpportunity
|
|
2828
2851
|
);
|
|
@@ -2887,7 +2910,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
2887
2910
|
registerTool(
|
|
2888
2911
|
server2,
|
|
2889
2912
|
"update_project",
|
|
2890
|
-
"Update fields on an existing project. Only the fields you provide are changed. Use status='CLOSED' to close a project. CLOSED projects remain fully editable \u2014 Capsule does not enforce closed-record immutability. Stage moves and description edits on a CLOSED project are accepted without warning.",
|
|
2913
|
+
"Update fields on an existing project, including the parent-reference field `partyId` to reassign the project to a different primary party. Only the fields you provide are changed. Use status='CLOSED' to close a project. CLOSED projects remain fully editable \u2014 Capsule does not enforce closed-record immutability. Stage moves and description edits on a CLOSED project are accepted without warning. Capsule requires every project to have a party \u2014 passing `partyId: null` is rejected with 422 'party is required'.",
|
|
2891
2914
|
updateProjectSchema,
|
|
2892
2915
|
updateProject
|
|
2893
2916
|
);
|
|
@@ -2966,7 +2989,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
2966
2989
|
registerTool(
|
|
2967
2990
|
server2,
|
|
2968
2991
|
"update_task",
|
|
2969
|
-
"Update fields on an existing task: `description`, `dueOn`, `dueTime`, `detail`, `status` (OPEN or COMPLETED), and `
|
|
2992
|
+
"Update fields on an existing task: `description`, `dueOn`, `dueTime`, `detail`, `status` (OPEN or COMPLETED), `ownerId`, and the parent-reference fields `partyId`, `opportunityId`, `projectId`. Pass a parent id to re-link the task, or null on a parent field to orphan/unlink it; at most one parent id may be set in a single call, though null+id swaps are allowed. Only the fields you provide are changed. To mark a task done, prefer the dedicated `complete_task` tool \u2014 it's idempotent (a no-op success on an already-completed task) and semantically clearer than `update_task status=COMPLETED`. Capsule rejects directly setting status=PENDING (which exists only internally for track-driven tasks); use OPEN or COMPLETED. Completed tasks remain fully editable \u2014 Capsule does not enforce closed-record immutability.",
|
|
2970
2993
|
updateTaskSchema,
|
|
2971
2994
|
updateTask
|
|
2972
2995
|
);
|
|
@@ -3169,7 +3192,7 @@ function createCapsuleMcpServer(opts) {
|
|
|
3169
3192
|
registerTool(
|
|
3170
3193
|
server2,
|
|
3171
3194
|
"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.",
|
|
3195
|
+
"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
3196
|
listTeamsSchema,
|
|
3174
3197
|
listTeams
|
|
3175
3198
|
);
|