@buildbase/sdk 0.0.50 → 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.
Files changed (52) hide show
  1. package/AGENTS.md +41 -13
  2. package/CHANGELOG.md +109 -0
  3. package/README.md +343 -44
  4. package/dist/css/styles.css +1 -1
  5. package/dist/index.cjs +1 -1
  6. package/dist/index.d.ts +874 -53
  7. package/dist/index.mjs +1 -1
  8. package/dist/mcp/index.cjs +1 -0
  9. package/dist/mcp/index.d.ts +1402 -0
  10. package/dist/mcp/index.mjs +1 -0
  11. package/dist/react/chunks/SettingsDialog-BxDYsie6.mjs +2 -0
  12. package/dist/react/chunks/SettingsDialog-Dy8W3NA6.cjs +2 -0
  13. package/dist/react/chunks/SubscriptionDialog-6c5dHoQd.mjs +2 -0
  14. package/dist/react/chunks/SubscriptionDialog-CpVnnXJg.cjs +2 -0
  15. package/dist/react/chunks/ar-1Z1MOGAP.mjs +2 -0
  16. package/dist/react/chunks/ar-VGzBv3AZ.cjs +2 -0
  17. package/dist/react/chunks/de-BjdDg60Y.cjs +2 -0
  18. package/dist/react/chunks/de-CXUItplO.mjs +2 -0
  19. package/dist/react/chunks/es-BBPsSZUp.cjs +2 -0
  20. package/dist/react/chunks/es-BTbzVutz.mjs +2 -0
  21. package/dist/react/chunks/fr-Cax48X_S.cjs +2 -0
  22. package/dist/react/chunks/fr-DPMeOQu3.mjs +2 -0
  23. package/dist/react/chunks/hi-BJqvpG6d.cjs +2 -0
  24. package/dist/react/chunks/hi-JfJx-LV7.mjs +2 -0
  25. package/dist/react/chunks/ja-CDGtLzTk.mjs +2 -0
  26. package/dist/react/chunks/ja-DvVyj5xX.cjs +2 -0
  27. package/dist/react/chunks/react-4SlWxHdh.cjs +2 -0
  28. package/dist/react/chunks/react-OL1uSkpr.mjs +2 -0
  29. package/dist/react/chunks/zh-BAcqxkxk.cjs +2 -0
  30. package/dist/react/chunks/zh-BNZ2Ooe8.mjs +2 -0
  31. package/dist/react/index.cjs +1 -1
  32. package/dist/react/index.d.ts +484 -93
  33. package/dist/react/index.mjs +1 -1
  34. package/package.json +22 -5
  35. package/dist/react/chunks/SubscriptionDialog-A6oETKbb.cjs +0 -2
  36. package/dist/react/chunks/SubscriptionDialog-nuMunBn4.mjs +0 -2
  37. package/dist/react/chunks/ar-BRvKHRYS.cjs +0 -2
  38. package/dist/react/chunks/ar-IhPhJAZA.mjs +0 -2
  39. package/dist/react/chunks/de-D3q0vQBG.cjs +0 -2
  40. package/dist/react/chunks/de-bvh5IFSx.mjs +0 -2
  41. package/dist/react/chunks/es-B9kiwJmF.cjs +0 -2
  42. package/dist/react/chunks/es-TyUz4_E1.mjs +0 -2
  43. package/dist/react/chunks/fr-C500Ytao.cjs +0 -2
  44. package/dist/react/chunks/fr-CKm-bSvG.mjs +0 -2
  45. package/dist/react/chunks/hi-CfFhH2uQ.cjs +0 -2
  46. package/dist/react/chunks/hi-Ck9cPpd-.mjs +0 -2
  47. package/dist/react/chunks/ja-AM4H1tXw.mjs +0 -2
  48. package/dist/react/chunks/ja-DfROrx_j.cjs +0 -2
  49. package/dist/react/chunks/react-CTX0_nOV.cjs +0 -2
  50. package/dist/react/chunks/react-DzQupoqH.mjs +0 -2
  51. package/dist/react/chunks/zh-CLmBh-it.cjs +0 -2
  52. package/dist/react/chunks/zh-DCVPxie-.mjs +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/css` | build | `import '@buildbase/sdk/css'` — required once for component styling. |
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.
@@ -24,9 +25,13 @@ BuildBase is a multi-tenant SaaS platform SDK: auth, workspaces, subscriptions/b
24
25
  import '@buildbase/sdk/css';
25
26
  import { SaaSOSProvider } from '@buildbase/sdk/react';
26
27
 
27
- <SaaSOSProvider serverUrl={process.env.NEXT_PUBLIC_BUILDBASE_URL!} orgId={process.env.NEXT_PUBLIC_BUILDBASE_ORG_ID!} locale="en">
28
+ <SaaSOSProvider
29
+ serverUrl={process.env.NEXT_PUBLIC_BUILDBASE_URL!}
30
+ orgId={process.env.NEXT_PUBLIC_BUILDBASE_ORG_ID!}
31
+ locale="en"
32
+ >
28
33
  {children}
29
- </SaaSOSProvider>
34
+ </SaaSOSProvider>;
30
35
  ```
31
36
 
32
37
  Everything below `SaaSOSProvider` can use the hooks and gate components. **Prefer SDK hooks over reaching into internal context/Redux.**
@@ -35,8 +40,22 @@ Everything below `SaaSOSProvider` can use the hooks and gate components. **Prefe
35
40
 
36
41
  ```ts
37
42
  import BuildBase from '@buildbase/sdk';
38
- export const { auth, workspace, subscription, usage, credits, plans, invoices,
39
- users, features, settings, notification, permissions, withSession, client } = BuildBase({
43
+ export const {
44
+ auth,
45
+ workspace,
46
+ subscription,
47
+ usage,
48
+ credits,
49
+ plans,
50
+ invoices,
51
+ users,
52
+ features,
53
+ settings,
54
+ notification,
55
+ permissions,
56
+ withSession,
57
+ client,
58
+ } = BuildBase({
40
59
  serverUrl: process.env.BUILDBASE_URL!,
41
60
  orgId: process.env.BUILDBASE_ORG_ID!,
42
61
  getSessionId: async () => (await cookies()).get('bb-session-id')?.value ?? null, // Next.js
@@ -52,9 +71,9 @@ Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err,
52
71
 
53
72
  - **Factory:** `BuildBase` (also default export) → `{ auth, workspace, subscription, usage, credits, plans, invoices, users, features, settings, notification, permissions, withSession, client }`.
54
73
  - **API classes:** `BaseApi` (extend for custom APIs), `AuthApi`, `UserApi`, `WorkspaceApi`, `SettingsApi`, `PushApi`.
55
- - **Webhook verification (Node only):** `verifyWebhookSignature`, `parseWebhookEvent`. Verify against the **raw** request body (`await req.text()`), not the parsed JSON.
56
- - **Agent readiness (framework-agnostic, returns plain `DiscoveryDocument`):** `resolveWellKnown` (dispatch all `.well-known/*`), `fetchAgentReadiness` (fail-soft, cached), `clearAgentReadinessCache`, `buildAgentCard`, `buildProtectedResourceMetadata`, `buildAgentSkillsIndex`, `buildSkillMd`, `buildSecurityTxt`, `buildLlmsTxt`, `sha256Digest`. Types: `AgentReadyConfig`, `AgentReadinessBundle`, `AgentSkill`, `DiscoveryDocument`.
57
- - **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`.
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.
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.
58
77
  - **Currency & pricing utils (pure):** `formatCents`, `formatOverageRate`, `formatOverageRateWithLabel`, `formatQuotaIncludedOverage`, `formatQuotaWithPrice`, `getCurrencySymbol`, `getCurrencyFlag`, `getQuotaUnitLabelFromName`, `getQuotaDisplayValue`, `CURRENCY_DISPLAY`, `CURRENCY_FLAG`, `PLAN_CURRENCY_CODES`, `PLAN_CURRENCY_OPTIONS`.
59
78
  - **Pricing variants (multi-currency):** `getPricingVariant`, `getBasePriceCents`, `getStripePriceIdForInterval`, `getQuotaOverageCents`, `getQuotaDisplayWithVariant`, `getAvailableCurrenciesFromPlans`, `getDisplayCurrency`, `getBillingIntervalAndCurrencyFromPriceId`.
60
79
  - **Seat pricing:** `calculateBillableSeats`, `calculateSeatOverageCents`, `calculateTotalSubscriptionCents`, `getPerSeatPriceCents`, `getSeatPricing`.
@@ -88,6 +107,15 @@ Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err,
88
107
 
89
108
  `countries`, `currencies`, `languages`, `timezones`, `betaFormSchema` (Zod), type `BetaFormValues`.
90
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
+
91
119
  ## Server actions cheat-sheet
92
120
 
93
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,115 @@ 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
+
64
+ ## [0.0.51] - 2026-07-08
65
+
66
+ ### Fixed
67
+
68
+ - **Combobox dropdowns (language/country/currency/timezone) looked disabled and ignored clicks.** The shared `CommandItem` styled items with the presence-based `data-[disabled]:` selector, but cmdk ≥1.0 always renders `data-disabled="true|false"` — so every option got `opacity-50` + `pointer-events-none`. Selector now matches the value (`data-[disabled=true]:`).
69
+ - **Dropdown search matches visible labels**, not just option codes ("germ" now finds Germany, not only "de") via cmdk `keywords`; a search with no matches shows a translated "No results found" empty state (new `dropdowns.noResults` key in all 8 locales) instead of a blank panel.
70
+ - **Dropdown triggers now match the Input styling** (same `rounded-md` radius, height, border, and text size) instead of inheriting the pill-shaped button base — form screens look consistent.
71
+ - **Switch thumb mirrors correctly in RTL locales** (Arabic) — the checked state used a physical `translate-x` that moved the wrong way.
72
+ - `CommandInput` wrapper attribute aligned to the canonical `cmdk-input-wrapper` so `CommandDialog`'s scoped selectors apply.
73
+
74
+ - **`@buildbase/sdk/react` now actually exports the core runtime values its types declared.** `src/react.ts` used `export type * from './core'`, but the generated `dist/react/index.d.ts` flattened it into value exports — so `import { Permission, formatCents, AuthStatus, … } from '@buildbase/sdk/react'` (as documented in the README) type-checked and then was `undefined` at runtime. The re-export is now a value re-export (`export * from './core'`); the React bundle exposes the full core surface (90 → 159 exports).
75
+ - **Caller-initiated aborts are no longer misreported as timeouts.** `BaseApi` converted every `AbortError` into a fake "Request timeout" error whenever a timeout was configured (i.e. always, default 30s), defeating `isAbortError()` filtering — component-unmount cancellations surfaced as real errors. Conversion now happens only when the internal timeout controller actually fired.
76
+ - **`unwrapResponse` no longer swallows falsy payloads.** `result.data || result` returned the whole `{success, data}` envelope when `data` was `0`, `false`, `''`, or `null`; now checks `!== undefined`.
77
+ - **API path parameters are URI-encoded.** All interpolated path segments (workspace/user/passkey/invoice/plan-version IDs, attribute keys) go through a new `BaseApi.apiPath` tagged template, so values containing `/ ? # ..` can't retarget the request path. Query strings are unaffected.
78
+ - **Invoices tab no longer claims an active subscription while loading.** `subscription?.subscription !== null` was `true` for `undefined` (initial load); now `!= null`.
79
+ - **Paid plans with no price for the selected interval render "—" instead of "Free"** in the plan dialog (dead ternary branch fell through to the free label).
80
+ - **`@hookform/resolvers` moved from devDependencies to dependencies** — it's a runtime import in 4 shipped files and was being silently bundled (contradicting the 0.0.50 externalization), risking version skew against the external `zod`/`react-hook-form`.
81
+ - Docs: AGENTS.md webhook verification note updated to runtime-agnostic (missed in 0.0.50); stale "3 levels" comment on `TranslationKey` corrected to 4.
82
+
83
+ ### Added
84
+
85
+ - **`ui` prop on `SaaSOSProvider`** (`SDKUIConfig`): implementor-facing UI configuration. Every option is additive and defaults to current behavior; visibility options only hide UI and never bypass platform permissions.
86
+ - `ui.settings.sections` — show/hide any section of the workspace settings dialog (`profile`, `security`, `connected-agents`, `general`, `users`, `subscription`, `usage`, `credits`, `features`, `notifications`, `permissions`, `danger`). Hidden sections are removed from the sidebar (empty groups collapse) and are unreachable via deep links or `defaultSection` (the dialog falls back to the first enabled section).
87
+ - Per-screen toggles: `settings.profile.{language,country,currency,timezone}`, `settings.security.{passkeyRename,passkeyDelete}`, `settings.general.{nameEdit,iconEditor}`, `settings.users.{invite,roleChange,remove,seatPricing}`, `settings.subscription.{changePlan,cancel,managePayment,invoicesTab,planDetails}`, `settings.credits.{buyButton,transactions}`.
88
+ - `ui.workspaceSwitcher.{show,createButton,planBadge,memberCount}` — client-side switcher control, ANDed with server settings (`showSwitcher`, `canCreateWorkspace`).
89
+ - `ui.behavior.autoOpenPlanDialog` — disable the automatic plan-picker popup when a workspace has no subscription (explicit `selectPlan` deep links still open). `ui.behavior.trialEndingDays` — global default threshold for `WhenTrialEnding`.
90
+ - `ui.messages` (`PartialSDKMessages`) — per-key overrides for any SDK UI string, deep-merged over the active locale bundle (e.g. rename "Credits" to "Tokens" without forking locale files).
91
+ - `ui.errorBoundary.{title,retryLabel}` — strings for the top-level error boundary's default fallback (plain strings, since the boundary renders even when the i18n layer crashed).
92
+ - `ui.formats.date` — `Intl.DateTimeFormatOptions` for SDK-rendered dates (passkey activity, connected agents).
93
+ - **`useUIVisibility()` hook** (exported): single-call visibility decision combining a `ui` config flag with an optional permission check — `visible(ui => ui.settings?.users?.invite, Permission.WORKSPACE_MEMBERS_INVITE)`. Used internally by every gated SDK surface; available to implementors for their own UI.
94
+ - **`useUIConfig()` hook** (exported): raw access to the provided `ui` config. **`mergeUIConfig(base, override)`** (exported): the deep-merge used for per-dialog overrides.
95
+ - **Per-dialog `ui` override**: `WorkspaceSettingsDialog` accepts its own `ui` prop, deep-merged over the provider-global config, so one app can render differently-configured settings dialogs.
96
+ - **Notifications screen toggles**: `settings.notifications.{push,emailToggles,pushToggles}` hide the browser push block or the per-event email/push preference columns (the preference list collapses when both columns are hidden).
97
+ - **Docs**: new `docs/UI-CONFIG.md` — full UI-configuration guide (complete toggle reference, visibility precedence, per-dialog override, recipes); README "UI Configuration" section links to it.
98
+ - **Auth gates get standard gate props**: `WhenAuthenticated` / `WhenUnauthenticated` now accept `loadingComponent` and `fallbackComponent`, matching the subscription/quota/credit gates. Loading now covers all transitional auth states (`loading`, `redirecting`, `authenticating`).
99
+ - **i18n English fallback chain**: `t()` now falls back to the English bundle for keys missing from an incomplete locale (or overrides) before returning the raw key.
100
+ - **`SDKErrorBoundary`** accepts `errorTitle` and `retryLabel` props for its default fallback UI.
101
+
102
+ ## [0.0.50] - 2026-07-07
103
+
104
+ ### Fixed
105
+
106
+ - **Webhook verification now works on every JS runtime**: `verifyWebhookSignature` / `parseWebhookEvent` previously used Node's `require('crypto')`, which was bundled into the ESM output and threw (silently caught → returned `false`, rejecting valid webhooks) on ESM Node, Cloudflare Workers, Vercel Edge, Deno, and Bun. They now use the dependency-free pure-JS HMAC-SHA256 (`src/lib/sha256.ts`, shared with the OAuth app-bridge), so verification behaves identically under CJS, ESM, bundlers, edge runtimes, Deno, Bun, and browsers. Added `hexToBytes` to `sha256`.
107
+ - **`safeRedirect` no longer throws off-browser**: it is exported from the framework-agnostic core entry but used unguarded `window`. It now guards `window` and no-ops (returns `false`) on non-browser runtimes, returning `true` only when it navigates.
108
+ - **`AbortSignal.any` compatibility**: request cancellation + timeout composition used `AbortSignal.any`, unavailable on Node <18.17 and older browsers/Deno. A feature-detected fallback (`combineAbortSignals`) restores support on those runtimes.
109
+ - **Clearer error when no `fetch` is available** (old Node without an injected `fetch`), instead of a cryptic `.bind` crash.
110
+
111
+ ### Changed
112
+
113
+ - **Dependencies are no longer bundled**: the build now externalizes all runtime dependencies (`zod`, `react-hook-form`, `@radix-ui/*`, `lucide-react`, …) instead of inlining them. This removes duplicate installs and bundle bloat and fixes duplicate-instance interop bugs (e.g. `zod` schema `instanceof`, `react-hook-form`/React context sharing) where a bundled copy diverged from the consumer's copy.
114
+ - **React entry code-splits again**: switched to multi-chunk directory output so the lazy i18n locale bundles and the Settings/Subscription dialogs load on demand (they were being inlined into a single chunk).
115
+ - **Packaging**: added a `typesVersions` map so legacy `moduleResolution: "node"` consumers resolve types for the `/react`, `/data`, and `/css` subpaths; added a `"./package.json"` export. Webhook docs updated from "Node.js only" to runtime-agnostic.
116
+
8
117
  ## [0.0.49] - 2026-07-07
9
118
 
10
119
  ### Added