@sellable/mcp 0.1.879 → 0.1.881

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.
@@ -45,6 +45,7 @@ export declare const inboxToolDefinitions: ({
45
45
  type: string;
46
46
  };
47
47
  threadId?: undefined;
48
+ messageLimit?: undefined;
48
49
  versionId?: undefined;
49
50
  body?: undefined;
50
51
  approvedBody?: undefined;
@@ -69,6 +70,54 @@ export declare const inboxToolDefinitions: ({
69
70
  openWorldHint: boolean;
70
71
  idempotentHint?: undefined;
71
72
  };
73
+ } | {
74
+ name: string;
75
+ description: string;
76
+ inputSchema: {
77
+ type: string;
78
+ properties: {
79
+ threadId: {
80
+ type: string;
81
+ };
82
+ messageLimit: {
83
+ type: string;
84
+ minimum: number;
85
+ maximum: number;
86
+ description: string;
87
+ };
88
+ limit?: undefined;
89
+ cursor?: undefined;
90
+ filter?: undefined;
91
+ status?: undefined;
92
+ senderId?: undefined;
93
+ classification?: undefined;
94
+ search?: undefined;
95
+ includeEnrichment?: undefined;
96
+ includeReplyEligibility?: undefined;
97
+ versionId?: undefined;
98
+ body?: undefined;
99
+ approvedBody?: undefined;
100
+ approvedBodyHash?: undefined;
101
+ approvedThreadSnapshot?: undefined;
102
+ idempotencyKey?: undefined;
103
+ approval?: undefined;
104
+ workspaceId?: undefined;
105
+ message?: undefined;
106
+ approvedMessageHash?: undefined;
107
+ };
108
+ required: string[];
109
+ additionalProperties: boolean;
110
+ };
111
+ outputSchema: {
112
+ type: string;
113
+ additionalProperties: boolean;
114
+ };
115
+ annotations: {
116
+ readOnlyHint: boolean;
117
+ destructiveHint: boolean;
118
+ openWorldHint: boolean;
119
+ idempotentHint?: undefined;
120
+ };
72
121
  } | {
73
122
  name: string;
74
123
  description: string;
@@ -87,6 +136,7 @@ export declare const inboxToolDefinitions: ({
87
136
  search?: undefined;
88
137
  includeEnrichment?: undefined;
89
138
  includeReplyEligibility?: undefined;
139
+ messageLimit?: undefined;
90
140
  versionId?: undefined;
91
141
  body?: undefined;
92
142
  approvedBody?: undefined;
@@ -135,6 +185,7 @@ export declare const inboxToolDefinitions: ({
135
185
  search?: undefined;
136
186
  includeEnrichment?: undefined;
137
187
  includeReplyEligibility?: undefined;
188
+ messageLimit?: undefined;
138
189
  approvedBody?: undefined;
139
190
  approvedBodyHash?: undefined;
140
191
  approvedThreadSnapshot?: undefined;
@@ -216,6 +267,7 @@ export declare const inboxToolDefinitions: ({
216
267
  search?: undefined;
217
268
  includeEnrichment?: undefined;
218
269
  includeReplyEligibility?: undefined;
270
+ messageLimit?: undefined;
219
271
  body?: undefined;
220
272
  message?: undefined;
221
273
  approvedMessageHash?: undefined;
@@ -289,6 +341,7 @@ export declare const inboxToolDefinitions: ({
289
341
  search?: undefined;
290
342
  includeEnrichment?: undefined;
291
343
  includeReplyEligibility?: undefined;
344
+ messageLimit?: undefined;
292
345
  versionId?: undefined;
293
346
  body?: undefined;
294
347
  approvedBody?: undefined;
@@ -309,7 +362,51 @@ export declare const inboxToolDefinitions: ({
309
362
  };
310
363
  })[];
311
364
  export declare function searchInboxThreads(input: Record<string, unknown>): Promise<ToolResult<{
365
+ ok: boolean;
366
+ error: string;
367
+ message: string;
368
+ }> | ToolResult<{
312
369
  routeCalled: string;
370
+ threads: {
371
+ id: string | null;
372
+ leadName: string | null;
373
+ leadProfileUrl: string | null;
374
+ senderName: string | null;
375
+ senderProviderId: string | null;
376
+ chatType: string | null;
377
+ classification: string | null;
378
+ latestMessage: {
379
+ id: string | null;
380
+ direction: string | null;
381
+ timestamp: string | null;
382
+ preview: string | null;
383
+ } | null;
384
+ draft: {
385
+ id: string | null;
386
+ versionId: string | null;
387
+ status: string | null;
388
+ versionNumber: number | null;
389
+ createdAt: string | null;
390
+ } | null;
391
+ enrichment: Record<string, unknown> | null;
392
+ campaign: Record<string, unknown> | null;
393
+ latestMessageTimestamp: string | null;
394
+ updatedAt: string | null;
395
+ hasUnprocessedInbound: boolean | null;
396
+ lastInboundMessageAt: string | null;
397
+ followUpsSuppressed: boolean | null;
398
+ replyEligibility: {
399
+ canSendDraft: boolean | null;
400
+ canSendManual: boolean | null;
401
+ reasonCodes: (string | null)[];
402
+ } | null;
403
+ }[];
404
+ pagination: {} | null;
405
+ totalCount: {};
406
+ filters: {} | null;
407
+ status: {} | null;
408
+ filter: {} | null;
409
+ resultShape: string;
313
410
  }>>;
314
411
  export declare function getInboxThread(input: Record<string, unknown>): Promise<ToolResult<any>>;
315
412
  export declare function checkInboxReplyEligibility(input: Record<string, unknown>): Promise<ToolResult<any>>;
@@ -1,5 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { getApi, SellableApiError } from "../api.js";
3
+ const MAX_INBOX_TOOL_RESULT_BYTES = 128 * 1024;
4
+ const MAX_INBOX_SEARCH_LIMIT = 25;
5
+ const MAX_INBOX_DETAIL_MESSAGES = 50;
3
6
  const snapshotSchema = {
4
7
  type: "object",
5
8
  properties: {
@@ -17,11 +20,15 @@ const resultOutputSchema = {
17
20
  export const inboxToolDefinitions = [
18
21
  {
19
22
  name: "search_inbox_threads",
20
- description: "Search LinkedIn inbox threads using the existing Sellable product inbox route. This calls GET /api/v3/inbox and returns thread summaries for the active workspace.",
23
+ description: "Search LinkedIn inbox threads using the existing Sellable product inbox route. Returns bounded compact summaries for the active workspace; use get_inbox_thread only for a relevant thread.",
21
24
  inputSchema: {
22
25
  type: "object",
23
26
  properties: {
24
- limit: { type: "number", minimum: 1, maximum: 50 },
27
+ limit: {
28
+ type: "number",
29
+ minimum: 1,
30
+ maximum: MAX_INBOX_SEARCH_LIMIT,
31
+ },
25
32
  cursor: { type: "string" },
26
33
  filter: { type: "string", enum: ["all", "drafts"] },
27
34
  status: {
@@ -46,11 +53,17 @@ export const inboxToolDefinitions = [
46
53
  },
47
54
  {
48
55
  name: "get_inbox_thread",
49
- description: "Fetch one LinkedIn inbox thread using the existing Sellable product route GET /api/v3/inbox/[threadId]. Returns the product payload plus a chronological transcript for MCP review.",
56
+ description: "Fetch one LinkedIn inbox thread using the existing Sellable product route GET /api/v3/inbox/[threadId]. Returns bounded thread detail plus a recent chronological transcript for MCP review.",
50
57
  inputSchema: {
51
58
  type: "object",
52
59
  properties: {
53
60
  threadId: { type: "string" },
61
+ messageLimit: {
62
+ type: "number",
63
+ minimum: 1,
64
+ maximum: MAX_INBOX_DETAIL_MESSAGES,
65
+ description: "Maximum recent messages to return in chronological order (default 25, max 50).",
66
+ },
54
67
  },
55
68
  required: ["threadId"],
56
69
  additionalProperties: false,
@@ -187,6 +200,145 @@ function textResult(structuredContent, text, isError = false) {
187
200
  function errorResult(error, message, extra = {}) {
188
201
  return textResult({ ok: false, error, message, ...extra }, message, true);
189
202
  }
203
+ function utf8Bytes(value) {
204
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
205
+ }
206
+ function boundedString(value, maxChars) {
207
+ if (typeof value !== "string")
208
+ return null;
209
+ return value.length <= maxChars
210
+ ? value
211
+ : `${value.slice(0, Math.max(0, maxChars - 1))}…`;
212
+ }
213
+ function asRecord(value) {
214
+ return value && typeof value === "object" && !Array.isArray(value)
215
+ ? value
216
+ : null;
217
+ }
218
+ function compactRecord(value, fields) {
219
+ const source = asRecord(value);
220
+ if (!source)
221
+ return null;
222
+ const result = {};
223
+ for (const [field, maxChars] of fields) {
224
+ const fieldValue = source[field];
225
+ if (typeof fieldValue === "string") {
226
+ result[field] = boundedString(fieldValue, maxChars);
227
+ }
228
+ else if (typeof fieldValue === "number" ||
229
+ typeof fieldValue === "boolean" ||
230
+ fieldValue === null) {
231
+ result[field] = fieldValue;
232
+ }
233
+ }
234
+ return Object.keys(result).length > 0 ? result : null;
235
+ }
236
+ function compactThreadSummary(value) {
237
+ const thread = asRecord(value) ?? {};
238
+ const messages = Array.isArray(thread.messages) ? thread.messages : [];
239
+ const latestMessage = asRecord(messages[0]);
240
+ const draft = asRecord(thread.draft);
241
+ const replyEligibility = asRecord(thread.replyEligibility);
242
+ return {
243
+ id: boundedString(thread.id, 256),
244
+ leadName: boundedString(thread.leadName, 256),
245
+ leadProfileUrl: boundedString(thread.leadProfileUrl, 1_024),
246
+ senderName: boundedString(thread.senderName, 256),
247
+ senderProviderId: boundedString(thread.senderProviderId, 256),
248
+ chatType: boundedString(thread.chatType, 32),
249
+ classification: boundedString(thread.classification, 128),
250
+ latestMessage: latestMessage
251
+ ? {
252
+ id: boundedString(latestMessage.id, 256),
253
+ direction: boundedString(latestMessage.direction, 32),
254
+ timestamp: normalizeTimestamp(latestMessage.timestamp),
255
+ preview: boundedString(latestMessage.message ?? latestMessage.body, 500),
256
+ }
257
+ : null,
258
+ draft: draft
259
+ ? {
260
+ id: boundedString(draft.id, 256),
261
+ versionId: boundedString(draft.versionId, 256),
262
+ status: boundedString(draft.status, 64),
263
+ versionNumber: typeof draft.versionNumber === "number"
264
+ ? draft.versionNumber
265
+ : null,
266
+ createdAt: normalizeTimestamp(draft.createdAt),
267
+ }
268
+ : null,
269
+ enrichment: compactRecord(thread.enrichment, [
270
+ ["name", 256],
271
+ ["title", 256],
272
+ ["company", 256],
273
+ ["companyWebsiteUrl", 1_024],
274
+ ["location", 256],
275
+ ["linkedinUrl", 1_024],
276
+ ]),
277
+ campaign: compactRecord(thread.campaign, [
278
+ ["id", 256],
279
+ ["name", 256],
280
+ ]),
281
+ latestMessageTimestamp: normalizeTimestamp(thread.latestMessageTimestamp),
282
+ updatedAt: normalizeTimestamp(thread.updatedAt),
283
+ hasUnprocessedInbound: typeof thread.hasUnprocessedInbound === "boolean"
284
+ ? thread.hasUnprocessedInbound
285
+ : null,
286
+ lastInboundMessageAt: normalizeTimestamp(thread.lastInboundMessageAt),
287
+ followUpsSuppressed: typeof thread.followUpsSuppressed === "boolean"
288
+ ? thread.followUpsSuppressed
289
+ : null,
290
+ replyEligibility: replyEligibility
291
+ ? {
292
+ canSendDraft: typeof replyEligibility.canSendDraft === "boolean"
293
+ ? replyEligibility.canSendDraft
294
+ : null,
295
+ canSendManual: typeof replyEligibility.canSendManual === "boolean"
296
+ ? replyEligibility.canSendManual
297
+ : null,
298
+ reasonCodes: Array.isArray(replyEligibility.reasonCodes)
299
+ ? replyEligibility.reasonCodes
300
+ .slice(0, 20)
301
+ .map((reason) => boundedString(reason, 128))
302
+ : [],
303
+ }
304
+ : null,
305
+ };
306
+ }
307
+ function compactThreadDetail(thread) {
308
+ if (!thread)
309
+ return null;
310
+ const source = thread;
311
+ const draft = asRecord(source.draft);
312
+ return {
313
+ ...compactThreadSummary(source),
314
+ messageOrder: boundedString(source.messageOrder, 32),
315
+ draft: draft
316
+ ? {
317
+ id: boundedString(draft.id, 256),
318
+ versionId: boundedString(draft.versionId, 256),
319
+ draftId: boundedString(draft.draftId, 256),
320
+ body: boundedString(draft.body, 16_000),
321
+ subject: boundedString(draft.subject, 2_000),
322
+ status: boundedString(draft.status, 64),
323
+ versionNumber: typeof draft.versionNumber === "number"
324
+ ? draft.versionNumber
325
+ : null,
326
+ createdAt: normalizeTimestamp(draft.createdAt),
327
+ }
328
+ : null,
329
+ };
330
+ }
331
+ function compactTranscriptMessage(value) {
332
+ const message = asRecord(value) ?? {};
333
+ return {
334
+ id: boundedString(message.id, 256),
335
+ message: boundedString(message.message ?? message.body, 8_000),
336
+ subject: boundedString(message.subject, 2_000),
337
+ timestamp: normalizeTimestamp(message.timestamp),
338
+ direction: boundedString(message.direction, 32),
339
+ senderProviderId: boundedString(message.senderProviderId, 256),
340
+ };
341
+ }
190
342
  function requireString(value, field) {
191
343
  if (typeof value !== "string" || value.trim() === "") {
192
344
  return errorResult("invalid_arguments", `${field} is required.`);
@@ -253,6 +405,39 @@ function chronologicalTranscript(thread) {
253
405
  return aTime - bTime;
254
406
  });
255
407
  }
408
+ function boundedTranscriptResult(input) {
409
+ const chronological = chronologicalTranscript(input.thread);
410
+ const selected = chronological
411
+ .slice(-input.messageLimit)
412
+ .map(compactTranscriptMessage);
413
+ const compactThread = compactThreadDetail(input.thread);
414
+ const acceptedChronological = [];
415
+ for (const message of [...selected].reverse()) {
416
+ const candidate = [message, ...acceptedChronological];
417
+ const payload = {
418
+ routeCalled: input.routeCalled,
419
+ thread: compactThread,
420
+ messageOrder: "chronological",
421
+ chronologicalTranscript: candidate,
422
+ messagesAvailable: chronological.length,
423
+ messagesReturned: candidate.length,
424
+ transcriptTruncated: candidate.length < chronological.length,
425
+ };
426
+ if (utf8Bytes(payload) > MAX_INBOX_TOOL_RESULT_BYTES)
427
+ break;
428
+ acceptedChronological.unshift(message);
429
+ }
430
+ const transcript = acceptedChronological;
431
+ return {
432
+ routeCalled: input.routeCalled,
433
+ thread: compactThread,
434
+ messageOrder: "chronological",
435
+ chronologicalTranscript: transcript,
436
+ messagesAvailable: chronological.length,
437
+ messagesReturned: transcript.length,
438
+ transcriptTruncated: transcript.length < chronological.length,
439
+ };
440
+ }
256
441
  function routeError(error, fallback) {
257
442
  if (error instanceof SellableApiError) {
258
443
  let parsed = null;
@@ -278,7 +463,7 @@ function appendParam(params, key, value) {
278
463
  export async function searchInboxThreads(input) {
279
464
  const params = new URLSearchParams();
280
465
  const limit = typeof input.limit === "number" && Number.isFinite(input.limit)
281
- ? Math.max(1, Math.min(Math.trunc(input.limit), 50))
466
+ ? Math.max(1, Math.min(Math.trunc(input.limit), MAX_INBOX_SEARCH_LIMIT))
282
467
  : undefined;
283
468
  appendParam(params, "limit", limit);
284
469
  appendParam(params, "cursor", input.cursor);
@@ -296,11 +481,23 @@ export async function searchInboxThreads(input) {
296
481
  const query = params.toString();
297
482
  const routeCalled = `/api/v3/inbox${query ? `?${query}` : ""}`;
298
483
  const response = await getApi().get(routeCalled);
299
- const threads = Array.isArray(response.threads) ? response.threads : [];
300
- return textResult({
484
+ const threads = Array.isArray(response.threads)
485
+ ? response.threads.map(compactThreadSummary)
486
+ : [];
487
+ const structuredContent = {
301
488
  routeCalled,
302
- ...response,
303
- }, `${threads.length} inbox thread${threads.length === 1 ? "" : "s"} returned.`);
489
+ threads,
490
+ pagination: response.pagination ?? null,
491
+ totalCount: response.totalCount ?? threads.length,
492
+ filters: response.filters ?? null,
493
+ status: response.status ?? null,
494
+ filter: response.filter ?? null,
495
+ resultShape: "compact_thread_summaries",
496
+ };
497
+ if (utf8Bytes(structuredContent) > MAX_INBOX_TOOL_RESULT_BYTES) {
498
+ return errorResult("inbox_search_result_too_large", "Inbox summaries exceeded the MCP response budget. Narrow the filters or page size.", { routeCalled, returnedCount: threads.length });
499
+ }
500
+ return textResult(structuredContent, `${threads.length} inbox thread${threads.length === 1 ? "" : "s"} returned.`);
304
501
  }
305
502
  export async function getInboxThread(input) {
306
503
  const required = requireStrings(input, ["threadId"]);
@@ -310,12 +507,16 @@ export async function getInboxThread(input) {
310
507
  const routeCalled = `/api/v3/inbox/${encodeURIComponent(threadId)}`;
311
508
  const response = await getApi().get(routeCalled);
312
509
  const thread = getThread(response);
313
- return textResult({
510
+ const messageLimit = typeof input.messageLimit === "number" &&
511
+ Number.isFinite(input.messageLimit)
512
+ ? Math.max(1, Math.min(Math.trunc(input.messageLimit), MAX_INBOX_DETAIL_MESSAGES))
513
+ : 25;
514
+ const structuredContent = boundedTranscriptResult({
314
515
  routeCalled,
315
516
  thread,
316
- messageOrder: thread?.messageOrder ?? "route_order",
317
- chronologicalTranscript: chronologicalTranscript(thread),
318
- }, thread
517
+ messageLimit,
518
+ });
519
+ return textResult(structuredContent, thread
319
520
  ? `Loaded inbox thread ${thread.id ?? threadId}.`
320
521
  : `Loaded inbox thread ${threadId}.`);
321
522
  }
@@ -167,6 +167,10 @@ export declare const opportunitySequenceToolDefinitions: readonly [{
167
167
  readonly type: "string";
168
168
  readonly format: "date-time";
169
169
  };
170
+ readonly voicemail_policy: {
171
+ readonly type: "string";
172
+ readonly enum: readonly ["context_only_max_15s_then_email", "context_social_proof_max_30s_then_email", "do_not_leave"];
173
+ };
170
174
  readonly subject: {
171
175
  readonly type: "string";
172
176
  readonly minLength: 1;
@@ -105,6 +105,14 @@ export const opportunitySequenceToolDefinitions = [
105
105
  additionalProperties: false,
106
106
  },
107
107
  due_at: { type: "string", format: "date-time" },
108
+ voicemail_policy: {
109
+ type: "string",
110
+ enum: [
111
+ "context_only_max_15s_then_email",
112
+ "context_social_proof_max_30s_then_email",
113
+ "do_not_leave",
114
+ ],
115
+ },
108
116
  subject: { type: "string", minLength: 1, maxLength: 200 },
109
117
  body: { type: "string", minLength: 1, maxLength: 4000 },
110
118
  rationale_summary: {
@@ -9569,6 +9569,12 @@ export declare const allTools: ({
9569
9569
  threadId: {
9570
9570
  type: string;
9571
9571
  };
9572
+ messageLimit: {
9573
+ type: string;
9574
+ minimum: number;
9575
+ maximum: number;
9576
+ description: string;
9577
+ };
9572
9578
  limit?: undefined;
9573
9579
  cursor?: undefined;
9574
9580
  filter?: undefined;
@@ -9602,6 +9608,49 @@ export declare const allTools: ({
9602
9608
  openWorldHint: boolean;
9603
9609
  idempotentHint?: undefined;
9604
9610
  };
9611
+ } | {
9612
+ name: string;
9613
+ description: string;
9614
+ inputSchema: {
9615
+ type: string;
9616
+ properties: {
9617
+ threadId: {
9618
+ type: string;
9619
+ };
9620
+ limit?: undefined;
9621
+ cursor?: undefined;
9622
+ filter?: undefined;
9623
+ status?: undefined;
9624
+ senderId?: undefined;
9625
+ classification?: undefined;
9626
+ search?: undefined;
9627
+ includeEnrichment?: undefined;
9628
+ includeReplyEligibility?: undefined;
9629
+ messageLimit?: undefined;
9630
+ versionId?: undefined;
9631
+ body?: undefined;
9632
+ approvedBody?: undefined;
9633
+ approvedBodyHash?: undefined;
9634
+ approvedThreadSnapshot?: undefined;
9635
+ idempotencyKey?: undefined;
9636
+ approval?: undefined;
9637
+ workspaceId?: undefined;
9638
+ message?: undefined;
9639
+ approvedMessageHash?: undefined;
9640
+ };
9641
+ required: string[];
9642
+ additionalProperties: boolean;
9643
+ };
9644
+ outputSchema: {
9645
+ type: string;
9646
+ additionalProperties: boolean;
9647
+ };
9648
+ annotations: {
9649
+ readOnlyHint: boolean;
9650
+ destructiveHint: boolean;
9651
+ openWorldHint: boolean;
9652
+ idempotentHint?: undefined;
9653
+ };
9605
9654
  } | {
9606
9655
  name: string;
9607
9656
  description: string;
@@ -9626,6 +9675,7 @@ export declare const allTools: ({
9626
9675
  search?: undefined;
9627
9676
  includeEnrichment?: undefined;
9628
9677
  includeReplyEligibility?: undefined;
9678
+ messageLimit?: undefined;
9629
9679
  approvedBody?: undefined;
9630
9680
  approvedBodyHash?: undefined;
9631
9681
  approvedThreadSnapshot?: undefined;
@@ -9707,6 +9757,7 @@ export declare const allTools: ({
9707
9757
  search?: undefined;
9708
9758
  includeEnrichment?: undefined;
9709
9759
  includeReplyEligibility?: undefined;
9760
+ messageLimit?: undefined;
9710
9761
  body?: undefined;
9711
9762
  message?: undefined;
9712
9763
  approvedMessageHash?: undefined;
@@ -9780,6 +9831,7 @@ export declare const allTools: ({
9780
9831
  search?: undefined;
9781
9832
  includeEnrichment?: undefined;
9782
9833
  includeReplyEligibility?: undefined;
9834
+ messageLimit?: undefined;
9783
9835
  versionId?: undefined;
9784
9836
  body?: undefined;
9785
9837
  approvedBody?: undefined;
@@ -10339,6 +10391,10 @@ export declare const allTools: ({
10339
10391
  readonly type: "string";
10340
10392
  readonly format: "date-time";
10341
10393
  };
10394
+ readonly voicemail_policy: {
10395
+ readonly type: "string";
10396
+ readonly enum: readonly ["context_only_max_15s_then_email", "context_social_proof_max_30s_then_email", "do_not_leave"];
10397
+ };
10342
10398
  readonly subject: {
10343
10399
  readonly type: "string";
10344
10400
  readonly minLength: 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.879",
3
+ "version": "0.1.881",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code, Codex, and Hermes campaign workflows",
6
6
  "main": "dist/index.js",