@buildbase/sdk 0.0.51 → 0.0.54
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.
- package/AGENTS.md +24 -8
- package/CHANGELOG.md +102 -0
- package/README.md +283 -33
- package/dist/css/styles.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1248 -76
- package/dist/index.mjs +1 -1
- package/dist/mcp/index.cjs +1 -0
- package/dist/mcp/index.d.ts +1956 -0
- package/dist/mcp/index.mjs +1 -0
- package/dist/react/chunks/{SettingsDialog-DitAiAqr.cjs → SettingsDialog-B28WJsAr.cjs} +1 -1
- package/dist/react/chunks/{SettingsDialog-uVKFxlal.mjs → SettingsDialog-blhppbNm.mjs} +1 -1
- package/dist/react/chunks/{SubscriptionDialog-P0GlXEyn.mjs → SubscriptionDialog-BTWVX3cH.mjs} +1 -1
- package/dist/react/chunks/{SubscriptionDialog-Cg-qYVKr.cjs → SubscriptionDialog-QKmwvJU1.cjs} +1 -1
- package/dist/react/chunks/ar-1Z1MOGAP.mjs +2 -0
- package/dist/react/chunks/ar-VGzBv3AZ.cjs +2 -0
- package/dist/react/chunks/de-BjdDg60Y.cjs +2 -0
- package/dist/react/chunks/de-CXUItplO.mjs +2 -0
- package/dist/react/chunks/es-BBPsSZUp.cjs +2 -0
- package/dist/react/chunks/es-BTbzVutz.mjs +2 -0
- package/dist/react/chunks/fr-Cax48X_S.cjs +2 -0
- package/dist/react/chunks/fr-DPMeOQu3.mjs +2 -0
- package/dist/react/chunks/hi-BJqvpG6d.cjs +2 -0
- package/dist/react/chunks/hi-JfJx-LV7.mjs +2 -0
- package/dist/react/chunks/ja-CDGtLzTk.mjs +2 -0
- package/dist/react/chunks/ja-DvVyj5xX.cjs +2 -0
- package/dist/react/chunks/react-6QJCOE-D.cjs +2 -0
- package/dist/react/chunks/react-CC6T7Zky.mjs +2 -0
- package/dist/react/chunks/zh-BAcqxkxk.cjs +2 -0
- package/dist/react/chunks/zh-BNZ2Ooe8.mjs +2 -0
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +98 -43
- package/dist/react/index.mjs +1 -1
- package/package.json +20 -5
- package/dist/react/chunks/ar-SrPOJXdL.mjs +0 -2
- package/dist/react/chunks/ar-a3aRbPCs.cjs +0 -2
- package/dist/react/chunks/de-DE3_z7r0.cjs +0 -2
- package/dist/react/chunks/de-xnPl61SS.mjs +0 -2
- package/dist/react/chunks/es-C_xcyGtp.cjs +0 -2
- package/dist/react/chunks/es-CyuBtosP.mjs +0 -2
- package/dist/react/chunks/fr-BMu7Bk4k.cjs +0 -2
- package/dist/react/chunks/fr-CxuDLZ27.mjs +0 -2
- package/dist/react/chunks/hi-CQ215yc9.cjs +0 -2
- package/dist/react/chunks/hi-CpuvyorS.mjs +0 -2
- package/dist/react/chunks/ja-DIJ_xCIi.mjs +0 -2
- package/dist/react/chunks/ja-DK6Buuws.cjs +0 -2
- package/dist/react/chunks/react-C2ikFWBG.cjs +0 -2
- package/dist/react/chunks/react-CCukNQ4q.mjs +0 -2
- package/dist/react/chunks/zh-CYQwZiBL.mjs +0 -2
- package/dist/react/chunks/zh-Dt-apS2s.cjs +0 -2
package/AGENTS.md
CHANGED
|
@@ -6,12 +6,13 @@ BuildBase is a multi-tenant SaaS platform SDK: auth, workspaces, subscriptions/b
|
|
|
6
6
|
|
|
7
7
|
## The one rule: two entry points, pick by runtime
|
|
8
8
|
|
|
9
|
-
| Import | Runtime | Contains
|
|
10
|
-
| ---------------------- | ---------------- |
|
|
11
|
-
| `@buildbase/sdk` | **Server** / any | Zero React. `BuildBase()` factory, API classes, webhooks, agent tooling, pure utils, enums, types.
|
|
12
|
-
| `@buildbase/sdk/react` | **Client** | Everything in core **re-exported** + React providers, hooks, gate components.
|
|
13
|
-
| `@buildbase/sdk/data` | any | Static datasets: `countries`, `currencies`, `languages`, `timezones`, `betaFormSchema`.
|
|
14
|
-
| `@buildbase/sdk/
|
|
9
|
+
| Import | Runtime | Contains |
|
|
10
|
+
| ---------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
11
|
+
| `@buildbase/sdk` | **Server** / any | Zero React. `BuildBase()` factory, API classes, webhooks, agent tooling, pure utils, enums, types. |
|
|
12
|
+
| `@buildbase/sdk/react` | **Client** | Everything in core **re-exported** + React providers, hooks, gate components. |
|
|
13
|
+
| `@buildbase/sdk/data` | any | Static datasets: `countries`, `currencies`, `languages`, `timezones`, `betaFormSchema`. |
|
|
14
|
+
| `@buildbase/sdk/mcp` | **Server** / any | MCP server toolkit: `createMcpHandler`, `defineMcpTool`, built-in BuildBase tools. Uses zod at runtime (why it's not in core). |
|
|
15
|
+
| `@buildbase/sdk/css` | build | `import '@buildbase/sdk/css'` — required once for component styling. |
|
|
15
16
|
|
|
16
17
|
- **In a React component / client hook → import from `@buildbase/sdk/react`.** It contains the whole core surface too, so you rarely need both.
|
|
17
18
|
- **In an API route, server action, webhook, cron, or Node/edge → import from `@buildbase/sdk`.** Never import `/react` on the server.
|
|
@@ -71,8 +72,8 @@ Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err,
|
|
|
71
72
|
- **Factory:** `BuildBase` (also default export) → `{ auth, workspace, subscription, usage, credits, plans, invoices, users, features, settings, notification, permissions, withSession, client }`.
|
|
72
73
|
- **API classes:** `BaseApi` (extend for custom APIs), `AuthApi`, `UserApi`, `WorkspaceApi`, `SettingsApi`, `PushApi`.
|
|
73
74
|
- **Webhook verification (runtime-agnostic — Node, edge, Deno, Bun, browsers):** `verifyWebhookSignature`, `parseWebhookEvent`. Verify against the **raw** request body (`await req.text()`), not the parsed JSON.
|
|
74
|
-
- **Agent readiness (framework-agnostic, returns plain `DiscoveryDocument`):** `
|
|
75
|
-
- **OAuth2 app-bridge:** `handleAppTokenRequest`, `handleAppRevokeRequest` (one-call handlers), `bearerChallenge` (RFC 9728/6750 401), `verifyAppTokenRequest`, `verifyAppRevokeRequest`, `verifyClientJwt`, `extractBearerToken`, `appTokenSuccess`, `appTokenFailure`, `AppBridgeError`. Types: `AppTokenRequestClaims`, `AppRevokeRequestClaims`, `AppTokenResult`, `AppTokenResponseBody`, `HandlerResult`.
|
|
75
|
+
- **Agent readiness (framework-agnostic, returns plain `DiscoveryDocument`):** **App owns discovery content, platform owns auth.** All content — `llms.txt`, `robots.txt`, `sitemap`, `protectedResources` (RFC 9728), API catalog, skills — is defined locally in `AgentReadyConfig` and served with no platform round-trip. The platform bundle (`fetchAgentReadiness`, fail-soft/cached) supplies ONLY `{ enabled, authorizationServer }` (the OAuth AS pointer). Functions: `resolveAgentPath` (dispatch ALL agent paths — root docs + `.well-known/*`; prefer for new wiring), `resolveWellKnown` (`.well-known/*` only), `clearAgentReadinessCache`, `buildAgentCard`, `buildA2AAgentCard` (A2A card at `/.well-known/agent-card.json`; served by DEFAULT, derived from `site`+`skills`; `a2aCard: false` disables, object overrides), `buildProtectedResourceMetadata(path, config)` (local), `buildAgentSkillsIndex`, `buildSkillMd`, `buildSecurityTxt`, `buildLlmsTxt(config)` (from `config.llmsTxt`), `buildLlmsFullTxt` (from `config.llmsFullTxt`), `buildRobotsTxt`, `buildSitemap`, `buildApiCatalog`, `buildMcpServerCard`, `buildAuthMd`, `buildWebBotAuthDirectory` (JWKS at `/.well-known/http-message-signatures-directory` from `config.webBotAuth.keys`; opt-in), `buildDnsAidRecords` (suggested `_agents` DNS records — publish at your DNS provider, can't be served over HTTP), `buildDiscoveryLinkHeader`, `wantsMarkdown`/`negotiateMarkdown`, `sha256Digest`, `AI_BOT_USER_AGENTS`. `config.extraPaths` serves ANY literal document by exact path (commerce discovery: x402/UCP/ACP/MPP, `/openapi.json`, future `.well-known` docs) and takes precedence over built-ins. Types: `AgentReadyConfig`, `AgentReadinessBundle`, `AgentSkill`, `ApiCatalogApi`, `A2ACardConfig`, `A2ACardSkill`, `DnsAidRecord`, `McpServerCard`, `RobotsConfig`, `RobotsPolicy`, `ContentSignals`, `SitemapUrl`, `DiscoveryDocument`.
|
|
76
|
+
- **OAuth2 app-bridge:** `handleAppTokenRequest`, `handleAppRevokeRequest` (one-call handlers), `bearerChallenge` (RFC 9728/6750 401; header values are quoted-string-escaped + CR/LF-stripped), `verifyAppTokenRequest`, `verifyAppRevokeRequest`, `verifyClientJwt`, `signClientJwt` (HS256 mint — symmetric counterpart of verify), `extractBearerToken`, `appTokenSuccess`, `appTokenFailure`, `AppBridgeError`. Types: `AppTokenRequestClaims`, `AppRevokeRequestClaims`, `AppTokenResult`, `AppTokenResponseBody`, `HandlerResult`, `VerifyClientJwtOptions`. `verifyClientJwt(token, secret, opts?: VerifyClientJwtOptions)` **requires a numeric `exp` by default** (`requireExp: false` to allow non-expiring tokens); optional `clockToleranceSec` / `issuer` / `audience`. **Presets** (also in core): `mintAgentToken` (the whole mint in one call — HS256 with the app's secret, `aud` from the grant's resource, encrypted `sid`) + `buildbaseAuth` (the whole verify) + `createSessionRefCrypto`. Security invariant: the platform relays the minted token but never sees the app secret — it can neither forge nor decode app tokens, and the BuildBase session is never stored and never plaintext in a token.
|
|
76
77
|
- **Currency & pricing utils (pure):** `formatCents`, `formatOverageRate`, `formatOverageRateWithLabel`, `formatQuotaIncludedOverage`, `formatQuotaWithPrice`, `getCurrencySymbol`, `getCurrencyFlag`, `getQuotaUnitLabelFromName`, `getQuotaDisplayValue`, `CURRENCY_DISPLAY`, `CURRENCY_FLAG`, `PLAN_CURRENCY_CODES`, `PLAN_CURRENCY_OPTIONS`.
|
|
77
78
|
- **Pricing variants (multi-currency):** `getPricingVariant`, `getBasePriceCents`, `getStripePriceIdForInterval`, `getQuotaOverageCents`, `getQuotaDisplayWithVariant`, `getAvailableCurrenciesFromPlans`, `getDisplayCurrency`, `getBillingIntervalAndCurrencyFromPriceId`.
|
|
78
79
|
- **Seat pricing:** `calculateBillableSeats`, `calculateSeatOverageCents`, `calculateTotalSubscriptionCents`, `getPerSeatPriceCents`, `getSeatPricing`.
|
|
@@ -106,6 +107,21 @@ Config also accepts: `timeout`, `maxRetries`, `debug`, `headers`, `onError(err,
|
|
|
106
107
|
|
|
107
108
|
`countries`, `currencies`, `languages`, `timezones`, `betaFormSchema` (Zod), type `BetaFormValues`.
|
|
108
109
|
|
|
110
|
+
### `@buildbase/sdk/mcp` — MCP server (server-only, zero React)
|
|
111
|
+
|
|
112
|
+
**Full guide: `docs/MCP-AND-AGENT-READINESS.md`** (mental model, fast path, auth, scope/resource customization).
|
|
113
|
+
|
|
114
|
+
Expose a live MCP server (stateless Streamable HTTP; Node 18+/edge) with built-in BuildBase tools + custom zod-schema tools. **Spec-compliant to MCP 2025-06-18**: server card SEP-1649 v1.0 (`$schema`, `version:"1.0"`, `protocolVersion`, `transport:{type,url}`, boolean caps), discovery manifest SEP-1960, auth via RFC 9728→8414→8707+PKCE. The platform issues **public clients** (RFC 7591 `token_endpoint_auth_method:"none"`, no secret, PKCE-only) so Claude Desktop/Cursor/ChatGPT/Inspector connect with zero manual config.
|
|
115
|
+
|
|
116
|
+
- **Handler:** `createMcpHandler({ buildbase?, auth, tools?, builtinTools?, context?, serverInfo?, instructions?, allowedOrigins?, maxRequestBytes?, rateLimit?, formatToolError?, onError? })` → `{ handle(McpHttpRequest), fetch(Request), listTools(), serverCard({ endpoint }) }`. `fetch(Request)` is the Web adapter (Next.js App Router, Hono, Bun, Deno, Cloudflare Workers, edge); `handle({method,headers,body})` is the pure form (Express, Fastify, Next.js Pages Router, Node http). Per-framework copy-paste recipes + mint route + production checklist in `docs/MCP-AND-AGENT-READINESS.md`. `auth.verify(token) → McpAuthInfo { sessionId, userId?, workspaceId?, scopes? }` maps the app's Bearer token (any format) to the BuildBase session tools run under; `auth.resourceMetadataUrl` makes 401s carry the RFC 9728 challenge. `auth: false` = dev only. **Hardening:** `maxRequestBytes` (default 1 MiB → 413), `rateLimit(auth, req)` gate (after auth; `false`/`{ok:false,retryAfter}` → 429, fails closed), `allowedOrigins` (non-matching browser Origin → 403; also drives CORS — `OPTIONS` preflight → 204 + `Access-Control-*`, matching Origin echoed on responses), `formatToolError(err, {tool})` (redact client-facing tool-error text; full error still hits `onError`). **No lock-in:** `buildbase` is optional (standalone server, only your tools); `context(auth, req)` injects your own per-request context as `ctx.custom`; custom tools override same-named built-ins; raw JSON Schema tools skip zod.
|
|
117
|
+
- **Tools:** `defineMcpTool` (typed custom tools), `builtinMcpTools`, `selectBuiltinTools`. Built-ins cover the **full BuildBase surface** (42 tools: workspaces/users/subscription/plans/invoices/usage/credits/features/permissions/settings — reads + writes + destructive). **Default `builtinTools: 'readonly'`** (least privilege) — reads only until you opt into `'all'` (or `{ include }`); narrow further with `false` | `{ exclude }`. Every tool runs under the user's session → platform enforces the user's real permissions (agent can't exceed the user). Built-ins carry **no** scope requirement (agent = authenticated app user; read/write boundary is `builtinTools`, not agent scopes). Per-scope gating is opt-in on custom tools via `requiredScopes` + the `scopes` returned from `auth.verify` (your scope names); a token with no scopes sees only tools that require none.
|
|
118
|
+
- **One-config stack:** `createAgentStack({ serverUrl, orgId, siteUrl, site, secret, mcp?, discovery? })` → `{ mcp, mcpEndpoint, config, linkHeader, resolvePath, serveAgentPath, routes }` — derives the MCP endpoint, server card, RFC 9728 protected resource, API catalog entry, BuildBase client, `buildbaseAuth`, and CORS from ONE object. Next.js wiring: `export const { GET, POST, DELETE, OPTIONS } = agent.routes` for `/api/mcp`, `export const GET = agent.serveAgentPath` for discovery routes. Everything derived is overridable via `mcp.*` / `discovery.*`.
|
|
119
|
+
- **BuildBase auth presets** (pure local crypto — the app's secret and tokens NEVER reach the platform): `mintAgentToken({ claims, secret, expiresInSec?, extraClaims? })` (drop into `handleAppTokenRequest.mintToken`; signs HS256 with YOUR secret, binds `aud` to the RFC 8707 resource, embeds the per-user session as an encrypted `sid` claim) and `buildbaseAuth({ secret, resource?, resourceMetadataUrl?, requireAudience?, verify? })` (the `auth` config for `createMcpHandler`: verify + RFC 8707 audience binding + `sid` decryption + derived `resourceMetadataUrl`; `resource` accepts `string|string[]`; `MCP_AUTH_DEBUG=1` logs received-vs-expected `aud` on rejection). `createSessionRefCrypto(secret)` exposes the underlying AES-256-GCM `sid` crypto (WebCrypto; wire format `base64url(iv|tag|ciphertext)`, key `SHA-256(secret+":bb-session")` — byte-compatible with Node `createCipheriv` implementations of the same layout).
|
|
120
|
+
- **Scopes & resources (restrict or open up) — ALL app-owned; the shared AS is scope/resource-agnostic:** THREE scope levers — (1) declare a catalog `scopes:[{name,description}]` on `AgentReadyConfig`/`createAgentStack` → `scopes_supported` in the app's OWN RFC 9728 protected-resource metadata (served from the app origin); (2) gate per custom tool via `requiredScopes` (hidden/refused unless the token carries all — YOUR scope names); (3) the floor — every tool runs under the user's session so the platform enforces the user's real permissions (agent ⊆ user). Built-ins are restricted by `builtinTools`, not scopes. TWO resource levers — declare `protectedResources` (RFC 9728: stack derives root + `<host>/mcp` + endpoint) and bind the token `aud` via `buildbaseAuth({ resource, requireAudience })` (RFC 8707) — the audience check at YOUR resource is the whole gate (token for A rejected at B); the AS just passes `resource` through opaquely (no per-org scope/resource lists, no `invalid_target`). Defaults are least-privilege; widen deliberately. RFC 9728 convention: canonical MCP resource is `<host>/mcp` (metadata at `/.well-known/oauth-protected-resource/mcp`), 401 `WWW-Authenticate` points there.
|
|
121
|
+
- **CORS:** on by default (`Access-Control-Allow-Origin: *` — safe for a Bearer-token API; reflects `allowedOrigins` when set). Narrow with `cors: [origins]` or disable with `cors: false`. Preflight answered automatically — never wrap the route in your own CORS.
|
|
122
|
+
- **Auth helpers re-exported:** `signClientJwt`, `verifyClientJwt`, `extractBearerToken`, `bearerChallenge`, `AppBridgeError`.
|
|
123
|
+
- Types: `CreateMcpHandlerConfig`, `McpHandler`, `McpToolDefinition`, `McpToolContext`, `McpAuthInfo`, `McpHttpRequest`, `McpHttpResponse`, `McpToolAnnotations`, `McpBuildBaseClient`, `BuiltinMcpToolName`, `AgentStack`, `AgentStackConfig`, `BuildBaseAuthOptions`, `MintAgentTokenOptions`, `SessionRefCrypto`.
|
|
124
|
+
|
|
109
125
|
## Server actions cheat-sheet
|
|
110
126
|
|
|
111
127
|
`workspace`: `list get create update delete` · `users`: `list invite remove updateRole getProfile updateProfile` · `subscription`: `get checkout update cancel resume getBillingPortalUrl` · `plans`: `getGroup getVersions getPublic getVersion` · `invoices`: `list get` · `usage`: `record recordBatch getQuota getAll getLogs` · `credits`: `getBalance consume purchase getPackages getTransactions getExpiring getBuckets getPublicPackages` · `settings`: `get` · `features`: `list update` · `notification`: `send(workspaceId, event, userId?, data?)` · `permissions`: `check(workspaceId, userId, permission) resolve(workspaceId, userId)`.
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,108 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.0.54] - 2026-07-10
|
|
11
|
+
|
|
12
|
+
MCP + agent-readiness brought to **MCP 2025-06-18** compliance, a one-config setup path, first-class auth presets, and a full per-framework guide. **Behavior changes** are marked ⚠️.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`createAgentStack(config)` — the whole MCP + agent-readiness surface from one config object** (`@buildbase/sdk/mcp`). Derives the MCP handler, the SEP-1649 server card, the RFC 9728 protected-resource metadata (root + canonical `<host>/mcp` + endpoint), the API-catalog entry, the BuildBase client, `buildbaseAuth`, CORS, and the discovery `Link` header. Returns `{ mcp, mcpEndpoint, config, linkHeader, resolvePath, serveAgentPath, routes }` — Next.js wires `export const { GET, POST, DELETE, OPTIONS } = agent.routes` + `export const GET = agent.serveAgentPath`. Everything derived is overridable via `mcp.*` / `discovery.*`.
|
|
17
|
+
- **Auth presets — the app mints & verifies its own tokens with pure local crypto; the platform never sees the secret.** `mintAgentToken({ claims, secret })` (drop into `handleAppTokenRequest.mintToken` — HS256 with your secret, `aud` from the granted RFC 8707 resource, per-user BuildBase session embedded as an encrypted `sid`), `buildbaseAuth({ secret, resource, requireAudience })` (the `auth` config for `createMcpHandler` — verify + audience binding + `sid` decrypt + derived `resourceMetadataUrl`; `resource` accepts `string | string[]`), and `createSessionRefCrypto(secret)` (the underlying AES-256-GCM `sid` crypto, WebCrypto, wire-format `base64url(iv|tag|ciphertext)`, key `SHA-256(secret+":bb-session")` — byte-compatible with a Node `createCipheriv` layout). Also exported from the core entry. `MCP_AUTH_DEBUG=1` logs received-vs-expected `aud` on rejection.
|
|
18
|
+
- **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.
|
|
19
|
+
- **`AgentReadyConfig.scopes` — an app scope catalog** (`AppScope[]`, `{ name, description }`) that drives `scopes_supported` in your RFC 9728 protected-resource metadata. Scopes are app-owned.
|
|
20
|
+
- **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).
|
|
21
|
+
- **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.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **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`.
|
|
26
|
+
- **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.
|
|
27
|
+
- **`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`.
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
|
|
31
|
+
- **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).
|
|
32
|
+
- **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).
|
|
33
|
+
- **`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.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- **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.
|
|
38
|
+
- **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.
|
|
39
|
+
- **`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.
|
|
40
|
+
- **`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.
|
|
41
|
+
- **`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.
|
|
42
|
+
- **`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.
|
|
43
|
+
- **`workspaceSettingsManager.clearParams()` notifies subscribers** (React state no longer diverges from the manager), and `getState()` returns a stable snapshot reference.
|
|
44
|
+
|
|
45
|
+
### Removed
|
|
46
|
+
|
|
47
|
+
- **`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.
|
|
48
|
+
|
|
49
|
+
### Internal
|
|
50
|
+
|
|
51
|
+
- Centralized the duplicated base64url codec into `src/lib/base64url.ts` (was copied in `agent-bridge.ts` and `agent-auth.ts`).
|
|
52
|
+
- **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`).
|
|
53
|
+
|
|
54
|
+
## [0.0.53] - 2026-07-09
|
|
55
|
+
|
|
56
|
+
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.
|
|
57
|
+
|
|
58
|
+
### Security
|
|
59
|
+
|
|
60
|
+
- **`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'`.
|
|
61
|
+
- **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.
|
|
62
|
+
- **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).
|
|
63
|
+
- **`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).
|
|
64
|
+
- **`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.
|
|
65
|
+
- **`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.
|
|
66
|
+
- **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).
|
|
67
|
+
- **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.
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
- **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).
|
|
72
|
+
- **`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`.
|
|
73
|
+
- **Public model types exported** from `@buildbase/sdk` and `/react`: `IUser`, `IWorkspace`, `ISettings`, `TranslationKey` (previously appeared in exported signatures but weren't name-exported).
|
|
74
|
+
- **`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).
|
|
75
|
+
- **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.
|
|
76
|
+
- **`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.
|
|
77
|
+
- **`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.
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
- **`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.
|
|
82
|
+
- **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.
|
|
83
|
+
- **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.
|
|
84
|
+
- **Palette token.** `focus:ring-red-600` → `focus:ring-destructive`; the `lint:tokens` guard was widened to also catch `ring`/`outline`/`from`/`via`/`to`/`fill`/`stroke`/`shadow` palette classes.
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
|
|
88
|
+
- **`lucide-react` bound to `>=0.544.0 <1`** — 0.x releases rename/remove icons; the previous open range let consumers pull a breaking minor.
|
|
89
|
+
|
|
90
|
+
## [0.0.52] - 2026-07-08
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
|
|
94
|
+
- **New entry point `@buildbase/sdk/mcp` — a live MCP server for your app.** `createMcpHandler({ buildbase, auth, tools })` returns a stateless Streamable-HTTP handler (`fetch(Request)` for Next.js/edge, pure `handle()` for Express) exposing the **full BuildBase surface** as MCP tools — 42 built-ins spanning workspaces, users, subscription, plans, invoices, usage, credits, feature flags, permissions, and settings (reads + writes + destructive ops). **Default `builtinTools: 'all'`** — the SDK restricts nothing; the consumer narrows via `'readonly'` | `false` | `{ include }` | `{ exclude }`. Every tool runs under the user's BuildBase session, so the platform enforces the user's real permissions (an agent can never exceed the user). Custom tools are defined with `defineMcpTool` + zod schemas (converted to JSON Schema with zod 4's native `z.toJSONSchema`). Bearer auth plugs into the OAuth2 app-bridge: `auth.verify` maps the app-minted token to the BuildBase session tools run under, and unauthenticated requests get an RFC 9728 `WWW-Authenticate` challenge. Built-in tools carry no BuildBase-specific scope requirement (the agent acts as an authenticated app user; the read/write boundary is `builtinTools`); optional per-scope gating (`requiredScopes` vs the token's `scopes`) is available for custom tools using your own scope names. Origin allowlisting, per-request `withSession`. Hand-rolled protocol subset (initialize / ping / tools/list / tools/call, protocol versions 2024-11-05 → 2025-06-18) — no `@modelcontextprotocol/sdk` dependency (zod-3-based, Node-http-bound). Separate entry so the core stays zod-free at runtime.
|
|
95
|
+
- **`resolveAgentPath(path, config)`** — superset of `resolveWellKnown` that also serves the root-level documents: `/robots.txt`, `/sitemap.xml`, `/llms.txt`, `/auth.md`, `/security.txt` (RFC 9116 legacy alias). One catch-all now covers the entire agent-readiness surface.
|
|
96
|
+
- **`buildRobotsTxt(config)`** — robots.txt with per-AI-bot policy groups (new `AI_BOT_USER_AGENTS` list: GPTBot, ClaudeBot, PerplexityBot, Bytespider, …; `aiBots: 'allow' | 'deny' | RobotsPolicy[]`), [Content Signals](https://contentsignals.org) directives (`contentSignals: { search, aiInput, aiTrain }`), and `Sitemap:` references.
|
|
97
|
+
- **`buildSitemap(config)`** — minimal sitemap.xml from `config.sitemap.urls` for API-first apps without a content pipeline (null when unconfigured, so next-sitemap users keep their files).
|
|
98
|
+
- **`wantsMarkdown(acceptHeader)` / `negotiateMarkdown(...)`** — q-value-aware markdown content negotiation; negotiated documents carry `vary: 'Accept'` (new optional field on `DiscoveryDocument`).
|
|
99
|
+
- **`buildDiscoveryLinkHeader(config)`** — `Link` response-header value advertising llms.txt, the Agent Card, sitemap, API catalog, and MCP server card. Sync/pure — edge-middleware-safe.
|
|
100
|
+
- **`signClientJwt(payload, secret, { expiresInSec? })`** — HS256 mint, the symmetric counterpart of `verifyClientJwt`, for `applicationTokenUrl` handlers that mint the app's agent tokens (pure JS, edge-safe).
|
|
101
|
+
- **`config.llmsTxt`** — local llms.txt override that wins over the platform-bundle content (mirrors the existing `authMd` convention).
|
|
102
|
+
- **MCP without lock-in:** `buildbase` is optional on `createMcpHandler` — omit it for a standalone MCP server exposing only your own tools (built-ins default off; `ctx.bb` throws a clear error if touched). New `context(auth, req)` factory injects your own per-request context (`ctx.custom`) into tool executions. Custom tools now override same-named built-ins instead of throwing.
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
|
|
106
|
+
- **Clean split: the app owns discovery content, the platform owns auth.** `protectedResources` (RFC 9728) and `llms.txt` are now defined locally in `AgentReadyConfig` and served with no platform round-trip — `buildProtectedResourceMetadata(path, config)` builds the doc from `config.protectedResources` (deriving `authorization_servers` from `serverUrl`/`orgId`), and `buildLlmsTxt(config)` serves `config.llmsTxt`. Both lost their `bundle` parameter. `AgentReadinessBundle` is slimmed to `{ enabled, authorizationServer }` — the only thing the platform supplies. New config field: `AgentReadyConfig.protectedResources`.
|
|
107
|
+
- **`/.well-known/oauth-authorization-server` now serves the full RFC 8414 metadata** (cached, fail-soft proxy of the platform's canonical document) instead of a nonstandard pointer object; falls back to the pointer when the platform is unreachable. `clearAgentReadinessCache()` clears this cache too.
|
|
108
|
+
- **`AppTokenRequestClaims.sessionId`** — the platform now mints a per-user BuildBase session on every OAuth2 grant (including refresh) and sends it in the app-bridge mint claims. Embed it encrypted in the token you mint (never plaintext, never stored) so your MCP/agent endpoints can call BuildBase as the consenting user.
|
|
109
|
+
|
|
8
110
|
## [0.0.51] - 2026-07-08
|
|
9
111
|
|
|
10
112
|
### Fixed
|
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@ Also works server-side (Next.js API routes, Express, Hono) — see [Server-Side
|
|
|
39
39
|
- [Server-Side Usage](#server-side-usage)
|
|
40
40
|
- [Webhook Verification](#webhook-verification)
|
|
41
41
|
- [Agent Readiness (Discovery)](#agent-readiness-discovery)
|
|
42
|
+
- [MCP Server](#mcp-server-buildbasesdkmcp)
|
|
42
43
|
- [OAuth2 App Bridge](#oauth2-app-bridge)
|
|
43
44
|
|
|
44
45
|
## 🚀 Features
|
|
@@ -2102,12 +2103,13 @@ All SDK API clients extend a shared base class and are exported from the package
|
|
|
2102
2103
|
|
|
2103
2104
|
Server-only toolkits (framework-agnostic, zero React) are exported from `@buildbase/sdk`:
|
|
2104
2105
|
|
|
2105
|
-
| Export group | Purpose
|
|
2106
|
-
| --------------------- |
|
|
2107
|
-
| `BuildBase()` factory | Session-scoped server actions — see [Server-Side Usage](#server-side-usage)
|
|
2108
|
-
| Webhook verification | `verifyWebhookSignature`, `parseWebhookEvent` — see [Webhook Verification](#webhook-verification)
|
|
2109
|
-
| Agent readiness | `
|
|
2110
|
-
| OAuth2 app bridge | `handleAppTokenRequest`, `handleAppRevokeRequest`, `bearerChallenge`, … — see [OAuth2 App Bridge](#oauth2-app-bridge) |
|
|
2106
|
+
| Export group | Purpose |
|
|
2107
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2108
|
+
| `BuildBase()` factory | Session-scoped server actions — see [Server-Side Usage](#server-side-usage) |
|
|
2109
|
+
| Webhook verification | `verifyWebhookSignature`, `parseWebhookEvent` — see [Webhook Verification](#webhook-verification) |
|
|
2110
|
+
| Agent readiness | `resolveAgentPath`, `buildRobotsTxt`, `buildAgentCard`, `buildLlmsTxt`, … — see [Agent Readiness](#agent-readiness-discovery) |
|
|
2111
|
+
| OAuth2 app bridge | `handleAppTokenRequest`, `handleAppRevokeRequest`, `signClientJwt`, `bearerChallenge`, … — see [OAuth2 App Bridge](#oauth2-app-bridge) |
|
|
2112
|
+
| MCP server | `createMcpHandler`, `defineMcpTool`, … from **`@buildbase/sdk/mcp`** — see [MCP Server](#mcp-server-buildbasesdkmcp) |
|
|
2111
2113
|
|
|
2112
2114
|
### Components
|
|
2113
2115
|
|
|
@@ -2933,60 +2935,304 @@ app.post('/webhooks/buildbase', (req, res) => {
|
|
|
2933
2935
|
|
|
2934
2936
|
## Agent Readiness (Discovery)
|
|
2935
2937
|
|
|
2936
|
-
|
|
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.
|
|
2939
|
+
|
|
2940
|
+
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
|
+
|
|
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`:
|
|
2937
2943
|
|
|
2938
2944
|
```ts
|
|
2939
2945
|
// lib/agent-ready.ts
|
|
2940
|
-
import type
|
|
2946
|
+
import { resolveAgentPath, type AgentReadyConfig } from '@buildbase/sdk';
|
|
2941
2947
|
|
|
2942
2948
|
export const agentConfig: AgentReadyConfig = {
|
|
2943
2949
|
serverUrl: process.env.BUILDBASE_URL!,
|
|
2944
2950
|
orgId: process.env.BUILDBASE_ORG_ID!,
|
|
2945
2951
|
siteUrl: 'https://imejis.io',
|
|
2946
2952
|
site: { name: 'Imejis', description: 'Generate images from templates via API.' },
|
|
2947
|
-
//
|
|
2953
|
+
// Discovery content is YOURS — defined here in code, not in the platform:
|
|
2954
|
+
scopes: [
|
|
2955
|
+
{ name: 'designs:read', description: 'View designs' },
|
|
2956
|
+
{ name: 'render:execute', description: 'Render images' },
|
|
2957
|
+
],
|
|
2958
|
+
llmsTxt: '# Imejis\n\n> Generate images from templates via API.',
|
|
2959
|
+
robots: { contentSignals: { search: true, aiInput: true, aiTrain: false } },
|
|
2960
|
+
sitemap: { urls: ['/', '/pricing'] }, // omit if you already generate sitemaps
|
|
2961
|
+
protectedResources: [{ resource: 'https://imejis.io/mcp' }], // scopes inherited from catalog
|
|
2962
|
+
// skills, security, apiCatalog, mcpServerCard, a2aCard, extraPaths — all optional
|
|
2948
2963
|
};
|
|
2949
|
-
```
|
|
2950
2964
|
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
```ts
|
|
2954
|
-
// app/.well-known/[...path]/route.ts (Next.js App Router)
|
|
2955
|
-
import { resolveWellKnown } from '@buildbase/sdk';
|
|
2956
|
-
import { agentConfig } from '@/lib/agent-ready';
|
|
2957
|
-
|
|
2958
|
-
export async function GET(req: Request) {
|
|
2959
|
-
const doc = await resolveWellKnown(new URL(req.url).pathname, agentConfig);
|
|
2965
|
+
export async function serveAgentPath(req: Request): Promise<Response> {
|
|
2966
|
+
const doc = await resolveAgentPath(new URL(req.url).pathname, agentConfig);
|
|
2960
2967
|
if (!doc) return new Response('{"error":"not_found"}', { status: 404 });
|
|
2961
2968
|
return new Response(doc.body, {
|
|
2962
2969
|
status: doc.status,
|
|
2963
|
-
headers: {
|
|
2970
|
+
headers: {
|
|
2971
|
+
'Content-Type': doc.contentType,
|
|
2972
|
+
'Cache-Control': doc.cacheControl,
|
|
2973
|
+
...(doc.vary ? { Vary: doc.vary } : {}),
|
|
2974
|
+
},
|
|
2964
2975
|
});
|
|
2965
2976
|
}
|
|
2966
2977
|
```
|
|
2967
2978
|
|
|
2968
|
-
`
|
|
2979
|
+
`resolveAgentPath` resolves the app's **entire agent-readiness surface**: the root-level documents (`/robots.txt`, `/sitemap.xml`, `/llms.txt`, `/auth.md`, `/security.txt`) plus everything under `/.well-known/*` — `agent.json`, `oauth-protected-resource` (+ per-resource paths), the full RFC 8414 `oauth-authorization-server` metadata (proxied fail-soft from the platform), the Agent Skills index + `SKILL.md`, `security.txt`, the API Catalog, and the MCP Server Card. Wire it once:
|
|
2969
2980
|
|
|
2970
2981
|
```ts
|
|
2971
|
-
// app/
|
|
2972
|
-
import {
|
|
2973
|
-
|
|
2982
|
+
// app/.well-known/[...path]/route.ts (Next.js App Router)
|
|
2983
|
+
import { serveAgentPath } from '@/lib/agent-ready';
|
|
2984
|
+
export const GET = serveAgentPath;
|
|
2974
2985
|
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
if (!doc) return new Response('Not found', { status: 404 });
|
|
2978
|
-
return new Response(doc.body, { headers: { 'Content-Type': doc.contentType } });
|
|
2979
|
-
}
|
|
2986
|
+
// plus identical two-liners at app/robots.txt/route.ts, app/llms.txt/route.ts,
|
|
2987
|
+
// app/auth.md/route.ts, app/security.txt/route.ts, app/sitemap.xml/route.ts
|
|
2980
2988
|
```
|
|
2981
2989
|
|
|
2990
|
+
```ts
|
|
2991
|
+
// Express — one middleware covers everything
|
|
2992
|
+
app.use(async (req, res, next) => {
|
|
2993
|
+
const doc = await resolveAgentPath(req.path, agentConfig);
|
|
2994
|
+
if (!doc) return next();
|
|
2995
|
+
res
|
|
2996
|
+
.status(doc.status)
|
|
2997
|
+
.setHeader('Content-Type', doc.contentType)
|
|
2998
|
+
.setHeader('Cache-Control', doc.cacheControl)
|
|
2999
|
+
.send(doc.body);
|
|
3000
|
+
});
|
|
3001
|
+
```
|
|
3002
|
+
|
|
3003
|
+
(`resolveWellKnown` remains available for `.well-known`-only wiring; `resolveAgentPath` is a superset.)
|
|
3004
|
+
|
|
2982
3005
|
`fetchAgentReadiness` is **fail-soft** — any network/HTTP/parse error resolves to `{ enabled: false }`, so a discovery route never 500s and never leaks that the platform is down; agents simply see the app as not (yet) agent-ready. Results are cached in-memory for `cacheTtlSeconds` (default 300s); call `clearAgentReadinessCache()` to reset (mainly for tests).
|
|
2983
3006
|
|
|
2984
|
-
|
|
3007
|
+
### robots.txt, Content Signals & AI bots
|
|
3008
|
+
|
|
3009
|
+
`buildRobotsTxt(config)` always returns a document. By default it emits an allow-all policy plus an explicit `Allow: /` group for every known AI crawler (`AI_BOT_USER_AGENTS`: GPTBot, ClaudeBot, PerplexityBot, …) so readiness checkers see AI bots addressed. Tune it via `config.robots`:
|
|
3010
|
+
|
|
3011
|
+
- `policies` — your base groups (default `[{ userAgent: '*', allow: ['/'] }]`)
|
|
3012
|
+
- `aiBots` — `'allow'` (default) | `'deny'` | explicit `RobotsPolicy[]` for per-bot control
|
|
3013
|
+
- `contentSignals` — [Content Signals](https://contentsignals.org): `{ search, aiInput, aiTrain }` → `Content-Signal: search=yes, ai-input=yes, ai-train=no`
|
|
3014
|
+
- `sitemaps` — `Sitemap:` lines (defaults to `${siteUrl}/sitemap.xml` when `config.sitemap` is set)
|
|
2985
3015
|
|
|
2986
|
-
|
|
3016
|
+
### Markdown content negotiation & Link headers
|
|
3017
|
+
|
|
3018
|
+
- `wantsMarkdown(acceptHeader)` — q-value-aware predicate: does this request prefer `text/markdown` over HTML? Use it in middleware to rewrite content pages to a markdown endpoint.
|
|
3019
|
+
- `negotiateMarkdown(acceptHeader, { html, markdown })` — picks the variant and returns a `DiscoveryDocument` with `vary: 'Accept'`.
|
|
3020
|
+
- `buildDiscoveryLinkHeader(config)` — the `Link` response header value advertising `llms.txt`, the Agent Card, and (when configured) sitemap, API catalog, and MCP server card. Sync and pure — safe in edge middleware.
|
|
3021
|
+
|
|
3022
|
+
### Who owns what
|
|
3023
|
+
|
|
3024
|
+
Clean split, no duplication: **your app owns discovery content, the platform owns auth.** You define `llms.txt`, `robots.txt`, `sitemap`, `protectedResources` (RFC 9728), the API catalog, and skills right here in `AgentReadyConfig` — the SDK serves them from your own origin with no platform round-trip. The platform only supplies the pointer to its OAuth **authorization server** (the one thing it owns); that's the sole content of `fetchAgentReadiness`'s bundle (`{ enabled, authorizationServer }`).
|
|
3025
|
+
|
|
3026
|
+
- **`/llms.txt`** — from `config.llmsTxt`. Returns null when unset (serve your own static file instead).
|
|
3027
|
+
- **`/.well-known/oauth-protected-resource`** — built from `config.protectedResources`; `authorization_servers` is derived from `serverUrl`/`orgId`.
|
|
3028
|
+
- **`/auth.md`** — agent registration/auth instructions. Generated from the OAuth metadata, or serves `config.authMd` verbatim.
|
|
2987
3029
|
- **WebMCP** — expose in-page actions to browser agents. In a client component, call `provideWebMcpTools([{ name, description, inputSchema, execute }])` (no-op off-browser).
|
|
2988
3030
|
|
|
2989
|
-
|
|
3031
|
+
### isitagentready.com coverage
|
|
3032
|
+
|
|
3033
|
+
| Check | Covered by |
|
|
3034
|
+
| ---------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
3035
|
+
| robots.txt + AI bot rules + Content Signals | `buildRobotsTxt` |
|
|
3036
|
+
| Sitemap | `buildSitemap` (or your own generator) |
|
|
3037
|
+
| Link response headers | `buildDiscoveryLinkHeader` |
|
|
3038
|
+
| Markdown content negotiation | `wantsMarkdown` / `negotiateMarkdown` |
|
|
3039
|
+
| llms.txt | `buildLlmsTxt` |
|
|
3040
|
+
| MCP Server Card + live MCP server | `buildMcpServerCard` + [`@buildbase/sdk/mcp`](#mcp-server-buildbasesdkmcp) |
|
|
3041
|
+
| Agent Skills / API Catalog | `buildAgentSkillsIndex` / `buildApiCatalog` |
|
|
3042
|
+
| A2A Agent Card | `buildA2AAgentCard` (served by default from `site` + `skills`) |
|
|
3043
|
+
| OAuth discovery (RFC 8414) + protected resource (RFC 9728) | `resolveAgentPath` (platform-backed) |
|
|
3044
|
+
| Web Bot Auth | `buildWebBotAuthDirectory` (opt-in — publish your JWKS) |
|
|
3045
|
+
| DNS-AID | `buildDnsAidRecords` (returns records — publish at your DNS provider) |
|
|
3046
|
+
| Commerce (x402/ACP/UCP/MPP), openapi.json | `config.extraPaths` (serve any literal document by path) |
|
|
3047
|
+
|
|
3048
|
+
**Functions:** `resolveAgentPath`, `resolveWellKnown`, `fetchAgentReadiness`, `clearAgentReadinessCache`, `buildAgentCard`, `buildA2AAgentCard`, `buildProtectedResourceMetadata`, `buildAgentSkillsIndex`, `buildSkillMd`, `buildSecurityTxt`, `buildLlmsTxt`, `buildLlmsFullTxt`, `buildRobotsTxt`, `buildSitemap`, `buildApiCatalog`, `buildMcpServerCard`, `buildMcpDiscoveryManifest`, `buildAuthMd`, `buildWebBotAuthDirectory`, `buildDnsAidRecords`, `buildDiscoveryLinkHeader`, `wantsMarkdown`, `negotiateMarkdown`, `provideWebMcpTools`, `sha256Digest`. **Constants:** `AI_BOT_USER_AGENTS`. **Types:** `AgentReadyConfig`, `AgentReadinessBundle`, `AgentSkill`, `AppScope`, `ApiCatalogApi`, `A2ACardConfig`, `A2ACardSkill`, `DnsAidRecord`, `McpServerCard`, `RobotsConfig`, `RobotsPolicy`, `ContentSignals`, `SitemapUrl`, `WebMcpTool`, `DiscoveryDocument`.
|
|
3049
|
+
|
|
3050
|
+
## MCP Server (`@buildbase/sdk/mcp`)
|
|
3051
|
+
|
|
3052
|
+
> **Full guide:** [`docs/MCP-AND-AGENT-READINESS.md`](docs/MCP-AND-AGENT-READINESS.md).
|
|
3053
|
+
|
|
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.
|
|
3055
|
+
|
|
3056
|
+
The fast path bundles the server + the whole discovery surface from one config:
|
|
3057
|
+
|
|
3058
|
+
```ts
|
|
3059
|
+
// lib/agent.ts
|
|
3060
|
+
import { createAgentStack, defineMcpTool } from '@buildbase/sdk/mcp';
|
|
3061
|
+
import { z } from 'zod';
|
|
3062
|
+
|
|
3063
|
+
export const agent = createAgentStack({
|
|
3064
|
+
serverUrl: process.env.NEXT_PUBLIC_BUILDBASE_SERVER_URL!,
|
|
3065
|
+
orgId: process.env.NEXT_PUBLIC_BUILDBASE_ORG_ID!,
|
|
3066
|
+
siteUrl: 'https://imejis.io',
|
|
3067
|
+
site: { name: 'Imejis', description: 'Generate images from templates via API.' },
|
|
3068
|
+
secret: process.env.SYSTEM_SECRET!, // derives buildbaseAuth (verify + aud + sid decrypt)
|
|
3069
|
+
scopes: [{ name: 'render:execute', description: 'Render images' }],
|
|
3070
|
+
mcp: {
|
|
3071
|
+
builtinTools: 'readonly',
|
|
3072
|
+
tools: [
|
|
3073
|
+
defineMcpTool({
|
|
3074
|
+
name: 'generate_image',
|
|
3075
|
+
description: 'Render an image from a template',
|
|
3076
|
+
inputSchema: z.object({ templateId: z.string(), data: z.record(z.string(), z.any()) }),
|
|
3077
|
+
requiredScopes: ['render:execute'],
|
|
3078
|
+
execute: async (input, { bb, workspaceId }) => {
|
|
3079
|
+
await bb.usage.record(workspaceId!, { quotaSlug: 'images', quantity: 1 });
|
|
3080
|
+
return renderTemplate(input.templateId, input.data);
|
|
3081
|
+
},
|
|
3082
|
+
}),
|
|
3083
|
+
],
|
|
3084
|
+
},
|
|
3085
|
+
});
|
|
3086
|
+
|
|
3087
|
+
// app/api/mcp/route.ts
|
|
3088
|
+
export const { GET, POST, DELETE, OPTIONS } = agent.routes;
|
|
3089
|
+
```
|
|
3090
|
+
|
|
3091
|
+
Prefer the primitives (own token format, unified web+agent auth)? Wire `createMcpHandler` + `buildbaseAuth` directly — `buildbaseAuth` does the full verify (HS256, RFC 8707 audience binding, `sid` decrypt) so you never hand-roll it:
|
|
3092
|
+
|
|
3093
|
+
```ts
|
|
3094
|
+
// lib/mcp.ts
|
|
3095
|
+
import BuildBase from '@buildbase/sdk';
|
|
3096
|
+
import { createMcpHandler, defineMcpTool, buildbaseAuth } from '@buildbase/sdk/mcp';
|
|
3097
|
+
|
|
3098
|
+
const buildbase = BuildBase({
|
|
3099
|
+
serverUrl: process.env.BUILDBASE_URL!,
|
|
3100
|
+
orgId: process.env.BUILDBASE_ORG_ID!,
|
|
3101
|
+
});
|
|
3102
|
+
|
|
3103
|
+
export const mcp = createMcpHandler({
|
|
3104
|
+
buildbase,
|
|
3105
|
+
serverInfo: { name: 'imejis', version: '1.0.0' },
|
|
3106
|
+
auth: buildbaseAuth({
|
|
3107
|
+
secret: process.env.SYSTEM_SECRET!,
|
|
3108
|
+
resource: ['https://imejis.io/mcp', 'https://imejis.io/api/mcp'],
|
|
3109
|
+
requireAudience: true,
|
|
3110
|
+
}),
|
|
3111
|
+
builtinTools: 'readonly',
|
|
3112
|
+
tools: [
|
|
3113
|
+
/* defineMcpTool(...) */
|
|
3114
|
+
],
|
|
3115
|
+
});
|
|
3116
|
+
```
|
|
3117
|
+
|
|
3118
|
+
```ts
|
|
3119
|
+
// Express / Fastify / Node — the pure form
|
|
3120
|
+
app.post('/mcp', async (req, res) => {
|
|
3121
|
+
const r = await mcp.handle({ method: req.method, headers: req.headers, body: req.body });
|
|
3122
|
+
res.status(r.status).set(r.headers).send(r.body);
|
|
3123
|
+
});
|
|
3124
|
+
```
|
|
3125
|
+
|
|
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).
|
|
3127
|
+
|
|
3128
|
+
Advertise it via the server card so agents find the endpoint (the stack sets this automatically):
|
|
3129
|
+
|
|
3130
|
+
```ts
|
|
3131
|
+
export const agentConfig: AgentReadyConfig = {
|
|
3132
|
+
// ...
|
|
3133
|
+
mcpServerCard: mcp.serverCard({ endpoint: 'https://imejis.io/api/mcp' }),
|
|
3134
|
+
};
|
|
3135
|
+
```
|
|
3136
|
+
|
|
3137
|
+
### Built-in tools
|
|
3138
|
+
|
|
3139
|
+
The built-ins cover the **full BuildBase surface**. By default (`builtinTools: 'readonly'`) only the read tools are exposed — **least privilege**: no agent can mutate, bill, or delete anything until you opt in. Set `'all'` to add the writes and destructive/billing ops. Every call still runs under the user's session, so the platform enforces the user's real permissions on top of whatever you expose — an agent can never exceed what the user can do.
|
|
3140
|
+
|
|
3141
|
+
- **Read** (`builtinTools: 'readonly'` for just these): `list_workspaces`, `get_workspace`, `get_user_profile`, `list_workspace_users`, `get_subscription`, `get_plans`, `get_plan_versions`, `get_public_plans`, `get_plan_version`, `list_invoices`, `get_invoice`, `get_quota_usage`, `get_all_quota_usage`, `get_usage_logs`, `get_credit_balance`, `list_credit_transactions`, `get_credit_packages`, `get_credit_buckets`, `get_expiring_credits`, `get_public_credit_packages`, `check_feature_flag`, `check_permission`, `resolve_permissions`, `get_settings`.
|
|
3142
|
+
- **Write / destructive**: `record_usage`, `record_usage_batch`, `consume_credits`, `send_notification`, `create_workspace`, `update_workspace`, `delete_workspace`, `invite_workspace_user`, `remove_workspace_user`, `update_workspace_user_role`, `update_user_profile`, `update_feature_flag`, `create_subscription_checkout`, `update_subscription`, `cancel_subscription`, `resume_subscription`, `get_billing_portal_url`, `purchase_credits`.
|
|
3143
|
+
|
|
3144
|
+
**You choose how much to expose** — the default is safe, and you widen it:
|
|
3145
|
+
|
|
3146
|
+
```ts
|
|
3147
|
+
builtinTools: 'readonly'; // default — reads only (least privilege)
|
|
3148
|
+
builtinTools: 'all'; // reads + writes + destructive/billing ops
|
|
3149
|
+
builtinTools: false; // none (custom tools only)
|
|
3150
|
+
builtinTools: {
|
|
3151
|
+
include: ['get_quota_usage', 'record_usage'];
|
|
3152
|
+
} // exactly these
|
|
3153
|
+
builtinTools: {
|
|
3154
|
+
exclude: ['delete_workspace', 'purchase_credits'];
|
|
3155
|
+
} // all reads, minus these (an `include` list can be trimmed the same way)
|
|
3156
|
+
```
|
|
3157
|
+
|
|
3158
|
+
The agent acts as an authenticated app user — it knows your app, not BuildBase — so the built-ins carry **no BuildBase-specific scope requirement**. Workspace-scoped tools take an optional `workspaceId` and fall back to the one pinned on the verified token (`McpAuthInfo.workspaceId`); pin it and cross-workspace access is refused even for tools you exposed.
|
|
3159
|
+
|
|
3160
|
+
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
|
+
|
|
3162
|
+
### Production hardening
|
|
3163
|
+
|
|
3164
|
+
The handler ships with safe defaults; these are the knobs to review before going live:
|
|
3165
|
+
|
|
3166
|
+
- **`builtinTools` defaults to `'readonly'`.** Writes and destructive/billing ops are opt-in. Prefer an explicit `{ include: [...] }` allowlist over `'all'` in production.
|
|
3167
|
+
- **Request-body cap.** Bodies over `maxRequestBytes` (default **1 MiB**) are rejected with 413 before parsing — and, on the `fetch` adapter, before the stream is even read (via `Content-Length`). Set `0` to disable.
|
|
3168
|
+
- **Rate limiting.** The server is stateless and keeps no counters. Supply a `rateLimit(auth, req)` gate — called after auth, before dispatch — backed by your own store (KV/Redis/edge limiter), returning `false` or `{ ok: false, retryAfter }` to answer 429. A failing limiter fails **closed**. Also put platform/edge rate limiting in front of the endpoint.
|
|
3169
|
+
- **Origin allowlist.** Set `allowedOrigins` for DNS-rebinding protection; browser-origin requests with a non-matching `Origin` get 403 (server-to-server requests with no `Origin` still pass).
|
|
3170
|
+
- **Token expiry.** `verifyClientJwt` **requires a numeric `exp` by default** — a token with no expiry never expires. `signClientJwt` always stamps one. Pin `issuer`/`audience` too for defense in depth: `verifyClientJwt(token, secret, { issuer, audience })`.
|
|
3171
|
+
- **Error visibility.** Pass `onError` — it fires for auth-callback, rate-limit, context-factory, tool-execution, and schema-conversion failures.
|
|
3172
|
+
|
|
3173
|
+
```ts
|
|
3174
|
+
export const mcp = createMcpHandler({
|
|
3175
|
+
buildbase,
|
|
3176
|
+
auth: { verify, resourceMetadataUrl },
|
|
3177
|
+
builtinTools: { include: ['get_quota_usage', 'record_usage'] },
|
|
3178
|
+
allowedOrigins: ['https://app.imejis.io'],
|
|
3179
|
+
maxRequestBytes: 512 * 1024,
|
|
3180
|
+
rateLimit: async auth => ({ ok: await limiter.allow(auth?.userId), retryAfter: 30 }),
|
|
3181
|
+
onError: (err, ctx) => logger.error({ err, ...ctx }, 'mcp error'),
|
|
3182
|
+
});
|
|
3183
|
+
```
|
|
3184
|
+
|
|
3185
|
+
### Your tools, your rules — no lock-in
|
|
3186
|
+
|
|
3187
|
+
The SDK accelerates; it never gates. Everything BuildBase-specific is optional:
|
|
3188
|
+
|
|
3189
|
+
- **Standalone mode** — omit `buildbase` entirely and ship an MCP server with only your own tools (built-ins are simply off). Nothing in the protocol layer depends on the BuildBase platform.
|
|
3190
|
+
- **Your own context** — the `context` factory runs once per call and its return value reaches every tool as `ctx.custom`: your Prisma client, your services, whatever your tools need. Type it with the second generic of `defineMcpTool`.
|
|
3191
|
+
|
|
3192
|
+
```ts
|
|
3193
|
+
type AppCtx = { db: PrismaClient };
|
|
3194
|
+
|
|
3195
|
+
const mcp = createMcpHandler({
|
|
3196
|
+
auth: { verify: verifyMyToken }, // any token format — yours
|
|
3197
|
+
context: (): AppCtx => ({ db: prisma }),
|
|
3198
|
+
tools: [
|
|
3199
|
+
defineMcpTool<z.ZodType, AppCtx>({
|
|
3200
|
+
name: 'search_orders',
|
|
3201
|
+
description: 'Search orders in our own database',
|
|
3202
|
+
inputSchema: z.object({ query: z.string() }),
|
|
3203
|
+
execute: (input, ctx) =>
|
|
3204
|
+
ctx.custom.db.order.findMany({ where: { name: { contains: input.query } } }),
|
|
3205
|
+
}),
|
|
3206
|
+
],
|
|
3207
|
+
});
|
|
3208
|
+
```
|
|
3209
|
+
|
|
3210
|
+
- **Override built-ins** — a custom tool named like a built-in (e.g. your own `get_subscription`) replaces it; your definition wins.
|
|
3211
|
+
- **Raw JSON Schema** — tools don't have to use zod; pass a plain JSON Schema object as `inputSchema` and receive arguments as-is.
|
|
3212
|
+
- **Composable transport** — `handle()` is a pure `{method, headers, body} → {status, headers, body}` function. Route around it, wrap it, or serve extra JSON-RPC methods yourself before delegating; you own the endpoint.
|
|
3213
|
+
- **Browser side** — `provideWebMcpTools` (core entry) registers any in-page tools with WebMCP-capable browsers, unrelated to BuildBase state.
|
|
3214
|
+
|
|
3215
|
+
### Auth in one line of plumbing
|
|
3216
|
+
|
|
3217
|
+
The OAuth2 app-bridge flow mints your app's Bearer tokens (see [OAuth2 App Bridge](#oauth2-app-bridge)). Use the presets and neither the mint nor the verify is hand-rolled — the per-user BuildBase session rides as an **encrypted `sid`** claim (JWTs are signed, not encrypted), never stored, expiring with the token:
|
|
3218
|
+
|
|
3219
|
+
```ts
|
|
3220
|
+
// mint (applicationTokenUrl) — HS256 with your secret, aud from the grant, encrypted sid
|
|
3221
|
+
import { mintAgentToken } from '@buildbase/sdk';
|
|
3222
|
+
mintToken: (claims) => mintAgentToken({ claims, secret: process.env.SYSTEM_SECRET! }),
|
|
3223
|
+
|
|
3224
|
+
// verify (MCP server) — HS256 + RFC 8707 audience + sid decrypt → McpAuthInfo
|
|
3225
|
+
import { buildbaseAuth } from '@buildbase/sdk/mcp';
|
|
3226
|
+
auth: buildbaseAuth({ secret: process.env.SYSTEM_SECRET!, resource: 'https://imejis.io/mcp' }),
|
|
3227
|
+
```
|
|
3228
|
+
|
|
3229
|
+
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`.
|
|
3230
|
+
|
|
3231
|
+
**Exports:** `createAgentStack`, `createMcpHandler`, `defineMcpTool`, `builtinMcpTools`, `selectBuiltinTools`, `buildbaseAuth`, `mintAgentToken`, `createSessionRefCrypto`, plus re-exported auth helpers (`signClientJwt`, `verifyClientJwt`, `extractBearerToken`, `bearerChallenge`, `AppBridgeError`). **Types:** `AgentStack`, `AgentStackConfig`, `CreateMcpHandlerConfig`, `McpHandler`, `McpToolDefinition`, `McpToolContext`, `McpAuthInfo`, `McpHttpRequest`, `McpHttpResponse`, `McpToolAnnotations`, `McpBuildBaseClient`, `BuiltinMcpToolName`, `BuildBaseAuthOptions`, `MintAgentTokenOptions`, `SessionRefCrypto`, `McpServerCard`, `ScopedActions`.
|
|
3232
|
+
|
|
3233
|
+
### Scopes & resources — restrict or open up
|
|
3234
|
+
|
|
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).
|
|
2990
3236
|
|
|
2991
3237
|
## OAuth2 App Bridge
|
|
2992
3238
|
|
|
@@ -2997,6 +3243,8 @@ BuildBase runs the full OAuth2 authorization flow (login, consent, code, PKCE, r
|
|
|
2997
3243
|
|
|
2998
3244
|
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.
|
|
2999
3245
|
|
|
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).
|
|
3247
|
+
|
|
3000
3248
|
```ts
|
|
3001
3249
|
// applicationTokenUrl handler (Next.js Pages Router)
|
|
3002
3250
|
import { handleAppTokenRequest } from '@buildbase/sdk';
|
|
@@ -3080,7 +3328,9 @@ return res.json({ id: claims.id, email: claims.email /* … */ });
|
|
|
3080
3328
|
|
|
3081
3329
|
The one-call handlers cover the common path; the underlying pieces are exported for custom flows: `verifyAppTokenRequest` / `verifyAppRevokeRequest` (verify → claims), `verifyClientJwt`, `extractBearerToken`, and the response builders `appTokenSuccess` / `appTokenFailure`. Verification throws `AppBridgeError` (with a machine-readable `code` like `invalid_signature`, `token_expired`, `invalid_algorithm`). HMAC verification uses the SDK's dependency-free pure-JS HMAC-SHA256, so it behaves identically under ESM, CJS, a bundler, or bare Node.
|
|
3082
3330
|
|
|
3083
|
-
**
|
|
3331
|
+
**Presets** (also on the core entry): `mintAgentToken` (the whole mint — HS256 + `aud` + encrypted `sid`), `buildbaseAuth` (the whole verify), `createSessionRefCrypto` (the `sid` AES-256-GCM crypto). The platform relays the minted token but never sees your secret — it can neither forge nor decode your tokens.
|
|
3332
|
+
|
|
3333
|
+
**Exports:** `handleAppTokenRequest`, `handleAppRevokeRequest`, `verifyAppTokenRequest`, `verifyAppRevokeRequest`, `verifyClientJwt`, `signClientJwt`, `extractBearerToken`, `bearerChallenge`, `appTokenSuccess`, `appTokenFailure`, `mintAgentToken`, `buildbaseAuth`, `createSessionRefCrypto`, `AppBridgeError`. **Types:** `AppTokenRequestClaims`, `AppRevokeRequestClaims`, `AppTokenResult`, `AppTokenResponseBody`, `HandlerResult`, `VerifyClientJwtOptions`, `BuildBaseAuthOptions`, `MintAgentTokenOptions`, `SessionRefCrypto`.
|
|
3084
3334
|
|
|
3085
3335
|
## 🤝 Contributing
|
|
3086
3336
|
|