@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/README.md CHANGED
@@ -7,6 +7,7 @@ Also works server-side (Next.js API routes, Express, Hono) โ€” see [Server-Side
7
7
  ## ๐Ÿ“‘ Table of Contents
8
8
 
9
9
  - [Features](#-features)
10
+ - [Customization Map](#-customization-map)
10
11
  - [Installation](#installation)
11
12
  - [Quick Start](#quick-start)
12
13
  - [UI Configuration](#๏ธ-ui-configuration)
@@ -19,6 +20,8 @@ Also works server-side (Next.js API routes, Express, Hono) โ€” see [Server-Side
19
20
  - [Trial Gates](#-trial-gates)
20
21
  - [Push Notifications](#-push-notifications)
21
22
  - [Notifications](#-notifications)
23
+ - [Connected Agents](#-connected-agents)
24
+ - [Devices & Sessions](#-devices--sessions)
22
25
  - [User Management](#-user-management)
23
26
  - [Workspace Management](#-complete-workspace-management)
24
27
  - [Public Pricing (No Login)](#-public-pricing-no-login)
@@ -52,6 +55,8 @@ Also works server-side (Next.js API routes, Express, Hono) โ€” see [Server-Side
52
55
  - **โณ Trial Gates** - `WhenTrialing`, `WhenNotTrialing`, `WhenTrialEnding` components + `useTrialStatus` hook
53
56
  - **๐Ÿ”” Push Notifications** - Browser push notifications with `usePushNotifications` hook, auto-triggers for billing events, and campaign management
54
57
  - **๐Ÿ“ฌ Notifications** - Email + push notification system with per-event channel control, workspace preferences, and server-side `notification.send()` API
58
+ - **๐Ÿ”Œ Connected Agents** - `<ConnectedAgents />` screen to list/revoke authorized AI agents, plus a built-in "Connect an agent" setup guide (ChatGPT, Claude, Cursor, VS Code, Windsurf, Cline) driven by an `mcp` provider prop
59
+ - **๐Ÿ“ฑ Devices & Sessions** - `<Devices />` and `<Sessions />` screens (Browser ยท OS, location + IP) to rename / sign out / remove devices and revoke sessions; server-side `bb.devices` / `bb.sessions`, per-action show/hide + label overrides
55
60
  - **๐Ÿ’บ Seat-Based Pricing** - Per-seat billing with included seats, billable seat tracking, and seat limit enforcement
56
61
  - **๐Ÿ’ฑ Multi-Currency** - Per-currency pricing variants with workspace billing currency lock
57
62
  - **๐Ÿค Affiliate Tracking** - Pass referral data to Stripe checkout via `getCheckoutStripeParams` prop (Rewardful, Endorsely, FirstPromoter, etc.)
@@ -66,6 +71,32 @@ Also works server-side (Next.js API routes, Express, Hono) โ€” see [Server-Side
66
71
  - **๐ŸŒ Internationalization (i18n)** - 8 locales (en, es, fr, de, ja, zh, hi, ar), ICU MessageFormat, RTL support, native numerals
67
72
  - **๐Ÿ  Workspace Modes** - Personal (solo B2C) or Platform (multi-user B2B), configured from admin dashboard
68
73
 
74
+ ## ๐ŸŽจ Customization Map
75
+
76
+ Everything the SDK lets you configure, override, or extend โ€” one row per knob, so you can see the whole surface before reading any deep-dive section. The philosophy throughout: **safe defaults, everything overridable, the SDK accelerates but never gates.**
77
+
78
+ | What you customize | How | Where to read more |
79
+ | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
80
+ | Provider setup & workspace behavior | `<SaaSOSProvider>` / `<BuildBaseProvider>` props (`serverUrl`, `orgId`, `locale`, workspace auto-create, redirects) | [Configuration Reference](#๏ธ-configuration-reference) |
81
+ | Which SDK UI renders at all | `ui` prop: hide/show settings sections, workspace switcher, per-screen toggles | [UI Configuration](#๏ธ-ui-configuration) ยท `docs/UI-CONFIG.md` |
82
+ | Every user-facing string | `ui.messages` deep-override (e.g. rename "Subscription" โ†’ "Billing"), per-locale | [UI Configuration](#๏ธ-ui-configuration) |
83
+ | Language & number systems | `locale` prop โ€” 8 locales, ICU plurals, RTL (`ar`), native numerals | [Internationalization](#-internationalization-i18n) |
84
+ | Look & feel | CSS custom properties (`--primary`, `--destructive`, โ€ฆ) โ€” semantic tokens, light/dark, no palette lock-in | `docs/THEMING.md` |
85
+ | Date/number formats | `ui.formats` (`Intl` options passed through) | `docs/UI-CONFIG.md` |
86
+ | Conditional UI | Gate components: `WhenAuthenticated`, `WhenPermission`, `WhenWorkspaceFeatureEnabled`, `WhenSubscription`, `WhenTrialing`, `WhenQuotaAvailable`, `WhenCreditsAvailable`, โ€ฆ | [Subscription Gates](#-subscription-gates) & siblings |
87
+ | Checkout behavior | `getCheckoutStripeParams` (affiliate/referral params), plan-picker behavior config | [Affiliate Tracking](#affiliate--referral-tracking) |
88
+ | Connect-an-agent guide | `mcp` prop (`url`, `name`, `docsUrl`, `clients`, `prompt`) โ†’ in-app setup guide + `<ConnectedAgents />` screen | [Connected Agents](#-connected-agents) |
89
+ | Devices & sessions screens | `<Devices />` / `<Sessions />` props (`showRename`/`showSignOut`/`showRemove`, `*Label`) or `ui.settings.devices` toggles | [Devices & Sessions](#-devices--sessions) |
90
+ | Error presentation | `ui.errorBoundary`, centralized `handleError` reporting | [Error Handling](#๏ธ-error-handling) |
91
+ | React-free server access | `BuildBase()` factory โ€” workspaces, usage, credits, notifications from API routes/jobs | [Server-Side Usage](#server-side-usage) |
92
+ | Agent discovery surface | `createAgentStack` / `resolveAgentPath`: robots.txt, llms.txt, `.well-known/*`, all documents overridable (e.g. `config.llmsTxt`) | [Agent Readiness](#agent-readiness-discovery) ยท `docs/MCP-AND-AGENT-READINESS.md` |
93
+ | MCP tools | `builtinTools` (`'readonly'`/`'all'`/`false`/include/exclude) + your own via `defineMcpTool` (custom tools override built-ins) | [MCP Server](#mcp-server-buildbasesdkmcp) |
94
+ | MCP resources & prompts | Opt-in `builtinResources` catalog + `defineMcpResource`/`defineMcpResourceTemplate`/`defineMcpPrompt` | [MCP Server](#mcp-server-buildbasesdkmcp) |
95
+ | MCP auth & hardening | `auth.verify` (your token format) or `buildbaseAuth` preset; `rateLimit`, `cors`, `allowedOrigins`, `formatToolError` | `docs/MCP-AND-AGENT-READINESS.md` |
96
+ | Per-request context for your MCP tools | `context(auth, req)` factory โ†’ `ctx.custom` (your DB, services) | [MCP Server](#mcp-server-buildbasesdkmcp) |
97
+ | Money display | `formatMinorAmountIntl`/`formatCents`/`getCurrencyDecimals` โ€” full ISO 4217 (0/2/3-decimal currencies) | [Multi-Currency](#-multi-currency--pricing-utilities) |
98
+ | Webhooks from the platform | `verifyWebhookSignature` (HMAC, timing-safe) | [Webhook Verification](#webhook-verification) |
99
+
69
100
  ## Installation
70
101
 
71
102
  ```bash
@@ -87,6 +118,7 @@ import '@buildbase/sdk/css';
87
118
  // components/provider.tsx
88
119
  'use client';
89
120
 
121
+ // BuildBaseProvider is an identical brand-aligned alias โ€” prefer it in new code
90
122
  import { SaaSOSProvider } from '@buildbase/sdk/react';
91
123
  import { ApiVersion } from '@buildbase/sdk';
92
124
 
@@ -224,6 +256,7 @@ The optional `ui` prop on `SaaSOSProvider` controls which parts of the SDK UI ar
224
256
  // Per-screen feature toggles
225
257
  profile: { currency: false, timezone: false },
226
258
  security: { passkeyDelete: false },
259
+ devices: { forget: false, sessions: false }, // hide "Remove" + the sessions block
227
260
  general: { iconEditor: false },
228
261
  users: { invite: false, seatPricing: false },
229
262
  subscription: { cancel: false, invoicesTab: false, planDetails: false },
@@ -248,13 +281,13 @@ The optional `ui` prop on `SaaSOSProvider` controls which parts of the SDK UI ar
248
281
  // Default fallback strings for the top-level error boundary
249
282
  errorBoundary: { title: 'Oops!', retryLabel: 'Retry' },
250
283
 
251
- // Date formatting for SDK-rendered dates (passkeys, connected agents)
284
+ // Date formatting for SDK-rendered dates (passkeys, connected agents, devices)
252
285
  formats: { date: { dateStyle: 'short' } },
253
286
  }}
254
287
  >
255
288
  ```
256
289
 
257
- Section keys match the `SettingsScreen` values: `profile`, `security`, `connected-agents`, `general`, `users`, `subscription`, `usage`, `credits`, `features`, `notifications`, `permissions`, `danger`. Hidden sections are removed from the sidebar (empty groups collapse) and unreachable via deep links or `defaultSection` โ€” the dialog falls back to the first enabled section.
290
+ Section keys match the `SettingsScreen` values: `profile`, `security`, `devices`, `connected-agents`, `general`, `users`, `subscription`, `usage`, `credits`, `features`, `notifications`, `permissions`, `danger`. Hidden sections are removed from the sidebar (empty groups collapse) and unreachable via deep links or `defaultSection` โ€” the dialog falls back to the first enabled section.
258
291
 
259
292
  **Per-dialog override** โ€” `WorkspaceSettingsDialog` accepts its own `ui` prop, deep-merged over the global config, so one app can render differently-configured dialogs:
260
293
 
@@ -283,7 +316,7 @@ function MembersPanel() {
283
316
 
284
317
  `useUIConfig()` gives you the raw `ui` object if you only need the config values; `mergeUIConfig(base, override)` is the deep-merge used for per-dialog overrides.
285
318
 
286
- ๐Ÿ“– **Full guide with the complete toggle reference, precedence rules, and recipes (single-tenant, external billing, white-label, read-only): [docs/UI-CONFIG.md](./docs/UI-CONFIG.md)**
319
+ ๐Ÿ“– **Full guide with the complete toggle reference, precedence rules, and recipes (single-tenant, external billing, white-label, read-only): [docs/UI-CONFIG.md](https://unpkg.com/@buildbase/sdk/docs/UI-CONFIG.md)**
287
320
 
288
321
  ## ๐Ÿ” Authentication
289
322
 
@@ -346,9 +379,16 @@ openWorkspaceSettings('users'); // Workspace members
346
379
  openWorkspaceSettings('subscription'); // Plan & Billing
347
380
  openWorkspaceSettings('usage'); // Quota usage dashboard
348
381
  openWorkspaceSettings('features'); // Feature toggles
382
+ openWorkspaceSettings('connected-agents'); // Connected AI agents + connect guide
349
383
  openWorkspaceSettings('danger'); // Delete workspace (owner only)
350
384
  ```
351
385
 
386
+ Some sections accept an optional second `action` argument to deep-link past the screen. For connected agents, open the setup guide directly:
387
+
388
+ ```tsx
389
+ openWorkspaceSettings('connected-agents', { action: 'openConnectGuide' });
390
+ ```
391
+
352
392
  ### Authentication Components
353
393
 
354
394
  For declarative rendering, use the conditional components:
@@ -504,7 +544,9 @@ Use the `useUserFeatures` hook to check feature flags programmatically:
504
544
  import { useUserFeatures } from '@buildbase/sdk/react';
505
545
 
506
546
  function FeatureCheck() {
507
- const { features, isFeatureEnabled, refreshFeatures } = useUserFeatures();
547
+ // { features, isFeatureEnabled, loading, error, refetch }
548
+ // (the older `isLoading` / `refreshFeatures` names still work but are deprecated)
549
+ const { features, isFeatureEnabled, refetch } = useUserFeatures();
508
550
 
509
551
  return (
510
552
  <div>{isFeatureEnabled('premium-features') ? <PremiumContent /> : <StandardContent />}</div>
@@ -1074,6 +1116,142 @@ Platform mode supports granular overrides from the admin dashboard:
1074
1116
 
1075
1117
  These let you achieve team-like, managed, or enterprise-like behavior without a separate mode.
1076
1118
 
1119
+ ## ๐Ÿ”Œ Connected Agents
1120
+
1121
+ Let users connect AI agents to their account and manage them. `<ConnectedAgents />` is a ready-made screen (also a workspace settings section) that lists the agents a user has authorized โ€” each with a **Disconnect** action โ€” and, when you provide an MCP server address, shows a **Connect an agent** button that opens a plain-language setup guide.
1122
+
1123
+ ### Enable the connect guide
1124
+
1125
+ Pass an `mcp` config to the provider. Without it the screen still lists and revokes agents; with it, the "Connect an agent" button and setup dialog appear.
1126
+
1127
+ ```tsx
1128
+ <SaaSOSProvider
1129
+ // ...serverUrl, orgId, auth
1130
+ mcp={{
1131
+ url: 'https://app.example.com/api/mcp', // your MCP server endpoint (required to show the guide)
1132
+ name: 'Acme', // friendly name used in prose + config snippets
1133
+ docsUrl: 'https://docs.example.com/agents', // optional "Learn more" link
1134
+ }}
1135
+ >
1136
+ ```
1137
+
1138
+ The dialog walks users through the major AI apps โ€” **ChatGPT, Claude, Cursor, VS Code, Windsurf, Cline** โ€” each with the exact click-path or config-file snippet (your server URL is filled in for them), plus a copy-and-paste prompt for chat assistants. It's fully translated (all 8 locales), RTL-aware, and responsive (full-screen on mobile, centered card on desktop).
1139
+
1140
+ ### The screen
1141
+
1142
+ ```tsx
1143
+ import { ConnectedAgents } from '@buildbase/sdk/react';
1144
+
1145
+ function AgentsPage() {
1146
+ return <ConnectedAgents />;
1147
+ }
1148
+ ```
1149
+
1150
+ Props: `title`, `description`, `disconnectLabel`, `emptyLabel` (all default to translated strings; pass `null` to hide the heading) and `showConnectGuide` (default `true` โ€” set `false` to hide the connect button even when `mcp` is configured).
1151
+
1152
+ Because it's a settings section, you can also open it programmatically:
1153
+
1154
+ ```tsx
1155
+ const { openWorkspaceSettings } = useSaaSAuth();
1156
+
1157
+ openWorkspaceSettings('connected-agents'); // the screen (list + connect button)
1158
+ openWorkspaceSettings('connected-agents', { action: 'openConnectGuide' }); // straight to the setup dialog
1159
+ ```
1160
+
1161
+ The same deep link works via URL: `?bb=action:openConnectGuide`.
1162
+
1163
+ ### Custom UI
1164
+
1165
+ - **`useConnectedAgents()`** โ€” headless `{ agents, loading, error, revoking, refresh, revoke }` for a fully custom list.
1166
+ - **`useMcpConnection()`** โ€” the `mcp` config you passed (or `null`), for building your own guide.
1167
+ - **`<ConnectMcpGuide />`** โ€” the guide body on its own (copyable server URL + paste-in prompt + per-client accordion), embeddable anywhere. Override the app list via `mcp.clients` and the prompt via `mcp.prompt` (`false` hides it).
1168
+ - **`fillMcpTemplate` / `mcpServerKey`** โ€” the placeholder helpers (`{{url}}` / `{{name}}` / `{{key}}`) for building custom client snippets.
1169
+
1170
+ ## ๐Ÿ“ฑ Devices & Sessions
1171
+
1172
+ Let users see and manage where they're signed in. Two ready-made screens (also combined into one workspace settings section, `devices`):
1173
+
1174
+ - **`<Devices />`** โ€” the devices the user has signed in from, each showing a friendly "Browser ยท OS" line, location + IP, last-used time, and a "This device" / "Trusted" / notifications badge. Per-row actions: **Rename**, **Sign out** (revoke that device's live sessions), and **Remove** (forget the device, with a confirm dialog).
1175
+ - **`<Sessions />`** โ€” the user's currently-active sessions, each with a per-row **Sign out** (the current session is badged, not revocable here).
1176
+
1177
+ Both are session-authed and scoped to the signed-in user โ€” no config beyond wrapping your app in `<SaaSOSProvider>`. For the "This device" flag and device binding to work, the SDK automatically sends a persisted `x-device-id` header.
1178
+
1179
+ ### The screens
1180
+
1181
+ ```tsx
1182
+ import { Devices, Sessions } from '@buildbase/sdk/react';
1183
+
1184
+ function SecurityPage() {
1185
+ return (
1186
+ <div className="space-y-8">
1187
+ <Devices />
1188
+ <Sessions />
1189
+ </div>
1190
+ );
1191
+ }
1192
+ ```
1193
+
1194
+ Or open the combined settings section programmatically:
1195
+
1196
+ ```tsx
1197
+ const { openWorkspaceSettings } = useSaaSAuth();
1198
+ openWorkspaceSettings('devices'); // "Devices & sessions" screen
1199
+ ```
1200
+
1201
+ ### Show/hide actions & override labels
1202
+
1203
+ Every action can be hidden and every button relabeled โ€” an explicit prop wins, otherwise it falls back to the provider `ui.settings.devices.*` config (visible unless set to `false`).
1204
+
1205
+ ```tsx
1206
+ <Devices
1207
+ title={null} // hide the heading (like every screen; also `description`)
1208
+ showRename={false} // hide per-row actions
1209
+ showSignOut={false}
1210
+ showRemove={false}
1211
+ renameLabel="Edit" // relabel buttons (default to translated strings)
1212
+ signOutLabel="Log out"
1213
+ removeLabel="Delete"
1214
+ emptyLabel="No devices yet"
1215
+ />
1216
+
1217
+ <Sessions showSignOut={false} signOutLabel="End session" emptyLabel="No other sessions" />
1218
+ ```
1219
+
1220
+ Provider-level equivalent (applies everywhere, incl. the settings screen):
1221
+
1222
+ ```tsx
1223
+ <SaaSOSProvider ui={{ settings: {
1224
+ sections: { devices: false }, // hide the whole "Devices & sessions" settings section
1225
+ devices: {
1226
+ rename: false, // device Rename
1227
+ signOut: false, // device Sign out
1228
+ forget: false, // device Remove
1229
+ sessions: false, // the active-sessions block on the settings screen
1230
+ sessionSignOut: false // per-session Sign out
1231
+ },
1232
+ }}}>
1233
+ ```
1234
+
1235
+ ### Custom UI
1236
+
1237
+ - **`useDevices()`** โ€” headless `{ devices, loading, error, busyId, refresh, rename, signOut, forget }`.
1238
+ - **`useSessions()`** โ€” headless `{ sessions, loading, error, revoking, refresh, revoke }`.
1239
+ - **`DevicesApi` / `SessionsApi`** (+ `IDeviceView` / `ISessionView` types) for direct calls.
1240
+
1241
+ ### Server-side
1242
+
1243
+ ```ts
1244
+ import BuildBase from '@buildbase/sdk';
1245
+ const bb = BuildBase({ serverUrl, orgId, getSessionId });
1246
+
1247
+ await bb.devices.list(); // IDeviceView[]
1248
+ await bb.devices.rename(deviceId, name);
1249
+ await bb.devices.signOut(deviceId); // revoke the device's sessions
1250
+ await bb.devices.forget(deviceId); // sign out + remove the row
1251
+ await bb.sessions.list(); // ISessionView[]
1252
+ await bb.sessions.revoke(sessionId); // by the session's public handle
1253
+ ```
1254
+
1077
1255
  ## ๐Ÿ‘ค User Management
1078
1256
 
1079
1257
  ### User Attributes
@@ -1084,8 +1262,9 @@ Manage custom user attributes (key-value pairs):
1084
1262
  import { useUserAttributes } from '@buildbase/sdk/react';
1085
1263
 
1086
1264
  function UserProfile() {
1087
- const { attributes, isLoading, updateAttribute, updateAttributes, refreshAttributes } =
1088
- useUserAttributes();
1265
+ // { attributes, loading, error, refetch, updateAttribute, updateAttributes }
1266
+ // (the older `isLoading` / `refreshAttributes` names still work but are deprecated)
1267
+ const { attributes, loading, updateAttribute, updateAttributes, refetch } = useUserAttributes();
1089
1268
 
1090
1269
  const handleUpdate = async () => {
1091
1270
  // Update single attribute
@@ -1245,19 +1424,23 @@ Plans support **pricing variants** (multi-currency). Use these utilities for dis
1245
1424
 
1246
1425
  ### Currency utilities
1247
1426
 
1248
- | Export | Purpose |
1249
- | ------------------------------------ | --------------------------------------------------------- |
1250
- | `CURRENCY_DISPLAY` | Map of currency code โ†’ symbol (e.g. `usd` โ†’ `$`) |
1251
- | `CURRENCY_FLAG` | Map of currency code โ†’ flag emoji |
1252
- | `PLAN_CURRENCY_CODES` | Allowed billing currency codes (for dropdowns/validation) |
1253
- | `PLAN_CURRENCY_OPTIONS` | Options array for plan currency selects |
1254
- | `getCurrencySymbol(currency)` | Symbol for a Stripe currency code |
1255
- | `getCurrencyFlag(currency)` | Flag emoji for a currency code |
1256
- | `formatCents(cents, currency)` | Format cents as localized price string |
1257
- | `formatOverageRate(cents, currency)` | Format overage rate for display |
1258
- | `formatOverageRateWithLabel(...)` | Overage rate with optional unit label |
1259
- | `formatQuotaIncludedOverage(...)` | "X included, then $Y / unit" style text |
1260
- | `getQuotaUnitLabelFromName(name)` | Human-readable unit label from quota name |
1427
+ | Export | Purpose |
1428
+ | -------------------------------------------------- | --------------------------------------------------------------- |
1429
+ | `CURRENCY_DISPLAY` | Map of currency code โ†’ symbol (e.g. `usd` โ†’ `$`) |
1430
+ | `CURRENCY_FLAG` | Map of currency code โ†’ flag emoji |
1431
+ | `PLAN_CURRENCY_CODES` | Allowed billing currency codes (for dropdowns/validation) |
1432
+ | `PLAN_CURRENCY_OPTIONS` | Options array for plan currency selects |
1433
+ | `getCurrencySymbol(currency)` | Symbol for a Stripe currency code |
1434
+ | `getCurrencyFlag(currency)` | Flag emoji for a currency code |
1435
+ | `formatCents(cents, currency)` | Format a minor-unit amount with the symbol table |
1436
+ | `formatMinorAmountIntl(amount, currency, locale?)` | Locale-aware `Intl` money formatting of minor units |
1437
+ | `getCurrencyDecimals(currency)` | ISO 4217 minor-unit digits (0 JPY, 2 USD, 3 KWD; CLDR fallback) |
1438
+ | `isZeroDecimalCurrency(currency)` | True when the currency has no minor unit (JPY, KRW, โ€ฆ) |
1439
+ | `minorAmountToDisplay(amount, currency)` | Minor units โ†’ display number string (no symbol) |
1440
+ | `formatOverageRate(cents, currency)` | Format overage rate for display |
1441
+ | `formatOverageRateWithLabel(...)` | Overage rate with optional unit label |
1442
+ | `formatQuotaIncludedOverage(...)` | "X included, then $Y / unit" style text |
1443
+ | `getQuotaUnitLabelFromName(name)` | Human-readable unit label from quota name |
1261
1444
 
1262
1445
  ### Pricing variant utilities
1263
1446
 
@@ -1297,7 +1480,9 @@ import {
1297
1480
  const variant = getPricingVariant(planVersion, 'usd');
1298
1481
  const cents = getBasePriceCents(planVersion, 'usd', 'monthly');
1299
1482
  if (cents != null) {
1300
- console.log(getCurrencySymbol('usd') + (cents / 100).toFixed(2));
1483
+ // Never divide by 100 yourself โ€” formatCents applies the currency's ISO 4217
1484
+ // minor-unit factor (JPY ยฅ1,000 stays whole, KWD divides by 1000, USD by 100).
1485
+ console.log(formatCents(cents, 'usd')); // "$12.99"
1301
1486
  }
1302
1487
 
1303
1488
  // Quota display with overage
@@ -2067,7 +2252,7 @@ import { eventEmitter } from '@buildbase/sdk';
2067
2252
 
2068
2253
  ## ๐Ÿ›ก๏ธ Error Handling
2069
2254
 
2070
- The SDK handles errors internally: API failures, auth errors, and component errors are logged and surfaced through hook states (e.g. `error` from `useSaaSWorkspaces`) and callbacks. **SaaSOSProvider** wraps its children in an internal **SDKErrorBoundary** to catch React render errors inside the SDK tree. For app-level errors, wrap your app (or routes) in your own error boundary (e.g. Reactโ€™s `ErrorBoundary` or your frameworkโ€™s error UI). For failed async operations, check the `error` property on hooks and show user feedback (e.g. toast or inline message). See [Error codes](docs/ERROR_CODES.md) for SDK error codes and HTTP mappings.
2255
+ The SDK handles errors internally: API failures, auth errors, and component errors are logged and surfaced through hook states (e.g. `error` from `useSaaSWorkspaces`) and callbacks. **SaaSOSProvider** wraps its children in an internal **SDKErrorBoundary** to catch React render errors inside the SDK tree. For app-level errors, wrap your app (or routes) in your own error boundary (e.g. Reactโ€™s `ErrorBoundary` or your frameworkโ€™s error UI). For failed async operations, check the `error` property on hooks and show user feedback (e.g. toast or inline message). See [Error codes](https://unpkg.com/@buildbase/sdk/docs/ERROR_CODES.md) for SDK error codes and HTTP mappings.
2071
2256
 
2072
2257
  ## โš™๏ธ Settings
2073
2258
 
@@ -2077,7 +2262,11 @@ Access OS-level settings:
2077
2262
  import { useSaaSSettings } from '@buildbase/sdk/react';
2078
2263
 
2079
2264
  function SettingsExample() {
2080
- const { settings, getSettings } = useSaaSSettings();
2265
+ // { settings, loading, error, refetch } โ€” `loading` is true until the one-time
2266
+ // fetch settles, so you can wait instead of rendering with partial defaults.
2267
+ const { settings, loading, refetch } = useSaaSSettings();
2268
+
2269
+ if (loading) return <Spinner />;
2081
2270
 
2082
2271
  return (
2083
2272
  <div>
@@ -2155,9 +2344,11 @@ Prefer these SDK hooks for state and operations instead of `useAppSelector`:
2155
2344
  | `useSaaSAuth()` | Auth state (user, session, status), signIn(returnUrl?), signOut, openWorkspaceSettings |
2156
2345
  | `useSaaSWorkspaces()` | Workspaces, currentWorkspace, loading, switching/switchingToId, CRUD and switch actions |
2157
2346
  | `useSaaSOs()` | OS config (serverUrl, version, orgId, auth, settings) when you need the full config object |
2158
- | `useSaaSSettings()` | Organization settings and getSettings (prefer this when you only need settings) |
2159
- | `useUserAttributes()` | User attributes and update/refresh |
2160
- | `useUserFeatures()` | User feature flags |
2347
+ | `useSaaSSettings()` | Organization settings โ€” `{ settings, loading, error, refetch }` (prefer this when you only need settings) |
2348
+ | `useUserAttributes()` | User attributes โ€” `{ attributes, loading, error, refetch, updateAttribute, updateAttributes }` |
2349
+ | `useUserFeatures()` | User feature flags โ€” `{ features, isFeatureEnabled, loading, error, refetch }` |
2350
+ | `useConnectedAgents()` | Authorized AI agents โ€” `{ agents, loading, error, revoking, refresh, revoke }` |
2351
+ | `useMcpConnection()` | The `mcp` connection config passed to the provider (or `null`), for a custom connect guide |
2161
2352
  | `useSubscriptionContext()` | Subscription for current workspace (response, loading, refetch); use inside SubscriptionContextProvider |
2162
2353
  | `useTrialStatus()` | Trial state: `isTrialing`, `daysRemaining`, `trialEndsAt`, `isTrialEnding` |
2163
2354
  | `usePushNotifications()` | Push notification state and actions: `isSubscribed`, `subscribe()`, `unsubscribe()` |
@@ -2177,12 +2368,12 @@ Using hooks keeps your code stable if internal state shape changes and avoids di
2177
2368
 
2178
2369
  ### Types
2179
2370
 
2180
- All TypeScript types are exported for type safety. See the [TypeScript definitions](./dist/index.d.ts) for complete type information.
2371
+ All TypeScript types are exported for type safety. See the [TypeScript definitions](https://unpkg.com/@buildbase/sdk/dist/index.d.ts) for complete type information.
2181
2372
 
2182
2373
  ### Further documentation
2183
2374
 
2184
- - [Architecture](docs/ARCHITECTURE.md) โ€“ Layers, providers, APIs (BaseApi, UserApi, WorkspaceApi, SettingsApi), state, auth flow
2185
- - [Error codes](docs/ERROR_CODES.md) โ€“ SDK error codes and HTTP status mappings
2375
+ - [Architecture](https://unpkg.com/@buildbase/sdk/docs/ARCHITECTURE.md) โ€“ Layers, providers, APIs (BaseApi, UserApi, WorkspaceApi, SettingsApi), state, auth flow
2376
+ - [Error codes](https://unpkg.com/@buildbase/sdk/docs/ERROR_CODES.md) โ€“ SDK error codes and HTTP status mappings
2186
2377
 
2187
2378
  ## โš™๏ธ Configuration Reference
2188
2379
 
@@ -2935,11 +3126,11 @@ app.post('/webhooks/buildbase', (req, res) => {
2935
3126
 
2936
3127
  ## Agent Readiness (Discovery)
2937
3128
 
2938
- > **Full guide:** [`docs/MCP-AND-AGENT-READINESS.md`](docs/MCP-AND-AGENT-READINESS.md) covers the whole surface โ€” the fast path, the auth model, and the scope/resource customization spectrum. This section is the overview.
3129
+ > **Full guide:** [`docs/MCP-AND-AGENT-READINESS.md`](https://unpkg.com/@buildbase/sdk/docs/MCP-AND-AGENT-READINESS.md) covers the whole surface โ€” the fast path, the auth model, and the scope/resource customization spectrum. This section is the overview.
2939
3130
 
2940
3131
  Make a consuming app [agent ready](https://isitagentready.com) by serving the machine-readable discovery documents an AI client looks for โ€” Agent Card, A2A card, OAuth metadata (RFC 8414/9728), Agent Skills, API Catalog (RFC 9727), MCP Server Card (SEP-1649 v1.0) + discovery manifest (SEP-1960), `robots.txt` with AI-bot rules + Content Signals, `sitemap.xml`, `security.txt`, `llms.txt`/`llms-full.txt`, and `auth.md`. Framework-agnostic and zero React: every function returns a plain `DiscoveryDocument` (`{ status, contentType, body, cacheControl, vary? }`).
2941
3132
 
2942
- The fast path is [`createAgentStack`](docs/MCP-AND-AGENT-READINESS.md#fast-path--createagentstack) โ€” one config derives the MCP handler **and** the whole discovery surface. To wire the discovery layer on its own, use `resolveAgentPath`:
3133
+ The fast path is [`createAgentStack`](https://unpkg.com/@buildbase/sdk/docs/MCP-AND-AGENT-READINESS.md#fast-path--createagentstack) โ€” one config derives the MCP handler **and** the whole discovery surface. To wire the discovery layer on its own, use `resolveAgentPath`:
2943
3134
 
2944
3135
  ```ts
2945
3136
  // lib/agent-ready.ts
@@ -2948,17 +3139,17 @@ import { resolveAgentPath, type AgentReadyConfig } from '@buildbase/sdk';
2948
3139
  export const agentConfig: AgentReadyConfig = {
2949
3140
  serverUrl: process.env.BUILDBASE_URL!,
2950
3141
  orgId: process.env.BUILDBASE_ORG_ID!,
2951
- siteUrl: 'https://imejis.io',
2952
- site: { name: 'Imejis', description: 'Generate images from templates via API.' },
3142
+ siteUrl: 'https://example.com',
3143
+ site: { name: 'Acme', description: 'Generate images from templates via API.' },
2953
3144
  // Discovery content is YOURS โ€” defined here in code, not in the platform:
2954
3145
  scopes: [
2955
3146
  { name: 'designs:read', description: 'View designs' },
2956
3147
  { name: 'render:execute', description: 'Render images' },
2957
3148
  ],
2958
- llmsTxt: '# Imejis\n\n> Generate images from templates via API.',
3149
+ llmsTxt: '# Acme\n\n> Generate images from templates via API.',
2959
3150
  robots: { contentSignals: { search: true, aiInput: true, aiTrain: false } },
2960
3151
  sitemap: { urls: ['/', '/pricing'] }, // omit if you already generate sitemaps
2961
- protectedResources: [{ resource: 'https://imejis.io/mcp' }], // scopes inherited from catalog
3152
+ protectedResources: [{ resource: 'https://example.com/mcp' }], // scopes inherited from catalog
2962
3153
  // skills, security, apiCatalog, mcpServerCard, a2aCard, extraPaths โ€” all optional
2963
3154
  };
2964
3155
 
@@ -3049,9 +3240,9 @@ Clean split, no duplication: **your app owns discovery content, the platform own
3049
3240
 
3050
3241
  ## MCP Server (`@buildbase/sdk/mcp`)
3051
3242
 
3052
- > **Full guide:** [`docs/MCP-AND-AGENT-READINESS.md`](docs/MCP-AND-AGENT-READINESS.md).
3243
+ > **Full guide:** [`docs/MCP-AND-AGENT-READINESS.md`](https://unpkg.com/@buildbase/sdk/docs/MCP-AND-AGENT-READINESS.md).
3053
3244
 
3054
- Turn your app into a **live MCP server** so AI agents can operate it: the built-in tools expose your BuildBase state (workspaces, subscription, quotas, credits, feature flags, permissions) and you add your own product tools with zod schemas. Stateless Streamable HTTP (MCP 2025-06-18) โ€” pure functions plus a Web-standard `Request`/`Response` adapter, so it runs on Node 18+, edge, Deno, and Bun. Server-only, zero React; split from the core entry because it uses `zod` at runtime.
3245
+ Turn your app into a **live MCP server** so AI agents can operate it: the built-in tools expose your BuildBase state (workspaces, subscription, quotas, credits, feature flags, permissions) and you add your own product tools with zod schemas โ€” plus **resources** (context a host loads without tool calls) and **prompts** (user-facing workflow templates). Stateless Streamable HTTP (MCP 2025-11-25, negotiating down to 2024-11-05) โ€” pure functions plus a Web-standard `Request`/`Response` adapter, so it runs on Node 18+, edge, Deno, and Bun. Server-only, zero React; split from the core entry because it uses `zod` at runtime.
3055
3246
 
3056
3247
  The fast path bundles the server + the whole discovery surface from one config:
3057
3248
 
@@ -3063,8 +3254,8 @@ import { z } from 'zod';
3063
3254
  export const agent = createAgentStack({
3064
3255
  serverUrl: process.env.NEXT_PUBLIC_BUILDBASE_SERVER_URL!,
3065
3256
  orgId: process.env.NEXT_PUBLIC_BUILDBASE_ORG_ID!,
3066
- siteUrl: 'https://imejis.io',
3067
- site: { name: 'Imejis', description: 'Generate images from templates via API.' },
3257
+ siteUrl: 'https://example.com',
3258
+ site: { name: 'Acme', description: 'Generate images from templates via API.' },
3068
3259
  secret: process.env.SYSTEM_SECRET!, // derives buildbaseAuth (verify + aud + sid decrypt)
3069
3260
  scopes: [{ name: 'render:execute', description: 'Render images' }],
3070
3261
  mcp: {
@@ -3102,10 +3293,10 @@ const buildbase = BuildBase({
3102
3293
 
3103
3294
  export const mcp = createMcpHandler({
3104
3295
  buildbase,
3105
- serverInfo: { name: 'imejis', version: '1.0.0' },
3296
+ serverInfo: { name: 'acme', version: '1.0.0' },
3106
3297
  auth: buildbaseAuth({
3107
3298
  secret: process.env.SYSTEM_SECRET!,
3108
- resource: ['https://imejis.io/mcp', 'https://imejis.io/api/mcp'],
3299
+ resource: ['https://example.com/mcp', 'https://example.com/api/mcp'],
3109
3300
  requireAudience: true,
3110
3301
  }),
3111
3302
  builtinTools: 'readonly',
@@ -3123,14 +3314,14 @@ app.post('/mcp', async (req, res) => {
3123
3314
  });
3124
3315
  ```
3125
3316
 
3126
- > **Copy-paste recipes for every framework** โ€” Next.js (App + Pages Router), Express, Fastify, Hono, Bun, Deno, Cloudflare Workers, and React/SPA (WebMCP) โ€” plus the per-framework mint route and a production checklist: [`docs/MCP-AND-AGENT-READINESS.md`](docs/MCP-AND-AGENT-READINESS.md#framework-recipes). Two adapters cover them all: `mcp.fetch(Request)` (web standard โ€” App Router, Hono, Bun, Deno, edge) and `mcp.handle({method,headers,body})` (pure โ€” Express, Fastify, Pages Router).
3317
+ > **Copy-paste recipes for every framework** โ€” Next.js (App + Pages Router), Express, Fastify, Hono, Bun, Deno, Cloudflare Workers, and React/SPA (WebMCP) โ€” plus the per-framework mint route and a production checklist: [`docs/MCP-AND-AGENT-READINESS.md`](https://unpkg.com/@buildbase/sdk/docs/MCP-AND-AGENT-READINESS.md#framework-recipes). Two adapters cover them all: `mcp.fetch(Request)` (web standard โ€” App Router, Hono, Bun, Deno, edge) and `mcp.handle({method,headers,body})` (pure โ€” Express, Fastify, Pages Router).
3127
3318
 
3128
3319
  Advertise it via the server card so agents find the endpoint (the stack sets this automatically):
3129
3320
 
3130
3321
  ```ts
3131
3322
  export const agentConfig: AgentReadyConfig = {
3132
3323
  // ...
3133
- mcpServerCard: mcp.serverCard({ endpoint: 'https://imejis.io/api/mcp' }),
3324
+ mcpServerCard: mcp.serverCard({ endpoint: 'https://example.com/api/mcp' }),
3134
3325
  };
3135
3326
  ```
3136
3327
 
@@ -3159,6 +3350,62 @@ The agent acts as an authenticated app user โ€” it knows your app, not BuildBase
3159
3350
 
3160
3351
  Want per-scope gating on top? Set `requiredScopes` on your own custom tools and return the granted `scopes` from `auth.verify` โ€” the handler hides a tool from `tools/list` (and refuses the call) when its `requiredScopes` aren't all granted. A token that carries **no** scopes sees only tools that require none (the built-ins), so a token minted without a `scope` claim can never unlock a scoped tool. Opt-in, using **your** scope names โ€” the SDK never imposes its own.
3161
3352
 
3353
+ ### Rich tool results (images, links, mixed content)
3354
+
3355
+ Return any JSON-serializable value and the handler wraps it (string โ†’ text block; object โ†’ JSON text + `structuredContent`). For full control โ€” images, audio, resource links, mixed blocks, or the `isError` flag โ€” return a wire-shaped `McpToolResult` built with the exported helpers:
3356
+
3357
+ ```ts
3358
+ import { mcpImage, mcpResourceLink, mcpText } from '@buildbase/sdk/mcp';
3359
+
3360
+ execute: async (input, ctx) => ({
3361
+ content: [
3362
+ mcpText('Revenue chart for Q2:'),
3363
+ mcpImage(chartPngBase64, 'image/png'),
3364
+ mcpResourceLink('https://app.example.com/reports/q2.pdf', 'q2-report'),
3365
+ ],
3366
+ structuredContent: { totalCents: 1_299_00, currency: 'usd' },
3367
+ });
3368
+ ```
3369
+
3370
+ ### Resources & prompts
3371
+
3372
+ Both are **opt-in**, and `initialize` capabilities only advertise what you actually configured โ€” existing servers are byte-identical until you opt in.
3373
+
3374
+ ```ts
3375
+ createMcpHandler({
3376
+ buildbase,
3377
+ // Lean built-in context catalog: buildbase://profile, buildbase://workspaces,
3378
+ // buildbase://workspace/{workspaceId}. Richer data stays tools-only.
3379
+ builtinResources: true,
3380
+ resources: [
3381
+ defineMcpResource({
3382
+ uri: 'app://docs/getting-started',
3383
+ name: 'getting-started',
3384
+ mimeType: 'text/markdown',
3385
+ read: () => gettingStartedMarkdown, // string | object (JSON) | { text } | { blob }
3386
+ }),
3387
+ ],
3388
+ resourceTemplates: [
3389
+ defineMcpResourceTemplate({
3390
+ uriTemplate: 'app://orders/{orderId}',
3391
+ name: 'order',
3392
+ read: (params, _uri, ctx) => ctx.custom.db.order.find(params.orderId),
3393
+ }),
3394
+ ],
3395
+ prompts: [
3396
+ defineMcpPrompt({
3397
+ name: 'analyze_usage',
3398
+ description: 'Review quota usage and suggest actions',
3399
+ arguments: [{ name: 'workspaceId', required: true }],
3400
+ get: async (args, ctx) =>
3401
+ `Review this workspace's quota usage:\n${JSON.stringify(await ctx.bb.usage.getAll(args.workspaceId))}`,
3402
+ }),
3403
+ ],
3404
+ });
3405
+ ```
3406
+
3407
+ `requiredScopes` gates resources and prompts exactly like tools. Deliberately out of scope (the stateless transport has no push channel): resource subscriptions, `listChanged`, sampling, elicitation.
3408
+
3162
3409
  ### Production hardening
3163
3410
 
3164
3411
  The handler ships with safe defaults; these are the knobs to review before going live:
@@ -3175,7 +3422,7 @@ export const mcp = createMcpHandler({
3175
3422
  buildbase,
3176
3423
  auth: { verify, resourceMetadataUrl },
3177
3424
  builtinTools: { include: ['get_quota_usage', 'record_usage'] },
3178
- allowedOrigins: ['https://app.imejis.io'],
3425
+ allowedOrigins: ['https://app.example.com'],
3179
3426
  maxRequestBytes: 512 * 1024,
3180
3427
  rateLimit: async auth => ({ ok: await limiter.allow(auth?.userId), retryAfter: 30 }),
3181
3428
  onError: (err, ctx) => logger.error({ err, ...ctx }, 'mcp error'),
@@ -3223,7 +3470,7 @@ mintToken: (claims) => mintAgentToken({ claims, secret: process.env.SYSTEM_SECRE
3223
3470
 
3224
3471
  // verify (MCP server) โ€” HS256 + RFC 8707 audience + sid decrypt โ†’ McpAuthInfo
3225
3472
  import { buildbaseAuth } from '@buildbase/sdk/mcp';
3226
- auth: buildbaseAuth({ secret: process.env.SYSTEM_SECRET!, resource: 'https://imejis.io/mcp' }),
3473
+ auth: buildbaseAuth({ secret: process.env.SYSTEM_SECRET!, resource: 'https://example.com/mcp' }),
3227
3474
  ```
3228
3475
 
3229
3476
  Bringing your own token format? `auth.verify(token, req) โ†’ McpAuthInfo` accepts any Bearer token; set `auth.resourceMetadataUrl` so 401s carry the RFC 9728 challenge. Set `auth: false` to disable auth (local dev only). Debug audience mismatches with `MCP_AUTH_DEBUG=1`. Test with the MCP Inspector: `npx @modelcontextprotocol/inspector --cli http://localhost:3000/api/mcp --transport http --header "Authorization: Bearer <token>" --method tools/list`.
@@ -3232,7 +3479,7 @@ Bringing your own token format? `auth.verify(token, req) โ†’ McpAuthInfo` accept
3232
3479
 
3233
3480
  ### Scopes & resources โ€” restrict or open up
3234
3481
 
3235
- Scopes and resources are **app-owned** โ€” the shared BuildBase authorization server stays scope/resource-agnostic. Scopes have three levers: **declare** a catalog (`scopes: [{ name, description }]` โ†’ `scopes_supported` in your own RFC 9728 protected-resource metadata), **gate per tool** (`requiredScopes` on a custom tool โ†’ hidden/refused unless the token carries them), and the **floor** (every tool runs under the user's BuildBase session, so the platform enforces the user's real permissions โ€” an agent never exceeds the user). Resources have two: **declare** `protectedResources` (RFC 9728) and **bind** the token audience via `buildbaseAuth({ resource, requireAudience })` (RFC 8707) โ€” the audience check at your resource is the whole gate. Built-ins are restricted with `builtinTools`, not scopes. Public clients (Claude Desktop, Cursor, Inspector) self-register with PKCE and no secret. Full model: [the guide](docs/MCP-AND-AGENT-READINESS.md#scopes--resources-restrict-or-open-up).
3482
+ Scopes and resources are **app-owned** โ€” the shared BuildBase authorization server stays scope/resource-agnostic. Scopes have three levers: **declare** a catalog (`scopes: [{ name, description }]` โ†’ `scopes_supported` in your own RFC 9728 protected-resource metadata), **gate per tool** (`requiredScopes` on a custom tool โ†’ hidden/refused unless the token carries them), and the **floor** (every tool runs under the user's BuildBase session, so the platform enforces the user's real permissions โ€” an agent never exceeds the user). Resources have two: **declare** `protectedResources` (RFC 9728) and **bind** the token audience via `buildbaseAuth({ resource, requireAudience })` (RFC 8707) โ€” the audience check at your resource is the whole gate. Built-ins are restricted with `builtinTools`, not scopes. Public clients (Claude Desktop, Cursor, Inspector) self-register with PKCE and no secret. Full model: [the guide](https://unpkg.com/@buildbase/sdk/docs/MCP-AND-AGENT-READINESS.md#scopes--resources-restrict-or-open-up).
3236
3483
 
3237
3484
  ## OAuth2 App Bridge
3238
3485
 
@@ -3243,7 +3490,7 @@ BuildBase runs the full OAuth2 authorization flow (login, consent, code, PKCE, r
3243
3490
 
3244
3491
  Both requests carry `Authorization: Bearer <JWT>`, an HS256 JWT signed with your client secret. This toolkit verifies those requests (timing-safe, no `alg` confusion) and shapes the exact response body the platform expects, so you write only the part that's yours: minting/invalidating a token in your own format.
3245
3492
 
3246
- > For the common case, `mintToken: (claims) => mintAgentToken({ claims, secret })` is the preset โ€” it signs HS256 with your secret, binds `aud` to the granted RFC 8707 resource, and embeds the encrypted `sid`. The examples below show the hand-rolled equivalent, for apps that mint their own token format (e.g. one shared with their web sessions). See [the guide](docs/MCP-AND-AGENT-READINESS.md#auth-minting--verifying-the-token).
3493
+ > For the common case, `mintToken: (claims) => mintAgentToken({ claims, secret })` is the preset โ€” it signs HS256 with your secret, binds `aud` to the granted RFC 8707 resource, and embeds the encrypted `sid`. The examples below show the hand-rolled equivalent, for apps that mint their own token format (e.g. one shared with their web sessions). See [the guide](https://unpkg.com/@buildbase/sdk/docs/MCP-AND-AGENT-READINESS.md#auth-minting--verifying-the-token).
3247
3494
 
3248
3495
  ```ts
3249
3496
  // applicationTokenUrl handler (Next.js Pages Router)
@@ -3284,7 +3531,7 @@ When an agent calls your protected API without a valid token, reply with an RFC
3284
3531
  import { bearerChallenge } from '@buildbase/sdk';
3285
3532
 
3286
3533
  const c = bearerChallenge({
3287
- resourceMetadataUrl: 'https://imejis.io/.well-known/oauth-protected-resource',
3534
+ resourceMetadataUrl: 'https://example.com/.well-known/oauth-protected-resource',
3288
3535
  error: 'invalid_token',
3289
3536
  });
3290
3537
  res.writeHead(c.status, c.headers).end(c.body);