Releases: buildbase-app/sdk
Release list
Release v0.0.57
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
Added
- Devices & sessions management. New
DevicesApi(list/rename/signOut/forget) andSessionsApi(list/revoke) withIDeviceView/ISessionView/IIpInfoLiteview types; the BuildBase server factory exposes them asbb.devices/bb.sessions. The SDK sends a persistedx-device-idheader 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
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 discoveryLinkheader. Returns{ mcp, mcpEndpoint, config, linkHeader, resolvePath, serveAgentPath, routes }— Next.js wiresexport const { GET, POST, DELETE, OPTIONS } = agent.routes+export const GET = agent.serveAgentPath. Everything derived is overridable viamcp.*/discovery.*.- Auth presets — the app mints & verifies its own tokens with pure local crypto; the platform never sees the secret.
mintAgentToken({ claims, secret })(drop intohandleAppTokenRequest.mintToken— HS256 with your secret,audfrom the granted RFC 8707 resource, per-user BuildBase session embedded as an encryptedsid),buildbaseAuth({ secret, resource, requireAudience })(theauthconfig forcreateMcpHandler— verify + audience binding +siddecrypt + derivedresourceMetadataUrl;resourceacceptsstring | string[]), andcreateSessionRefCrypto(secret)(the underlying AES-256-GCMsidcrypto, WebCrypto, wire-formatbase64url(iv|tag|ciphertext), keySHA-256(secret+":bb-session")— byte-compatible with a NodecreateCipherivlayout). Also exported from the core entry.MCP_AUTH_DEBUG=1logs received-vs-expectedaudon rejection. - A2A Agent Card at
/.well-known/agent-card.json(buildA2AAgentCard) — served by default, derived fromsite+skills;a2aCard: falsedisables, an object overrides. Advertised on the Agent Card capabilities and the discoveryLinkheader. AgentReadyConfig.scopes— an app scope catalog (AppScope[],{ name, description }) that drivesscopes_supportedin 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_agentsDNS records to publish at your DNS provider), andconfig.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 guide —
docs/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.jsonnow emits$schema,version: "1.0",protocolVersion: "2025-06-18",transport: { type, url }(wastransport.endpoint), and booleancapabilities({ 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-servertransportobject.McpServerCardgains an optionalprotocolVersion. - CORS is on by default on
createMcpHandler.⚠️ Behavior change. Responses now carryAccess-Control-Allow-Origin: *(safe for a Bearer-token API — no cookies) andOPTIONSis answered automatically, so browser MCP clients work with zero config. Newcorsoption:cors: [origins]narrows to those origins (reflected),cors: falsedisables.allowedOrigins, when set, still restricts and drives reflection. buildbaseAuthaudience binding accepts multiple resources and takes an explicitresourceMetadataUrl;createAgentStackbinds both the canonical<host>/mcpand 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 replayingpurchase_credits,consume_credits, orcreate_checkout_sessionrisked 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//hostand backslash/\hostforms are not); newRedirectValidationOptions({ sameOrigin, allowedOrigins }) restricts absolute URLs;safeRedirectvalidates 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-originhttps:targets are unaffected by default. Also fixed: thehttp://[::1]localhost allowance never matched (URL.hostnameincludes the brackets). verifyClientJwtpins the JWTalgbefore 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 acrossformatCents,formatOverageRate,formatOverageRateWithLabel,formatQuotaWithPrice, andgetQuotaDisplayParts; newisZeroDecimalCurrency(currency)andminorAmountToDisplay(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 clearsloading; unmount aborts. useSaaSSettingsno 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.UserProviderstate split per resource. Attributes and features now have independent loading/error (attributesLoading/featuresLoading,attributesError/featuresError; the combinedisLoading/errorremain for back-compat) — a features failure no longer surfaces as an attributes error, stale errors clear when a new request starts, anduseUserFeatures().isLoading/errornow reflect the features pipeline only, as documented.createBBUrlthrows on an invalid explicit base URL instead of silently substitutinghttps://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), andgetState()returns a stable snapshot reference.
Removed
verifyClientJwt's legacy bare-numberthird argument.⚠️ Behavior change. The third parameter is nowVerifyClientJwtOptionsonly ({ 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 inagent-bridge.tsandagent-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.tsexists in the runtime bundle (would have auto-caught the 0.0.51/reactphantom-exports bug).@types/nodeadded; test files are now typechecked (tsconfig.jsonno longer excludes them; the build still does viatsconfig.build.json).
Release v0.0.53
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
builtinToolsnow defaults to'readonly', not'all'.⚠️ Behavior change. AcreateMcpHandler({ buildbase })with no explicitbuiltinToolspreviously exposed the entire surface — includingdelete_workspace,cancel_subscription,purchase_credits,consume_credits— to any tokenauth.verifyaccepted. It now exposes reads only (least privilege); opt into writes withbuiltinTools: 'all'or an explicit{ include }list. If you relied on the old default, addbuiltinTools: 'all'.- Scope gating no longer bypassed by scopeless tokens.
visibleToolstreatedscopes: undefinedas "grant everything" — a JWT minted without ascopeclaim received every tool, including custom tools declaringrequiredScopes. 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: []andscopes: undefinedbehave identically. - Built-in
update_workspace/update_user_profileno longer accept arbitrary fields. Both tookdata: z.record(z.string(), z.unknown())forwarded to the backendas 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). verifyClientJwtrequires a numericexpby default. A JWT with no expiry was accepted and never expired. NewVerifyClientJwtOptions(passable as the 3rd arg, which still also accepts a bareclockToleranceSecnumber):requireExp(defaulttrue), plus optionalissuer/audiencepinning (RFC 7519).bearerChallengeescapesWWW-Authenticatevalues.resource_metadata/error/error_descriptionare quoted-string-escaped and CR/LF-stripped, closing a header-injection vector whenerrorDescriptionis fed dynamic text.buildRobotsTxt/buildSitemapsanitize 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.resolvePermissionsused 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 inheritingmemberpermissions; only an explicitly-configured, knownsettings.workspace.defaultRoleis honored as a fallback. Owners are unaffected (still all permissions). - A dead session (401) is now cleared.
useWorkspaceApiWithOs'sonUnauthorizedonly calledonSessionExpired; it now also runsremoveSession()+ dispatchesauthActions.removeSession(), so gated UI can't keep rendering "authenticated" behind an invalid token.
Added
- MCP CORS / preflight. With
allowedOriginsset,OPTIONSreturns 204 withAccess-Control-*headers and successful responses echoAccess-Control-Allow-Originfor a matching Origin — browser-based MCP clients now work. NoallowedOrigins→ 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 reachesonError.- Public model types exported from
@buildbase/sdkand/react:IUser,IWorkspace,ISettings,TranslationKey(previously appeared in exported signatures but weren't name-exported). createMcpHandlerDoS controls.maxRequestBytes(default 1 MiB) rejects oversized bodies with 413 before parsing — and before the stream is read on thefetchadapter (viaContent-Length); set0to disable.rateLimit(auth, req)gate runs after auth and before dispatch, returningfalseor{ 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/resolveAgentPathnow also serve the authorization-server metadata at/.well-known/openid-configuration(same document as/.well-known/oauth-authorization-server), and the Agent Card advertisesopenid_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 viaAbortControllerand fail soft, so a hung BuildBase server can't hang discovery routes forever.onErrorcoverstools/listschema conversion. A tool whose zod schema can't convert to JSON Schema no longer throws an unhandled 500 out ofhandle; it's reported viaonErrorand served a permissive object schema.
Fixed
useConnectedAgents().refresh()guards against stale writes. The list fetch now passes anAbortSignal, 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) — nowrole="button"+tabIndex+ Enter/Space handling +aria-label. The subscription tabs hadrole="tab"/tablistwithout the rest of the pattern — now complete:id/aria-controls/rovingtabIndex/arrow-key navigation, with realrole="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-previewalttext. - Palette token.
focus:ring-red-600→focus:ring-destructive; thelint:tokensguard was widened to also catchring/outline/from/via/to/fill/stroke/shadowpalette classes.
Changed
lucide-reactbound 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
Fixed
-
Combobox dropdowns (language/country/currency/timezone) looked disabled and ignored clicks. The shared
CommandItemstyled items with the presence-baseddata-[disabled]:selector, but cmdk ≥1.0 always rendersdata-disabled="true|false"— so every option gotopacity-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 (newdropdowns.noResultskey in all 8 locales) instead of a blank panel. -
Dropdown triggers now match the Input styling (same
rounded-mdradius, 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-xthat moved the wrong way. -
CommandInputwrapper attribute aligned to the canonicalcmdk-input-wrappersoCommandDialog's scoped selectors apply. -
@buildbase/sdk/reactnow actually exports the core runtime values its types declared.src/react.tsusedexport type * from './core', but the generateddist/react/index.d.tsflattened it into value exports — soimport { Permission, formatCents, AuthStatus, … } from '@buildbase/sdk/react'(as documented in the README) type-checked and then wasundefinedat 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.
BaseApiconverted everyAbortErrorinto a fake "Request timeout" error whenever a timeout was configured (i.e. always, default 30s), defeatingisAbortError()filtering — component-unmount cancellations surfaced as real errors. Conversion now happens only when the internal timeout controller actually fired. -
unwrapResponseno longer swallows falsy payloads.result.data || resultreturned the whole{success, data}envelope whendatawas0,false,'', ornull; 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.apiPathtagged 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 !== nullwastrueforundefined(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/resolversmoved 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 externalzod/react-hook-form. -
Docs: AGENTS.md webhook verification note updated to runtime-agnostic (missed in 0.0.50); stale "3 levels" comment on
TranslationKeycorrected to 4.
Added
uiprop onSaaSOSProvider(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 ordefaultSection(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 (explicitselectPlandeep links still open).ui.behavior.trialEndingDays— global default threshold forWhenTrialEnding.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.date—Intl.DateTimeFormatOptionsfor SDK-rendered dates (passkey activity, connected agents).
useUIVisibility()hook (exported): single-call visibility decision combining auiconfig 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 provideduiconfig.mergeUIConfig(base, override)(exported): the deep-merge used for per-dialog overrides.- Per-dialog
uioverride:WorkspaceSettingsDialogaccepts its ownuiprop, 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/WhenUnauthenticatednow acceptloadingComponentandfallbackComponent, 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. SDKErrorBoundaryacceptserrorTitleandretryLabelprops for its default fallback UI.