@aotter/mantle 0.1.2-rc.1 → 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 +40 -17
- 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/adapter-guide.md +5 -0
- package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +109 -2
- package/docs/agent-prompts.md +41 -33
- package/docs/auth-hosting-model.md +1 -1
- package/docs/examples/host-chatgpt-sites/README.md +22 -8
- package/docs/examples/host-chatgpt-sites/package.json +1 -1
- package/docs/examples/host-chatgpt-sites/src/index.ts +1 -1
- package/docs/examples/host-local-admin-otp/README.md +13 -4
- package/docs/examples/host-local-admin-otp/package.json +4 -4
- package/docs/examples/host-minimal-worker/README.md +4 -3
- package/docs/examples/host-minimal-worker/package.json +2 -2
- package/docs/handbook/cloudflare/authentication.md +44 -3
- package/docs/handbook/concepts/mcp-and-agents.md +10 -5
- package/docs/handbook/concepts/runtime-and-adapters.md +2 -2
- 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/sites/host-reference.md +1 -4
- package/docs/handbook/sites/index.md +7 -13
- package/docs/handbook/start/project-and-cli.md +15 -4
- package/docs/handbook/start/quickstart-admin.md +20 -209
- package/docs/handbook/start/quickstart-worker.md +4 -4
- package/docs/labels.md +3 -3
- package/docs/migration-0.1.2.md +10 -126
- package/docs/release-process.md +36 -31
- package/docs/sealed-pipeline-ownership.md +1 -1
- package/docs/spec-only-host-adoption.md +3 -4
- package/package.json +25 -16
- package/skills/README.md +33 -11
- package/skills/install/SKILL.md +29 -13
- package/skills/plugin/SKILL.md +6 -6
- package/skills/provision/SKILL.md +27 -15
- package/skills/update/SKILL.md +4 -3
- package/docs/examples/host-chatgpt-sites/package-lock.json +0 -7088
package/docs/agent-prompts.md
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
# Task-specific agent prompts
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Cold start from GitHub or a marketplace host is one pinned skill:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx skills add aotter/mantle@v0.1.3-alpha.1 --skill install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Copy one block into a coding agent after that skill is present. Paths below
|
|
10
|
+
are relative to the Mantle docs root: `node_modules/@aotter/mantle/docs/`
|
|
11
|
+
after `@aotter/mantle` is installed, or `docs/` in the installed agent plugin.
|
|
12
|
+
`npx --no-install mantle --help` is the layered CLI overview; it mirrors the
|
|
13
|
+
authoring docs. A live `/mcp` catalog mirrors the Manifest → RuntimePlan, not
|
|
14
|
+
the CLI. Interview first. Worker is the default host unless the user names
|
|
15
|
+
ChatGPT Sites. There is no `mantle create`. Empty `generate` fails until
|
|
16
|
+
manifests exist. Admin is opt-in.
|
|
17
|
+
|
|
18
|
+
### Interview then build
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
Interview me about the service: host, who uses it, whether humans need a
|
|
22
|
+
Dev UI, and whether we only embed Spec/Runtime. If the install skill is
|
|
23
|
+
missing, run npx skills add aotter/mantle@v0.1.3-alpha.1 --skill install. Read the
|
|
24
|
+
install skill and npx --no-install mantle --help, then
|
|
25
|
+
handbook/start/project-and-cli.md.
|
|
26
|
+
Use examples/README.md as the examples index; copy builtin-* Manifests only
|
|
27
|
+
(not cf-primitives-*). Implement locally first. Take only the surfaces we
|
|
28
|
+
chose. For Spec-only use, skip Runtime and code generation. For a Worker
|
|
29
|
+
without Admin, follow examples/host-minimal-worker/. For a Worker with
|
|
30
|
+
Dev UI, follow examples/host-local-admin-otp/README.md. ChatGPT Sites only
|
|
31
|
+
if I name that host. No mantle create. Pin all @aotter/mantle* packages
|
|
32
|
+
to one exact version.
|
|
33
|
+
```
|
|
8
34
|
|
|
9
35
|
### Embed Runtime with typed APIs
|
|
10
36
|
|
|
@@ -32,30 +58,11 @@ Admin or wrangler ASSETS unless I ask.
|
|
|
32
58
|
### Full local Dev UI (opt-in)
|
|
33
59
|
|
|
34
60
|
```text
|
|
35
|
-
I want the optional Admin / Dev UI. Read
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Admin is installed). Wire createAuth email-otp + ConsoleEmailSender.
|
|
41
|
-
Then pnpm install && pnpm generate && pnpm dev, open /admin/sign-in, and
|
|
42
|
-
read the OTP from wrangler logs. If /admin is a white screen, fetch
|
|
43
|
-
/_mantle/admin/assets/* — 404 means ASSETS is missing, not a missing
|
|
44
|
-
frontend build.
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Interview then build
|
|
48
|
-
|
|
49
|
-
```text
|
|
50
|
-
Interview me about the service: host, who uses it, whether humans need a
|
|
51
|
-
Dev UI, and whether we only embed Spec/Runtime. Read mantle --help, then
|
|
52
|
-
handbook/start/project-and-cli.md. Use docs/examples/README.md as the
|
|
53
|
-
examples index; copy builtin-* Manifests only (not cf-primitives-*).
|
|
54
|
-
Implement locally first. Take only the surfaces we chose.
|
|
55
|
-
For Spec-only use, skip Runtime and code generation. For a Worker without
|
|
56
|
-
Admin, follow examples/host-minimal-worker/. For a Worker with Dev UI,
|
|
57
|
-
follow examples/host-local-admin-otp/. No mantle create. Pin all
|
|
58
|
-
@aotter/mantle* packages to one exact version.
|
|
61
|
+
I want the optional Admin / Dev UI. Read examples/host-local-admin-otp/README.md
|
|
62
|
+
(the procedural SSOT) and the thin pointer at handbook/start/quickstart-admin.md.
|
|
63
|
+
Interview me for a bootstrap owner email, then follow that example locally.
|
|
64
|
+
Prefer 127.0.0.1 over localhost. After pnpm check / smoke, restore .dev.vars
|
|
65
|
+
from .dev.vars.example before pnpm dev.
|
|
59
66
|
```
|
|
60
67
|
|
|
61
68
|
### Later layer: MCP or public web (opt-in)
|
|
@@ -71,12 +78,13 @@ claim Auth or Admin works from a public 200.
|
|
|
71
78
|
|
|
72
79
|
### Mantle on ChatGPT Sites, including media
|
|
73
80
|
|
|
81
|
+
Use only when the user names ChatGPT Sites as the host.
|
|
82
|
+
|
|
74
83
|
```text
|
|
75
84
|
Build with ChatGPT Sites; use Mantle for content management and publishing.
|
|
76
|
-
Read
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
installation while release support is pending. Derive Schema, View,
|
|
85
|
+
Read handbook/sites/index.md and examples/host-chatgpt-sites/README.md.
|
|
86
|
+
Use that runnable host as a reference and install its pinned
|
|
87
|
+
dependencies from the registry. Derive Schema, View,
|
|
80
88
|
Procedure and Trigger from my requirements and check the Admin editor/picker and public
|
|
81
89
|
projections against them. Request both Sites D1 and R2 when my workflow
|
|
82
90
|
includes uploads. Bind the R2 media port, declare media.purposes, and keep
|
|
@@ -8,7 +8,7 @@ basic login. The split is:
|
|
|
8
8
|
- **Mantle's conventional Cloudflare adapter** runs the generated site's
|
|
9
9
|
selected self-hosted or Mantle Platform hosted client configuration.
|
|
10
10
|
- **The application owner/host** declares the explicit auth mode and provider
|
|
11
|
-
configuration
|
|
11
|
+
configuration.
|
|
12
12
|
- **Mantle Platform** can sell hosted identity, provider setup, email,
|
|
13
13
|
and billing convenience for site owners who do not want to operate
|
|
14
14
|
those pieces.
|
|
@@ -2,21 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
The runnable application for [Mantle on ChatGPT Sites](../../handbook/sites/index.md) connects Sites D1 + R2 bindings, Sign in with ChatGPT identity, Mantle Admin and staff roles, same-origin media upload, a published-only article frontend, anonymous read-only `/api/mcp`, and Sites-session staff tools at `/api/mcp/staff`. Remote OAuth MCP remains a separate integration; see [MCP support](../../handbook/sites/host-reference.md#remote-mcp-is-a-separate-gate).
|
|
4
4
|
|
|
5
|
-
**SDK requirement:** this revision requires the checkout's `mountMantleAdmin.mcpEndpoints` support. Published `0.1.2-alpha.6` does not include it, even though the checkout still carries that version number. Use the exact packed-checkout workflow below; copying this folder and running `npm ci` against the registry is not a supported reproduction of this revision. Build typechecking and the endpoint smoke assertions reject that mismatch. Once a release contains this change, update every Mantle dependency and the lockfile together before switching back to registry installation.
|
|
6
|
-
|
|
7
5
|
After setup, follow [Publish your first article](../../handbook/sites/index.md#publish-your-first-article) to verify the editorial workflow in Admin.
|
|
8
6
|
|
|
9
7
|
## Before writing code
|
|
10
8
|
|
|
11
9
|
Read the user's business request and author the manifest for **their** records and lifecycle. The included `articles` example deliberately allows a title-only draft; `body` uses `x-mcp-hint: markdown`, while `coverAssetId` uses both `x-mantle-ref: media_assets` (Admin picker) and `x-mcp-hint: media-image` (agent guidance). The `published-articles` View is a list projection, not the detail page contract. If the user's content must always have a body, add it to `required`; if the public API must return body or cover ID, add those to the View's `fields`. Review staff roles, public filters and indexes before deployment. `mantle validate` checks grammar, **not** whether this model matches the business request. Manifest changes after deployment require a new reviewed D1 migration and matching storage fingerprint; never edit an applied migration.
|
|
12
10
|
|
|
13
|
-
##
|
|
11
|
+
## Install and run
|
|
12
|
+
|
|
13
|
+
Requires Mantle 0.1.2 or newer. Copy this directory outside the SDK checkout, then:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install # or: bun install
|
|
17
|
+
npx mantle validate --phase deploy # or: bunx mantle ...
|
|
18
|
+
npm run generate && npm run check
|
|
19
|
+
npx wrangler d1 migrations apply DB --local
|
|
20
|
+
npm run dev -- --port 4174 # leave running
|
|
21
|
+
npm test # smoke, in a second terminal
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`npm install` resolves `@aotter/mantle*` from the `latest` dist-tag; this
|
|
25
|
+
example does not commit a lockfile, so every fresh install picks up the
|
|
26
|
+
current stable release. `bun install` works too, resolving from
|
|
27
|
+
`package.json` the same way.
|
|
28
|
+
|
|
29
|
+
Keep `.openai/hosting.json`; do not copy an existing Site's `project_id`. To use another port, also set the Worker's `PUBLIC_ORIGIN` and the test's `MANTLE_TEST_ORIGIN` to that same localhost origin. Local test headers simulate Sites' trusted dispatcher; they do **not** prove deployed ChatGPT login.
|
|
30
|
+
|
|
31
|
+
Review the entire [smoke script](./scripts/check.mjs) before adapting it. It covers D1 CRUD/version conflict, owner/member/role revocation, R2 read/write/delete, media create → PUT → commit → public read, both advertised MCP URLs, public MCP `initialize`/`tools/list`/View call, staff MCP authentication/catalog, draft isolation, published article HTML/Markdown/SEO, and negative auth/Origin/size checks. It creates and deletes only its own test records and objects.
|
|
14
32
|
|
|
15
|
-
|
|
16
|
-
2. Run `node scripts/check-packed-consumer.mjs --project docs/examples/host-chatgpt-sites --output /absolute/path/to/new-sites-reproduction -- pnpm build`. The output path must not exist and must be outside the SDK checkout. This existing helper copies the committed example, packs the SDK, installs exact tarball overrides for all Mantle packages, and reports the source SHA and package hashes. Keep the resulting `artifacts/` beside `consumer/` so the lockfile's tarball paths remain valid.
|
|
17
|
-
3. Work in `/absolute/path/to/new-sites-reproduction/consumer`. Run `pnpm exec mantle validate --phase deploy`, `pnpm check`, then `pnpm exec wrangler d1 migrations apply DB --local`. Keep `.openai/hosting.json` but do not copy an existing Site's `project_id`.
|
|
18
|
-
4. Start `pnpm dev --port 4174` in another terminal, then run `pnpm test`. To use another port, also override the Worker's `PUBLIC_ORIGIN` and set `MANTLE_TEST_ORIGIN` for the test to that same localhost origin. Local test headers simulate Sites' trusted dispatcher; they do **not** prove deployed ChatGPT login.
|
|
19
|
-
5. Review the entire [smoke script](./scripts/check.mjs) before adapting it. It covers D1 CRUD/version conflict, owner/member/role revocation, R2 read/write/delete, media create → PUT → commit → public read, both advertised MCP URLs, public MCP `initialize`/`tools/list`/View call, staff MCP authentication/catalog, draft isolation, published article HTML/Markdown/SEO, and negative auth/Origin/size checks. It creates and deletes only its own test records and objects.
|
|
33
|
+
To run this example against an unreleased Mantle checkout instead of the registry, use `node scripts/check-packed-consumer.mjs --project docs/examples/host-chatgpt-sites --output <new dir> -- pnpm build` from a clean SDK checkout and work in its `consumer/`. That path is for SDK development only.
|
|
20
34
|
|
|
21
35
|
The checked-in `drizzle/` migrations and `src/storage-fingerprint.json` match the example manifest. `scripts/migration.mjs` shows the one-time generation mechanism; do **not** run it against a deployed database or overwrite an applied migration. For a new business manifest, generate/review an initial migration before the first deployment; for a later change, generate an additive migration from the previous schema state.
|
|
22
36
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"mantle-sites-reference","version":"0.0.1","private":true,"type":"module","scripts":{"build":"node scripts/build.mjs","dev":"wrangler dev","generate":"mantle generate","check":"tsc --noEmit","test":"node --experimental-strip-types scripts/check.mjs"},"dependencies":{"@aotter/mantle":"
|
|
1
|
+
{"name":"mantle-sites-reference","version":"0.0.1","private":true,"type":"module","scripts":{"build":"node scripts/build.mjs","dev":"wrangler dev","generate":"mantle generate","check":"tsc --noEmit","test":"node --experimental-strip-types scripts/check.mjs"},"dependencies":{"@aotter/mantle":"latest","@aotter/mantle-admin":"latest","@aotter/mantle-admin-ui":"latest","@aotter/mantle-cloudflare":"latest","@aotter/mantle-web":"latest","hono":"^4.9.0","micromark":"4.0.2","zod":"^4.0.0"},"devDependencies":{"@cloudflare/workers-types":"*","esbuild":"^0.28.0","typescript":"^5.9.0","wrangler":"^4.0.0"}}
|
|
@@ -23,7 +23,7 @@ function assemble(env:Env) {
|
|
|
23
23
|
mountWeb(app,get);
|
|
24
24
|
mountMcp(app,get,auth);
|
|
25
25
|
mountMedia(app,auth,env);
|
|
26
|
-
app.get('/health',async()=>{await get();return Response.json({ok:true,storage:'D1',auth:'ChatGPT Sites',mantle:'0.1.2
|
|
26
|
+
app.get('/health',async()=>{await get();return Response.json({ok:true,storage:'D1',auth:'ChatGPT Sites',mantle:'0.1.2'});});
|
|
27
27
|
app.get('/admin/sign-in',async c=>{
|
|
28
28
|
if(c.req.header('cookie')?.split(';').some(v=>v.trim()==='mantle-sites-signout=1')) {
|
|
29
29
|
c.header('Set-Cookie','mantle-sites-signout=; Path=/admin/sign-in; HttpOnly; SameSite=Strict; Max-Age=0'+(env.PUBLIC_ORIGIN.startsWith('https:')?'; Secure':''));
|
|
@@ -9,10 +9,9 @@ Schema is example business data; `mantle generate` never invents it.
|
|
|
9
9
|
Spec + adapter without Admin:
|
|
10
10
|
[`docs/examples/host-minimal-worker`](../host-minimal-worker/README.md).
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
in a disposable copy.
|
|
12
|
+
This reference depends on every `@aotter/mantle*` package via the `latest`
|
|
13
|
+
dist-tag, so a fresh install always resolves the current stable release.
|
|
14
|
+
Core's test runner substitutes its exact candidate in a disposable copy.
|
|
16
15
|
|
|
17
16
|
## One-shot
|
|
18
17
|
|
|
@@ -59,6 +58,16 @@ paths must fall through to Static Assets.
|
|
|
59
58
|
|
|
60
59
|
`ConsoleEmailSender` is the local path only. Do not wire it in production.
|
|
61
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
|
+
|
|
62
71
|
## What this project does not do
|
|
63
72
|
|
|
64
73
|
`/` is `404`: no visitor frontend is installed. Public GET
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
"check": "node ensure-dev-vars.mjs && mantle generate && mantle generate --check && mantle validate && mantle skills && mantle skills --check && tsc --noEmit && node smoke.mjs"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aotter/mantle": "
|
|
15
|
-
"@aotter/mantle-admin": "
|
|
16
|
-
"@aotter/mantle-admin-ui": "
|
|
17
|
-
"@aotter/mantle-cloudflare": "
|
|
14
|
+
"@aotter/mantle": "latest",
|
|
15
|
+
"@aotter/mantle-admin": "latest",
|
|
16
|
+
"@aotter/mantle-admin-ui": "latest",
|
|
17
|
+
"@aotter/mantle-cloudflare": "latest",
|
|
18
18
|
"better-auth": "1.7.2",
|
|
19
19
|
"hono": "^4.13.3",
|
|
20
20
|
"zod": "^4.5.4",
|
|
@@ -9,9 +9,10 @@ Schema/View is example business data; `mantle generate` never invents it.
|
|
|
9
9
|
|
|
10
10
|
For your own project, author package.json, manifests, Worker/provider config
|
|
11
11
|
and TypeScript settings for your requirements. Pin all selected `@aotter/mantle*`
|
|
12
|
-
dependencies to the same intended release. This reference
|
|
13
|
-
|
|
14
|
-
its exact candidate in a
|
|
12
|
+
dependencies to the same intended release. This reference depends on them via
|
|
13
|
+
the `latest` dist-tag instead, so a fresh install always resolves the current
|
|
14
|
+
stable release; Core's test runner substitutes its exact candidate in a
|
|
15
|
+
disposable copy.
|
|
15
16
|
|
|
16
17
|
Outside the SDK workspace, with Node 22+ and pnpm 9+:
|
|
17
18
|
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"check": "mantle generate && mantle generate --check && mantle validate && mantle skills && mantle skills --check && tsc --noEmit && node smoke.mjs"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@aotter/mantle": "
|
|
14
|
-
"@aotter/mantle-cloudflare": "
|
|
13
|
+
"@aotter/mantle": "latest",
|
|
14
|
+
"@aotter/mantle-cloudflare": "latest",
|
|
15
15
|
"better-auth": "1.7.2",
|
|
16
16
|
"hono": "^4.13.3",
|
|
17
17
|
"zod": "^4.5.4",
|
|
@@ -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).
|
|
@@ -112,7 +112,7 @@ Bun, with the server and SQLite handle staying yours:
|
|
|
112
112
|
|
|
113
113
|
```ts
|
|
114
114
|
import { Database } from "bun:sqlite";
|
|
115
|
-
import { createBunMantle } from "@aotter/mantle-bun";
|
|
115
|
+
import { createBunMantle } from "@aotter/mantle-bun"; // experimental
|
|
116
116
|
|
|
117
117
|
const database = new Database("app.sqlite");
|
|
118
118
|
const mantle = createBunMantle({ plan, database, handlers });
|
|
@@ -128,7 +128,7 @@ Vercel Functions, with storage injected:
|
|
|
128
128
|
|
|
129
129
|
```ts
|
|
130
130
|
import { SqliteMantleStorageAdapter } from "@aotter/mantle-runtime";
|
|
131
|
-
import { createVercelMantle } from "@aotter/mantle-vercel";
|
|
131
|
+
import { createVercelMantle } from "@aotter/mantle-vercel"; // experimental
|
|
132
132
|
import { LibsqlDatabaseDriver } from "@aotter/mantle-vercel/libsql";
|
|
133
133
|
|
|
134
134
|
const mantle = createVercelMantle({
|
|
@@ -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)
|