@usethrottle/mcp 0.1.1 → 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,29 @@ 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
+ },
591
763
  {
592
764
  id: "get_business_summary",
593
765
  title: "Business summary",
@@ -657,9 +829,9 @@ var ACTIONS = [
657
829
  scopes: ["quotes:write"],
658
830
  input: [
659
831
  { name: "customerId", type: "string", required: true, description: "Customer the quote is for." },
660
- { name: "lineItems", type: "array", required: true, description: "Quote line items." },
832
+ { name: "items", type: "array", required: true, description: "Quote line items: { name, unitPrice, quantity }." },
661
833
  { name: "expiresAt", type: "string", required: false, description: "ISO 8601 expiry." },
662
- { 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." },
663
835
  { name: "notes", type: "string", required: false, description: "Buyer-visible notes." }
664
836
  ],
665
837
  requests: [{ method: "POST", path: "/api/v1/quotes" }],
@@ -750,7 +922,7 @@ var ACTIONS = [
750
922
  title: "Replay a webhook delivery",
751
923
  description: "Re-send a past webhook delivery to its endpoint \u2014 useful after fixing a broken handler.",
752
924
  kind: "write",
753
- scopes: ["webhooks:write"],
925
+ scopes: ["webhook_deliveries:write"],
754
926
  input: [{ name: "id", type: "string", required: true, description: "Delivery id." }],
755
927
  requests: [{ method: "POST", path: "/api/v1/webhook-deliveries/:id/replay" }],
756
928
  channels: ["mcp", "n8n"],
@@ -1141,6 +1313,54 @@ var HANDLERS = {
1141
1313
  // ---------------------------------------------------------------- invoices
1142
1314
  list_invoices: async (ctx, args) => ctx.client.list("/api/v1/invoices", { status: args.status, ...page(args) }),
1143
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
+ },
1144
1364
  // --------------------------------------------------------------- debugging
1145
1365
  search_events: async (ctx, args) => ctx.client.list("/api/v1/events", {
1146
1366
  type: args.type,
@@ -1154,6 +1374,13 @@ var HANDLERS = {
1154
1374
  limit: args.limit
1155
1375
  }),
1156
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
+ }),
1157
1384
  explain_checkout_failure: async (ctx, args) => {
1158
1385
  const id = String(args.id);
1159
1386
  const session = await optional(() => ctx.client.get(`/api/v1/checkout-sessions/${id}`));
@@ -1198,7 +1425,11 @@ var HANDLERS = {
1198
1425
  idempotencyKey: `mcp-seed-customer-${stamp}`
1199
1426
  });
1200
1427
  const cart = await ctx.client.post("/api/v1/carts", {
1201
- 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 },
1202
1433
  idempotencyKey: `mcp-seed-cart-${stamp}`
1203
1434
  });
1204
1435
  const requested = Array.isArray(args.lineItems) && args.lineItems.length > 0 ? args.lineItems : [{ name: "Seed widget", unitPrice: 2500, quantity: 2 }];
@@ -1223,8 +1454,54 @@ var HANDLERS = {
1223
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."
1224
1455
  };
1225
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
+ ),
1226
1486
  get_business_summary: async (ctx, args) => ctx.client.get("/api/v1/analytics/business-summary", { period: args.period }),
1227
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
+ },
1228
1505
  get_integration_status: async (ctx) => {
1229
1506
  const [connectors, endpoints, coverage] = await Promise.all([
1230
1507
  optional(() => ctx.client.list("/api/v1/connectors")),
@@ -1239,7 +1516,10 @@ var HANDLERS = {
1239
1516
  webhookEndpoints: endpoints.ok ? endpoints.value.data : endpoints.unavailable,
1240
1517
  unsubscribedEvents: coverage.ok ? coverage.value : coverage.unavailable
1241
1518
  };
1242
- }
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 })
1243
1523
  };
1244
1524
 
1245
1525
  // src/input.ts
@@ -1275,7 +1555,7 @@ function applyModifiers(field, type) {
1275
1555
  }
1276
1556
 
1277
1557
  // src/server.ts
1278
- var VERSION = true ? "0.1.1" : "0.0.0-dev";
1558
+ var VERSION = true ? "0.2.0" : "0.0.0-dev";
1279
1559
  function registerableActions(policy) {
1280
1560
  const isProduction = policy.identity.environment.providerEnvironment === "production";
1281
1561
  return actionsForChannel("mcp").filter((action) => {
@@ -1362,6 +1642,26 @@ function describeForModel(action) {
1362
1642
  return parts.join(" ");
1363
1643
  }
1364
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
+
1365
1665
  // src/index.ts
1366
1666
  var DEFAULT_API = "https://api.usethrottle.dev";
1367
1667
  function fail(message) {
@@ -1413,17 +1713,6 @@ function parseArgs(argv) {
1413
1713
  allowLiveWrites: args.includes("--allow-live-writes")
1414
1714
  };
1415
1715
  }
1416
- function loadIndex(file, empty, label) {
1417
- try {
1418
- return JSON.parse(readFileSync(new URL(`./${file}`, import.meta.url), "utf8"));
1419
- } catch {
1420
- process.stderr.write(
1421
- `throttle-mcp: ${label} index missing (${file}); that tool will return no results. Run "pnpm --filter @usethrottle/mcp build:indexes".
1422
- `
1423
- );
1424
- return empty;
1425
- }
1426
- }
1427
1716
  async function main() {
1428
1717
  const options = parseArgs(process.argv);
1429
1718
  const client = new ThrottleClient({ apiKey: options.apiKey, apiBaseUrl: options.apiBaseUrl });
@@ -1436,12 +1725,8 @@ async function main() {
1436
1725
  `could not identify the credential via GET /api/v1/whoami (${e?.code ?? "error"}: ${e?.message ?? String(error)}). Check the key and --api URL.`
1437
1726
  );
1438
1727
  }
1439
- const docs = loadIndex("docs-index.json", { generatedAt: "", entries: [] }, "docs");
1440
- const contract = loadIndex(
1441
- "contract-index.json",
1442
- { generatedAt: "", operations: [] },
1443
- "contract"
1444
- );
1728
+ const docs = loadDocsIndex();
1729
+ const contract = loadContractIndex();
1445
1730
  const server = createServer({
1446
1731
  identity,
1447
1732
  client,