@aotter/mantle 0.1.2 → 0.1.3-alpha.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 +36 -13
- package/dist/auth.d.ts +2 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +2 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +3 -0
- package/dist/cli/generate.js.map +1 -1
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +5 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +3 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/codegen/emitMantleModule.d.ts.map +1 -1
- package/dist/codegen/emitMantleModule.js +38 -2
- package/dist/codegen/emitMantleModule.js.map +1 -1
- package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +109 -2
- package/docs/agent-prompts.md +38 -29
- package/docs/auth-hosting-model.md +1 -1
- package/docs/examples/host-local-admin-otp/README.md +10 -0
- package/docs/handbook/cloudflare/authentication.md +44 -3
- package/docs/handbook/concepts/mcp-and-agents.md +10 -5
- package/docs/handbook/navigation.json +8 -2
- package/docs/handbook/reference/diagnostics.md +5 -1
- package/docs/handbook/reference/manifest.md +1 -1
- package/docs/handbook/reference/procedure.md +1 -0
- package/docs/handbook/reference/surface.md +6 -4
- package/docs/handbook/reference/trigger.md +1 -1
- package/docs/handbook/releases/index.md +75 -0
- package/docs/handbook/start/project-and-cli.md +15 -4
- package/docs/handbook/start/quickstart-admin.md +20 -209
- package/docs/labels.md +3 -3
- package/docs/migration-0.1.2.md +10 -126
- package/docs/release-process.md +36 -32
- package/docs/spec-only-host-adoption.md +1 -1
- package/package.json +25 -16
- package/skills/README.md +21 -15
- package/skills/install/SKILL.md +5 -5
- package/skills/plugin/SKILL.md +6 -6
- package/skills/provision/SKILL.md +27 -17
- 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` |
|
|
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
|
|
116
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
97
|
+
# Canonical
|
|
98
|
+
npx skills add aotter/mantle@v0.1.3-alpha.1 --skill install
|
|
99
|
+
|
|
100
|
+
# Claude Code — two separate prompts
|
|
101
|
+
/plugin marketplace add aotter/mantle@v0.1.3-alpha.1
|
|
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": "
|
|
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
|
|
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 `
|
|
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.1 --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
|
|
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.1`):
|
|
96
104
|
|
|
97
105
|
```sh
|
|
106
|
+
# Canonical
|
|
107
|
+
npx skills add aotter/mantle@v0.1.3-alpha.1 --skill install
|
|
108
|
+
|
|
98
109
|
# Claude Code — two separate prompts
|
|
99
|
-
/plugin marketplace add aotter/mantle@
|
|
110
|
+
/plugin marketplace add aotter/mantle@v0.1.3-alpha.1
|
|
100
111
|
/plugin install mantle@mantle
|
|
101
112
|
|
|
102
113
|
# Codex
|
|
103
|
-
codex plugin marketplace add aotter/mantle --ref
|
|
114
|
+
codex plugin marketplace add aotter/mantle --ref v0.1.3-alpha.1
|
|
104
115
|
codex plugin add mantle@mantle
|
|
105
116
|
```
|
|
106
117
|
|