@bettercms-ai/mcp 0.79.0 → 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.";
|
|
@@ -3017,6 +3032,7 @@ var PARAM_TEXT = {
|
|
|
3017
3032
|
runtimeBundle: "instead of pageId: the server-app bundle cleanup",
|
|
3018
3033
|
pageRef: "page id or path ('/de/about'); a bare slug only when one page has it",
|
|
3019
3034
|
editorSetupForce: "re-run a setup already ready for the live build",
|
|
3035
|
+
nextStepsFull: "true = every list uncut",
|
|
3020
3036
|
editorSetupCheck: "true = only report the state; starts nothing",
|
|
3021
3037
|
waitSeconds: "wait up to N seconds (max 50) for it to finish; call again while it runs",
|
|
3022
3038
|
editorSetupExempt: "instead of running: record setup-gate exceptions [{ route, check, reason, note }]; reason null removes one. Reasons: get_next_steps `receipt.gate`",
|
|
@@ -3040,6 +3056,23 @@ var PARAM_TEXT = {
|
|
|
3040
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",
|
|
3041
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"
|
|
3042
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
|
+
};
|
|
3043
3076
|
var INPUT_CONFIRMED_HEADER = "x-bcms-confirmed-input";
|
|
3044
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.";
|
|
3045
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}.";
|
|
@@ -3067,6 +3100,10 @@ var AUTHORING_DESCRIPTION = "Record which authoring architecture this site uses:
|
|
|
3067
3100
|
var MEDIA_DEFAULT_NOTE = "Leave the default (the CDN) unless the owner asked otherwise; never ask.";
|
|
3068
3101
|
var REBUILD_DESCRIPTION = "Rebuild from the connected repository, as the dashboard's Rebuild does. Use it when a get_next_steps fix names it, or after a push that started no build; then get_deploy_status { waitSeconds: 50 } until live, and editor_setup. 409 NOT_QUEUED: no repository (deploy_project the working tree) or publishing held.";
|
|
3069
3102
|
var withWait = (path, waitSeconds) => typeof waitSeconds === "number" && waitSeconds > 0 ? `${path}?wait=${Math.min(Math.round(waitSeconds), 50)}` : path;
|
|
3103
|
+
var nextStepsPath = (path, a) => {
|
|
3104
|
+
const q = [a.importJunk === true ? "importJunk=true" : "", a.full === true ? "full=true" : ""].filter(Boolean).join("&");
|
|
3105
|
+
return q ? `${path}?${q}` : path;
|
|
3106
|
+
};
|
|
3070
3107
|
|
|
3071
3108
|
// src/entry-query.ts
|
|
3072
3109
|
var text = (v) => Array.isArray(v) ? v.map(String).join(",") : String(v);
|
|
@@ -3506,6 +3543,16 @@ function proseType(f) {
|
|
|
3506
3543
|
}
|
|
3507
3544
|
var confirmedHeaders = (confirmed) => confirmed === true ? { [INPUT_CONFIRMED_HEADER]: "true" } : {};
|
|
3508
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
|
+
};
|
|
3509
3556
|
var DRY_RUN_SUMMARY = "Dry run: nothing was written.";
|
|
3510
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) }));
|
|
3511
3558
|
function toField(f) {
|
|
@@ -5175,6 +5222,83 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
5175
5222
|
// The whole body: a `test` delivery's outcome rides beside `data`.
|
|
5176
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 }) }))
|
|
5177
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
|
+
),
|
|
5178
5302
|
def(
|
|
5179
5303
|
"list_branches",
|
|
5180
5304
|
"List content branches",
|
|
@@ -5258,10 +5382,11 @@ ${d.outline}` : "Proposed Content structure.", d);
|
|
|
5258
5382
|
"Get what to do next",
|
|
5259
5383
|
NEXT_STEPS_DESCRIPTION,
|
|
5260
5384
|
z.object({
|
|
5261
|
-
importJunk: z.boolean().optional().describe("true = add rule import-junk: what an old import left, with removal calls")
|
|
5385
|
+
importJunk: z.boolean().optional().describe("true = add rule import-junk: what an old import left, with removal calls"),
|
|
5386
|
+
full: z.boolean().optional().describe(PARAM_TEXT.nextStepsFull)
|
|
5262
5387
|
}).shape,
|
|
5263
5388
|
// The whole body: `receipt` rides beside `data`, and `data()` would drop it.
|
|
5264
|
-
async (c, a) => ok("Next steps.", await c.fetchJSON(c.url(`/management/insights/next-steps
|
|
5389
|
+
async (c, a) => ok("Next steps.", await c.fetchJSON(c.url(nextStepsPath(`/management/insights/next-steps`, a)), { method: "GET" }))
|
|
5265
5390
|
),
|
|
5266
5391
|
def(
|
|
5267
5392
|
"get_binding_report",
|