@sellable/mcp 0.1.613 → 0.1.614

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/dist/server.js CHANGED
@@ -52,7 +52,7 @@ import { addRubricItem, checkRubric, deleteRubricItem, draftRubrics, saveRubrics
52
52
  import { getSchedulerFillCapacity } from "./tools/scheduler-fill-capacity.js";
53
53
  import { runSchedulerSweep } from "./tools/scheduler-run.js";
54
54
  import { getSenderRoutingTool, setSenderRoutingTool, } from "./tools/sender-routing.js";
55
- import { getSender, listSenders, refreshPaidInmailCredits, } from "./tools/senders.js";
55
+ import { getSender, listSenders, refreshPaidInmailCredits, setSenderCalendarLink, } from "./tools/senders.js";
56
56
  import { attachRecommendedSequence, attachSequence, createWorkflowTable, } from "./tools/sequencer.js";
57
57
  import { setupEvergreenCampaigns } from "./tools/setup-evergreen-campaigns.js";
58
58
  import { exportTableCsv, listTables } from "./tools/tables.js";
@@ -577,6 +577,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
577
577
  case "get_sender":
578
578
  result = await getSender(args);
579
579
  break;
580
+ case "set_sender_calendar_link":
581
+ result = await setSenderCalendarLink(args);
582
+ break;
580
583
  case "refresh_paid_inmail_credits":
581
584
  result = await refreshPaidInmailCredits(args);
582
585
  break;
@@ -2720,10 +2720,12 @@ export declare const allTools: ({
2720
2720
  senderId: {
2721
2721
  type: string;
2722
2722
  description: string;
2723
+ minLength?: undefined;
2723
2724
  };
2724
2725
  workspaceId: {
2725
2726
  type: string;
2726
2727
  description: string;
2728
+ minLength?: undefined;
2727
2729
  };
2728
2730
  actionType?: undefined;
2729
2731
  campaignId?: undefined;
@@ -2732,10 +2734,134 @@ export declare const allTools: ({
2732
2734
  threshold?: undefined;
2733
2735
  maxStalenessSeconds?: undefined;
2734
2736
  paidInmailCreditsMaxStalenessSeconds?: undefined;
2737
+ calendarLink?: undefined;
2738
+ expectedCalendarLink?: undefined;
2739
+ expectedCalendarLinkRevision?: undefined;
2740
+ force?: undefined;
2735
2741
  };
2736
2742
  required: string[];
2737
2743
  additionalProperties: boolean;
2738
2744
  };
2745
+ outputSchema?: undefined;
2746
+ annotations?: undefined;
2747
+ } | {
2748
+ name: string;
2749
+ description: string;
2750
+ inputSchema: {
2751
+ type: string;
2752
+ properties: {
2753
+ senderId: {
2754
+ type: string;
2755
+ minLength: number;
2756
+ description?: undefined;
2757
+ };
2758
+ workspaceId: {
2759
+ type: string;
2760
+ minLength: number;
2761
+ description?: undefined;
2762
+ };
2763
+ calendarLink: {
2764
+ readonly type: readonly ["string", "null"];
2765
+ };
2766
+ expectedCalendarLink: {
2767
+ readonly type: readonly ["string", "null"];
2768
+ };
2769
+ expectedCalendarLinkRevision: {
2770
+ readonly type: readonly ["string", "null"];
2771
+ };
2772
+ force: {
2773
+ type: string;
2774
+ };
2775
+ actionType?: undefined;
2776
+ campaignId?: undefined;
2777
+ tableId?: undefined;
2778
+ columnId?: undefined;
2779
+ threshold?: undefined;
2780
+ maxStalenessSeconds?: undefined;
2781
+ paidInmailCreditsMaxStalenessSeconds?: undefined;
2782
+ };
2783
+ required: string[];
2784
+ additionalProperties: boolean;
2785
+ };
2786
+ outputSchema: {
2787
+ readonly type: "object";
2788
+ readonly oneOf: readonly [{
2789
+ readonly type: "object";
2790
+ readonly properties: {
2791
+ readonly ok: {
2792
+ readonly const: true;
2793
+ };
2794
+ readonly senderId: {
2795
+ readonly type: "string";
2796
+ };
2797
+ readonly workspaceId: {
2798
+ readonly type: "string";
2799
+ };
2800
+ readonly previousCalendarLink: {
2801
+ readonly type: readonly ["string", "null"];
2802
+ };
2803
+ readonly calendarLink: {
2804
+ readonly type: readonly ["string", "null"];
2805
+ };
2806
+ readonly previousCalendarLinkRevision: {
2807
+ readonly type: readonly ["string", "null"];
2808
+ };
2809
+ readonly calendarLinkRevision: {
2810
+ readonly type: readonly ["string", "null"];
2811
+ };
2812
+ readonly changed: {
2813
+ readonly type: "boolean";
2814
+ };
2815
+ readonly cleared: {
2816
+ readonly type: "boolean";
2817
+ };
2818
+ readonly forced: {
2819
+ readonly type: "boolean";
2820
+ };
2821
+ };
2822
+ readonly required: readonly ["ok", "senderId", "workspaceId", "previousCalendarLink", "calendarLink", "previousCalendarLinkRevision", "calendarLinkRevision", "changed", "cleared", "forced"];
2823
+ readonly additionalProperties: false;
2824
+ }, {
2825
+ readonly type: "object";
2826
+ readonly properties: {
2827
+ readonly ok: {
2828
+ readonly const: false;
2829
+ };
2830
+ readonly code: {
2831
+ readonly type: "string";
2832
+ };
2833
+ readonly message: {
2834
+ readonly type: "string";
2835
+ };
2836
+ readonly status: {
2837
+ readonly type: readonly ["number", "null"];
2838
+ };
2839
+ readonly authError: {
2840
+ readonly type: "boolean";
2841
+ };
2842
+ readonly uncertain: {
2843
+ readonly type: "boolean";
2844
+ };
2845
+ readonly currentCalendarLink: {
2846
+ readonly type: readonly ["string", "null"];
2847
+ };
2848
+ readonly currentCalendarLinkRevision: {
2849
+ readonly type: readonly ["string", "null"];
2850
+ };
2851
+ readonly guidance: {
2852
+ readonly type: readonly ["string", "null"];
2853
+ };
2854
+ };
2855
+ readonly required: readonly ["ok", "code", "message", "status", "authError", "uncertain", "currentCalendarLink", "currentCalendarLinkRevision", "guidance"];
2856
+ readonly additionalProperties: false;
2857
+ }];
2858
+ };
2859
+ annotations: {
2860
+ readOnlyHint: boolean;
2861
+ destructiveHint: boolean;
2862
+ idempotentHint: boolean;
2863
+ openWorldHint: boolean;
2864
+ };
2739
2865
  } | {
2740
2866
  name: string;
2741
2867
  description: string;
@@ -14,6 +14,8 @@ export type SenderListItem = {
14
14
  timeZone: string;
15
15
  displayName: string;
16
16
  linkedinProfileUrl: string | null;
17
+ calendarLink: string | null;
18
+ calendarLinkRevision: string | null;
17
19
  verifiedAccountIdentity: VerifiedAccountIdentity | null;
18
20
  identityProofAvailable: boolean;
19
21
  identityMatchFields: SenderIdentityMatchField[];
@@ -70,6 +72,45 @@ export type RefreshPaidInmailCreditsResponse = {
70
72
  sendMutation: boolean;
71
73
  };
72
74
  };
75
+ export type SetSenderCalendarLinkInput = {
76
+ senderId: string;
77
+ workspaceId: string;
78
+ calendarLink: string | null;
79
+ expectedCalendarLink: string | null;
80
+ expectedCalendarLinkRevision: string | null;
81
+ force?: boolean;
82
+ };
83
+ export type SetSenderCalendarLinkSuccess = {
84
+ ok: true;
85
+ senderId: string;
86
+ workspaceId: string;
87
+ previousCalendarLink: string | null;
88
+ calendarLink: string | null;
89
+ previousCalendarLinkRevision: string | null;
90
+ calendarLinkRevision: string | null;
91
+ changed: boolean;
92
+ cleared: boolean;
93
+ forced: boolean;
94
+ };
95
+ export type SetSenderCalendarLinkError = {
96
+ ok: false;
97
+ code: string;
98
+ message: string;
99
+ status: number | null;
100
+ authError: boolean;
101
+ uncertain: boolean;
102
+ currentCalendarLink: string | null;
103
+ currentCalendarLinkRevision: string | null;
104
+ guidance: string | null;
105
+ };
106
+ type SenderCalendarLinkToolResult = {
107
+ structuredContent: SetSenderCalendarLinkSuccess | SetSenderCalendarLinkError;
108
+ content: [{
109
+ type: "text";
110
+ text: string;
111
+ }];
112
+ isError?: true;
113
+ };
73
114
  export declare const senderToolDefinitions: ({
74
115
  name: string;
75
116
  description: string;
@@ -79,6 +120,7 @@ export declare const senderToolDefinitions: ({
79
120
  workspaceId: {
80
121
  type: string;
81
122
  description: string;
123
+ minLength?: undefined;
82
124
  };
83
125
  senderId?: undefined;
84
126
  actionType?: undefined;
@@ -88,10 +130,16 @@ export declare const senderToolDefinitions: ({
88
130
  threshold?: undefined;
89
131
  maxStalenessSeconds?: undefined;
90
132
  paidInmailCreditsMaxStalenessSeconds?: undefined;
133
+ calendarLink?: undefined;
134
+ expectedCalendarLink?: undefined;
135
+ expectedCalendarLinkRevision?: undefined;
136
+ force?: undefined;
91
137
  };
92
138
  required: never[];
93
139
  additionalProperties: boolean;
94
140
  };
141
+ outputSchema?: undefined;
142
+ annotations?: undefined;
95
143
  } | {
96
144
  name: string;
97
145
  description: string;
@@ -101,10 +149,12 @@ export declare const senderToolDefinitions: ({
101
149
  senderId: {
102
150
  type: string;
103
151
  description: string;
152
+ minLength?: undefined;
104
153
  };
105
154
  workspaceId: {
106
155
  type: string;
107
156
  description: string;
157
+ minLength?: undefined;
108
158
  };
109
159
  actionType?: undefined;
110
160
  campaignId?: undefined;
@@ -113,10 +163,16 @@ export declare const senderToolDefinitions: ({
113
163
  threshold?: undefined;
114
164
  maxStalenessSeconds?: undefined;
115
165
  paidInmailCreditsMaxStalenessSeconds?: undefined;
166
+ calendarLink?: undefined;
167
+ expectedCalendarLink?: undefined;
168
+ expectedCalendarLinkRevision?: undefined;
169
+ force?: undefined;
116
170
  };
117
171
  required: string[];
118
172
  additionalProperties: boolean;
119
173
  };
174
+ outputSchema?: undefined;
175
+ annotations?: undefined;
120
176
  } | {
121
177
  name: string;
122
178
  description: string;
@@ -126,10 +182,12 @@ export declare const senderToolDefinitions: ({
126
182
  senderId: {
127
183
  type: string;
128
184
  description: string;
185
+ minLength?: undefined;
129
186
  };
130
187
  workspaceId: {
131
188
  type: string;
132
189
  description: string;
190
+ minLength?: undefined;
133
191
  };
134
192
  actionType: {
135
193
  type: string;
@@ -159,12 +217,137 @@ export declare const senderToolDefinitions: ({
159
217
  type: string;
160
218
  description: string;
161
219
  };
220
+ calendarLink?: undefined;
221
+ expectedCalendarLink?: undefined;
222
+ expectedCalendarLinkRevision?: undefined;
223
+ force?: undefined;
224
+ };
225
+ required: string[];
226
+ additionalProperties: boolean;
227
+ };
228
+ outputSchema?: undefined;
229
+ annotations?: undefined;
230
+ } | {
231
+ name: string;
232
+ description: string;
233
+ inputSchema: {
234
+ type: string;
235
+ properties: {
236
+ senderId: {
237
+ type: string;
238
+ minLength: number;
239
+ description?: undefined;
240
+ };
241
+ workspaceId: {
242
+ type: string;
243
+ minLength: number;
244
+ description?: undefined;
245
+ };
246
+ calendarLink: {
247
+ readonly type: readonly ["string", "null"];
248
+ };
249
+ expectedCalendarLink: {
250
+ readonly type: readonly ["string", "null"];
251
+ };
252
+ expectedCalendarLinkRevision: {
253
+ readonly type: readonly ["string", "null"];
254
+ };
255
+ force: {
256
+ type: string;
257
+ };
258
+ actionType?: undefined;
259
+ campaignId?: undefined;
260
+ tableId?: undefined;
261
+ columnId?: undefined;
262
+ threshold?: undefined;
263
+ maxStalenessSeconds?: undefined;
264
+ paidInmailCreditsMaxStalenessSeconds?: undefined;
162
265
  };
163
266
  required: string[];
164
267
  additionalProperties: boolean;
165
268
  };
269
+ outputSchema: {
270
+ readonly type: "object";
271
+ readonly oneOf: readonly [{
272
+ readonly type: "object";
273
+ readonly properties: {
274
+ readonly ok: {
275
+ readonly const: true;
276
+ };
277
+ readonly senderId: {
278
+ readonly type: "string";
279
+ };
280
+ readonly workspaceId: {
281
+ readonly type: "string";
282
+ };
283
+ readonly previousCalendarLink: {
284
+ readonly type: readonly ["string", "null"];
285
+ };
286
+ readonly calendarLink: {
287
+ readonly type: readonly ["string", "null"];
288
+ };
289
+ readonly previousCalendarLinkRevision: {
290
+ readonly type: readonly ["string", "null"];
291
+ };
292
+ readonly calendarLinkRevision: {
293
+ readonly type: readonly ["string", "null"];
294
+ };
295
+ readonly changed: {
296
+ readonly type: "boolean";
297
+ };
298
+ readonly cleared: {
299
+ readonly type: "boolean";
300
+ };
301
+ readonly forced: {
302
+ readonly type: "boolean";
303
+ };
304
+ };
305
+ readonly required: readonly ["ok", "senderId", "workspaceId", "previousCalendarLink", "calendarLink", "previousCalendarLinkRevision", "calendarLinkRevision", "changed", "cleared", "forced"];
306
+ readonly additionalProperties: false;
307
+ }, {
308
+ readonly type: "object";
309
+ readonly properties: {
310
+ readonly ok: {
311
+ readonly const: false;
312
+ };
313
+ readonly code: {
314
+ readonly type: "string";
315
+ };
316
+ readonly message: {
317
+ readonly type: "string";
318
+ };
319
+ readonly status: {
320
+ readonly type: readonly ["number", "null"];
321
+ };
322
+ readonly authError: {
323
+ readonly type: "boolean";
324
+ };
325
+ readonly uncertain: {
326
+ readonly type: "boolean";
327
+ };
328
+ readonly currentCalendarLink: {
329
+ readonly type: readonly ["string", "null"];
330
+ };
331
+ readonly currentCalendarLinkRevision: {
332
+ readonly type: readonly ["string", "null"];
333
+ };
334
+ readonly guidance: {
335
+ readonly type: readonly ["string", "null"];
336
+ };
337
+ };
338
+ readonly required: readonly ["ok", "code", "message", "status", "authError", "uncertain", "currentCalendarLink", "currentCalendarLinkRevision", "guidance"];
339
+ readonly additionalProperties: false;
340
+ }];
341
+ };
342
+ annotations: {
343
+ readOnlyHint: boolean;
344
+ destructiveHint: boolean;
345
+ idempotentHint: boolean;
346
+ openWorldHint: boolean;
347
+ };
166
348
  })[];
167
349
  export declare function listSenders(input?: ListSendersInput): Promise<ListSendersResponse>;
350
+ export declare function setSenderCalendarLink(input: SetSenderCalendarLinkInput): Promise<SenderCalendarLinkToolResult>;
168
351
  export declare function getSender(input: GetSenderInput): Promise<SenderDetailResponse>;
169
352
  export declare function refreshPaidInmailCredits(input: RefreshPaidInmailCreditsInput): Promise<RefreshPaidInmailCreditsResponse>;
170
353
  export {};
@@ -1,6 +1,66 @@
1
- import { getApi } from "../api.js";
1
+ import { getApi, SellableApiError } from "../api.js";
2
2
  import { isLinkedInProfileInput, normalizeLinkedInProfileInput, } from "./linkedin-url.js";
3
3
  import { normalizeExplicitWorkspaceId, workspaceRequestOptions, } from "./workspace-context.js";
4
+ const nullableStringSchema = { type: ["string", "null"] };
5
+ const setSenderCalendarLinkOutputSchema = {
6
+ type: "object",
7
+ oneOf: [
8
+ {
9
+ type: "object",
10
+ properties: {
11
+ ok: { const: true },
12
+ senderId: { type: "string" },
13
+ workspaceId: { type: "string" },
14
+ previousCalendarLink: nullableStringSchema,
15
+ calendarLink: nullableStringSchema,
16
+ previousCalendarLinkRevision: nullableStringSchema,
17
+ calendarLinkRevision: nullableStringSchema,
18
+ changed: { type: "boolean" },
19
+ cleared: { type: "boolean" },
20
+ forced: { type: "boolean" },
21
+ },
22
+ required: [
23
+ "ok",
24
+ "senderId",
25
+ "workspaceId",
26
+ "previousCalendarLink",
27
+ "calendarLink",
28
+ "previousCalendarLinkRevision",
29
+ "calendarLinkRevision",
30
+ "changed",
31
+ "cleared",
32
+ "forced",
33
+ ],
34
+ additionalProperties: false,
35
+ },
36
+ {
37
+ type: "object",
38
+ properties: {
39
+ ok: { const: false },
40
+ code: { type: "string" },
41
+ message: { type: "string" },
42
+ status: { type: ["number", "null"] },
43
+ authError: { type: "boolean" },
44
+ uncertain: { type: "boolean" },
45
+ currentCalendarLink: nullableStringSchema,
46
+ currentCalendarLinkRevision: nullableStringSchema,
47
+ guidance: nullableStringSchema,
48
+ },
49
+ required: [
50
+ "ok",
51
+ "code",
52
+ "message",
53
+ "status",
54
+ "authError",
55
+ "uncertain",
56
+ "currentCalendarLink",
57
+ "currentCalendarLinkRevision",
58
+ "guidance",
59
+ ],
60
+ additionalProperties: false,
61
+ },
62
+ ],
63
+ };
4
64
  function pickString(...values) {
5
65
  for (const value of values) {
6
66
  if (typeof value !== "string")
@@ -11,6 +71,74 @@ function pickString(...values) {
11
71
  }
12
72
  return null;
13
73
  }
74
+ function isNullableString(value) {
75
+ return value === null || typeof value === "string";
76
+ }
77
+ function calendarLinkToolResult(structuredContent) {
78
+ return {
79
+ structuredContent,
80
+ content: [{ type: "text", text: JSON.stringify(structuredContent) }],
81
+ ...(structuredContent.ok ? {} : { isError: true }),
82
+ };
83
+ }
84
+ function calendarLinkError(values) {
85
+ return calendarLinkToolResult({ ok: false, ...values });
86
+ }
87
+ function parseErrorBody(body) {
88
+ try {
89
+ const parsed = JSON.parse(body);
90
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
91
+ ? parsed
92
+ : null;
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ }
98
+ function safeErrorCode(value, fallback) {
99
+ return typeof value === "string" && /^[a-z0-9_]+$/.test(value)
100
+ ? value
101
+ : fallback;
102
+ }
103
+ function canonicalConflictCalendarLink(value) {
104
+ if (typeof value !== "string")
105
+ return null;
106
+ let parsed;
107
+ try {
108
+ parsed = new URL(value.trim());
109
+ }
110
+ catch {
111
+ return null;
112
+ }
113
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
114
+ return null;
115
+ }
116
+ if (parsed.username !== "" || parsed.password !== "") {
117
+ return null;
118
+ }
119
+ return parsed.toString();
120
+ }
121
+ function canonicalConflictRevision(value) {
122
+ if (typeof value !== "string")
123
+ return null;
124
+ const parsed = new Date(value);
125
+ if (!Number.isFinite(parsed.getTime()))
126
+ return null;
127
+ const canonical = parsed.toISOString();
128
+ return canonical === value ? canonical : null;
129
+ }
130
+ function isSenderCalendarLinkReceipt(value) {
131
+ if (!value || typeof value !== "object" || Array.isArray(value))
132
+ return false;
133
+ const receipt = value;
134
+ return (isNullableString(receipt.previousCalendarLink) &&
135
+ isNullableString(receipt.calendarLink) &&
136
+ isNullableString(receipt.previousCalendarLinkRevision) &&
137
+ isNullableString(receipt.calendarLinkRevision) &&
138
+ typeof receipt.changed === "boolean" &&
139
+ typeof receipt.cleared === "boolean" &&
140
+ typeof receipt.forced === "boolean");
141
+ }
14
142
  const DEFAULT_PAID_INMAIL_CREDITS_MAX_STALENESS_SECONDS = 4 * 60 * 60;
15
143
  function numberOrNull(value) {
16
144
  return typeof value === "number" && Number.isFinite(value) ? value : null;
@@ -188,6 +316,36 @@ export const senderToolDefinitions = [
188
316
  additionalProperties: false,
189
317
  },
190
318
  },
319
+ {
320
+ name: "set_sender_calendar_link",
321
+ description: "Set or clear one exact outbound sender's calendar booking link through Sellable's workspace-scoped compare-before-write route. Read list_senders first and pass its calendarLink and calendarLinkRevision. Use force only after the user explicitly authorizes overwriting newer state.",
322
+ inputSchema: {
323
+ type: "object",
324
+ properties: {
325
+ senderId: { type: "string", minLength: 1 },
326
+ workspaceId: { type: "string", minLength: 1 },
327
+ calendarLink: nullableStringSchema,
328
+ expectedCalendarLink: nullableStringSchema,
329
+ expectedCalendarLinkRevision: nullableStringSchema,
330
+ force: { type: "boolean" },
331
+ },
332
+ required: [
333
+ "senderId",
334
+ "workspaceId",
335
+ "calendarLink",
336
+ "expectedCalendarLink",
337
+ "expectedCalendarLinkRevision",
338
+ ],
339
+ additionalProperties: false,
340
+ },
341
+ outputSchema: setSenderCalendarLinkOutputSchema,
342
+ annotations: {
343
+ readOnlyHint: false,
344
+ destructiveHint: true,
345
+ idempotentHint: true,
346
+ openWorldHint: false,
347
+ },
348
+ },
191
349
  ];
192
350
  export async function listSenders(input = {}) {
193
351
  const api = getApi();
@@ -205,6 +363,12 @@ export async function listSenders(input = {}) {
205
363
  timeZone: String(s?.timeZone || ""),
206
364
  displayName: computeDisplayName(s),
207
365
  linkedinProfileUrl,
366
+ calendarLink: isNullableString(s?.replySettings?.calendarLink)
367
+ ? s.replySettings.calendarLink
368
+ : null,
369
+ calendarLinkRevision: isNullableString(s?.replySettings?.calendarLinkRevision)
370
+ ? s.replySettings.calendarLinkRevision
371
+ : null,
208
372
  verifiedAccountIdentity: identity.verifiedAccountIdentity,
209
373
  identityProofAvailable: identity.identityMatchFields.length > 0,
210
374
  identityMatchFields: identity.identityMatchFields,
@@ -213,6 +377,127 @@ export async function listSenders(input = {}) {
213
377
  }),
214
378
  };
215
379
  }
380
+ export async function setSenderCalendarLink(input) {
381
+ const candidate = input;
382
+ const record = candidate && typeof candidate === "object" && !Array.isArray(candidate)
383
+ ? candidate
384
+ : null;
385
+ const senderId = record && typeof record.senderId === "string"
386
+ ? record.senderId.trim()
387
+ : "";
388
+ const workspaceId = record && typeof record.workspaceId === "string"
389
+ ? record.workspaceId.trim()
390
+ : "";
391
+ const hasOwn = (key) => record !== null && Object.prototype.hasOwnProperty.call(record, key);
392
+ const valid = senderId.length > 0 &&
393
+ workspaceId.length > 0 &&
394
+ hasOwn("calendarLink") &&
395
+ isNullableString(record?.calendarLink) &&
396
+ hasOwn("expectedCalendarLink") &&
397
+ isNullableString(record?.expectedCalendarLink) &&
398
+ hasOwn("expectedCalendarLinkRevision") &&
399
+ isNullableString(record?.expectedCalendarLinkRevision) &&
400
+ (!hasOwn("force") || typeof record?.force === "boolean");
401
+ if (!valid || !record) {
402
+ return calendarLinkError({
403
+ code: "invalid_arguments",
404
+ message: "senderId, workspaceId, calendarLink, expectedCalendarLink, and expectedCalendarLinkRevision are required with the documented types.",
405
+ status: null,
406
+ authError: false,
407
+ uncertain: false,
408
+ currentCalendarLink: null,
409
+ currentCalendarLinkRevision: null,
410
+ guidance: "Read list_senders and retry with its current calendarLink and calendarLinkRevision values.",
411
+ });
412
+ }
413
+ const api = getApi();
414
+ try {
415
+ const rawReceipt = await api.put(`/api/v3/mcp/senders/${encodeURIComponent(senderId)}/calendar-link`, {
416
+ workspaceId,
417
+ calendarLink: record.calendarLink,
418
+ expectedCalendarLink: record.expectedCalendarLink,
419
+ expectedCalendarLinkRevision: record.expectedCalendarLinkRevision,
420
+ force: record.force === true,
421
+ }, workspaceRequestOptions(workspaceId));
422
+ if (!isSenderCalendarLinkReceipt(rawReceipt)) {
423
+ return calendarLinkError({
424
+ code: "malformed_sender_calendar_link_response",
425
+ message: "The calendar-link update returned an incomplete receipt, so the final state is uncertain.",
426
+ status: null,
427
+ authError: false,
428
+ uncertain: true,
429
+ currentCalendarLink: null,
430
+ currentCalendarLinkRevision: null,
431
+ guidance: "Reread list_senders and reconcile the sender state before retrying or forcing another write.",
432
+ });
433
+ }
434
+ return calendarLinkToolResult({
435
+ ok: true,
436
+ senderId,
437
+ workspaceId,
438
+ previousCalendarLink: rawReceipt.previousCalendarLink,
439
+ calendarLink: rawReceipt.calendarLink,
440
+ previousCalendarLinkRevision: rawReceipt.previousCalendarLinkRevision,
441
+ calendarLinkRevision: rawReceipt.calendarLinkRevision,
442
+ changed: rawReceipt.changed,
443
+ cleared: rawReceipt.cleared,
444
+ forced: rawReceipt.forced,
445
+ });
446
+ }
447
+ catch (error) {
448
+ if (error instanceof SellableApiError) {
449
+ const body = parseErrorBody(error.body);
450
+ const code = safeErrorCode(body?.code, `http_${error.status}`);
451
+ const isConflict = error.status === 409 && code === "calendar_link_conflict";
452
+ const uncertain = error.status >= 500;
453
+ const guidance = isConflict
454
+ ? "Reread list_senders, compare the current value, and use force only after the user explicitly authorizes overwriting it."
455
+ : uncertain
456
+ ? "Reread list_senders and reconcile the sender state before retrying; do not force while the result is uncertain."
457
+ : error.isAuthError
458
+ ? error.guidance || "Authenticate again and retry the same scoped request."
459
+ : error.status === 400 && code === "precondition_required"
460
+ ? "Reread list_senders and retry with all current comparison fields."
461
+ : error.status === 400
462
+ ? "Use an HTTP(S) booking URL or null, then retry with current comparison fields."
463
+ : error.status === 404
464
+ ? "Reread list_senders in the explicit workspace and select an existing sender."
465
+ : null;
466
+ return calendarLinkError({
467
+ code,
468
+ message: isConflict
469
+ ? "The sender calendar link changed after it was read."
470
+ : uncertain
471
+ ? "The calendar-link update did not complete with a confirmed result."
472
+ : error.isAuthError
473
+ ? "Sellable authentication or authorization failed."
474
+ : error.status === 404
475
+ ? "The sender was not found in the explicit workspace."
476
+ : "The sender calendar link was not updated.",
477
+ status: error.status,
478
+ authError: error.isAuthError,
479
+ uncertain,
480
+ currentCalendarLink: isConflict
481
+ ? canonicalConflictCalendarLink(body?.currentCalendarLink)
482
+ : null,
483
+ currentCalendarLinkRevision: isConflict
484
+ ? canonicalConflictRevision(body?.currentCalendarLinkRevision)
485
+ : null,
486
+ guidance,
487
+ });
488
+ }
489
+ return calendarLinkError({
490
+ code: "sender_calendar_link_uncertain",
491
+ message: "The calendar-link request ended without a confirmed response, so the final state is uncertain.",
492
+ status: null,
493
+ authError: false,
494
+ uncertain: true,
495
+ currentCalendarLink: null,
496
+ currentCalendarLinkRevision: null,
497
+ guidance: "Reread list_senders and reconcile the sender state before retrying; do not force while the result is uncertain.",
498
+ });
499
+ }
500
+ }
216
501
  export async function getSender(input) {
217
502
  const api = getApi();
218
503
  const requestOptions = workspaceRequestOptions(input.workspaceId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.613",
3
+ "version": "0.1.614",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",
@@ -255,6 +255,7 @@
255
255
  "set_engage_state",
256
256
  "set_engage_style_guide",
257
257
  "set_headline_icp_criteria",
258
+ "set_sender_calendar_link",
258
259
  "set_sender_routing",
259
260
  "setup_evergreen_campaigns",
260
261
  "start_campaign",