@sendly/mcp 2.0.1 → 2.2.0

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.
Files changed (2) hide show
  1. package/dist/index.js +63 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -439,6 +439,67 @@ function registerAllTools(server2, api2) {
439
439
  }
440
440
  }
441
441
  );
442
+ server2.tool(
443
+ "mark_contact_valid",
444
+ "Clear the invalid flag on a contact so future campaigns include it again. Contacts get auto-flagged as invalid when a send fails with a terminal bad-number error (landline, invalid number) or when a carrier lookup reports they can't receive SMS. Use this when you disagree with the auto-flag.",
445
+ {
446
+ contactId: z.string().describe("The contact ID to clear the invalid flag on")
447
+ },
448
+ async ({ contactId }) => {
449
+ try {
450
+ return ok(await api2("POST", `/contacts/${contactId}/mark-valid`));
451
+ } catch (e) {
452
+ return err(e);
453
+ }
454
+ }
455
+ );
456
+ server2.tool(
457
+ "check_contact_numbers",
458
+ "Trigger a background carrier lookup across contacts. Landlines and other non-SMS-capable numbers are auto-excluded from future campaigns. The lookup runs asynchronously (1-5 minutes). Results populate line_type, carrier_name, and invalid_reason fields on affected contacts. Idempotent: re-triggering while a lookup is running for the same scope is a no-op \u2014 response carries alreadyRunning: true in that case.",
459
+ {
460
+ listId: z.string().optional().describe("Scope the lookup to a single contact list"),
461
+ force: z.boolean().optional().describe("Re-check contacts even if already looked up (default: false)")
462
+ },
463
+ async ({ listId, force }) => {
464
+ try {
465
+ return ok(
466
+ await api2("POST", "/contacts/lookup", {
467
+ listId: listId ?? null,
468
+ force: force ?? false
469
+ })
470
+ );
471
+ } catch (e) {
472
+ return err(e);
473
+ }
474
+ }
475
+ );
476
+ server2.tool(
477
+ "bulk_mark_contacts_valid",
478
+ "Clear the invalid flag on many contacts at once (up to 10,000 per call). Pass either an explicit id array OR a listId \u2014 not both. Foreign ids silently no-op via the per-organization filter. Returns { cleared } \u2014 the number of contacts whose flag was actually cleared.",
479
+ {
480
+ ids: z.array(z.string()).max(1e4).optional().describe("Explicit contact ids to clear (max 10,000)"),
481
+ listId: z.string().optional().describe("Clear every flagged member of this list")
482
+ },
483
+ async ({ ids, listId }) => {
484
+ if (!ids && !listId) {
485
+ return err("bulk_mark_contacts_valid requires either 'ids' or 'listId'");
486
+ }
487
+ if (ids && listId) {
488
+ return err("bulk_mark_contacts_valid accepts 'ids' OR 'listId', not both");
489
+ }
490
+ try {
491
+ return ok(
492
+ await api2(
493
+ "POST",
494
+ "/contacts/bulk-mark-valid",
495
+ ids ? { ids } : { listId }
496
+ )
497
+ );
498
+ } catch (e) {
499
+ return err(e);
500
+ }
501
+ }
502
+ );
442
503
  server2.tool(
443
504
  "import_contacts",
444
505
  "Bulk import contacts from an array. Optionally add all imported contacts to a list. Returns created/updated/skipped counts.",
@@ -1396,7 +1457,8 @@ async function api(method, path, body, query) {
1396
1457
  }
1397
1458
  }
1398
1459
  const headers = {
1399
- Authorization: `Bearer ${API_KEY}`
1460
+ Authorization: `Bearer ${API_KEY}`,
1461
+ "User-Agent": "@sendly/mcp/2.0.2"
1400
1462
  };
1401
1463
  if (body) headers["Content-Type"] = "application/json";
1402
1464
  const res = await fetch(url.toString(), {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendly/mcp",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "Sendly MCP Server — Full SMS platform for AI agents. Messaging, contacts, campaigns, templates, webhooks, OTP verification, and more.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",