@usethrottle/mcp 0.1.0 → 0.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.
package/dist/mcp.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync } from "fs";
5
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
5
 
7
6
  // ../errors/dist/index.js
@@ -504,6 +503,116 @@ var ACTIONS = [
504
503
  requests: [{ method: "GET", path: "/api/v1/invoices/aging" }],
505
504
  channels: ["mcp", "zapier", "make", "n8n"]
506
505
  },
506
+ // --------------------------------------------------------------- discounts
507
+ {
508
+ id: "search_discounts",
509
+ title: "Search discounts",
510
+ description: "List discount codes with optional filters \u2014 type, active state, and creation date range.",
511
+ kind: "read",
512
+ scopes: ["discounts:read"],
513
+ input: [
514
+ { name: "q", type: "string", required: false, description: "Free-text search." },
515
+ { name: "code", type: "string", required: false, description: "Exact code match." },
516
+ {
517
+ name: "type",
518
+ type: "string",
519
+ required: false,
520
+ description: "Filter by discount type.",
521
+ enum: ["percentage", "fixed_amount"]
522
+ },
523
+ { name: "isActive", type: "boolean", required: false, description: "Filter by active state." },
524
+ { name: "createdAfter", type: "string", required: false, description: "ISO 8601 date lower bound." },
525
+ { name: "createdBefore", type: "string", required: false, description: "ISO 8601 date upper bound." },
526
+ { name: "limit", type: "number", required: false, description: "Page size." },
527
+ { name: "cursor", type: "string", required: false, description: "Pagination cursor." }
528
+ ],
529
+ requests: [{ method: "GET", path: "/api/v1/discounts" }],
530
+ channels: ["mcp", "zapier", "make", "n8n"]
531
+ },
532
+ {
533
+ id: "get_discount",
534
+ title: "Get one discount",
535
+ description: "Read a single discount code: value, conditions, usage limits and redemption count.",
536
+ kind: "read",
537
+ scopes: ["discounts:read"],
538
+ input: [{ name: "id", type: "string", required: true, description: "Discount id." }],
539
+ requests: [{ method: "GET", path: "/api/v1/discounts/:id" }],
540
+ channels: ["mcp", "zapier", "make", "n8n"]
541
+ },
542
+ // ---------------------------------------------------------------- payments
543
+ {
544
+ id: "search_payments",
545
+ title: "Search payments",
546
+ description: "List payments with optional filters \u2014 method, status, order, amount range, or a Net-N overdue bucket. Today payments are only reachable nested inside get_order; this is the standalone entry point.",
547
+ kind: "read",
548
+ scopes: ["payments:read"],
549
+ input: [
550
+ { name: "method", type: "string", required: false, description: "Filter by payment method." },
551
+ { name: "status", type: "string", required: false, description: "Filter by payment status." },
552
+ { name: "orderId", type: "string", required: false, description: "Filter to one order." },
553
+ {
554
+ name: "aging",
555
+ type: "string",
556
+ required: false,
557
+ description: "Overdue bucket filter, Net-N payments only.",
558
+ enum: ["0_30", "31_60", "61_90", "90_plus"]
559
+ },
560
+ { name: "dueFrom", type: "string", required: false, description: "Due-date range start, YYYY-MM-DD." },
561
+ { name: "dueTo", type: "string", required: false, description: "Due-date range end, YYYY-MM-DD." },
562
+ { name: "amountMin", type: "number", required: false, description: "Minimum amount, minor units." },
563
+ { name: "amountMax", type: "number", required: false, description: "Maximum amount, minor units." },
564
+ { name: "limit", type: "number", required: false, description: "Page size." },
565
+ { name: "cursor", type: "string", required: false, description: "Pagination cursor." }
566
+ ],
567
+ requests: [{ method: "GET", path: "/api/v1/payments" }],
568
+ channels: ["mcp", "zapier", "make", "n8n"]
569
+ },
570
+ {
571
+ id: "get_payment",
572
+ title: "Get one payment with its transactions",
573
+ description: "Read a single payment plus its full transaction history (authorize/capture/refund/void attempts).",
574
+ kind: "read",
575
+ scopes: ["payments:read"],
576
+ input: [{ name: "id", type: "string", required: true, description: "Payment id." }],
577
+ requests: [
578
+ { method: "GET", path: "/api/v1/payments/:id" },
579
+ { method: "GET", path: "/api/v1/payments/:id/transactions" }
580
+ ],
581
+ channels: ["mcp"],
582
+ guardrails: [
583
+ "The transactions sub-read needs its own read of the same payment scope; report it as unavailable rather than failing the whole call if the underlying request errors."
584
+ ]
585
+ },
586
+ // ---------------------------------------------------------------- invoices
587
+ {
588
+ id: "get_invoice",
589
+ title: "Get one invoice",
590
+ description: "Read a single issued invoice: line items, totals, status and the billing snapshot at issue time.",
591
+ kind: "read",
592
+ scopes: ["invoices:read"],
593
+ input: [{ name: "id", type: "string", required: true, description: "Invoice id." }],
594
+ requests: [{ method: "GET", path: "/api/v1/invoices/:id" }],
595
+ channels: ["mcp", "zapier", "make", "n8n"]
596
+ },
597
+ // ------------------------------------------------------------------ carts
598
+ {
599
+ id: "list_abandoned_carts",
600
+ title: "List abandoned carts",
601
+ description: "Carts that were started but never finalized, with recovery status \u2014 the abandoned-cart recovery list.",
602
+ kind: "read",
603
+ scopes: ["carts:read"],
604
+ input: [
605
+ { name: "sinceDays", type: "number", required: false, description: "How many days back to look." },
606
+ { name: "limit", type: "number", required: false, description: "Page size." },
607
+ { name: "cursor", type: "string", required: false, description: "Pagination cursor." }
608
+ ],
609
+ requests: [
610
+ { method: "GET", path: "/api/v1/abandoned-carts" },
611
+ { method: "GET", path: "/api/v1/abandoned-carts/summary" }
612
+ ],
613
+ channels: ["mcp", "zapier", "make", "n8n"],
614
+ guardrails: ["Report the summary as unavailable rather than failing the whole call if that sub-read errors."]
615
+ },
507
616
  // ---------------------------------------------------------------- debugging
508
617
  {
509
618
  id: "search_events",
@@ -526,7 +635,7 @@ var ACTIONS = [
526
635
  title: "List webhook deliveries",
527
636
  description: "Recent outbound webhook deliveries, filterable by endpoint and status.",
528
637
  kind: "read",
529
- scopes: ["webhooks:read"],
638
+ scopes: ["webhook_deliveries:read"],
530
639
  input: [
531
640
  { name: "endpointId", type: "string", required: false, description: "Filter by endpoint." },
532
641
  { name: "status", type: "string", required: false, description: "Filter by delivery status." },
@@ -540,7 +649,7 @@ var ACTIONS = [
540
649
  title: "Get one webhook delivery",
541
650
  description: "Full delivery record: the signed payload we sent, the response we got back, and every attempt.",
542
651
  kind: "read",
543
- scopes: ["webhooks:read"],
652
+ scopes: ["webhook_deliveries:read"],
544
653
  input: [{ name: "id", type: "string", required: true, description: "Delivery id." }],
545
654
  requests: [{ method: "GET", path: "/api/v1/webhook-deliveries/:id" }],
546
655
  channels: ["mcp", "n8n"]
@@ -570,6 +679,46 @@ var ACTIONS = [
570
679
  "Report which sections were unavailable rather than failing the call when a sub-read is unscoped."
571
680
  ]
572
681
  },
682
+ // ------------------------------------------------------------- extensions
683
+ {
684
+ id: "list_extension_installations",
685
+ title: "List installed extensions",
686
+ description: "Extensions installed on this application, with status and configuration.",
687
+ kind: "read",
688
+ scopes: ["extensions:read"],
689
+ input: [
690
+ {
691
+ name: "status",
692
+ type: "string",
693
+ required: false,
694
+ description: "Filter by installation status.",
695
+ enum: ["active", "suspended", "uninstalled"]
696
+ },
697
+ { name: "limit", type: "number", required: false, description: "Page size." },
698
+ { name: "cursor", type: "string", required: false, description: "Pagination cursor." }
699
+ ],
700
+ requests: [{ method: "GET", path: "/api/v1/installations" }],
701
+ channels: ["mcp", "zapier", "make", "n8n"]
702
+ },
703
+ // ------------------------------------------------------------- connectors
704
+ {
705
+ id: "get_connector_routing",
706
+ title: "Payment routing and decline rules",
707
+ description: "Read the payment-routing cascade, active routing connectors, and card/other decline rules \u2014 what determines which processor handles a payment and what happens on a decline.",
708
+ kind: "read",
709
+ scopes: ["connectors:read"],
710
+ input: [],
711
+ requests: [
712
+ { method: "GET", path: "/api/v1/payment-routing" },
713
+ { method: "GET", path: "/api/v1/routing-connectors" },
714
+ { method: "GET", path: "/api/v1/card-decline" },
715
+ { method: "GET", path: "/api/v1/other-decline" }
716
+ ],
717
+ channels: ["mcp"],
718
+ guardrails: [
719
+ "Every sub-read proxies live to the payment provider's routing API and none are guaranteed to succeed together; report any that fail as unavailable rather than failing the whole call."
720
+ ]
721
+ },
573
722
  {
574
723
  id: "get_integration_status",
575
724
  title: "Integration readiness",
@@ -588,6 +737,64 @@ var ACTIONS = [
588
737
  'Each section needs its own scope (connectors:read, webhooks:read). Report a section as "not readable with this credential" instead of failing.'
589
738
  ]
590
739
  },
740
+ // --------------------------------------------------------------- workspace
741
+ {
742
+ id: "get_workspace_settings",
743
+ title: "Get workspace settings",
744
+ description: "Read this workspace's identity and settings: name, slug, branding, onboarding state, and the default Net-N payment terms.",
745
+ kind: "read",
746
+ scopes: ["merchant:read"],
747
+ input: [],
748
+ requests: [{ method: "GET", path: "/api/v1/merchants/me" }],
749
+ channels: ["mcp", "zapier", "make", "n8n"]
750
+ },
751
+ {
752
+ id: "get_audit_log",
753
+ title: "Get the workspace audit log",
754
+ description: "Recent admin actions taken on this workspace \u2014 invites, billing changes, key rotation, suspensions.",
755
+ kind: "read",
756
+ scopes: ["audit_logs:read"],
757
+ input: [
758
+ { name: "limit", type: "number", required: false, description: "Maximum entries (1-200).", default: 100 }
759
+ ],
760
+ requests: [{ method: "GET", path: "/api/v1/audit-log" }],
761
+ channels: ["mcp", "zapier", "make", "n8n"]
762
+ },
763
+ {
764
+ id: "get_business_summary",
765
+ title: "Business summary",
766
+ description: "How are we doing? Orders/revenue, subscription MRR, receivables and quotes pipeline for a week/month/quarter, always compared to the immediately preceding period of equal length.",
767
+ kind: "read",
768
+ scopes: [],
769
+ input: [
770
+ {
771
+ name: "period",
772
+ type: "string",
773
+ required: false,
774
+ description: "Reporting window.",
775
+ enum: ["week", "month", "quarter"],
776
+ default: "month"
777
+ }
778
+ ],
779
+ requests: [{ method: "GET", path: "/api/v1/analytics/business-summary" }],
780
+ channels: ["mcp", "zapier", "make", "n8n"],
781
+ guardrails: [
782
+ "Each section needs its own scope (orders:read, subscriptions:read, invoices:read, quotes:read). A section without its scope is reported as omitted rather than failing the whole call."
783
+ ]
784
+ },
785
+ {
786
+ id: "get_attention_digest",
787
+ title: "Attention digest",
788
+ description: "What needs attention right now? Open disputes, subscriptions in dunning, overdue Net-N invoices, and webhooks with degraded delivery health \u2014 a point-in-time snapshot, not a time range.",
789
+ kind: "read",
790
+ scopes: [],
791
+ input: [],
792
+ requests: [{ method: "GET", path: "/api/v1/analytics/attention-digest" }],
793
+ channels: ["mcp", "zapier", "make", "n8n"],
794
+ guardrails: [
795
+ "Each section needs its own scope (payment_disputes:read, subscriptions:read, invoices:read, webhook_deliveries:read). A section without its scope is reported as omitted rather than failing the whole call."
796
+ ]
797
+ },
591
798
  // ------------------------------------------------------------ sandbox write
592
799
  {
593
800
  id: "seed_test_data",
@@ -622,9 +829,9 @@ var ACTIONS = [
622
829
  scopes: ["quotes:write"],
623
830
  input: [
624
831
  { name: "customerId", type: "string", required: true, description: "Customer the quote is for." },
625
- { name: "lineItems", type: "array", required: true, description: "Quote line items." },
832
+ { name: "items", type: "array", required: true, description: "Quote line items: { name, unitPrice, quantity }." },
626
833
  { name: "expiresAt", type: "string", required: false, description: "ISO 8601 expiry." },
627
- { name: "netN", type: "number", required: false, description: "Payment terms in days." },
834
+ { name: "netN", type: "number", required: false, description: "Payment terms in days. Omit for pay-in-full." },
628
835
  { name: "notes", type: "string", required: false, description: "Buyer-visible notes." }
629
836
  ],
630
837
  requests: [{ method: "POST", path: "/api/v1/quotes" }],
@@ -715,7 +922,7 @@ var ACTIONS = [
715
922
  title: "Replay a webhook delivery",
716
923
  description: "Re-send a past webhook delivery to its endpoint \u2014 useful after fixing a broken handler.",
717
924
  kind: "write",
718
- scopes: ["webhooks:write"],
925
+ scopes: ["webhook_deliveries:write"],
719
926
  input: [{ name: "id", type: "string", required: true, description: "Delivery id." }],
720
927
  requests: [{ method: "POST", path: "/api/v1/webhook-deliveries/:id/replay" }],
721
928
  channels: ["mcp", "n8n"],
@@ -1106,6 +1313,54 @@ var HANDLERS = {
1106
1313
  // ---------------------------------------------------------------- invoices
1107
1314
  list_invoices: async (ctx, args) => ctx.client.list("/api/v1/invoices", { status: args.status, ...page(args) }),
1108
1315
  receivables_aging: async (ctx) => ctx.client.get("/api/v1/invoices/aging"),
1316
+ // --------------------------------------------------------------- discounts
1317
+ search_discounts: async (ctx, args) => ctx.client.list("/api/v1/discounts", {
1318
+ q: args.q,
1319
+ code: args.code,
1320
+ type: args.type,
1321
+ isActive: args.isActive,
1322
+ createdAfter: args.createdAfter,
1323
+ createdBefore: args.createdBefore,
1324
+ ...page(args)
1325
+ }),
1326
+ get_discount: async (ctx, args) => ctx.client.get(`/api/v1/discounts/${String(args.id)}`),
1327
+ // ---------------------------------------------------------------- payments
1328
+ search_payments: async (ctx, args) => ctx.client.list("/api/v1/payments", {
1329
+ method: args.method,
1330
+ status: args.status,
1331
+ // The route only accepts these as snake_case query params -- confirmed
1332
+ // by reading its querystring schema directly, not assumed from the
1333
+ // platform's usual camelCase convention.
1334
+ order_id: args.orderId,
1335
+ aging: args.aging,
1336
+ due_from: args.dueFrom,
1337
+ due_to: args.dueTo,
1338
+ amount_min: args.amountMin,
1339
+ amount_max: args.amountMax,
1340
+ ...page(args)
1341
+ }),
1342
+ get_payment: async (ctx, args) => {
1343
+ const id = String(args.id);
1344
+ const payment = await ctx.client.get(`/api/v1/payments/${id}`);
1345
+ const transactions = await optional(() => ctx.client.list(`/api/v1/payments/${id}/transactions`));
1346
+ return {
1347
+ payment,
1348
+ transactions: transactions.ok ? transactions.value.data : transactions.unavailable
1349
+ };
1350
+ },
1351
+ // ---------------------------------------------------------------- invoices
1352
+ get_invoice: async (ctx, args) => ctx.client.get(`/api/v1/invoices/${String(args.id)}`),
1353
+ // ------------------------------------------------------------------ carts
1354
+ list_abandoned_carts: async (ctx, args) => {
1355
+ const [carts, summary] = await Promise.all([
1356
+ optional(() => ctx.client.list("/api/v1/abandoned-carts", { ...page(args) })),
1357
+ optional(() => ctx.client.get("/api/v1/abandoned-carts/summary", { sinceDays: args.sinceDays }))
1358
+ ]);
1359
+ return {
1360
+ carts: carts.ok ? carts.value.data : carts.unavailable,
1361
+ summary: summary.ok ? summary.value : summary.unavailable
1362
+ };
1363
+ },
1109
1364
  // --------------------------------------------------------------- debugging
1110
1365
  search_events: async (ctx, args) => ctx.client.list("/api/v1/events", {
1111
1366
  type: args.type,
@@ -1119,6 +1374,13 @@ var HANDLERS = {
1119
1374
  limit: args.limit
1120
1375
  }),
1121
1376
  get_webhook_delivery: async (ctx, args) => ctx.client.get(`/api/v1/webhook-deliveries/${String(args.id)}`),
1377
+ send_test_webhook: async (ctx, args) => ctx.client.post(`/api/v1/webhook-endpoints/${String(args.id)}/test`, {
1378
+ body: { eventType: args.eventType },
1379
+ idempotencyKey: `mcp-send-test-webhook-${String(args.id)}-${Date.now()}`
1380
+ }),
1381
+ replay_webhook_delivery: async (ctx, args) => ctx.client.post(`/api/v1/webhook-deliveries/${String(args.id)}/replay`, {
1382
+ idempotencyKey: `mcp-replay-webhook-delivery-${String(args.id)}-${Date.now()}`
1383
+ }),
1122
1384
  explain_checkout_failure: async (ctx, args) => {
1123
1385
  const id = String(args.id);
1124
1386
  const session = await optional(() => ctx.client.get(`/api/v1/checkout-sessions/${id}`));
@@ -1163,7 +1425,11 @@ var HANDLERS = {
1163
1425
  idempotencyKey: `mcp-seed-customer-${stamp}`
1164
1426
  });
1165
1427
  const cart = await ctx.client.post("/api/v1/carts", {
1166
- body: { customerId: customer?.id },
1428
+ // Unlike /api/v1/customers, cart creation requires applicationId
1429
+ // explicitly in the body (it's cross-checked against the caller's own
1430
+ // authenticated application, not inferred) -- omitting it is a real
1431
+ // 400 missing_application_id from the live API, not a hypothetical.
1432
+ body: { applicationId: ctx.identity.application?.id, customerId: customer?.id },
1167
1433
  idempotencyKey: `mcp-seed-cart-${stamp}`
1168
1434
  });
1169
1435
  const requested = Array.isArray(args.lineItems) && args.lineItems.length > 0 ? args.lineItems : [{ name: "Seed widget", unitPrice: 2500, quantity: 2 }];
@@ -1188,6 +1454,54 @@ var HANDLERS = {
1188
1454
  note: "Seeded a finalized DRAFT order. No payment was captured \u2014 that needs a browser-tokenised payment method, which a server-side key cannot produce."
1189
1455
  };
1190
1456
  },
1457
+ // -------------------------------------------------------------- money/mail
1458
+ manage_subscription: async (ctx, args) => {
1459
+ const id = String(args.id);
1460
+ const action = String(args.action);
1461
+ if (action !== "pause" && action !== "resume") {
1462
+ throw new Error(`manage_subscription: action must be "pause" or "resume", got "${action}".`);
1463
+ }
1464
+ return ctx.client.post(`/api/v1/subscriptions/${id}/${action}`, {
1465
+ idempotencyKey: `mcp-manage-subscription-${id}-${action}-${Date.now()}`
1466
+ });
1467
+ },
1468
+ create_quote: async (ctx, args) => (
1469
+ // The real route's body shape differs from this tool's flatter,
1470
+ // model-facing input: line items are "items" (not "lineItems"), Net-N
1471
+ // terms are nested under paymentTerms.netN with an explicit mode (not a
1472
+ // flat "netN"), and the buyer-visible note field is "buyerNote" (not
1473
+ // "notes"). Confirmed against packages/platform-api/src/validation/quotes.ts's
1474
+ // actual zod schema after a live call 400'd with unrecognized_keys.
1475
+ ctx.client.post("/api/v1/quotes", {
1476
+ body: {
1477
+ customerId: args.customerId,
1478
+ items: args.items,
1479
+ expiresAt: args.expiresAt,
1480
+ paymentTerms: args.netN !== void 0 ? { mode: "net_terms", netN: args.netN } : void 0,
1481
+ buyerNote: args.notes
1482
+ },
1483
+ idempotencyKey: `mcp-create-quote-${String(args.customerId)}-${Date.now()}`
1484
+ })
1485
+ ),
1486
+ get_business_summary: async (ctx, args) => ctx.client.get("/api/v1/analytics/business-summary", { period: args.period }),
1487
+ get_attention_digest: async (ctx) => ctx.client.get("/api/v1/analytics/attention-digest"),
1488
+ // ------------------------------------------------------------- extensions
1489
+ list_extension_installations: async (ctx, args) => ctx.client.list("/api/v1/installations", { status: args.status, ...page(args) }),
1490
+ // ------------------------------------------------------------- connectors
1491
+ get_connector_routing: async (ctx) => {
1492
+ const [paymentRouting, routingConnectors, cardDecline, otherDecline] = await Promise.all([
1493
+ optional(() => ctx.client.get("/api/v1/payment-routing")),
1494
+ optional(() => ctx.client.get("/api/v1/routing-connectors")),
1495
+ optional(() => ctx.client.get("/api/v1/card-decline")),
1496
+ optional(() => ctx.client.get("/api/v1/other-decline"))
1497
+ ]);
1498
+ return {
1499
+ paymentRouting: paymentRouting.ok ? paymentRouting.value : paymentRouting.unavailable,
1500
+ routingConnectors: routingConnectors.ok ? routingConnectors.value : routingConnectors.unavailable,
1501
+ cardDecline: cardDecline.ok ? cardDecline.value : cardDecline.unavailable,
1502
+ otherDecline: otherDecline.ok ? otherDecline.value : otherDecline.unavailable
1503
+ };
1504
+ },
1191
1505
  get_integration_status: async (ctx) => {
1192
1506
  const [connectors, endpoints, coverage] = await Promise.all([
1193
1507
  optional(() => ctx.client.list("/api/v1/connectors")),
@@ -1202,7 +1516,10 @@ var HANDLERS = {
1202
1516
  webhookEndpoints: endpoints.ok ? endpoints.value.data : endpoints.unavailable,
1203
1517
  unsubscribedEvents: coverage.ok ? coverage.value : coverage.unavailable
1204
1518
  };
1205
- }
1519
+ },
1520
+ // --------------------------------------------------------------- workspace
1521
+ get_workspace_settings: async (ctx) => ctx.client.get("/api/v1/merchants/me"),
1522
+ get_audit_log: async (ctx, args) => ctx.client.list("/api/v1/audit-log", { limit: args.limit })
1206
1523
  };
1207
1524
 
1208
1525
  // src/input.ts
@@ -1238,7 +1555,7 @@ function applyModifiers(field, type) {
1238
1555
  }
1239
1556
 
1240
1557
  // src/server.ts
1241
- var VERSION = true ? "0.1.0" : "0.0.0-dev";
1558
+ var VERSION = true ? "0.2.0" : "0.0.0-dev";
1242
1559
  function registerableActions(policy) {
1243
1560
  const isProduction = policy.identity.environment.providerEnvironment === "production";
1244
1561
  return actionsForChannel("mcp").filter((action) => {
@@ -1325,6 +1642,26 @@ function describeForModel(action) {
1325
1642
  return parts.join(" ");
1326
1643
  }
1327
1644
 
1645
+ // src/indexes.ts
1646
+ import { readFileSync } from "fs";
1647
+ function loadIndex(file, empty, label) {
1648
+ try {
1649
+ return JSON.parse(readFileSync(new URL(`./${file}`, import.meta.url), "utf8"));
1650
+ } catch {
1651
+ process.stderr.write(
1652
+ `throttle-mcp: ${label} index missing (${file}); that tool will return no results. Run "pnpm --filter @usethrottle/mcp build:indexes".
1653
+ `
1654
+ );
1655
+ return empty;
1656
+ }
1657
+ }
1658
+ function loadDocsIndex() {
1659
+ return loadIndex("docs-index.json", { generatedAt: "", entries: [] }, "docs");
1660
+ }
1661
+ function loadContractIndex() {
1662
+ return loadIndex("contract-index.json", { generatedAt: "", operations: [] }, "contract");
1663
+ }
1664
+
1328
1665
  // src/index.ts
1329
1666
  var DEFAULT_API = "https://api.usethrottle.dev";
1330
1667
  function fail(message) {
@@ -1376,17 +1713,6 @@ function parseArgs(argv) {
1376
1713
  allowLiveWrites: args.includes("--allow-live-writes")
1377
1714
  };
1378
1715
  }
1379
- function loadIndex(file, empty, label) {
1380
- try {
1381
- return JSON.parse(readFileSync(new URL(`./${file}`, import.meta.url), "utf8"));
1382
- } catch {
1383
- process.stderr.write(
1384
- `throttle-mcp: ${label} index missing (${file}); that tool will return no results. Run "pnpm --filter @usethrottle/mcp build:indexes".
1385
- `
1386
- );
1387
- return empty;
1388
- }
1389
- }
1390
1716
  async function main() {
1391
1717
  const options = parseArgs(process.argv);
1392
1718
  const client = new ThrottleClient({ apiKey: options.apiKey, apiBaseUrl: options.apiBaseUrl });
@@ -1399,12 +1725,8 @@ async function main() {
1399
1725
  `could not identify the credential via GET /api/v1/whoami (${e?.code ?? "error"}: ${e?.message ?? String(error)}). Check the key and --api URL.`
1400
1726
  );
1401
1727
  }
1402
- const docs = loadIndex("docs-index.json", { generatedAt: "", entries: [] }, "docs");
1403
- const contract = loadIndex(
1404
- "contract-index.json",
1405
- { generatedAt: "", operations: [] },
1406
- "contract"
1407
- );
1728
+ const docs = loadDocsIndex();
1729
+ const contract = loadContractIndex();
1408
1730
  const server = createServer({
1409
1731
  identity,
1410
1732
  client,