@mapled/mcp 0.12.0 → 0.13.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/README.md +7 -3
- package/dist/tools.js +50 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
MCP server for [Mapled](https://mapled.io) — a hosted headless CMS built for sites created with AI. Connect it to Claude, Cursor, or any MCP-capable agent, and the agent can model collections, fill in content, and wire the site up end to end.
|
|
4
4
|
|
|
5
|
+
No agent at hand? Everything an agent does here can be done by a person: [Integrate by hand](https://www.npmjs.com/package/@mapled/next#integrate-by-hand) in the `@mapled/next` README is the same integration, step by step.
|
|
6
|
+
|
|
5
7
|
## Setup
|
|
6
8
|
|
|
7
9
|
The quickest way is the hosted endpoint with OAuth — no token to copy. In Claude Code:
|
|
@@ -14,7 +16,6 @@ Your browser opens Mapled, you pick the project and authorize; the connection ap
|
|
|
14
16
|
|
|
15
17
|
The local stdio server below is the alternative for clients without OAuth support.
|
|
16
18
|
|
|
17
|
-
|
|
18
19
|
Create a token in Mapled: project → Integrations → **Connect with AI**. Then add the server to your agent's MCP config:
|
|
19
20
|
|
|
20
21
|
```json
|
|
@@ -48,9 +49,11 @@ For the first integration, propose one plan and let the owner approve it on a tr
|
|
|
48
49
|
5. Wire the site: `get_connection`, `configure_revalidation`, deploy, then close with `report_setup` (files changed, `buildPassed`, `secretsCommitted: false`). Mapled runs its own checks — the site reads content, the webhook delivered, the preview route responds, fields have help texts — and the run is completed only when they pass. Fix what failed and call `verify_setup`.
|
|
49
50
|
6. Leave a guide: `get_mapled_md` renders `MAPLED.md` from the project — what the site reads and where, the content model, the working rules, the commands that verify the integration. Write it to the repository root and commit it; the next agent (or person) starts from it. When the file exists, replace everything above its `<!-- mapled:notes -->` line and keep the notes below.
|
|
50
51
|
|
|
51
|
-
## Destructive changes
|
|
52
|
+
## Destructive and breaking changes
|
|
53
|
+
|
|
54
|
+
`delete_collection`, `remove_field` and `clear_records` never act on their own, and neither do the two breaking schema changes — `rename_field` (a field's key, what the site's code reads) and `convert_field` (its type, converting the stored values). Each one files a request and returns a `reviewUrl`: send it to the user, who confirms on a trusted Mapled screen by typing the name. Poll `get_confirmation` until the status is `applied` (the result says what changed), `denied` or `expired` (after an hour). Nothing changes until then.
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
A rename or a conversion Mapled wouldn't take is refused at once with the reason: a key that is taken or malformed, a type the field can't become, a formula that reads the field, values that can't be converted (with their count and examples — fix the content, or ask again with `clearFailed: true`; the person confirming sees how many will be cleared). After a rename the old key keeps working as an alias until a person removes it, so move the site's code to the new key first. Both can be undone from Field settings.
|
|
54
57
|
|
|
55
58
|
## Tools
|
|
56
59
|
|
|
@@ -67,6 +70,7 @@ For the first integration, propose one plan and let the owner approve it on a tr
|
|
|
67
70
|
| `push_site_manifest` / `list_bindings` | Tell Mapled where each field is rendered; read every binding's health (type mismatch, outdated, missing on site). Keep the repository's copy in `mapled/manifest.json` — `npx @mapled/cli scan --write` derives it from the code and `bindings push` / `bindings pull` exchange it with Mapled. A push records the integration hash — the schema and these bindings — and `list_bindings` says whether it still matches (`integration.inSync`, with `schemaChanged` / `bindingsChanged` naming what moved) |
|
|
68
71
|
| `configure_revalidation` | Point the publish webhook at the site, get the signing secret |
|
|
69
72
|
| `check_integration` | The site's integration as Mapled sees it — delivery reads, the webhook and its last delivery, the bindings summary, the integration hash (is the last push still in step with the schema and the bindings?), current package versions; `npx @mapled/cli doctor` shows the same from inside the repository |
|
|
73
|
+
| `delete_collection` / `remove_field` / `clear_records` / `rename_field` / `convert_field` / `get_confirmation` | Destructive and breaking changes — each files a request a person confirms on a trusted Mapled screen (see above); `get_confirmation` reads its status |
|
|
70
74
|
| `get_mapled_md` | `MAPLED.md` rendered from the project — the guide for the next agent: project, how the site reads it, content model, bindings by page, last setup run, working rules, verification commands. Never a secret. `npx @mapled/cli md pull` writes the same file; `mapled doctor` says when it is out of date |
|
|
71
75
|
|
|
72
76
|
Agents write drafts only — publishing stays with humans in the Mapled app, and nothing changes on the live site until someone presses Publish.
|
package/dist/tools.js
CHANGED
|
@@ -41,9 +41,9 @@ export const FIELD_TYPES = [
|
|
|
41
41
|
];
|
|
42
42
|
/** What add_field and propose_setup_plan say about formulas (§14.9). */
|
|
43
43
|
const COMPUTED_HELP = "For type computed only: { expression } — a formula over the record's other fields that Mapled evaluates when a record is read or published (read-only for editors and agents; the site reads the value like any field of the result type, filters and sorts included; writes ignore it). " +
|
|
44
|
-
"Field keys as written (price, unit-cost — put spaces around a minus to subtract: price - cost); one link deep through relations: author.name, and lists over many-relations for aggregates: sum(items.price), count(tags), join(tags.name, \", \"). " +
|
|
44
|
+
"Field keys as written (price, unit-cost — put spaces around a minus to subtract: price - cost); one link deep through relations: author.name, and lists over many-relations for aggregates: sum(items.price), count(tags), join(tags.name, \", \"); or one link back — the records of another collection whose relation points at this record, written @collection.relation[.field], always a list, oldest first: count(@posts.author), sum(@order-items.order.total), max(@posts.author.published-on). " +
|
|
45
45
|
"Numbers: + - * / %, round(x, digits), floor, ceil, abs, min, max, sum, avg, count, fixed(x, digits) → text. Text: & joins (empty counts as \"\"), concat, upper, lower, trim, length, left(s, n), right(s, n), replace(s, from, to), contains(s, part), slug(s), text(x), number(s). " +
|
|
46
|
-
"Logic: = != < <= > >=, and, or, not, if(cond, a, b), coalesce(a, b), empty(x). Dates: year, month, day, date(datetime), daysBetween(a, b), addDays(d, n). Literals: 12, 2.5, \"text\", true, false, null. " +
|
|
46
|
+
"Logic: = != < <= > >=, and, or, not, if(cond, a, b), coalesce(a, b), empty(x). Dates: year, month, day, date(datetime), daysBetween(a, b), addDays(d, n), created() — when the record was added (a datetime). Literals: 12, 2.5, \"text\", true, false, null. " +
|
|
47
47
|
"Sensitive fields, groups, JSON and location can't be read; there is no now(). The result type (number, text, boolean, date, datetime) follows from the formula.";
|
|
48
48
|
export function createTools(api) {
|
|
49
49
|
return [
|
|
@@ -215,6 +215,52 @@ export function createTools(api) {
|
|
|
215
215
|
fieldKey: args.fieldKey,
|
|
216
216
|
}),
|
|
217
217
|
},
|
|
218
|
+
{
|
|
219
|
+
name: "rename_field",
|
|
220
|
+
description: "Breaking change, a person's decision: rename a field's key (what the site's code reads). The values follow, formulas and the " +
|
|
221
|
+
"schema are updated, and the old key keeps working as an alias — delivery serves the value under both keys, and get_schema lists " +
|
|
222
|
+
"it under `aliases` — until a person removes it in Field settings, so the site never breaks mid-change. Nothing changes until a " +
|
|
223
|
+
"person confirms it on a trusted Mapled screen by typing the field's name (returns a reviewUrl to send to the user); poll " +
|
|
224
|
+
"get_confirmation. After it is applied: update the code to read the new key, regenerate the types, push the manifest. A refusal " +
|
|
225
|
+
"(key taken, malformed key) comes back at once. It can be undone from Field settings.",
|
|
226
|
+
schema: {
|
|
227
|
+
collectionKey: z.string().min(1).max(120),
|
|
228
|
+
fieldKey: z.string().min(1).max(120).describe("The field's current key."),
|
|
229
|
+
newKey: z.string().min(1).max(50).describe("Lowercase letters, digits and hyphens, up to 50 characters."),
|
|
230
|
+
},
|
|
231
|
+
handler: async (args) => api.request("POST", "/v1/agent/confirmations", {
|
|
232
|
+
action: "rename_field",
|
|
233
|
+
collectionKey: args.collectionKey,
|
|
234
|
+
fieldKey: args.fieldKey,
|
|
235
|
+
newKey: args.newKey,
|
|
236
|
+
}),
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "convert_field",
|
|
240
|
+
description: "Breaking change, a person's decision: change a field's type, converting the stored values (get_schema lists the types a field " +
|
|
241
|
+
"can become under `convertibleTo`). Mapled works the conversion out over every record first: if some values can't be converted " +
|
|
242
|
+
"the request is refused with their count and a few examples — fix the content, or ask again with clearFailed: true to clear " +
|
|
243
|
+
"those values (the person confirming sees how many). A conversion that would break a formula reading the field is refused too. " +
|
|
244
|
+
"Nothing changes until a person confirms it on a trusted Mapled screen by typing the field's name (returns a reviewUrl to send " +
|
|
245
|
+
"to the user); poll get_confirmation. It can be undone from Field settings, with the previous values.",
|
|
246
|
+
schema: {
|
|
247
|
+
collectionKey: z.string().min(1).max(120),
|
|
248
|
+
fieldKey: z.string().min(1).max(120),
|
|
249
|
+
type: z.string().min(1).max(40).describe("The type to convert to — one of the field's convertibleTo."),
|
|
250
|
+
cardinality: z.enum(["one", "many"]).optional().describe("For a relation: one ↔ many."),
|
|
251
|
+
options: z.array(z.string().min(1).max(60)).max(60).optional().describe("For enum: the choices; derived from the values when omitted."),
|
|
252
|
+
clearFailed: z.boolean().optional().describe("Clear the values that can't be converted instead of refusing."),
|
|
253
|
+
},
|
|
254
|
+
handler: async (args) => api.request("POST", "/v1/agent/confirmations", {
|
|
255
|
+
action: "convert_field",
|
|
256
|
+
collectionKey: args.collectionKey,
|
|
257
|
+
fieldKey: args.fieldKey,
|
|
258
|
+
type: args.type,
|
|
259
|
+
...(args.cardinality !== undefined ? { cardinality: args.cardinality } : {}),
|
|
260
|
+
...(args.options !== undefined ? { options: args.options } : {}),
|
|
261
|
+
...(args.clearFailed !== undefined ? { clearFailed: args.clearFailed } : {}),
|
|
262
|
+
}),
|
|
263
|
+
},
|
|
218
264
|
{
|
|
219
265
|
name: "clear_records",
|
|
220
266
|
description: "Destructive: move every record of a collection to Trash (kept 7 to 90 days by the project's plan), e.g. before re-importing content. " +
|
|
@@ -228,8 +274,8 @@ export function createTools(api) {
|
|
|
228
274
|
},
|
|
229
275
|
{
|
|
230
276
|
name: "get_confirmation",
|
|
231
|
-
description: "Check a destructive
|
|
232
|
-
"says what changed), denied, expired (after an hour; request again if still needed) or failed.",
|
|
277
|
+
description: "Check a request that waits for a person (a destructive change, a rename, a conversion): status is pending (waiting for the " +
|
|
278
|
+
"person), applied (done — result says what changed), denied, expired (after an hour; request again if still needed) or failed.",
|
|
233
279
|
schema: { confirmationId: z.string().uuid() },
|
|
234
280
|
handler: async (args) => api.request("GET", `/v1/agent/confirmations/${encodeURIComponent(args.confirmationId)}`),
|
|
235
281
|
},
|