Skip to content

Releases: buildbase-app/sdk

Release v0.0.57

Choose a tag to compare

@github-actions github-actions released this 16 Jul 12:22

Changed

  • CI maintenance. Removed the redundant pull-request CI workflow (.github/workflows/ci.yml); tag-triggered build/release remains in .github/workflows/main.yml. No SDK runtime changes.

Release v0.0.56

Choose a tag to compare

@github-actions github-actions released this 16 Jul 09:53

Added

  • Devices & sessions management. New DevicesApi (list/rename/signOut/forget) and SessionsApi (list/revoke) with IDeviceView/ISessionView/IIpInfoLite view types; the BuildBase server factory exposes them as bb.devices / bb.sessions. The SDK sends a persisted x-device-id header so the "current device" flag and server-side device binding work correctly.
  • React: useDevices() / useSessions() headless hooks and <Devices/> / <Sessions/> components. The components parse Browser · OS, show location + IP, use colored destructive actions, and gate "forget" behind a confirm dialog. A new "Devices & sessions" workspace settings screen (with sidebar entry) hosts them.
  • Per-action configuration. Each action can be shown/hidden and its label overridden via component props or provider ui.settings.devices (rename/signOut/forget/sessions/sessionSignOut). Props win over provider config; both default to visible.
  • i18n across all 8 locales (ICU plurals with correct CLDR categories) and unit coverage for device-display + API contracts (18 tests). README section added.

Release v0.0.54

Choose a tag to compare

@github-actions github-actions released this 16 Jul 09:29

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 ⚠️.

Added

  • 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.*.
  • 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.
  • 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.
  • AgentReadyConfig.scopes — an app scope catalog (AppScope[], { name, description }) that drives scopes_supported in your RFC 9728 protected-resource metadata. Scopes are app-owned.
  • More discovery builders / paths: buildLlmsFullTxt (/llms-full.txt), buildWebBotAuthDirectory (JWKS at /.well-known/http-message-signatures-directory, opt-in), buildMcpDiscoveryManifest (SEP-1960 /.well-known/mcp.json), buildDnsAidRecords (suggested _agents DNS records to publish at your DNS provider), and config.extraPaths (serve any literal document by exact path — commerce discovery x402/UCP/ACP/MPP, /openapi.json, future well-knowns; takes precedence over built-ins).
  • Per-framework guidedocs/MCP-AND-AGENT-READINESS.md: the complete flows (cold-start, mint, verify), the scope/resource model, and copy-paste recipes for Next.js (App + Pages Router), Express, Fastify, Hono, Bun, Deno, Cloudflare Workers, and React/SPA (WebMCP), plus a production checklist.

Changed

  • MCP Server Card is now SEP-1649 v1.0. ⚠️ Output shape change. /.well-known/mcp/server-card.json now emits $schema, version: "1.0", protocolVersion: "2025-06-18", transport: { type, url } (was transport.endpoint), and boolean capabilities ({ tools: true, resources: false, prompts: false }; a legacy { tools: {} }-style object is normalized). /.well-known/mcp.json (SEP-1960) gains $schema + version: "1.0" and a per-server transport object. McpServerCard gains an optional protocolVersion.
  • CORS is on by default on createMcpHandler. ⚠️ Behavior change. Responses now carry Access-Control-Allow-Origin: * (safe for a Bearer-token API — no cookies) and OPTIONS is answered automatically, so browser MCP clients work with zero config. New cors option: cors: [origins] narrows to those origins (reflected), cors: false disables. allowedOrigins, when set, still restricts and drives reflection.
  • buildbaseAuth audience binding accepts multiple resources and takes an explicit resourceMetadataUrl; createAgentStack binds both the canonical <host>/mcp and the literal endpoint URL and points the 401 challenge at /.well-known/oauth-protected-resource/mcp.

Security

  • API retries are restricted to idempotent methods. ⚠️ Behavior change. Automatic retries (network error / 5xx) now apply only to GET/HEAD/OPTIONS/PUT/DELETE. POST/PATCH are never replayed — the server may have processed a request whose response was lost, so replaying purchase_credits, consume_credits, or create_checkout_session risked double-charging. The retry backoff also respects the abort signal now (an unmount or timeout ends the wait immediately).
  • Open-redirect hardening in validateRedirectUrl / safeRedirect. Relative paths (/dashboard) are now accepted (protocol-relative //host and backslash /\host forms are not); new RedirectValidationOptions ({ sameOrigin, allowedOrigins }) restricts absolute URLs; safeRedirect validates its fallback URL too. ⚠️ Auth return URLs (saveAuthIntent/consumeAuthIntent) are now same-origin only — a tampered localStorage intent can no longer redirect off-site. Stripe/OAuth cross-origin https: targets are unaffected by default. Also fixed: the http://[::1] localhost allowance never matched (URL.hostname includes the brackets).
  • verifyClientJwt pins the JWT alg before computing the HMAC (was checked after) — hardens the verifier against algorithm-confusion if it is ever extended beyond HS256.

Fixed

  • Zero-decimal currencies (JPY, KRW, …) format correctly. formatCents(1000, 'jpy') now renders ¥1,000 (was ¥10.00 — a ÷100 on a currency with no minor unit). Fixed across formatCents, formatOverageRate, formatOverageRateWithLabel, formatQuotaWithPrice, and getQuotaDisplayParts; new isZeroDecimalCurrency(currency) and minorAmountToDisplay(amount, currency) are exported.
  • Stale-response races in the workspace data hooks. All 15 read hooks (useSubscription, useAllQuotaUsage, useCreditBalance, usePlanGroup, useInvoices, useUsageLogs, usePublicPlans, and the rest of the subscription/credit read surface) now drop superseded responses — a workspace switch can no longer render the previous workspace's subscription, quotas, credits, or invoices. Only the latest request clears loading; unmount aborts.
  • useSaaSSettings no longer loses org settings for the session under React StrictMode. The deduplicated global settings fetch was bound to the first caller's abort signal, so StrictMode's throwaway first mount aborted it for every waiter with no retry. The shared fetch is now detached — its result lands in the global store regardless of any one component's lifetime.
  • UserProvider state split per resource. Attributes and features now have independent loading/error (attributesLoading/featuresLoading, attributesError/featuresError; the combined isLoading/error remain for back-compat) — a features failure no longer surfaces as an attributes error, stale errors clear when a new request starts, and useUserFeatures().isLoading/error now reflect the features pipeline only, as documented.
  • createBBUrl throws on an invalid explicit base URL instead of silently substituting https://localhost (which sent Stripe success/cancel URLs to localhost with no error). The localhost default remains only for the no-argument server-side case.
  • createWorkspace's delayed plan-picker open is cancelled on unmount — it can no longer pop open after the user navigated away or signed out during the 300ms delay.
  • workspaceSettingsManager.clearParams() notifies subscribers (React state no longer diverges from the manager), and getState() returns a stable snapshot reference.

Removed

  • verifyClientJwt's legacy bare-number third argument. ⚠️ Behavior change. The third parameter is now VerifyClientJwtOptions only ({ clockToleranceSec?, requireExp?, issuer?, audience? }); pass { clockToleranceSec: n } instead of a bare number.

Internal

  • Centralized the duplicated base64url codec into src/lib/base64url.ts (was copied in agent-bridge.ts and agent-auth.ts).
  • Test infrastructure (vitest): 113 tests across sha256/HMAC vectors, webhook verification, agent-bridge JWT (round-trip, alg-confusion, exp/nbf, issuer/audience pinning, header-injection escaping), agent-auth session crypto (Node-crypto compatibility both directions), agent stack, permissions revoke/unknown-role semantics, redirect validation, URL params, API retry idempotency + abortable backoff, zero-decimal currency math — plus a packaging test asserting every value declared in each entry point's .d.ts exists in the runtime bundle (would have auto-caught the 0.0.51 /react phantom-exports bug). @types/node added; test files are now typechecked (tsconfig.json no longer excludes them; the build still does via tsconfig.build.json).

Release v0.0.53

Choose a tag to compare

@github-actions github-actions released this 16 Jul 09:28

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.

Security

  • 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'.
  • 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.
  • 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).
  • 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).
  • 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.
  • 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.
  • 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).
  • 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.

Added

  • 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).
  • 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.
  • Public model types exported from @buildbase/sdk and /react: IUser, IWorkspace, ISettings, TranslationKey (previously appeared in exported signatures but weren't name-exported).
  • 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).
  • 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.
  • 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.
  • 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.

Fixed

  • 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.
  • 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.
  • 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.
  • Palette token. focus:ring-red-600focus:ring-destructive; the lint:tokens guard was widened to also catch ring/outline/from/via/to/fill/stroke/shadow palette classes.

Changed

  • lucide-react bound to >=0.544.0 <1 — 0.x releases rename/remove icons; the previous open range let consumers pull a breaking minor.

Release v0.0.51

Choose a tag to compare

@github-actions github-actions released this 16 Jul 09:27

Fixed

  • 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]:).

  • 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.

  • 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.

  • Switch thumb mirrors correctly in RTL locales (Arabic) — the checked state used a physical translate-x that moved the wrong way.

  • CommandInput wrapper attribute aligned to the canonical cmdk-input-wrapper so CommandDialog's scoped selectors apply.

  • @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).

  • 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.

  • 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.

  • 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.

  • Invoices tab no longer claims an active subscription while loading. subscription?.subscription !== null was true for undefined (initial load); now != null.

  • 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).

  • @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.

  • Docs: AGENTS.md webhook verification note updated to runtime-agnostic (missed in 0.0.50); stale "3 levels" comment on TranslationKey corrected to 4.

Added

  • 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.
    • 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).
    • 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}.
    • ui.workspaceSwitcher.{show,createButton,planBadge,memberCount} — client-side switcher control, ANDed with server settings (showSwitcher, canCreateWorkspace).
    • 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.
    • 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).
    • 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).
    • ui.formats.dateIntl.DateTimeFormatOptions for SDK-rendered dates (passkey activity, connected agents).
  • 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.
  • useUIConfig() hook (exported): raw access to the provided ui config. mergeUIConfig(base, override) (exported): the deep-merge used for per-dialog overrides.
  • 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.
  • 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).
  • 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.
  • 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).
  • 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.
  • SDKErrorBoundary accepts errorTitle and retryLabel props for its default fallback UI.