@buildbase/sdk 0.0.51 → 0.0.53
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/AGENTS.md +18 -8
- package/CHANGELOG.md +56 -0
- package/README.md +247 -32
- package/dist/css/styles.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +865 -53
- package/dist/index.mjs +1 -1
- package/dist/mcp/index.cjs +1 -0
- package/dist/mcp/index.d.ts +1402 -0
- package/dist/mcp/index.mjs +1 -0
- package/dist/react/chunks/{SettingsDialog-uVKFxlal.mjs → SettingsDialog-BxDYsie6.mjs} +1 -1
- package/dist/react/chunks/{SettingsDialog-DitAiAqr.cjs → SettingsDialog-Dy8W3NA6.cjs} +1 -1
- package/dist/react/chunks/{SubscriptionDialog-P0GlXEyn.mjs → SubscriptionDialog-6c5dHoQd.mjs} +1 -1
- package/dist/react/chunks/{SubscriptionDialog-Cg-qYVKr.cjs → SubscriptionDialog-CpVnnXJg.cjs} +1 -1
- package/dist/react/chunks/ar-1Z1MOGAP.mjs +2 -0
- package/dist/react/chunks/ar-VGzBv3AZ.cjs +2 -0
- package/dist/react/chunks/de-BjdDg60Y.cjs +2 -0
- package/dist/react/chunks/de-CXUItplO.mjs +2 -0
- package/dist/react/chunks/es-BBPsSZUp.cjs +2 -0
- package/dist/react/chunks/es-BTbzVutz.mjs +2 -0
- package/dist/react/chunks/fr-Cax48X_S.cjs +2 -0
- package/dist/react/chunks/fr-DPMeOQu3.mjs +2 -0
- package/dist/react/chunks/hi-BJqvpG6d.cjs +2 -0
- package/dist/react/chunks/hi-JfJx-LV7.mjs +2 -0
- package/dist/react/chunks/ja-CDGtLzTk.mjs +2 -0
- package/dist/react/chunks/ja-DvVyj5xX.cjs +2 -0
- package/dist/react/chunks/react-4SlWxHdh.cjs +2 -0
- package/dist/react/chunks/react-OL1uSkpr.mjs +2 -0
- package/dist/react/chunks/zh-BAcqxkxk.cjs +2 -0
- package/dist/react/chunks/zh-BNZ2Ooe8.mjs +2 -0
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +39 -32
- package/dist/react/index.mjs +1 -1
- package/package.json +17 -4
- package/dist/react/chunks/ar-SrPOJXdL.mjs +0 -2
- package/dist/react/chunks/ar-a3aRbPCs.cjs +0 -2
- package/dist/react/chunks/de-DE3_z7r0.cjs +0 -2
- package/dist/react/chunks/de-xnPl61SS.mjs +0 -2
- package/dist/react/chunks/es-C_xcyGtp.cjs +0 -2
- package/dist/react/chunks/es-CyuBtosP.mjs +0 -2
- package/dist/react/chunks/fr-BMu7Bk4k.cjs +0 -2
- package/dist/react/chunks/fr-CxuDLZ27.mjs +0 -2
- package/dist/react/chunks/hi-CQ215yc9.cjs +0 -2
- package/dist/react/chunks/hi-CpuvyorS.mjs +0 -2
- package/dist/react/chunks/ja-DIJ_xCIi.mjs +0 -2
- package/dist/react/chunks/ja-DK6Buuws.cjs +0 -2
- package/dist/react/chunks/react-C2ikFWBG.cjs +0 -2
- package/dist/react/chunks/react-CCukNQ4q.mjs +0 -2
- package/dist/react/chunks/zh-CYQwZiBL.mjs +0 -2
- package/dist/react/chunks/zh-Dt-apS2s.cjs +0 -2
package/AGENTS.md
CHANGED
|
@@ -6,12 +6,13 @@ BuildBase is a multi-tenant SaaS platform SDK: auth, workspaces, subscriptions/b
|
|
|
6
6
|
|
|
7
7
|
## The one rule: two entry points, pick by runtime
|
|
8
8
|
|
|
9
|
-
| Import | Runtime | Contains
|
|
10
|
-
| ---------------------- | ---------------- |
|
|
11
|
-
| `@buildbase/sdk` | **Server** / any | Zero React. `BuildBase()` factory, API classes, webhooks, agent tooling, pure utils, enums, types.
|
|
12
|
-
| `@buildbase/sdk/react` | **Client** | Everything in core **re-exported** + React providers, hooks, gate components.
|
|
13
|
-
| `@buildbase/sdk/data` | any | Static datasets: `countries`, `currencies`, `languages`, `timezones`, `betaFormSchema`.
|
|
14
|
-
| `@buildbase/sdk/
|
|
9
|
+
| Import | Runtime | Contains |
|
|
10
|
+
| ---------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
11
|
+
| `@buildbase/sdk` | **Server** / any | Zero React. `BuildBase()` factory, API classes, webhooks, agent tooling, pure utils, enums, types. |
|
|
12
|
+
| `@buildbase/sdk/react` | **Client** | Everything in core **re-exported** + React providers, hooks, gate components. |
|
|
13
|
+
| `@buildbase/sdk/data` | any | Static datasets: `countries`, `currencies`, `languages`, `timezones`, `betaFormSchema`. |
|
|
14
|
+
| `@buildbase/sdk/mcp` | **Server** / any | MCP server toolkit: `createMcpHandler`, `defineMcpTool`, built-in BuildBase tools. Uses zod at runtime (why it's not in core). |
|
|
15
|
+
| `@buildbase/sdk/css` | build | `import '@buildbase/sdk/css'` — required once for component styling. |
|
|
15
16
|
|
|
16
17
|
- **In a React component / client hook → import from `@buildbase/sdk/react`.** It contains the whole core surface too, so you rarely need both.
|
|
17
18
|
- **In an API route, server action, webhook, cron, or Node/edge → import from `@buildbase/sdk`.** Never import `/react` on the server.
|
|
@@ -71,8 +72,8 @@ Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err,
|
|
|
71
72
|
- **Factory:** `BuildBase` (also default export) → `{ auth, workspace, subscription, usage, credits, plans, invoices, users, features, settings, notification, permissions, withSession, client }`.
|
|
72
73
|
- **API classes:** `BaseApi` (extend for custom APIs), `AuthApi`, `UserApi`, `WorkspaceApi`, `SettingsApi`, `PushApi`.
|
|
73
74
|
- **Webhook verification (runtime-agnostic — Node, edge, Deno, Bun, browsers):** `verifyWebhookSignature`, `parseWebhookEvent`. Verify against the **raw** request body (`await req.text()`), not the parsed JSON.
|
|
74
|
-
- **Agent readiness (framework-agnostic, returns plain `DiscoveryDocument`):** `
|
|
75
|
-
- **OAuth2 app-bridge:** `handleAppTokenRequest`, `handleAppRevokeRequest` (one-call handlers), `bearerChallenge` (RFC 9728/6750 401), `verifyAppTokenRequest`, `verifyAppRevokeRequest`, `verifyClientJwt`, `extractBearerToken`, `appTokenSuccess`, `appTokenFailure`, `AppBridgeError`. Types: `AppTokenRequestClaims`, `AppRevokeRequestClaims`, `AppTokenResult`, `AppTokenResponseBody`, `HandlerResult`.
|
|
75
|
+
- **Agent readiness (framework-agnostic, returns plain `DiscoveryDocument`):** **App owns discovery content, platform owns auth.** All content — `llms.txt`, `robots.txt`, `sitemap`, `protectedResources` (RFC 9728), API catalog, skills — is defined locally in `AgentReadyConfig` and served with no platform round-trip. The platform bundle (`fetchAgentReadiness`, fail-soft/cached) supplies ONLY `{ enabled, authorizationServer }` (the OAuth AS pointer). Functions: `resolveAgentPath` (dispatch ALL agent paths — root docs + `.well-known/*`; prefer for new wiring), `resolveWellKnown` (`.well-known/*` only), `clearAgentReadinessCache`, `buildAgentCard`, `buildProtectedResourceMetadata(path, config)` (local), `buildAgentSkillsIndex`, `buildSkillMd`, `buildSecurityTxt`, `buildLlmsTxt(config)` (from `config.llmsTxt`), `buildRobotsTxt`, `buildSitemap`, `buildApiCatalog`, `buildMcpServerCard`, `buildAuthMd`, `buildDiscoveryLinkHeader`, `wantsMarkdown`/`negotiateMarkdown`, `sha256Digest`, `AI_BOT_USER_AGENTS`. Types: `AgentReadyConfig`, `AgentReadinessBundle`, `AgentSkill`, `ApiCatalogApi`, `McpServerCard`, `RobotsConfig`, `RobotsPolicy`, `ContentSignals`, `SitemapUrl`, `DiscoveryDocument`.
|
|
76
|
+
- **OAuth2 app-bridge:** `handleAppTokenRequest`, `handleAppRevokeRequest` (one-call handlers), `bearerChallenge` (RFC 9728/6750 401; header values are quoted-string-escaped + CR/LF-stripped), `verifyAppTokenRequest`, `verifyAppRevokeRequest`, `verifyClientJwt`, `signClientJwt` (HS256 mint — symmetric counterpart of verify), `extractBearerToken`, `appTokenSuccess`, `appTokenFailure`, `AppBridgeError`. Types: `AppTokenRequestClaims`, `AppRevokeRequestClaims`, `AppTokenResult`, `AppTokenResponseBody`, `HandlerResult`, `VerifyClientJwtOptions`. `verifyClientJwt(token, secret, opts?)` **requires a numeric `exp` by default** (`requireExp: false` to allow non-expiring tokens); optional `issuer`/`audience` pinning; 3rd arg still accepts a bare `clockToleranceSec` number for back-compat.
|
|
76
77
|
- **Currency & pricing utils (pure):** `formatCents`, `formatOverageRate`, `formatOverageRateWithLabel`, `formatQuotaIncludedOverage`, `formatQuotaWithPrice`, `getCurrencySymbol`, `getCurrencyFlag`, `getQuotaUnitLabelFromName`, `getQuotaDisplayValue`, `CURRENCY_DISPLAY`, `CURRENCY_FLAG`, `PLAN_CURRENCY_CODES`, `PLAN_CURRENCY_OPTIONS`.
|
|
77
78
|
- **Pricing variants (multi-currency):** `getPricingVariant`, `getBasePriceCents`, `getStripePriceIdForInterval`, `getQuotaOverageCents`, `getQuotaDisplayWithVariant`, `getAvailableCurrenciesFromPlans`, `getDisplayCurrency`, `getBillingIntervalAndCurrencyFromPriceId`.
|
|
78
79
|
- **Seat pricing:** `calculateBillableSeats`, `calculateSeatOverageCents`, `calculateTotalSubscriptionCents`, `getPerSeatPriceCents`, `getSeatPricing`.
|
|
@@ -106,6 +107,15 @@ Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err,
|
|
|
106
107
|
|
|
107
108
|
`countries`, `currencies`, `languages`, `timezones`, `betaFormSchema` (Zod), type `BetaFormValues`.
|
|
108
109
|
|
|
110
|
+
### `@buildbase/sdk/mcp` — MCP server (server-only, zero React)
|
|
111
|
+
|
|
112
|
+
Expose a live MCP server (stateless Streamable HTTP; Node 18+/edge) with built-in BuildBase tools + custom zod-schema tools.
|
|
113
|
+
|
|
114
|
+
- **Handler:** `createMcpHandler({ buildbase?, auth, tools?, builtinTools?, context?, serverInfo?, instructions?, allowedOrigins?, maxRequestBytes?, rateLimit?, formatToolError?, onError? })` → `{ handle(McpHttpRequest), fetch(Request), listTools(), serverCard({ endpoint }) }`. `fetch` is the Web adapter (Next.js route: `export const POST = (req) => mcp.fetch(req)`); `handle` is the pure form (Express). `auth.verify(token) → McpAuthInfo { sessionId, userId?, workspaceId?, scopes? }` maps the app's Bearer token (any format) to the BuildBase session tools run under; `auth.resourceMetadataUrl` makes 401s carry the RFC 9728 challenge. `auth: false` = dev only. **Hardening:** `maxRequestBytes` (default 1 MiB → 413), `rateLimit(auth, req)` gate (after auth; `false`/`{ok:false,retryAfter}` → 429, fails closed), `allowedOrigins` (non-matching browser Origin → 403; also drives CORS — `OPTIONS` preflight → 204 + `Access-Control-*`, matching Origin echoed on responses), `formatToolError(err, {tool})` (redact client-facing tool-error text; full error still hits `onError`). **No lock-in:** `buildbase` is optional (standalone server, only your tools); `context(auth, req)` injects your own per-request context as `ctx.custom`; custom tools override same-named built-ins; raw JSON Schema tools skip zod.
|
|
115
|
+
- **Tools:** `defineMcpTool` (typed custom tools), `builtinMcpTools`, `selectBuiltinTools`. Built-ins cover the **full BuildBase surface** (42 tools: workspaces/users/subscription/plans/invoices/usage/credits/features/permissions/settings — reads + writes + destructive). **Default `builtinTools: 'readonly'`** (least privilege) — reads only until you opt into `'all'` (or `{ include }`); narrow further with `false` | `{ exclude }`. Every tool runs under the user's session → platform enforces the user's real permissions (agent can't exceed the user). Built-ins carry **no** scope requirement (agent = authenticated app user; read/write boundary is `builtinTools`, not agent scopes). Per-scope gating is opt-in on custom tools via `requiredScopes` + the `scopes` returned from `auth.verify` (your scope names); a token with no scopes sees only tools that require none.
|
|
116
|
+
- **Auth helpers re-exported:** `signClientJwt`, `verifyClientJwt`, `extractBearerToken`, `bearerChallenge`, `AppBridgeError`.
|
|
117
|
+
- Types: `CreateMcpHandlerConfig`, `McpHandler`, `McpToolDefinition`, `McpToolContext`, `McpAuthInfo`, `McpHttpRequest`, `McpHttpResponse`, `McpToolAnnotations`, `McpBuildBaseClient`, `BuiltinMcpToolName`.
|
|
118
|
+
|
|
109
119
|
## Server actions cheat-sheet
|
|
110
120
|
|
|
111
121
|
`workspace`: `list get create update delete` · `users`: `list invite remove updateRole getProfile updateProfile` · `subscription`: `get checkout update cancel resume getBillingPortalUrl` · `plans`: `getGroup getVersions getPublic getVersion` · `invoices`: `list get` · `usage`: `record recordBatch getQuota getAll getLogs` · `credits`: `getBalance consume purchase getPackages getTransactions getExpiring getBuckets getPublicPackages` · `settings`: `get` · `features`: `list update` · `notification`: `send(workspaceId, event, userId?, data?)` · `permissions`: `check(workspaceId, userId, permission) resolve(workspaceId, userId)`.
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,62 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.0.53] - 2026-07-09
|
|
9
|
+
|
|
10
|
+
Production-readiness pass — MCP + agent-bridge hardening, permission-resolution correctness, auth-session teardown, and settings/a11y/i18n fixes. **Behavior changes** are marked ⚠️ (`builtinTools` default; permission-override semantics); the rest are additive or internal.
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
|
|
14
|
+
- **`builtinTools` now defaults to `'readonly'`, not `'all'`.** ⚠️ **Behavior change.** A `createMcpHandler({ buildbase })` with no explicit `builtinTools` previously exposed the entire surface — including `delete_workspace`, `cancel_subscription`, `purchase_credits`, `consume_credits` — to any token `auth.verify` accepted. It now exposes reads only (least privilege); opt into writes with `builtinTools: 'all'` or an explicit `{ include }` list. If you relied on the old default, add `builtinTools: 'all'`.
|
|
15
|
+
- **Scope gating no longer bypassed by scopeless tokens.** `visibleTools` treated `scopes: undefined` as "grant everything" — a JWT minted without a `scope` claim received every tool, including custom tools declaring `requiredScopes`. A token now sees a scoped tool only when it carries all of that tool's scopes; no scopes → only tools that require none. `scopes: []` and `scopes: undefined` behave identically.
|
|
16
|
+
- **Built-in `update_workspace` / `update_user_profile` no longer accept arbitrary fields.** Both took `data: z.record(z.string(), z.unknown())` forwarded to the backend `as any` (mass-assignment). They now expose an explicit, `.strict()` allowlist — `update_workspace`: `name`, `image`; `update_user_profile`: `name`, `image`, `country`, `timezone`, `language`, `currency` (role/email/attributes are not self-updatable via the agent tool).
|
|
17
|
+
- **`verifyClientJwt` requires a numeric `exp` by default.** A JWT with no expiry was accepted and never expired. New `VerifyClientJwtOptions` (passable as the 3rd arg, which still also accepts a bare `clockToleranceSec` number): `requireExp` (default `true`), plus optional `issuer` / `audience` pinning (RFC 7519).
|
|
18
|
+
- **`bearerChallenge` escapes `WWW-Authenticate` values.** `resource_metadata` / `error` / `error_description` are quoted-string-escaped and CR/LF-stripped, closing a header-injection vector when `errorDescription` is fed dynamic text.
|
|
19
|
+
- **`buildRobotsTxt` / `buildSitemap` sanitize interpolated config.** robots.txt directive values strip CR/LF (can't inject extra directives); sitemap `<changefreq>` is XML-escaped and `<priority>` is clamped to 0.0–1.0.
|
|
20
|
+
- **Permission resolution: explicit `[]` now revokes, and unknown roles are denied.** ⚠️ **Behavior change.** `resolvePermissions` used a non-emptiness check, so an explicit empty override (`workspace.permissions[role] = []`, a deliberate revoke) silently fell through to org/SDK defaults and granted permissions anyway. It now uses presence: an explicit entry — including `[]` — fully defines that role's permissions at that tier. And an **unknown role is denied by default** instead of silently inheriting `member` permissions; only an explicitly-configured, _known_ `settings.workspace.defaultRole` is honored as a fallback. Owners are unaffected (still all permissions).
|
|
21
|
+
- **A dead session (401) is now cleared.** `useWorkspaceApiWithOs`'s `onUnauthorized` only called `onSessionExpired`; it now also runs `removeSession()` + dispatches `authActions.removeSession()`, so gated UI can't keep rendering "authenticated" behind an invalid token.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **MCP CORS / preflight.** With `allowedOrigins` set, `OPTIONS` returns 204 with `Access-Control-*` headers and successful responses echo `Access-Control-Allow-Origin` for a matching Origin — browser-based MCP clients now work. No `allowedOrigins` → no CORS headers (server-to-server unaffected).
|
|
26
|
+
- **`createMcpHandler({ formatToolError })`.** Map a thrown tool error to the message returned to the agent — redact internal details in production while the full error still reaches `onError`.
|
|
27
|
+
- **Public model types exported** from `@buildbase/sdk` and `/react`: `IUser`, `IWorkspace`, `ISettings`, `TranslationKey` (previously appeared in exported signatures but weren't name-exported).
|
|
28
|
+
- **`createMcpHandler` DoS controls.** `maxRequestBytes` (default 1 MiB) rejects oversized bodies with 413 before parsing — and before the stream is read on the `fetch` adapter (via `Content-Length`); set `0` to disable. `rateLimit(auth, req)` gate runs after auth and before dispatch, returning `false` or `{ ok: false, retryAfter }` for a 429 (the server holds no counters — back it with your own store; a throwing limiter fails closed).
|
|
29
|
+
- **OIDC discovery alias.** `resolveWellKnown`/`resolveAgentPath` now also serve the authorization-server metadata at `/.well-known/openid-configuration` (same document as `/.well-known/oauth-authorization-server`), and the Agent Card advertises `openid_configuration` — so agents that only probe the OIDC path no longer get a 404.
|
|
30
|
+
- **`AgentReadyConfig.fetchTimeoutMs`** (default 5000) — platform fetches (`fetchAgentReadiness`, auth-server metadata) now time out via `AbortController` and fail soft, so a hung BuildBase server can't hang discovery routes forever.
|
|
31
|
+
- **`onError` covers `tools/list` schema conversion.** A tool whose zod schema can't convert to JSON Schema no longer throws an unhandled 500 out of `handle`; it's reported via `onError` and served a permissive object schema.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **`useConnectedAgents().refresh()` guards against stale writes.** The list fetch now passes an `AbortSignal`, ignores superseded/aborted responses, and aborts on unmount — a rapid workspace/server switch or unmount can no longer clobber state with an older response.
|
|
36
|
+
- **Accessibility.** The personal-mode settings trigger was a bare `div onClick` (no keyboard/AT access) — now `role="button"` + `tabIndex` + Enter/Space handling + `aria-label`. The subscription tabs had `role="tab"`/`tablist` without the rest of the pattern — now complete: `id`/`aria-controls`/roving `tabIndex`/arrow-key navigation, with real `role="tabpanel"` panels.
|
|
37
|
+
- **Hardcoded strings moved into i18n** (all 8 locales): subscription tabs `aria-label`, "(ends {date})", "Version {version}", the invite-email placeholder, and the logo / workspace-preview `alt` text.
|
|
38
|
+
- **Palette token.** `focus:ring-red-600` → `focus:ring-destructive`; the `lint:tokens` guard was widened to also catch `ring`/`outline`/`from`/`via`/`to`/`fill`/`stroke`/`shadow` palette classes.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **`lucide-react` bound to `>=0.544.0 <1`** — 0.x releases rename/remove icons; the previous open range let consumers pull a breaking minor.
|
|
43
|
+
|
|
44
|
+
## [0.0.52] - 2026-07-08
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- **New entry point `@buildbase/sdk/mcp` — a live MCP server for your app.** `createMcpHandler({ buildbase, auth, tools })` returns a stateless Streamable-HTTP handler (`fetch(Request)` for Next.js/edge, pure `handle()` for Express) exposing the **full BuildBase surface** as MCP tools — 42 built-ins spanning workspaces, users, subscription, plans, invoices, usage, credits, feature flags, permissions, and settings (reads + writes + destructive ops). **Default `builtinTools: 'all'`** — the SDK restricts nothing; the consumer narrows via `'readonly'` | `false` | `{ include }` | `{ exclude }`. Every tool runs under the user's BuildBase session, so the platform enforces the user's real permissions (an agent can never exceed the user). Custom tools are defined with `defineMcpTool` + zod schemas (converted to JSON Schema with zod 4's native `z.toJSONSchema`). Bearer auth plugs into the OAuth2 app-bridge: `auth.verify` maps the app-minted token to the BuildBase session tools run under, and unauthenticated requests get an RFC 9728 `WWW-Authenticate` challenge. Built-in tools carry no BuildBase-specific scope requirement (the agent acts as an authenticated app user; the read/write boundary is `builtinTools`); optional per-scope gating (`requiredScopes` vs the token's `scopes`) is available for custom tools using your own scope names. Origin allowlisting, per-request `withSession`. Hand-rolled protocol subset (initialize / ping / tools/list / tools/call, protocol versions 2024-11-05 → 2025-06-18) — no `@modelcontextprotocol/sdk` dependency (zod-3-based, Node-http-bound). Separate entry so the core stays zod-free at runtime.
|
|
49
|
+
- **`resolveAgentPath(path, config)`** — superset of `resolveWellKnown` that also serves the root-level documents: `/robots.txt`, `/sitemap.xml`, `/llms.txt`, `/auth.md`, `/security.txt` (RFC 9116 legacy alias). One catch-all now covers the entire agent-readiness surface.
|
|
50
|
+
- **`buildRobotsTxt(config)`** — robots.txt with per-AI-bot policy groups (new `AI_BOT_USER_AGENTS` list: GPTBot, ClaudeBot, PerplexityBot, Bytespider, …; `aiBots: 'allow' | 'deny' | RobotsPolicy[]`), [Content Signals](https://contentsignals.org) directives (`contentSignals: { search, aiInput, aiTrain }`), and `Sitemap:` references.
|
|
51
|
+
- **`buildSitemap(config)`** — minimal sitemap.xml from `config.sitemap.urls` for API-first apps without a content pipeline (null when unconfigured, so next-sitemap users keep their files).
|
|
52
|
+
- **`wantsMarkdown(acceptHeader)` / `negotiateMarkdown(...)`** — q-value-aware markdown content negotiation; negotiated documents carry `vary: 'Accept'` (new optional field on `DiscoveryDocument`).
|
|
53
|
+
- **`buildDiscoveryLinkHeader(config)`** — `Link` response-header value advertising llms.txt, the Agent Card, sitemap, API catalog, and MCP server card. Sync/pure — edge-middleware-safe.
|
|
54
|
+
- **`signClientJwt(payload, secret, { expiresInSec? })`** — HS256 mint, the symmetric counterpart of `verifyClientJwt`, for `applicationTokenUrl` handlers that mint the app's agent tokens (pure JS, edge-safe).
|
|
55
|
+
- **`config.llmsTxt`** — local llms.txt override that wins over the platform-bundle content (mirrors the existing `authMd` convention).
|
|
56
|
+
- **MCP without lock-in:** `buildbase` is optional on `createMcpHandler` — omit it for a standalone MCP server exposing only your own tools (built-ins default off; `ctx.bb` throws a clear error if touched). New `context(auth, req)` factory injects your own per-request context (`ctx.custom`) into tool executions. Custom tools now override same-named built-ins instead of throwing.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- **Clean split: the app owns discovery content, the platform owns auth.** `protectedResources` (RFC 9728) and `llms.txt` are now defined locally in `AgentReadyConfig` and served with no platform round-trip — `buildProtectedResourceMetadata(path, config)` builds the doc from `config.protectedResources` (deriving `authorization_servers` from `serverUrl`/`orgId`), and `buildLlmsTxt(config)` serves `config.llmsTxt`. Both lost their `bundle` parameter. `AgentReadinessBundle` is slimmed to `{ enabled, authorizationServer }` — the only thing the platform supplies. New config field: `AgentReadyConfig.protectedResources`.
|
|
61
|
+
- **`/.well-known/oauth-authorization-server` now serves the full RFC 8414 metadata** (cached, fail-soft proxy of the platform's canonical document) instead of a nonstandard pointer object; falls back to the pointer when the platform is unreachable. `clearAgentReadinessCache()` clears this cache too.
|
|
62
|
+
- **`AppTokenRequestClaims.sessionId`** — the platform now mints a per-user BuildBase session on every OAuth2 grant (including refresh) and sends it in the app-bridge mint claims. Embed it encrypted in the token you mint (never plaintext, never stored) so your MCP/agent endpoints can call BuildBase as the consenting user.
|
|
63
|
+
|
|
8
64
|
## [0.0.51] - 2026-07-08
|
|
9
65
|
|
|
10
66
|
### Fixed
|
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@ Also works server-side (Next.js API routes, Express, Hono) — see [Server-Side
|
|
|
39
39
|
- [Server-Side Usage](#server-side-usage)
|
|
40
40
|
- [Webhook Verification](#webhook-verification)
|
|
41
41
|
- [Agent Readiness (Discovery)](#agent-readiness-discovery)
|
|
42
|
+
- [MCP Server](#mcp-server-buildbasesdkmcp)
|
|
42
43
|
- [OAuth2 App Bridge](#oauth2-app-bridge)
|
|
43
44
|
|
|
44
45
|
## 🚀 Features
|
|
@@ -2102,12 +2103,13 @@ All SDK API clients extend a shared base class and are exported from the package
|
|
|
2102
2103
|
|
|
2103
2104
|
Server-only toolkits (framework-agnostic, zero React) are exported from `@buildbase/sdk`:
|
|
2104
2105
|
|
|
2105
|
-
| Export group | Purpose
|
|
2106
|
-
| --------------------- |
|
|
2107
|
-
| `BuildBase()` factory | Session-scoped server actions — see [Server-Side Usage](#server-side-usage)
|
|
2108
|
-
| Webhook verification | `verifyWebhookSignature`, `parseWebhookEvent` — see [Webhook Verification](#webhook-verification)
|
|
2109
|
-
| Agent readiness | `
|
|
2110
|
-
| OAuth2 app bridge | `handleAppTokenRequest`, `handleAppRevokeRequest`, `bearerChallenge`, … — see [OAuth2 App Bridge](#oauth2-app-bridge) |
|
|
2106
|
+
| Export group | Purpose |
|
|
2107
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2108
|
+
| `BuildBase()` factory | Session-scoped server actions — see [Server-Side Usage](#server-side-usage) |
|
|
2109
|
+
| Webhook verification | `verifyWebhookSignature`, `parseWebhookEvent` — see [Webhook Verification](#webhook-verification) |
|
|
2110
|
+
| Agent readiness | `resolveAgentPath`, `buildRobotsTxt`, `buildAgentCard`, `buildLlmsTxt`, … — see [Agent Readiness](#agent-readiness-discovery) |
|
|
2111
|
+
| OAuth2 app bridge | `handleAppTokenRequest`, `handleAppRevokeRequest`, `signClientJwt`, `bearerChallenge`, … — see [OAuth2 App Bridge](#oauth2-app-bridge) |
|
|
2112
|
+
| MCP server | `createMcpHandler`, `defineMcpTool`, … from **`@buildbase/sdk/mcp`** — see [MCP Server](#mcp-server-buildbasesdkmcp) |
|
|
2111
2113
|
|
|
2112
2114
|
### Components
|
|
2113
2115
|
|
|
@@ -2933,60 +2935,273 @@ app.post('/webhooks/buildbase', (req, res) => {
|
|
|
2933
2935
|
|
|
2934
2936
|
## Agent Readiness (Discovery)
|
|
2935
2937
|
|
|
2936
|
-
Make a consuming app [agent ready](https://isitagentready.com) by serving the standard machine-readable discovery documents an AI agent looks for — Agent Card, OAuth
|
|
2938
|
+
Make a consuming app [agent ready](https://isitagentready.com) by serving the standard machine-readable discovery documents an AI agent looks for — Agent Card, OAuth metadata (RFC 8414/9728), Agent Skills, API Catalog (RFC 9727), MCP Server Card, `robots.txt` with AI-bot rules + Content Signals, `sitemap.xml`, `security.txt` (RFC 9116), `llms.txt`, and `auth.md` — with the BuildBase server as the source of truth. Framework-agnostic and zero React: every function returns a plain `DiscoveryDocument` (`{ status, contentType, body, cacheControl, vary? }`); you wire it into your own router.
|
|
2937
2939
|
|
|
2938
2940
|
```ts
|
|
2939
2941
|
// lib/agent-ready.ts
|
|
2940
|
-
import type
|
|
2942
|
+
import { resolveAgentPath, type AgentReadyConfig } from '@buildbase/sdk';
|
|
2941
2943
|
|
|
2942
2944
|
export const agentConfig: AgentReadyConfig = {
|
|
2943
2945
|
serverUrl: process.env.BUILDBASE_URL!,
|
|
2944
2946
|
orgId: process.env.BUILDBASE_ORG_ID!,
|
|
2945
2947
|
siteUrl: 'https://imejis.io',
|
|
2946
2948
|
site: { name: 'Imejis', description: 'Generate images from templates via API.' },
|
|
2947
|
-
//
|
|
2949
|
+
// Discovery content is YOURS — defined here in code, not in the platform:
|
|
2950
|
+
llmsTxt: '# Imejis\n\n> Generate images from templates via API.',
|
|
2951
|
+
robots: { contentSignals: { search: true, aiInput: true, aiTrain: false } },
|
|
2952
|
+
sitemap: { urls: ['/', '/pricing'] }, // omit if you already generate sitemaps
|
|
2953
|
+
protectedResources: [{ scopes: ['read:profile', 'write:posts'] }], // RFC 9728
|
|
2954
|
+
// skills, security, apiCatalog, mcpServerCard, authMd, cacheTtlSeconds — all optional
|
|
2948
2955
|
};
|
|
2949
|
-
```
|
|
2950
|
-
|
|
2951
|
-
Serve every `.well-known/*` document from one catch-all route via `resolveWellKnown`:
|
|
2952
|
-
|
|
2953
|
-
```ts
|
|
2954
|
-
// app/.well-known/[...path]/route.ts (Next.js App Router)
|
|
2955
|
-
import { resolveWellKnown } from '@buildbase/sdk';
|
|
2956
|
-
import { agentConfig } from '@/lib/agent-ready';
|
|
2957
2956
|
|
|
2958
|
-
export async function
|
|
2959
|
-
const doc = await
|
|
2957
|
+
export async function serveAgentPath(req: Request): Promise<Response> {
|
|
2958
|
+
const doc = await resolveAgentPath(new URL(req.url).pathname, agentConfig);
|
|
2960
2959
|
if (!doc) return new Response('{"error":"not_found"}', { status: 404 });
|
|
2961
2960
|
return new Response(doc.body, {
|
|
2962
2961
|
status: doc.status,
|
|
2963
|
-
headers: {
|
|
2962
|
+
headers: {
|
|
2963
|
+
'Content-Type': doc.contentType,
|
|
2964
|
+
'Cache-Control': doc.cacheControl,
|
|
2965
|
+
...(doc.vary ? { Vary: doc.vary } : {}),
|
|
2966
|
+
},
|
|
2964
2967
|
});
|
|
2965
2968
|
}
|
|
2966
2969
|
```
|
|
2967
2970
|
|
|
2968
|
-
`
|
|
2971
|
+
`resolveAgentPath` resolves the app's **entire agent-readiness surface**: the root-level documents (`/robots.txt`, `/sitemap.xml`, `/llms.txt`, `/auth.md`, `/security.txt`) plus everything under `/.well-known/*` — `agent.json`, `oauth-protected-resource` (+ per-resource paths), the full RFC 8414 `oauth-authorization-server` metadata (proxied fail-soft from the platform), the Agent Skills index + `SKILL.md`, `security.txt`, the API Catalog, and the MCP Server Card. Wire it once:
|
|
2969
2972
|
|
|
2970
2973
|
```ts
|
|
2971
|
-
// app/
|
|
2972
|
-
import {
|
|
2973
|
-
|
|
2974
|
+
// app/.well-known/[...path]/route.ts (Next.js App Router)
|
|
2975
|
+
import { serveAgentPath } from '@/lib/agent-ready';
|
|
2976
|
+
export const GET = serveAgentPath;
|
|
2974
2977
|
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2978
|
+
// plus identical two-liners at app/robots.txt/route.ts, app/llms.txt/route.ts,
|
|
2979
|
+
// app/auth.md/route.ts, app/security.txt/route.ts, app/sitemap.xml/route.ts
|
|
2980
|
+
```
|
|
2981
|
+
|
|
2982
|
+
```ts
|
|
2983
|
+
// Express — one middleware covers everything
|
|
2984
|
+
app.use(async (req, res, next) => {
|
|
2985
|
+
const doc = await resolveAgentPath(req.path, agentConfig);
|
|
2986
|
+
if (!doc) return next();
|
|
2987
|
+
res
|
|
2988
|
+
.status(doc.status)
|
|
2989
|
+
.setHeader('Content-Type', doc.contentType)
|
|
2990
|
+
.setHeader('Cache-Control', doc.cacheControl)
|
|
2991
|
+
.send(doc.body);
|
|
2992
|
+
});
|
|
2980
2993
|
```
|
|
2981
2994
|
|
|
2995
|
+
(`resolveWellKnown` remains available for `.well-known`-only wiring; `resolveAgentPath` is a superset.)
|
|
2996
|
+
|
|
2982
2997
|
`fetchAgentReadiness` is **fail-soft** — any network/HTTP/parse error resolves to `{ enabled: false }`, so a discovery route never 500s and never leaks that the platform is down; agents simply see the app as not (yet) agent-ready. Results are cached in-memory for `cacheTtlSeconds` (default 300s); call `clearAgentReadinessCache()` to reset (mainly for tests).
|
|
2983
2998
|
|
|
2984
|
-
|
|
2999
|
+
### robots.txt, Content Signals & AI bots
|
|
3000
|
+
|
|
3001
|
+
`buildRobotsTxt(config)` always returns a document. By default it emits an allow-all policy plus an explicit `Allow: /` group for every known AI crawler (`AI_BOT_USER_AGENTS`: GPTBot, ClaudeBot, PerplexityBot, …) so readiness checkers see AI bots addressed. Tune it via `config.robots`:
|
|
3002
|
+
|
|
3003
|
+
- `policies` — your base groups (default `[{ userAgent: '*', allow: ['/'] }]`)
|
|
3004
|
+
- `aiBots` — `'allow'` (default) | `'deny'` | explicit `RobotsPolicy[]` for per-bot control
|
|
3005
|
+
- `contentSignals` — [Content Signals](https://contentsignals.org): `{ search, aiInput, aiTrain }` → `Content-Signal: search=yes, ai-input=yes, ai-train=no`
|
|
3006
|
+
- `sitemaps` — `Sitemap:` lines (defaults to `${siteUrl}/sitemap.xml` when `config.sitemap` is set)
|
|
2985
3007
|
|
|
2986
|
-
|
|
3008
|
+
### Markdown content negotiation & Link headers
|
|
3009
|
+
|
|
3010
|
+
- `wantsMarkdown(acceptHeader)` — q-value-aware predicate: does this request prefer `text/markdown` over HTML? Use it in middleware to rewrite content pages to a markdown endpoint.
|
|
3011
|
+
- `negotiateMarkdown(acceptHeader, { html, markdown })` — picks the variant and returns a `DiscoveryDocument` with `vary: 'Accept'`.
|
|
3012
|
+
- `buildDiscoveryLinkHeader(config)` — the `Link` response header value advertising `llms.txt`, the Agent Card, and (when configured) sitemap, API catalog, and MCP server card. Sync and pure — safe in edge middleware.
|
|
3013
|
+
|
|
3014
|
+
### Who owns what
|
|
3015
|
+
|
|
3016
|
+
Clean split, no duplication: **your app owns discovery content, the platform owns auth.** You define `llms.txt`, `robots.txt`, `sitemap`, `protectedResources` (RFC 9728), the API catalog, and skills right here in `AgentReadyConfig` — the SDK serves them from your own origin with no platform round-trip. The platform only supplies the pointer to its OAuth **authorization server** (the one thing it owns); that's the sole content of `fetchAgentReadiness`'s bundle (`{ enabled, authorizationServer }`).
|
|
3017
|
+
|
|
3018
|
+
- **`/llms.txt`** — from `config.llmsTxt`. Returns null when unset (serve your own static file instead).
|
|
3019
|
+
- **`/.well-known/oauth-protected-resource`** — built from `config.protectedResources`; `authorization_servers` is derived from `serverUrl`/`orgId`.
|
|
3020
|
+
- **`/auth.md`** — agent registration/auth instructions. Generated from the OAuth metadata, or serves `config.authMd` verbatim.
|
|
2987
3021
|
- **WebMCP** — expose in-page actions to browser agents. In a client component, call `provideWebMcpTools([{ name, description, inputSchema, execute }])` (no-op off-browser).
|
|
2988
3022
|
|
|
2989
|
-
|
|
3023
|
+
### isitagentready.com coverage
|
|
3024
|
+
|
|
3025
|
+
| Check | Covered by |
|
|
3026
|
+
| ---------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
3027
|
+
| robots.txt + AI bot rules + Content Signals | `buildRobotsTxt` |
|
|
3028
|
+
| Sitemap | `buildSitemap` (or your own generator) |
|
|
3029
|
+
| Link response headers | `buildDiscoveryLinkHeader` |
|
|
3030
|
+
| Markdown content negotiation | `wantsMarkdown` / `negotiateMarkdown` |
|
|
3031
|
+
| llms.txt | `buildLlmsTxt` |
|
|
3032
|
+
| MCP Server Card + live MCP server | `buildMcpServerCard` + [`@buildbase/sdk/mcp`](#mcp-server-buildbasesdkmcp) |
|
|
3033
|
+
| Agent Skills / API Catalog | `buildAgentSkillsIndex` / `buildApiCatalog` |
|
|
3034
|
+
| OAuth discovery (RFC 8414) + protected resource (RFC 9728) | `resolveAgentPath` (platform-backed) |
|
|
3035
|
+
| DNS-AID | out of SDK scope — add the DNS record at your registrar |
|
|
3036
|
+
| Web Bot Auth, commerce (x402/ACP/UCP) | not yet — planned |
|
|
3037
|
+
|
|
3038
|
+
**Functions:** `resolveAgentPath`, `resolveWellKnown`, `fetchAgentReadiness`, `clearAgentReadinessCache`, `buildAgentCard`, `buildProtectedResourceMetadata`, `buildAgentSkillsIndex`, `buildSkillMd`, `buildSecurityTxt`, `buildLlmsTxt`, `buildRobotsTxt`, `buildSitemap`, `buildApiCatalog`, `buildMcpServerCard`, `buildAuthMd`, `buildDiscoveryLinkHeader`, `wantsMarkdown`, `negotiateMarkdown`, `provideWebMcpTools`, `sha256Digest`. **Constants:** `AI_BOT_USER_AGENTS`. **Types:** `AgentReadyConfig`, `AgentReadinessBundle`, `AgentSkill`, `ApiCatalogApi`, `McpServerCard`, `RobotsConfig`, `RobotsPolicy`, `ContentSignals`, `SitemapUrl`, `WebMcpTool`, `DiscoveryDocument`.
|
|
3039
|
+
|
|
3040
|
+
## MCP Server (`@buildbase/sdk/mcp`)
|
|
3041
|
+
|
|
3042
|
+
Turn your app into a **live MCP server** so AI agents can operate it: the built-in tools expose your BuildBase state (workspaces, subscription, quotas, credits, feature flags, permissions) and you add your own product tools with zod schemas. Stateless Streamable HTTP — pure functions plus a Web-standard `Request`/`Response` adapter, so it runs on Node 18+, edge, Deno, and Bun. Server-only, zero React; split from the core entry because it uses `zod` at runtime.
|
|
3043
|
+
|
|
3044
|
+
```ts
|
|
3045
|
+
// lib/mcp.ts
|
|
3046
|
+
import BuildBase from '@buildbase/sdk';
|
|
3047
|
+
import { createMcpHandler, defineMcpTool, verifyClientJwt } from '@buildbase/sdk/mcp';
|
|
3048
|
+
import { z } from 'zod';
|
|
3049
|
+
|
|
3050
|
+
const buildbase = BuildBase({
|
|
3051
|
+
serverUrl: process.env.BUILDBASE_URL!,
|
|
3052
|
+
orgId: process.env.BUILDBASE_ORG_ID!,
|
|
3053
|
+
});
|
|
3054
|
+
|
|
3055
|
+
export const mcp = createMcpHandler({
|
|
3056
|
+
buildbase,
|
|
3057
|
+
serverInfo: { name: 'imejis', version: '1.0.0' },
|
|
3058
|
+
auth: {
|
|
3059
|
+
// Verify the Bearer token your app minted in the OAuth2 app-bridge flow
|
|
3060
|
+
// and map it to the BuildBase session the tools run under.
|
|
3061
|
+
verify: token => {
|
|
3062
|
+
const c = verifyClientJwt(token, process.env.BUILDBASE_CLIENT_SECRET!);
|
|
3063
|
+
return { sessionId: String(c.sid), userId: String(c.sub), scopes: c.scope as string[] };
|
|
3064
|
+
},
|
|
3065
|
+
// Unauthenticated requests get a 401 whose WWW-Authenticate header points
|
|
3066
|
+
// here, so agents bootstrap the whole OAuth discovery flow (RFC 9728).
|
|
3067
|
+
resourceMetadataUrl: 'https://imejis.io/.well-known/oauth-protected-resource',
|
|
3068
|
+
},
|
|
3069
|
+
builtinTools: 'readonly', // default; 'all' opts into the mutating tools
|
|
3070
|
+
tools: [
|
|
3071
|
+
defineMcpTool({
|
|
3072
|
+
name: 'generate_image',
|
|
3073
|
+
description: 'Render an image from a template',
|
|
3074
|
+
inputSchema: z.object({ templateId: z.string(), data: z.record(z.string(), z.any()) }),
|
|
3075
|
+
annotations: { readOnlyHint: false },
|
|
3076
|
+
execute: async (input, { bb, workspaceId }) => {
|
|
3077
|
+
await bb.usage.record(workspaceId!, { quotaSlug: 'images', quantity: 1 });
|
|
3078
|
+
return renderTemplate(input.templateId, input.data);
|
|
3079
|
+
},
|
|
3080
|
+
}),
|
|
3081
|
+
],
|
|
3082
|
+
});
|
|
3083
|
+
```
|
|
3084
|
+
|
|
3085
|
+
```ts
|
|
3086
|
+
// app/api/mcp/route.ts (Next.js App Router)
|
|
3087
|
+
import { mcp } from '@/lib/mcp';
|
|
3088
|
+
export const POST = (req: Request) => mcp.fetch(req);
|
|
3089
|
+
export const GET = (req: Request) => mcp.fetch(req); // 405 per spec (no SSE)
|
|
3090
|
+
export const DELETE = (req: Request) => mcp.fetch(req); // 405 (stateless)
|
|
3091
|
+
```
|
|
3092
|
+
|
|
3093
|
+
```ts
|
|
3094
|
+
// Express — the pure form
|
|
3095
|
+
app.post('/mcp', async (req, res) => {
|
|
3096
|
+
const r = await mcp.handle({ method: req.method, headers: req.headers, body: req.body });
|
|
3097
|
+
res.status(r.status).set(r.headers).send(r.body);
|
|
3098
|
+
});
|
|
3099
|
+
```
|
|
3100
|
+
|
|
3101
|
+
Advertise it via the server card so agents find the endpoint:
|
|
3102
|
+
|
|
3103
|
+
```ts
|
|
3104
|
+
export const agentConfig: AgentReadyConfig = {
|
|
3105
|
+
// ...
|
|
3106
|
+
mcpServerCard: mcp.serverCard({ endpoint: 'https://imejis.io/api/mcp' }),
|
|
3107
|
+
};
|
|
3108
|
+
```
|
|
3109
|
+
|
|
3110
|
+
### Built-in tools
|
|
3111
|
+
|
|
3112
|
+
The built-ins cover the **full BuildBase surface**. By default (`builtinTools: 'readonly'`) only the read tools are exposed — **least privilege**: no agent can mutate, bill, or delete anything until you opt in. Set `'all'` to add the writes and destructive/billing ops. Every call still runs under the user's session, so the platform enforces the user's real permissions on top of whatever you expose — an agent can never exceed what the user can do.
|
|
3113
|
+
|
|
3114
|
+
- **Read** (`builtinTools: 'readonly'` for just these): `list_workspaces`, `get_workspace`, `get_user_profile`, `list_workspace_users`, `get_subscription`, `get_plans`, `get_plan_versions`, `get_public_plans`, `get_plan_version`, `list_invoices`, `get_invoice`, `get_quota_usage`, `get_all_quota_usage`, `get_usage_logs`, `get_credit_balance`, `list_credit_transactions`, `get_credit_packages`, `get_credit_buckets`, `get_expiring_credits`, `get_public_credit_packages`, `check_feature_flag`, `check_permission`, `resolve_permissions`, `get_settings`.
|
|
3115
|
+
- **Write / destructive**: `record_usage`, `record_usage_batch`, `consume_credits`, `send_notification`, `create_workspace`, `update_workspace`, `delete_workspace`, `invite_workspace_user`, `remove_workspace_user`, `update_workspace_user_role`, `update_user_profile`, `update_feature_flag`, `create_subscription_checkout`, `update_subscription`, `cancel_subscription`, `resume_subscription`, `get_billing_portal_url`, `purchase_credits`.
|
|
3116
|
+
|
|
3117
|
+
**You choose how much to expose** — the default is safe, and you widen it:
|
|
3118
|
+
|
|
3119
|
+
```ts
|
|
3120
|
+
builtinTools: 'readonly'; // default — reads only (least privilege)
|
|
3121
|
+
builtinTools: 'all'; // reads + writes + destructive/billing ops
|
|
3122
|
+
builtinTools: false; // none (custom tools only)
|
|
3123
|
+
builtinTools: {
|
|
3124
|
+
include: ['get_quota_usage', 'record_usage'];
|
|
3125
|
+
} // exactly these
|
|
3126
|
+
builtinTools: {
|
|
3127
|
+
exclude: ['delete_workspace', 'purchase_credits'];
|
|
3128
|
+
} // all reads, minus these (an `include` list can be trimmed the same way)
|
|
3129
|
+
```
|
|
3130
|
+
|
|
3131
|
+
The agent acts as an authenticated app user — it knows your app, not BuildBase — so the built-ins carry **no BuildBase-specific scope requirement**. Workspace-scoped tools take an optional `workspaceId` and fall back to the one pinned on the verified token (`McpAuthInfo.workspaceId`); pin it and cross-workspace access is refused even for tools you exposed.
|
|
3132
|
+
|
|
3133
|
+
Want per-scope gating on top? Set `requiredScopes` on your own custom tools and return the granted `scopes` from `auth.verify` — the handler hides a tool from `tools/list` (and refuses the call) when its `requiredScopes` aren't all granted. A token that carries **no** scopes sees only tools that require none (the built-ins), so a token minted without a `scope` claim can never unlock a scoped tool. Opt-in, using **your** scope names — the SDK never imposes its own.
|
|
3134
|
+
|
|
3135
|
+
### Production hardening
|
|
3136
|
+
|
|
3137
|
+
The handler ships with safe defaults; these are the knobs to review before going live:
|
|
3138
|
+
|
|
3139
|
+
- **`builtinTools` defaults to `'readonly'`.** Writes and destructive/billing ops are opt-in. Prefer an explicit `{ include: [...] }` allowlist over `'all'` in production.
|
|
3140
|
+
- **Request-body cap.** Bodies over `maxRequestBytes` (default **1 MiB**) are rejected with 413 before parsing — and, on the `fetch` adapter, before the stream is even read (via `Content-Length`). Set `0` to disable.
|
|
3141
|
+
- **Rate limiting.** The server is stateless and keeps no counters. Supply a `rateLimit(auth, req)` gate — called after auth, before dispatch — backed by your own store (KV/Redis/edge limiter), returning `false` or `{ ok: false, retryAfter }` to answer 429. A failing limiter fails **closed**. Also put platform/edge rate limiting in front of the endpoint.
|
|
3142
|
+
- **Origin allowlist.** Set `allowedOrigins` for DNS-rebinding protection; browser-origin requests with a non-matching `Origin` get 403 (server-to-server requests with no `Origin` still pass).
|
|
3143
|
+
- **Token expiry.** `verifyClientJwt` **requires a numeric `exp` by default** — a token with no expiry never expires. `signClientJwt` always stamps one. Pin `issuer`/`audience` too for defense in depth: `verifyClientJwt(token, secret, { issuer, audience })`.
|
|
3144
|
+
- **Error visibility.** Pass `onError` — it fires for auth-callback, rate-limit, context-factory, tool-execution, and schema-conversion failures.
|
|
3145
|
+
|
|
3146
|
+
```ts
|
|
3147
|
+
export const mcp = createMcpHandler({
|
|
3148
|
+
buildbase,
|
|
3149
|
+
auth: { verify, resourceMetadataUrl },
|
|
3150
|
+
builtinTools: { include: ['get_quota_usage', 'record_usage'] },
|
|
3151
|
+
allowedOrigins: ['https://app.imejis.io'],
|
|
3152
|
+
maxRequestBytes: 512 * 1024,
|
|
3153
|
+
rateLimit: async auth => ({ ok: await limiter.allow(auth?.userId), retryAfter: 30 }),
|
|
3154
|
+
onError: (err, ctx) => logger.error({ err, ...ctx }, 'mcp error'),
|
|
3155
|
+
});
|
|
3156
|
+
```
|
|
3157
|
+
|
|
3158
|
+
### Your tools, your rules — no lock-in
|
|
3159
|
+
|
|
3160
|
+
The SDK accelerates; it never gates. Everything BuildBase-specific is optional:
|
|
3161
|
+
|
|
3162
|
+
- **Standalone mode** — omit `buildbase` entirely and ship an MCP server with only your own tools (built-ins are simply off). Nothing in the protocol layer depends on the BuildBase platform.
|
|
3163
|
+
- **Your own context** — the `context` factory runs once per call and its return value reaches every tool as `ctx.custom`: your Prisma client, your services, whatever your tools need. Type it with the second generic of `defineMcpTool`.
|
|
3164
|
+
|
|
3165
|
+
```ts
|
|
3166
|
+
type AppCtx = { db: PrismaClient };
|
|
3167
|
+
|
|
3168
|
+
const mcp = createMcpHandler({
|
|
3169
|
+
auth: { verify: verifyMyToken }, // any token format — yours
|
|
3170
|
+
context: (): AppCtx => ({ db: prisma }),
|
|
3171
|
+
tools: [
|
|
3172
|
+
defineMcpTool<z.ZodType, AppCtx>({
|
|
3173
|
+
name: 'search_orders',
|
|
3174
|
+
description: 'Search orders in our own database',
|
|
3175
|
+
inputSchema: z.object({ query: z.string() }),
|
|
3176
|
+
execute: (input, ctx) =>
|
|
3177
|
+
ctx.custom.db.order.findMany({ where: { name: { contains: input.query } } }),
|
|
3178
|
+
}),
|
|
3179
|
+
],
|
|
3180
|
+
});
|
|
3181
|
+
```
|
|
3182
|
+
|
|
3183
|
+
- **Override built-ins** — a custom tool named like a built-in (e.g. your own `get_subscription`) replaces it; your definition wins.
|
|
3184
|
+
- **Raw JSON Schema** — tools don't have to use zod; pass a plain JSON Schema object as `inputSchema` and receive arguments as-is.
|
|
3185
|
+
- **Composable transport** — `handle()` is a pure `{method, headers, body} → {status, headers, body}` function. Route around it, wrap it, or serve extra JSON-RPC methods yourself before delegating; you own the endpoint.
|
|
3186
|
+
- **Browser side** — `provideWebMcpTools` (core entry) registers any in-page tools with WebMCP-capable browsers, unrelated to BuildBase state.
|
|
3187
|
+
|
|
3188
|
+
### Auth in one line of plumbing
|
|
3189
|
+
|
|
3190
|
+
The OAuth2 app-bridge flow already mints your app's Bearer tokens (see [OAuth2 App Bridge](#oauth2-app-bridge)); `signClientJwt(payload, secret)` is the symmetric counterpart of `verifyClientJwt` for exactly that. The mint claims include `sessionId` — a per-user BuildBase session bound to the grant (fresh on every mint, including refresh). Embed it **encrypted** (JWTs are signed, not encrypted — the agent can read its own token) and keep it out of any storage, so it simply expires with the token; your MCP `auth.verify` decrypts it back into `McpAuthInfo.sessionId`:
|
|
3191
|
+
|
|
3192
|
+
```ts
|
|
3193
|
+
mintToken: (user) => ({
|
|
3194
|
+
token: signClientJwt(
|
|
3195
|
+
{ sub: user.id, scope: user.scope, aud: user.resource, sid: encrypt(user.sessionId) },
|
|
3196
|
+
process.env.BUILDBASE_CLIENT_SECRET!
|
|
3197
|
+
),
|
|
3198
|
+
expiresIn: 3600,
|
|
3199
|
+
}),
|
|
3200
|
+
```
|
|
3201
|
+
|
|
3202
|
+
Set `auth: false` to disable auth entirely (local development only). Test with the MCP Inspector: `npx @modelcontextprotocol/inspector --cli http://localhost:3000/api/mcp --transport http --header "Authorization: Bearer <token>" --method tools/list`.
|
|
3203
|
+
|
|
3204
|
+
**Exports:** `createMcpHandler`, `defineMcpTool`, `builtinMcpTools`, `selectBuiltinTools`, plus re-exported auth helpers (`signClientJwt`, `verifyClientJwt`, `extractBearerToken`, `bearerChallenge`, `AppBridgeError`). **Types:** `CreateMcpHandlerConfig`, `McpHandler`, `McpToolDefinition`, `McpToolContext`, `McpAuthInfo`, `McpHttpRequest`, `McpHttpResponse`, `McpToolAnnotations`, `McpBuildBaseClient`, `BuiltinMcpToolName`, `McpServerCard`, `ScopedActions`.
|
|
2990
3205
|
|
|
2991
3206
|
## OAuth2 App Bridge
|
|
2992
3207
|
|