@buildbase/sdk 0.0.54 → 0.0.56

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 (60) hide show
  1. package/AGENTS.md +8 -4
  2. package/CHANGELOG.md +55 -0
  3. package/LICENSE +21 -0
  4. package/README.md +294 -47
  5. package/dist/css/styles.css +1 -1
  6. package/dist/index.cjs +1 -1
  7. package/dist/index.d.ts +367 -88
  8. package/dist/index.mjs +1 -1
  9. package/dist/mcp/index.cjs +1 -1
  10. package/dist/mcp/index.d.ts +332 -16
  11. package/dist/mcp/index.mjs +1 -1
  12. package/dist/react/chunks/SettingsDialog-CvoDs5eM.cjs +2 -0
  13. package/dist/react/chunks/SettingsDialog-VaOW2jtS.mjs +2 -0
  14. package/dist/react/chunks/SubscriptionDialog-8CagFA_V.cjs +2 -0
  15. package/dist/react/chunks/SubscriptionDialog-C5EgpXE0.mjs +2 -0
  16. package/dist/react/chunks/ar-CGbuXEeR.cjs +2 -0
  17. package/dist/react/chunks/ar-DnShbYvQ.mjs +2 -0
  18. package/dist/react/chunks/de-C3iuiLAz.mjs +2 -0
  19. package/dist/react/chunks/de-a1mX2rR4.cjs +2 -0
  20. package/dist/react/chunks/es-D4T7YE7x.cjs +2 -0
  21. package/dist/react/chunks/es-u9vKMeaX.mjs +2 -0
  22. package/dist/react/chunks/fr-Bnnz2yJZ.cjs +2 -0
  23. package/dist/react/chunks/fr-DjgpP202.mjs +2 -0
  24. package/dist/react/chunks/hi-B8Mjr8rU.cjs +2 -0
  25. package/dist/react/chunks/hi-Cu_9iCjb.mjs +2 -0
  26. package/dist/react/chunks/ja-B0eCVKdZ.cjs +2 -0
  27. package/dist/react/chunks/ja-DfJ5WUrG.mjs +2 -0
  28. package/dist/react/chunks/react-BBY-Vuzr.cjs +2 -0
  29. package/dist/react/chunks/react-CvU8uS25.mjs +2 -0
  30. package/dist/react/chunks/zh-BnPZ0j4w.cjs +2 -0
  31. package/dist/react/chunks/zh-JjoMmHmK.mjs +2 -0
  32. package/dist/react/index.cjs +1 -1
  33. package/dist/react/index.d.ts +728 -142
  34. package/dist/react/index.mjs +1 -1
  35. package/docs/ARCHITECTURE.md +419 -0
  36. package/docs/ERROR_CODES.md +96 -0
  37. package/docs/MCP-AND-AGENT-READINESS.md +893 -0
  38. package/docs/THEMING.md +67 -0
  39. package/docs/UI-CONFIG.md +207 -0
  40. package/package.json +14 -3
  41. package/dist/react/chunks/SettingsDialog-B28WJsAr.cjs +0 -2
  42. package/dist/react/chunks/SettingsDialog-blhppbNm.mjs +0 -2
  43. package/dist/react/chunks/SubscriptionDialog-BTWVX3cH.mjs +0 -2
  44. package/dist/react/chunks/SubscriptionDialog-QKmwvJU1.cjs +0 -2
  45. package/dist/react/chunks/ar-1Z1MOGAP.mjs +0 -2
  46. package/dist/react/chunks/ar-VGzBv3AZ.cjs +0 -2
  47. package/dist/react/chunks/de-BjdDg60Y.cjs +0 -2
  48. package/dist/react/chunks/de-CXUItplO.mjs +0 -2
  49. package/dist/react/chunks/es-BBPsSZUp.cjs +0 -2
  50. package/dist/react/chunks/es-BTbzVutz.mjs +0 -2
  51. package/dist/react/chunks/fr-Cax48X_S.cjs +0 -2
  52. package/dist/react/chunks/fr-DPMeOQu3.mjs +0 -2
  53. package/dist/react/chunks/hi-BJqvpG6d.cjs +0 -2
  54. package/dist/react/chunks/hi-JfJx-LV7.mjs +0 -2
  55. package/dist/react/chunks/ja-CDGtLzTk.mjs +0 -2
  56. package/dist/react/chunks/ja-DvVyj5xX.cjs +0 -2
  57. package/dist/react/chunks/react-6QJCOE-D.cjs +0 -2
  58. package/dist/react/chunks/react-CC6T7Zky.mjs +0 -2
  59. package/dist/react/chunks/zh-BAcqxkxk.cjs +0 -2
  60. package/dist/react/chunks/zh-BNZ2Ooe8.mjs +0 -2
package/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md — `@buildbase/sdk`
2
2
 
3
- Machine-friendly reference for AI coding agents working **with** this SDK (in a consumer app) or **on** this repo. Dense by design. For prose, examples, and props tables see [README.md](./README.md); for internals see [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) and [docs/ERROR_CODES.md](./docs/ERROR_CODES.md).
3
+ Machine-friendly reference for AI coding agents working **with** this SDK (in a consumer app) or **on** this repo. Dense by design. For prose, examples, and props tables see [README.md](https://unpkg.com/@buildbase/sdk/README.md); for internals see [docs/ARCHITECTURE.md](https://unpkg.com/@buildbase/sdk/docs/ARCHITECTURE.md) and [docs/ERROR_CODES.md](https://unpkg.com/@buildbase/sdk/docs/ERROR_CODES.md).
4
4
 
5
5
  BuildBase is a multi-tenant SaaS platform SDK: auth, workspaces, subscriptions/billing (Stripe), quotas/usage, credits, feature flags, RBAC, notifications, i18n, and server-side agent (OAuth2 / discovery) tooling.
6
6
 
@@ -34,6 +34,8 @@ import { SaaSOSProvider } from '@buildbase/sdk/react';
34
34
  </SaaSOSProvider>;
35
35
  ```
36
36
 
37
+ `BuildBaseProvider` is the brand-aligned alias of `SaaSOSProvider` (same component/props; prefer it in new code).
38
+
37
39
  Everything below `SaaSOSProvider` can use the hooks and gate components. **Prefer SDK hooks over reaching into internal context/Redux.**
38
40
 
39
41
  ## Server setup
@@ -111,16 +113,18 @@ Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err,
111
113
 
112
114
  **Full guide: `docs/MCP-AND-AGENT-READINESS.md`** (mental model, fast path, auth, scope/resource customization).
113
115
 
114
- Expose a live MCP server (stateless Streamable HTTP; Node 18+/edge) with built-in BuildBase tools + custom zod-schema tools. **Spec-compliant to MCP 2025-06-18**: server card SEP-1649 v1.0 (`$schema`, `version:"1.0"`, `protocolVersion`, `transport:{type,url}`, boolean caps), discovery manifest SEP-1960, auth via RFC 9728→8414→8707+PKCE. The platform issues **public clients** (RFC 7591 `token_endpoint_auth_method:"none"`, no secret, PKCE-only) so Claude Desktop/Cursor/ChatGPT/Inspector connect with zero manual config.
116
+ Expose a live MCP server (stateless Streamable HTTP; Node 18+/edge) with built-in BuildBase tools + custom zod-schema tools, plus opt-in **resources** and **prompts**. **Spec-compliant to MCP 2025-11-25** (negotiates down to 2024-11-05; invalid tool args → `isError` tool results per SEP-1303; optional `icons` per SEP-973): server card SEP-1649 v1.0 (`$schema`, `version:"1.0"`, `protocolVersion`, `transport:{type,url}`, boolean caps), discovery manifest SEP-1960, auth via RFC 9728→8414→8707+PKCE. The platform issues **public clients** (RFC 7591 `token_endpoint_auth_method:"none"`, no secret, PKCE-only) so Claude Desktop/Cursor/ChatGPT/Inspector connect with zero manual config.
115
117
 
116
- - **Handler:** `createMcpHandler({ buildbase?, auth, tools?, builtinTools?, context?, serverInfo?, instructions?, allowedOrigins?, maxRequestBytes?, rateLimit?, formatToolError?, onError? })` → `{ handle(McpHttpRequest), fetch(Request), listTools(), serverCard({ endpoint }) }`. `fetch(Request)` is the Web adapter (Next.js App Router, Hono, Bun, Deno, Cloudflare Workers, edge); `handle({method,headers,body})` is the pure form (Express, Fastify, Next.js Pages Router, Node http). Per-framework copy-paste recipes + mint route + production checklist in `docs/MCP-AND-AGENT-READINESS.md`. `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.
118
+ - **Handler:** `createMcpHandler({ buildbase?, auth, tools?, resources?, resourceTemplates?, builtinResources?, prompts?, builtinTools?, context?, serverInfo?, instructions?, allowedOrigins?, cors?, maxRequestBytes?, rateLimit?, formatToolError?, onError? })` → `{ handle(McpHttpRequest), fetch(Request), listTools(), serverCard({ endpoint }) }`. `fetch(Request)` is the Web adapter (Next.js App Router, Hono, Bun, Deno, Cloudflare Workers, edge); `handle({method,headers,body})` is the pure form (Express, Fastify, Next.js Pages Router, Node http). Per-framework copy-paste recipes + mint route + production checklist in `docs/MCP-AND-AGENT-READINESS.md`. `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.
119
+ - **Tool results:** return anything (string → text block; object → JSON text + `structuredContent`) or a wire-shaped `McpToolResult` for full control — mixed content blocks (`text`/`image`/`audio`/`resource_link`/embedded `resource`) + `isError`; helpers `mcpText`/`mcpImage`/`mcpAudio`/`mcpResourceLink`/`mcpEmbeddedResource`.
120
+ - **Resources & prompts (opt-in; capabilities advertised only when configured):** `builtinResources: true` = lean context catalog (`buildbase://profile`, `buildbase://workspaces`, `buildbase://workspace/{workspaceId}` template; richer data stays tools-only); custom via `defineMcpResource`/`defineMcpResourceTemplate` (`read` → string | object (JSON) | `{text}`/`{blob}`; level-1 `{var}` URI templates) and `defineMcpPrompt` (`get` → string | `McpPromptMessage[]`; required-arg validation). `requiredScopes` gates all three primitives identically; under-scoped = not-found (`-32002`). Not implemented by design (stateless transport, no push channel): subscriptions, `listChanged`, sampling, elicitation.
117
121
  - **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.
118
122
  - **One-config stack:** `createAgentStack({ serverUrl, orgId, siteUrl, site, secret, mcp?, discovery? })` → `{ mcp, mcpEndpoint, config, linkHeader, resolvePath, serveAgentPath, routes }` — derives the MCP endpoint, server card, RFC 9728 protected resource, API catalog entry, BuildBase client, `buildbaseAuth`, and CORS from ONE object. Next.js wiring: `export const { GET, POST, DELETE, OPTIONS } = agent.routes` for `/api/mcp`, `export const GET = agent.serveAgentPath` for discovery routes. Everything derived is overridable via `mcp.*` / `discovery.*`.
119
123
  - **BuildBase auth presets** (pure local crypto — the app's secret and tokens NEVER reach the platform): `mintAgentToken({ claims, secret, expiresInSec?, extraClaims? })` (drop into `handleAppTokenRequest.mintToken`; signs HS256 with YOUR secret, binds `aud` to the RFC 8707 resource, embeds the per-user session as an encrypted `sid` claim) and `buildbaseAuth({ secret, resource?, resourceMetadataUrl?, requireAudience?, verify? })` (the `auth` config for `createMcpHandler`: verify + RFC 8707 audience binding + `sid` decryption + derived `resourceMetadataUrl`; `resource` accepts `string|string[]`; `MCP_AUTH_DEBUG=1` logs received-vs-expected `aud` on rejection). `createSessionRefCrypto(secret)` exposes the underlying AES-256-GCM `sid` crypto (WebCrypto; wire format `base64url(iv|tag|ciphertext)`, key `SHA-256(secret+":bb-session")` — byte-compatible with Node `createCipheriv` implementations of the same layout).
120
124
  - **Scopes & resources (restrict or open up) — ALL app-owned; the shared AS is scope/resource-agnostic:** THREE scope levers — (1) declare a catalog `scopes:[{name,description}]` on `AgentReadyConfig`/`createAgentStack` → `scopes_supported` in the app's OWN RFC 9728 protected-resource metadata (served from the app origin); (2) gate per custom tool via `requiredScopes` (hidden/refused unless the token carries all — YOUR scope names); (3) the floor — every tool runs under the user's session so the platform enforces the user's real permissions (agent ⊆ user). Built-ins are restricted by `builtinTools`, not scopes. TWO resource levers — declare `protectedResources` (RFC 9728: stack derives root + `<host>/mcp` + endpoint) and bind the token `aud` via `buildbaseAuth({ resource, requireAudience })` (RFC 8707) — the audience check at YOUR resource is the whole gate (token for A rejected at B); the AS just passes `resource` through opaquely (no per-org scope/resource lists, no `invalid_target`). Defaults are least-privilege; widen deliberately. RFC 9728 convention: canonical MCP resource is `<host>/mcp` (metadata at `/.well-known/oauth-protected-resource/mcp`), 401 `WWW-Authenticate` points there.
121
125
  - **CORS:** on by default (`Access-Control-Allow-Origin: *` — safe for a Bearer-token API; reflects `allowedOrigins` when set). Narrow with `cors: [origins]` or disable with `cors: false`. Preflight answered automatically — never wrap the route in your own CORS.
122
126
  - **Auth helpers re-exported:** `signClientJwt`, `verifyClientJwt`, `extractBearerToken`, `bearerChallenge`, `AppBridgeError`.
123
- - Types: `CreateMcpHandlerConfig`, `McpHandler`, `McpToolDefinition`, `McpToolContext`, `McpAuthInfo`, `McpHttpRequest`, `McpHttpResponse`, `McpToolAnnotations`, `McpBuildBaseClient`, `BuiltinMcpToolName`, `AgentStack`, `AgentStackConfig`, `BuildBaseAuthOptions`, `MintAgentTokenOptions`, `SessionRefCrypto`.
127
+ - Types: `CreateMcpHandlerConfig`, `McpHandler`, `McpToolDefinition`, `AnyMcpTool`, `McpToolContext`, `McpToolResult`, `McpContentBlock` (+ per-block types), `McpIcon`, `McpResourceDefinition`, `McpResourceTemplateDefinition`, `McpResourceReadResult`, `McpPromptDefinition`, `McpPromptMessage`, `McpPromptContent`, `McpAuthInfo`, `McpHttpRequest`, `McpHttpResponse`, `McpToolAnnotations`, `McpBuildBaseClient`, `BuiltinMcpToolName`, `AgentStack`, `AgentStackConfig`, `BuildBaseAuthOptions`, `MintAgentTokenOptions`, `SessionRefCrypto`, `VerifiedJwtPayload`.
124
128
 
125
129
  ## Server actions cheat-sheet
126
130
 
package/CHANGELOG.md CHANGED
@@ -7,12 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.55] - 2026-07-14
11
+
12
+ ### Added
13
+
14
+ - **"Connect an agent" flow on the Connected Agents screen.** Pass `mcp={{ url, name?, docsUrl?, clients?, prompt? }}` to `SaaSOSProvider` and the screen gains a **"Connect an agent" button** (in the header and the empty state) that opens a plain-language setup dialog written for non-technical users. The dialog has three parts: (1) the copyable **server address**, (2) a copy-and-paste **prompt** for chat assistants ("paste this into your AI"), and (3) step-by-step setup for the **major AI apps** in an accordion — ChatGPT, Claude, Cursor, VS Code, Windsurf, and Cline by default, each with the exact click-path or config-file snippet (`{{url}}`/`{{name}}`/`{{key}}` are substituted). Override the app list via `mcp.clients`, customize or hide the prompt via `mcp.prompt` (`false` hides it). The action lives in the header so it stays reachable no matter how long the agent list is, and never pushes the list around. Hide the whole thing with `<ConnectedAgents showConnectGuide={false} />`. **Open it programmatically** with the existing settings entry point — `openWorkspaceSettings('connected-agents')` navigates to the screen, and `openWorkspaceSettings('connected-agents', { action: 'openConnectGuide' })` (or the URL deep link `?bb=action:openConnectGuide`) lands straight on the setup dialog (new `BBAction.OpenConnectGuide`). New public surface on `/react`: `useMcpConnection()`, the standalone `<ConnectMcpGuide />`, and the `McpConnectionConfig`/`McpClientGuide` types. Internally: a `CopyField` primitive (inline/code/text variants) and a Radix-backed `Accordion` (adds `@radix-ui/react-accordion`, externalized like the other Radix deps). Sixteen i18n keys across all 8 locales; renders nothing when no `mcp.url` is set (fully backward-compatible).
15
+
16
+ - **MCP protocol 2025-11-25.** The handler negotiates the latest revision (down to 2024-11-05): invalid tool arguments now return an `isError` tool result with the validation issues in-band instead of a protocol error (SEP-1303 — models can self-correct); tools/resources/prompts accept optional `icons` metadata (SEP-973); the server card advertises `protocolVersion: "2025-11-25"`.
17
+ - **MCP resources** (`resources/list`/`read`/`templates/list`). Opt-in `builtinResources: true` serves a deliberately small BuildBase context catalog — `buildbase://profile`, `buildbase://workspaces`, `buildbase://workspace/{workspaceId}` (template) — and `resources`/`resourceTemplates` config (with `defineMcpResource`/`defineMcpResourceTemplate`) serves your own, with the same `requiredScopes` gating as tools. `read` returns a string, any object (JSON), or `{ text }`/`{ blob }`. Capabilities in `initialize` and the server card are truthful — advertised only when configured.
18
+ - **MCP prompts** (`prompts/list`/`get`). App-defined workflow templates via `prompts` config + `defineMcpPrompt`; required-argument validation per spec; `get` returns a string or full `McpPromptMessage[]`. No built-in prompts — they're app-specific.
19
+ - **MCP tool results support all spec content types.** Tools can return a wire-shaped `McpToolResult` — mixed content blocks (`text`, `image`, `audio`, `resource_link`, embedded `resource`), `structuredContent`, and `isError` — passed through verbatim; any other return value keeps the legacy auto-wrap (string → text block, object → JSON text + `structuredContent`). New helpers `mcpText`/`mcpImage`/`mcpAudio`/`mcpResourceLink`/`mcpEmbeddedResource` and the block types are exported from `/mcp`. Detection is strict (every entry must be a valid block), so existing tools returning plain data — even with a `content` key — behave exactly as before.
20
+ - **`AnyMcpTool`** — exported existential alias for heterogeneous tool lists (`McpToolDefinition<any, any>`); registries and config arrays use it instead of scattering `any`.
21
+ - **`VerifiedJwtPayload`** — `verifyClientJwt` (and `buildbaseAuth`'s internals) now return RFC 7519-typed claims with everything else `unknown` instead of `Record<string, any>`. ⚠️ Type-level change: custom claims must be narrowed before use (`typeof c.sid === 'string'`) — they're attacker-influenced input even after the signature verifies. Also fixed: a JWT whose payload segment is valid JSON but not an object (`"null"`, `"42"`) now fails with a clean `invalid_token` error instead of a `TypeError`.
22
+ - **Every `WorkspaceApi` method accepts a trailing `signal?: AbortSignal`** (49 methods) — matching the Auth/User/Settings API classes, so callers can cancel in-flight workspace/billing/credit requests.
23
+ - **`formatMinorAmountIntl(amount, currency, locale?)`** — locale-aware `Intl.NumberFormat` money formatting of minor-unit amounts, zero-decimal-safe (used by the invoices screen; exported for consumers).
24
+ - **Settings subscription screen renders `<NoPermission/>`** instead of a blank pane when the user lacks `workspace:billing:view` (and the permission check runs before the loading skeleton).
25
+ - **`NotificationData`/`ISettings` extra keys are typed `unknown`** instead of `any` — narrow before use. ⚠️ Type-level change for code that read arbitrary keys untyped. **`WebMcpTool` is now generic** (`WebMcpTool<TInput = any>`): inline-typed `execute` handlers keep working, and consumers can pin the input shape (`WebMcpTool<{ path: string }>`); verified against the reference webapp.
26
+ - **`BuildBaseProvider` / `BuildBaseProviderProps`** — brand-aligned aliases of `SaaSOSProvider`/`SaaSOSProviderProps` on `/react` (identical component; prefer in new code).
27
+ - **`npm run lint:i18n`** — new guard (wired into `npm run lint`) that fails when a non-English locale value is byte-identical to the English source; genuine cognates ("Plan" in de/es/fr, "Logo"/"Name" in de, …) live in an allowlist in `scripts/check-i18n.mjs`. TypeScript enforces key parity but can't catch English text pasted into a locale file — this can.
28
+ - **`hardLimit` label is now part of the quota/overage label sets.** `QuotaLabels.hardLimit?` (quota-utils) and `DEFAULT_OVERAGE_LABELS.hardLimit` (currency-utils) — `formatQuotaWithPrice` and `formatQuotaIncludedOverage` no longer hardcode English "(hard limit)"; pass a translated label like the other entries. New trailing optional `locale` param on `formatOverageRate`, `formatOverageRateWithLabel`, and `formatQuotaIncludedOverage` groups numbers (`1,000` vs `1.000`) in the display locale instead of the runtime default.
29
+
30
+ ### Changed
31
+
32
+ - **State contexts are backed by `useSyncExternalStore` — selector bailout is now real.** ⚠️ **Behavior change (performance).** `useAuthSelector`/`useOSSelector`/`useWorkspaceSelector`/`useAppSelector` previously re-rendered every consumer on every dispatch (the docs claimed otherwise); state now lives in a stable per-provider store and a consumer re-renders only when its selected value changes (`Object.is` or the custom `equalityFn`). Pass a stable selector (module-level or memoized) for the bailout to hold across renders. Hook APIs, state shapes, and dispatch semantics are unchanged.
33
+ - **Workspace lifecycle moved into a real `WorkspaceProvider` (mounted by `SaaSOSProvider`).** ⚠️ **Behavior change.** The storage restore, request dedup, switch versioning, and current-workspace sync/fallback previously ran per `useSaaSWorkspaces()` instance — mounting the hook in several components caused duplicate `getWorkspaces` fetches and **multiple `onWorkspaceChange` / `workspace:changed` fires per switch** (implementors mint tokens there). All of it now runs exactly once in the provider; the hook is a thin selector + actions consumer with an unchanged return shape. Nesting the provider is a safe no-op.
34
+ - **Hook return shapes follow the `{ data, loading, error, refetch }` convention.** ⚠️ **Breaking-ish (deprecated aliases kept).** `useUserAttributes()` now returns per-resource `{ attributes, loading, error, refetch, updateAttributes, updateAttribute }` (it previously exposed the whole user context with a combined `isLoading`); `useUserFeatures()` returns `{ features, loading, error, refetch, isFeatureEnabled }`. The old `isLoading`/`refreshAttributes`/`refreshFeatures` names still work but are `@deprecated`. `useSaaSSettings()` now also returns `{ loading, error, refetch }` — and the workspace switcher waits for settings instead of guessing with `?? true` defaults, so orgs configured without a switcher no longer flash one on load.
35
+ - **Signed JWTs with mistyped registered claims are rejected.** ⚠️ **Behavior change.** `verifyClientJwt` now runtime-validates the RFC 7519 registered claims after signature verification — a validly-signed token whose `sub`/`iss`/`jti` is not a string, whose `exp`/`nbf`/`iat` is not a finite number, or whose `aud` is not a string/string-array throws `invalid_token` instead of surfacing lying types to consumers. `buildbaseAuth` also drops non-string entries from an array `scope` claim instead of casting.
36
+ - **The last two response-envelope outliers now follow the standard `fetchUnwrapped` convention.** ⚠️ **Behavior/type change.** `AuthApi.requestAuth` no longer resolves with the raw `{ success, data, message }` wire envelope — it unwraps like every other method and resolves with the payload (`{ redirectUrl }`); the exported `AuthRequestResponse` type now describes that payload (read `result.redirectUrl`, not `result.data.redirectUrl`). For both `requestAuth` and `WorkspaceApi.selectFreePlan`, a 2xx envelope with `success: false` now **throws** a coded Error carrying the server's `message` instead of resolving silently — remove any manual `if (!result.success)` checks. `selectFreePlan`'s resolved shape is unchanged (`{ success, message }` — this endpoint's envelope carries no `data` payload, and on resolution `success` is always `true`).
37
+
38
+ ### Fixed
39
+
40
+ - **MCP: an under-scoped exact resource no longer falls through to a matching template (scope bypass).** In `resources/read`, when a fixed resource existed at the requested URI but the token lacked its `requiredScopes`, the handler tried the resource _templates_ next — a laxer-scoped template matching the same URI (e.g. `app://secret/{id}` shadowing `app://secret/data`) served content the exact resource's scope gate was supposed to protect. The exact resource now owns its URI outright: a scope failure returns `-32002` (indistinguishable from not-found, per the no-existence-oracle rule) and templates are never consulted. Regression-tested with under-scoped tokens across `resources/list`, `resources/read` (exact + template), `resources/templates/list`, `prompts/list`, and `prompts/get`.
41
+ - **MCP: `resources/read` with a missing or non-string `uri` returns `-32602` (Invalid params)** instead of conflating the malformed request with `-32002` resource-not-found.
42
+ - **Plan-picker table cells show the correct currency when no pricing variant matches.** Four `SubscriptionDialog` cell computations (features/limits/quotas/per-seat) skipped the currency-match guard the card/header use — when a plan had pricing variants but none matched the workspace's effective currency, cells labeled base-currency values with the effective currency's symbol. All cells now share `getDisplayCurrency`.
43
+ - **Full ISO 4217 minor-unit support.** New `getCurrencyDecimals(currency)` (exported from `/core`) resolves a currency's minor-unit digits — 0 (JPY, KRW, …), 3 (KWD, BHD, OMR, TND, JOD — ÷1000, per Stripe), or the runtime's CLDR data for anything else (IQD/LYD → 3, CLF/UYW → 4); ISK is pinned to 2 to match Stripe. All central formatters (`formatCents`, `minorAmountToDisplay`, `formatMinorAmountIntl`, quota-utils) now divide by the correct factor instead of assuming ÷100. Also fixed: `formatMinorAmountIntl` was documented as exported but missing from `/core`; `getQuotaDisplayParts` could throw a `RangeError` for currencies whose Intl fraction digits differ from the forced minimum.
44
+ - **~290 untranslated locale values translated.** Three key blocks added after the last locale audit were English in all 7 non-English locale files: the entire `notifications.*` preferences screen (categories + all 24 event names/descriptions), the `subscription.items` credit labels, and the credit purchase dialog (`credits.buyAmount`/`loadMore`/`dialogTitle`/`dialogDescription`/`dialogDescriptionWorkspace`/`perCredit`). Now translated in ar/de/es/fr/hi/ja/zh following each file's existing terminology and register (de du-form "Guthaben", zh 您 "积分", …). The new `lint:i18n` prevents a recurrence.
45
+ - **Last hardcoded English strings now go through `t()`.** Dialog's screen-reader-only close label (`settings.common.close`), `FullScreenLoader`'s default message (`settings.common.loading`), error fallbacks in `useConnectedAgents` (`security.connectedAgentsLoadFailed`/`RevokeFailed`) and `useSaaSWorkspaces` (`errors.fetchWorkspaces`) via the standard `getHookErrorMessage`, the features screen's missing-name fallback (`features.fallbackName`), and `useConsumeCredits`' insufficient-credits detail (`credits.insufficientDetail` with `{available}`/`{requested}`). Six new keys in types.ts + all 8 locales. The push service-worker template's `'Notification'` title fallback is a documented exception — service workers run outside the translation provider.
46
+ - **`fmtCents` (i18n) and the usage screen are zero-decimal-safe too.** `useTranslation().fmtCents` — used by the plan dialog, seat pricing, credit packages, and subscription screens — now delegates to `formatMinorAmountIntl` instead of always dividing by 100 (a JPY plan stored as `1000` rendered `¥10.00`). `SettingsUsage` overage rates/estimates dropped their local symbol-pasting formatter and raw `/100` math for the same central helper. All money display now flows through `currency-utils`; unknown currency codes fall back to the symbol table instead of a bare number.
47
+ - **Remaining hardcoded English UI strings translated.** The workspace preview image `alt` on the general settings screen now uses `t('general.previewImageAlt')`, and `CommandSelect`'s default `placeholder`/`emptyLabel` resolve to `t('dropdowns.search')`/`t('dropdowns.chooseOption')` instead of English literals (explicit props still override).
48
+ - **Usage screen counts are compact in every locale.** The quota cards' local `formatNumber` pasted Latin `K`/`M` suffixes onto localized digits; it now uses `Intl.NumberFormat(locale, { notation: 'compact' })`, so compact counts follow the display locale (`1.2M` in en, `1,2 Mio.` in de, `12.3万` in ja).
49
+ - **Standalone hard-limit label is capitalized again.** `formatQuotaIncludedOverage` with no included count and `allowOverage: false` rendered lowercase "hard limit" since the `hardLimit` label joined the default label set — the standalone default is back to "Hard limit" while the parenthesized mid-sentence form stays lowercase. Callers passing `labels.hardLimit` are unaffected; regression tests added.
50
+ - **Switching locale no longer aborts and refetches list data.** `useConnectedAgents.refresh` and `useSaaSWorkspaces.fetchWorkspaces` had `t` in their dependency arrays (for translated error fallbacks), so a locale switch recreated them — aborting in-flight requests and flashing loading states. `t` now lives in a ref; error-message behavior is unchanged.
51
+ - **Sub-cent credit packages no longer show "$0.00 per credit".** The per-credit price was rounded to whole minor units; when a package's per-credit amount is below one minor unit, the dialog now shows a fractional value with just enough precision (e.g. `$0.0040`), zero-decimal-safe via `getCurrencyDecimals`. Amounts of one minor unit or more render exactly as before.
52
+ - **`AGENTS.md` doc links resolve on npmjs.com.** The relative `README.md`/`docs/*` links (404 on the registry page) now use the same `unpkg.com/@buildbase/sdk/…` form as the README's doc links.
53
+
54
+ ### Internal (structure only — rendered output unchanged)
55
+
56
+ - **`SettingsSubscription.tsx` decomposed (1,473 → 1,040 lines)** into `ui/subscription/`: `SubscriptionStatusBadge`, `TrialBanner`, a single `SubscriptionNoticeBanner` primitive behind the four notice banners, `PlanDetailsSection`, `CancelSubscriptionDialog`/`ResumeSubscriptionDialog`.
57
+ - **`SubscriptionDialog.tsx` decomposed (1,356 → 1,249 lines)** into `ui/subscription-dialog/`: shared `getDisplayCurrency`/`getCreditRenewalModeKey`/`getAllSubscriptionItems` utils plus `PlanTrialBadge`, `PlanPriceBlock`, `CreditGrantSummary` for the byte-identical mobile/desktop pieces.
58
+ - **`useSaaSWorkspaces()` return object is memoized** — stable identity between renders (values unchanged), so consumer memo/effect dependencies stop re-firing on every render.
59
+ - **Duplicate definitions consolidated**: `SettingsNotifications` imports `NotificationEvent` from `workspace-api` instead of re-declaring it; `SettingsSecurity` uses the shared `format-utils.formatDate` (same rendered dates — the screen's `{ dateStyle: 'medium' }` default is now passed explicitly); `WorkspaceLike` is defined once in `workspace-utils` and extended (with `permissions?`) in `lib/permissions` — exported shapes unchanged; the subscription dialog's `getDisplayCurrency` delegates its variant matching to the shared `getPricingVariant` instead of re-implementing it (UI fallback semantics preserved).
60
+ - **`lint:i18n` locale loader hardened**: strips all import statements (not just the first), tolerates `as const`/`satisfies` export suffixes, and reports the failing file name instead of a bare eval stack when a catalog can't be evaluated.
61
+ - **`trimTrailingSlash` defined once** (agent-discovery exports it; agent-stack imports it), `agent-stack`'s `tools` config uses the `AnyMcpTool` alias, and the discovery-path `fetchWithTimeout` documents why it deliberately diverges from `api-utils.fetchWithTimeout`. The agent-stack test suite stubs `discovery.fetch`, so no test performs real network I/O (the protected-resource test previously hit a 5s timeout on machines where the fake hostname hangs).
62
+
10
63
  ## [0.0.54] - 2026-07-10
11
64
 
12
65
  MCP + agent-readiness brought to **MCP 2025-06-18** compliance, a one-config setup path, first-class auth presets, and a full per-framework guide. **Behavior changes** are marked ⚠️.
13
66
 
14
67
  ### Added
15
68
 
69
+ - **`UserContextValue` per-resource state**: `attributesLoading` / `featuresLoading` / `attributesError` / `featuresError` alongside the combined `isLoading`/`error`.
70
+ - **`RedirectValidationOptions`** exported from the core entry.
16
71
  - **`createAgentStack(config)` — the whole MCP + agent-readiness surface from one config object** (`@buildbase/sdk/mcp`). Derives the MCP handler, the SEP-1649 server card, the RFC 9728 protected-resource metadata (root + canonical `<host>/mcp` + endpoint), the API-catalog entry, the BuildBase client, `buildbaseAuth`, CORS, and the discovery `Link` header. Returns `{ mcp, mcpEndpoint, config, linkHeader, resolvePath, serveAgentPath, routes }` — Next.js wires `export const { GET, POST, DELETE, OPTIONS } = agent.routes` + `export const GET = agent.serveAgentPath`. Everything derived is overridable via `mcp.*` / `discovery.*`.
17
72
  - **Auth presets — the app mints & verifies its own tokens with pure local crypto; the platform never sees the secret.** `mintAgentToken({ claims, secret })` (drop into `handleAppTokenRequest.mintToken` — HS256 with your secret, `aud` from the granted RFC 8707 resource, per-user BuildBase session embedded as an encrypted `sid`), `buildbaseAuth({ secret, resource, requireAudience })` (the `auth` config for `createMcpHandler` — verify + audience binding + `sid` decrypt + derived `resourceMetadataUrl`; `resource` accepts `string | string[]`), and `createSessionRefCrypto(secret)` (the underlying AES-256-GCM `sid` crypto, WebCrypto, wire-format `base64url(iv|tag|ciphertext)`, key `SHA-256(secret+":bb-session")` — byte-compatible with a Node `createCipheriv` layout). Also exported from the core entry. `MCP_AUTH_DEBUG=1` logs received-vs-expected `aud` on rejection.
18
73
  - **A2A Agent Card** at `/.well-known/agent-card.json` (`buildA2AAgentCard`) — served **by default**, derived from `site` + `skills`; `a2aCard: false` disables, an object overrides. Advertised on the Agent Card capabilities and the discovery `Link` header.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Buildbase
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.