@mapled/mcp 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,9 +65,9 @@ A rename or a conversion Mapled wouldn't take is refused at once with the reason
65
65
  | `propose_setup_plan` / `get_setup_run` / `apply_setup_plan` / `report_setup` / `verify_setup` | One approved plan for the whole setup — every field type including relations (`$ref` handles between plan records, ids for existing collections) and groups — verified by Mapled (see above) |
66
66
  | `get_schema` | Read the project's collections and fields |
67
67
  | `create_collection` | Add a collection or single |
68
- | `add_field` | Add a field (short_text, long_text, rich_text, slug, image, number, boolean, date, datetime, relation, enum, url, email, group, file, color, json — an object or list up to 32 KB, location — { lat, lng }, computed). Optional `validation` ({min, max, pattern}) and `defaultValue`; `relation` ({target, cardinality: one \| many, onDelete: restrict \| nullify}) is required for relation fields — values are record ids of the target collection, kept in the order given; `onDelete` says what a delete of a linked record does (restrict: it can't be deleted while linked, nullify: the links are cleared — the default is restrict for required fields and nullify otherwise); `options` (1–50 labels) is required for enum fields; `sensitive: true` keeps a field out of lists, history and delivery (a group's sub-fields take it too); `group` ({fields, repeatable, maxItems}) shapes a group field — its values are objects (or arrays of them) keyed by the sub-field keys; `computed` ({expression}) makes a computed field — a formula Mapled evaluates whenever a record is read or published, over the record's fields and up to two links through relations (`author.company.name`, `sum(items.product.price)`) or back along one and one link on (`count(@posts.author)`, `sum(@order-items.order.product.price)`), at most one list per path, never a sensitive field or relation; the site reads the value like any field of its result type. |
69
- | `add_records` | Insert draft records |
70
- | `list_records` | Read a collection's draft records, newest edit first — `query` searches their content, `limit` (1–200) and `cursor` (the previous answer's `nextCursor`) page through them; `total` counts every match |
68
+ | `add_field` | Add a field (short_text, long_text, rich_text, slug, image, number, boolean, date, datetime, relation, enum, url, email, group, file, color, json — an object or list up to 32 KB, location — { lat, lng }, computed). Optional `validation` ({min, max, pattern}) and `defaultValue`; `relation` ({target, cardinality: one \| many, onDelete: restrict \| nullify}) is required for relation fields — values are record ids of the target collection, kept in the order given; `onDelete` says what a delete of a linked record does (restrict: it can't be deleted while linked, nullify: the links are cleared — the default is restrict for required fields and nullify otherwise); `options` (1–50 labels) is required for enum fields; `sensitive: true` keeps a field out of lists, history and delivery (a group's sub-fields take it too); `group` ({fields, repeatable, maxItems}) shapes a group field — its values are objects (or arrays of them) keyed by the sub-field keys; `computed` ({expression}) makes a computed field — a formula Mapled evaluates whenever a record is read or published, over the record's fields and up to two links through relations (`author.company.name`, `sum(items.product.price)`) or back along one and one link on (`count(@posts.author)`, `sum(@order-items.order.product.price)`), at most one list per path, never a sensitive field or relation; `today()` and `now()` are the moment the value was computed — baked at publish and recomputed once a day for the current release; the site reads the value like any field of its result type. |
69
+ | `add_records` | Insert draft records — a translated field (`localized: true` in `get_schema`) by language, `{ "en": "About us", "ru": "О нас" }`, or as one plain value, the default language's |
70
+ | `list_records` | Read a collection's draft records, newest edit first — `query` searches their content, `limit` (1–200) and `cursor` (the previous answer's `nextCursor`) page through them; `total` counts every match; `locale` reads the translated fields in one language (`ru`) or in every one at once (`*`, as `{ "en": …, "ru": … }`) |
71
71
  | `create_form` / `list_forms` | Set up public forms with spam protection |
72
72
  | `get_connection` | The delivery key and the API URL, plus — for the `framework` the agent names (`nextjs`, `react-spa`, `plain-html`, …) or the project's own — the package the site reads through (`@mapled/next`, `@mapled/react`, `@mapled/vanilla` — a script tag and `data-mapled-*` attributes for plain HTML — or `@mapled/client`), the env var of the key, whether the site renders on a server or in the browser, and the wire-up steps. Image values → `assetUrl(id, { width })` of the same package |
73
73
  | `set_site_url` | Where the site is deployed — for a site rendered in the browser (a React single-page app, plain HTML), which has no webhook to learn it from: Preview opens the site there, verification checks that it answers |
package/dist/tools.js CHANGED
@@ -55,7 +55,8 @@ const COMPUTED_HELP = "For type computed only: { expression } — a formula over
55
55
  "Field keys as written (price, unit-cost — put spaces around a minus to subtract: price - cost); up to two links through relations: author.name, author.company.name, and lists over many-relations for aggregates: sum(items.price), count(tags), join(tags.name, \", \"), sum(items.product.price); 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) — and one more link on from them: sum(@order-items.order.product.price). A path goes through at most one list (a many-relation, or the records that link back), so items.tags.name is refused. " +
56
56
  "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). " +
57
57
  "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. " +
58
- "Sensitive fields and relations, groups, JSON and location can't be read; there is no now(). The result type (number, text, boolean, date, datetime) follows from the formula.";
58
+ "today() and now() — the day and the moment the value was computed: a publish bakes them into the release, and Mapled recomputes the current release once a day (after midnight UTC), sending the publish webhook again — so daysBetween(today(), due) counts down, but not by the minute; a pinned release keeps its values. " +
59
+ "Sensitive fields and relations, groups, JSON and location can't be read. The result type (number, text, boolean, date, datetime) follows from the formula.";
59
60
  /** The variable the site's revalidate route verifies publishes with. */
60
61
  const WEBHOOK_SECRET_ENV = "MAPLED_WEBHOOK_SECRET";
61
62
  /** The header a publish webhook carries its signature in. */
@@ -457,6 +458,13 @@ export function createTools(api) {
457
458
  "script tag) sends { realtime: true }; a site without live updates sends { realtime: false }. Only these two keys " +
458
459
  "are accepted. Nothing is graded by them and they stay out of the integration hash, but MAPLED.md's " +
459
460
  "Live updates line comes from them — send them with every push, as a push without them drops the line. " +
461
+ "`locales` says how the routes carry the language on a site with more than one (§28.4): " +
462
+ "{ routing: \"prefix\", param: \"locale\" } when every language lives under its own segment " +
463
+ "(/[locale]/blog/[slug] — the page routes keep the segment), { routing: \"prefix-except-default\" } when the " +
464
+ "default language is at the root and the others under /ru/…, /de/… (without `param` Mapled puts the language " +
465
+ "before the page). Mapled builds each language's address of a record from it (the editor's routes, Preview); " +
466
+ "a translated slug is the address in that language, a missing translation falls back. Leave it out on a " +
467
+ "site with one address for every language. " +
460
468
  "The answer's manifest.integrationHash is the hash of the schema and these " +
461
469
  "bindings — what the site is synced with from now on; check_integration reports inSync against it. " +
462
470
  "Needs the builder plan.",
@@ -498,6 +506,15 @@ export function createTools(api) {
498
506
  .strict()
499
507
  .optional()
500
508
  .describe("Whether open tabs follow a publish: realtime, and releaseRoute — the path of the site's release route, e.g. /api/mapled/release."),
509
+ // routes by language (§28.4), the API's shape (lib/bindings.ts): strict
510
+ locales: z
511
+ .object({
512
+ routing: z.enum(["prefix", "prefix-except-default"]),
513
+ param: z.string().regex(/^[A-Za-z0-9_-]{1,60}$/).optional(),
514
+ })
515
+ .strict()
516
+ .optional()
517
+ .describe("How the routes carry the language: routing — prefix (every language under its own) or prefix-except-default (the default at the root); param — the dynamic segment that holds it, e.g. locale for /[locale]/blog/[slug]."),
501
518
  },
502
519
  handler: async (args) => api.request("POST", "/v1/agent/manifest", args),
503
520
  },
@@ -615,7 +632,8 @@ export function createTools(api) {
615
632
  },
616
633
  {
617
634
  name: "add_records",
618
- description: "Insert up to 100 records into a collection. Each record maps field keys to values (rich_text takes Markdown; image takes an asset id; relation takes record ids; group takes objects).",
635
+ description: "Insert up to 100 records into a collection. Each record maps field keys to values (rich_text takes Markdown; image takes an asset id; relation takes record ids; group takes objects). " +
636
+ 'A translated field (`localized: true` in get_schema) takes its values by language — { "en": "About us", "ru": "О нас" }; give the default language, it is what the site shows where a translation is missing — or one plain value, which is the default language\'s; a translated slug is unique within its language.',
619
637
  schema: {
620
638
  collectionKey: z.string().min(1).max(120),
621
639
  records: z.array(z.record(z.string(), z.unknown())).min(1).max(100),
@@ -643,12 +661,14 @@ export function createTools(api) {
643
661
  },
644
662
  {
645
663
  name: "list_records",
646
- description: "List a collection's draft records (id, title, data), most recently edited first — up to `limit` per page (200 by default). `query` narrows the list to records whose content contains it; when the answer carries nextCursor, pass it as `cursor` for the next page. `total` counts every match.",
664
+ description: "List a collection's draft records (id, title, data), most recently edited first — up to `limit` per page (200 by default). `query` narrows the list to records whose content contains it; when the answer carries nextCursor, pass it as `cursor` for the next page. `total` counts every match. " +
665
+ "`locale` reads the translated fields in one language (a code such as ru — a field with no value in that language is left out) or, with *, in every language at once as { \"<code>\": value }; without it the default language, as always.",
647
666
  schema: {
648
667
  collectionKey: z.string().min(1).max(120),
649
668
  query: z.string().max(200).optional().describe("Text to search for in the records' content."),
650
669
  limit: z.number().int().min(1).max(200).optional().describe("Records per page, 1–200 (default 200)."),
651
670
  cursor: z.string().max(200).optional().describe("nextCursor from the previous page."),
671
+ locale: z.string().max(40).optional().describe("A language code such as ru or pt-BR, or * for every language at once."),
652
672
  },
653
673
  handler: async (args) => {
654
674
  const params = new URLSearchParams();
@@ -658,6 +678,8 @@ export function createTools(api) {
658
678
  params.set("limit", String(args.limit));
659
679
  if (args.cursor)
660
680
  params.set("cursor", args.cursor);
681
+ if (args.locale)
682
+ params.set("locale", args.locale);
661
683
  const qs = params.toString();
662
684
  return api.request("GET", `/v1/agent/collections/${encodeURIComponent(args.collectionKey)}/records${qs ? `?${qs}` : ""}`);
663
685
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mapled/mcp",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "description": "Mapled MCP server: lets AI agents build schema and content for one Mapled project.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://mapled.io",