@layers/amba-mcp 4.0.2 → 4.0.3

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.
@@ -77,7 +77,7 @@ export declare const AMBA_SETUP_SUB_RESOURCES: readonly [{
77
77
  readonly name: "amba-setup-economy";
78
78
  readonly uri: "amba://setup/economy";
79
79
  readonly mime: "text/markdown";
80
- readonly body: "# Economy\n\nVirtual currencies, the catalog of things they buy, stores (curated catalog subsets, possibly segment-gated), and the per-user inventory. Currencies come in two flavors: **soft** (earned in-app, e.g. `gold` / `coins` / `gems`) and **premium** (bought with real money via App Store / Play / Stripe / RevenueCat). Both flow through the same APIs; the difference is whether real money or a tracked event is the input.\n\nPattern:\n\n1. Agent (MCP): create a currency, create catalog items, set prices, group items into one or more stores.\n2. Client SDK: read the catalog, read the store, show the offer, call `Amba.stores.purchase(...)` (real money) or `Amba.inventory.purchase(...)` (soft currency).\n\nReal-money purchases need a billing integration configured separately — see `amba://setup/infrastructure` for `amba_integrations_configure` with RevenueCat.\n\n## MCP tools\n\n### Currencies\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_currencies_create` | Define a currency. Soft or premium, with optional auto-recharge (hearts / energy). | `{ project_id, code: \"gems\", name: \"Gems\", is_premium: false, initial_balance: 0, max_balance: null }` |\n| `amba_currencies_list` | List currencies. | `{ project_id }` |\n| `amba_currencies_update` | Edit (rename, change caps, change auto-recharge). | `{ project_id, currency_id, max_balance: 10000 }` |\n| `amba_currencies_delete` | Delete a currency (irreversible — users lose their balance). | `{ project_id, currency_id }` |\n| `amba_currencies_grant` | Grant currency to a specific user. | `{ project_id, app_user_id, currency_code: \"gems\", amount: 100, reason: \"welcome_bonus\" }` |\n| `amba_currencies_get_transactions` | Per-user transaction ledger. | `{ project_id, user_id, currency_code: \"gems\", limit: 100 }` |\n\n#### Currency grant rules (auto-grants)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_currency_grant_rules_create` | Auto-grant currency on an event. | `{ project_id, currency_code: \"gems\", event_name: \"workout_completed\", amount: 10, max_per_day: 5 }` |\n| `amba_currency_grant_rules_list` | List grant rules. | `{ project_id, currency_code }` |\n| `amba_currency_grant_rules_delete` | Delete a grant rule. | `{ project_id, rule_id }` |\n\n#### Hearts / energy (auto-recharge)\n\nSet `auto_recharge_amount` + `auto_recharge_interval_hours` when creating the currency:\n\n```jsonc\n{\n \"project_id\": \"...\",\n \"code\": \"hearts\",\n \"name\": \"Hearts\",\n \"is_premium\": false,\n \"initial_balance\": 5,\n \"max_balance\": 5,\n \"auto_recharge_amount\": 1,\n \"auto_recharge_interval_hours\": 4\n}\n```\n\nThis is the Duolingo pattern: spend a heart on failure, regenerate 1 every 4 hours, capped at 5.\n\n### Catalog\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_catalog_items_create` | Create a catalog item. | `{ project_id, key: \"premium_theme\", name: \"Dark Pro Theme\", item_type: \"durable\", description: \"...\", icon_url: \"...\", category: \"themes\" }` |\n| `amba_catalog_list` | List the catalog. | `{ project_id }` |\n| `amba_catalog_items_get` | Read one item. | `{ project_id, item_id }` |\n| `amba_catalog_items_update` | Edit an item. | `{ project_id, item_id, name: \"...\" }` |\n| `amba_catalog_items_delete` | Delete an item. | `{ project_id, item_id }` |\n| `amba_catalog_items_set_price` | Set or update a price. | `{ project_id, item_id, currency_code: \"gems\", amount: 200 }` or `{ project_id, item_id, iap_product_id: \"com.example.premium_theme\" }` |\n| `amba_catalog_items_delete_price` | Delete a price. | `{ project_id, item_id, price_id }` |\n| `amba_catalog_bundles_add_item` | Add an item to a bundle. | `{ project_id, bundle_item_id, child_item_id, quantity: 1 }` |\n| `amba_catalog_bundles_remove_item` | Remove an item from a bundle. | `{ project_id, bundle_item_id, child_item_id }` |\n\nItem types:\n- `durable` — owned forever (themes, character skins, ad removal).\n- `consumable` — used up (extra lives, hint packs, energy refills).\n- `bundle` — contains other items (starter pack with 100 gems + 5 hints + 1 theme).\n\n### Stores\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_stores_create` | Create a store (curated catalog subset). Optionally segment-gated. | `{ project_id, name: \"Main Shop\", description: \"Tap to spend gems\" }` |\n| `amba_stores_list` | List stores. | `{ project_id }` |\n| `amba_stores_patch` | Edit a store. | `{ project_id, store_id, name: \"...\" }` |\n| `amba_stores_delete` | Delete a store. | `{ project_id, store_id }` |\n| `amba_stores_add_listing` | Add an item to a store. | `{ project_id, store_id, item_id, sort_order: 1, featured: true }` |\n| `amba_stores_list_listings` | List items in a store. | `{ project_id, store_id }` |\n| `amba_stores_patch_listing` | Edit a listing (re-order, mark featured). | `{ project_id, store_id, listing_id, featured: true }` |\n| `amba_stores_delete_listing` | Remove an item from a store. | `{ project_id, store_id, listing_id }` |\n\nA segment-gated store: pass `segment_id` to `amba_stores_create` — only users in that segment see it via `Amba.stores.list()`.\n\n### Inventory (admin-side grants)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_inventory_grant_item` | Grant an item to a user without payment. | `{ project_id, app_user_id, item_key: \"premium_theme\", quantity: 1 }` |\n| `amba_users_get_inventory` | Read a user's inventory. | `{ project_id, user_id }` |\n\n## SDK init per stack\n\nThe SDK side is mostly read + purchase. `Amba.configure(...)` runs first.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\nconst balances = await Amba.currencies.getBalance();\nconst items = await Amba.catalog.list();\nconst stores = await Amba.stores.list();\nconst offers = await Amba.stores.getPurchaseOptions(stores[0].key);\n\n// Soft-currency purchase\nawait Amba.inventory.purchase({ item_key: 'premium_theme', currency_code: 'gems' });\n\n// Consume a consumable\nawait Amba.inventory.consume({ item_key: 'hint_pack', quantity: 1 });\n\nconst inv = await Amba.inventory.getItems();\n```\n\nFor real-money IAP on RN, combine Amba with RevenueCat or `react-native-iap`. Capture the receipt then:\n\n```tsx\nawait Amba.stores.purchase('main_shop', product.identifier, {\n receipt: transaction.transactionReceipt,\n platform: 'ios',\n});\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst balances = await Amba.currencies.getBalance();\nconst items = await Amba.catalog.list();\nawait Amba.inventory.purchase({ item_key: 'pro_plan', currency_code: 'credits' });\n```\n\nReal-money web flow — typically Stripe Checkout. Configure a Stripe webhook via `amba_integrations_configure`; Amba fulfils via `amba_inventory_grant_item` automatically. No SDK call required.\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nlet balances = try await Amba.currencies.getBalance()\nlet items = try await Amba.catalog.list()\nlet stores = try await Amba.stores.list()\n\n_ = try await Amba.inventory.purchase(PurchaseRequest(\n itemKey: \"premium_theme\",\n currencyCode: \"gems\"\n))\n\n// Real-money via StoreKit 2\nimport StoreKit\nlet products = try await Product.products(for: [\"com.example.premium_theme\"])\nlet result = try await products[0].purchase()\nif case .success(.verified(let transaction)) = result {\n _ = try await Amba.stores.purchase(\n storeKey: \"main_shop\",\n purchaseOptionId: products[0].id,\n receipt: [\"jws_representation\": transaction.jsonRepresentation]\n )\n await transaction.finish()\n}\n```\n\n### Android (Kotlin)\n\n```kotlin\nval balances = Amba.currencies.getBalance()\nval items = Amba.catalog.list()\nAmba.inventory.purchase(PurchaseRequest(itemKey = \"premium_theme\", currencyCode = \"gems\"))\n```\n\nReal-money via Google Play Billing — capture `purchaseToken` then `Amba.stores.purchase` with `receipt: mapOf(\"purchase_token\" to purchaseToken, \"package_name\" to packageName, \"product_id\" to sku)`.\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal balances = await Amba.currencies.getBalance();\nfinal items = await Amba.catalog.list();\nawait Amba.inventory.purchase(\n PurchaseRequest(itemKey: 'premium_theme', currencyCode: 'gems'),\n);\n```\n\nFor IAP, the `in_app_purchase` plugin gives you the receipt; pass it to `Amba.stores.purchase`.\n\n## Common follow-ups\n\nBatch.\n\n1. **Virtual currency: what's it called?**\n - `gems` (recommended — neutral, premium feel)\n - `coins` / `gold`\n - `credits` (recommended for ai_chatbot)\n - `points`\n - Custom — I'll provide\n - None — no soft currency for now\n\n2. **Add a \"hearts\" / energy mechanic?** (only ask for fitness / game / education)\n - Yes — 5 hearts max, +1 every 4 hours (Duolingo style)\n - Yes but custom (ask for cap and recharge rate)\n - No\n\n3. **Premium currency too?** (real-money purchases of a tradeable virtual currency)\n - Yes — `gems` (premium) — pairs with App Store / Play / Stripe billing\n - No — only soft currency\n\n4. **Seed a starter catalog?**\n - Yes — 3 cosmetics + 1 consumable + 1 starter bundle (uses the chosen currency)\n - Yes but seed it empty — I'll add items myself\n - No\n\n5. **Stores: one store or segmented stores?**\n - One \"Main Shop\" — recommended for v1\n - Multiple — main + a \"Trial Users\" segment-gated store\n - I'll wire stores myself\n\n6. **Auto-grant rules:** earn currency on the gamification event?\n - Yes — grant 10 of `<currency>` per `<event>` (same event as XP rule), cap at 5/day\n - No — currency is granted only through achievement rewards / IAP\n\n7. **Real-money integration:** which provider?\n - RevenueCat (recommended for mobile)\n - Native StoreKit / Play Billing only\n - Stripe (web)\n - None yet\n\n## Re-run behavior\n\n1. Before creating anything:\n - `amba_currencies_list` — match on `code`. Codes are unique per project. Collision → ask to update instead.\n - `amba_catalog_list` — match on `key`. Same.\n - `amba_stores_list` — match on `name`. Same.\n\n2. **Never delete a currency or item without explicit confirmation** — users have balances and inventory tied to them. Suggest renaming + updating instead. If they insist on delete, surface what gets lost (number of users with non-zero balances / inventory).\n\n3. For real-money integrations, treat as orthogonal: `amba_integrations_list` shows what's wired. Don't duplicate. RevenueCat needs webhook URL + secret on the RevenueCat dashboard side; the configure tool prints the URL, but the user has to paste it into RevenueCat themselves — surface that as a \"needs your input\" line.\n";
80
+ readonly body: "# Economy\n\nVirtual currencies, the catalog of things they buy, stores (curated catalog subsets, possibly segment-gated), and the per-user inventory. Currencies come in two flavors: **soft** (earned in-app, e.g. `gold` / `coins` / `gems`) and **premium** (bought with real money via App Store / Play / Stripe / RevenueCat). Both flow through the same APIs; the difference is whether real money or a tracked event is the input.\n\nPattern:\n\n1. Agent (MCP): create a currency, create catalog items, set prices, group items into one or more stores.\n2. Client SDK: read the catalog, read the store, show the offer, call `Amba.stores.purchase(...)` (real money) or `Amba.inventory.purchase(...)` (soft currency).\n\nReal-money purchases need a billing integration configured separately — see `amba://setup/infrastructure` for `amba_integrations_configure` with RevenueCat.\n\n## MCP tools\n\n### Currencies\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_currencies_create` | Define a currency. Soft or premium, with optional auto-recharge (hearts / energy). | `{ project_id, code: \"gems\", name: \"Gems\", is_premium: false, initial_balance: 0, max_balance: null }` |\n| `amba_currencies_list` | List currencies. | `{ project_id }` |\n| `amba_currencies_update` | Edit (rename, change caps, change auto-recharge). | `{ project_id, currency_id, max_balance: 10000 }` |\n| `amba_currencies_delete` | Delete a currency (irreversible — users lose their balance). | `{ project_id, currency_id }` |\n| `amba_currencies_grant` | Grant currency to a specific user. | `{ project_id, app_user_id, currency_code: \"gems\", amount: 100, reason: \"welcome_bonus\" }` |\n| `amba_currencies_spend` | Debit currency from a specific user. Atomic; returns INSUFFICIENT_FUNDS if balance is too low (no partial debit). | `{ project_id, app_user_id, currency_code: \"gems\", amount: 5, reason: \"ai_generation\" }` |\n| `amba_currencies_get_transactions` | Per-user transaction ledger. | `{ project_id, user_id, currency_code: \"gems\", limit: 100 }` |\n\n#### Currency grant rules (auto-grants)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_currency_grant_rules_create` | Auto-grant currency on an event. | `{ project_id, currency_code: \"gems\", event_name: \"workout_completed\", amount: 10, max_per_day: 5 }` |\n| `amba_currency_grant_rules_list` | List grant rules. | `{ project_id, currency_code }` |\n| `amba_currency_grant_rules_delete` | Delete a grant rule. | `{ project_id, rule_id }` |\n\n#### Hearts / energy (auto-recharge)\n\nSet `auto_recharge_amount` + `auto_recharge_interval_hours` when creating the currency:\n\n```jsonc\n{\n \"project_id\": \"...\",\n \"code\": \"hearts\",\n \"name\": \"Hearts\",\n \"is_premium\": false,\n \"initial_balance\": 5,\n \"max_balance\": 5,\n \"auto_recharge_amount\": 1,\n \"auto_recharge_interval_hours\": 4\n}\n```\n\nThis is the Duolingo pattern: spend a heart on failure, regenerate 1 every 4 hours, capped at 5.\n\n### Catalog\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_catalog_items_create` | Create a catalog item. | `{ project_id, key: \"premium_theme\", name: \"Dark Pro Theme\", item_type: \"durable\", description: \"...\", icon_url: \"...\", category: \"themes\" }` |\n| `amba_catalog_list` | List the catalog. | `{ project_id }` |\n| `amba_catalog_items_get` | Read one item. | `{ project_id, item_id }` |\n| `amba_catalog_items_update` | Edit an item. | `{ project_id, item_id, name: \"...\" }` |\n| `amba_catalog_items_delete` | Delete an item. | `{ project_id, item_id }` |\n| `amba_catalog_items_set_price` | Set or update a price. | `{ project_id, item_id, currency_code: \"gems\", amount: 200 }` or `{ project_id, item_id, iap_product_id: \"com.example.premium_theme\" }` |\n| `amba_catalog_items_delete_price` | Delete a price. | `{ project_id, item_id, price_id }` |\n| `amba_catalog_bundles_add_item` | Add an item to a bundle. | `{ project_id, bundle_item_id, child_item_id, quantity: 1 }` |\n| `amba_catalog_bundles_remove_item` | Remove an item from a bundle. | `{ project_id, bundle_item_id, child_item_id }` |\n\nItem types:\n- `durable` — owned forever (themes, character skins, ad removal).\n- `consumable` — used up (extra lives, hint packs, energy refills).\n- `bundle` — contains other items (starter pack with 100 gems + 5 hints + 1 theme).\n\n### Stores\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_stores_create` | Create a store (curated catalog subset). Optionally segment-gated. | `{ project_id, name: \"Main Shop\", description: \"Tap to spend gems\" }` |\n| `amba_stores_list` | List stores. | `{ project_id }` |\n| `amba_stores_patch` | Edit a store. | `{ project_id, store_id, name: \"...\" }` |\n| `amba_stores_delete` | Delete a store. | `{ project_id, store_id }` |\n| `amba_stores_add_listing` | Add an item to a store. | `{ project_id, store_id, item_id, sort_order: 1, featured: true }` |\n| `amba_stores_list_listings` | List items in a store. | `{ project_id, store_id }` |\n| `amba_stores_patch_listing` | Edit a listing (re-order, mark featured). | `{ project_id, store_id, listing_id, featured: true }` |\n| `amba_stores_delete_listing` | Remove an item from a store. | `{ project_id, store_id, listing_id }` |\n\nA segment-gated store: pass `segment_id` to `amba_stores_create` — only users in that segment see it via `Amba.stores.list()`.\n\n### Inventory (admin-side grants)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_inventory_grant_item` | Grant an item to a user without payment. | `{ project_id, app_user_id, item_key: \"premium_theme\", quantity: 1 }` |\n| `amba_users_get_inventory` | Read a user's inventory. | `{ project_id, user_id }` |\n\n## SDK init per stack\n\nThe SDK side is mostly read + purchase. `Amba.configure(...)` runs first.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\nconst balances = await Amba.currencies.getBalance();\nconst items = await Amba.catalog.list();\nconst stores = await Amba.stores.list();\nconst offers = await Amba.stores.getPurchaseOptions(stores[0].key);\n\n// Soft-currency purchase\nawait Amba.inventory.purchase({ item_key: 'premium_theme', currency_code: 'gems' });\n\n// Consume a consumable\nawait Amba.inventory.consume({ item_key: 'hint_pack', quantity: 1 });\n\nconst inv = await Amba.inventory.getItems();\n```\n\nFor real-money IAP on RN, combine Amba with RevenueCat or `react-native-iap`. Capture the receipt then:\n\n```tsx\nawait Amba.stores.purchase('main_shop', product.identifier, {\n receipt: transaction.transactionReceipt,\n platform: 'ios',\n});\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst balances = await Amba.currencies.getBalance();\nconst items = await Amba.catalog.list();\nawait Amba.inventory.purchase({ item_key: 'pro_plan', currency_code: 'credits' });\n```\n\nReal-money web flow — typically Stripe Checkout. Configure a Stripe webhook via `amba_integrations_configure`; Amba fulfils via `amba_inventory_grant_item` automatically. No SDK call required.\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nlet balances = try await Amba.currencies.getBalance()\nlet items = try await Amba.catalog.list()\nlet stores = try await Amba.stores.list()\n\n_ = try await Amba.inventory.purchase(PurchaseRequest(\n itemKey: \"premium_theme\",\n currencyCode: \"gems\"\n))\n\n// Real-money via StoreKit 2\nimport StoreKit\nlet products = try await Product.products(for: [\"com.example.premium_theme\"])\nlet result = try await products[0].purchase()\nif case .success(.verified(let transaction)) = result {\n _ = try await Amba.stores.purchase(\n storeKey: \"main_shop\",\n purchaseOptionId: products[0].id,\n receipt: [\"jws_representation\": transaction.jsonRepresentation]\n )\n await transaction.finish()\n}\n```\n\n### Android (Kotlin)\n\n```kotlin\nval balances = Amba.currencies.getBalance()\nval items = Amba.catalog.list()\nAmba.inventory.purchase(PurchaseRequest(itemKey = \"premium_theme\", currencyCode = \"gems\"))\n```\n\nReal-money via Google Play Billing — capture `purchaseToken` then `Amba.stores.purchase` with `receipt: mapOf(\"purchase_token\" to purchaseToken, \"package_name\" to packageName, \"product_id\" to sku)`.\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal balances = await Amba.currencies.getBalance();\nfinal items = await Amba.catalog.list();\nawait Amba.inventory.purchase(\n PurchaseRequest(itemKey: 'premium_theme', currencyCode: 'gems'),\n);\n```\n\nFor IAP, the `in_app_purchase` plugin gives you the receipt; pass it to `Amba.stores.purchase`.\n\n## Common follow-ups\n\nBatch.\n\n1. **Virtual currency: what's it called?**\n - `gems` (recommended — neutral, premium feel)\n - `coins` / `gold`\n - `credits` (recommended for ai_chatbot)\n - `points`\n - Custom — I'll provide\n - None — no soft currency for now\n\n2. **Add a \"hearts\" / energy mechanic?** (only ask for fitness / game / education)\n - Yes — 5 hearts max, +1 every 4 hours (Duolingo style)\n - Yes but custom (ask for cap and recharge rate)\n - No\n\n3. **Premium currency too?** (real-money purchases of a tradeable virtual currency)\n - Yes — `gems` (premium) — pairs with App Store / Play / Stripe billing\n - No — only soft currency\n\n4. **Seed a starter catalog?**\n - Yes — 3 cosmetics + 1 consumable + 1 starter bundle (uses the chosen currency)\n - Yes but seed it empty — I'll add items myself\n - No\n\n5. **Stores: one store or segmented stores?**\n - One \"Main Shop\" — recommended for v1\n - Multiple — main + a \"Trial Users\" segment-gated store\n - I'll wire stores myself\n\n6. **Auto-grant rules:** earn currency on the gamification event?\n - Yes — grant 10 of `<currency>` per `<event>` (same event as XP rule), cap at 5/day\n - No — currency is granted only through achievement rewards / IAP\n\n7. **Real-money integration:** which provider?\n - RevenueCat (recommended for mobile)\n - Native StoreKit / Play Billing only\n - Stripe (web)\n - None yet\n\n## Re-run behavior\n\n1. Before creating anything:\n - `amba_currencies_list` — match on `code`. Codes are unique per project. Collision → ask to update instead.\n - `amba_catalog_list` — match on `key`. Same.\n - `amba_stores_list` — match on `name`. Same.\n\n2. **Never delete a currency or item without explicit confirmation** — users have balances and inventory tied to them. Suggest renaming + updating instead. If they insist on delete, surface what gets lost (number of users with non-zero balances / inventory).\n\n3. For real-money integrations, treat as orthogonal: `amba_integrations_list` shows what's wired. Don't duplicate. RevenueCat needs webhook URL + secret on the RevenueCat dashboard side; the configure tool prints the URL, but the user has to paste it into RevenueCat themselves — surface that as a \"needs your input\" line.\n";
81
81
  readonly surface: "economy";
82
82
  readonly title: "Amba setup — economy";
83
83
  readonly description: string;
@@ -85,7 +85,7 @@ export declare const AMBA_SETUP_SUB_RESOURCES: readonly [{
85
85
  readonly name: "amba-setup-social";
86
86
  readonly uri: "amba://setup/social";
87
87
  readonly mime: "text/markdown";
88
- readonly body: "# Social\n\nThe social graph and everything that runs on top of it: friendships (with block lists), groups (guilds / clubs), activity feeds with rule-driven filters, 1:1 + group messaging, user-generated reviews, and the moderation queue + trust system that keeps it all from rotting.\n\nMost of this is \"create-the-rule, let-the-SDK-call-it\" — feeds are rule-driven, moderation has its own queue, friend graph is bidirectional. Only the structural pieces (feed rules, moderation rules, group capacity) are MCP-provisioned; the runtime calls (`Amba.friends.sendRequest`, `Amba.messaging.sendMessage`, `Amba.feeds.getActivity`) are SDK-side.\n\n## MCP tools\n\n### Friendships\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_friendships_list` | List friendships in a project (admin / moderation view). | `{ project_id, status: \"accepted\", limit: 100 }` |\n| `amba_friendships_get_stats` | Aggregate metrics — accepted, pending, blocked. | `{ project_id }` |\n| `amba_friendships_delete` | Admin-delete a friendship row. | `{ project_id, friendship_id }` |\n\nFriend requests + accept/decline + block are SDK-side. There's no MCP tool to create a friendship — by design, only end-users can.\n\n### Groups\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_groups_create` | Create a group (guild / club / squad). | `{ project_id, name: \"Morning Runners\", owner_id: \"u_…\", description: \"5am crew\", is_public: true, max_members: 100 }` |\n| `amba_groups_list` | List groups. | `{ project_id, limit: 50 }` |\n| `amba_groups_update` | Edit a group (rename, change visibility, change cap). | `{ project_id, group_id, max_members: 500 }` |\n| `amba_groups_delete` | Delete a group. | `{ project_id, group_id }` |\n| `amba_groups_list_members` | List members. | `{ project_id, group_id }` |\n| `amba_groups_update_member` | Change a member's role (promote to admin / mute). | `{ project_id, group_id, member_id, role: \"admin\" }` |\n| `amba_groups_remove_member` | Kick a member. | `{ project_id, group_id, member_id }` |\n\n### Feeds\n\nActivity feeds are rule-driven: each rule says \"events of type X published by users matching Y should appear in feed Z\". Items land in feeds automatically when matching events fire.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_feeds_rules_create` | Define a feed rule. | `{ project_id, feed: \"global\", event: \"post_published\", filter: { all: [{ field: \"user.is_creator\", op: \"==\", value: true }] } }` |\n| `amba_feeds_list_rules` | List rules. | `{ project_id, feed }` |\n| `amba_feeds_patch_rule` | Edit a rule. | `{ project_id, rule_id, filter: {...} }` |\n| `amba_feeds_delete_rule` | Delete a rule. | `{ project_id, rule_id }` |\n| `amba_feeds_list_items` | List items in a feed (admin / debugging). | `{ project_id, feed: \"global\", limit: 50 }` |\n| `amba_feeds_delete_item` | Remove a single feed item (moderation). | `{ project_id, feed, item_id }` |\n\nConventional feed names: `global` (everyone), `following` (just users you follow), `group:<group_id>` (a single group's feed).\n\n### Messaging\n\nMostly SDK-side — admin tools are for moderation + stats.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_messaging_list_conversations` | List conversations (admin / moderation view). | `{ project_id, limit: 50 }` |\n| `amba_messaging_list_messages` | List messages in a conversation. | `{ project_id, conversation_id, limit: 100 }` |\n| `amba_messaging_delete_message` | Hard-delete a message (moderation). | `{ project_id, conversation_id, message_id }` |\n| `amba_messaging_get_stats` | Aggregate messaging metrics. | `{ project_id }` |\n\n### Moderation\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_moderation_configure` | Set the project's default moderation policy. | `{ project_id, auto_review_threshold: 0.8, default_action: \"queue\", auto_block_threshold: 0.95 }` |\n| `amba_moderation_list_rules` | List rules. | `{ project_id }` |\n| `amba_moderation_update_rule` | Edit a rule. | `{ project_id, rule_id, action: \"block\" }` |\n| `amba_moderation_delete_rule` | Delete a rule. | `{ project_id, rule_id }` |\n| `amba_moderation_queue_list` | List pending reports. | `{ project_id, status: \"pending\", limit: 50 }` |\n| `amba_moderation_queue_get` | Fetch one report. | `{ project_id, report_id }` |\n| `amba_moderation_queue_approve` | Approve (resolve with no action). | `{ project_id, report_id, reason: \"false positive\" }` |\n| `amba_moderation_queue_reject` | Reject (take action — hide/delete content, ban user). | `{ project_id, report_id, action: \"delete_message\", reason: \"spam\" }` |\n| `amba_moderation_queue_escalate` | Escalate to a senior moderator. | `{ project_id, report_id }` |\n| `amba_moderation_list_trust` | List per-user trust scores. | `{ project_id, limit: 100 }` |\n| `amba_moderation_set_trust` | Manually bump a user's trust score. | `{ project_id, user_id, trust_score: 0.9, reason: \"verified power user\" }` |\n\n### Reviews\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_reviews_list` | List reviews. | `{ project_id, target_type: \"catalog_item\", target_id: \"...\", limit: 50 }` |\n| `amba_reviews_list_items` | List the things that have reviews on them. | `{ project_id, target_type: \"catalog_item\" }` |\n| `amba_reviews_patch` | Edit / hide a review (moderation). | `{ project_id, review_id, hidden: true }` |\n| `amba_reviews_delete` | Delete a review. | `{ project_id, review_id }` |\n| `amba_reviews_get_stats` | Aggregate review stats. | `{ project_id, target_type, target_id }` |\n| `amba_reviews_export` | Export to CSV / JSON. | `{ project_id, format: \"csv\" }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first. Snippets below are the social-only calls.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\n// Friend graph\nconst friendship = await Amba.friends.sendRequest(otherUserId);\nconst friends = await Amba.friends.getFriends();\nawait Amba.friends.acceptRequest(friendship.id);\nawait Amba.friends.removeFriend(otherUserId);\nawait Amba.friends.blockUser(otherUserId);\n\n// Groups\nconst group = await Amba.groups.create({ name: 'Morning Runners' });\n\n// Messaging\nconst conv = await Amba.messaging.createConversation({\n participant_ids: [otherUserId],\n type: 'direct',\n});\nconst msg = await Amba.messaging.sendMessage(conv.id, { body: 'hi' });\nconst inbox = await Amba.messaging.conversations();\nawait Amba.messaging.markRead(conv.id);\n\n// Feeds\nconst { items, next_cursor } = await Amba.feeds.getActivity('global');\n\n// Reviews\nconst reviews = await Amba.reviews.list('catalog_item', 'premium_theme');\nawait Amba.reviews.create({\n target_type: 'catalog_item',\n target_id: 'premium_theme',\n rating: 5,\n body: 'Beautiful theme!',\n});\n\n// Moderation — user-side\nawait Amba.moderation.reportUser({ reported_user_id: otherUserId, reason: 'harassment' });\nawait Amba.moderation.reportContent({ target_type: 'message', target_id: msg.id, reason: 'spam' });\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nawait Amba.friends.sendRequest(otherUserId);\nconst conv = await Amba.messaging.createConversation({\n participant_ids: [otherUserId],\n type: 'direct',\n});\nawait Amba.messaging.sendMessage(conv.id, { body: 'hello' });\nconst activity = await Amba.feeds.getActivity('global');\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nlet friendship = try await Amba.friends.sendRequest(userId: otherUserId)\nlet conv = try await Amba.messaging.createConversation(CreateConversationRequest(\n participantIds: [otherUserId], type: .direct\n))\n_ = try await Amba.messaging.sendMessage(\n conversationId: conv.id,\n request: SendMessageRequest(body: \"hi\")\n)\nlet feed = try await Amba.feeds.getActivity(feed: \"global\")\n```\n\n### Android (Kotlin)\n\n```kotlin\nval friendship = Amba.friends.sendRequest(otherUserId)\nval conv = Amba.messaging.createConversation(\n CreateConversationRequest(participantIds = listOf(otherUserId), type = \"direct\")\n)\nval msg = Amba.messaging.sendMessage(\n conversationId = conv.id,\n request = SendMessageRequest(body = \"hi\")\n)\nval feed = Amba.feeds.getActivity(\"global\")\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal friendship = await Amba.friends.sendRequest(otherUserId);\nfinal conv = await Amba.messaging.createConversation(\n CreateConversationRequest(\n participantIds: [otherUserId],\n type: ConversationType.direct,\n ),\n);\nfinal msg = await Amba.messaging.sendMessage(conv.id, SendMessageRequest(body: 'hi'));\nfinal feed = await Amba.feeds.getActivity('global');\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Which social features?** (multi-select)\n - [x] Friend graph (friend requests, accept/decline, block, unfriend)\n - [ ] Groups / guilds (multi-user)\n - [x] 1:1 messaging\n - [ ] Group messaging\n - [x] Activity feed\n - [ ] User reviews\n - [x] Moderation queue + user-report flow (recommended whenever messaging or feed is on)\n\n2. **Default feed:**\n - Global (everyone) (recommended for content_creator, social)\n - Following (only people you friend) (recommended for dating, fitness — privacy-leaning)\n - Both — wire two feeds, let users switch\n\n3. **Feed rule: what event lands in the feed?**\n - For fitness: `workout_completed` (with user details)\n - For social: `post_published`\n - For game: `level_completed`\n - For education: `lesson_completed`\n - Custom — I'll provide\n\n4. **Moderation policy:** (only ask if any social feature was chosen)\n - Auto-block obvious abuse (>= 0.95 confidence), queue the rest (>= 0.8 confidence), allow below (recommended)\n - Queue everything — manual review of all flagged content\n - Allow everything — only act on user reports (closed communities only)\n\n5. **Dating-specific:**\n - Match-only messaging (recommended for dating apps)\n - Open messaging\n - Group chats enabled?\n\n6. **Reviews (only if economy surface is wired):**\n - Catalog item reviews\n - User-on-user reviews\n - Both\n\n## Re-run behavior\n\n1. Before creating:\n - `amba_feeds_list_rules` — match on `feed + event + filter`. Collision → ask to update or skip.\n - `amba_groups_list` — group names aren't unique; only skip if `name + owner_id` collides.\n - `amba_moderation_list_rules` — match on rule key.\n\n2. **Never delete a group, friendship, or message without explicit confirmation** — these are user-created. If the user asks to \"wipe friendships\", give them `amba_moderation_queue_list` to find the actually-problematic rows first.\n\n3. If the user enables messaging on re-run, double-check whether moderation is already wired. If not, **strongly recommend** turning it on before opening the messaging surface to all users — wire it in the same pass (additive — `amba_moderation_configure`).\n\n4. For abusive-user enforcement, prefer trust-score updates (`amba_moderation_set_trust({ trust_score: 0.0 })`) and report rejection over user deletion. Deletion is `amba_users_delete` and is destructive — only on a user-initiated GDPR-style request.\n";
88
+ readonly body: "# Social\n\nThe social graph and everything that runs on top of it: friendships (with block lists), groups (guilds / clubs), activity feeds with rule-driven filters, 1:1 + group messaging, user-generated reviews, and the moderation queue + trust system that keeps it all from rotting.\n\nMost of this is \"create-the-rule, let-the-SDK-call-it\" — feeds are rule-driven, moderation has its own queue, friend graph is bidirectional. Only the structural pieces (feed rules, moderation rules, group capacity) are MCP-provisioned; the runtime calls (`Amba.friends.sendRequest`, `Amba.messaging.sendMessage`, `Amba.feeds.getActivity`) are SDK-side.\n\n## MCP tools\n\n### Friendships\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_friendships_list` | List friendships in a project (admin / moderation view). | `{ project_id, status: \"accepted\", limit: 100 }` |\n| `amba_friendships_get_stats` | Aggregate metrics — accepted, pending, blocked. | `{ project_id }` |\n| `amba_friendships_delete` | Admin-delete a friendship row. | `{ project_id, friendship_id }` |\n\nFriend requests + accept/decline + block are SDK-side. There's no MCP tool to create a friendship — by design, only end-users can.\n\n### Groups\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_groups_create` | Create a group (guild / club / squad). | `{ project_id, name: \"Morning Runners\", owner_id: \"u_…\", description: \"5am crew\", is_public: true, max_members: 100 }` |\n| `amba_groups_list` | List groups. | `{ project_id, limit: 50 }` |\n| `amba_groups_update` | Edit a group (rename, change visibility, change cap). | `{ project_id, group_id, max_members: 500 }` |\n| `amba_groups_delete` | Delete a group. | `{ project_id, group_id }` |\n| `amba_groups_list_members` | List members. | `{ project_id, group_id }` |\n| `amba_groups_update_member` | Change a member's role (promote to admin / mute). | `{ project_id, group_id, member_id, role: \"admin\" }` |\n| `amba_groups_remove_member` | Kick a member. | `{ project_id, group_id, member_id }` |\n\n### Feeds\n\nActivity feeds are rule-driven: each rule says \"events of type X published by users matching Y should appear in feed Z\". Items land in feeds automatically when matching events fire.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_feeds_rules_create` | Define a feed rule. | `{ project_id, feed: \"global\", event: \"post_published\", filter: { all: [{ field: \"user.is_creator\", op: \"==\", value: true }] } }` |\n| `amba_feeds_list_rules` | List rules. | `{ project_id, feed }` |\n| `amba_feeds_patch_rule` | Edit a rule. | `{ project_id, rule_id, filter: {...} }` |\n| `amba_feeds_delete_rule` | Delete a rule. | `{ project_id, rule_id }` |\n| `amba_feeds_list_items` | List items in a feed (admin / debugging). | `{ project_id, feed: \"global\", limit: 50 }` |\n| `amba_feeds_delete_item` | Remove a single feed item (moderation). | `{ project_id, feed, item_id }` |\n\nConventional feed names: `global` (everyone), `following` (just users you follow), `group:<group_id>` (a single group's feed).\n\n### Messaging\n\nMostly SDK-side — admin tools are for moderation + stats.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_messaging_create_conversation` | Create a conversation server-side (zero or more initial participants — for matchmaker-built circles). | `{ project_id, type: \"group\", name: \"Circle\" }` |\n| `amba_messaging_add_participant` | Add a user to a conversation (idempotent). The matchmaker primitive for growing membership over time. | `{ project_id, conversation_id, user_id }` |\n| `amba_messaging_remove_participant` | Remove a user from a conversation (idempotent). | `{ project_id, conversation_id, user_id }` |\n| `amba_messaging_list_conversations` | List conversations (admin / moderation view). | `{ project_id, limit: 50 }` |\n| `amba_messaging_list_messages` | List messages in a conversation. | `{ project_id, conversation_id, limit: 100 }` |\n| `amba_messaging_delete_message` | Hard-delete a message (moderation). | `{ project_id, conversation_id, message_id }` |\n| `amba_messaging_get_stats` | Aggregate messaging metrics. | `{ project_id }` |\n\n### Moderation\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_moderation_configure` | Set the project's default moderation policy. | `{ project_id, auto_review_threshold: 0.8, default_action: \"queue\", auto_block_threshold: 0.95 }` |\n| `amba_moderation_list_rules` | List rules. | `{ project_id }` |\n| `amba_moderation_update_rule` | Edit a rule. | `{ project_id, rule_id, action: \"block\" }` |\n| `amba_moderation_delete_rule` | Delete a rule. | `{ project_id, rule_id }` |\n| `amba_moderation_queue_list` | List pending reports. | `{ project_id, status: \"pending\", limit: 50 }` |\n| `amba_moderation_queue_get` | Fetch one report. | `{ project_id, report_id }` |\n| `amba_moderation_queue_approve` | Approve (resolve with no action). | `{ project_id, report_id, reason: \"false positive\" }` |\n| `amba_moderation_queue_reject` | Reject (take action — hide/delete content, ban user). | `{ project_id, report_id, action: \"delete_message\", reason: \"spam\" }` |\n| `amba_moderation_queue_escalate` | Escalate to a senior moderator. | `{ project_id, report_id }` |\n| `amba_moderation_list_trust` | List per-user trust scores. | `{ project_id, limit: 100 }` |\n| `amba_moderation_set_trust` | Manually bump a user's trust score. | `{ project_id, user_id, trust_score: 0.9, reason: \"verified power user\" }` |\n\n### Reviews\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_reviews_list` | List reviews. | `{ project_id, target_type: \"catalog_item\", target_id: \"...\", limit: 50 }` |\n| `amba_reviews_list_items` | List the things that have reviews on them. | `{ project_id, target_type: \"catalog_item\" }` |\n| `amba_reviews_patch` | Edit / hide a review (moderation). | `{ project_id, review_id, hidden: true }` |\n| `amba_reviews_delete` | Delete a review. | `{ project_id, review_id }` |\n| `amba_reviews_get_stats` | Aggregate review stats. | `{ project_id, target_type, target_id }` |\n| `amba_reviews_export` | Export to CSV / JSON. | `{ project_id, format: \"csv\" }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first. Snippets below are the social-only calls.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\n// Friend graph\nconst friendship = await Amba.friends.sendRequest(otherUserId);\nconst friends = await Amba.friends.getFriends();\nawait Amba.friends.acceptRequest(friendship.id);\nawait Amba.friends.removeFriend(otherUserId);\nawait Amba.friends.blockUser(otherUserId);\n\n// Groups\nconst group = await Amba.groups.create({ name: 'Morning Runners' });\n\n// Messaging\nconst conv = await Amba.messaging.createConversation({\n participant_ids: [otherUserId],\n type: 'direct',\n});\nconst msg = await Amba.messaging.sendMessage(conv.id, { body: 'hi' });\nconst inbox = await Amba.messaging.conversations();\nawait Amba.messaging.markRead(conv.id);\n\n// Feeds\nconst { items, next_cursor } = await Amba.feeds.getActivity('global');\n\n// Reviews\nconst reviews = await Amba.reviews.list('catalog_item', 'premium_theme');\nawait Amba.reviews.create({\n target_type: 'catalog_item',\n target_id: 'premium_theme',\n rating: 5,\n body: 'Beautiful theme!',\n});\n\n// Moderation — user-side\nawait Amba.moderation.reportUser({ reported_user_id: otherUserId, reason: 'harassment' });\nawait Amba.moderation.reportContent({ target_type: 'message', target_id: msg.id, reason: 'spam' });\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nawait Amba.friends.sendRequest(otherUserId);\nconst conv = await Amba.messaging.createConversation({\n participant_ids: [otherUserId],\n type: 'direct',\n});\nawait Amba.messaging.sendMessage(conv.id, { body: 'hello' });\nconst activity = await Amba.feeds.getActivity('global');\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nlet friendship = try await Amba.friends.sendRequest(userId: otherUserId)\nlet conv = try await Amba.messaging.createConversation(CreateConversationRequest(\n participantIds: [otherUserId], type: .direct\n))\n_ = try await Amba.messaging.sendMessage(\n conversationId: conv.id,\n request: SendMessageRequest(body: \"hi\")\n)\nlet feed = try await Amba.feeds.getActivity(feed: \"global\")\n```\n\n### Android (Kotlin)\n\n```kotlin\nval friendship = Amba.friends.sendRequest(otherUserId)\nval conv = Amba.messaging.createConversation(\n CreateConversationRequest(participantIds = listOf(otherUserId), type = \"direct\")\n)\nval msg = Amba.messaging.sendMessage(\n conversationId = conv.id,\n request = SendMessageRequest(body = \"hi\")\n)\nval feed = Amba.feeds.getActivity(\"global\")\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal friendship = await Amba.friends.sendRequest(otherUserId);\nfinal conv = await Amba.messaging.createConversation(\n CreateConversationRequest(\n participantIds: [otherUserId],\n type: ConversationType.direct,\n ),\n);\nfinal msg = await Amba.messaging.sendMessage(conv.id, SendMessageRequest(body: 'hi'));\nfinal feed = await Amba.feeds.getActivity('global');\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Which social features?** (multi-select)\n - [x] Friend graph (friend requests, accept/decline, block, unfriend)\n - [ ] Groups / guilds (multi-user)\n - [x] 1:1 messaging\n - [ ] Group messaging\n - [x] Activity feed\n - [ ] User reviews\n - [x] Moderation queue + user-report flow (recommended whenever messaging or feed is on)\n\n2. **Default feed:**\n - Global (everyone) (recommended for content_creator, social)\n - Following (only people you friend) (recommended for dating, fitness — privacy-leaning)\n - Both — wire two feeds, let users switch\n\n3. **Feed rule: what event lands in the feed?**\n - For fitness: `workout_completed` (with user details)\n - For social: `post_published`\n - For game: `level_completed`\n - For education: `lesson_completed`\n - Custom — I'll provide\n\n4. **Moderation policy:** (only ask if any social feature was chosen)\n - Auto-block obvious abuse (>= 0.95 confidence), queue the rest (>= 0.8 confidence), allow below (recommended)\n - Queue everything — manual review of all flagged content\n - Allow everything — only act on user reports (closed communities only)\n\n5. **Dating-specific:**\n - Match-only messaging (recommended for dating apps)\n - Open messaging\n - Group chats enabled?\n\n6. **Reviews (only if economy surface is wired):**\n - Catalog item reviews\n - User-on-user reviews\n - Both\n\n## Re-run behavior\n\n1. Before creating:\n - `amba_feeds_list_rules` — match on `feed + event + filter`. Collision → ask to update or skip.\n - `amba_groups_list` — group names aren't unique; only skip if `name + owner_id` collides.\n - `amba_moderation_list_rules` — match on rule key.\n\n2. **Never delete a group, friendship, or message without explicit confirmation** — these are user-created. If the user asks to \"wipe friendships\", give them `amba_moderation_queue_list` to find the actually-problematic rows first.\n\n3. If the user enables messaging on re-run, double-check whether moderation is already wired. If not, **strongly recommend** turning it on before opening the messaging surface to all users — wire it in the same pass (additive — `amba_moderation_configure`).\n\n4. For abusive-user enforcement, prefer trust-score updates (`amba_moderation_set_trust({ trust_score: 0.0 })`) and report rejection over user deletion. Deletion is `amba_users_delete` and is destructive — only on a user-initiated GDPR-style request.\n";
89
89
  readonly surface: "social";
90
90
  readonly title: "Amba setup — social";
91
91
  readonly description: string;
@@ -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\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_collections_create` | Create a typed collection. | `{ 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 }] }` |\n| `amba_collections_list` | List collections in this project. | `{ project_id }` |\n| `amba_collections_get` | Read one collection's schema. | `{ project_id, collection_name: \"todos\" }` |\n| `amba_collections_alter` | Add / drop columns, add / drop indexes. | `{ project_id, collection_name: \"todos\", add_columns: [{ name: \"priority\", type: \"int\", nullable: true }] }` |\n| `amba_collections_delete` | Drop the table (destructive). | `{ project_id, collection_name }` |\n| `amba_admin_insert_row` | Insert a row as the developer (bypasses user-scope). | `{ project_id, collection: \"todos\", row: { title: \"Sample\", done: false } }` |\n| `amba_admin_list_rows` | Read rows as the developer. | `{ project_id, collection: \"todos\", limit: 100 }` |\n| `amba_client_insert_row` | Insert as an end-user. Requires `api_key` + `session_token`. | `{ project_id, api_key, session_token, collection: \"todos\", row: {...} }` |\n| `amba_client_list_rows` | Read as an end-user (auto user-scoped). | `{ project_id, api_key, session_token, collection: \"todos\" }` |\n| `amba_client_get_row` | Get one row by id (end-user). | `{ project_id, api_key, session_token, collection, row_id }` |\n| `amba_client_update_row` | Update one row (end-user). | `{ project_id, api_key, session_token, collection, row_id, patch: {...} }` |\n| `amba_client_delete_row` | Delete one row (end-user). | `{ project_id, api_key, session_token, collection, row_id }` |\n| `amba_client_count_rows` | Count rows matching a filter. | `{ project_id, api_key, session_token, collection, filter: {...} }` |\n| `amba_client_find_rows` | Filter / sort / paginate rows. | `{ project_id, api_key, session_token, collection, filter: {...}, order_by: [...], limit: 50 }` |\n| `amba_client_find_nearest_rows` | Vector-similarity search (rows with a `vector(<dim>)` column). | `{ project_id, api_key, session_token, collection, vector_column: \"embedding\", query_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: \"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";
97
97
  readonly surface: "infrastructure";
98
98
  readonly title: "Amba setup — infrastructure";
99
99
  readonly description: string;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Domain purchase MCP tools (T-Registrar / task #25).
3
+ *
4
+ * Lets an agent search for an available domain, see its price, and buy it
5
+ * through Amba — Amba registers the domain and connects it to one of the
6
+ * project's sites as a live custom domain with no DNS setup by the customer.
7
+ *
8
+ * Tools:
9
+ * - `amba_domains_search` — find available domains for a query (free).
10
+ * - `amba_domains_check` — authoritative availability + price for
11
+ * specific domains (free).
12
+ * - `amba_domains_purchase` — buy + connect a domain to a site. Returns a
13
+ * QUOTE you must confirm before money moves;
14
+ * re-call with `confirm: true` + the quoted
15
+ * `accept_price_usd` to execute.
16
+ * - `amba_domains_list` — list domains this project has purchased.
17
+ *
18
+ * Money safety: `amba_domains_purchase` never charges on the first call. It
19
+ * returns the price and `confirmation_required: true`; the agent must
20
+ * surface the cost to the user and re-call with `confirm: true` and
21
+ * `accept_price_usd` matching the quote. Even then, the platform only
22
+ * executes a real registration when purchasing is enabled — otherwise it
23
+ * returns the quote with `gated: true`.
24
+ *
25
+ * Provider neutrality: this is "buy a domain through Amba" — the upstream
26
+ * registrar is never named in tool descriptions or output.
27
+ *
28
+ * Authentication: every tool accepts an optional inline `pat` via the
29
+ * `registerTool` helper (see `../lib/with-pat.ts`).
30
+ */
31
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
32
+ import type { ApiClient } from '../api-client.js';
33
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Transactional email tools — agentic configuration of templates,
3
+ * suppressions, sending, and delivery inspection.
4
+ *
5
+ * Mirrors the REST surface in `apps/api/src/routes/admin/email.ts` 1:1 so
6
+ * an agent setting up a project can wire transactional email end-to-end
7
+ * without leaving the agentic context:
8
+ *
9
+ * amba_email_templates_create POST /email/templates (upsert by name)
10
+ * amba_email_templates_list GET /email/templates
11
+ * amba_email_templates_get GET /email/templates/:name
12
+ * amba_email_templates_update PATCH /email/templates/:name
13
+ * amba_email_templates_delete DELETE /email/templates/:name
14
+ * amba_email_suppressions_create POST /email/suppressions
15
+ * amba_email_suppressions_list GET /email/suppressions
16
+ * amba_email_suppressions_delete DELETE /email/suppressions/:email
17
+ * amba_email_send POST /email/send
18
+ * amba_email_deliveries_list GET /email/deliveries
19
+ * amba_email_deliveries_get GET /email/deliveries/:id
20
+ *
21
+ * Descriptions stay provider-neutral — the underlying email delivery
22
+ * provider is never named.
23
+ */
24
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
25
+ import type { ApiClient } from '../api-client.js';
26
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ApiClient } from '../api-client.js';
3
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
@@ -9,12 +9,13 @@
9
9
  * - member: write data
10
10
  * - viewer: read-only
11
11
  *
12
- * Owners cannot be removed; transfer ownership first (transfer-owner tool
13
- * not yet implemented). Members can remove themselves.
12
+ * Owners cannot be removed; transfer ownership first via
13
+ * `amba_projects_transfer_owner`. Members can remove themselves.
14
14
  *
15
15
  * The REST endpoints these tools call are shipped by W2-C in parallel
16
- * (`/v1/admin/projects/:id/invites`, `.../members`, `.../members/:dev_id`).
17
- * If those aren't live yet, calls surface as 404 via `AmbaApiError`.
16
+ * (`/v1/admin/projects/:id/invites`, `.../members`, `.../members/:dev_id`,
17
+ * `.../transfer-ownership`). If those aren't live yet, calls surface as
18
+ * 404 via `AmbaApiError`.
18
19
  */
19
20
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
20
21
  import type { ApiClient } from '../api-client.js';
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Function-secret MCP tools (#38).
2
+ * Secret MCP tools (#38).
3
3
  *
4
- * Wraps the customer-function secrets proxy (UNBURY-10 / S-401). Every
5
- * secret is scoped to a single function — the API enforces this with
6
- * the `function` field on POST and the `?function=` query param on
7
- * DELETE. The previous customer-shoes dogfood hit
8
- * `INVALID_FUNCTION: function must match …` because the CLI initially
9
- * didn't pass `function`; this tool surfaces the constraint clearly in
10
- * the description + makes the `function` arg required.
4
+ * Wraps the customer secrets proxy (UNBURY-10 / S-401). A secret is either
5
+ * FUNCTION-SCOPED (`function` set) or PROJECT-WIDE (`function` omitted —
6
+ * one value visible to every function in the project, including ones
7
+ * deployed later). The API carries this via the optional `function` field
8
+ * on POST and the optional `?function=` query param on DELETE. Secrets may
9
+ * be set BEFORE the function is deployed — the sync drains once a
10
+ * deployment lands.
11
11
  *
12
12
  * Naming:
13
13
  * - Secret name: /^[A-Z][A-Z0-9_]{0,62}$/ (uppercase env-var shape).
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Outbound webhook subscription tools — agentic configuration of
3
+ * server-to-server event delivery.
4
+ *
5
+ * A webhook subscription registers an HTTPS endpoint that Amba POSTs to
6
+ * (HMAC-signed) whenever a matching engagement event fires. These tools
7
+ * mirror the REST surface 1:1 so an agent can stand up, inspect, test, and
8
+ * troubleshoot subscriptions without leaving the agentic context:
9
+ *
10
+ * amba_webhooks_create POST /webhooks/subscriptions
11
+ * amba_webhooks_list GET /webhooks/subscriptions
12
+ * amba_webhooks_get GET /webhooks/subscriptions/:id
13
+ * amba_webhooks_update PATCH /webhooks/subscriptions/:id
14
+ * amba_webhooks_delete DELETE /webhooks/subscriptions/:id
15
+ * amba_webhooks_rotate_secret POST /webhooks/subscriptions/:id/rotate-secret
16
+ * amba_webhooks_test POST /webhooks/subscriptions/:id/test
17
+ * amba_webhooks_deliveries_list GET /webhooks/deliveries
18
+ * amba_webhooks_deliveries_get GET /webhooks/deliveries/:id
19
+ * amba_webhooks_deliveries_replay POST /webhooks/deliveries/:id/replay
20
+ *
21
+ * Param schemas are derived from the REST request shapes in
22
+ * `apps/api/src/routes/admin/webhooks.ts`. Descriptions stay
23
+ * provider-neutral — the underlying delivery infrastructure is never named.
24
+ */
25
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
26
+ import type { ApiClient } from '../api-client.js';
27
+ 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.2",
3
+ "version": "4.0.3",
4
4
  "license": "Apache-2.0",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.12.1",
28
28
  "zod": "^3.25.0",
29
- "@layers/amba-shared": "4.0.2"
29
+ "@layers/amba-shared": "4.0.3"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.10.2",