@zackbart/connecta 0.31.0 → 0.32.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 (95) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +15 -13
  3. package/bin/connecta.mjs +1 -0
  4. package/bin/version.mjs +1 -1
  5. package/dist/auth-elicitation.js +1 -1
  6. package/dist/catalog-service.js +7 -2
  7. package/dist/config-value-sources.js +6 -3
  8. package/dist/config.d.ts +26 -8
  9. package/dist/config.js +38 -16
  10. package/dist/connector-access.d.ts +2 -1
  11. package/dist/connectors/guarded-fetch.d.ts +9 -0
  12. package/dist/connectors/guarded-fetch.js +7 -0
  13. package/dist/describe-config.d.ts +6 -3
  14. package/dist/describe-config.js +3 -2
  15. package/dist/errors.d.ts +1 -1
  16. package/dist/errors.js +1 -0
  17. package/dist/execute.js +16 -14
  18. package/dist/index.js +4 -3
  19. package/dist/invocation.js +7 -4
  20. package/dist/meta-tools.js +17 -19
  21. package/dist/operator-record.d.ts +2 -2
  22. package/dist/operator-record.js +2 -0
  23. package/dist/operator-ui/contract.d.ts +5 -4
  24. package/dist/operator-ui/generated.js +2 -2
  25. package/dist/operator-ui/model.d.ts +1 -1
  26. package/dist/operator-ui/view.js +1 -0
  27. package/dist/program-writes.js +2 -2
  28. package/dist/provider.d.ts +2 -3
  29. package/dist/providers/_shared/google/workspace.js +1 -1
  30. package/dist/providers/breeze/skill.generated.js +2 -2
  31. package/dist/providers/cloudflare/skill.generated.js +1 -1
  32. package/dist/providers/docs/index.d.ts +1 -1
  33. package/dist/providers/docs/index.js +2 -2
  34. package/dist/providers/drive/index.d.ts +1 -1
  35. package/dist/providers/drive/index.js +1 -1
  36. package/dist/providers/forms/index.d.ts +1 -1
  37. package/dist/providers/forms/index.js +2 -2
  38. package/dist/providers/github/skill.generated.js +2 -2
  39. package/dist/providers/gmail/index.d.ts +1 -1
  40. package/dist/providers/gmail/index.js +274 -23
  41. package/dist/providers/gmail/skill.generated.js +2 -2
  42. package/dist/providers/linear/index.js +1 -1
  43. package/dist/providers/mixpanel/index.d.ts +27 -12
  44. package/dist/providers/mixpanel/index.js +58 -6
  45. package/dist/providers/mixpanel/operations.js +20 -0
  46. package/dist/providers/mixpanel/rest.js +603 -0
  47. package/dist/providers/mixpanel/skill.generated.js +3 -2
  48. package/dist/providers/mixpanel/value-safety.js +16 -0
  49. package/dist/providers/notion/index.js +16 -0
  50. package/dist/providers/notion/rest.js +138 -19
  51. package/dist/providers/notion/skill.generated.js +4 -4
  52. package/dist/providers/planning-center/index.d.ts +27 -0
  53. package/dist/providers/planning-center/index.js +286 -24
  54. package/dist/providers/planning-center/skill.generated.js +24 -4
  55. package/dist/providers/sheets/index.d.ts +1 -1
  56. package/dist/providers/sheets/index.js +3 -3
  57. package/dist/providers/slides/index.d.ts +1 -1
  58. package/dist/providers/slides/index.js +3 -3
  59. package/dist/providers/stripe/skill.generated.js +2 -2
  60. package/dist/providers/vercel/index.js +2 -2
  61. package/dist/providers/vercel/skill.generated.js +2 -2
  62. package/dist/providers/wispr-flow/index.d.ts +14 -0
  63. package/dist/providers/wispr-flow/index.js +117 -0
  64. package/dist/providers/wispr-flow/skill.generated.js +8 -0
  65. package/dist/routes/mcp.js +30 -10
  66. package/dist/routes/operator.js +14 -7
  67. package/dist/routes/ui-config.js +2 -2
  68. package/dist/server.js +3 -0
  69. package/dist/skills.js +5 -4
  70. package/dist/tool-safety.js +13 -5
  71. package/dist/types.d.ts +1 -1
  72. package/dist/ui.d.ts +1 -1
  73. package/dist/ui.js +1 -1
  74. package/dist/usage-guide.js +3 -3
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.js +1 -1
  77. package/documentation/architecture.md +38 -23
  78. package/documentation/auth.md +86 -27
  79. package/documentation/code-mode.md +81 -72
  80. package/documentation/deploying.md +10 -5
  81. package/documentation/integrating.md +56 -4
  82. package/documentation/meta-tools.md +44 -37
  83. package/documentation/operating.md +11 -8
  84. package/documentation/operator-tests.md +2 -3
  85. package/documentation/operator-ui.md +9 -3
  86. package/documentation/provider-auth-migration.md +5 -4
  87. package/documentation/provider-migration-0.29.md +1 -1
  88. package/examples/worker/AGENTS.md +5 -0
  89. package/examples/worker/src/connecta.config.ts +5 -0
  90. package/package.json +5 -1
  91. package/templates/node/AGENTS.md +6 -4
  92. package/templates/node/README.md +4 -2
  93. package/templates/node/package.json +1 -1
  94. package/templates/node/src/connecta.config.ts +5 -0
  95. package/dist/tool-safety.d.ts +0 -9
@@ -237,7 +237,7 @@ function isApp(value) {
237
237
  * normalization the guarded transport applies — so `..` cannot carry a request
238
238
  * from `/people/v2` to `/oauth` or anywhere else outside a product.
239
239
  */
240
- function appForPath(path) {
240
+ function appForPath(path, apps) {
241
241
  let normalized;
242
242
  if (path.startsWith("/") && !path.startsWith("//")) {
243
243
  try {
@@ -249,10 +249,145 @@ function appForPath(path) {
249
249
  }
250
250
  const app = normalized === undefined ? undefined : APP_PATH.exec(normalized)?.[1];
251
251
  if (!app || !isApp(app)) {
252
- throw new ConnectorCallError("invalid_args", `A Planning Center path begins with /<app>/v2, where app is one of ${PLANNING_CENTER_APPS.join(", ")} — for example /people/v2/people. No host, query string, or traversal.`);
252
+ throw new ConnectorCallError("invalid_args", `A Planning Center path begins with /<app>/v2, where app is one of ${apps.join(", ")} — for example /people/v2/people. No host, query string, or traversal.`);
253
253
  }
254
254
  return app;
255
255
  }
256
+ /** Display names, in guide order. The guide never lists `api` as a product. */
257
+ const APP_TITLES = {
258
+ people: "People",
259
+ services: "Services",
260
+ groups: "Groups",
261
+ "check-ins": "Check-Ins",
262
+ calendar: "Calendar",
263
+ registrations: "Registrations",
264
+ giving: "Giving",
265
+ publishing: "Publishing",
266
+ webhooks: "Webhooks",
267
+ api: "API",
268
+ };
269
+ /**
270
+ * A path's segments, each percent-decoded once, or a refusal when the path is
271
+ * not literal. While a product is excluded no path may lean on normalization:
272
+ * a dot segment, an empty segment, a backslash, whitespace, or an encoded
273
+ * `/`, `\`, `?`, `#`, or `%` could each mean one thing here and another to a
274
+ * server that decodes or resolves it, so none is admitted at all rather than
275
+ * reasoned about.
276
+ */
277
+ function literalSegments(path) {
278
+ const ambiguous = () => new ConnectorCallError("invalid_args", "While this deployment excludes a Planning Center product, a path must be literal: no . or .. segments, empty segments, backslashes, whitespace, or percent-encoded /, \\, ?, #, or %.");
279
+ if (!path.startsWith("/"))
280
+ throw ambiguous();
281
+ const raw = path.slice(1).split("/");
282
+ return raw.map((segment, index) => {
283
+ if (segment === "" && index !== raw.length - 1)
284
+ throw ambiguous();
285
+ let decoded;
286
+ try {
287
+ decoded = decodeURIComponent(segment);
288
+ }
289
+ catch {
290
+ throw ambiguous();
291
+ }
292
+ const control = [...decoded].some((char) => char <= " " || char === "\x7f");
293
+ if (decoded === "." || decoded === ".." || control || /[/\\?#%]/.test(decoded))
294
+ throw ambiguous();
295
+ return decoded;
296
+ });
297
+ }
298
+ /**
299
+ * People resources a list is built from or answers with. A list's conditions
300
+ * can draw on any product — a Giving condition makes a donor list — and one
301
+ * list can be a condition of another, so while any product is excluded none
302
+ * of them is read, by path or by `include`.
303
+ */
304
+ const LIST_RESOURCES = new Set(["lists", "list_results", "rules", "conditions"]);
305
+ /** The People tools that read or refresh a list, removed with any exclusion. */
306
+ const LIST_TOOLS = new Set(["list_lists", "list_list_people", "run_list"]);
307
+ /** The webhook subscription collections, current and 2018-08-01. */
308
+ const SUBSCRIPTION_COLLECTIONS = new Set(["webhook_subscriptions", "subscriptions"]);
309
+ /**
310
+ * Refuse, before anything is sent, a request that would reach an excluded
311
+ * product. It reads literal segments, so it runs twice: on the path as given,
312
+ * and on the exact URL the transport is about to fetch (`admit`), which is the
313
+ * one that counts. Five doors lead to an excluded product:
314
+ *
315
+ * - its own paths, in any case or percent-encoding of a literal segment;
316
+ * - People lists, rules, conditions, and results, whose criteria can come from
317
+ * the excluded product, by path or by `include`;
318
+ * - a new webhook subscription to its events, which delivers its records to
319
+ * any URL — so a new subscription must name a reachable product's event;
320
+ * - an existing subscription, changed by id: a path names a subscription by
321
+ * id, not by product, so no subscription is enabled, disabled, deleted, or
322
+ * re-keyed while any product is excluded; and
323
+ * - webhook event and delivery payloads, which carry the record that fired
324
+ * them, refused for every subscription for the same reason.
325
+ *
326
+ * People documents no other `include` or attribute that reaches another
327
+ * product's records.
328
+ */
329
+ function refuseExcluded(segments, query, request, excluded) {
330
+ const disabled = (app, detail) => new ConnectorCallError("provider_permission_denied", `Planning Center ${APP_TITLES[app]} (/${app}/v2) is disabled in this deployment, so this connection ${detail}. Retrying will not help; only an operator can remove "${app}" from excludeApps.`, { retryable: false });
331
+ const because = (detail) => new ConnectorCallError("provider_permission_denied", `This deployment disables Planning Center ${series([...excluded].map((name) => APP_TITLES[name]))}, and ${detail} Retrying will not help.`, { retryable: false });
332
+ const lower = segments.map((segment) => segment.toLowerCase());
333
+ const app = lower[0] ?? "";
334
+ if (isApp(app) && excluded.has(app))
335
+ throw disabled(app, "does not reach its endpoints");
336
+ // Lists are refused and workflows are not. A list is a live query: reading
337
+ // it evaluates its conditions, Giving ones included, at read time, so it
338
+ // is the excluded product's API by another path. A workflow card is a
339
+ // stored People record. A Giving automation can create one, but so can
340
+ // staff, and once created it is People data, like a note or custom field
341
+ // value typed from a donor's details. Exclusion guards products' APIs,
342
+ // not provenance; refusing workflows would cost every exclusion a core
343
+ // People feature and argue equally for notes, fields, forms, and groups.
344
+ // The hard boundary is the token user's own per-product permissions.
345
+ const lists = "a People list's conditions can draw on a disabled product, so this connection reads no list, list result, rule, or condition.";
346
+ if (app === "people" && lower.some((segment) => LIST_RESOURCES.has(segment)))
347
+ throw because(lists);
348
+ for (const [key, value] of query) {
349
+ if (/include/i.test(key) &&
350
+ value
351
+ .toLowerCase()
352
+ .split(/[\s,.]+/)
353
+ .some((part) => LIST_RESOURCES.has(part))) {
354
+ throw because(lists);
355
+ }
356
+ }
357
+ if (app !== "webhooks")
358
+ return;
359
+ if (lower.some((segment) => /^(?:events|deliveries)\b/.test(segment))) {
360
+ throw because("a webhook event or delivery payload may carry any product's records, so this connection does not read them. Subscriptions themselves stay readable.");
361
+ }
362
+ if (request.method === "GET" || request.method === "HEAD")
363
+ return;
364
+ const resource = lower.slice(2).filter((segment) => segment !== "");
365
+ if (request.method !== "POST" || resource.length !== 1 || !SUBSCRIPTION_COLLECTIONS.has(resource[0])) {
366
+ throw because("a webhook subscription's id does not name its product, so this connection does not enable, disable, delete, or re-key an existing subscription.");
367
+ }
368
+ const data = asRecord(request.body)["data"];
369
+ const name = Array.isArray(data) ? undefined : asRecord(asRecord(data)["attributes"])["name"];
370
+ const event = typeof name === "string" ? /^([a-z-]+)\.v2\.events\./.exec(name.trim().toLowerCase())?.[1] : undefined;
371
+ if (event === undefined || !isApp(event)) {
372
+ throw because("a new webhook subscription must name one product's event, such as people.v2.events.person.created, in data.attributes.name.");
373
+ }
374
+ if (excluded.has(event))
375
+ throw disabled(event, "cannot subscribe to its webhook events");
376
+ }
377
+ /**
378
+ * The exclusion, enforced on the exact URL about to be fetched: after the
379
+ * transport has resolved and encoded it, so what is checked is what Planning
380
+ * Center routes. The origin and base path are asserted again here, not taken
381
+ * on trust from the confinement before it.
382
+ */
383
+ function admit(baseUrl, url, request, excluded) {
384
+ const base = new URL(baseUrl);
385
+ const basePath = base.pathname.replace(/\/+$/, "");
386
+ if (url.origin !== base.origin || !url.pathname.startsWith(`${basePath}/`) || url.hash) {
387
+ throw new ConnectorCallError("invalid_args", "A Planning Center request left the Planning Center API origin.");
388
+ }
389
+ refuseExcluded(literalSegments(url.pathname.slice(basePath.length)), url.searchParams, request, excluded);
390
+ }
256
391
  function errorDetail(payload, status) {
257
392
  const parts = [];
258
393
  for (const entry of asArray(asRecord(payload)["errors"])) {
@@ -309,9 +444,12 @@ function planningCenterFailure(status, headers, payload) {
309
444
  retryable: false,
310
445
  });
311
446
  }
312
- function planningCenterCall(send, versions) {
447
+ function planningCenterCall(send, versions, excluded) {
448
+ const apps = PLANNING_CENTER_APPS.filter((app) => !excluded.has(app));
313
449
  return async (request, ctx) => {
314
- const app = appForPath(request.path);
450
+ if (excluded.size > 0)
451
+ refuseExcluded(literalSegments(request.path), [], request, excluded);
452
+ const app = appForPath(request.path, apps);
315
453
  return await send(compact({
316
454
  method: request.method,
317
455
  path: request.path,
@@ -787,11 +925,11 @@ function personAttributes(args) {
787
925
  };
788
926
  }
789
927
  const WORKFLOW_CARD_ACTIONS = ["promote", "go_back", "skip_step", "snooze", "unsnooze", "remove", "restore"];
790
- function tools(call, defaultPageSize) {
928
+ function tools(call, defaultPageSize, excluded) {
791
929
  const perPage = (args) => args["perPage"] ?? defaultPageSize;
792
930
  const list = (spec) => listTool(call, defaultPageSize, spec);
793
- return [
794
- // --- Hatches ------------------------------------------------------------
931
+ // --- Hatches --------------------------------------------------------------
932
+ const hatches = [
795
933
  {
796
934
  name: "pco_api_get",
797
935
  description: "Call any Planning Center GET endpoint in any product and return its untouched JSON:API body. Prefer a named read; this one does not flatten or resolve includes.",
@@ -813,7 +951,7 @@ function tools(call, defaultPageSize) {
813
951
  },
814
952
  {
815
953
  name: "pco_api_mutate",
816
- description: "Call any Planning Center POST, PATCH, or DELETE endpoint with a JSON:API body. The approval-gated hatch for writes, actions, and money movement no named tool covers.",
954
+ description: `Call any Planning Center POST, PATCH, or DELETE endpoint with a JSON:API body. The approval-gated hatch for ${excluded.has("giving") ? "writes and actions" : "writes, actions, and money movement"} no named tool covers.`,
817
955
  annotations: DESTRUCTIVE,
818
956
  inputSchema: input({
819
957
  method: {
@@ -844,7 +982,9 @@ function tools(call, defaultPageSize) {
844
982
  }), ctx)) ?? null,
845
983
  }),
846
984
  },
847
- // --- People -------------------------------------------------------------
985
+ ];
986
+ // --- People ---------------------------------------------------------------
987
+ const people = [
848
988
  {
849
989
  name: "get_me",
850
990
  description: "Get the Planning Center person this token acts as, with the organization's name and time zone. Supplies personId for my-schedule and assignee questions.",
@@ -1292,7 +1432,9 @@ function tools(call, defaultPageSize) {
1292
1432
  };
1293
1433
  },
1294
1434
  }),
1295
- // --- Services -----------------------------------------------------------
1435
+ ];
1436
+ // --- Services -------------------------------------------------------------
1437
+ const services = [
1296
1438
  list({
1297
1439
  name: "list_service_types",
1298
1440
  description: "List Services service types — the recurring services such as Sunday Morning. Supplies serviceTypeId for plans and teams.",
@@ -1596,7 +1738,9 @@ function tools(call, defaultPageSize) {
1596
1738
  keys: relatedMany(row, "keys", index).map((key) => pick(key, ["name", "starting_key", "ending_key"])),
1597
1739
  }),
1598
1740
  }),
1599
- // --- Groups -------------------------------------------------------------
1741
+ ];
1742
+ // --- Groups ---------------------------------------------------------------
1743
+ const groups = [
1600
1744
  list({
1601
1745
  name: "list_groups",
1602
1746
  description: "List Groups groups with type, schedule, and member count. Archived groups are excluded unless asked for; the API cannot create a group.",
@@ -1712,7 +1856,9 @@ function tools(call, defaultPageSize) {
1712
1856
  group_id: relId(row, "group") ?? null,
1713
1857
  }),
1714
1858
  }),
1715
- // --- Check-Ins (read-only product) ---------------------------------------
1859
+ ];
1860
+ // --- Check-Ins (read-only product) -----------------------------------------
1861
+ const checkIns = [
1716
1862
  list({
1717
1863
  name: "list_check_in_events",
1718
1864
  description: "List Check-Ins events, the recurring things people check in to. Supplies eventId for event periods and check-ins.",
@@ -1806,7 +1952,9 @@ function tools(call, defaultPageSize) {
1806
1952
  event_period_id: relId(row, "event_period") ?? null,
1807
1953
  }),
1808
1954
  }),
1809
- // --- Calendar (events read-only) ----------------------------------------
1955
+ ];
1956
+ // --- Calendar (events read-only) ------------------------------------------
1957
+ const calendar = [
1810
1958
  list({
1811
1959
  name: "list_calendar_event_instances",
1812
1960
  description: "List what is on the Calendar in a date range: each occurrence with times and location, recurrence expanded. Events cannot be written through the API.",
@@ -1848,7 +1996,9 @@ function tools(call, defaultPageSize) {
1848
1996
  event_id: relId(row, "event") ?? null,
1849
1997
  }),
1850
1998
  }),
1851
- // --- Registrations (read-only product) ----------------------------------
1999
+ ];
2000
+ // --- Registrations (read-only product) ------------------------------------
2001
+ const registrations = [
1852
2002
  list({
1853
2003
  name: "list_signups",
1854
2004
  description: "List Registrations signups (registration events) with open, close, and capacity state. Read-only: the API cannot register anyone.",
@@ -1917,7 +2067,9 @@ function tools(call, defaultPageSize) {
1917
2067
  person_id: relId(row, "person") ?? null,
1918
2068
  }),
1919
2069
  }),
1920
- // --- Giving (reads; money moves only through pco_api_mutate) -------------
2070
+ ];
2071
+ // --- Giving (reads; money moves only through pco_api_mutate) ---------------
2072
+ const giving = [
1921
2073
  list({
1922
2074
  name: "list_donations",
1923
2075
  description: "List Giving donations with their fund designations, newest first, by date, fund, or donor. Amounts are integer cents. Read-only.",
@@ -2075,7 +2227,9 @@ function tools(call, defaultPageSize) {
2075
2227
  designations: designations(row, index),
2076
2228
  }),
2077
2229
  }),
2078
- // --- Webhooks -----------------------------------------------------------
2230
+ ];
2231
+ // --- Webhooks -------------------------------------------------------------
2232
+ const webhooks = [
2079
2233
  {
2080
2234
  name: "list_webhook_subscriptions",
2081
2235
  description: "List the organization's webhook subscriptions: event name, URL, and active state. Never returns the signing secret; that needs pco_api_get.",
@@ -2104,18 +2258,117 @@ function tools(call, defaultPageSize) {
2104
2258
  },
2105
2259
  },
2106
2260
  ];
2261
+ return {
2262
+ hatches,
2263
+ apps: {
2264
+ people,
2265
+ services,
2266
+ groups,
2267
+ "check-ins": checkIns,
2268
+ calendar,
2269
+ registrations,
2270
+ giving,
2271
+ publishing: [],
2272
+ webhooks,
2273
+ api: [],
2274
+ },
2275
+ };
2107
2276
  }
2108
2277
  // ---------------------------------------------------------------------------
2109
2278
  // Guide and construction
2110
2279
  // ---------------------------------------------------------------------------
2111
- function usageGuide(purpose, instructions) {
2280
+ /** `a, b, and c`, the way the guide and description list products. */
2281
+ function series(items) {
2282
+ return items.length < 3 ? items.join(" and ") : `${items.slice(0, -1).join(", ")}, and ${items.at(-1)}`;
2283
+ }
2284
+ /** The products the description names: those with named tools for staff work. */
2285
+ const DESCRIBED_APPS = [
2286
+ "people",
2287
+ "services",
2288
+ "groups",
2289
+ "check-ins",
2290
+ "calendar",
2291
+ "registrations",
2292
+ "giving",
2293
+ ];
2294
+ /** What a person id joins in each other product, for the guide's identity rule. */
2295
+ const PERSON_JOINS = [
2296
+ ["services", "Services schedules"],
2297
+ ["groups", "Groups memberships"],
2298
+ ["giving", "Giving donations"],
2299
+ ["check-ins", "Check-Ins"],
2300
+ ];
2301
+ /**
2302
+ * The maintained guide for the products this connection reaches. Every
2303
+ * product-specific line is its own fragment, so an excluded product is
2304
+ * neither described, pinned, nor named by a tool the guide points to, and an
2305
+ * exclusion adds the line stating what the hatches refuse. With nothing
2306
+ * excluded, the text is the whole guide as before; the `published` fragment
2307
+ * is that same text without a purpose.
2308
+ */
2309
+ function usageGuide(purpose, instructions, excluded) {
2310
+ const fragments = skill.fragments;
2311
+ const has = (app) => !excluded.has(app);
2312
+ const pair = (a, b, both, onlyA, onlyB) => has(a) && has(b) ? both : has(a) ? onlyA : has(b) ? onlyB : "";
2313
+ const apps = PLANNING_CENTER_APPS.filter(has);
2314
+ const joins = PERSON_JOINS.filter(([app]) => has(app)).map(([, label]) => label);
2112
2315
  const organizationInstructions = instructions?.trim();
2113
- return `${skill.fragments.guide_0}${purpose}${skill.fragments.guide_1}${PLANNING_CENTER_APPS.map((app) => `${app} ${PLANNING_CENTER_API_VERSIONS[app]}`).join(", ")}${skill.fragments.guide_2}${organizationInstructions ? `\n## ${skill.instructionsHeading}\n\n${organizationInstructions}\n` : ""}`;
2316
+ return [
2317
+ fragments.heading,
2318
+ apps
2319
+ .filter((app) => app !== "api")
2320
+ .map((app) => APP_TITLES[app])
2321
+ .join(", "),
2322
+ fragments.purpose,
2323
+ purpose,
2324
+ fragments.identity,
2325
+ joins.length > 0 ? series(joins) : "every People record",
2326
+ fragments.conventions,
2327
+ has("services") ? fragments.plans : "",
2328
+ fragments.writes,
2329
+ excluded.size === 0 ? fragments.people : fragments.people_without_lists,
2330
+ has("services") ? fragments.services : "",
2331
+ has("groups") ? fragments.groups : "",
2332
+ pair("check-ins", "registrations", fragments.check_ins_registrations, fragments.check_ins, fragments.registrations),
2333
+ has("calendar") ? fragments.calendar : "",
2334
+ has("giving") ? fragments.giving : "",
2335
+ pair("publishing", "api", fragments.publishing_api, fragments.publishing, fragments.api),
2336
+ fragments.hatches,
2337
+ apps.map((app) => `${app} ${PLANNING_CENTER_API_VERSIONS[app]}`).join(", "),
2338
+ fragments.pins,
2339
+ excluded.size === 0 ? "" : fragments.disabled,
2340
+ fragments.limits,
2341
+ organizationInstructions ? `\n## ${skill.instructionsHeading}\n\n${organizationInstructions}\n` : "",
2342
+ ].join("");
2343
+ }
2344
+ /**
2345
+ * `excludeApps` as a set, refusing at construction what cannot be meant
2346
+ * (INV-11): a product Planning Center does not have, a misspelling, or People.
2347
+ */
2348
+ function excludedApps(value) {
2349
+ const excluded = new Set();
2350
+ if (value === undefined)
2351
+ return excluded;
2352
+ const excludable = PLANNING_CENTER_APPS.filter((app) => app !== "people").join(", ");
2353
+ if (!Array.isArray(value)) {
2354
+ throw new Error(`planningCenter() excludeApps must be an array of app names from: ${excludable}.`);
2355
+ }
2356
+ for (const entry of value) {
2357
+ if (entry === "people") {
2358
+ throw new Error('planningCenter() excludeApps cannot exclude "people": every product joins through People person ids, and the credential test reads /people/v2/me.');
2359
+ }
2360
+ if (typeof entry !== "string" || !isApp(entry)) {
2361
+ const lower = typeof entry === "string" ? entry.trim().toLowerCase() : "";
2362
+ throw new Error(`planningCenter() excludeApps has unknown app ${JSON.stringify(entry)}; expected any of ${excludable}.${isApp(lower) && lower !== "people" ? ` App names are lowercase path segments: "${lower}".` : ""}`);
2363
+ }
2364
+ excluded.add(entry);
2365
+ }
2366
+ return excluded;
2114
2367
  }
2115
2368
  /** The closed options planningCenter() accepts; see `assertKnownOptions`. */
2116
2369
  const PLANNING_CENTER_OPTIONS = optionsOf()({
2117
2370
  ...PROVIDER_COMMON,
2118
- ...keys("userAgent", "defaultPageSize", "baseUrl"),
2371
+ ...keys("userAgent", "defaultPageSize", "excludeApps", "baseUrl"),
2119
2372
  });
2120
2373
  /** A maintained Planning Center Online connection over the REST API. */
2121
2374
  export const planningCenter = asProviderFactory({
@@ -2145,19 +2398,25 @@ function planningCenterConnector(id, options) {
2145
2398
  if (!userAgent || /[\r\n]/.test(userAgent)) {
2146
2399
  throw new Error("planningCenter() userAgent must be a non-empty single line.");
2147
2400
  }
2401
+ const excluded = excludedApps(options.excludeApps);
2148
2402
  const auth = PERSONAL_ACCESS_TOKEN;
2403
+ const baseUrl = options.baseUrl?.trim() || PLANNING_CENTER_API_BASE_URL;
2149
2404
  const send = guardedFetch({
2150
2405
  provider: "Planning Center",
2151
- baseUrl: options.baseUrl?.trim() || PLANNING_CENTER_API_BASE_URL,
2406
+ baseUrl,
2152
2407
  headers: { Accept: "application/json", "User-Agent": userAgent },
2153
2408
  maxResponseBytes: PLANNING_CENTER_MAX_RESPONSE_BYTES,
2154
2409
  authenticate: (ctx) => auth.headers(ctx),
2410
+ ...(excluded.size > 0
2411
+ ? { admit: (url, request) => admit(baseUrl, url, request, excluded) }
2412
+ : {}),
2155
2413
  });
2156
- const call = planningCenterCall(send, PLANNING_CENTER_API_VERSIONS);
2414
+ const call = planningCenterCall(send, PLANNING_CENTER_API_VERSIONS, excluded);
2415
+ const surface = tools(call, defaultPageSize, excluded);
2157
2416
  return api(id, {
2158
2417
  ...(options.authScope ? { authScope: options.authScope } : {}),
2159
2418
  title: options.title ?? "Planning Center",
2160
- description: `Planning Center church management — People, Services, Groups, Check-Ins, Calendar, Registrations, and Giving: ${purpose}`,
2419
+ description: `Planning Center church management — ${series(DESCRIBED_APPS.filter((app) => !excluded.has(app)).map((app) => APP_TITLES[app]))}: ${purpose}`,
2161
2420
  credential: auth.credential,
2162
2421
  async testCredentials(values, ctx) {
2163
2422
  try {
@@ -2186,7 +2445,7 @@ function planningCenterConnector(id, options) {
2186
2445
  },
2187
2446
  callAdmission: options.callAdmission ?? PLANNING_CENTER_ADMISSION,
2188
2447
  usageGuide: {
2189
- content: usageGuide(purpose, options.instructions),
2448
+ content: usageGuide(purpose, options.instructions, excluded),
2190
2449
  summary: "Person ids join every product, offset paging, per-product write limits, JSON:API hatches, pinned versions.",
2191
2450
  // Deliberately not `required`. Every named tool's schema is complete
2192
2451
  // enough to call it on its own; the guide carries the cross-product
@@ -2194,6 +2453,9 @@ function planningCenterConnector(id, options) {
2194
2453
  // it leaves the named surface or hits one of them.
2195
2454
  },
2196
2455
  ...(options.maxResultBytes !== undefined ? { maxResultBytes: options.maxResultBytes } : {}),
2197
- tools: tools(call, defaultPageSize),
2456
+ tools: [
2457
+ ...surface.hatches,
2458
+ ...PLANNING_CENTER_APPS.flatMap((app) => (excluded.has(app) ? [] : surface.apps[app])).filter((tool) => excluded.size === 0 || !LIST_TOOLS.has(tool.name)),
2459
+ ],
2198
2460
  });
2199
2461
  }
@@ -1,10 +1,30 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "# Planning Center usage\n\nPlanning Center Online church management — People, Services, Groups, Check-Ins, Calendar, Registrations, Giving, Publishing, Webhooks — for: \n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the `personId` for Services schedules, Groups memberships, Giving donations, and Check-Ins. Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n- `get_plan` fetches the plan, its items, and its team in one call and sets `itemsTruncated`/`teamMembersTruncated` past 100 rows; page the rest with `pco_api_get`.\n\n## What each product lets the API write\n\n- People: full read/write. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`. `run_list` can fire list automations.\n- Services: full read/write. A plan's dates come from its plan times. Scheduling needs a team.\n- Groups: memberships and group settings are writable; groups cannot be created, and events and attendance are read-only.\n- Check-Ins and Registrations: read-only. Nobody can be checked in or registered through the API.\n- Calendar: events, instances, and times are read-only; tags, resources, rooms, and folders are writable through `pco_api_mutate`.\n- Giving: the named tools only read, amounts in integer cents. Creating or editing donations, refunds, and committing a batch move money and go through `pco_api_mutate`.\n- Publishing and the `api` product (organization, OAuth applications, personal access tokens) have no named tools; use the hatches.\n\n## The hatches\n\n- `pco_api_get` reaches any GET; `pco_api_mutate` any POST, PATCH, or DELETE, always approval-gated. Paths start `/<app>/v2`, exactly as Planning Center documents them; the connector owns host, auth, content type, and the `X-PCO-API-Version` header.\n- Bodies are JSON:API documents: `{\"data\":{\"type\":\"Email\",\"attributes\":{...}}}`. `null` clears an attribute; an omitted key is left alone.\n- Each product is pinned to a reviewed API version: . Pass `version` only for an endpoint those versions lack.\n- Query parameters are name/value pairs with literal brackets: `where[status]`, `include`, `order`, `filter`, `fields[Person]`. Every list response's `meta` names the `can_query_by`, `can_order_by`, and `can_include` values for that endpoint; an unknown `where` key is ignored rather than refused.\n- File uploads go to a separate host (upload.planningcenteronline.com), which this connection does not reach.\n\n## Rate limits\n\nPlanning Center allows 100 requests per 20 seconds per user, shared by every integration running as that user. This connection admits 100 calls per 20 seconds, five at a time, per runtime — an approximation, not a guarantee. A `rate_limited` failure carries Planning Center's wait. Filter server-side and use `totalCount` rather than paging to count.\n",
3
+ "content": "# Planning Center usage\n\nPlanning Center Online church management — People, Services, Groups, Check-Ins, Calendar, Registrations, Giving, Publishing, Webhooks.\n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the `personId` for Services schedules, Groups memberships, Giving donations, and Check-Ins. Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n- `get_plan` fetches the plan, its items, and its team in one call and sets `itemsTruncated`/`teamMembersTruncated` past 100 rows; page the rest with `pco_api_get`.\n\n## What each product lets the API write\n\n- People: full read/write. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`. `run_list` can fire list automations.\n- Services: full read/write. A plan's dates come from its plan times. Scheduling needs a team.\n- Groups: memberships and group settings are writable; groups cannot be created, and events and attendance are read-only.\n- Check-Ins and Registrations: read-only. Nobody can be checked in or registered through the API.\n- Calendar: events, instances, and times are read-only; tags, resources, rooms, and folders are writable through `pco_api_mutate`.\n- Giving: the named tools only read, amounts in integer cents. Creating or editing donations, refunds, and committing a batch move money and go through `pco_api_mutate`.\n- Publishing and the `api` product (organization, OAuth applications, personal access tokens) have no named tools; use the hatches.\n\n## The hatches\n\n- `pco_api_get` reaches any GET; `pco_api_mutate` any POST, PATCH, or DELETE, always approval-gated. Paths start `/<app>/v2`, exactly as Planning Center documents them; the connector owns host, auth, content type, and the `X-PCO-API-Version` header.\n- Bodies are JSON:API documents: `{\"data\":{\"type\":\"Email\",\"attributes\":{...}}}`. `null` clears an attribute; an omitted key is left alone.\n- Each product is pinned to a reviewed API version: people 2026-06-04, services 2018-11-01, groups 2023-07-10, check-ins 2025-05-28, calendar 2026-06-22, registrations 2025-05-01, giving 2019-10-18, publishing 2024-03-25, webhooks 2022-10-20, api 2026-09-24. Pass `version` only for an endpoint those versions lack.\n- Query parameters are name/value pairs with literal brackets: `where[status]`, `include`, `order`, `filter`, `fields[Person]`. Every list response's `meta` names the `can_query_by`, `can_order_by`, and `can_include` values for that endpoint; an unknown `where` key is ignored rather than refused.\n- File uploads go to a separate host (upload.planningcenteronline.com), which this connection does not reach.\n\n## Rate limits\n\nPlanning Center allows 100 requests per 20 seconds per user, shared by every integration running as that user. This connection admits 100 calls per 20 seconds, five at a time, per runtime — an approximation, not a guarantee. A `rate_limited` failure carries Planning Center's wait. Filter server-side and use `totalCount` rather than paging to count.\n",
4
4
  "instructionsHeading": "Organization instructions",
5
5
  "fragments": {
6
- "guide_0": "# Planning Center usage\n\nPlanning Center Online church management — People, Services, Groups, Check-Ins, Calendar, Registrations, Giving, Publishing, Webhooks — for: ",
7
- "guide_1": "\n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the `personId` for Services schedules, Groups memberships, Giving donations, and Check-Ins. Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n- `get_plan` fetches the plan, its items, and its team in one call and sets `itemsTruncated`/`teamMembersTruncated` past 100 rows; page the rest with `pco_api_get`.\n\n## What each product lets the API write\n\n- People: full read/write. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`. `run_list` can fire list automations.\n- Services: full read/write. A plan's dates come from its plan times. Scheduling needs a team.\n- Groups: memberships and group settings are writable; groups cannot be created, and events and attendance are read-only.\n- Check-Ins and Registrations: read-only. Nobody can be checked in or registered through the API.\n- Calendar: events, instances, and times are read-only; tags, resources, rooms, and folders are writable through `pco_api_mutate`.\n- Giving: the named tools only read, amounts in integer cents. Creating or editing donations, refunds, and committing a batch move money and go through `pco_api_mutate`.\n- Publishing and the `api` product (organization, OAuth applications, personal access tokens) have no named tools; use the hatches.\n\n## The hatches\n\n- `pco_api_get` reaches any GET; `pco_api_mutate` any POST, PATCH, or DELETE, always approval-gated. Paths start `/<app>/v2`, exactly as Planning Center documents them; the connector owns host, auth, content type, and the `X-PCO-API-Version` header.\n- Bodies are JSON:API documents: `{\"data\":{\"type\":\"Email\",\"attributes\":{...}}}`. `null` clears an attribute; an omitted key is left alone.\n- Each product is pinned to a reviewed API version: ",
8
- "guide_2": ". Pass `version` only for an endpoint those versions lack.\n- Query parameters are name/value pairs with literal brackets: `where[status]`, `include`, `order`, `filter`, `fields[Person]`. Every list response's `meta` names the `can_query_by`, `can_order_by`, and `can_include` values for that endpoint; an unknown `where` key is ignored rather than refused.\n- File uploads go to a separate host (upload.planningcenteronline.com), which this connection does not reach.\n\n## Rate limits\n\nPlanning Center allows 100 requests per 20 seconds per user, shared by every integration running as that user. This connection admits 100 calls per 20 seconds, five at a time, per runtime — an approximation, not a guarantee. A `rate_limited` failure carries Planning Center's wait. Filter server-side and use `totalCount` rather than paging to count.\n"
6
+ "heading": "# Planning Center usage\n\nPlanning Center Online church management — ",
7
+ "purpose": " — for: ",
8
+ "identity": "\n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the `personId` for ",
9
+ "conventions": ". Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n",
10
+ "plans": "- `get_plan` fetches the plan, its items, and its team in one call and sets `itemsTruncated`/`teamMembersTruncated` past 100 rows; page the rest with `pco_api_get`.\n",
11
+ "writes": "\n## What each product lets the API write\n\n",
12
+ "people": "- People: full read/write. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`. `run_list` can fire list automations.\n",
13
+ "people_without_lists": "- People: full read/write, except lists. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`.\n",
14
+ "services": "- Services: full read/write. A plan's dates come from its plan times. Scheduling needs a team.\n",
15
+ "groups": "- Groups: memberships and group settings are writable; groups cannot be created, and events and attendance are read-only.\n",
16
+ "check_ins_registrations": "- Check-Ins and Registrations: read-only. Nobody can be checked in or registered through the API.\n",
17
+ "check_ins": "- Check-Ins: read-only. Nobody can be checked in through the API.\n",
18
+ "registrations": "- Registrations: read-only. Nobody can be registered through the API.\n",
19
+ "calendar": "- Calendar: events, instances, and times are read-only; tags, resources, rooms, and folders are writable through `pco_api_mutate`.\n",
20
+ "giving": "- Giving: the named tools only read, amounts in integer cents. Creating or editing donations, refunds, and committing a batch move money and go through `pco_api_mutate`.\n",
21
+ "publishing_api": "- Publishing and the `api` product (organization, OAuth applications, personal access tokens) have no named tools; use the hatches.\n",
22
+ "publishing": "- Publishing has no named tools; use the hatches.\n",
23
+ "api": "- The `api` product (organization, OAuth applications, personal access tokens) has no named tools; use the hatches.\n",
24
+ "hatches": "\n## The hatches\n\n- `pco_api_get` reaches any GET; `pco_api_mutate` any POST, PATCH, or DELETE, always approval-gated. Paths start `/<app>/v2`, exactly as Planning Center documents them; the connector owns host, auth, content type, and the `X-PCO-API-Version` header.\n- Bodies are JSON:API documents: `{\"data\":{\"type\":\"Email\",\"attributes\":{...}}}`. `null` clears an attribute; an omitted key is left alone.\n- Each product is pinned to a reviewed API version: ",
25
+ "pins": ". Pass `version` only for an endpoint those versions lack.\n- Query parameters are name/value pairs with literal brackets: `where[status]`, `include`, `order`, `filter`, `fields[Person]`. Every list response's `meta` names the `can_query_by`, `can_order_by`, and `can_include` values for that endpoint; an unknown `where` key is ignored rather than refused.\n- File uploads go to a separate host (upload.planningcenteronline.com), which this connection does not reach.\n",
26
+ "disabled": "- This deployment disables some products, and the hatches refuse their paths before sending. While any product is disabled they also refuse People lists, list results, rules, and conditions, by path or `include`, because a list's conditions can draw on any product; webhook event and delivery payloads; and any change to an existing webhook subscription, whose id does not name its product. A new subscription must name a reachable product's event.\n",
27
+ "limits": "\n## Rate limits\n\nPlanning Center allows 100 requests per 20 seconds per user, shared by every integration running as that user. This connection admits 100 calls per 20 seconds, five at a time, per runtime — an approximation, not a guarantee. A `rate_limited` failure carries Planning Center's wait. Filter server-side and use `totalCount` rather than paging to count.\n",
28
+ "published": "# Planning Center usage\n\nPlanning Center Online church management — People, Services, Groups, Check-Ins, Calendar, Registrations, Giving, Publishing, Webhooks.\n\n## Identity first\n\n- A person id is the join key across every product: the id `search_people` returns is the `personId` for Services schedules, Groups memberships, Giving donations, and Check-Ins. Resolve ids with list tools; never guess one.\n- A person id that used to resolve and now answers `not_found` was most likely merged. Read `/people/v2/person_mergers` with `where[person_to_remove_id]` and follow `person_to_keep_id`.\n- `get_me` names the user this token acts as. Every read and write runs with that user's permissions, product by product; a `provider_permission_denied` means an administrator must grant that user access to the product or record.\n\n## Results and paging\n\n- Records keep Planning Center's own snake_case attribute names; a relationship appears as `<relationship>_id`. Pass `raw: true` for the untouched JSON:API resources and `included`.\n- Lists page by offset: `perPage` up to 100, then pass `page.nextOffset` back as `offset` while `page.hasMore` is true. `page.totalCount` answers \"how many\" without paging — request `perPage: 1`.\n- A bare date in a date filter is the organization's local day; pass a full timestamp for exact bounds. Name filters are exact; end one with `%` for a prefix match.\n- `get_plan` fetches the plan, its items, and its team in one call and sets `itemsTruncated`/`teamMembersTruncated` past 100 rows; page the rest with `pco_api_get`.\n\n## What each product lets the API write\n\n- People: full read/write. `update_person` with `status: inactive` archives reversibly; a DELETE is permanent and only reachable through `pco_api_mutate`. `run_list` can fire list automations.\n- Services: full read/write. A plan's dates come from its plan times. Scheduling needs a team.\n- Groups: memberships and group settings are writable; groups cannot be created, and events and attendance are read-only.\n- Check-Ins and Registrations: read-only. Nobody can be checked in or registered through the API.\n- Calendar: events, instances, and times are read-only; tags, resources, rooms, and folders are writable through `pco_api_mutate`.\n- Giving: the named tools only read, amounts in integer cents. Creating or editing donations, refunds, and committing a batch move money and go through `pco_api_mutate`.\n- Publishing and the `api` product (organization, OAuth applications, personal access tokens) have no named tools; use the hatches.\n\n## The hatches\n\n- `pco_api_get` reaches any GET; `pco_api_mutate` any POST, PATCH, or DELETE, always approval-gated. Paths start `/<app>/v2`, exactly as Planning Center documents them; the connector owns host, auth, content type, and the `X-PCO-API-Version` header.\n- Bodies are JSON:API documents: `{\"data\":{\"type\":\"Email\",\"attributes\":{...}}}`. `null` clears an attribute; an omitted key is left alone.\n- Each product is pinned to a reviewed API version: people 2026-06-04, services 2018-11-01, groups 2023-07-10, check-ins 2025-05-28, calendar 2026-06-22, registrations 2025-05-01, giving 2019-10-18, publishing 2024-03-25, webhooks 2022-10-20, api 2026-09-24. Pass `version` only for an endpoint those versions lack.\n- Query parameters are name/value pairs with literal brackets: `where[status]`, `include`, `order`, `filter`, `fields[Person]`. Every list response's `meta` names the `can_query_by`, `can_order_by`, and `can_include` values for that endpoint; an unknown `where` key is ignored rather than refused.\n- File uploads go to a separate host (upload.planningcenteronline.com), which this connection does not reach.\n\n## Rate limits\n\nPlanning Center allows 100 requests per 20 seconds per user, shared by every integration running as that user. This connection admits 100 calls per 20 seconds, five at a time, per runtime — an approximation, not a guarantee. A `rate_limited` failure carries Planning Center's wait. Filter server-side and use `totalCount` rather than paging to count.\n"
9
29
  }
10
30
  };
@@ -48,7 +48,7 @@ export type SheetsOptions = GoogleWorkspaceOptions;
48
48
  *
49
49
  * Reads are read-only. `create_spreadsheet`, `add_sheet`, and `append_values`
50
50
  * are additive writes the host approves through call_destructive_tool or a
51
- * trusted program; the value writes, `clear_values`, and the raw
51
+ * program on a writable endpoint; the value writes, `clear_values`, and the raw
52
52
  * `batch_update_spreadsheet` are destructive.
53
53
  */
54
54
  export declare const sheets: import("../../provider.js").ProviderFactory<GoogleWorkspaceOptions>;
@@ -550,8 +550,8 @@ const SPREADSHEET_FIELDS = [
550
550
  // --- Tools ------------------------------------------------------------------------
551
551
  function tools(client) {
552
552
  const readOnly = { readOnlyHint: true };
553
- // Additive: nothing that existed is lost. Program writes require a trusted
554
- // pool; other writes use call_destructive_tool. The host controls approval.
553
+ // Additive: nothing that existed is lost. Writes require a writable
554
+ // endpoint: call_destructive_tool or a program. The host controls approval.
555
555
  const additive = { readOnlyHint: false, destructiveHint: false };
556
556
  const destructive = { readOnlyHint: false, destructiveHint: true };
557
557
  const spreadsheetPath = (args) => `/spreadsheets/${encodeURIComponent(String(args["spreadsheetId"]))}`;
@@ -1188,7 +1188,7 @@ function usageGuide(purpose, instructions) {
1188
1188
  *
1189
1189
  * Reads are read-only. `create_spreadsheet`, `add_sheet`, and `append_values`
1190
1190
  * are additive writes the host approves through call_destructive_tool or a
1191
- * trusted program; the value writes, `clear_values`, and the raw
1191
+ * program on a writable endpoint; the value writes, `clear_values`, and the raw
1192
1192
  * `batch_update_spreadsheet` are destructive.
1193
1193
  */
1194
1194
  export const sheets = asProviderFactory({
@@ -45,7 +45,7 @@ export type SlidesOptions = GoogleWorkspaceOptions;
45
45
  *
46
46
  * Reads run in programs. `create_presentation`, `create_slide`,
47
47
  * `create_comment`, and `create_comment_reply` are additive writes the host
48
- * approves through call_destructive_tool or a trusted program;
48
+ * approves through call_destructive_tool or a program on a writable endpoint;
49
49
  * `replace_all_text`, `update_comment_thread`, `update_comment_post`,
50
50
  * `delete_comment`, `delete_comment_reply`, and `batch_update_presentation`
51
51
  * are destructive.
@@ -2051,7 +2051,7 @@ function tools(client) {
2051
2051
  name: "create_presentation",
2052
2052
  description: "Create an empty Google Slides deck with one title slide in the user's My Drive root. It cannot place the deck in a folder or copy a template.",
2053
2053
  // Additive: a new file changes nothing that existed. Not read-only, so
2054
- // program calls require a trusted pool; other writes use
2054
+ // program calls require a writable endpoint; other writes use
2055
2055
  // call_destructive_tool. The host controls approval.
2056
2056
  annotations: { readOnlyHint: false, destructiveHint: false },
2057
2057
  inputSchema: input({
@@ -2239,7 +2239,7 @@ function tools(client) {
2239
2239
  name: "create_comment",
2240
2240
  description: "Comment on a slide, a notes page, an element, a range of its text, or a table cell, optionally assigning it. Adds a thread; Slides notifies as in the editor.",
2241
2241
  // Additive: a new thread changes nothing that existed. Not read-only,
2242
- // so program calls require a trusted pool; other writes use
2242
+ // so program calls require a writable endpoint; other writes use
2243
2243
  // call_destructive_tool. The host controls approval.
2244
2244
  annotations: { readOnlyHint: false, destructiveHint: false },
2245
2245
  inputSchema: input({
@@ -2594,7 +2594,7 @@ function usageGuide(purpose, instructions) {
2594
2594
  *
2595
2595
  * Reads run in programs. `create_presentation`, `create_slide`,
2596
2596
  * `create_comment`, and `create_comment_reply` are additive writes the host
2597
- * approves through call_destructive_tool or a trusted program;
2597
+ * approves through call_destructive_tool or a program on a writable endpoint;
2598
2598
  * `replace_all_text`, `update_comment_thread`, `update_comment_post`,
2599
2599
  * `delete_comment`, `delete_comment_reply`, and `batch_update_presentation`
2600
2600
  * are destructive.