@aotter/mantle 0.2.0-alpha.7 → 0.2.0-alpha.8
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 +10 -5
- package/dist/auth/caller.js +2 -1
- package/dist/auth/identity.d.ts +2 -2
- package/dist/auth/oauth.d.ts +33 -1
- package/dist/auth/oauth.js +121 -28
- package/dist/auth/routes.js +3 -2
- package/dist/build/compiler.d.ts +44 -122
- package/dist/build/compiler.js +202 -277
- package/dist/build/spec/domain/model/ManifestGrammar.d.ts +6 -5
- package/dist/build/spec/domain/model/SqlIr.d.ts +2 -0
- package/dist/build/spec/domain/service/PlanFingerprint.d.ts +2 -0
- package/dist/build/spec/kernel/diagnostic.d.ts +1 -1
- package/dist/domain/describe/{chunk-ZIQVLA7X.js → chunk-ZVXBGP7Y.js} +69 -8
- package/dist/domain/describe/index.js +1 -1
- package/dist/domain/describe/target-hash.js +1 -1
- package/dist/domain/describe/targets.d.ts +1 -1
- package/dist/domain/generated-tools.js +2 -0
- package/dist/domain/paging.js +41 -15
- package/dist/infrastructure/bundle.js +105 -15
- package/dist/infrastructure/cli.js +11 -15
- package/dist/infrastructure/generate.d.ts +1 -3
- package/dist/infrastructure/generate.js +2 -2
- package/dist/infrastructure/generateAuth.js +6 -0
- package/dist/infrastructure/inputs.js +8 -3
- package/dist/infrastructure/wrangler.js +35 -12
- package/dist/kernel/drain.d.ts +5 -0
- package/dist/kernel/drain.js +35 -0
- package/dist/kernel/driver/postgres.js +3 -0
- package/dist/kernel/host-caller.d.ts +1 -0
- package/dist/kernel/host-caller.js +1 -0
- package/dist/kernel/mcp-apps.d.ts +29 -9
- package/dist/kernel/mcp-apps.js +2 -2
- package/dist/kernel/mcp-handler.js +100 -17
- package/dist/kernel/mcp.d.ts +7 -2
- package/dist/kernel/mcp.js +29 -5
- package/dist/kernel/rest-router.js +3 -2
- package/dist/kernel/site.d.ts +1 -3
- package/dist/kernel/site.js +2 -2
- package/dist/kernel/web.d.ts +3 -1
- package/dist/kernel/web.js +3 -0
- package/dist/usecase/adminBindings.js +2 -3
- package/dist/usecase/adminClient.js +87 -0
- package/dist/usecase/adminDescriptors.js +19 -65
- package/dist/usecase/generate.js +5 -4
- package/dist/usecase/mcp-media.js +2 -2
- package/dist/usecase/mcp.js +16 -12
- package/dist/usecase/paging.js +2 -3
- package/dist/usecase/publishing.js +2 -2
- package/dist/usecase/tool-version.js +9 -0
- package/dist/usecase/translations.js +1 -1
- package/dist/usecase/validation.js +120 -19
- package/dist/usecase/web.js +3 -2
- package/docs/examples/README.md +1 -1
- package/docs/examples/app-shell/README.md +39 -1
- package/docs/examples/app-shell/package.json +14 -6
- package/docs/examples/app-shell/smoke-kit.mjs +54 -0
- package/docs/examples/app-shell/src/auth.ts +4 -2
- package/docs/examples/app-shell/src/service.ts +2 -2
- package/docs/examples/app-shell/tsconfig.json +4 -1
- package/docs/examples/app-shell/worker-configuration.d.ts +2 -2
- package/docs/examples/app-shell/wrangler.jsonc +5 -4
- package/docs/examples/generated-web/README.md +6 -2
- package/docs/examples/generated-web/wrangler.jsonc +1 -0
- package/docs/examples/purchasing/README.md +1 -1
- package/docs/examples/purchasing/manifests/purchasing.yaml +1 -1
- package/docs/examples/purchasing/smoke.mjs +10 -4
- package/docs/examples/purchasing/src/admin.tsx +13 -0
- package/docs/examples/purchasing/wrangler.jsonc +5 -4
- package/docs/examples/recipes/wrangler.jsonc +5 -4
- package/docs/handbook/cloudflare/authentication.md +22 -2
- package/docs/handbook/cloudflare/deploy-and-operate.md +21 -0
- package/docs/handbook/cloudflare/service-entry.md +55 -28
- package/docs/handbook/concepts/authorization.md +1 -1
- package/docs/handbook/concepts/mcp-and-agents.md +19 -0
- package/docs/handbook/examples/hub.md +1 -1
- package/docs/handbook/guides/admin-ui.md +107 -1
- package/docs/handbook/guides/agent-setup.md +27 -9
- package/docs/handbook/guides/mcp-apps.md +445 -6
- package/docs/handbook/guides/native-admin.md +296 -51
- package/docs/handbook/guides/oauth.md +61 -5
- package/docs/handbook/guides/public-pages.md +2 -2
- package/docs/handbook/guides/size-and-startup.md +1 -1
- package/docs/handbook/navigation.json +0 -4
- package/docs/handbook/reference/admin-descriptors.md +0 -35
- package/docs/handbook/reference/procedure.md +5 -1
- package/docs/handbook/reference/schema.md +13 -3
- package/docs/handbook/reference/surface.md +123 -13
- package/docs/handbook/reference/trigger.md +34 -1
- package/docs/handbook/reference/view.md +1 -1
- package/docs/handbook/releases/index.md +74 -2
- package/docs/handbook/start/overview.md +1 -1
- package/docs/handbook/start/project-and-cli.md +8 -8
- package/docs/handbook/start/quickstart-worker.md +1 -1
- package/docs/skills/develop/SKILL.md +4 -3
- package/docs/skills/integrate/SKILL.md +1 -1
- package/docs/skills/media-gc/SKILL.md +7 -5
- package/docs/skills/provision/SKILL.md +4 -1
- package/docs/skills/update/SKILL.md +1 -1
- package/docs/upgrade-0.1-to-0.2.md +6 -6
- package/package.json +2 -1
- package/skills/README.md +11 -16
- package/skills/mantle/SKILL.md +4 -4
- package/skills/mantle/references/local-testing.md +4 -0
- package/skills/mantle/references/web.md +1 -1
- package/skills/mantle/references/website.md +24 -22
- package/docs/examples/app-shell/src/admin.ts +0 -10
- package/docs/examples/purchasing/src/admin.ts +0 -12
- package/docs/handbook/guides/admin-extensions.md +0 -20
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "node_modules/wrangler/config-schema.json",
|
|
3
3
|
"name": "mantle-app",
|
|
4
|
-
"main": "dist/index.js",
|
|
4
|
+
"main": "dist/worker/index.js",
|
|
5
5
|
"compatibility_date": "2026-09-01",
|
|
6
6
|
"compatibility_flags": [
|
|
7
|
-
"nodejs_compat"
|
|
7
|
+
"nodejs_compat",
|
|
8
|
+
"global_fetch_strictly_public"
|
|
8
9
|
],
|
|
9
10
|
"vars": {
|
|
10
11
|
"EMPLOYEE_DOMAIN": "example.com"
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
}
|
|
37
38
|
],
|
|
38
39
|
"assets": {
|
|
39
|
-
"directory": "
|
|
40
|
+
"directory": "dist/client",
|
|
40
41
|
"binding": "ASSETS",
|
|
41
42
|
"run_worker_first": true,
|
|
42
43
|
"html_handling": "none"
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
},
|
|
49
50
|
"no_bundle": true,
|
|
50
51
|
"find_additional_modules": true,
|
|
51
|
-
"base_dir": "dist",
|
|
52
|
+
"base_dir": "dist/worker",
|
|
52
53
|
"build": {
|
|
53
54
|
"command": "mantle bundle src/index.ts dist",
|
|
54
55
|
"watch_dir": [
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "node_modules/wrangler/config-schema.json",
|
|
3
3
|
"name": "mantle-app",
|
|
4
|
-
"main": "dist/index.js",
|
|
4
|
+
"main": "dist/worker/index.js",
|
|
5
5
|
"compatibility_date": "2026-09-01",
|
|
6
6
|
"compatibility_flags": [
|
|
7
|
-
"nodejs_compat"
|
|
7
|
+
"nodejs_compat",
|
|
8
|
+
"global_fetch_strictly_public"
|
|
8
9
|
],
|
|
9
10
|
"secrets": {
|
|
10
11
|
"required": [
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
"MEDIA_S3_ENDPOINT": "https://mantle-app-media.ACCOUNT_ID.r2.cloudflarestorage.com"
|
|
44
45
|
},
|
|
45
46
|
"assets": {
|
|
46
|
-
"directory": "
|
|
47
|
+
"directory": "dist/client",
|
|
47
48
|
"binding": "ASSETS",
|
|
48
49
|
"run_worker_first": true,
|
|
49
50
|
"html_handling": "none"
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
},
|
|
54
55
|
"no_bundle": true,
|
|
55
56
|
"find_additional_modules": true,
|
|
56
|
-
"base_dir": "dist",
|
|
57
|
+
"base_dir": "dist/worker",
|
|
57
58
|
"build": {
|
|
58
59
|
"command": "mantle bundle src/index.ts dist",
|
|
59
60
|
"watch_dir": [
|
|
@@ -12,7 +12,22 @@ Mantle supplies the database adapter, schema generation and caller resolution:
|
|
|
12
12
|
|
|
13
13
|
## `auth.ts`
|
|
14
14
|
|
|
15
|
-
Copy `src/auth.ts` from the [app shell](../../examples/app-shell/README.md), which CI runs from the packed packages
|
|
15
|
+
Copy `src/auth.ts` from the [app shell](../../examples/app-shell/README.md), which CI runs from the packed packages;
|
|
16
|
+
installed, it is `node_modules/@aotter/mantle/docs/examples/app-shell/src/auth.ts`. Its essentials:
|
|
17
|
+
|
|
18
|
+
- Imports: `betterAuth` from `better-auth/minimal`; `admin`, `emailOTP` (and `jwt` for MCP) from `better-auth/plugins`;
|
|
19
|
+
`createAccessControl` from `better-auth/plugins/access`; `defaultStatements` from `better-auth/plugins/admin/access`;
|
|
20
|
+
`d1Adapter` from `@aotter/mantle/auth/adapter`; the hardening and bootstrap helpers below. For MCP it also imports `jwt`,
|
|
21
|
+
`mcp` from `@better-auth/mcp`, `cimd` from `@better-auth/cimd` and `oauthGrantExtension`/`oauthGrantRevocation` from
|
|
22
|
+
`@aotter/mantle/auth/oauth`; the file marks that block as deletable.
|
|
23
|
+
- `authOptions(env)` returns plain options and is the module's export for `--auth src/auth.ts`. Generation calls it with an
|
|
24
|
+
empty `env`, so it must not throw: fall back to placeholders for `secret` and origin there.
|
|
25
|
+
- The bootstrap admin: `d1BootstrapAdmin(env.DB, { match: "email", value: env.ADMIN_EMAIL }, () => ({ options: { plugins } }))`
|
|
26
|
+
is assigned to `databaseHooks.user.create.after` and `.update.after` ([Identity helpers](#identity-helpers)).
|
|
27
|
+
- `createAuth(env)` is the same module's second export: it checks its three variables are set and returns
|
|
28
|
+
`betterAuth(authOptions(env))`. The Worker's `authLoader(() => import("./auth.js").then(m => m.createAuth(env)))`
|
|
29
|
+
([service composition](service-entry.md)) is what calls it.
|
|
30
|
+
|
|
16
31
|
The application owns Better Auth and its optional plugins. Schema generation reads plain options without constructing
|
|
17
32
|
Better Auth; apply `schema.sql` during deployment, never at boot. OAuth resource
|
|
18
33
|
rows are created on first OAuth use from runtime configuration; they require no
|
|
@@ -154,7 +169,12 @@ Better Auth adapter code stays outside the Worker startup graph.
|
|
|
154
169
|
|
|
155
170
|
`@aotter/mantle/auth/admin` exports `guardAuthAdminRequest(request, resolveFreshCaller, basePath?)` for selected auth transports. It resolves authority only for raw/encoded admin paths, requires an admin cookie session and checks mutation origin. Both the app-owned template and OAuth routes use this same guard.
|
|
156
171
|
|
|
157
|
-
|
|
172
|
+
Bare `ctx.admin` admits any verified admin layer classified by Better Auth's
|
|
173
|
+
`adminRoles` / `adminUserIds`. Role lists in `requires` are refused; decide
|
|
174
|
+
per-target permissions in `authorize(target, caller)`, using `hasAdminRole`
|
|
175
|
+
when appropriate. Verified role names match `^[a-z][a-zA-Z0-9_-]{0,62}$`.
|
|
176
|
+
Member role names alone grant no privilege. See
|
|
177
|
+
[authorization requirements](../reference/authorization.md).
|
|
158
178
|
|
|
159
179
|
The generated auth DDL adds a native `(createdAt, id)` member-paging index (respecting schema field/table names). Unfiltered forward/backward paging orders native timestamps through that index; PostgreSQL cursors retain all six fractional digits and bind as text before timestamp casting. Member search (`%term%` over id/name/email, casting UUID ids to text only in the search expression) is a full scan; the index does not accelerate it.
|
|
160
180
|
|
|
@@ -5,6 +5,27 @@ description: Install schema explicitly, migrate before deploying, and operate a
|
|
|
5
5
|
|
|
6
6
|
Run the same generation command with `--check`, typecheck the application and test its selected routes before deploying. Keep generated TypeScript, SQL and Wrangler edits committed; keep bundle `dist/` untracked. The app owns the database, auth secrets, trusted origin and assets.
|
|
7
7
|
|
|
8
|
+
## Public fetch routing
|
|
9
|
+
|
|
10
|
+
Workers use `compatibility_flags: ["nodejs_compat", "global_fetch_strictly_public"]`.
|
|
11
|
+
The public-fetch flag routes native `fetch` through Cloudflare's public Internet
|
|
12
|
+
boundary. OAuth verification needs it to fetch JWKS on the service's own public
|
|
13
|
+
hostname; CIMD also requires this boundary for client metadata. `nodejs_compat`
|
|
14
|
+
alone does not establish it.
|
|
15
|
+
|
|
16
|
+
`mantle generate cloudflare-*` adds these flags without replacing other flags,
|
|
17
|
+
including in environments with their own `compatibility_flags` array. The same
|
|
18
|
+
command with `--check` refuses missing flags without writing. JSON/JSONC configs
|
|
19
|
+
are updated; TOML receives manual-edit instructions. Keep both flags in every
|
|
20
|
+
TOML environment override. An explicit `global_fetch_private_origin` is preserved
|
|
21
|
+
for applications without OAuth/CIMD, but conflicts with selected OAuth/CIMD auth
|
|
22
|
+
and with the public-fetch flag: generation refuses that config without rewriting
|
|
23
|
+
it. Such applications must use public routing for their OAuth/CIMD transport.
|
|
24
|
+
|
|
25
|
+
After deployment, complete a real OAuth sign-in and use the resulting bearer
|
|
26
|
+
for MCP `initialize` and `tools/list`. Local development and cookie-only Admin
|
|
27
|
+
checks do not verify production public-hostname routing.
|
|
28
|
+
|
|
8
29
|
## Install and change schemas
|
|
9
30
|
|
|
10
31
|
The runtime never creates, alters or checks tables. Boot and requests run no DDL, introspection or schema checks.
|
|
@@ -31,16 +31,18 @@ import { authLoader, resolveCaller } from '@aotter/mantle/auth';
|
|
|
31
31
|
import { authRoutes } from '@aotter/mantle/auth/routes';
|
|
32
32
|
import { withAuthTasks } from '@aotter/mantle/auth/hardening';
|
|
33
33
|
import { checkOrigin, surfaceFailure, type Caller } from '@aotter/mantle/contracts';
|
|
34
|
-
import { admin } from '@aotter/mantle-admin/server';
|
|
34
|
+
import { admin, adminShell } from '@aotter/mantle-admin/server';
|
|
35
|
+
import { adminMcpApps } from '@aotter/mantle-admin/mcp';
|
|
35
36
|
import { entries } from '@aotter/mantle-admin/server/entries';
|
|
36
37
|
import { adminBindings } from '../generated/admin-bindings.js';
|
|
37
|
-
import {
|
|
38
|
+
import { mcpAppCatalogues } from '../generated/admin-descriptors.js';
|
|
38
39
|
import { mcp } from '../generated/mcp.js';
|
|
39
40
|
import { rest } from '../generated/rest.js';
|
|
40
41
|
import { handlers } from './handlers.js'; // omit when there are no ref Procedures
|
|
41
42
|
|
|
42
43
|
// Env comes from worker-configuration.d.ts: declare DB, ASSETS, the email provider binding
|
|
43
|
-
// and the
|
|
44
|
+
// and the PUBLIC_ORIGIN, BETTER_AUTH_SECRET and ADMIN_EMAIL secrets in Wrangler.
|
|
45
|
+
// createAuth(env) is exported by the app's src/auth.ts (see the auth template).
|
|
44
46
|
let auth: import('@aotter/mantle/auth').AuthLoader<ReturnType<typeof import('./auth.js').createAuth>> | undefined;
|
|
45
47
|
export default {
|
|
46
48
|
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
@@ -50,7 +52,7 @@ export default {
|
|
|
50
52
|
let authority: Promise<Caller> | undefined, readAuthority: Promise<Caller> | undefined;
|
|
51
53
|
const resolve = (readOnly: boolean) => resolveCaller(request, auth!, {
|
|
52
54
|
readOnly,
|
|
53
|
-
audience: [`${env.
|
|
55
|
+
audience: [`${env.PUBLIC_ORIGIN}/mcp`, `${env.PUBLIC_ORIGIN}/mcp/admin`],
|
|
54
56
|
responseHeaders: headers,
|
|
55
57
|
}).then(value => { checkOrigin(request, value); return value; });
|
|
56
58
|
const caller = (_request?: Request, _env?: Env, classification = { readOnly: false }) =>
|
|
@@ -60,9 +62,9 @@ export default {
|
|
|
60
62
|
const routes = [
|
|
61
63
|
authRoutes(auth, { methods: [{ kind: 'email-otp' }], resolver: caller }),
|
|
62
64
|
admin(bindings.metadata, bindings.views, bindings.operations, {
|
|
63
|
-
...options, routes: entries(bindings.schemas, bindings.adapter),
|
|
65
|
+
...options, developerViews: bindings.developerViews, routes: entries(bindings.schemas, bindings.adapter),
|
|
64
66
|
}),
|
|
65
|
-
mcp(options),
|
|
67
|
+
mcp({ ...options, apps: { admin: adminMcpApps({ catalogue: mcpAppCatalogues.admin }) } }),
|
|
66
68
|
rest(options),
|
|
67
69
|
];
|
|
68
70
|
try {
|
|
@@ -74,8 +76,9 @@ export default {
|
|
|
74
76
|
for (const cookie of headers.getSetCookie()) result.headers.append('set-cookie', cookie);
|
|
75
77
|
return result;
|
|
76
78
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
// Serves the built Admin shell for /admin and its client routes; undefined for any other path.
|
|
80
|
+
const shell = await adminShell(request, path => env.ASSETS.fetch(new Request(new URL('/admin/' + path, request.url), request)));
|
|
81
|
+
if (shell) return shell;
|
|
79
82
|
// Your public frontend/web.ts routes can use an anonymous Caller without
|
|
80
83
|
// loading auth; their generated SQL still controls publication visibility.
|
|
81
84
|
return new Response('Not found', { status: 404, headers: { 'cache-control': 'no-store' } });
|
|
@@ -88,28 +91,32 @@ export default {
|
|
|
88
91
|
Remove imports and array entries for omitted surfaces. `mcp.ts` exists only when
|
|
89
92
|
the manifest has an MCP surface, and requires the documented optional MCP peer.
|
|
90
93
|
Pass the same handlers to each selected surface that exposes ref Procedures.
|
|
91
|
-
Bind site/media/identity
|
|
94
|
+
Bind site/media/identity capabilities through the selected Admin/MCP
|
|
92
95
|
options; do not expose native auth administrative routes without `authRoutes` or
|
|
93
96
|
`guardAuthAdminRequest`. `withAuthTasks` retains background callbacks on the
|
|
94
97
|
current request's context; `authHardening` configures the background handler.
|
|
95
98
|
Select `--admin` to emit `admin-bindings.ts`. Admin's API is a separate
|
|
96
99
|
`@aotter/mantle-admin` package; the SDK never imports it. The API handles only
|
|
97
|
-
`/admin/api`.
|
|
98
|
-
client routes, including `/admin/sign-in`,
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
`/admin/api`. `adminShell` (in the block above) serves the built shell for `/admin` and
|
|
101
|
+
client routes, including `/admin/sign-in`; it sets `frame-ancestors 'none'`, `X-Frame-Options: DENY`, `no-store` and `nosniff`
|
|
102
|
+
on the shell, answers a missing file under `/admin` with a 404 `Response`, and returns `undefined` for other paths and
|
|
103
|
+
non-GET requests. The app owns the rest of the CSP and the asset cache headers.
|
|
104
|
+
Collection statistics use the generated shared `statisticsView`:
|
|
101
105
|
its collection parameter selects only a generated table and visibility policy.
|
|
102
106
|
|
|
103
107
|
## Workers assets and secrets
|
|
104
108
|
|
|
105
109
|
`mantle generate cloudflare-d1 generated manifests/*.yaml --auth src/auth.ts --admin`
|
|
106
|
-
updates the split-build Wrangler keys
|
|
110
|
+
updates the split-build Wrangler keys (`main` and `base_dir` under `dist/worker`),
|
|
111
|
+
ensures `nodejs_compat` and defaults to `global_fetch_strictly_public` ([public fetch routing](deploy-and-operate.md#public-fetch-routing))
|
|
112
|
+
and, with `--admin`, `assets.directory`: `mantle bundle` builds Admin into
|
|
113
|
+
`dist/client/admin`, served at `/admin`. Configure the rest explicitly; JSONC
|
|
107
114
|
comments and unrelated settings survive regeneration:
|
|
108
115
|
|
|
109
116
|
```jsonc
|
|
110
117
|
{
|
|
111
118
|
"assets": {
|
|
112
|
-
"directory": "
|
|
119
|
+
"directory": "dist/client",
|
|
113
120
|
"binding": "ASSETS",
|
|
114
121
|
"run_worker_first": true,
|
|
115
122
|
"html_handling": "none"
|
|
@@ -119,16 +126,18 @@ comments and unrelated settings survive regeneration:
|
|
|
119
126
|
```
|
|
120
127
|
|
|
121
128
|
`run_worker_first` lets the application supply shell/security/cache headers;
|
|
122
|
-
`none` keeps `index.html` available by name.
|
|
129
|
+
`none` keeps `index.html` available by name. `adminShell(request, assets)` passes
|
|
130
|
+
paths relative to the Admin directory, so the app fetches them under `/admin/`:
|
|
131
|
+
`path => env.ASSETS.fetch(new Request(new URL("/admin/" + path, request.url), request))`. Admin assets are not a public database
|
|
123
132
|
API. PostgreSQL uses application-owned Hyperdrive/postgres.js wiring,
|
|
124
133
|
with Hyperdrive query caching disabled for authority and mutation visibility.
|
|
125
134
|
|
|
126
135
|
Commit `.dev.vars.example` containing variable names and placeholders only:
|
|
127
136
|
|
|
128
137
|
```dotenv
|
|
129
|
-
|
|
138
|
+
PUBLIC_ORIGIN=http://localhost:8787
|
|
130
139
|
ADMIN_EMAIL=you@example.test
|
|
131
|
-
|
|
140
|
+
BETTER_AUTH_SECRET=replace-with-a-random-secret
|
|
132
141
|
```
|
|
133
142
|
|
|
134
143
|
Copy it to ignored `.dev.vars`; use deployment secrets for production. Configure
|
|
@@ -137,8 +146,7 @@ an app-owned [`EmailSender`](../guides/email.md#application-wiring) and call its
|
|
|
137
146
|
callbacks. Adapt the auth template's `MAIL.otp`/`MAIL.link` callbacks to this
|
|
138
147
|
sender; it is an object, not a `.dev.vars` value. The auth
|
|
139
148
|
module wires `ADMIN_EMAIL` to the bootstrap helper, which requires verified email.
|
|
140
|
-
`
|
|
141
|
-
explicitly when constructing auth and keep the trusted origin consistent.
|
|
149
|
+
Name the variables as your `auth.ts` reads them (the app shell uses these three) and keep the trusted origin consistent.
|
|
142
150
|
|
|
143
151
|
## Scheduling and Bun
|
|
144
152
|
|
|
@@ -150,22 +158,42 @@ invocation; see the [TTL contract](../guides/native-execution.md#ttl-maintenance
|
|
|
150
158
|
larger sweeps' cursors for a later invocation instead of scanning without a bound.
|
|
151
159
|
|
|
152
160
|
On Bun, reuse a process-owned `Database` or `SQL` pool and call the same generated
|
|
153
|
-
surfaces from `Bun.serve({fetch})`.
|
|
154
|
-
the
|
|
161
|
+
surfaces from `Bun.serve({fetch})`. Bun runs the server as TypeScript, so
|
|
162
|
+
`mantle bundle` builds only Admin for it: the `bun-*` target that
|
|
163
|
+
`generated/.mantle-inputs.json` records skips the Worker bundle, and Admin goes
|
|
164
|
+
to `dist/client/admin` (`--assets` as on Workers) when the server imports
|
|
165
|
+
`@aotter/mantle-admin/server`. Add it as the build script and run it before
|
|
166
|
+
starting the server:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{ "scripts": { "build": "mantle bundle src/server.ts dist" } }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The optional host reader is independent of the query target:
|
|
155
173
|
|
|
156
174
|
```ts
|
|
157
175
|
import { bunAdminAssets } from '@aotter/mantle/bun/assets';
|
|
158
|
-
const assets = bunAdminAssets('
|
|
176
|
+
const assets = bunAdminAssets('dist/client/admin');
|
|
159
177
|
const bindings = adminBindings({ handlers });
|
|
160
178
|
const serveAdmin = admin(bindings.metadata, bindings.views, bindings.operations, {
|
|
161
179
|
db: () => db, caller, handlers, routes: entries(bindings.schemas, bindings.adapter),
|
|
162
180
|
});
|
|
163
|
-
// Call assets(path) from your separate shell/static route.
|
|
181
|
+
// Call assets(path) from your separate shell/static route. adminShell(request, reader) wants
|
|
182
|
+
// paths without a leading slash and a Response (404 when missing), so adapt the reader:
|
|
183
|
+
const reader = async (path: string) => (await assets('/' + path)) ?? new Response(null, { status: 404 });
|
|
164
184
|
```
|
|
165
185
|
|
|
166
186
|
The reader returns a native `Response` or `null`, rejects traversal/control
|
|
167
187
|
characters and escaping symlinks; the app owns shell, CSP and cache headers.
|
|
168
|
-
Use ignored `.env` on Bun.
|
|
188
|
+
Use ignored `.env` on Bun.
|
|
189
|
+
|
|
190
|
+
Admin's build tools and UI packages are dependencies of `@aotter/mantle-admin`,
|
|
191
|
+
so a production install carries them (about 65 MB). For a container image, use a
|
|
192
|
+
multi-stage build: the build stage installs everything and runs `mantle bundle`
|
|
193
|
+
and `bun build src/server.ts --target bun --outdir dist/server`; the runtime stage
|
|
194
|
+
copies only `dist/` (plus any files the server reads at runtime) and starts
|
|
195
|
+
`bun dist/server/server.js`. Neither the Admin assets nor the bundled server read
|
|
196
|
+
`node_modules` once built. Run cron work with your native scheduler; neither
|
|
169
197
|
imports nor ordinary requests execute DDL, migrations or cron work. OAuth resource
|
|
170
198
|
rows are inserted on first OAuth use from runtime configuration, with no deploy
|
|
171
199
|
step; auth initialization and cookie/bearer/MCP/REST requests do no resource work.
|
|
@@ -190,13 +218,12 @@ untracked. Deployment uses committed generated source; it does not regenerate.
|
|
|
190
218
|
# CI, with the project's exact installed SDK and peer versions:
|
|
191
219
|
pnpm exec mantle generate cloudflare-d1 generated manifests/*.yaml --auth src/auth.ts --admin --check
|
|
192
220
|
pnpm exec tsc --noEmit
|
|
193
|
-
pnpm exec mantle bundle src/worker.ts dist
|
|
221
|
+
pnpm exec mantle bundle src/worker.ts dist # dist/worker, and dist/client/admin with --admin
|
|
194
222
|
```
|
|
195
223
|
|
|
196
224
|
`--check` regenerates in memory, lists missing/changed/obsolete SDK-owned files
|
|
197
225
|
and stale Wrangler build settings, exits nonzero, and writes nothing. Unmarked
|
|
198
226
|
application files are preserved. TOML generation prints manual edits, so an
|
|
199
|
-
exact Wrangler `--check` requires JSON/JSONC. Use all the same `--site`, `--auth
|
|
200
|
-
`--admin` and `--admin-extensions` options as normal generation. The check needs no database,
|
|
227
|
+
exact Wrangler `--check` requires JSON/JSONC. Use all the same `--site`, `--auth` and `--admin` options as normal generation. The check needs no database,
|
|
201
228
|
network access, credentials or deployment. Imported configuration modules must
|
|
202
229
|
remain pure: exporting `authOptions(env)` avoids constructing Better Auth at build time.
|
|
@@ -16,4 +16,4 @@ The compiler adds row policy to every Schema relation, including joins and neste
|
|
|
16
16
|
|
|
17
17
|
Field `scope` hides rows even from admin callers; use View/Procedure [`restrict`](../reference/view.md#restrict) to limit member access while Admin collection reads see all rows allowed by the Schema's scope.
|
|
18
18
|
|
|
19
|
-
The app owns custom identity and roles. Better Auth’s admin plugin `adminRoles` (default `["admin"]`) classifies comma-separated `user.role` values. `resolveCaller` carries this layer as `admin`; `isAdmin(caller)` and `hasAdminRole(caller, name)` consult it.
|
|
19
|
+
The app owns custom identity and roles. Better Auth’s admin plugin `adminRoles` (default `["admin"]`) classifies comma-separated `user.role` values. `resolveCaller` carries this layer as `admin`; `isAdmin(caller)` and `hasAdminRole(caller, name)` consult it. Callers come from `resolveCaller` over Better Auth; an existing identity provider connects through Better Auth's OAuth or SSO plugins, since no public API builds a Caller from another provider's session (`authenticatedAs` in `@aotter/mantle/testing` is for tests). Setting `admin: true` on a plain object grants no privilege. Member role names alone do not grant privilege. Optional [Better Auth](../cloudflare/authentication.md) helpers use fresh primary authority by default. Admin additionally requires an admin cookie session. UI visibility never replaces a server authorization check.
|
|
@@ -16,3 +16,22 @@ The app owns OAuth configuration, discovery and consent; follow [OAuth](../guide
|
|
|
16
16
|
[MCP Apps](../guides/mcp-apps.md) add static UI resources through the public contributor hook. Admin uses the same hook from its separate package. Credentials never go in App HTML. WebMCP catalogues are generated display/tool metadata for the mounted Admin product; server checks remain authoritative.
|
|
17
17
|
|
|
18
18
|
See [surface contracts](../reference/surface.md) for tool schemas, challenges and scalar conversion.
|
|
19
|
+
|
|
20
|
+
## When a host shows old tools
|
|
21
|
+
|
|
22
|
+
A fresh `tools/list` always returns the deployed tools, but many hosts keep their own copy and do not ask again after a deploy. Mantle sends no list-changed notifications: tools change only at deploy, and the hosts that keep copies do not act on them. Instead each generated tool's description ends with its own version, and the results of a call that reaches the tool carry the deployed version, so a model holding an older definition can see it, correct its call or ask the user to refresh. Failures also state the input schema the tool is listed with now, except authorization refusals. A call naming a tool the catalogue no longer has gets the protocol's unknown-tool error, whose message gives `serverInfo.version`: unless the application sets it, a hash of every tool the endpoint serves, before per-caller filtering. Refusals before a call reaches its tool, such as authentication challenges, carry neither.
|
|
23
|
+
|
|
24
|
+
In Admin, WebMCP results from the tool route carry the version, and refusals add it with the input schema, except authorization refusals; a tool removed since the page loaded is refused as not found, without a catalogue version. Admin's two navigation tools, `admin_get_context` and `admin_navigate`, carry no version: the page defines and runs them without a server call, so it cannot list one definition and run another, and a reload after a deploy replaces both. The exact shapes are in [surface contracts](../reference/surface.md#tool-versions).
|
|
25
|
+
|
|
26
|
+
How each client refreshes its copy (as of 2026-10):
|
|
27
|
+
|
|
28
|
+
| Client | Refresh |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| claude.ai connectors | A server-side copy keyed by URL, kept about an hour (users report up to 24 hours). Only adding the connector again under a new URL, such as `?v=2`, forces a refresh, and that needs authorization again. |
|
|
31
|
+
| Claude Code | `/mcp` reconnect, or a new session |
|
|
32
|
+
| ChatGPT developer mode | Refresh the connection, then start a new chat |
|
|
33
|
+
| Published ChatGPT apps | A daily scan, or Rescan, followed by review; keep tools compatible meanwhile |
|
|
34
|
+
| Cursor | Quit fully and reopen |
|
|
35
|
+
| VS Code | Run "MCP: Reset Cached Tools", or restart the server |
|
|
36
|
+
| Gemini CLI | `/mcp reload` |
|
|
37
|
+
| MCP Inspector | The refresh button |
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
Use the [Worker quickstart](../start/quickstart-worker.md) for a complete native manifest and entry, [typed queries](../guides/typed-queries.md) for embedding, and [service composition](../cloudflare/service-entry.md) for optional surfaces.
|
|
4
4
|
|
|
5
|
-
The runnable [reference service](https://github.com/aotter/mantle/
|
|
5
|
+
The runnable [reference service](https://github.com/aotter/mantle/tree/v0.2.0-alpha.8/docs/examples/reference-service) exercises the native package integration. See the [generated public website](../../examples/generated-web/README.md) for a native Worker example.
|
|
@@ -8,4 +8,110 @@ Admin Views provide custom read lists; their SELECT outputs, column lineage and
|
|
|
8
8
|
|
|
9
9
|
Generic publishing edits apply to drafts; publishing transitions retain their atomic guards and OCC. Read-only collections refuse generic edits while declared Procedures can still run. Ref operations receive the caller/transaction-bound Store.
|
|
10
10
|
|
|
11
|
-
Use [
|
|
11
|
+
Use [`defineAdmin` components](native-admin.md#customising-admin) for app-owned cells, inputs, record asides and actions; server work is a Procedure, a View or an application route. Keep exact conflict/uncertain-outcome handling in the shared UI controller.
|
|
12
|
+
|
|
13
|
+
## Theme
|
|
14
|
+
|
|
15
|
+
Admin is themed with CSS variables. Import your own stylesheet from `src/admin.tsx` (`import "./admin.css"`) and override the public tokens on `:root` for light and on `.dark` for dark:
|
|
16
|
+
|
|
17
|
+
```css
|
|
18
|
+
:root {
|
|
19
|
+
--primary: #0f766e;
|
|
20
|
+
--primary-foreground: #ffffff;
|
|
21
|
+
--radius: 0.5rem;
|
|
22
|
+
}
|
|
23
|
+
.dark {
|
|
24
|
+
--primary: #2dd4bf;
|
|
25
|
+
--primary-foreground: #042f2e;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Admin marks the active theme as both the `.dark` class and `data-theme="dark"` (or `"light"`) on the root. Dark tokens and Tailwind's `dark:` variant apply when either is present, so `[data-theme="dark"] {}` is equivalent to `.dark {}`. Your overrides apply in the browser Admin and, because the entry imports your CSS, in the [MCP App](mcp-apps.md#theme) too, where the host's neutral colours are laid over them.
|
|
30
|
+
|
|
31
|
+
| Token | Meaning |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `--background` | Page background |
|
|
34
|
+
| `--foreground` | Default text |
|
|
35
|
+
| `--card` | Card and panel surface |
|
|
36
|
+
| `--card-foreground` | Text on a card |
|
|
37
|
+
| `--popover` | Menu, popover and dialog surface |
|
|
38
|
+
| `--popover-foreground` | Text on a popover |
|
|
39
|
+
| `--primary` | Brand color: primary buttons, active states |
|
|
40
|
+
| `--primary-foreground` | Text on `--primary` |
|
|
41
|
+
| `--secondary` | Secondary button surface |
|
|
42
|
+
| `--secondary-foreground` | Text on `--secondary` |
|
|
43
|
+
| `--muted` | Subtle surface: table stripes, disabled areas |
|
|
44
|
+
| `--muted-foreground` | Secondary text |
|
|
45
|
+
| `--accent` | Hover and selected surface |
|
|
46
|
+
| `--accent-foreground` | Text on `--accent` |
|
|
47
|
+
| `--destructive` | Destructive actions and errors |
|
|
48
|
+
| `--border` | Borders and dividers |
|
|
49
|
+
| `--input` | Form control borders |
|
|
50
|
+
| `--ring` | Focus ring |
|
|
51
|
+
| `--radius` | Base corner radius; the other radii derive from it |
|
|
52
|
+
| `--success` | Success status color |
|
|
53
|
+
| `--warning` | Warning status color |
|
|
54
|
+
| `--info` | Informational status color |
|
|
55
|
+
| `--chart-1` | Chart series 1 |
|
|
56
|
+
| `--chart-2` | Chart series 2 |
|
|
57
|
+
| `--chart-3` | Chart series 3 |
|
|
58
|
+
| `--chart-4` | Chart series 4 |
|
|
59
|
+
| `--chart-5` | Chart series 5 |
|
|
60
|
+
| `--sidebar` | Sidebar surface |
|
|
61
|
+
| `--sidebar-foreground` | Sidebar text |
|
|
62
|
+
| `--sidebar-primary` | Sidebar active item |
|
|
63
|
+
| `--sidebar-primary-foreground` | Text on `--sidebar-primary` |
|
|
64
|
+
| `--sidebar-accent` | Sidebar hover surface |
|
|
65
|
+
| `--sidebar-accent-foreground` | Text on `--sidebar-accent` |
|
|
66
|
+
| `--sidebar-border` | Sidebar borders |
|
|
67
|
+
| `--sidebar-ring` | Sidebar focus ring |
|
|
68
|
+
| `--app-base` | Solid color behind the page |
|
|
69
|
+
| `--app-background` | Page backdrop (any CSS background, such as a gradient) |
|
|
70
|
+
|
|
71
|
+
Every other variable, including `--mantle-blue-*` and `--chrome-glass`, is internal and may change without notice.
|
|
72
|
+
|
|
73
|
+
## Eject a component
|
|
74
|
+
|
|
75
|
+
`@aotter/mantle-admin` ships a [shadcn registry](https://ui.shadcn.com/docs/registry) of starting-point components for the `defineAdmin` slots. Ejecting copies one into your source: you own that file and edit it freely, while the hooks it calls (`@aotter/mantle-admin/hooks`) and the slot prop types (`@aotter/mantle-admin/app`) keep upgrading with the package. The registry is inside the installed package, so the copy matches the version you run and no registry host is involved (the shadcn CLI itself downloads from npm and needs network). Install from the package, in the app's root:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx shadcn@4 add ./node_modules/@aotter/mantle-admin/dist/registry/admin-cell.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The file lands in `src/admin/<item>.tsx` (the item's `target` ignores aliases). The CLI needs a `tsconfig.json` and a `components.json` in the app's root; this minimal one works, because no alias or stylesheet is used:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
86
|
+
"style": "new-york",
|
|
87
|
+
"rsc": false,
|
|
88
|
+
"tsx": true,
|
|
89
|
+
"tailwind": { "config": "", "css": "", "baseColor": "neutral", "cssVariables": true },
|
|
90
|
+
"aliases": { "components": "@/components", "utils": "@/lib/utils" }
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The CLI removes a comment above a file's first import, so each item keeps its header below the imports. Import it in `src/admin.tsx` and name the slot, replacing the sample collection, field, View and operation with your own:
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { AdminCell } from "./admin/admin-cell";
|
|
98
|
+
|
|
99
|
+
export default defineAdmin(admin, { components: { "cell:requests.status": AdminCell } });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| Item | Slots | Surface |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| `admin-cell` | `cell:view:<view>.<column>` | both (also reusable as a browser collection cell) |
|
|
105
|
+
| `admin-input` | `input:<schema>.<field>` | browser |
|
|
106
|
+
| `admin-record-actions` | `record.actions:<schema>` (runs an operation with `useOperation`) | browser |
|
|
107
|
+
| `admin-record-aside` | `record.aside:<schema>` (lists a View's rows with `useView`) | browser |
|
|
108
|
+
| `admin-sidebar` | `Sidebar` (`renderDefault` with a footer link) | browser |
|
|
109
|
+
| `admin-home` | `Home` (`renderDefault` under a section that reads a View) | browser |
|
|
110
|
+
|
|
111
|
+
Each file starts with a comment naming its surface and rules, and imports only `react`, `@aotter/mantle-admin/app` and `@aotter/mantle-admin/hooks`. A `both` component also renders in the Admin MCP App, inside a host's sandboxed iframe, so keep it to these rules:
|
|
112
|
+
|
|
113
|
+
- Read data only through `useView` and `useOperation`; `useEntries`, `useEntry`, `useCollectionTotal`, `useMe` and `useBadges` read `unavailable` there. Declare what a card needs as an admin View.
|
|
114
|
+
- Link only with `<AdminLink>` or `useOpenLink()`; never `window.open` or `target="_blank"`.
|
|
115
|
+
- No `fetch`, no `document.cookie` or `localStorage` authentication, no remote origin that the host's CSP does not allow, and no assumed width.
|
|
116
|
+
- Where `useOperation` returns no `run` (a host without server tools), show the control disabled with a note rather than throwing or vanishing.
|
|
117
|
+
- Branch on `useAdminSurface()` (or the `surface` prop) if you must, and render something sensible in both branches.
|
|
@@ -12,24 +12,42 @@ carries.
|
|
|
12
12
|
|
|
13
13
|
The Mantle plugin carries one OSS authoring skill, `mantle`, and local
|
|
14
14
|
acceptance references. It does not configure a hosted-service connection.
|
|
15
|
+
Install from the Git tag matching the exact SDK version you selected (currently
|
|
16
|
+
`v0.2.0-alpha.8`). Alpha releases integrate on `develop`; an unqualified
|
|
17
|
+
`aotter/mantle` source follows `main` and can install older, incompatible
|
|
18
|
+
instructions. A skill install does not upgrade the application's SDK.
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
# Claude Code: two separate prompts
|
|
18
|
-
/plugin marketplace add aotter/mantle
|
|
19
|
-
/plugin install mantle@mantle
|
|
20
|
+
For Codex, pin the marketplace checkout:
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
codex plugin marketplace add aotter/mantle
|
|
22
|
+
```sh
|
|
23
|
+
codex plugin marketplace add aotter/mantle --ref v0.2.0-alpha.8
|
|
23
24
|
codex plugin add mantle@mantle
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
For Claude Code, first clone the release into a directory you will keep:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
git clone --depth 1 --branch v0.2.0-alpha.8 https://github.com/aotter/mantle.git mantle-plugin
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then, in Claude Code, use that checkout's absolute path:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
/plugin marketplace add /absolute/path/to/mantle-plugin
|
|
37
|
+
/plugin install mantle@mantle
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Cursor and GitHub Copilot use their plugin manifests from the same tagged
|
|
41
|
+
checkout. Without a plugin host, install only the consumer skill by name:
|
|
28
42
|
|
|
29
43
|
```sh
|
|
30
|
-
npx skills add aotter/mantle
|
|
44
|
+
npx skills add https://github.com/aotter/mantle/tree/v0.2.0-alpha.8 --skill mantle
|
|
31
45
|
```
|
|
32
46
|
|
|
47
|
+
When upgrading the SDK, update the marketplace ref or local tagged checkout and
|
|
48
|
+
refresh/reinstall the plugin using the host's native workflow. Verify the loaded
|
|
49
|
+
manifest version and skill source. Do not reset a checkout containing local edits.
|
|
50
|
+
|
|
33
51
|
Read the path the installer prints (for project-local Codex,
|
|
34
52
|
`.agents/skills/mantle/SKILL.md`). The skill installs no SDK, creates no
|
|
35
53
|
project and copies no handbook.
|