@aotter/mantle 0.1.2 → 0.1.3-alpha.2

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 (43) hide show
  1. package/README.md +36 -13
  2. package/dist/auth.d.ts +2 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +2 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/cli/generate.d.ts.map +1 -1
  7. package/dist/cli/generate.js +3 -0
  8. package/dist/cli/generate.js.map +1 -1
  9. package/dist/cli/main.d.ts +1 -1
  10. package/dist/cli/main.d.ts.map +1 -1
  11. package/dist/cli/main.js +5 -0
  12. package/dist/cli/main.js.map +1 -1
  13. package/dist/cli/skills.d.ts.map +1 -1
  14. package/dist/cli/skills.js +3 -0
  15. package/dist/cli/skills.js.map +1 -1
  16. package/dist/codegen/emitMantleModule.d.ts.map +1 -1
  17. package/dist/codegen/emitMantleModule.js +38 -2
  18. package/dist/codegen/emitMantleModule.js.map +1 -1
  19. package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +109 -2
  20. package/docs/agent-prompts.md +38 -29
  21. package/docs/auth-hosting-model.md +1 -1
  22. package/docs/examples/host-local-admin-otp/README.md +10 -0
  23. package/docs/handbook/cloudflare/authentication.md +44 -3
  24. package/docs/handbook/concepts/mcp-and-agents.md +10 -5
  25. package/docs/handbook/navigation.json +8 -2
  26. package/docs/handbook/reference/diagnostics.md +5 -1
  27. package/docs/handbook/reference/manifest.md +1 -1
  28. package/docs/handbook/reference/procedure.md +1 -0
  29. package/docs/handbook/reference/surface.md +6 -4
  30. package/docs/handbook/reference/trigger.md +1 -1
  31. package/docs/handbook/releases/index.md +75 -0
  32. package/docs/handbook/start/project-and-cli.md +15 -4
  33. package/docs/handbook/start/quickstart-admin.md +20 -209
  34. package/docs/labels.md +3 -3
  35. package/docs/migration-0.1.2.md +10 -126
  36. package/docs/release-process.md +80 -39
  37. package/docs/spec-only-host-adoption.md +1 -1
  38. package/package.json +25 -16
  39. package/skills/README.md +21 -15
  40. package/skills/install/SKILL.md +5 -5
  41. package/skills/plugin/SKILL.md +6 -6
  42. package/skills/provision/SKILL.md +27 -17
  43. package/skills/update/SKILL.md +4 -3
@@ -58,6 +58,16 @@ paths must fall through to Static Assets.
58
58
 
59
59
  `ConsoleEmailSender` is the local path only. Do not wire it in production.
60
60
 
61
+ ## Local traps
62
+
63
+ - Prefer `http://127.0.0.1:8787` over `http://localhost:8787`. Wrangler still
64
+ serves HTML on localhost, but the OTP `Origin` header will not match
65
+ `PUBLIC_ORIGIN` and Better Auth returns `INVALID_ORIGIN`.
66
+ - `pnpm check` runs `smoke.mjs`, which rewrites `.dev.vars` to a smoke-only
67
+ port (`18787`). Restore `.dev.vars` from `.dev.vars.example` (or delete it
68
+ and let `predev` recopy) before `pnpm dev`, or OTP will fail with
69
+ `INVALID_ORIGIN` against the Ready-on `8787` origin.
70
+
61
71
  ## What this project does not do
62
72
 
63
73
  `/` is `404`: no visitor frontend is installed. Public GET
@@ -77,7 +77,7 @@ The staff role is re-read from D1 on every protected REST and MCP call; a revoke
77
77
  |---|---|
78
78
  | `/admin`, `/admin/api/*` | Staff session; role gates per route |
79
79
  | `/api/auth/*`, `/oauth/*`, `/.well-known/oauth*` | Auth-owned; public endpoints of the OAuth flow |
80
- | `/mcp` | Any authenticated OAuth caller; anonymous requests get `401` with a `WWW-Authenticate` challenge |
80
+ | `/mcp` | Same caller resolution as HTTP routes (bearer, same-origin cookie session, or anonymous); each tool's `requires` gates the call, and a call that needs identity answers `401` with a `WWW-Authenticate` challenge |
81
81
  | `/mcp/staff` | Authenticated caller with a staff role |
82
82
  | `/<locale>/<segment>/<slug>?preview=1` | Staff session (`401` without a session, `403` without a staff role) |
83
83
  | Public Views, public HTTP Triggers, public pages, `.md`, `llms.txt`, sitemap | None, unless the manifest declares `requires` |
@@ -112,8 +112,10 @@ methods: [
112
112
  ]
113
113
  ```
114
114
 
115
- Email OTP and magic-link storage defaults to `hashed`. Explicit official
116
- overrides remain available, including custom hashing/encryption:
115
+ Email OTP storage defaults to a keyed HMAC-SHA-256 of the code using
116
+ `BETTER_AUTH_SECRET`. Magic-link tokens remain `hashed` (high-entropy).
117
+ Explicit official overrides remain available, including `plain` and custom
118
+ hashing/encryption:
117
119
 
118
120
  ```ts
119
121
  { kind: "email-otp", sender, options: {
@@ -154,6 +156,45 @@ const auth = createAuth({
154
156
 
155
157
  Shared cookies do not cross registrable domains. A browser never sends an `example.com` cookie to `customer.com`. For a customer-owned domain, use an OAuth/OIDC broker flow: the customer site redirects to the identity provider's authorize endpoint, receives the callback, verifies the response and creates its own local session. The broker returns identity; the customer site remains the authority for its members and grants.
156
158
 
159
+ ### Account linking across providers
160
+
161
+ One person signing in with Google, then with GitHub, may land on one user row
162
+ or be refused — Better Auth decides this, and `createAuth()` does not override
163
+ it. Left unconfigured, Better Auth's own defaults apply: implicit linking is
164
+ on, so a social sign-in whose provider reports a verified email attaches to the
165
+ existing row carrying that email. It never creates a second row for the same
166
+ address; when linking is not permitted the sign-in fails with
167
+ `account not linked`.
168
+
169
+ Two defaults are worth knowing before you change anything. `requireLocalEmailVerified`
170
+ is on, so linking is refused while the *local* row is still unverified — this is
171
+ what stops someone pre-registering an unverified row at your user's address and
172
+ having that user's Google identity attach to it. It is also why a staff invitation
173
+ (`inviteUser` writes `emailVerified: 0`) cannot be claimed by a social sign-in
174
+ until the invitee verifies by email once. Separately, `trustedProviders` is
175
+ empty, so every provider must supply `email_verified` to link at all.
176
+
177
+ Pass `accountLinking` to scope this. It is forwarded verbatim:
178
+
179
+ ```ts
180
+ const auth = createAuth({
181
+ database: env.DB,
182
+ baseURL: env.PUBLIC_ORIGIN,
183
+ secret: env.BETTER_AUTH_SECRET,
184
+ methods,
185
+ accountLinking: {
186
+ // Accept these providers' word without an `email_verified` claim.
187
+ trustedProviders: ["google", "github"],
188
+ },
189
+ });
190
+ ```
191
+
192
+ Listing a provider in `trustedProviders` asserts that it verifies the addresses
193
+ it returns; a provider that does not turns the list into an account-takeover
194
+ path. To go the other way and keep every identity separate, set
195
+ `disableImplicitLinking: true` (users may still link deliberately via
196
+ `linkSocial()` while signed in) or `enabled: false` to refuse linking outright.
197
+
157
198
  ## Self-hosted and hosted
158
199
 
159
200
  A free self-hosted site runs every method `createAuth()` exposes: Better Auth social providers, email OTP, magic link, and parent-domain SSO. The owner supplies provider credentials, email sending and cookie policy. Hosted auth is an operations convenience: the platform holds provider and email configuration and registers the site as a PKCE client, while the site still owns grants, members, content and `ctx.user`/`ctx.staff` mapping. Neither mode changes the runtime's authorization vocabulary.
@@ -3,13 +3,13 @@ description: Mantle serves /mcp and /mcp/staff from the same Manifest — tool n
3
3
  ---
4
4
  # MCP and agents
5
5
 
6
- Mantle is an MCP server out of the box. Nothing is registered, exported or annotated to make it one: the same compiled plan that produces REST and Admin also produces the tool catalog, so an agent and a browser reach identical behavior through different transports. This page covers the two surfaces, how tools are named, how a client authenticates, and the rest of the agent-facing surface area.
6
+ Mantle is an MCP server out of the box. Nothing is registered, exported or annotated to make it one: the same compiled plan that produces REST and Admin also produces the tool catalog, so an agent and a browser reach identical behavior through different transports. `/mcp` and `/mcp/staff` are that live-app catalog — Manifest → RuntimePlan verbs — not a how-to-author-Mantle manual. The CLI and pinned package docs are the authoring SSOT; MCP does not mirror the CLI. This page covers the two surfaces, how tools are named, how a client authenticates, and the rest of the agent-facing surface area.
7
7
 
8
8
  ## Two surfaces
9
9
 
10
10
  | Mount | Caller | Exposes |
11
11
  |---|---|---|
12
- | `/mcp` | Any authenticated OAuth caller; anonymous requests get `401` | Views with `surface: public`, and Procedures reached by an MCP Trigger with `surface: public` |
12
+ | `/mcp` | Any caller the site's HTTP routes would accept: OAuth bearer, same-origin cookie session, or anonymous. Each tool's `requires` decides; a `tools/call` that needs identity answers `401` with the OAuth challenge | Views with `surface: public`, and Procedures reached by an MCP Trigger with `surface: public` |
13
13
  | `/mcp/staff` | Authenticated caller holding a staff role | Views with `surface: staff`, the generic authoring tools (rank-gated at `tools/call`), Procedures reached by an MCP Trigger with `surface: staff` |
14
14
 
15
15
  Both accept tokens for one canonical protected resource, `${PUBLIC_ORIGIN}/mcp`. `/mcp/staff` is a stricter server-side role projection, not a second OAuth audience.
@@ -33,7 +33,7 @@ Procedures are never exposed on their own. A Procedure becomes a tool only throu
33
33
 
34
34
  ## The OAuth model
35
35
 
36
- The Cloudflare adapter runs one Better Auth 1.7 instance for staff identity, authorization, consent, client registration and MCP resource verification. Client identity is CIMD-first — the MCP 2026-07-28 Client ID Metadata Document profile, which is why the Worker needs the `global_fetch_strictly_public` flag to fetch client metadata across the public Internet boundary. Unauthenticated Dynamic Client Registration remains available as a bounded path with a 90-day default lifetime for clients that do not present CIMD. One non-colon scope, `mcp`, is advertised in `scopes_supported`, because clients such as claude.ai reject colon-shaped scopes; per-surface enforcement then happens server-side, not through scope strings. Authorization is session-bound: the JWT's originating Better Auth session must still exist and be unexpired, so signing out of Admin also ends that session's MCP access, and a refresh token is not an independent authorization. Unauthenticated requests to either mount answer `401` with a `WWW-Authenticate` challenge pointing at the RFC 9728 protected-resource metadata document served under the auth mount. Authorization endpoints live under `/api/auth/oauth2/*` and are discovered from the advertised metadata, never hard-coded.
36
+ The Cloudflare adapter runs one Better Auth 1.7 instance for staff identity, authorization, consent, client registration and MCP resource verification. Client identity is CIMD-first — the MCP 2026-07-28 Client ID Metadata Document profile, which is why the Worker needs the `global_fetch_strictly_public` flag to fetch client metadata across the public Internet boundary. Unauthenticated Dynamic Client Registration remains available as a bounded path with a 90-day default lifetime for clients that do not present CIMD. One non-colon scope, `mcp`, is advertised in `scopes_supported`, because clients such as claude.ai reject colon-shaped scopes; per-surface enforcement then happens server-side, not through scope strings. Authorization is session-bound: the JWT's originating Better Auth session must still exist and be unexpired, so signing out of Admin also ends that session's MCP access, and a refresh token is not an independent authorization. Invalid credentials on either mount, and anonymous requests to `/mcp/staff`, answer `401` with a `WWW-Authenticate` challenge pointing at the RFC 9728 protected-resource metadata document served under the auth mount; on `/mcp` an anonymous caller can list tools and call anonymous ones, and receives the same `401` challenge from the first `tools/call` whose target requires identity. Authorization endpoints live under `/api/auth/oauth2/*` and are discovered from the advertised metadata, never hard-coded.
37
37
 
38
38
  ## Connecting a local client
39
39
 
@@ -91,10 +91,15 @@ pnpm exec mantle skills --check
91
91
 
92
92
  This copies every skill the installed package marks `projection: project` — the develop skill among them — into matching `.agents/skills/mantle-*` and `.claude/skills/mantle-*` paths. Both layouts receive identical bytes; `--check` detects drift without writing. Skills that act destructively or target one platform stay out of that set and are opt-in. Manifest generation never rewrites agent instructions.
93
93
 
94
- For Claude Code, the same bundle is installable from the plugin marketplace at the exact installed version:
94
+ For Claude Code, the same bundle is installable from the plugin marketplace at the published pin (or the exact version in `package.json`):
95
95
 
96
96
  ```sh
97
- /plugin marketplace add aotter/mantle@v<installed-version>
97
+ # Canonical
98
+ npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
99
+
100
+ # Claude Code — two separate prompts
101
+ /plugin marketplace add aotter/mantle@v0.1.3-alpha.2
102
+ /plugin install mantle@mantle
98
103
  ```
99
104
 
100
105
  Never point a versioned project at a mutable branch. See [Project layout and the CLI loop](../start/project-and-cli.md).
@@ -4,10 +4,10 @@
4
4
  {
5
5
  "text": "Start here",
6
6
  "items": [
7
+ { "text": "Project layout and the CLI loop", "link": "/start/project-and-cli" },
7
8
  { "text": "Quickstart: a minimal Worker", "link": "/start/quickstart-worker" },
8
- { "text": "Build with ChatGPT Sites", "link": "/sites/index" },
9
9
  { "text": "Quickstart: local Admin (opt-in)", "link": "/start/quickstart-admin" },
10
- { "text": "Project layout and the CLI loop", "link": "/start/project-and-cli" }
10
+ { "text": "When the host is ChatGPT Sites", "link": "/sites/index" }
11
11
  ]
12
12
  },
13
13
  {
@@ -73,6 +73,12 @@
73
73
  { "text": "Diagnostic codes", "link": "/reference/diagnostics" },
74
74
  { "text": "HTTP, MCP, CLI and packages", "link": "/reference/surface" }
75
75
  ]
76
+ },
77
+ {
78
+ "text": "Releases",
79
+ "items": [
80
+ { "text": "Stable releases", "link": "/releases/index" }
81
+ ]
76
82
  }
77
83
  ]
78
84
  }
@@ -98,6 +98,10 @@ Named by the same code in validate, boot or runtime, depending on where the cond
98
98
  | `TRIGGER_PATH_COLLISION` | Two HTTP Triggers claim the same `(method, path)`. | — |
99
99
  | `TRIGGER_PATH_INVALID` | An HTTP Trigger path does not start `/api/` (validate), or falls under an adapter-reserved prefix (boot). | — |
100
100
  | `MCP_TOOL_NAME_COLLISION` | Two atoms mangle to the same MCP tool name, a Procedure takes a reserved generic name or prefix, or two MCP Triggers share a `(surface, tool name)`. | — |
101
+ | `MCP_TOOL_DESCRIPTION_MISSING` | Warning. A Procedure reached by an MCP Trigger has no `spec.description`; `tools/list` would show a generated placeholder instead of something an agent can choose by. | — |
102
+ | `MCP_TOOL_INPUT_UNREACHABLE` | Warning. An MCP write tool requires `expectedVersion` for a collection that no View on the same surface exposes `version` for, so an agent cannot read the value it must send. SQL Views are checked only by a conservative token scan. | — |
103
+ | `MCP_TOOL_INPUT_UNION_AMBIGUOUS` | Warning. An MCP-surfaced Procedure input is a top-level `oneOf`; MCP clients render it poorly, and when its branches require fields the advertised `required` omits a caller satisfying the schema can still be rejected. Prefer one tool per branch. Disable or tune through `ValidateManifestsRequest.mcpInput`, or `mantle validate --no-mcp-input-checks`. | — |
104
+ | `MCP_TOOL_INPUT_UNBOUNDED` | Warning. An MCP-surfaced Procedure input has an array without `maxItems` (or above the configured bound) or a free-form object with no declared properties, so an agent must serialise an unbounded payload into one `tools/call`. Typed maps (`additionalProperties: { type: … }`) do not count as free-form. | — |
101
105
  | `PROCEDURE_NOT_FOUND` | An invocation names a Procedure that is not in the compiled plan. | — |
102
106
  | `NOT_FOUND` | The addressed resource does not exist: an entry id, a View name, a media asset, an operation name. | `404` |
103
107
  | `METHOD_NOT_ALLOWED` | The path exists but the method is not bound. | `405` |
@@ -108,7 +112,7 @@ Named by the same code in validate, boot or runtime, depending on where the cond
108
112
  | Code | Meaning | HTTP |
109
113
  |---|---|---|
110
114
  | `BUILTIN_HANDLER_SCHEMA_UNKNOWN` | `handler.schema` names no declared Schema. | — |
111
- | `BUILTIN_HANDLER_CONTRACT_INVALID` | The Procedure's `input` breaks the builtin op's contract, such as a missing `expectedVersion` on `update` or a `match` tuple that is not exactly one `uniqueIndexes` entry. | — |
115
+ | `BUILTIN_HANDLER_CONTRACT_INVALID` | The Procedure's `input` breaks the builtin op's contract, such as a missing `expectedVersion` on `update` or a `match` tuple that is not exactly one `uniqueIndexes` entry; or `spec.mcp` contradicts the op (`readOnlyHint: true` on any builtin, `destructiveHint: false` on `op: delete`). | — |
112
116
  | `LIFECYCLE_SCHEMA_UNKNOWN` | A lifecycle Trigger's `source.schema` names no declared Schema. | — |
113
117
  | `LIFECYCLE_HOOK_REJECTED` | A `before_*` hook aborted the mutation. The diagnostic names the rejecting hook. | `409` |
114
118
 
@@ -38,7 +38,7 @@ The parser rejects keys outside the shipped grammar at every level it knows. The
38
38
  | `/metadata` | `name` |
39
39
  | `/spec` (Schema) | `title`, `description`, `schema`, `uiSchema`, `uniqueIndexes`, `indexes`, `searchableFields`, `localized`, `translates`, `lifecycle` |
40
40
  | `/spec` (View) | `title`, `uiSchema`, `from`, `sql`, `surface`, `requires`, `filter`, `fields`, `orderBy`, `limit`, `params` |
41
- | `/spec` (Procedure) | `title`, `description`, `requires`, `input`, `uiSchema`, `output`, `handler` |
41
+ | `/spec` (Procedure) | `title`, `description`, `requires`, `input`, `uiSchema`, `output`, `handler`, `mcp` |
42
42
  | `/spec` (Trigger) | `source`, `target` |
43
43
  | `/spec/translates` | `parent`, `on` |
44
44
  | `/spec/requires` | `auth`, `guard` |
@@ -16,6 +16,7 @@ A Procedure is a typed callable: input schema, output schema, authorization requ
16
16
  | `uiSchema` | object | no | Admin-only. Accepts `collectionAction` and `fields`. Violations are `SCHEMA_UI_INVALID`. |
17
17
  | `output` | JSON Schema | yes | Checked after the handler returns. Failure is `OUTPUT_VALIDATION_FAILED` (500). |
18
18
  | `handler` | `ref` \| `builtin` | yes | Exactly one binding shape; see below. |
19
+ | `mcp` | object | no | MCP tool annotations the author asserts: `readOnlyHint`, `destructiveHint`, `openWorldHint` (booleans). Core infers what it can prove — every builtin handler writes, `op: delete` destroys, an `x-mcp-hint: idempotency-key` input makes the tool idempotent — and emits nothing else, so absent hints keep the MCP spec's conservative defaults. `readOnlyHint: true` on a builtin handler is `BUILTIN_HANDLER_CONTRACT_INVALID`. |
19
20
 
20
21
  Both `input` and `output` are walked by the [JSON Schema subset](./schema.md#json-schema-subset) validator, so the same recognized and rejected keywords apply.
21
22
 
@@ -30,7 +30,7 @@ All `/admin/api/*` routes require a staff session, carry a 1 MiB JSON body limit
30
30
  | `GET /admin/api/views/<name>/export` | The same query as CSV, covering every matching row rather than one page. |
31
31
  | `GET /admin/api/views-manifest` | `{ views: … }` — the View manifest projection the SPA renders from. |
32
32
  | `GET /admin/api/operations` | `{ operations: [ { name, title, description, input, uiSchema, triggers, rowBindings } ] }`, filtered per caller by re-evaluating each Procedure's `requires.auth.all`. |
33
- | `POST /admin/api/operations/:name` | Invokes a staff-operable Procedure through the same use case the staff MCP surface uses. `404` when the name is not staff-operable. |
33
+ | `POST /admin/api/operations/:name` | Invokes a staff-operable Procedure through the same use case the staff MCP surface uses. `404` when the name is not staff-operable or when the caller's `requires.auth.all` predicates exclude it — the same filter the listing applies, so names cannot be probed. |
34
34
  | `GET /admin/api/me`, `/collections`, `/collections/:name/statistics`, `/entries`, `/entries/export`, `/entries/:id`, `/site` | Session, catalog and entry reads. Entry detail requires `?collection=<schema>`. |
35
35
  | `POST /admin/api/entries`, `PATCH /admin/api/entries/:id` | Create and edit. Entry mutation routes require `?collection=<schema>`; contributors are limited to drafts on publishing Schemas. |
36
36
  | `POST /admin/api/entries/:id/publish`, `/unpublish`, `DELETE /admin/api/entries/:id` | Lifecycle. Requires `?collection=<schema>` and editor or above. |
@@ -52,7 +52,7 @@ All `/admin/api/*` routes require a staff session, carry a 1 MiB JSON body limit
52
52
  | `ALL /mcp` | Public MCP surface. JSON-RPC. | `private, no-store` |
53
53
  | `ALL /mcp/staff` | Staff MCP surface. Rejects a verified caller with no staff row using `403` and `insufficient_scope`. | `private, no-store` |
54
54
 
55
- Both MCP surfaces verify an OAuth access token against one canonical resource, `${PUBLIC_ORIGIN}/mcp`, and one scope, `mcp`. A missing or invalid token is `401` with a `Bearer` challenge naming the resource metadata URL; DPoP failures answer with a `DPoP` challenge. Misconfigured or partial auth environment variables keep public routes serving and return `503 setup_incomplete` from every Auth-owned route above — see [Authentication](../cloudflare/authentication.md).
55
+ Both MCP surfaces resolve the caller exactly as the HTTP routes do — consumer credential, then an OAuth access token against one canonical resource, `${PUBLIC_ORIGIN}/mcp`, and one scope, `mcp`, then a same-origin cookie session, then anonymous. The surface adds one rule: `/mcp/staff` requires a staff caller. An invalid token, or an anonymous caller on the staff surface, is `401` with a `Bearer` challenge naming the resource metadata URL; DPoP failures answer with a `DPoP` challenge; on `/mcp` an anonymous `tools/call` whose target requires identity answers the same `401` challenge. Misconfigured or partial auth environment variables keep public routes serving and return `503 setup_incomplete` from every Auth-owned route above — see [Authentication](../cloudflare/authentication.md).
56
56
 
57
57
  ### Public pages
58
58
 
@@ -98,6 +98,7 @@ Tool names are the mangled `metadata.name`: lower-cased, with `-` replaced by `_
98
98
  | Tool | Surface | Registered when |
99
99
  |---|---|---|
100
100
  | `query_view_<segment>` | The View's own `surface` | One per declared View. `annotations.readOnlyHint` is `true`; the input schema is the View's `params.properties` plus `page` and `show`. |
101
+ | `<procedure_segment>` | The MCP Trigger's `surface` | One per `mcp` Trigger. `annotations` carry what Core can prove (`readOnlyHint: false` for every builtin handler, `destructiveHint: true` for `op: delete`, `idempotentHint: true` when an input carries `x-mcp-hint: idempotency-key`) plus whatever the Procedure declares under `spec.mcp`; `ref` handlers get nothing inferred beyond the idempotency key. Generic authoring, lifecycle and media tools are `readOnlyHint: false`; `delete_entry` is also `destructiveHint: true`. |
101
102
  | `<procedure segment>` | The Trigger's `surface` | One per `Trigger.source.kind: mcp`. A Procedure with no MCP Trigger is not exposed. |
102
103
  | `request_publish` | staff | Always. Rejected at call time for an operational Schema. |
103
104
  | `unpublish_entry` | staff | Always. Same restriction. |
@@ -152,11 +153,11 @@ mantle.triggers.expireOrderHttp; // { name, source, target }
152
153
  await mantle.runtime.archive.execute({ id, ctx });
153
154
  ```
154
155
 
155
- `entries.<collection>` exposes `createDraft`, `get`, `list` and `delete`, supplying the required collection identity to Core. Generic MCP entry tools require a `collection` argument. `runtime` is the underlying Core runtime, so the typed projection never hides it. A host that owns its own lifecycle can skip generation entirely and call `runtime.executeView({ view: "published-notes" })` directly.
156
+ `entries.<collection>` exposes `createDraft`, `get`, `list`, `delete`, and the indexed field reads `readBySlug`, `readByDataField`, `readByDataFieldIn` and `findManyByDataField`, supplying the required collection identity to Core. The field reads accept only declared Schema fields and their scalar types, and return entries whose `data` is the generated Schema shape, so an author never has to drop to an untyped `runtime.entries` call or scan `list` to find rows by an indexed field. Generic MCP entry tools require a `collection` argument. `runtime` is the underlying Core runtime, so the typed projection never hides it. A host that owns its own lifecycle can skip generation entirely and call `runtime.executeView({ view: "published-notes" })` directly.
156
157
 
157
158
  ## Packages
158
159
 
159
- The umbrella installs Spec and Runtime only. Web, Admin, Admin UI, Bun, Vercel and Cloudflare are optional peers; install one before importing its subpath. Every sub-package is also directly installable.
160
+ The umbrella installs Spec and Runtime only. Web, Admin, Auth, Admin UI, Bun, Vercel and Cloudflare are optional peers; install one before importing its subpath. Every sub-package is also directly installable.
160
161
 
161
162
  | Package | Umbrella subpath | Holds |
162
163
  |---|---|---|
@@ -167,6 +168,7 @@ The umbrella installs Spec and Runtime only. Web, Admin, Admin UI, Bun, Vercel a
167
168
  | — | `/codegen` | The pure linked-manifests to typed-module emitter, with no IO. |
168
169
  | `@aotter/mantle-web` | `/web` | HTML, Markdown, `llms.txt`, sitemap, SEO and preview composition. No routes, no platform dependencies. |
169
170
  | `@aotter/mantle-admin` | `/admin` | Admin API, auth route mounting, OAuth pages, static-asset composition. |
171
+ | `@aotter/mantle-auth` | `/auth` | Host-neutral Better Auth identity, staff roles, and OAuth 2.1 / MCP authorization. Adapters own IP headers and storage bindings. |
170
172
  | `@aotter/mantle-admin-ui` | `/admin-ui` | Pre-built React 19 Admin SPA bundle. |
171
173
  | `@aotter/mantle-bun` | `/bun` | Bun adapter over a caller-owned `bun:sqlite` database. |
172
174
  | `@aotter/mantle-vercel` | `/vercel` | Vercel Functions adapter with injected durable storage and platform `waitUntil`. |
@@ -91,7 +91,7 @@ spec:
91
91
 
92
92
  `surface` is **discovery only**. It decides which tools appear in `tools/list` on which endpoint; it authorizes nothing. The target Procedure's `requires.auth.all` predicates and its optional guard are re-evaluated on every `tools/call` against the authenticated caller, exactly as they are over HTTP. A `public`-surface Procedure that requires `ctx.staff` is discoverable on `/mcp` and will still be denied there.
93
93
 
94
- The tool name is derived from the **Procedure's** `metadata.name`, not the Trigger's: lower-cased, with `-` replaced by `_`. Only one Trigger may claim a given `(surface, tool name)` pair; a second is `MCP_TOOL_NAME_COLLISION`. The same code also fires when the mangled name hits a reserved generic tool name or prefix, or a Schema's or another Procedure's segment — see [Reserved names](./manifest.md#reserved-names).
94
+ The tool name is derived from the **Procedure's** `metadata.name`, not the Trigger's: lower-cased, with `-` replaced by `_`. Only one Trigger may claim a given `(surface, tool name)` pair; a second is `MCP_TOOL_NAME_COLLISION`. The same code also fires when the mangled name hits a reserved generic tool name or prefix, or a Schema's or another Procedure's segment — see [Reserved names](./manifest.md#reserved-names). A Procedure exposed this way should carry `spec.description`; a missing one is the `MCP_TOOL_DESCRIPTION_MISSING` warning, because the catalog's generated fallback tells an agent nothing about when to call the tool. A write tool that requires `expectedVersion` also needs a View on the same surface that exposes that collection's `version`; otherwise the tool is listed but uncallable, and validation warns with `MCP_TOOL_INPUT_UNREACHABLE`.
95
95
 
96
96
  The tool carries the Procedure's `title` and `description`, with a short authorization summary appended to the description. `output` is not surfaced; MCP clients infer the response shape from the `tools/call` result. See [MCP and agents](../concepts/mcp-and-agents.md).
97
97
 
@@ -0,0 +1,75 @@
1
+ ---
2
+ description: Every stable Mantle release — what it contains, what it requires, and what changed since the previous stable.
3
+ ---
4
+ # Releases
5
+
6
+ Mantle publishes to npm under the `@aotter/*` scope. A **stable** release is a
7
+ plain `X.Y.Z` version on the `latest` dist-tag; it is the only kind of release
8
+ covered by this chapter and the only kind intended for production use.
9
+
10
+ ```sh
11
+ npm install @aotter/mantle
12
+ ```
13
+
14
+ Prereleases exist so a stable can be prepared in the open, and they are not
15
+ covered here: `alpha` is cut from `develop` and may break anything, `rc` is a
16
+ stable candidate cut from `main`. Installing a prerelease means opting into an
17
+ exact version, not a channel. [GitHub Releases](https://github.com/aotter/mantle/releases)
18
+ is the canonical, immutable change history; this chapter is the narrative one.
19
+
20
+ All ten packages share a single version and are published together, so mixed
21
+ versions across `@aotter/mantle*` are never a supported combination. Pin the
22
+ version you install and upgrade the whole set at once.
23
+
24
+ ## 0.1.2 — 2026-09-21
25
+
26
+ The first stable release, and Mantle's first public one. Everything before it
27
+ was an internal prerelease; there is no earlier stable to upgrade from and no
28
+ migration path to follow.
29
+
30
+ **Requires** Node.js 22 or newer. Cloudflare Workers is the supported host.
31
+
32
+ **The manifest engine.** Describe data, queries, actions and triggers as four
33
+ atoms — [Schema](../reference/schema.md), [View](../reference/view.md),
34
+ [Procedure](../reference/procedure.md) and [Trigger](../reference/trigger.md) —
35
+ in YAML under `manifests/`. `mantle generate` parses, links and compiles them
36
+ into `.mantle/generated/mantle.ts`: a sealed execution plan, TypeScript types
37
+ and typed bindings. Fingerprint or version mismatches between a generated plan
38
+ and the installed packages fail immediately and ask you to regenerate.
39
+
40
+ **Storage.** Each Manifest Schema becomes one native SQLite/D1 table. Authored
41
+ fields keep their exact names as columns; Mantle's row envelope adds
42
+ `_mantle_id`, `_mantle_status`, `_mantle_version`, `_mantle_author_id`,
43
+ `_mantle_created_at` and `_mantle_updated_at`. Generated migration artifacts
44
+ cover initial and additive changes; renames, type changes, data transforms and
45
+ `uniqueIndexes` tuple changes are a manual rebuild.
46
+
47
+ **Surfaces.** One contract drives all of them: public REST Views and HTTP
48
+ Triggers, server-rendered HTML with Markdown, `llms.txt` and sitemap output, the
49
+ Admin console and its prebuilt React SPA, and MCP — anonymous read-only Views at
50
+ `/api/mcp`, staff tools at `/api/mcp/staff`, and Admin WebMCP in the browser.
51
+ See [HTTP, MCP, CLI and packages](../reference/surface.md) for the full list.
52
+
53
+ **Hosting.** `createMantleWorker` assembles the conventional Cloudflare Worker:
54
+ D1 and assets bindings, Better Auth 1.7 (social providers, email OTP, magic
55
+ link, passkey), Admin, MCP, Web and R2 media uploads. The Bun and Vercel
56
+ adapters are experimental, cover public Views and HTTP Triggers only, and leave
57
+ authentication and CSRF to the host. [ChatGPT Sites](../sites/index.md) is a
58
+ first-class integration with a runnable reference.
59
+
60
+ **Agents.** `mantle skills` projects the installed package's skills into
61
+ `.agents/skills/mantle-*` and `.claude/skills/mantle-*`, so an agent working in
62
+ your project reads instructions matched to the version you installed. `--check`
63
+ detects drift without writing.
64
+
65
+ **Not yet covered.** Stable does not carry a published latency budget,
66
+ production-traffic measurement or a soak window; those acceptance items are
67
+ tracked for 0.1.3 in [#962](https://github.com/aotter/mantle/issues/962). The
68
+ Bun and Vercel adapters may change in a minor release.
69
+
70
+ ## Source
71
+
72
+ - [`CHANGELOG.md`](../../../CHANGELOG.md)
73
+ - [`docs/release-process.md`](../../../docs/release-process.md)
74
+ - [`.github/workflows/release.yml`](../../../.github/workflows/release.yml)
75
+ - [`scripts/release-tag-order.mjs`](../../../scripts/release-tag-order.mjs)
@@ -5,6 +5,12 @@ description: The files you own in a Mantle project, every mantle and mantle-harn
5
5
 
6
6
  This page describes a directly authored Mantle project: which files are yours, what the installed CLI does to them, and the loop you run before every commit. Surfaces are optional — take only what you need. [The minimal Worker](./quickstart-worker.md) is Spec + adapter without Admin. [Local Admin](./quickstart-admin.md) is the opt-in Dev UI path when humans need a console.
7
7
 
8
+ Cold start from GitHub or a marketplace host is the pinned install skill, not this page:
9
+
10
+ ```sh
11
+ npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
12
+ ```
13
+
8
14
  ## You own the project
9
15
 
10
16
  Core is a manifest compiler and a runtime, not a project generator. You write `package.json`, the manifests, the Worker entry, handlers, TypeScript and provider configuration. `mantle generate` compiles what exists; it never initializes a missing project or invents a default Schema, frontend or home route.
@@ -71,7 +77,7 @@ It does not project skills, update packages, change styling, provision providers
71
77
  | `createMantle({ storage, handlers, ports })` | Prepares storage eagerly once and returns the typed binding. No caching or retry. |
72
78
  | `bindMantle(runtime)` | The same typed binding over a runtime whose lifecycle the host already owns. |
73
79
 
74
- The binding exposes `mantle.views.<lowerCamelName>()`, `mantle.procedures.<name>(input, ctx)`, `mantle.entries.<collection>.createDraft({ data, authorId })` and the underlying `mantle.runtime`. Generated property names are deterministic lower-camel identifiers; calls keep the authored wire names internally. Details are in [HTTP, MCP, CLI and packages](../reference/surface.md).
80
+ The binding exposes `mantle.views.<lowerCamelName>()`, `mantle.procedures.<name>(input, ctx)`, `mantle.entries.<collection>.createDraft({ data, authorId })`, typed indexed field reads such as `mantle.entries.<collection>.findManyByDataField({ field, value, limit })`, and the underlying `mantle.runtime`. Generated property names are deterministic lower-camel identifiers; calls keep the authored wire names internally. Details are in [HTTP, MCP, CLI and packages](../reference/surface.md).
75
81
 
76
82
  ## The daily loop
77
83
 
@@ -92,15 +98,20 @@ Run the harness after any change to a Schema index, View filter or ordering, or
92
98
 
93
99
  `mantle skills` projects the skills the installed package marks `projection: project`. At this version those are `develop`, `plugin`, `theme` and `update`; `install`, `media-gc` and `provision` stay opt-in because they create projects, delete remote objects or handle production secrets. Both tool layouts receive identical bytes. Generation never rewrites these files.
94
100
 
95
- Install the version-matched plugin bundle in the agent host, using the exact version from `package.json`:
101
+ Install the version-matched plugin bundle in the agent host. Cold start uses
102
+ the published pin; an already-installed project uses the exact version from
103
+ `package.json` (currently `0.1.3-alpha.2`):
96
104
 
97
105
  ```sh
106
+ # Canonical
107
+ npx skills add aotter/mantle@v0.1.3-alpha.2 --skill install
108
+
98
109
  # Claude Code — two separate prompts
99
- /plugin marketplace add aotter/mantle@v<installed-version>
110
+ /plugin marketplace add aotter/mantle@v0.1.3-alpha.2
100
111
  /plugin install mantle@mantle
101
112
 
102
113
  # Codex
103
- codex plugin marketplace add aotter/mantle --ref v<installed-version>
114
+ codex plugin marketplace add aotter/mantle --ref v0.1.3-alpha.2
104
115
  codex plugin add mantle@mantle
105
116
  ```
106
117