@layers/amba-mcp 4.0.3 → 4.0.5

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.
@@ -80,13 +80,6 @@ export type ToolVerbClass = 'read' | 'destructive' | 'write';
80
80
  * `amba_` prefix. Exported for the test suite.
81
81
  */
82
82
  export declare function toolNameTokens(name: string): string[];
83
- /**
84
- * Classify a tool name into its behavioural class by PRECEDENCE
85
- * (destructive > read > write). Returns `null` if no token matches any
86
- * known verb — that should never happen for a real Amba tool and the
87
- * test suite asserts it doesn't, so a `null` at runtime means a new tool
88
- * used an unrecognised verb and needs a verb-set entry here.
89
- */
90
83
  export declare function classifyToolVerb(name: string): ToolVerbClass | null;
91
84
  /**
92
85
  * Derive a human-readable Title-Case title from the tool name. The
@@ -13,6 +13,6 @@
13
13
  * Drift gate in `amba-setup.test.ts`. No vendor leakage (Postgres /
14
14
  * Temporal / R2 names scrubbed before exposing on the wire).
15
15
  */
16
- export declare const AMBA_SETUP_INFRASTRUCTURE_MD = "# Infrastructure\n\nThe plumbing that sits behind every other surface: custom database tables (Collections \u2014 schema-first, per-tenant), serverless functions (run server-side code without standing up a backend), analytics (events + sessions), AI prompts (managed LLM templates, callable from the SDK with per-tenant keys), secrets, runtime configs, feature flags, third-party integrations (RevenueCat / Superwall / Resend / Stripe / etc.), media (file storage + CDN), and sites (static asset hosting at `*.app.amba.host`).\n\nIf gamification, economy, and social are the playable surface, **infrastructure is what you build a custom product on top of**. Anything that doesn't fit the canned surfaces lands here.\n\n## MCP tools\n\n### Collections (typed tables)\n\nA collection is a schema-first table inside the project's isolated tenant database. You describe the columns, the server creates the table and any indexes. Rows are scoped to the signed-in `app_user` automatically (server-enforced auto row-level isolation) for SDK clients \u2014 admin tools bypass this.\n\nAdmin tools authenticate the developer/agent (pass `pat` or send it as the inbound Bearer) and take `project_id`. Client tools authenticate an end-user and take `api_key` (+ `session_token`) \u2014 NOT `project_id` and NOT a `pat`. Every row tool names the collection with `name`, never `collection`.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_collections_create` | Create a typed collection. Pass `shared: true` for developer-seeded GLOBAL content (question banks, lookup tables) so `user_id` is nullable. | `{ project_id, name: \"todos\", columns: [{ name: \"title\", type: \"text\", nullable: false }, { name: \"done\", type: \"boolean\", nullable: false, default: false }, { name: \"due_at\", type: \"timestamptz\", nullable: true }], shared: false }` |\n| `amba_collections_list` | List collections in this project. | `{ project_id }` |\n| `amba_collections_get` | Read one collection's schema. | `{ project_id, name: \"todos\" }` |\n| `amba_collections_alter` | Exactly ONE of: `add_column`, `add_index`, `drop_column`, or `relax_user_id` per call. `relax_user_id: true` converts an existing collection to shared (drops the `user_id` NOT NULL). | `{ project_id, name: \"todos\", add_column: { name: \"priority\", type: \"int\", nullable: true } }` |\n| `amba_collections_delete` | Drop the table (destructive). `confirm` must equal the collection name. | `{ project_id, name: \"todos\", confirm: \"todos\" }` |\n| `amba_admin_insert_row` | Insert one row as the developer (bypasses user-scope; `user_id` honored if present). | `{ project_id, name: \"todos\", row: { title: \"Sample\", done: false } }` |\n| `amba_admin_insert_rows` | Bulk-insert up to 500 rows in one atomic statement \u2014 the canonical seeding/migration path. `on_conflict`: `\"error\"` (default) or `\"skip\"`. | `{ project_id, name: \"questions\", rows: [{ q: \"...\" }, { q: \"...\" }], on_conflict: \"skip\" }` |\n| `amba_admin_list_rows` | Read rows as the developer. | `{ project_id, name: \"todos\", limit: 100 }` |\n| `amba_client_insert_row` | Insert as an end-user. Requires `api_key` (+ `session_token`). | `{ api_key, session_token, name: \"todos\", row: {...} }` |\n| `amba_client_list_rows` | Read as an end-user (auto user-scoped). | `{ api_key, session_token, name: \"todos\" }` |\n| `amba_client_get_row` | Get one row by id (end-user). | `{ api_key, session_token, name: \"todos\", id }` |\n| `amba_client_update_row` | Update one row by id (end-user). Fields go in `set`. Omit `id` + pass `where` for a bulk update. | `{ api_key, session_token, name: \"todos\", id, set: {...} }` |\n| `amba_client_delete_row` | Soft-delete one row by id (end-user). | `{ api_key, session_token, name: \"todos\", id }` |\n| `amba_client_count_rows` | Count rows matching an optional `where`. | `{ api_key, session_token, name: \"todos\", where: {...} }` |\n| `amba_client_find_rows` | Filter / sort / paginate rows (SDK-shaped `filter`). | `{ api_key, session_token, name: \"todos\", filter: {...}, order: [\"created_at desc\"], limit: 50 }` |\n| `amba_client_find_nearest_rows` | Vector-similarity search (rows with a `vector(<dim>)` column). | `{ api_key, session_token, name: \"todos\", column: \"embedding\", to_vector: [...], k: 10 }` |\n\nColumn types: `text`, `int`, `bigint`, `float`, `boolean`, `timestamptz`, `date`, `json`, `jsonb`, `uuid`, `vector(<dim>)` (e.g. `vector(1536)` for OpenAI embeddings).\n\n### Functions (serverless code)\n\nRun user code in a sandbox triggered by HTTP, cron, or webhook. The function gets the tenant connection automatically via injected env.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_functions_deploy` | Deploy a function from source. | `{ project_id, name: \"send_welcome_email\", runtime: \"node22\", source: \"export default async (req) => { ... }\", trigger: { type: \"http\" } }` |\n| `amba_functions_list` | List functions. | `{ project_id }` |\n| `amba_functions_get` | Read function metadata. | `{ project_id, function_id }` |\n| `amba_functions_get_logs` | Recent invocation logs. | `{ project_id, function_id, limit: 100 }` |\n| `amba_functions_delete` | Delete a function. | `{ project_id, function_id }` |\n| `amba_functions_schedule` | Attach a cron schedule. | `{ project_id, function_id, cron: \"0 9 * * *\", timezone: \"America/Los_Angeles\" }` |\n| `amba_functions_pause_schedule` | Pause a scheduled trigger without deleting it. | `{ project_id, function_id }` |\n| `amba_functions_resume_schedule` | Resume. | `{ project_id, function_id }` |\n| `amba_functions_trigger_schedule` | Fire a scheduled function ad-hoc (testing). | `{ project_id, function_id }` |\n\n### AI prompts\n\nManaged LLM templates: stored prompt with model + system message + variables, callable by name from the SDK. The actual LLM call is rewritten per-tenant \u2014 the customer's API keys (Anthropic / OpenAI) live in the tenant secrets, never on the device.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_ai_prompts_create` | Create a prompt template. | `{ project_id, key: \"summarize\", model: \"claude-opus-4-5\", system: \"Summarize the user's text in 2 sentences.\", variables: [\"text\"] }` |\n| `amba_ai_prompts_list` | List prompts. | `{ project_id }` |\n| `amba_ai_prompts_get` | Read one prompt. | `{ project_id, key }` |\n| `amba_ai_prompts_update` | Edit a prompt. | `{ project_id, key, system: \"...\" }` |\n| `amba_ai_prompts_invoke` | Invoke a prompt server-side (admin testing). | `{ project_id, key, variables: { text: \"...\" } }` |\n| `amba_ai_prompts_delete` | Delete. | `{ project_id, key }` |\n\n### Analytics + events + sessions\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_analytics_get` | Top-level metrics dashboard (MAU, DAU, retention). | `{ project_id, period: \"7d\" }` |\n| `amba_events_list` | Browse raw events. | `{ project_id, limit: 100, since: \"2026-05-19T00:00:00Z\" }` |\n| `amba_events_count` | Count events matching a filter. | `{ project_id, event: \"workout_completed\", since: \"...\" }` |\n| `amba_sessions_list` | List user sessions. | `{ project_id, limit: 50 }` |\n| `amba_sessions_analytics` | Session-level metrics. | `{ project_id, period: \"7d\" }` |\n| `amba_users_list_events` | Per-user event history. | `{ project_id, user_id }` |\n| `amba_users_export` | Export the full user list. | `{ project_id, format: \"csv\" }` |\n\n### Secrets + configs + integrations\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_secrets_set` | Set a tenant secret (encrypted at rest). | `{ project_id, name: \"OPENAI_API_KEY\", value: \"sk-...\" }` |\n| `amba_secrets_get` | Read a secret (returns `\"<redacted>\"` unless explicitly requested). | `{ project_id, name }` |\n| `amba_secrets_list` | List secret names. | `{ project_id }` |\n| `amba_secrets_delete` | Delete. | `{ project_id, name }` |\n| `amba_configs_create` | Create a runtime config value (read from SDK as `Amba.config.fetch()`). | `{ project_id, key: \"primary_color\", value: \"#ff0066\", segment_id: null }` |\n| `amba_configs_list` | List configs. | `{ project_id }` |\n| `amba_configs_update` | Edit. | `{ project_id, config_id, value: \"...\" }` |\n| `amba_configs_delete` | Delete. | `{ project_id, config_id }` |\n| `amba_integrations_list` | List third-party integrations. | `{ project_id }` |\n| `amba_integrations_configure` | Configure a provider. | `{ project_id, provider: \"revenuecat\", config: { webhook_secret: \"...\", default_offering: \"...\" } }` |\n| `amba_integrations_set` | Set/replace integration config wholesale. | `{ project_id, provider, config }` |\n| `amba_integrations_patch` | Patch one field. | `{ project_id, provider, patch: { webhook_secret: \"...\" } }` |\n| `amba_integrations_test` | Send a test event to a configured provider. | `{ project_id, provider }` |\n\n### Media (file storage + CDN)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_media_upload` | Upload a file (returns a tenant-scoped URL). | `{ project_id, name: \"logo.png\", content_type: \"image/png\", data: \"<base64>\" }` |\n| `amba_media_list` | List files. | `{ project_id, folder: \"/\", limit: 100 }` |\n| `amba_media_delete` | Delete a file. | `{ project_id, file_id }` |\n| `amba_media_create_folder` | Create a logical folder. | `{ project_id, path: \"/uploads/avatars\" }` |\n| `amba_media_list_folders` | List folders. | `{ project_id }` |\n| `amba_media_delete_folder` | Delete a folder (must be empty). | `{ project_id, path }` |\n\n### Sites (static asset hosting)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_sites_deploy` | Deploy a static site bundle (zip / tar). | `{ project_id, name: \"marketing\", bundle: \"<base64>\", index: \"index.html\" }` |\n| `amba_sites_list` | List sites. | `{ project_id }` |\n| `amba_sites_get` | Read a site. | `{ project_id, site_id }` |\n| `amba_sites_add_domain` | Attach a custom domain. | `{ project_id, site_id, domain: \"marketing.example.com\" }` |\n| `amba_sites_list_domains` | List domains on a site. | `{ project_id, site_id }` |\n| `amba_sites_remove_domain` | Detach a domain. | `{ project_id, site_id, domain }` |\n| `amba_sites_delete` | Delete a site. | `{ project_id, site_id }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first. The infrastructure surfaces \u2014 collections, AI, config, flags, events \u2014 are SDK-side reads; the snippets below show what the client calls look like.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\n// Collections \u2014 typed table, user-scoped reads + writes\ntype Todo = { id: string; title: string; done: boolean; created_at: string };\n\nconst { data: todos } = await Amba.collections.find<Todo>('todos', {\n filter: Amba.collections.where.eq('done', false),\n order: [{ column: 'created_at', direction: 'desc' }],\n limit: 50,\n});\n\nconst newTodo = await Amba.collections.insert('todos', { title: 'Ship the app', done: false });\nawait Amba.collections.update('todos', newTodo.id, { done: true });\nawait Amba.collections.delete('todos', newTodo.id);\n\n// AI \u2014 call a managed prompt\nconst response = await Amba.ai.anthropic.messages.create({\n prompt_key: 'summarize',\n variables: { text: 'A long article about backend services \u2026' },\n});\n\n// Track an analytics event\nawait Amba.events.track('button_clicked', { button: 'cta' });\n\n// Read runtime config\nconst config = await Amba.config.fetch();\n\n// Read a feature flag\nconst showBeta = await Amba.flags.get('beta_feature');\n\n// Diagnostics \u2014 wire-verify\nconst ping = await Amba.diagnostics.ping();\nif (!ping.ok) console.error('Amba misconfigured:', ping);\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst { data: todos } = await Amba.collections.find('todos', {\n filter: Amba.collections.where.eq('done', false),\n limit: 50,\n});\nawait Amba.collections.insert('todos', { title: 'Ship', done: false });\nawait Amba.events.track('page_view', { path: location.pathname });\n```\n\nWith `@layers/amba-react`:\n\n```tsx\nimport { useCollection, useFlag } from '@layers/amba-react';\n\nfunction TodoList() {\n const { data: todos, loading, refetch } = useCollection<{ id: string; title: string }>('todos');\n const showArchive = useFlag('archive_todos');\n if (loading) return <Spinner />;\n return (\n <ul>\n {todos?.map(t => <li key={t.id}>{t.title}</li>)}\n {showArchive && <ArchiveButton onArchive={refetch} />}\n </ul>\n );\n}\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nstruct Todo: Codable {\n let id: String\n let title: String\n let done: Bool\n}\n\nlet response = try await Amba.collections.find(\"todos\", as: Todo.self)\n_ = try await Amba.collections.insert(\"todos\", row: [\"title\": \"Ship\", \"done\": false])\n\nlet config = try await Amba.config.fetch()\nlet showBeta = try await Amba.flags.get(name: \"beta_feature\")\ntry await Amba.events.track(\"app_opened\", properties: [\"source\": \"deep_link\"])\n\nlet reply = try await Amba.ai.anthropic.messages.create(\n promptKey: \"summarize\",\n variables: [\"text\": \"A long article...\"]\n)\n```\n\n### Android (Kotlin)\n\n```kotlin\ndata class Todo(val id: String, val title: String, val done: Boolean)\n\nval todos = Amba.collections.find<Todo>(\"todos\")\nAmba.collections.insert(\"todos\", mapOf(\"title\" to \"Ship\", \"done\" to false))\n\nval config = Amba.config.fetch()\nval showBeta = Amba.flags.get(\"beta_feature\")\nAmba.events.track(\"app_opened\", mapOf(\"source\" to \"deep_link\"))\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal response = await Amba.collections.find('todos', limit: 50);\nawait Amba.collections.insert('todos', {'title': 'Ship', 'done': false});\nfinal config = await Amba.config.fetch();\nfinal showBeta = await Amba.flags.get('beta_feature');\nawait Amba.events.track('app_opened', {'source': 'deep_link'});\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Custom data tables (collections):** any domain-specific tables to create?\n - Yes \u2014 I'll list them. (For each: name + columns + types.)\n - No, just use the canned Amba surfaces (auth, push, gamification, etc.)\n - Auto-create from the existing code's models \u2014 read `lib/models/`, `src/types/`, `Models/`, infer column lists, confirm with me.\n\n2. **Custom backend logic (functions):** any server-side code to deploy?\n - Yes \u2014 describe what it should do. (Then offer to scaffold a function template and deploy.)\n - No\n\n3. **AI features:** want managed LLM prompts?\n - Yes \u2014 what's the use case? (summarize, translate, classify, generate, custom)\n - No\n\n4. **Analytics:** which tracker do you want?\n - Only Amba's built-in events (recommended \u2014 already wired)\n - Amba + Mixpanel / PostHog / Segment forwarding (configure via `amba_integrations_configure`)\n - None (rarely useful \u2014 events drive XP / achievements / streaks; disabling cripples gamification)\n\n5. **Third-party integrations to set up:**\n - [ ] RevenueCat (IAP / subscriptions on iOS + Android)\n - [ ] Superwall (paywall A/B)\n - [ ] Resend (transactional email)\n - [ ] Stripe (web payments / subscriptions)\n - [ ] Mixpanel / PostHog / Segment (analytics forwarding)\n - [ ] OpenAI / Anthropic (LLM keys \u2014 required for `Amba.ai.*` calls)\n\n6. **Feature flags:** seed any starter flags?\n - Yes \u2014 wire `beta_feature` (off by default) so I can ship the wiring before the feature exists\n - No\n\n7. **Static site:** want a marketing page hosted under your tenant subdomain?\n - Yes \u2014 scaffold and deploy a 1-page index\n - No\n\n## Re-run behavior\n\n1. Before creating:\n - `amba_collections_list` \u2014 match on `name`. Collisions: never silently recreate (data loss). Offer `amba_collections_alter` to add new columns instead.\n - `amba_functions_list` \u2014 match on `name`. Collisions: ask to redeploy (with the new source) or skip.\n - `amba_ai_prompts_list` \u2014 match on `key`. Same.\n - `amba_integrations_list` \u2014 match on `provider`. Same.\n - `amba_configs_list` \u2014 match on `key`. Same.\n\n2. **Never call `amba_collections_delete` on re-run unless the user explicitly asks** \u2014 this drops the underlying table and every row in it across every user of the tenant.\n\n3. For functions: re-deploying replaces source in place (versioned server-side). It's safe to call `amba_functions_deploy` with the same name + new source.\n\n4. For integrations: if a provider is already configured, prefer `amba_integrations_patch` (partial update) over `amba_integrations_set` (full replace).\n\n5. Secrets: don't list secret values in chat output, even on read. Just confirm \"OPENAI_API_KEY is set\" / \"not set\".\n";
16
+ export declare const AMBA_SETUP_INFRASTRUCTURE_MD = "# Infrastructure\n\nThe plumbing that sits behind every other surface: custom database tables (Collections \u2014 schema-first, per-tenant), serverless functions (run server-side code without standing up a backend), analytics (events + sessions), AI prompts (managed LLM templates, callable from the SDK with per-tenant keys), secrets, runtime configs, feature flags, third-party integrations (RevenueCat / Superwall / Resend / Stripe / etc.), media (file storage + CDN), and sites (static asset hosting at `*.app.amba.host`).\n\nIf gamification, economy, and social are the playable surface, **infrastructure is what you build a custom product on top of**. Anything that doesn't fit the canned surfaces lands here.\n\n## MCP tools\n\n### Collections (typed tables)\n\nA collection is a schema-first table inside the project's isolated tenant database. You describe the columns, the server creates the table and any indexes. Rows are scoped to the signed-in `app_user` automatically (server-enforced auto row-level isolation) for SDK clients \u2014 admin tools bypass this.\n\nAdmin tools authenticate the developer/agent (pass `pat` or send it as the inbound Bearer) and take `project_id`. Client tools authenticate an end-user and take `api_key` (+ `session_token`) \u2014 NOT `project_id` and NOT a `pat`. Every row tool names the collection with `name`, never `collection`.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_collections_create` | Create a typed collection. Pass `shared: true` for developer-seeded GLOBAL content (question banks, lookup tables) so `user_id` is nullable. | `{ project_id, name: \"todos\", columns: [{ name: \"title\", type: \"text\", nullable: false }, { name: \"done\", type: \"boolean\", nullable: false, default: false }, { name: \"due_at\", type: \"timestamptz\", nullable: true }], shared: false }` |\n| `amba_collections_list` | List collections in this project. | `{ project_id }` |\n| `amba_collections_get` | Read one collection's schema. | `{ project_id, name: \"todos\" }` |\n| `amba_collections_alter` | Exactly ONE of: `add_column`, `add_index`, `drop_column`, or `relax_user_id` per call. `relax_user_id: true` converts an existing collection to shared (drops the `user_id` NOT NULL). | `{ project_id, name: \"todos\", add_column: { name: \"priority\", type: \"integer\", nullable: true } }` |\n| `amba_collections_delete` | Drop the table (destructive). `confirm` must equal the collection name. | `{ project_id, name: \"todos\", confirm: \"todos\" }` |\n| `amba_admin_insert_row` | Insert one row as the developer (bypasses user-scope; `user_id` honored if present). | `{ project_id, name: \"todos\", row: { title: \"Sample\", done: false } }` |\n| `amba_admin_insert_rows` | Bulk-insert up to 500 rows in one atomic statement \u2014 the canonical seeding/migration path. `on_conflict`: `\"error\"` (default) or `\"skip\"`. | `{ project_id, name: \"questions\", rows: [{ q: \"...\" }, { q: \"...\" }], on_conflict: \"skip\" }` |\n| `amba_admin_list_rows` | Read rows as the developer. | `{ project_id, name: \"todos\", limit: 100 }` |\n| `amba_client_insert_row` | Insert as an end-user. Requires `api_key` (+ `session_token`). | `{ api_key, session_token, name: \"todos\", row: {...} }` |\n| `amba_client_list_rows` | Read as an end-user (auto user-scoped). | `{ api_key, session_token, name: \"todos\" }` |\n| `amba_client_get_row` | Get one row by id (end-user). | `{ api_key, session_token, name: \"todos\", id }` |\n| `amba_client_update_row` | Update one row by id (end-user). Fields go in `set`. Omit `id` + pass `where` for a bulk update. | `{ api_key, session_token, name: \"todos\", id, set: {...} }` |\n| `amba_client_delete_row` | Soft-delete one row by id (end-user). | `{ api_key, session_token, name: \"todos\", id }` |\n| `amba_client_count_rows` | Count rows matching an optional `where`. | `{ api_key, session_token, name: \"todos\", where: {...} }` |\n| `amba_client_find_rows` | Filter / sort / paginate rows (SDK-shaped `filter`). | `{ api_key, session_token, name: \"todos\", filter: {...}, order: [\"created_at desc\"], limit: 50 }` |\n| `amba_client_find_nearest_rows` | Vector-similarity search (rows with a `vector(<dim>)` column). | `{ api_key, session_token, name: \"todos\", column: \"embedding\", to_vector: [...], k: 10 }` |\n\nColumn types: `text`, `integer`, `bigint`, `numeric`, `boolean`, `timestamptz`, `date`, `jsonb`, `uuid`, `vector(<dim>)` (e.g. `vector(1536)` for OpenAI embeddings), plus array forms `text[]`, `integer[]`, `bigint[]`, `numeric[]`, `boolean[]`, `uuid[]`. Use `integer` (not `int`), `numeric` (not `float`/`real`/`double`), and `jsonb` (not `json`) \u2014 the validator rejects the aliases.\n\n### Functions (serverless code)\n\nRun user code in a sandbox triggered by HTTP, cron, or webhook. The function gets the tenant connection automatically via injected env.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_functions_deploy` | Deploy a function from source. | `{ project_id, name: \"send_welcome_email\", runtime: \"node22\", source: \"export default async (req) => { ... }\", trigger: { type: \"http\" } }` |\n| `amba_functions_list` | List functions. | `{ project_id }` |\n| `amba_functions_get` | Read function metadata. | `{ project_id, function_id }` |\n| `amba_functions_get_logs` | Recent invocation logs. | `{ project_id, function_id, limit: 100 }` |\n| `amba_functions_delete` | Delete a function. | `{ project_id, function_id }` |\n| `amba_functions_schedule` | Attach a cron schedule. | `{ project_id, function_id, cron: \"0 9 * * *\", timezone: \"America/Los_Angeles\" }` |\n| `amba_functions_pause_schedule` | Pause a scheduled trigger without deleting it. | `{ project_id, function_id }` |\n| `amba_functions_resume_schedule` | Resume. | `{ project_id, function_id }` |\n| `amba_functions_trigger_schedule` | Fire a scheduled function ad-hoc (testing). | `{ project_id, function_id }` |\n\n### AI prompts\n\nManaged LLM templates: a stored prompt with provider + model + system message, invoked by name from the SDK. The actual LLM call is rewritten server-side per-tenant \u2014 the customer's provider API key (Anthropic / OpenAI / Mistral / Gemini) stays server-side, never on the device.\n\n**Two steps, in order:** first register the provider key with `amba_ai_providers_set`, then create prompts against it. A prompt registered before its provider has a key still saves, but invocations fail with `provider_not_configured` (424) until the key is set.\n\n> The provider key is **not** a function secret. `amba_secrets_set` writes function-scoped Worker secrets, which the AI gateway never reads. Provider keys live in a separate gateway-owned store and are set **only** via `amba_ai_providers_set`.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_ai_providers_set` | Register / rotate the upstream provider API key. **Do this first.** | `{ project_id, provider: \"anthropic\", api_key: \"sk-ant-...\" }` |\n| `amba_ai_providers_list` | List registered providers (`configured` = key set). | `{ project_id }` |\n| `amba_ai_providers_delete` | Revoke a provider key (fails if prompts still reference it). | `{ project_id, provider: \"anthropic\" }` |\n| `amba_ai_prompts_create` | Create a prompt template. `client_invokable: true` lets the device SDK invoke it directly. | `{ project_id, name: \"summarize\", provider: \"anthropic\", model: \"claude-opus-4-5\", system_prompt: \"Summarize the user's text in 2 sentences.\", client_invokable: true }` |\n| `amba_ai_prompts_list` | List prompts. | `{ project_id }` |\n| `amba_ai_prompts_get` | Read one prompt. | `{ project_id, name }` |\n| `amba_ai_prompts_update` | Edit a prompt (replaces all fields; bumps version). | `{ project_id, name, provider, model, system_prompt: \"...\" }` |\n| `amba_ai_prompts_invoke` | Invoke a prompt server-side (admin testing). `messages` is a provider-shaped array. | `{ project_id, name, messages: [{ role: \"user\", content: \"...\" }] }` |\n| `amba_ai_prompts_delete` | Delete. | `{ project_id, name }` |\n\n### Analytics + events + sessions\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_analytics_get` | Top-level metrics dashboard (MAU, DAU, retention). | `{ project_id, period: \"7d\" }` |\n| `amba_events_list` | Browse raw events. | `{ project_id, limit: 100, since: \"2026-05-19T00:00:00Z\" }` |\n| `amba_events_count` | Count events matching a filter. | `{ project_id, event: \"workout_completed\", since: \"...\" }` |\n| `amba_sessions_list` | List user sessions. | `{ project_id, limit: 50 }` |\n| `amba_sessions_analytics` | Session-level metrics. | `{ project_id, period: \"7d\" }` |\n| `amba_users_list_events` | Per-user event history. | `{ project_id, user_id }` |\n| `amba_users_export` | Export the full user list. | `{ project_id, format: \"csv\" }` |\n\n### Secrets + configs + integrations\n\nSecrets here are **function-scoped** \u2014 they become environment bindings on your deployed functions. They are NOT where AI provider keys go (use `amba_ai_providers_set` for those \u2014 see AI prompts above).\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_secrets_set` | Set a function-scoped secret (encrypted at rest; bound on the next deploy). | `{ project_id, name: \"STRIPE_WEBHOOK_SECRET\", value: \"whsec_...\" }` |\n| `amba_secrets_get` | Read a secret (returns `\"<redacted>\"` unless explicitly requested). | `{ project_id, name }` |\n| `amba_secrets_list` | List secret names. | `{ project_id }` |\n| `amba_secrets_delete` | Delete. | `{ project_id, name }` |\n| `amba_configs_create` | Create a runtime config value (read from SDK as `Amba.config.fetch()`). | `{ project_id, key: \"primary_color\", value: \"#ff0066\", segment_id: null }` |\n| `amba_configs_list` | List configs. | `{ project_id }` |\n| `amba_configs_update` | Edit. | `{ project_id, config_id, value: \"...\" }` |\n| `amba_configs_delete` | Delete. | `{ project_id, config_id }` |\n| `amba_integrations_list` | List third-party integrations. | `{ project_id }` |\n| `amba_integrations_configure` | Configure a provider. | `{ project_id, provider: \"revenuecat\", config: { webhook_secret: \"...\", default_offering: \"...\" } }` |\n| `amba_integrations_set` | Set/replace integration config wholesale. | `{ project_id, provider, config }` |\n| `amba_integrations_patch` | Patch one field. | `{ project_id, provider, patch: { webhook_secret: \"...\" } }` |\n| `amba_integrations_test` | Send a test event to a configured provider. | `{ project_id, provider }` |\n\n### Media (file storage + CDN)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_media_upload` | Upload a file (returns a tenant-scoped URL). | `{ project_id, name: \"logo.png\", content_type: \"image/png\", data: \"<base64>\" }` |\n| `amba_media_list` | List files. | `{ project_id, folder: \"/\", limit: 100 }` |\n| `amba_media_delete` | Delete a file. | `{ project_id, file_id }` |\n| `amba_media_create_folder` | Create a logical folder. | `{ project_id, path: \"/uploads/avatars\" }` |\n| `amba_media_list_folders` | List folders. | `{ project_id }` |\n| `amba_media_delete_folder` | Delete a folder (must be empty). | `{ project_id, path }` |\n\n### Sites (static asset hosting)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_sites_deploy` | Deploy a static site bundle (zip / tar). | `{ project_id, name: \"marketing\", bundle: \"<base64>\", index: \"index.html\" }` |\n| `amba_sites_list` | List sites. | `{ project_id }` |\n| `amba_sites_get` | Read a site. | `{ project_id, site_id }` |\n| `amba_sites_add_domain` | Attach a custom domain. | `{ project_id, site_id, domain: \"marketing.example.com\" }` |\n| `amba_sites_list_domains` | List domains on a site. | `{ project_id, site_id }` |\n| `amba_sites_remove_domain` | Detach a domain. | `{ project_id, site_id, domain }` |\n| `amba_sites_delete` | Delete a site. | `{ project_id, site_id }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first. The infrastructure surfaces \u2014 collections, AI, config, flags, events \u2014 are SDK-side reads; the snippets below show what the client calls look like.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\n// Collections \u2014 typed table, user-scoped reads + writes\ntype Todo = { id: string; title: string; done: boolean; created_at: string };\n\nconst { data: todos } = await Amba.collections.find<Todo>('todos', {\n filter: Amba.collections.where.eq('done', false),\n order: [{ column: 'created_at', direction: 'desc' }],\n limit: 50,\n});\n\nconst newTodo = await Amba.collections.insert('todos', { title: 'Ship the app', done: false });\nawait Amba.collections.update('todos', newTodo.id, { done: true });\nawait Amba.collections.delete('todos', newTodo.id);\n\n// AI \u2014 call a managed prompt (prompt_slug names the registered prompt)\nconst response = await Amba.ai.anthropic.messages.create({\n prompt_slug: 'summarize',\n variables: { text: 'A long article about backend services \u2026' },\n});\n\n// Track an analytics event\nawait Amba.events.track('button_clicked', { button: 'cta' });\n\n// Read runtime config\nconst config = await Amba.config.fetch();\n\n// Read a feature flag\nconst showBeta = await Amba.flags.get('beta_feature');\n\n// Diagnostics \u2014 wire-verify\nconst ping = await Amba.diagnostics.ping();\nif (!ping.ok) console.error('Amba misconfigured:', ping);\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst { data: todos } = await Amba.collections.find('todos', {\n filter: Amba.collections.where.eq('done', false),\n limit: 50,\n});\nawait Amba.collections.insert('todos', { title: 'Ship', done: false });\nawait Amba.events.track('page_view', { path: location.pathname });\n```\n\nWith `@layers/amba-react`:\n\n```tsx\nimport { useCollection, useFlag } from '@layers/amba-react';\n\nfunction TodoList() {\n const { data: todos, loading, refetch } = useCollection<{ id: string; title: string }>('todos');\n const showArchive = useFlag('archive_todos');\n if (loading) return <Spinner />;\n return (\n <ul>\n {todos?.map(t => <li key={t.id}>{t.title}</li>)}\n {showArchive && <ArchiveButton onArchive={refetch} />}\n </ul>\n );\n}\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nstruct Todo: Codable {\n let id: String\n let title: String\n let done: Bool\n}\n\nlet response = try await Amba.collections.find(\"todos\", as: Todo.self)\n_ = try await Amba.collections.insert(\"todos\", row: [\"title\": \"Ship\", \"done\": false])\n\nlet config = try await Amba.config.fetch()\nlet showBeta = try await Amba.flags.get(name: \"beta_feature\")\ntry await Amba.events.track(\"app_opened\", properties: [\"source\": \"deep_link\"])\n\nlet reply = try await Amba.ai.anthropic.messages.create(\n request: AiMessageRequest(promptSlug: \"summarize\", variables: [\"text\": \"A long article...\"])\n)\n```\n\n### Android (Kotlin)\n\n```kotlin\ndata class Todo(val id: String, val title: String, val done: Boolean)\n\nval todos = Amba.collections.find<Todo>(\"todos\")\nAmba.collections.insert(\"todos\", mapOf(\"title\" to \"Ship\", \"done\" to false))\n\nval config = Amba.config.fetch()\nval showBeta = Amba.flags.get(\"beta_feature\")\nAmba.events.track(\"app_opened\", mapOf(\"source\" to \"deep_link\"))\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal response = await Amba.collections.find('todos', limit: 50);\nawait Amba.collections.insert('todos', {'title': 'Ship', 'done': false});\nfinal config = await Amba.config.fetch();\nfinal showBeta = await Amba.flags.get('beta_feature');\nawait Amba.events.track('app_opened', {'source': 'deep_link'});\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Custom data tables (collections):** any domain-specific tables to create?\n - Yes \u2014 I'll list them. (For each: name + columns + types.)\n - No, just use the canned Amba surfaces (auth, push, gamification, etc.)\n - Auto-create from the existing code's models \u2014 read `lib/models/`, `src/types/`, `Models/`, infer column lists, confirm with me.\n\n2. **Custom backend logic (functions):** any server-side code to deploy?\n - Yes \u2014 describe what it should do. (Then offer to scaffold a function template and deploy.)\n - No\n\n3. **AI features:** want managed LLM prompts?\n - Yes \u2014 what's the use case? (summarize, translate, classify, generate, custom)\n - No\n\n4. **Analytics:** which tracker do you want?\n - Only Amba's built-in events (recommended \u2014 already wired)\n - Amba + Mixpanel / PostHog / Segment forwarding (configure via `amba_integrations_configure`)\n - None (rarely useful \u2014 events drive XP / achievements / streaks; disabling cripples gamification)\n\n5. **Third-party integrations to set up:**\n - [ ] RevenueCat (IAP / subscriptions on iOS + Android)\n - [ ] Superwall (paywall A/B)\n - [ ] Resend (transactional email)\n - [ ] Stripe (web payments / subscriptions)\n - [ ] Mixpanel / PostHog / Segment (analytics forwarding)\n - [ ] OpenAI / Anthropic / Mistral / Gemini LLM keys (required for `Amba.ai.*` \u2014 set via `amba_ai_providers_set`, **not** `amba_integrations_configure`)\n\n6. **Feature flags:** seed any starter flags?\n - Yes \u2014 wire `beta_feature` (off by default) so I can ship the wiring before the feature exists\n - No\n\n7. **Static site:** want a marketing page hosted under your tenant subdomain?\n - Yes \u2014 scaffold and deploy a 1-page index\n - No\n\n## Re-run behavior\n\n1. Before creating:\n - `amba_collections_list` \u2014 match on `name`. Collisions: never silently recreate (data loss). Offer `amba_collections_alter` to add new columns instead.\n - `amba_functions_list` \u2014 match on `name`. Collisions: ask to redeploy (with the new source) or skip.\n - `amba_ai_prompts_list` \u2014 match on `name`. Same. (And `amba_ai_providers_list` \u2014 match on `provider`; re-running `amba_ai_providers_set` rotates the key in place.)\n - `amba_integrations_list` \u2014 match on `provider`. Same.\n - `amba_configs_list` \u2014 match on `key`. Same.\n\n2. **Never call `amba_collections_delete` on re-run unless the user explicitly asks** \u2014 this drops the underlying table and every row in it across every user of the tenant.\n\n3. For functions: re-deploying replaces source in place (versioned server-side). It's safe to call `amba_functions_deploy` with the same name + new source.\n\n4. For integrations: if a provider is already configured, prefer `amba_integrations_patch` (partial update) over `amba_integrations_set` (full replace).\n\n5. Secrets: don't list secret values in chat output, even on read. Just confirm \"OPENAI_API_KEY is set\" / \"not set\".\n";
17
17
  export declare const AMBA_SETUP_INFRASTRUCTURE_URI = "amba://setup/infrastructure";
18
18
  export declare const AMBA_SETUP_INFRASTRUCTURE_MIME = "text/markdown";
@@ -40,7 +40,7 @@
40
40
  * The body is plain CommonMark — no MDX, no JSX — so it renders
41
41
  * identically wherever it lands.
42
42
  */
43
- export declare const AMBA_SETUP_GUIDE_MD = "# Amba\n\n## What Amba is\n\nAmba is the backend your app calls at runtime via the SDK. The MCP tools (`amba_*`) are for provisioning at build time \u2014 call them as the coding agent.\n\nOne Amba project = one isolated tenant database + one set of API keys + a hosted MCP at `mcp.amba.dev` for agent-driven provisioning. The runtime traffic goes through an SDK (`@layers/amba-*` on npm, `Amba` on SPM / Maven / pub.dev). The provisioning traffic \u2014 what you're about to do \u2014 goes through the tools listed below.\n\nYour job is to take the user from \"I have an app\" to \"Amba is wired in and the features are real\" in one pass, without 12 follow-up questions. The journey below is the standard playbook. Don't deviate without a reason.\n\n## Step 0 \u2014 Bootstrap credentials\n\nYou're in an MCP client (Claude.ai web, Claude Desktop, Cursor, Windsurf, etc.) without a local CLI. The MCP server has one tool that works **without** a Bearer token: `amba_developer_signup`. Use it to mint a personal access token (PAT), project, and client/server keys in a single round trip \u2014 no browser, no email verification.\n\nCall:\n\n```\namba_developer_signup({\n email: \"sandbox-<unix-seconds>-<6char-nonce>@layers.com\",\n password: \"<32 random base64url chars>\",\n name: \"amba-sandbox-agent\"\n})\n```\n\nThe response shape:\n\n```json\n{\n \"data\": {\n \"pat\": \"amb_dpat_\u2026\",\n \"developer\": { \"id\": \"\u2026\", \"email\": \"\u2026\", \"tier\": \"agent_sandbox\" },\n \"project\": {\n \"project_id\": \"\u2026\",\n \"client_key\": \"amb_ck_\u2026\",\n \"server_key\": \"amb_sk_\u2026\",\n \"provisioning_status\": \"provisioning\",\n \"verify_url\": \"https://app.amba.dev/verify?token=\u2026\",\n \"verify_token\": \"\u2026\"\n }\n }\n}\n```\n\nAfter signup, the PAT is in your hand \u2014 keep building in THIS session immediately. Pass `pat: \"<the PAT from the response>\"` as an inline argument on every subsequent `amba_*` tool call; the inline arg overrides the inbound Bearer for that one call so no client config change is needed mid-session. For future sessions, write the matching snippet from `mcp_config` to the customer's MCP-client config file so the static Bearer takes over automatically on the next launch.\n\nThe project status is `\"provisioning\"` immediately after signup; the per-tenant DB activates within ~5 seconds. Don't block \u2014 your next call may briefly retry, that's fine. If you want to be polite, call `amba_projects_get_provisioning_status({ project_id })` once and proceed when it returns `\"active\"` (or after 15s, whichever first).\n\nTell the user where their credentials live:\n\n- `pat` \u2014 the Bearer they should configure in this MCP client's settings (and treat like a password).\n- `project_id`, `client_key` \u2014 the values they paste into their app's `.env.local` / `.env`.\n- `server_key` \u2014 never ship to user devices; only into a server `.env` or a secret manager. The `amb_dev_sk_` / `amb_live_sk_` prefix is the marker.\n\n**Already have a PAT?** Skip the signup. Call `amba_developer_me({})` to verify the Bearer; if it succeeds, either reuse the most recent project (`amba_projects_list`) or call `amba_projects_create({ name: \"<app-name>\", platform: \"all\" })` and then `amba_api_keys_create` twice to mint client + server keys for `environment: \"development\"`.\n\n## Step 1 \u2014 Classify the app\n\nLook at what the user told you and at any files they shared. You're trying to pick one of ten presets in 30 seconds, not write a treatise. Inputs:\n\n- The user's prompt \u2014 \"I'm building a fitness tracker\" / \"a marketplace for\u2026\" / \"a Duolingo for X\".\n- README content if shared.\n- `package.json` / `pubspec.yaml` / `build.gradle.kts` / `Package.swift` \u2014 framework + dependencies.\n- Screen / view names \u2014 `WorkoutScreen`, `MatchView`, `LessonPage`, `CartView`, `ProductDetail`, `ChatThread`.\n\nPick the closest match:\n\n| Preset | When | Default Amba surfaces |\n| --- | --- | --- |\n| **fitness** | health / fitness tracker (workouts, steps, meditation) | identity (Apple+Google), push, XP, achievements, streaks, leaderboards, content (daily tips) |\n| **social** | social network / community (friends, feeds, groups) | identity, push, friends, groups, feeds, messaging, moderation, content |\n| **marketplace** | commerce / marketplace (catalog, stores, payments) | identity, push, catalog, stores, currencies (loyalty), reviews, segments |\n| **productivity** | productivity / SaaS tool (collaboration, milestones) | identity (Apple+Google+OTP), push, collections, achievements, content (changelog), segments |\n| **education** | education / learning app (courses, progress, rewards) | identity, push, XP, achievements, streaks, leaderboards, content (lessons), onboarding |\n| **game** | game / casual gaming | identity (anon-first), push, XP, achievements, currencies, inventory, leaderboards, challenges, stores |\n| **dating** | dating / matching app | identity (phone-OTP), push, friends (matches), messaging, moderation (heavy), reviews |\n| **content_creator** | content platform (feeds, subscriptions, tips) | identity, push, feeds, content, currencies (tips), referrals, stores (subscriptions) |\n| **ai_chatbot** | AI / chatbot / assistant app | identity, push, AI prompts, currencies (credits), content (system prompts), onboarding |\n| **custom** | none of the above | pick features individually |\n\nDetection heuristics, in priority order:\n\n1. The user's own description \u2014 most direct signal.\n2. Filename match in `screens/` or `views/` (high signal).\n3. Dependency in `package.json` \u2014 `react-native-health` \u2192 fitness, `@stream-io/*` \u2192 social or dating, `@stripe/*` \u2192 marketplace, `revenuecat` \u2192 marketplace or content_creator.\n4. README copy \u2014 \"fitness\", \"habit\", \"match\", \"chat\", \"store\", \"subscription\".\n\nIf two presets tie, pick the one the user's filenames match more closely. If still tied or no signal, fall back to **custom** and let them pick.\n\n## Step 2 \u2014 Confirm with the user\n\nUse a single multi-choice. Quote the surfaces from the table above so they know what they're getting.\n\n**Question 1: classification + scope**\n\n> I'm reading this as a **\\{kind\\}** app. I'd wire up: **\\{surfaces\\}**. Sound right?\n>\n> 1. Yes, wire it up as proposed (Recommended)\n> 2. Same kind but I want to pick features individually\n> 3. Wrong kind \u2014 let me pick from the list\n> 4. Custom \u2014 I'll pick features manually\n\nIf the user picks 1, go to Step 3. If 2 or 4, follow up with a multi-select of surfaces. If 3, present the table again and pick a different preset.\n\n**Question 2 (preset-specific):** see the per-surface sub-resources (`amba://setup/<surface>`) for the full \"Common follow-ups\" list. Examples:\n\n- **fitness / game / education** \u2014 leaderboard scope? (all-time, weekly, daily, none)\n- **game / content_creator** \u2014 virtual currency name? (`gold`, `gems`, `coins`, `credits` \u2014 defaults to `coins`)\n- **content_creator** \u2014 monetization? (tips, subscriptions, both)\n- **dating** \u2014 phone OTP or email-only? (phone strongly recommended)\n- **ai_chatbot** \u2014 daily free credit cap?\n\nBatch the follow-ups into one or two multi-choice rounds. Don't drip-feed six separate questions.\n\n## Step 3 \u2014 Wire it up\n\nFor each surface in the confirmed set, read the relevant sub-resource and execute its procedure. Each sub-resource is the full per-surface playbook (MCP tools + SDK init per stack + common follow-ups + re-run behavior):\n\n- **identity** (auth, anonymous/Apple/Google/OTP/magic-link, link/unlink) \u2192 `amba://setup/identity`\n- **engagement** (push, segments, content libraries, onboarding flows, deeplinks, referrals, tracked links) \u2192 `amba://setup/engagement`\n- **gamification** (XP rules, achievements, streaks, leaderboards, challenges) \u2192 `amba://setup/gamification`\n- **economy** (currencies, catalog, stores, inventory) \u2192 `amba://setup/economy`\n- **social** (friends, groups, feeds, messaging, moderation, reviews) \u2192 `amba://setup/social`\n- **infrastructure** (collections / DB tables, functions, analytics, AI prompts, media, secrets, configs, integrations, sites) \u2192 `amba://setup/infrastructure`\n\nThe general flow for every surface:\n\n1. **Detect stack.** Look at `package.json`, `pubspec.yaml`, `build.gradle.kts`, `ios/*.xcodeproj`. The detection rules:\n - `pubspec.yaml` present \u2192 Flutter.\n - `package.json` with `expo` \u2192 Expo.\n - `package.json` with `react-native` (no `expo`) \u2192 bare React Native.\n - `package.json` with `react` (no `react-native`) \u2192 web (or Next.js \u2014 same SDK).\n - `Package.swift` or `*.xcodeproj` only \u2192 iOS Swift.\n - `build.gradle.kts` or `build.gradle` with `com.android.application` \u2192 Android Kotlin.\n - Multiple (e.g. `ios/` + `android/` inside an Expo repo) \u2192 Expo wins.\n\n2. **Create resources via MCP.** Call the `amba_<surface>_create` tools to mint the definitions. Always include `project_id` from the project you created in Step 0. Always show the user the tool call before making destructive changes (creating a resource isn't destructive \u2014 but creating 30 of them is noisy).\n\n3. **Write SDK init code.** Drop the per-stack snippet (from the sub-resource) into the user's entry file. Detection:\n - Expo / React Native: `app/_layout.tsx`, `App.tsx`, `index.js` (in that order)\n - web / Next.js: `app/layout.tsx`, `pages/_app.tsx`, `src/main.tsx`, `src/App.tsx`\n - iOS Swift: `Sources/<App>/<App>App.swift`, `App/AppDelegate.swift`\n - Android Kotlin: `app/src/main/java/.../<App>.kt` (the `Application` subclass \u2014 create one if missing)\n - Flutter: `lib/main.dart`\n\n Always make additive edits \u2014 `await Amba.configure(...)` next to existing init, not replacing it. Never refactor existing auth or storage code; if the user has Firebase Auth or Supabase, leave it. Amba's auth is opt-in per call.\n\n4. **Run the project's existing test command** to confirm nothing broke. Detection:\n - `package.json` `scripts.test` \u2192 `npm test` (or `pnpm test` if `pnpm-lock.yaml` present)\n - `pubspec.yaml` \u2192 `flutter test`\n - `build.gradle.kts` \u2192 `./gradlew test` (skip on first wire-up \u2014 slow)\n - iOS \u2014 skip (need a simulator).\n\n If tests fail because of your edits, undo the offending edit and surface a clear error. If they fail for unrelated reasons (pre-existing red), note it and proceed.\n\n5. **Verify with the SDK.** Tell the user to call `Amba.diagnostics.ping()` (`Amba.Diagnostics.Ping()` on Unity) in their entry file. It returns `{ ok, server_project_id, environment, key_fingerprint, latency_ms }`. `ok: true` with the expected `server_project_id` confirms the wiring.\n\n## Step 4 \u2014 Report\n\nTell the user a structured summary. Use this exact shape so they can skim it fast:\n\n```\nAmba is wired in. Here's what changed:\n\nDONE\n - identity: Apple + Google sign-in available; signInAnonymously() called at app start\n - gamification: 3 achievements, 1 streak, 1 leaderboard created\n resources: first_workout, week_warrior, century_club / daily_workout / weekly_xp\n - engagement: push registration wired; default segment \"active_users\" created\n\nSKIPPED (low signal \u2014 re-run with /amba <feature> if you want them)\n - economy: no in-app currency UI found in your screens\n - social: no friends/feed surfaces found\n\nNEEDS YOUR INPUT\n - Apple Sign In: add the \"Sign in with Apple\" capability in Xcode > Signing & Capabilities.\n - Google Sign In: paste your Google OAuth client ID into amba_projects_update({ google_oauth_client_id: \"...\" }).\n - APNs / FCM: upload credentials in app.amba.dev before push delivers.\n\nNEXT STEPS\n - Paste AMBA_CLIENT_KEY into your build env (already shown above)\n - Trigger a workout in your existing flow \u2014 watch the achievement unlock + XP land\n - Open https://app.amba.dev to see users pour in\n```\n\nBe specific. List resources by key, not \"some achievements\". If something needs the user's input (third-party credentials, OAuth client IDs, push certs), say it clearly with the exact next action.\n\n## Stance (read this once)\n\n- **Don't ask which surfaces to use.** Classify, then confirm in one multi-choice. The taxonomy is the whole point.\n- **Default to additive, non-breaking changes.** Don't refactor existing auth, storage, or networking code. Drop in `await Amba.configure(...)` next to whatever the user already has.\n- **Never create resources without the user's confirmation in Step 2.** A 3rd-party \"convenience\" achievement called `first_login` is debt.\n- **If something is genuinely ambiguous** (leaderboard scope, currency real-money vs virtual, dating phone vs email), ask via a follow-up multi-choice. Don't guess and don't paragraph-it.\n- **clientKey vs serverKey.** `AMBA_CLIENT_KEY` (`amb_dev_ck_\u2026` in dev, `amb_live_ck_\u2026` in prod) ships to user devices. `AMBA_SERVER_KEY` (`amb_dev_sk_\u2026` / `amb_live_sk_\u2026`) never does \u2014 only into server `.env` or a secret manager. Mixing them is the #1 security mistake; if you're writing into a file that ships with the app binary, it's the client key, period.\n- **Don't echo the PAT in chat output on every call.** Showing it once after signup is fine; do not repeat it.\n\n## Get credentials (cheat sheet)\n\n- No terminal, in an MCP client: call `amba_developer_signup` (no Bearer required) \u2014 this guide's Step 0.\n- With a terminal: `npx -y @layers/amba init` signs up, mints a project + client/server keys, writes `.env.local` + `AMBA.md`, installs the `/amba` skill, and wires `mcpServers.amba` into every detected MCP-client config in one command. Auto-detects non-TTY invocations (the coding-agent bash-tool case) and runs headlessly.\n- Bind the sandbox account to a real email later: `npx @layers/amba claim me@example.com`. The backend emails a one-click magic link; clicking it lifts the sandbox cap to the Free tier.\n- Hosted MCP endpoint: `https://mcp.amba.dev/mcp` (Streamable HTTP, Bearer auth).\n\n## SDKs\n\n| Stack | Registry | Package |\n|---|---|---|\n| Browser / Node / React / React Native / Expo | npm | `@layers/amba-{web,node,react,react-native,expo}` |\n| Swift | SPM | `https://github.com/layers/amba-sdk-ios` |\n| Kotlin | Maven Central | `com.layers.amba:amba-sdk-android` |\n| Flutter | pub.dev | `amba` |\n| Unity | UPM (git) | `https://github.com/layers/amba-sdk-unity.git` |\n\nAll SDKs expose the same surface: `Amba.configure({ projectId, apiKey })`, then `Amba.events.track(...)`, `Amba.users.*`, `Amba.collections.*`, etc. Per-stack quickstart pages with the exact initialization snippet: `https://docs.amba.dev/sdk/<framework>`.\n\n## What Amba does\n\n### Identity\n- **users** \u2014 app-user registry. Auto-created on first SDK call; admin via `amba_users_*`.\n- **roles + permissions** \u2014 RBAC. Define with `amba_roles_create`; assign via `amba_roles_assign`.\n- **api_keys** \u2014 client + server keys per project. Mint via `amba_api_keys_create`.\n\n### Engagement\n- **onboarding** \u2014 multi-step first-run flows. Define with `amba_onboarding_create`; SDK `Amba.onboarding.next()`.\n- **segments** \u2014 user cohorts. Define with `amba_segments_create`; used as push/feed targets.\n- **push** \u2014 scheduled or triggered notifications. Chain: configure integrations (apns/fcm) \u2192 `amba_push_campaigns_create` \u2192 `amba_push_campaigns_send` (or schedule).\n- **referrals** \u2014 referral codes. Define with `amba_referrals_create`.\n- **deeplinks** \u2014 universal links. Set domain with `amba_deeplinks_set_config`.\n- **tracked_links** \u2014 UTM-tagged outbound links. Define with `amba_tracked_links_create`.\n- **content** \u2014 episodic delivery (lessons, quotes, daily prompts). Chain: `amba_content_libraries_create` \u2192 `amba_content_items_add` \u2192 `amba_content_schedules_create`.\n\n### Gamification\n- **xp** \u2014 experience points + level. Define rules with `amba_xp_rules_create`; SDK `Amba.xp.getBalance`.\n- **achievements** \u2014 earnable badges. Define with `amba_achievements_create`; unlock via xp rules or `amba_inventory_grant_item`.\n- **streaks** \u2014 recurring engagement counters. Define with `amba_streaks_create`; client calls `Amba.streaks.qualify(key)`.\n- **leaderboards** \u2014 ranked user lists. Define with `amba_leaderboards_create`; populated from events.\n- **challenges** \u2014 time-bounded goals. Define with `amba_challenges_create`; progress via SDK.\n\n### Economy\n- **currencies** \u2014 virtual currencies (coins, gems). Define with `amba_currencies_create`; grant via `amba_currencies_grant` or event rules via `amba_currency_grant_rules_create`; debit via `amba_currencies_spend` (atomic, rejects on insufficient funds).\n- **catalog + stores** \u2014 purchasable items + storefronts. Chain: `amba_catalog_items_create` \u2192 `amba_catalog_items_set_price` \u2192 `amba_stores_create` \u2192 `amba_stores_add_listing`. (Define currency first.)\n- **inventory** \u2014 items users own. Read via SDK `Amba.inventory.*`; grant with `amba_inventory_grant_item`.\n\n### Social\n- **friendships** \u2014 friend graph. SDK `Amba.friends.*`; admin via `amba_friendships_*`.\n- **groups** \u2014 guilds/parties/chats. Define with `amba_groups_create`; members managed via SDK + admin tools.\n- **messaging** \u2014 DMs + group chat. Enabled by default; moderate via `amba_messaging_*`.\n- **feeds** \u2014 algorithmic activity feeds. Define ranking with `amba_feeds_rules_create`.\n- **reviews** \u2014 user-submitted reviews. Enabled by default; moderate via `amba_reviews_*`.\n- **moderation** \u2014 content review queue + trust scores. Configure with `amba_moderation_configure`; review via `amba_moderation_queue_list`.\n\n### Analytics\n- **events** \u2014 track user actions. SDK `Amba.events.track()`; query via `amba_events_count`.\n- **sessions** \u2014 session telemetry. Tracked automatically; query via `amba_sessions_list`.\n- **analytics** \u2014 funnels + retention. Query via `amba_analytics_get`.\n\n### Infrastructure\n- **collections** \u2014 your own typed key-value tables. Define with `amba_collections_create`; read/write from SDK `Amba.client.*`.\n- **functions** \u2014 serverless TypeScript handlers. Deploy with `amba_functions_deploy`; schedule with `amba_functions_schedule`.\n- **sites** \u2014 static site hosting at `*.app.amba.host`. Deploy with `amba_sites_deploy`.\n- **media** \u2014 file storage + CDN. Upload via `amba_media_upload`.\n- **secrets** \u2014 env vars for functions. Set via `amba_secrets_set`.\n- **configs** \u2014 remote config flags. Define with `amba_remote_configs_create`.\n- **integrations** \u2014 third-party webhooks (RevenueCat, Superwall, AppsFlyer, etc.). Configure with `amba_integrations_configure`.\n- **ai_prompts** \u2014 versioned LLM prompts callable from SDK. Define with `amba_ai_prompts_create`; call via `amba_ai_prompts_invoke`.\n";
43
+ export declare const AMBA_SETUP_GUIDE_MD = "# Amba\n\n## What Amba is\n\nAmba is the backend your app calls at runtime via the SDK. The MCP tools (`amba_*`) are for provisioning at build time \u2014 call them as the coding agent.\n\nOne Amba project = one isolated tenant database + one set of API keys + a hosted MCP at `mcp.amba.dev` for agent-driven provisioning. The runtime traffic goes through an SDK (`@layers/amba-*` on npm, `Amba` on SPM / Maven / pub.dev). The provisioning traffic \u2014 what you're about to do \u2014 goes through the tools listed below.\n\nYour job is to take the user from \"I have an app\" to \"Amba is wired in and the features are real\" in one pass, without 12 follow-up questions. The journey below is the standard playbook. Don't deviate without a reason.\n\n## Step 0 \u2014 Bootstrap credentials\n\nYou're in an MCP client (Claude.ai web, Claude Desktop, Cursor, Windsurf, etc.) without a local CLI. The MCP server has one tool that works **without** a Bearer token: `amba_developer_signup`. Use it to mint a personal access token (PAT), project, and client/server keys in a single round trip \u2014 no browser, no email verification.\n\nCall:\n\n```\namba_developer_signup({\n email: \"sandbox-<unix-seconds>-<6char-nonce>@layers.com\",\n password: \"<32 random base64url chars>\",\n name: \"amba-sandbox-agent\"\n})\n```\n\nThe response shape:\n\n```json\n{\n \"data\": {\n \"pat\": \"amb_dpat_\u2026\",\n \"developer\": { \"id\": \"\u2026\", \"email\": \"\u2026\", \"tier\": \"agent_sandbox\" },\n \"project\": {\n \"project_id\": \"\u2026\",\n \"client_key\": \"amb_ck_\u2026\",\n \"server_key\": \"amb_sk_\u2026\",\n \"provisioning_status\": \"provisioning\",\n \"verify_url\": \"https://app.amba.dev/verify?token=\u2026\",\n \"verify_token\": \"\u2026\"\n }\n }\n}\n```\n\nAfter signup, the PAT is in your hand \u2014 keep building in THIS session immediately. Pass `pat: \"<the PAT from the response>\"` as an inline argument on every subsequent `amba_*` tool call; the inline arg overrides the inbound Bearer for that one call so no client config change is needed mid-session. For future sessions, write the matching snippet from `mcp_config` to the customer's MCP-client config file so the static Bearer takes over automatically on the next launch.\n\nThe project status is `\"provisioning\"` immediately after signup; the per-tenant DB activates within ~5 seconds. Don't block \u2014 your next call may briefly retry, that's fine. If you want to be polite, call `amba_projects_get_provisioning_status({ project_id })` once and proceed when it returns `\"active\"` (or after 15s, whichever first).\n\nTell the user where their credentials live:\n\n- `pat` \u2014 the Bearer they should configure in this MCP client's settings (and treat like a password).\n- `project_id`, `client_key` \u2014 the values they paste into their app's `.env.local` / `.env`.\n- `server_key` \u2014 never ship to user devices; only into a server `.env` or a secret manager. The `amb_dev_sk_` / `amb_live_sk_` prefix is the marker.\n\n**Already have a PAT?** Skip the signup. Call `amba_developer_me({})` to verify the Bearer; if it succeeds, either reuse the most recent project (`amba_projects_list`) or call `amba_projects_create({ name: \"<app-name>\", platform: \"all\" })` and then `amba_api_keys_create` twice to mint client + server keys for `environment: \"development\"`.\n\n**Can't reach `amba_developer_signup` \u2014 it's not in your tool list, or your harness blocks the call?** Do NOT try to work around it by adding the MCP server yourself, running an installer, or editing your own settings/permissions \u2014 refusing to self-modify or widen your own permissions is the correct call, and you should not circumvent it. Instead, hand the developer **one** command to run in **their own** terminal (which is not subject to your sandbox):\n\n```bash\nnpx @layers/amba init\n```\n\nIt creates the account with no browser and writes `AMBA_PROJECT_ID` + `AMBA_CLIENT_KEY` to `.env.local` (and wires this MCP server up for your next session). Ask them to run it, tell you when it's done, then resume from this Step 0 by reading `.env.local`. Asking the developer to paste an `amb_ck_\u2026` client key is the last-resort lite path \u2014 it's enough to wire `Amba.configure()` + runtime calls and verify events land, but a client key alone can't do admin provisioning (creating achievements, collections, segments, etc.), which needs the PAT or server key.\n\n## Step 1 \u2014 Classify the app\n\nLook at what the user told you and at any files they shared. You're trying to pick one of ten presets in 30 seconds, not write a treatise. Inputs:\n\n- The user's prompt \u2014 \"I'm building a fitness tracker\" / \"a marketplace for\u2026\" / \"a Duolingo for X\".\n- README content if shared.\n- `package.json` / `pubspec.yaml` / `build.gradle.kts` / `Package.swift` \u2014 framework + dependencies.\n- Screen / view names \u2014 `WorkoutScreen`, `MatchView`, `LessonPage`, `CartView`, `ProductDetail`, `ChatThread`.\n\nPick the closest match:\n\n| Preset | When | Default Amba surfaces |\n| --- | --- | --- |\n| **fitness** | health / fitness tracker (workouts, steps, meditation) | identity (Apple+Google), push, XP, achievements, streaks, leaderboards, content (daily tips) |\n| **social** | social network / community (friends, feeds, groups) | identity, push, friends, groups, feeds, messaging, moderation, content |\n| **marketplace** | commerce / marketplace (catalog, stores, payments) | identity, push, catalog, stores, currencies (loyalty), reviews, segments |\n| **productivity** | productivity / SaaS tool (collaboration, milestones) | identity (Apple+Google+OTP), push, collections, achievements, content (changelog), segments |\n| **education** | education / learning app (courses, progress, rewards) | identity, push, XP, achievements, streaks, leaderboards, content (lessons), onboarding |\n| **game** | game / casual gaming | identity (anon-first), push, XP, achievements, currencies, inventory, leaderboards, challenges, stores |\n| **dating** | dating / matching app | identity (phone-OTP), push, friends (matches), messaging, moderation (heavy), reviews |\n| **content_creator** | content platform (feeds, subscriptions, tips) | identity, push, feeds, content, currencies (tips), referrals, stores (subscriptions) |\n| **ai_chatbot** | AI / chatbot / assistant app | identity, push, AI prompts, currencies (credits), content (system prompts), onboarding |\n| **custom** | none of the above | pick features individually |\n\nDetection heuristics, in priority order:\n\n1. The user's own description \u2014 most direct signal.\n2. Filename match in `screens/` or `views/` (high signal).\n3. Dependency in `package.json` \u2014 `react-native-health` \u2192 fitness, `@stream-io/*` \u2192 social or dating, `@stripe/*` \u2192 marketplace, `revenuecat` \u2192 marketplace or content_creator.\n4. README copy \u2014 \"fitness\", \"habit\", \"match\", \"chat\", \"store\", \"subscription\".\n\nIf two presets tie, pick the one the user's filenames match more closely. If still tied or no signal, fall back to **custom** and let them pick.\n\n## Step 2 \u2014 Confirm with the user\n\nUse a single multi-choice. Quote the surfaces from the table above so they know what they're getting.\n\n**Question 1: classification + scope**\n\n> I'm reading this as a **\\{kind\\}** app. I'd wire up: **\\{surfaces\\}**. Sound right?\n>\n> 1. Yes, wire it up as proposed (Recommended)\n> 2. Same kind but I want to pick features individually\n> 3. Wrong kind \u2014 let me pick from the list\n> 4. Custom \u2014 I'll pick features manually\n\nIf the user picks 1, go to Step 3. If 2 or 4, follow up with a multi-select of surfaces. If 3, present the table again and pick a different preset.\n\n**Question 2 (preset-specific):** see the per-surface sub-resources (`amba://setup/<surface>`) for the full \"Common follow-ups\" list. Examples:\n\n- **fitness / game / education** \u2014 leaderboard scope? (all-time, weekly, daily, none)\n- **game / content_creator** \u2014 virtual currency name? (`gold`, `gems`, `coins`, `credits` \u2014 defaults to `coins`)\n- **content_creator** \u2014 monetization? (tips, subscriptions, both)\n- **dating** \u2014 phone OTP or email-only? (phone strongly recommended)\n- **ai_chatbot** \u2014 daily free credit cap?\n\nBatch the follow-ups into one or two multi-choice rounds. Don't drip-feed six separate questions.\n\n## Step 3 \u2014 Wire it up\n\nFor each surface in the confirmed set, read the relevant sub-resource and execute its procedure. Each sub-resource is the full per-surface playbook (MCP tools + SDK init per stack + common follow-ups + re-run behavior):\n\n- **identity** (auth, anonymous/Apple/Google/OTP/magic-link, link/unlink) \u2192 `amba://setup/identity`\n- **engagement** (push, segments, content libraries, onboarding flows, deeplinks, referrals, tracked links) \u2192 `amba://setup/engagement`\n- **gamification** (XP rules, achievements, streaks, leaderboards, challenges) \u2192 `amba://setup/gamification`\n- **economy** (currencies, catalog, stores, inventory) \u2192 `amba://setup/economy`\n- **social** (friends, groups, feeds, messaging, moderation, reviews) \u2192 `amba://setup/social`\n- **infrastructure** (collections / DB tables, functions, analytics, AI prompts, media, secrets, configs, integrations, sites) \u2192 `amba://setup/infrastructure`\n\nThe general flow for every surface:\n\n1. **Detect stack.** Look at `package.json`, `pubspec.yaml`, `build.gradle.kts`, `ios/*.xcodeproj`. The detection rules:\n - `pubspec.yaml` present \u2192 Flutter.\n - `package.json` with `expo` \u2192 Expo.\n - `package.json` with `react-native` (no `expo`) \u2192 bare React Native.\n - `package.json` with `react` (no `react-native`) \u2192 web (or Next.js \u2014 same SDK).\n - `Package.swift` or `*.xcodeproj` only \u2192 iOS Swift.\n - `build.gradle.kts` or `build.gradle` with `com.android.application` \u2192 Android Kotlin.\n - Multiple (e.g. `ios/` + `android/` inside an Expo repo) \u2192 Expo wins.\n\n2. **Create resources via MCP.** Call the `amba_<surface>_create` tools to mint the definitions. Always include `project_id` from the project you created in Step 0. Always show the user the tool call before making destructive changes (creating a resource isn't destructive \u2014 but creating 30 of them is noisy).\n\n3. **Write SDK init code.** Drop the per-stack snippet (from the sub-resource) into the user's entry file. Detection:\n - Expo / React Native: `app/_layout.tsx`, `App.tsx`, `index.js` (in that order)\n - web / Next.js: `app/layout.tsx`, `pages/_app.tsx`, `src/main.tsx`, `src/App.tsx`\n - iOS Swift: `Sources/<App>/<App>App.swift`, `App/AppDelegate.swift`\n - Android Kotlin: `app/src/main/java/.../<App>.kt` (the `Application` subclass \u2014 create one if missing)\n - Flutter: `lib/main.dart`\n\n Always make additive edits \u2014 `await Amba.configure(...)` next to existing init, not replacing it. Never refactor existing auth or storage code; if the user has Firebase Auth or Supabase, leave it. Amba's auth is opt-in per call.\n\n4. **Run the project's existing test command** to confirm nothing broke. Detection:\n - `package.json` `scripts.test` \u2192 `npm test` (or `pnpm test` if `pnpm-lock.yaml` present)\n - `pubspec.yaml` \u2192 `flutter test`\n - `build.gradle.kts` \u2192 `./gradlew test` (skip on first wire-up \u2014 slow)\n - iOS \u2014 skip (need a simulator).\n\n If tests fail because of your edits, undo the offending edit and surface a clear error. If they fail for unrelated reasons (pre-existing red), note it and proceed.\n\n5. **Verify with the SDK.** Tell the user to call `Amba.diagnostics.ping()` (`Amba.Diagnostics.Ping()` on Unity) in their entry file. It returns `{ ok, server_project_id, environment, key_fingerprint, latency_ms }`. `ok: true` with the expected `server_project_id` confirms the wiring.\n\n## Step 4 \u2014 Report\n\nTell the user a structured summary. Use this exact shape so they can skim it fast:\n\n```\nAmba is wired in. Here's what changed:\n\nDONE\n - identity: Apple + Google sign-in available; signInAnonymously() called at app start\n - gamification: 3 achievements, 1 streak, 1 leaderboard created\n resources: first_workout, week_warrior, century_club / daily_workout / weekly_xp\n - engagement: push registration wired; default segment \"active_users\" created\n\nSKIPPED (low signal \u2014 re-run with /amba <feature> if you want them)\n - economy: no in-app currency UI found in your screens\n - social: no friends/feed surfaces found\n\nNEEDS YOUR INPUT\n - Apple Sign In: add the \"Sign in with Apple\" capability in Xcode > Signing & Capabilities.\n - Google Sign In: paste your Google OAuth client ID into amba_projects_update({ google_oauth_client_id: \"...\" }).\n - APNs / FCM: configure credentials with amba_integrations_configure (paste the .p8 / service-account JSON inline) before push delivers.\n\nNEXT STEPS\n - Paste AMBA_CLIENT_KEY into your build env (already shown above)\n - Trigger a workout in your existing flow \u2014 watch the achievement unlock + XP land\n - Open https://app.amba.dev to see users pour in\n```\n\nBe specific. List resources by key, not \"some achievements\". If something needs the user's input (third-party credentials, OAuth client IDs, push certs), say it clearly with the exact next action.\n\n## Stance (read this once)\n\n- **Don't ask which surfaces to use.** Classify, then confirm in one multi-choice. The taxonomy is the whole point.\n- **Default to additive, non-breaking changes.** Don't refactor existing auth, storage, or networking code. Drop in `await Amba.configure(...)` next to whatever the user already has.\n- **Never create resources without the user's confirmation in Step 2.** A 3rd-party \"convenience\" achievement called `first_login` is debt.\n- **If something is genuinely ambiguous** (leaderboard scope, currency real-money vs virtual, dating phone vs email), ask via a follow-up multi-choice. Don't guess and don't paragraph-it.\n- **clientKey vs serverKey.** `AMBA_CLIENT_KEY` (`amb_dev_ck_\u2026` in dev, `amb_live_ck_\u2026` in prod) ships to user devices. `AMBA_SERVER_KEY` (`amb_dev_sk_\u2026` / `amb_live_sk_\u2026`) never does \u2014 only into server `.env` or a secret manager. Mixing them is the #1 security mistake; if you're writing into a file that ships with the app binary, it's the client key, period.\n- **Don't echo the PAT in chat output on every call.** Showing it once after signup is fine; do not repeat it.\n\n## Get credentials (cheat sheet)\n\n- No terminal, in an MCP client: call `amba_developer_signup` (no Bearer required) \u2014 this guide's Step 0.\n- With a terminal: `npx -y @layers/amba init` signs up, mints a project + client/server keys, writes `.env.local` + `AMBA.md`, installs the `/amba` skill, and wires `mcpServers.amba` into every detected MCP-client config in one command. Auto-detects non-TTY invocations (the coding-agent bash-tool case) and runs headlessly.\n- Bind the sandbox account to a real email later: `npx @layers/amba claim me@example.com`. The backend emails a one-click magic link; clicking it lifts the sandbox cap to the Free tier.\n- Hosted MCP endpoint: `https://mcp.amba.dev/mcp` (Streamable HTTP, Bearer auth).\n\n## SDKs\n\n| Stack | Registry | Package |\n|---|---|---|\n| Browser / Node / React / React Native / Expo | npm | `@layers/amba-{web,node,react,react-native,expo}` |\n| Swift | SPM | `https://github.com/layers/amba-sdk-ios` |\n| Kotlin | Maven Central | `com.layers.amba:amba-sdk-android` |\n| Flutter | pub.dev | `amba` |\n| Unity | UPM (git) | `https://github.com/layers/amba-sdk-unity.git` |\n\nAll SDKs expose the same surface: `Amba.configure({ projectId, apiKey })`, then `Amba.events.track(...)`, `Amba.users.*`, `Amba.collections.*`, etc. Per-stack quickstart pages with the exact initialization snippet: `https://docs.amba.dev/sdk/<framework>`.\n\n## What Amba does\n\n### Identity\n- **users** \u2014 app-user registry. Auto-created on first SDK call; admin via `amba_users_*`.\n- **roles + permissions** \u2014 RBAC. Define with `amba_roles_create`; assign via `amba_roles_assign`.\n- **api_keys** \u2014 client + server keys per project. Mint via `amba_api_keys_create`.\n\n### Engagement\n- **onboarding** \u2014 multi-step first-run flows. Define with `amba_onboarding_create`; SDK `Amba.onboarding.next()`.\n- **segments** \u2014 user cohorts. Define with `amba_segments_create`; used as push/feed targets.\n- **push** \u2014 scheduled or triggered notifications. Chain: configure integrations (apns/fcm) \u2192 `amba_push_campaigns_create` \u2192 `amba_push_campaigns_send` (or schedule).\n- **referrals** \u2014 referral codes. Define with `amba_referrals_create`.\n- **deeplinks** \u2014 universal links. Set domain with `amba_deeplinks_set_config`.\n- **tracked_links** \u2014 UTM-tagged outbound links. Define with `amba_tracked_links_create`.\n- **content** \u2014 episodic delivery (lessons, quotes, daily prompts). Chain: `amba_content_libraries_create` \u2192 `amba_content_items_add` \u2192 `amba_content_schedules_create`.\n\n### Gamification\n- **xp** \u2014 experience points + level. Define rules with `amba_xp_rules_create`; SDK `Amba.xp.getBalance`.\n- **achievements** \u2014 earnable badges. Define with `amba_achievements_create`; unlock via xp rules or `amba_inventory_grant_item`.\n- **streaks** \u2014 recurring engagement counters. Define with `amba_streaks_create`; client calls `Amba.streaks.qualify(key)`.\n- **leaderboards** \u2014 ranked user lists. Define with `amba_leaderboards_create`; populated from events.\n- **challenges** \u2014 time-bounded goals. Define with `amba_challenges_create`; progress via SDK.\n\n### Economy\n- **currencies** \u2014 virtual currencies (coins, gems). Define with `amba_currencies_create`; grant via `amba_currencies_grant` or event rules via `amba_currency_grant_rules_create`; debit via `amba_currencies_spend` (atomic, rejects on insufficient funds).\n- **catalog + stores** \u2014 purchasable items + storefronts. Chain: `amba_catalog_items_create` \u2192 `amba_catalog_items_set_price` \u2192 `amba_stores_create` \u2192 `amba_stores_add_listing`. (Define currency first.)\n- **inventory** \u2014 items users own. Read via SDK `Amba.inventory.*`; grant with `amba_inventory_grant_item`.\n\n### Social\n- **friendships** \u2014 friend graph. SDK `Amba.friends.*`; admin via `amba_friendships_*`.\n- **groups** \u2014 guilds/parties/chats. Define with `amba_groups_create`; members managed via SDK + admin tools.\n- **messaging** \u2014 DMs + group chat. Enabled by default; moderate via `amba_messaging_*`.\n- **feeds** \u2014 algorithmic activity feeds. Define ranking with `amba_feeds_rules_create`.\n- **reviews** \u2014 user-submitted reviews. Enabled by default; moderate via `amba_reviews_*`.\n- **moderation** \u2014 content review queue + trust scores. Configure with `amba_moderation_configure`; review via `amba_moderation_queue_list`.\n\n### Analytics\n- **events** \u2014 track user actions. SDK `Amba.events.track()`; query via `amba_events_count`.\n- **sessions** \u2014 session telemetry. Tracked automatically; query via `amba_sessions_list`.\n- **analytics** \u2014 funnels + retention. Query via `amba_analytics_get`.\n\n### Infrastructure\n- **collections** \u2014 your own typed key-value tables. Define with `amba_collections_create`; read/write from SDK `Amba.client.*`.\n- **functions** \u2014 serverless TypeScript handlers. Deploy with `amba_functions_deploy`; schedule with `amba_functions_schedule`.\n- **sites** \u2014 static site hosting at `*.app.amba.host`. Deploy with `amba_sites_deploy`.\n- **media** \u2014 file storage + CDN. Upload via `amba_media_upload`.\n- **secrets** \u2014 env vars for functions. Set via `amba_secrets_set`.\n- **configs** \u2014 remote config flags. Define with `amba_remote_configs_create`.\n- **integrations** \u2014 third-party webhooks (RevenueCat, Superwall, AppsFlyer, etc.). Configure with `amba_integrations_configure`.\n- **ai_prompts** \u2014 versioned LLM prompts callable from SDK. Define with `amba_ai_prompts_create`; call via `amba_ai_prompts_invoke`.\n";
44
44
  /** Canonical URI for the MCP resource. */
45
45
  export declare const AMBA_SETUP_GUIDE_URI = "amba://setup";
46
46
  /** Canonical MIME type for the guide body. */
@@ -93,7 +93,7 @@ export declare const AMBA_SETUP_SUB_RESOURCES: readonly [{
93
93
  readonly name: "amba-setup-infrastructure";
94
94
  readonly uri: "amba://setup/infrastructure";
95
95
  readonly mime: "text/markdown";
96
- readonly body: "# Infrastructure\n\nThe plumbing that sits behind every other surface: custom database tables (Collections — schema-first, per-tenant), serverless functions (run server-side code without standing up a backend), analytics (events + sessions), AI prompts (managed LLM templates, callable from the SDK with per-tenant keys), secrets, runtime configs, feature flags, third-party integrations (RevenueCat / Superwall / Resend / Stripe / etc.), media (file storage + CDN), and sites (static asset hosting at `*.app.amba.host`).\n\nIf gamification, economy, and social are the playable surface, **infrastructure is what you build a custom product on top of**. Anything that doesn't fit the canned surfaces lands here.\n\n## MCP tools\n\n### Collections (typed tables)\n\nA collection is a schema-first table inside the project's isolated tenant database. You describe the columns, the server creates the table and any indexes. Rows are scoped to the signed-in `app_user` automatically (server-enforced auto row-level isolation) for SDK clients — admin tools bypass this.\n\nAdmin tools authenticate the developer/agent (pass `pat` or send it as the inbound Bearer) and take `project_id`. Client tools authenticate an end-user and take `api_key` (+ `session_token`) — NOT `project_id` and NOT a `pat`. Every row tool names the collection with `name`, never `collection`.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_collections_create` | Create a typed collection. Pass `shared: true` for developer-seeded GLOBAL content (question banks, lookup tables) so `user_id` is nullable. | `{ project_id, name: \"todos\", columns: [{ name: \"title\", type: \"text\", nullable: false }, { name: \"done\", type: \"boolean\", nullable: false, default: false }, { name: \"due_at\", type: \"timestamptz\", nullable: true }], shared: false }` |\n| `amba_collections_list` | List collections in this project. | `{ project_id }` |\n| `amba_collections_get` | Read one collection's schema. | `{ project_id, name: \"todos\" }` |\n| `amba_collections_alter` | Exactly ONE of: `add_column`, `add_index`, `drop_column`, or `relax_user_id` per call. `relax_user_id: true` converts an existing collection to shared (drops the `user_id` NOT NULL). | `{ project_id, name: \"todos\", add_column: { name: \"priority\", type: \"int\", nullable: true } }` |\n| `amba_collections_delete` | Drop the table (destructive). `confirm` must equal the collection name. | `{ project_id, name: \"todos\", confirm: \"todos\" }` |\n| `amba_admin_insert_row` | Insert one row as the developer (bypasses user-scope; `user_id` honored if present). | `{ project_id, name: \"todos\", row: { title: \"Sample\", done: false } }` |\n| `amba_admin_insert_rows` | Bulk-insert up to 500 rows in one atomic statement — the canonical seeding/migration path. `on_conflict`: `\"error\"` (default) or `\"skip\"`. | `{ project_id, name: \"questions\", rows: [{ q: \"...\" }, { q: \"...\" }], on_conflict: \"skip\" }` |\n| `amba_admin_list_rows` | Read rows as the developer. | `{ project_id, name: \"todos\", limit: 100 }` |\n| `amba_client_insert_row` | Insert as an end-user. Requires `api_key` (+ `session_token`). | `{ api_key, session_token, name: \"todos\", row: {...} }` |\n| `amba_client_list_rows` | Read as an end-user (auto user-scoped). | `{ api_key, session_token, name: \"todos\" }` |\n| `amba_client_get_row` | Get one row by id (end-user). | `{ api_key, session_token, name: \"todos\", id }` |\n| `amba_client_update_row` | Update one row by id (end-user). Fields go in `set`. Omit `id` + pass `where` for a bulk update. | `{ api_key, session_token, name: \"todos\", id, set: {...} }` |\n| `amba_client_delete_row` | Soft-delete one row by id (end-user). | `{ api_key, session_token, name: \"todos\", id }` |\n| `amba_client_count_rows` | Count rows matching an optional `where`. | `{ api_key, session_token, name: \"todos\", where: {...} }` |\n| `amba_client_find_rows` | Filter / sort / paginate rows (SDK-shaped `filter`). | `{ api_key, session_token, name: \"todos\", filter: {...}, order: [\"created_at desc\"], limit: 50 }` |\n| `amba_client_find_nearest_rows` | Vector-similarity search (rows with a `vector(<dim>)` column). | `{ api_key, session_token, name: \"todos\", column: \"embedding\", to_vector: [...], k: 10 }` |\n\nColumn types: `text`, `int`, `bigint`, `float`, `boolean`, `timestamptz`, `date`, `json`, `jsonb`, `uuid`, `vector(<dim>)` (e.g. `vector(1536)` for OpenAI embeddings).\n\n### Functions (serverless code)\n\nRun user code in a sandbox triggered by HTTP, cron, or webhook. The function gets the tenant connection automatically via injected env.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_functions_deploy` | Deploy a function from source. | `{ project_id, name: \"send_welcome_email\", runtime: \"node22\", source: \"export default async (req) => { ... }\", trigger: { type: \"http\" } }` |\n| `amba_functions_list` | List functions. | `{ project_id }` |\n| `amba_functions_get` | Read function metadata. | `{ project_id, function_id }` |\n| `amba_functions_get_logs` | Recent invocation logs. | `{ project_id, function_id, limit: 100 }` |\n| `amba_functions_delete` | Delete a function. | `{ project_id, function_id }` |\n| `amba_functions_schedule` | Attach a cron schedule. | `{ project_id, function_id, cron: \"0 9 * * *\", timezone: \"America/Los_Angeles\" }` |\n| `amba_functions_pause_schedule` | Pause a scheduled trigger without deleting it. | `{ project_id, function_id }` |\n| `amba_functions_resume_schedule` | Resume. | `{ project_id, function_id }` |\n| `amba_functions_trigger_schedule` | Fire a scheduled function ad-hoc (testing). | `{ project_id, function_id }` |\n\n### AI prompts\n\nManaged LLM templates: stored prompt with model + system message + variables, callable by name from the SDK. The actual LLM call is rewritten per-tenant — the customer's API keys (Anthropic / OpenAI) live in the tenant secrets, never on the device.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_ai_prompts_create` | Create a prompt template. | `{ project_id, key: \"summarize\", model: \"claude-opus-4-5\", system: \"Summarize the user's text in 2 sentences.\", variables: [\"text\"] }` |\n| `amba_ai_prompts_list` | List prompts. | `{ project_id }` |\n| `amba_ai_prompts_get` | Read one prompt. | `{ project_id, key }` |\n| `amba_ai_prompts_update` | Edit a prompt. | `{ project_id, key, system: \"...\" }` |\n| `amba_ai_prompts_invoke` | Invoke a prompt server-side (admin testing). | `{ project_id, key, variables: { text: \"...\" } }` |\n| `amba_ai_prompts_delete` | Delete. | `{ project_id, key }` |\n\n### Analytics + events + sessions\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_analytics_get` | Top-level metrics dashboard (MAU, DAU, retention). | `{ project_id, period: \"7d\" }` |\n| `amba_events_list` | Browse raw events. | `{ project_id, limit: 100, since: \"2026-05-19T00:00:00Z\" }` |\n| `amba_events_count` | Count events matching a filter. | `{ project_id, event: \"workout_completed\", since: \"...\" }` |\n| `amba_sessions_list` | List user sessions. | `{ project_id, limit: 50 }` |\n| `amba_sessions_analytics` | Session-level metrics. | `{ project_id, period: \"7d\" }` |\n| `amba_users_list_events` | Per-user event history. | `{ project_id, user_id }` |\n| `amba_users_export` | Export the full user list. | `{ project_id, format: \"csv\" }` |\n\n### Secrets + configs + integrations\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_secrets_set` | Set a tenant secret (encrypted at rest). | `{ project_id, name: \"OPENAI_API_KEY\", value: \"sk-...\" }` |\n| `amba_secrets_get` | Read a secret (returns `\"<redacted>\"` unless explicitly requested). | `{ project_id, name }` |\n| `amba_secrets_list` | List secret names. | `{ project_id }` |\n| `amba_secrets_delete` | Delete. | `{ project_id, name }` |\n| `amba_configs_create` | Create a runtime config value (read from SDK as `Amba.config.fetch()`). | `{ project_id, key: \"primary_color\", value: \"#ff0066\", segment_id: null }` |\n| `amba_configs_list` | List configs. | `{ project_id }` |\n| `amba_configs_update` | Edit. | `{ project_id, config_id, value: \"...\" }` |\n| `amba_configs_delete` | Delete. | `{ project_id, config_id }` |\n| `amba_integrations_list` | List third-party integrations. | `{ project_id }` |\n| `amba_integrations_configure` | Configure a provider. | `{ project_id, provider: \"revenuecat\", config: { webhook_secret: \"...\", default_offering: \"...\" } }` |\n| `amba_integrations_set` | Set/replace integration config wholesale. | `{ project_id, provider, config }` |\n| `amba_integrations_patch` | Patch one field. | `{ project_id, provider, patch: { webhook_secret: \"...\" } }` |\n| `amba_integrations_test` | Send a test event to a configured provider. | `{ project_id, provider }` |\n\n### Media (file storage + CDN)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_media_upload` | Upload a file (returns a tenant-scoped URL). | `{ project_id, name: \"logo.png\", content_type: \"image/png\", data: \"<base64>\" }` |\n| `amba_media_list` | List files. | `{ project_id, folder: \"/\", limit: 100 }` |\n| `amba_media_delete` | Delete a file. | `{ project_id, file_id }` |\n| `amba_media_create_folder` | Create a logical folder. | `{ project_id, path: \"/uploads/avatars\" }` |\n| `amba_media_list_folders` | List folders. | `{ project_id }` |\n| `amba_media_delete_folder` | Delete a folder (must be empty). | `{ project_id, path }` |\n\n### Sites (static asset hosting)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_sites_deploy` | Deploy a static site bundle (zip / tar). | `{ project_id, name: \"marketing\", bundle: \"<base64>\", index: \"index.html\" }` |\n| `amba_sites_list` | List sites. | `{ project_id }` |\n| `amba_sites_get` | Read a site. | `{ project_id, site_id }` |\n| `amba_sites_add_domain` | Attach a custom domain. | `{ project_id, site_id, domain: \"marketing.example.com\" }` |\n| `amba_sites_list_domains` | List domains on a site. | `{ project_id, site_id }` |\n| `amba_sites_remove_domain` | Detach a domain. | `{ project_id, site_id, domain }` |\n| `amba_sites_delete` | Delete a site. | `{ project_id, site_id }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first. The infrastructure surfaces — collections, AI, config, flags, events — are SDK-side reads; the snippets below show what the client calls look like.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\n// Collections — typed table, user-scoped reads + writes\ntype Todo = { id: string; title: string; done: boolean; created_at: string };\n\nconst { data: todos } = await Amba.collections.find<Todo>('todos', {\n filter: Amba.collections.where.eq('done', false),\n order: [{ column: 'created_at', direction: 'desc' }],\n limit: 50,\n});\n\nconst newTodo = await Amba.collections.insert('todos', { title: 'Ship the app', done: false });\nawait Amba.collections.update('todos', newTodo.id, { done: true });\nawait Amba.collections.delete('todos', newTodo.id);\n\n// AI — call a managed prompt\nconst response = await Amba.ai.anthropic.messages.create({\n prompt_key: 'summarize',\n variables: { text: 'A long article about backend services …' },\n});\n\n// Track an analytics event\nawait Amba.events.track('button_clicked', { button: 'cta' });\n\n// Read runtime config\nconst config = await Amba.config.fetch();\n\n// Read a feature flag\nconst showBeta = await Amba.flags.get('beta_feature');\n\n// Diagnostics — wire-verify\nconst ping = await Amba.diagnostics.ping();\nif (!ping.ok) console.error('Amba misconfigured:', ping);\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst { data: todos } = await Amba.collections.find('todos', {\n filter: Amba.collections.where.eq('done', false),\n limit: 50,\n});\nawait Amba.collections.insert('todos', { title: 'Ship', done: false });\nawait Amba.events.track('page_view', { path: location.pathname });\n```\n\nWith `@layers/amba-react`:\n\n```tsx\nimport { useCollection, useFlag } from '@layers/amba-react';\n\nfunction TodoList() {\n const { data: todos, loading, refetch } = useCollection<{ id: string; title: string }>('todos');\n const showArchive = useFlag('archive_todos');\n if (loading) return <Spinner />;\n return (\n <ul>\n {todos?.map(t => <li key={t.id}>{t.title}</li>)}\n {showArchive && <ArchiveButton onArchive={refetch} />}\n </ul>\n );\n}\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nstruct Todo: Codable {\n let id: String\n let title: String\n let done: Bool\n}\n\nlet response = try await Amba.collections.find(\"todos\", as: Todo.self)\n_ = try await Amba.collections.insert(\"todos\", row: [\"title\": \"Ship\", \"done\": false])\n\nlet config = try await Amba.config.fetch()\nlet showBeta = try await Amba.flags.get(name: \"beta_feature\")\ntry await Amba.events.track(\"app_opened\", properties: [\"source\": \"deep_link\"])\n\nlet reply = try await Amba.ai.anthropic.messages.create(\n promptKey: \"summarize\",\n variables: [\"text\": \"A long article...\"]\n)\n```\n\n### Android (Kotlin)\n\n```kotlin\ndata class Todo(val id: String, val title: String, val done: Boolean)\n\nval todos = Amba.collections.find<Todo>(\"todos\")\nAmba.collections.insert(\"todos\", mapOf(\"title\" to \"Ship\", \"done\" to false))\n\nval config = Amba.config.fetch()\nval showBeta = Amba.flags.get(\"beta_feature\")\nAmba.events.track(\"app_opened\", mapOf(\"source\" to \"deep_link\"))\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal response = await Amba.collections.find('todos', limit: 50);\nawait Amba.collections.insert('todos', {'title': 'Ship', 'done': false});\nfinal config = await Amba.config.fetch();\nfinal showBeta = await Amba.flags.get('beta_feature');\nawait Amba.events.track('app_opened', {'source': 'deep_link'});\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Custom data tables (collections):** any domain-specific tables to create?\n - Yes — I'll list them. (For each: name + columns + types.)\n - No, just use the canned Amba surfaces (auth, push, gamification, etc.)\n - Auto-create from the existing code's models — read `lib/models/`, `src/types/`, `Models/`, infer column lists, confirm with me.\n\n2. **Custom backend logic (functions):** any server-side code to deploy?\n - Yes — describe what it should do. (Then offer to scaffold a function template and deploy.)\n - No\n\n3. **AI features:** want managed LLM prompts?\n - Yes — what's the use case? (summarize, translate, classify, generate, custom)\n - No\n\n4. **Analytics:** which tracker do you want?\n - Only Amba's built-in events (recommended — already wired)\n - Amba + Mixpanel / PostHog / Segment forwarding (configure via `amba_integrations_configure`)\n - None (rarely useful — events drive XP / achievements / streaks; disabling cripples gamification)\n\n5. **Third-party integrations to set up:**\n - [ ] RevenueCat (IAP / subscriptions on iOS + Android)\n - [ ] Superwall (paywall A/B)\n - [ ] Resend (transactional email)\n - [ ] Stripe (web payments / subscriptions)\n - [ ] Mixpanel / PostHog / Segment (analytics forwarding)\n - [ ] OpenAI / Anthropic (LLM keys — required for `Amba.ai.*` calls)\n\n6. **Feature flags:** seed any starter flags?\n - Yes — wire `beta_feature` (off by default) so I can ship the wiring before the feature exists\n - No\n\n7. **Static site:** want a marketing page hosted under your tenant subdomain?\n - Yes — scaffold and deploy a 1-page index\n - No\n\n## Re-run behavior\n\n1. Before creating:\n - `amba_collections_list` — match on `name`. Collisions: never silently recreate (data loss). Offer `amba_collections_alter` to add new columns instead.\n - `amba_functions_list` — match on `name`. Collisions: ask to redeploy (with the new source) or skip.\n - `amba_ai_prompts_list` — match on `key`. Same.\n - `amba_integrations_list` — match on `provider`. Same.\n - `amba_configs_list` — match on `key`. Same.\n\n2. **Never call `amba_collections_delete` on re-run unless the user explicitly asks** — this drops the underlying table and every row in it across every user of the tenant.\n\n3. For functions: re-deploying replaces source in place (versioned server-side). It's safe to call `amba_functions_deploy` with the same name + new source.\n\n4. For integrations: if a provider is already configured, prefer `amba_integrations_patch` (partial update) over `amba_integrations_set` (full replace).\n\n5. Secrets: don't list secret values in chat output, even on read. Just confirm \"OPENAI_API_KEY is set\" / \"not set\".\n";
96
+ readonly body: "# Infrastructure\n\nThe plumbing that sits behind every other surface: custom database tables (Collections — schema-first, per-tenant), serverless functions (run server-side code without standing up a backend), analytics (events + sessions), AI prompts (managed LLM templates, callable from the SDK with per-tenant keys), secrets, runtime configs, feature flags, third-party integrations (RevenueCat / Superwall / Resend / Stripe / etc.), media (file storage + CDN), and sites (static asset hosting at `*.app.amba.host`).\n\nIf gamification, economy, and social are the playable surface, **infrastructure is what you build a custom product on top of**. Anything that doesn't fit the canned surfaces lands here.\n\n## MCP tools\n\n### Collections (typed tables)\n\nA collection is a schema-first table inside the project's isolated tenant database. You describe the columns, the server creates the table and any indexes. Rows are scoped to the signed-in `app_user` automatically (server-enforced auto row-level isolation) for SDK clients — admin tools bypass this.\n\nAdmin tools authenticate the developer/agent (pass `pat` or send it as the inbound Bearer) and take `project_id`. Client tools authenticate an end-user and take `api_key` (+ `session_token`) — NOT `project_id` and NOT a `pat`. Every row tool names the collection with `name`, never `collection`.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_collections_create` | Create a typed collection. Pass `shared: true` for developer-seeded GLOBAL content (question banks, lookup tables) so `user_id` is nullable. | `{ project_id, name: \"todos\", columns: [{ name: \"title\", type: \"text\", nullable: false }, { name: \"done\", type: \"boolean\", nullable: false, default: false }, { name: \"due_at\", type: \"timestamptz\", nullable: true }], shared: false }` |\n| `amba_collections_list` | List collections in this project. | `{ project_id }` |\n| `amba_collections_get` | Read one collection's schema. | `{ project_id, name: \"todos\" }` |\n| `amba_collections_alter` | Exactly ONE of: `add_column`, `add_index`, `drop_column`, or `relax_user_id` per call. `relax_user_id: true` converts an existing collection to shared (drops the `user_id` NOT NULL). | `{ project_id, name: \"todos\", add_column: { name: \"priority\", type: \"integer\", nullable: true } }` |\n| `amba_collections_delete` | Drop the table (destructive). `confirm` must equal the collection name. | `{ project_id, name: \"todos\", confirm: \"todos\" }` |\n| `amba_admin_insert_row` | Insert one row as the developer (bypasses user-scope; `user_id` honored if present). | `{ project_id, name: \"todos\", row: { title: \"Sample\", done: false } }` |\n| `amba_admin_insert_rows` | Bulk-insert up to 500 rows in one atomic statement — the canonical seeding/migration path. `on_conflict`: `\"error\"` (default) or `\"skip\"`. | `{ project_id, name: \"questions\", rows: [{ q: \"...\" }, { q: \"...\" }], on_conflict: \"skip\" }` |\n| `amba_admin_list_rows` | Read rows as the developer. | `{ project_id, name: \"todos\", limit: 100 }` |\n| `amba_client_insert_row` | Insert as an end-user. Requires `api_key` (+ `session_token`). | `{ api_key, session_token, name: \"todos\", row: {...} }` |\n| `amba_client_list_rows` | Read as an end-user (auto user-scoped). | `{ api_key, session_token, name: \"todos\" }` |\n| `amba_client_get_row` | Get one row by id (end-user). | `{ api_key, session_token, name: \"todos\", id }` |\n| `amba_client_update_row` | Update one row by id (end-user). Fields go in `set`. Omit `id` + pass `where` for a bulk update. | `{ api_key, session_token, name: \"todos\", id, set: {...} }` |\n| `amba_client_delete_row` | Soft-delete one row by id (end-user). | `{ api_key, session_token, name: \"todos\", id }` |\n| `amba_client_count_rows` | Count rows matching an optional `where`. | `{ api_key, session_token, name: \"todos\", where: {...} }` |\n| `amba_client_find_rows` | Filter / sort / paginate rows (SDK-shaped `filter`). | `{ api_key, session_token, name: \"todos\", filter: {...}, order: [\"created_at desc\"], limit: 50 }` |\n| `amba_client_find_nearest_rows` | Vector-similarity search (rows with a `vector(<dim>)` column). | `{ api_key, session_token, name: \"todos\", column: \"embedding\", to_vector: [...], k: 10 }` |\n\nColumn types: `text`, `integer`, `bigint`, `numeric`, `boolean`, `timestamptz`, `date`, `jsonb`, `uuid`, `vector(<dim>)` (e.g. `vector(1536)` for OpenAI embeddings), plus array forms `text[]`, `integer[]`, `bigint[]`, `numeric[]`, `boolean[]`, `uuid[]`. Use `integer` (not `int`), `numeric` (not `float`/`real`/`double`), and `jsonb` (not `json`) — the validator rejects the aliases.\n\n### Functions (serverless code)\n\nRun user code in a sandbox triggered by HTTP, cron, or webhook. The function gets the tenant connection automatically via injected env.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_functions_deploy` | Deploy a function from source. | `{ project_id, name: \"send_welcome_email\", runtime: \"node22\", source: \"export default async (req) => { ... }\", trigger: { type: \"http\" } }` |\n| `amba_functions_list` | List functions. | `{ project_id }` |\n| `amba_functions_get` | Read function metadata. | `{ project_id, function_id }` |\n| `amba_functions_get_logs` | Recent invocation logs. | `{ project_id, function_id, limit: 100 }` |\n| `amba_functions_delete` | Delete a function. | `{ project_id, function_id }` |\n| `amba_functions_schedule` | Attach a cron schedule. | `{ project_id, function_id, cron: \"0 9 * * *\", timezone: \"America/Los_Angeles\" }` |\n| `amba_functions_pause_schedule` | Pause a scheduled trigger without deleting it. | `{ project_id, function_id }` |\n| `amba_functions_resume_schedule` | Resume. | `{ project_id, function_id }` |\n| `amba_functions_trigger_schedule` | Fire a scheduled function ad-hoc (testing). | `{ project_id, function_id }` |\n\n### AI prompts\n\nManaged LLM templates: a stored prompt with provider + model + system message, invoked by name from the SDK. The actual LLM call is rewritten server-side per-tenant — the customer's provider API key (Anthropic / OpenAI / Mistral / Gemini) stays server-side, never on the device.\n\n**Two steps, in order:** first register the provider key with `amba_ai_providers_set`, then create prompts against it. A prompt registered before its provider has a key still saves, but invocations fail with `provider_not_configured` (424) until the key is set.\n\n> The provider key is **not** a function secret. `amba_secrets_set` writes function-scoped Worker secrets, which the AI gateway never reads. Provider keys live in a separate gateway-owned store and are set **only** via `amba_ai_providers_set`.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_ai_providers_set` | Register / rotate the upstream provider API key. **Do this first.** | `{ project_id, provider: \"anthropic\", api_key: \"sk-ant-...\" }` |\n| `amba_ai_providers_list` | List registered providers (`configured` = key set). | `{ project_id }` |\n| `amba_ai_providers_delete` | Revoke a provider key (fails if prompts still reference it). | `{ project_id, provider: \"anthropic\" }` |\n| `amba_ai_prompts_create` | Create a prompt template. `client_invokable: true` lets the device SDK invoke it directly. | `{ project_id, name: \"summarize\", provider: \"anthropic\", model: \"claude-opus-4-5\", system_prompt: \"Summarize the user's text in 2 sentences.\", client_invokable: true }` |\n| `amba_ai_prompts_list` | List prompts. | `{ project_id }` |\n| `amba_ai_prompts_get` | Read one prompt. | `{ project_id, name }` |\n| `amba_ai_prompts_update` | Edit a prompt (replaces all fields; bumps version). | `{ project_id, name, provider, model, system_prompt: \"...\" }` |\n| `amba_ai_prompts_invoke` | Invoke a prompt server-side (admin testing). `messages` is a provider-shaped array. | `{ project_id, name, messages: [{ role: \"user\", content: \"...\" }] }` |\n| `amba_ai_prompts_delete` | Delete. | `{ project_id, name }` |\n\n### Analytics + events + sessions\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_analytics_get` | Top-level metrics dashboard (MAU, DAU, retention). | `{ project_id, period: \"7d\" }` |\n| `amba_events_list` | Browse raw events. | `{ project_id, limit: 100, since: \"2026-05-19T00:00:00Z\" }` |\n| `amba_events_count` | Count events matching a filter. | `{ project_id, event: \"workout_completed\", since: \"...\" }` |\n| `amba_sessions_list` | List user sessions. | `{ project_id, limit: 50 }` |\n| `amba_sessions_analytics` | Session-level metrics. | `{ project_id, period: \"7d\" }` |\n| `amba_users_list_events` | Per-user event history. | `{ project_id, user_id }` |\n| `amba_users_export` | Export the full user list. | `{ project_id, format: \"csv\" }` |\n\n### Secrets + configs + integrations\n\nSecrets here are **function-scoped** — they become environment bindings on your deployed functions. They are NOT where AI provider keys go (use `amba_ai_providers_set` for those — see AI prompts above).\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_secrets_set` | Set a function-scoped secret (encrypted at rest; bound on the next deploy). | `{ project_id, name: \"STRIPE_WEBHOOK_SECRET\", value: \"whsec_...\" }` |\n| `amba_secrets_get` | Read a secret (returns `\"<redacted>\"` unless explicitly requested). | `{ project_id, name }` |\n| `amba_secrets_list` | List secret names. | `{ project_id }` |\n| `amba_secrets_delete` | Delete. | `{ project_id, name }` |\n| `amba_configs_create` | Create a runtime config value (read from SDK as `Amba.config.fetch()`). | `{ project_id, key: \"primary_color\", value: \"#ff0066\", segment_id: null }` |\n| `amba_configs_list` | List configs. | `{ project_id }` |\n| `amba_configs_update` | Edit. | `{ project_id, config_id, value: \"...\" }` |\n| `amba_configs_delete` | Delete. | `{ project_id, config_id }` |\n| `amba_integrations_list` | List third-party integrations. | `{ project_id }` |\n| `amba_integrations_configure` | Configure a provider. | `{ project_id, provider: \"revenuecat\", config: { webhook_secret: \"...\", default_offering: \"...\" } }` |\n| `amba_integrations_set` | Set/replace integration config wholesale. | `{ project_id, provider, config }` |\n| `amba_integrations_patch` | Patch one field. | `{ project_id, provider, patch: { webhook_secret: \"...\" } }` |\n| `amba_integrations_test` | Send a test event to a configured provider. | `{ project_id, provider }` |\n\n### Media (file storage + CDN)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_media_upload` | Upload a file (returns a tenant-scoped URL). | `{ project_id, name: \"logo.png\", content_type: \"image/png\", data: \"<base64>\" }` |\n| `amba_media_list` | List files. | `{ project_id, folder: \"/\", limit: 100 }` |\n| `amba_media_delete` | Delete a file. | `{ project_id, file_id }` |\n| `amba_media_create_folder` | Create a logical folder. | `{ project_id, path: \"/uploads/avatars\" }` |\n| `amba_media_list_folders` | List folders. | `{ project_id }` |\n| `amba_media_delete_folder` | Delete a folder (must be empty). | `{ project_id, path }` |\n\n### Sites (static asset hosting)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_sites_deploy` | Deploy a static site bundle (zip / tar). | `{ project_id, name: \"marketing\", bundle: \"<base64>\", index: \"index.html\" }` |\n| `amba_sites_list` | List sites. | `{ project_id }` |\n| `amba_sites_get` | Read a site. | `{ project_id, site_id }` |\n| `amba_sites_add_domain` | Attach a custom domain. | `{ project_id, site_id, domain: \"marketing.example.com\" }` |\n| `amba_sites_list_domains` | List domains on a site. | `{ project_id, site_id }` |\n| `amba_sites_remove_domain` | Detach a domain. | `{ project_id, site_id, domain }` |\n| `amba_sites_delete` | Delete a site. | `{ project_id, site_id }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first. The infrastructure surfaces — collections, AI, config, flags, events — are SDK-side reads; the snippets below show what the client calls look like.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\n// Collections — typed table, user-scoped reads + writes\ntype Todo = { id: string; title: string; done: boolean; created_at: string };\n\nconst { data: todos } = await Amba.collections.find<Todo>('todos', {\n filter: Amba.collections.where.eq('done', false),\n order: [{ column: 'created_at', direction: 'desc' }],\n limit: 50,\n});\n\nconst newTodo = await Amba.collections.insert('todos', { title: 'Ship the app', done: false });\nawait Amba.collections.update('todos', newTodo.id, { done: true });\nawait Amba.collections.delete('todos', newTodo.id);\n\n// AI — call a managed prompt (prompt_slug names the registered prompt)\nconst response = await Amba.ai.anthropic.messages.create({\n prompt_slug: 'summarize',\n variables: { text: 'A long article about backend services …' },\n});\n\n// Track an analytics event\nawait Amba.events.track('button_clicked', { button: 'cta' });\n\n// Read runtime config\nconst config = await Amba.config.fetch();\n\n// Read a feature flag\nconst showBeta = await Amba.flags.get('beta_feature');\n\n// Diagnostics — wire-verify\nconst ping = await Amba.diagnostics.ping();\nif (!ping.ok) console.error('Amba misconfigured:', ping);\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst { data: todos } = await Amba.collections.find('todos', {\n filter: Amba.collections.where.eq('done', false),\n limit: 50,\n});\nawait Amba.collections.insert('todos', { title: 'Ship', done: false });\nawait Amba.events.track('page_view', { path: location.pathname });\n```\n\nWith `@layers/amba-react`:\n\n```tsx\nimport { useCollection, useFlag } from '@layers/amba-react';\n\nfunction TodoList() {\n const { data: todos, loading, refetch } = useCollection<{ id: string; title: string }>('todos');\n const showArchive = useFlag('archive_todos');\n if (loading) return <Spinner />;\n return (\n <ul>\n {todos?.map(t => <li key={t.id}>{t.title}</li>)}\n {showArchive && <ArchiveButton onArchive={refetch} />}\n </ul>\n );\n}\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nstruct Todo: Codable {\n let id: String\n let title: String\n let done: Bool\n}\n\nlet response = try await Amba.collections.find(\"todos\", as: Todo.self)\n_ = try await Amba.collections.insert(\"todos\", row: [\"title\": \"Ship\", \"done\": false])\n\nlet config = try await Amba.config.fetch()\nlet showBeta = try await Amba.flags.get(name: \"beta_feature\")\ntry await Amba.events.track(\"app_opened\", properties: [\"source\": \"deep_link\"])\n\nlet reply = try await Amba.ai.anthropic.messages.create(\n request: AiMessageRequest(promptSlug: \"summarize\", variables: [\"text\": \"A long article...\"])\n)\n```\n\n### Android (Kotlin)\n\n```kotlin\ndata class Todo(val id: String, val title: String, val done: Boolean)\n\nval todos = Amba.collections.find<Todo>(\"todos\")\nAmba.collections.insert(\"todos\", mapOf(\"title\" to \"Ship\", \"done\" to false))\n\nval config = Amba.config.fetch()\nval showBeta = Amba.flags.get(\"beta_feature\")\nAmba.events.track(\"app_opened\", mapOf(\"source\" to \"deep_link\"))\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal response = await Amba.collections.find('todos', limit: 50);\nawait Amba.collections.insert('todos', {'title': 'Ship', 'done': false});\nfinal config = await Amba.config.fetch();\nfinal showBeta = await Amba.flags.get('beta_feature');\nawait Amba.events.track('app_opened', {'source': 'deep_link'});\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Custom data tables (collections):** any domain-specific tables to create?\n - Yes — I'll list them. (For each: name + columns + types.)\n - No, just use the canned Amba surfaces (auth, push, gamification, etc.)\n - Auto-create from the existing code's models — read `lib/models/`, `src/types/`, `Models/`, infer column lists, confirm with me.\n\n2. **Custom backend logic (functions):** any server-side code to deploy?\n - Yes — describe what it should do. (Then offer to scaffold a function template and deploy.)\n - No\n\n3. **AI features:** want managed LLM prompts?\n - Yes — what's the use case? (summarize, translate, classify, generate, custom)\n - No\n\n4. **Analytics:** which tracker do you want?\n - Only Amba's built-in events (recommended — already wired)\n - Amba + Mixpanel / PostHog / Segment forwarding (configure via `amba_integrations_configure`)\n - None (rarely useful — events drive XP / achievements / streaks; disabling cripples gamification)\n\n5. **Third-party integrations to set up:**\n - [ ] RevenueCat (IAP / subscriptions on iOS + Android)\n - [ ] Superwall (paywall A/B)\n - [ ] Resend (transactional email)\n - [ ] Stripe (web payments / subscriptions)\n - [ ] Mixpanel / PostHog / Segment (analytics forwarding)\n - [ ] OpenAI / Anthropic / Mistral / Gemini LLM keys (required for `Amba.ai.*` — set via `amba_ai_providers_set`, **not** `amba_integrations_configure`)\n\n6. **Feature flags:** seed any starter flags?\n - Yes — wire `beta_feature` (off by default) so I can ship the wiring before the feature exists\n - No\n\n7. **Static site:** want a marketing page hosted under your tenant subdomain?\n - Yes — scaffold and deploy a 1-page index\n - No\n\n## Re-run behavior\n\n1. Before creating:\n - `amba_collections_list` — match on `name`. Collisions: never silently recreate (data loss). Offer `amba_collections_alter` to add new columns instead.\n - `amba_functions_list` — match on `name`. Collisions: ask to redeploy (with the new source) or skip.\n - `amba_ai_prompts_list` — match on `name`. Same. (And `amba_ai_providers_list` — match on `provider`; re-running `amba_ai_providers_set` rotates the key in place.)\n - `amba_integrations_list` — match on `provider`. Same.\n - `amba_configs_list` — match on `key`. Same.\n\n2. **Never call `amba_collections_delete` on re-run unless the user explicitly asks** — this drops the underlying table and every row in it across every user of the tenant.\n\n3. For functions: re-deploying replaces source in place (versioned server-side). It's safe to call `amba_functions_deploy` with the same name + new source.\n\n4. For integrations: if a provider is already configured, prefer `amba_integrations_patch` (partial update) over `amba_integrations_set` (full replace).\n\n5. Secrets: don't list secret values in chat output, even on read. Just confirm \"OPENAI_API_KEY is set\" / \"not set\".\n";
97
97
  readonly surface: "infrastructure";
98
98
  readonly title: "Amba setup — infrastructure";
99
99
  readonly description: string;
@@ -22,9 +22,11 @@
22
22
  * API shape per provider.
23
23
  *
24
24
  * The customer brings their own provider key — they must first register
25
- * a provider via the (currently CLI-only) `POST /ai/providers` flow.
26
- * Prompt registration fails with PROVIDER_NOT_REGISTERED (409) if the
27
- * provider isn't set up.
25
+ * a provider via `amba_ai_providers_set` (or the `POST /ai/providers` REST
26
+ * route). Prompt registration fails with PROVIDER_NOT_REGISTERED (409) if
27
+ * the provider isn't set up; and a prompt registered against a provider
28
+ * with no key yet comes back with a `warning` and invokes with
29
+ * `provider_not_configured` (424) until the key is set.
28
30
  *
29
31
  * Authentication: every tool accepts an optional inline `pat` arg via the
30
32
  * `registerTool` helper in `../lib/with-pat.ts`. Resolution: `args.pat ??
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Admin AI *provider* MCP tools.
3
+ *
4
+ * Wraps `/admin/projects/:p/ai/providers/*` — register / list / revoke the
5
+ * upstream provider API key (Anthropic / OpenAI / Mistral / Gemini account
6
+ * key) that the AI gateway uses to call the model on the customer's behalf.
7
+ *
8
+ * Why this exists: registering a provider key was previously reachable ONLY
9
+ * via the CLI / console — there was no agent path. That left an
10
+ * agent-driven setup able to provision everything *except* the one secret
11
+ * that makes `Amba.ai.*` work, forcing a console detour. These tools close
12
+ * that gap so an agent can stand up the full AI surface end-to-end:
13
+ *
14
+ * 1. `amba_ai_providers_set` — register / rotate the provider key.
15
+ * 2. `amba_ai_prompts_create` — register a named prompt against it.
16
+ * 3. `Amba.ai.*` from the SDK — invoke it at runtime.
17
+ *
18
+ * IMPORTANT — this is NOT `amba_secrets_set`. Function-scoped secrets
19
+ * (`amba_secrets_set`) become Worker bindings for deployed *functions*; the
20
+ * AI gateway never reads them. The provider key lives in a separate,
21
+ * gateway-owned secret namespace and is populated ONLY through these tools
22
+ * (or the equivalent `POST /ai/providers` REST route).
23
+ *
24
+ * Authentication: every tool accepts an optional inline `pat` arg via the
25
+ * `registerTool` helper in `../lib/with-pat.ts`. Resolution: `args.pat ??
26
+ * inbound-Authorization-Bearer`, with `MISSING_PAT` short-circuit if neither.
27
+ */
28
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
29
+ import type { ApiClient } from '../api-client.js';
30
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Event catalog + control-plane webhook tools — discover what a project can
3
+ * emit, and subscribe webhooks to PLATFORM lifecycle events.
4
+ *
5
+ * amba_events_catalog GET /webhooks/catalog
6
+ * amba_control_webhooks_create POST /control-webhooks
7
+ * amba_control_webhooks_list GET /control-webhooks
8
+ * amba_control_webhooks_delete DELETE /control-webhooks/:id
9
+ *
10
+ * Tenant/app-event subscription tools live in `webhooks.ts` (those subscribe to
11
+ * APP events like `economy.currency.spent`). These cover CONTROL-plane events
12
+ * about the project itself — provisioning, deploys, domains, billing — which is
13
+ * what an autonomous provisioning agent wants to react to.
14
+ *
15
+ * Descriptions stay provider-neutral and nudge toward the production pattern
16
+ * (discover the catalog → wire the lifecycle events) without overclaiming.
17
+ */
18
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
19
+ import type { ApiClient } from '../api-client.js';
20
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Admin A/B experiment MCP tools (#22).
3
+ *
4
+ * Wraps `/admin/projects/:p/experiments/*`. An experiment is a named key +
5
+ * weighted variants + status (active/paused/ended). End-users are bucketed
6
+ * into a sticky variant the first time they ask for an assignment (via the
7
+ * SDK / client API), and that bucket survives variant re-weights. These tools
8
+ * are the developer-facing management surface: define experiments, list/get/
9
+ * update/delete them, and read aggregated results.
10
+ *
11
+ * `amba_experiments_results` joins the exposure log against the analytics
12
+ * sink: for a supplied `conversion_event` it counts, per variant, the distinct
13
+ * exposed users who emitted that event after assignment, and runs a
14
+ * two-proportion z-test of each non-control variant against the control (the
15
+ * FIRST variant) returning a z-score + approximate two-sided p-value.
16
+ *
17
+ * Authentication: every tool accepts an optional inline `pat` arg via the
18
+ * `registerTool` helper in `../lib/with-pat.ts` (resolution: `args.pat ??
19
+ * inbound-Authorization-Bearer`, with `MISSING_PAT` short-circuit). Handlers
20
+ * use the bound `{ client }` from the context, which carries the resolved PAT.
21
+ */
22
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
23
+ import type { ApiClient } from '../api-client.js';
24
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
@@ -5,7 +5,7 @@
5
5
  * deploy a bundled script, list deployments, describe one, delete a
6
6
  * function (cascade), and full schedule lifecycle (create, pause,
7
7
  * resume, trigger now). Logs read is the last operation in the set —
8
- * Workers Logpush events for a single function over a bounded time
8
+ * Function logs for a single function over a bounded time
9
9
  * range.
10
10
  *
11
11
  * Wire details:
@@ -0,0 +1,12 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ApiClient } from '../api-client.js';
3
+ /**
4
+ * Leagues — the Duolingo-style promote/demote tiers (Bronze/Silver/Gold/…).
5
+ * Distinct from leaderboards (a single ranked list): a league has weekly
6
+ * cohorts that the rollover workflow reshuffles. These tools provision and
7
+ * inspect leagues; the admin routes are mounted at
8
+ * `/v1/admin/projects/:projectId/leagues`. Members are assigned by the weekly
9
+ * rollover (the scheduled weekly rollover) and score live off `xp_awarded`
10
+ * engagement events.
11
+ */
12
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Operation-handle MCP tools — poll an async operation to done | failed.
3
+ *
4
+ * Several Amba actions kick off work that may take a moment (e.g. buying a
5
+ * domain). Those endpoints return an `operation_id`; pass it to
6
+ * `amba_operations_get` to read the current status without re-triggering the
7
+ * action. Poll until `status` is `succeeded` or `failed` — on `failed`,
8
+ * `failed_reason` explains why.
9
+ *
10
+ * Tools:
11
+ * - `amba_operations_get` — read one operation's status (poll to done).
12
+ * - `amba_operations_list` — list operations, optionally filtered by kind
13
+ * and/or status.
14
+ *
15
+ * Both are READ-ONLY — they never start or change an operation, only report
16
+ * it. The producing tool (e.g. `amba_domains_purchase`) is what creates the
17
+ * operation and returns its id.
18
+ *
19
+ * Authentication: every tool accepts an optional inline `pat` via the
20
+ * `registerTool` helper (see `../lib/with-pat.ts`).
21
+ */
22
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
23
+ import type { ApiClient } from '../api-client.js';
24
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Cross-environment promotion MCP tools (Blush feedback #24).
3
+ *
4
+ * The agentic "promote dev → prod" flow. Instead of replaying dozens of
5
+ * sequential create calls to stand up a second project, an agent:
6
+ *
7
+ * 1. `amba_project_export({ project_id: dev })` → a declarative bundle of
8
+ * the dev project's reusable configuration (flags, collection schemas,
9
+ * content libraries + items, and the full gamification set: currencies,
10
+ * achievements, streaks, challenges, leaderboards, xp rules).
11
+ * 2. `amba_project_import({ project_id: prod, bundle, mode })` → idempotently
12
+ * applies that bundle to the prod project.
13
+ *
14
+ * The bundle is configuration only — no secrets, no per-user data, no
15
+ * end-user rows. Import is idempotent: `skip_existing` (default) no-ops on a
16
+ * name/key/code conflict; `merge` refreshes the matching definition. Safe to
17
+ * run twice.
18
+ *
19
+ * Wraps `GET /admin/projects/:p/promotion/export` and
20
+ * `POST /admin/projects/:p/promotion/import`.
21
+ *
22
+ * Authentication: PAT via the `registerTool` helper (mirrors
23
+ * `ai-prompts-admin.ts`). `args.pat ?? inbound-Bearer`, `MISSING_PAT`
24
+ * short-circuit if neither.
25
+ */
26
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
27
+ import type { ApiClient } from '../api-client.js';
28
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layers/amba-mcp",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "license": "Apache-2.0",
5
5
  "engines": {
6
6
  "node": ">=22"