@bettercms-ai/mcp 0.79.1 → 0.80.1

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/index.js CHANGED
@@ -2907,6 +2907,14 @@ var LISTING_TAIL = /* @__PURE__ */ new Set([
2907
2907
  "generate_image",
2908
2908
  "transform_image",
2909
2909
  "write_alt_text",
2910
+ // Custom integrations: reached for once a site exists and its owner names a vendor.
2911
+ "list_integrations",
2912
+ "get_integration",
2913
+ "create_integration",
2914
+ "update_integration",
2915
+ "delete_integration",
2916
+ "test_integration_fetch",
2917
+ "run_integration_sync",
2910
2918
  // The ABM campaign lane — also hidden from the default listing (ABM_TOOLS below).
2911
2919
  "research_account",
2912
2920
  "build_abm_page",
@@ -2990,7 +2998,14 @@ var NEW_TOOL_TEXT = {
2990
2998
  import_redirects: "Bulk-create redirects for a migration, from `rows` or from `text` in `format` (our CSV export, a Netlify `_redirects` file, or a vercel.json). Call it with `dryRun: true` FIRST: nothing is written and each row comes back `would-create`, `existing` or `skipped` with a reason. Rows get create_redirect's rules. Max 5000.",
2991
2999
  editor_setup: "Make the live build editable: derives pages, collections, forms, bindings and components from the live release (skill bindings). Run it after an import or a rebuild, before the conversion brief. Idempotent: `ready` when it already ran for the live sha, else it starts (`running`; poll with `check: true`). `state`: never or stale (run it), running, ready, failed. 409 = nothing is live yet: deploy first. Then get_next_steps.",
2992
3000
  transform_image: "Edit an existing Media Library image with an instruction (e.g. 'remove the background', 'make it dusk'); the result is saved as a NEW asset and the original is kept (5 AI credits). Returns the new asset (id, url, altText).",
2993
- write_alt_text: "Alt text for a Media Library image (mediaId) or an https image URL (url), written from its pixels (1 AI credit). A suggestion: save it with update_media."
3001
+ write_alt_text: "Alt text for a Media Library image (mediaId) or an https image URL (url), written from its pixels (1 AI credit). A suggestion: save it with update_media.",
3002
+ list_integrations: "List the project's custom integrations (any vendor with a public API: jobs boards, stores, CRMs, video). Each has its allowed hosts, secret NAMES with `secretsPresent` (is the env var set; never the value), `inject` (where the secrets go), syncs, panel and `approved`. Read bettercms://skill/integrations before building one.",
3003
+ get_integration: "One integration (`integration`: id or slug) with its last 20 sync runs (status, counts, error).",
3004
+ create_integration: "Declare a custom integration: `allowedHosts` (exact hostnames the server may call), `secrets` (template name -> project env var KEY), `inject` (the headers / query parameters that carry them on every request, e.g. {headers: {authorization: 'Bearer {{secret.TOKEN}}'}}), `allowPost` (GET only unless true), optional `auth` token exchange (inject {{token}}), `syncs` (vendor list -> collection) and `panel`. Secret VALUES never pass through here: the user sets them under Settings \u2192 Environment variables (production); list_env_vars shows the names. Until an admin approves it, vendor calls and syncs are refused: give the user `approval.approveUrl` and the `approval.changes` in plain words. You cannot approve it yourself.",
3005
+ update_integration: "Change an integration (`integration`: id or slug); keys you leave out are kept, and `secrets`, `inject` and `syncs` replace the stored ones whole. Changing `allowedHosts`, `secrets`, `inject`, `allowPost` or `auth` withdraws its approval until an admin approves again (`approval.approveUrl`). Name, syncs, settings and panel never need approval.",
3006
+ delete_integration: "Delete an integration and its sync runs. Entries it synced stay (their provenance still names it); the env vars stay.",
3007
+ run_integration_sync: "Run one of an integration's syncs now (`integration`, `sync`: its key): every page of the vendor list is fetched and each item is created or updated as a DRAFT entry, matched on its externalKey, so a re-run only changes what changed (`unchanged` counts the rest); `publish: true` on the sync publishes what changed. Answers with the run: queued, running, done (counts: created, updated, unchanged, failed, pages, items) or failed (error). `waitSeconds` (\u2264 20) waits for it to finish; else follow it with get_integration. A sync with `schedule` 'hourly' or 'daily' also runs on its own.",
3008
+ test_integration_fetch: "Call the vendor once through the server, as the integration: an approved host only, with the integration's `inject` secrets added server-side and every secret redacted from the answer. The request itself takes no {{...}} templates, and the token endpoint cannot be called. Use it to read the response shape before writing a sync: `items`, `externalKey` and `map` are JSON Pointers into it. Bodies over 1 MB come back truncated. 409 integration_not_approved: an admin must approve it first."
2994
3009
  };
2995
3010
  var DELETE_PAGE_RUNTIME_BUNDLE = " Or, with no pageId, `runtimeBundle`: 'report' lists the duplicate pages derived from a server app's own bundle (`app/` folders); 'apply' retires them and their empty folders.";
2996
3011
  var FIELD_LOCKED_NOTE = "A readOnly 'developer' field keeps its stored value: changing or omitting it without schema access is 403 FIELD_LOCKED.";
@@ -3041,6 +3056,23 @@ var PARAM_TEXT = {
3041
3056
  mergeHref: "pageId only: the link field paired with `path` (the label). Both become ONE `button` field keyed `key` (default: their stem, primaryCtaHref \u2192 primaryCta). Each entry's DRAFT gets {label, href} and keeps the old values; the live copy changes on publish. ifMatch is the page's version. 409 CODE_RENDERED_ROUTE: do its `next` recipe in code",
3042
3057
  confirmedInput: "true only after the user explicitly approved adding this custom input (`ui.input`); else setting or changing one is refused input_needs_confirmation. Removing one needs no approval"
3043
3058
  };
3059
+ var INTEGRATION_PARAM_TEXT = {
3060
+ integration: "the integration's id or slug (from list_integrations)",
3061
+ slug: "lowercase letters, digits, hyphens; at most 48",
3062
+ allowedHosts: "exact hostnames the server may call, e.g. ['boards-api.greenhouse.io']; https only",
3063
+ secrets: "template name (UPPER_SNAKE) -> project env var KEY, e.g. {TOKEN: 'SHOPIFY_ADMIN_TOKEN'}; never a value",
3064
+ inject: "where the secrets go on every vendor request: {headers?: {name: template}, query?: {name: template}}, templates {{secret.NAME}} or {{token}}",
3065
+ allowPost: "true lets requests other than GET through (vendor writes, POST search APIs such as Apollo's); GET only when omitted",
3066
+ auth: "optional token exchange: {request:{method,url,headers?,query?,form?}, tokenPath, expiresInPath?, ttlSeconds?}; {{secret.NAME}} in its values; its result is {{token}}",
3067
+ settings: "non-secret JSON the panel reads and writes (store id, toggles); at most 16 KB",
3068
+ syncs: "[{key, contentModelId, request:{method,url,headers?,query?}, pagination?:{style:'none'|'page'|'cursor'|'link', param?, cursorPath?, maxPages?}, items, externalKey, slug?, map:{fieldKey: pointer | {path, transform?:'string'|'number'|'boolean'|'join'}}, publish?, schedule?:'hourly'|'daily'|null}]; pointers are JSON Pointers",
3069
+ panel: "the custom panel's slug: bettercms/panels/<panel>.tsx in the site repo; null for the stock page",
3070
+ url: "the full https URL on an allowed host (no templates in the URL)",
3071
+ sync: "the sync's key (from the integration's syncs)",
3072
+ headers: "header -> value (no templates: the integration's inject adds the secrets)",
3073
+ query: "query parameter -> value (no templates)",
3074
+ json: "a JSON body for POST"
3075
+ };
3044
3076
  var INPUT_CONFIRMED_HEADER = "x-bcms-confirmed-input";
3045
3077
  var BUTTON_FIELD_NOTE = "'button' is ONE call to action, value {label, href, variant? (a Button family look), size?, arrow?, icon?, newTab?, rel?, fullWidth?} (arrow:false, icon:null off): never a text field beside a link field.";
3046
3078
  var LINK_VALUE_NOTE = "A link or button href: a URL string or {type: url|page|entry|email|phone|anchor|file, href, title?, newTab?, rel?, section?, ref: page/entry id}.";
@@ -3511,6 +3543,16 @@ function proseType(f) {
3511
3543
  }
3512
3544
  var confirmedHeaders = (confirmed) => confirmed === true ? { [INPUT_CONFIRMED_HEADER]: "true" } : {};
3513
3545
  var withHeaders = (headers) => Object.keys(headers).length > 0 ? { headers } : {};
3546
+ var integrationShape = {
3547
+ allowedHosts: z.array(z.string()).max(10).optional().describe(INTEGRATION_PARAM_TEXT.allowedHosts),
3548
+ secrets: z.record(z.string(), z.string()).optional().describe(INTEGRATION_PARAM_TEXT.secrets),
3549
+ inject: z.object({ headers: z.record(z.string(), z.string()).optional(), query: z.record(z.string(), z.string()).optional() }).optional().describe(INTEGRATION_PARAM_TEXT.inject),
3550
+ allowPost: z.boolean().optional().describe(INTEGRATION_PARAM_TEXT.allowPost),
3551
+ auth: z.record(z.string(), z.unknown()).nullable().optional().describe(INTEGRATION_PARAM_TEXT.auth),
3552
+ settings: z.record(z.string(), z.unknown()).optional().describe(INTEGRATION_PARAM_TEXT.settings),
3553
+ syncs: z.array(z.record(z.string(), z.unknown())).max(20).optional().describe(INTEGRATION_PARAM_TEXT.syncs),
3554
+ panel: z.string().max(48).nullable().optional().describe(INTEGRATION_PARAM_TEXT.panel)
3555
+ };
3514
3556
  var DRY_RUN_SUMMARY = "Dry run: nothing was written.";
3515
3557
  var dryRunOf = async (client, method, path, body, headers = {}) => ok(DRY_RUN_SUMMARY, await client.fetchJSON(client.url(path), { method, body: JSON.stringify({ ...body, dryRun: true }), ...withHeaders(headers) }));
3516
3558
  function toField(f) {
@@ -5180,6 +5222,83 @@ ${d.outline}` : "Proposed Content structure.", d);
5180
5222
  // The whole body: a `test` delivery's outcome rides beside `data`.
5181
5223
  async (c, a) => ok("Webhook created. Store the `secret` now: it is never shown again.", await c.fetchJSON(c.url(`/management/webhooks`), { method: "POST", body: JSON.stringify({ name: a.name, url: a.url, events: a.events, isActive: a.isActive, test: a.test }) }))
5182
5224
  ),
5225
+ def(
5226
+ "list_integrations",
5227
+ "List custom integrations",
5228
+ NEW_TOOL_TEXT.list_integrations,
5229
+ z.object({}).shape,
5230
+ async (c) => ok("Integrations.", await data(c, "GET", `/management/integrations`))
5231
+ ),
5232
+ def(
5233
+ "get_integration",
5234
+ "Get a custom integration",
5235
+ NEW_TOOL_TEXT.get_integration,
5236
+ z.object({ integration: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.integration) }).shape,
5237
+ async (c, a) => ok("Integration.", await data(c, "GET", `/management/integrations/${s(a.integration)}`))
5238
+ ),
5239
+ def(
5240
+ "create_integration",
5241
+ "Create a custom integration",
5242
+ NEW_TOOL_TEXT.create_integration,
5243
+ z.object({
5244
+ slug: z.string().min(1).max(48).describe(INTEGRATION_PARAM_TEXT.slug),
5245
+ name: z.string().min(1).max(120).describe("a label, 1-120 chars"),
5246
+ vendor: z.string().max(60).optional().describe("the vendor's name, e.g. 'Greenhouse'"),
5247
+ ...integrationShape
5248
+ }).shape,
5249
+ // The whole body: `approval` rides beside `data`.
5250
+ async (c, a) => ok("Integration created.", await c.fetchJSON(c.url(`/management/integrations`), { method: "POST", body: JSON.stringify(a) }))
5251
+ ),
5252
+ def(
5253
+ "update_integration",
5254
+ "Update a custom integration",
5255
+ NEW_TOOL_TEXT.update_integration,
5256
+ z.object({
5257
+ integration: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.integration),
5258
+ name: z.string().min(1).max(120).optional().describe("a label, 1-120 chars"),
5259
+ vendor: z.string().max(60).nullable().optional().describe("the vendor's name"),
5260
+ ...integrationShape
5261
+ }).shape,
5262
+ async (c, a) => {
5263
+ const { integration, ...body } = a;
5264
+ return ok("Integration updated.", await c.fetchJSON(c.url(`/management/integrations/${s(integration)}`), { method: "PATCH", body: JSON.stringify(body) }));
5265
+ }
5266
+ ),
5267
+ def(
5268
+ "delete_integration",
5269
+ "Delete a custom integration",
5270
+ NEW_TOOL_TEXT.delete_integration,
5271
+ z.object({ integration: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.integration) }).shape,
5272
+ async (c, a) => ok("Integration deleted.", await data(c, "DELETE", `/management/integrations/${s(a.integration)}`))
5273
+ ),
5274
+ def(
5275
+ "run_integration_sync",
5276
+ "Run an integration's sync",
5277
+ NEW_TOOL_TEXT.run_integration_sync,
5278
+ z.object({
5279
+ integration: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.integration),
5280
+ sync: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.sync),
5281
+ waitSeconds: z.number().min(0).max(50).optional().describe(PARAM_TEXT.waitSeconds)
5282
+ }).shape,
5283
+ async (c, a) => ok("Sync run.", await data(c, "POST", withWait(`/management/integrations/${s(a.integration)}/syncs/${s(a.sync)}/run`, a.waitSeconds)))
5284
+ ),
5285
+ def(
5286
+ "test_integration_fetch",
5287
+ "Call a vendor through an integration",
5288
+ NEW_TOOL_TEXT.test_integration_fetch,
5289
+ z.object({
5290
+ integration: z.string().min(1).describe(INTEGRATION_PARAM_TEXT.integration),
5291
+ url: z.string().url().describe(INTEGRATION_PARAM_TEXT.url),
5292
+ method: z.enum(["GET", "POST"]).optional().describe("GET when omitted"),
5293
+ headers: z.record(z.string(), z.string()).optional().describe(INTEGRATION_PARAM_TEXT.headers),
5294
+ query: z.record(z.string(), z.string()).optional().describe(INTEGRATION_PARAM_TEXT.query),
5295
+ json: z.unknown().optional().describe(INTEGRATION_PARAM_TEXT.json)
5296
+ }).shape,
5297
+ async (c, a) => {
5298
+ const { integration, ...req } = a;
5299
+ return ok("Vendor response.", await data(c, "POST", `/management/integrations/${s(integration)}/fetch`, req));
5300
+ }
5301
+ ),
5183
5302
  def(
5184
5303
  "list_branches",
5185
5304
  "List content branches",