@layers/amba-mcp 1.0.1 → 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.
- package/README.md +17 -19
- package/dist/api-client.d.ts +42 -4
- package/dist/categories.d.ts +48 -0
- package/dist/expo-build-prompt.js +641 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5388 -715
- package/dist/lib/aliases.d.ts +36 -0
- package/dist/lib/annotations.d.ts +113 -0
- package/dist/lib/tool-result.d.ts +51 -0
- package/dist/lib/with-pat.d.ts +125 -0
- package/dist/resources/amba-setup-economy.d.ts +14 -0
- package/dist/resources/amba-setup-engagement.d.ts +16 -0
- package/dist/resources/amba-setup-gamification.d.ts +15 -0
- package/dist/resources/amba-setup-identity.d.ts +20 -0
- package/dist/resources/amba-setup-infrastructure.d.ts +18 -0
- package/dist/resources/amba-setup-social.d.ts +15 -0
- package/dist/resources/amba-setup.d.ts +56 -0
- package/dist/resources/expo-build-prompt.d.ts +35 -0
- package/dist/resources/index.d.ts +108 -0
- package/dist/resources/prompts.d.ts +13 -0
- package/dist/resources/prompts.js +2 -0
- package/dist/tools/__test-fixtures__/rename-map.d.ts +19 -0
- package/dist/tools/_pat.d.ts +64 -0
- package/dist/tools/ai-prompts-admin.d.ts +35 -0
- package/dist/tools/auth.d.ts +32 -9
- package/dist/tools/billing.d.ts +29 -0
- package/dist/tools/collections.d.ts +37 -0
- package/dist/tools/domains.d.ts +33 -0
- package/dist/tools/email.d.ts +26 -0
- package/dist/tools/functions.d.ts +34 -0
- package/dist/tools/funnels.d.ts +3 -0
- package/dist/tools/invites.d.ts +22 -0
- package/dist/tools/secrets.d.ts +31 -0
- package/dist/tools/setup.d.ts +1 -1
- package/dist/tools/sites.d.ts +37 -0
- package/dist/tools/webhooks.d.ts +27 -0
- package/package.json +10 -4
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical long-form Amba setup guide — markdown body.
|
|
3
|
+
*
|
|
4
|
+
* Companion to the short-form `instructions` field served by the MCP
|
|
5
|
+
* server's initialize response. The pointer "Full guide: amba://setup"
|
|
6
|
+
* in those instructions tells the agent to fetch this resource when it
|
|
7
|
+
* needs more detail than the ~1 KB summary provides.
|
|
8
|
+
*
|
|
9
|
+
* Consumed by:
|
|
10
|
+
*
|
|
11
|
+
* - The MCP resource at `amba://setup`, registered by
|
|
12
|
+
* `registerAllResources()` in `./index.ts` and exposed by the
|
|
13
|
+
* hosted MCP server at `mcp.amba.dev`. Any client (Claude Code,
|
|
14
|
+
* Cursor, Codex, Cowork, etc.) can fetch it via `resources/read`.
|
|
15
|
+
*
|
|
16
|
+
* Twin: this body is the server-side mirror of
|
|
17
|
+
* `packages/cli/skill-bundle/SKILL.md`, which the CLI installs locally
|
|
18
|
+
* during `npx @layers/amba init`. The two surfaces target two
|
|
19
|
+
* different audiences:
|
|
20
|
+
*
|
|
21
|
+
* - `SKILL.md` ships to a local `.claude/skills/amba/` and assumes
|
|
22
|
+
* the agent CAN shell out (the bootstrap path can `npx @layers/amba
|
|
23
|
+
* signup`). It also writes credentials into `.env.local` + `~/.amba/`.
|
|
24
|
+
* - `AMBA_SETUP_GUIDE_MD` (this constant) is served by the hosted MCP
|
|
25
|
+
* and assumes the agent CANNOT shell out (e.g. Claude.ai web).
|
|
26
|
+
* The bootstrap path must therefore use the `amba_developer_signup`
|
|
27
|
+
* MCP tool (the only pre-auth tool the server registers).
|
|
28
|
+
*
|
|
29
|
+
* The playbook shape (Step 0 → Step 1 classify → Step 2 confirm →
|
|
30
|
+
* Step 3 wire → Step 4 report) is identical between the two, so an
|
|
31
|
+
* agent reading either ends up at the same outcome. A drift gate test
|
|
32
|
+
* in `amba-setup.test.ts` asserts the structural anchors match.
|
|
33
|
+
*
|
|
34
|
+
* Taxonomy: the SDK capability map below is grouped under the canonical
|
|
35
|
+
* 7-category taxonomy (Identity / Engagement / Gamification / Economy /
|
|
36
|
+
* Social / Analytics / Infrastructure) — same shape as `categories.ts`,
|
|
37
|
+
* the marketing-site feature grid, and the docs IA. Drift is caught by
|
|
38
|
+
* `amba-setup.test.ts`.
|
|
39
|
+
*
|
|
40
|
+
* The body is plain CommonMark — no MDX, no JSX — so it renders
|
|
41
|
+
* identically wherever it lands.
|
|
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";
|
|
44
|
+
/** Canonical URI for the MCP resource. */
|
|
45
|
+
export declare const AMBA_SETUP_GUIDE_URI = "amba://setup";
|
|
46
|
+
/** Canonical MIME type for the guide body. */
|
|
47
|
+
export declare const AMBA_SETUP_GUIDE_MIME = "text/markdown";
|
|
48
|
+
/**
|
|
49
|
+
* Body-version marker. Bumped when the canonical body changes shape
|
|
50
|
+
* in a way that downstream agents should re-read. The CLI embeds it
|
|
51
|
+
* into every fan-out file (\`CLAUDE.md\`, \`AGENTS.md\`, etc.) so a
|
|
52
|
+
* re-init can detect stale content. Bumped to \`v2\` when this PR
|
|
53
|
+
* replaced the capability-only guide with the full classify → confirm
|
|
54
|
+
* → wire-up playbook.
|
|
55
|
+
*/
|
|
56
|
+
export declare const AMBA_SETUP_GUIDE_BODY_VERSION = "v2";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Amba Expo build prompt — markdown body (no MDX frontmatter).
|
|
3
|
+
*
|
|
4
|
+
* Source of truth for three customer-facing surfaces:
|
|
5
|
+
*
|
|
6
|
+
* 1. The published docs page at
|
|
7
|
+
* `https://docs.amba.dev/prompts/expo-build` — the MDX file at
|
|
8
|
+
* `apps/docs/content/docs/prompts/expo-build.mdx` ships the same
|
|
9
|
+
* body wrapped in fumadocs frontmatter.
|
|
10
|
+
* 2. The MCP resource `amba://prompts/expo-build` registered by
|
|
11
|
+
* `registerAllResources()` in `./index.ts` and exposed by the
|
|
12
|
+
* hosted MCP server at `mcp.amba.dev`.
|
|
13
|
+
* 3. The inlined snapshot baked into the `/amba-build` Claude Code
|
|
14
|
+
* skill by `amba init --sandbox` (see `packages/cli/src/skills.ts`).
|
|
15
|
+
*
|
|
16
|
+
* Drift between this constant and the MDX file is caught by
|
|
17
|
+
* `expo-build-prompt.test.ts` — that test reads the MDX from disk,
|
|
18
|
+
* strips the YAML frontmatter, and asserts it equals `EXPO_BUILD_PROMPT_MD`.
|
|
19
|
+
*
|
|
20
|
+
* **Update protocol:** edit the MDX (it's the human-facing surface;
|
|
21
|
+
* it renders on docs.amba.dev). Re-run the drift test. The test will
|
|
22
|
+
* fail with a diff. Apply the same diff here. The two are kept in
|
|
23
|
+
* sync by hand because the MDX must be statically parseable for
|
|
24
|
+
* fumadocs + we can't import `.md` files as raw strings without a
|
|
25
|
+
* build-step that pulls in extra config.
|
|
26
|
+
*
|
|
27
|
+
* The body itself is plain CommonMark — no MDX components, no JSX —
|
|
28
|
+
* so it renders identically as `.md` (the MCP / skill consumers) and
|
|
29
|
+
* as `.mdx` (the docs site).
|
|
30
|
+
*/
|
|
31
|
+
export declare const EXPO_BUILD_PROMPT_MD = "> **Last reviewed:** 2026-05-17. The canonical version of this page lives\n> at [docs.amba.dev/prompts/expo-build](https://docs.amba.dev/prompts/expo-build).\n> If you're reading an inlined snapshot from your\n> `.claude/skills/amba-build/SKILL.md`, check the URL above for updates.\n\nThis is the prompt an AI coding agent runs to build a full Expo app\nwhere Amba is the only backend. It's structured as a single `/goal`\ndirective \u2014 paste it, replace `<DESIGN_HASH>` with whatever describes\nyour design (a URL, a description, a Figma link), and let the agent\nexecute.\n\n## Quick setup\n\nThe CLI handles signup, project provisioning, env-file writes, and MCP\nclient config wiring in one command:\n\n```bash\nnpx -y @layers/amba init\n```\n\nThat's the entire setup. The CLI:\n\n1. Signs up an agent-mode developer account (no browser, no email\n verification needed for sandbox).\n2. Creates an Amba project and mints a client key + admin PAT.\n3. Writes `.env.local` (`AMBA_PROJECT_ID`, `AMBA_CLIENT_KEY`,\n `AMBA_API_URL`).\n4. Writes `AMBA.md` (project-scoped context for the agent).\n5. Auto-wires `mcpServers.amba` into every MCP client config it\n detects on disk \u2014 Claude Code, Cursor, Windsurf.\n6. Verifies the PAT against the API and confirms it's good.\n\nThe Amba MCP toolset (`amba_*` tools \u2014 ~130 of them) is available to\nthe agent immediately: pass the freshly-minted `pat` as an inline\nargument on every `amba_*` call in the current session. The next time\nyour MCP client starts it picks the PAT up from the config as the\ninbound Bearer automatically \u2014 at that point the `pat` arg becomes\noptional. No restart needed; nothing for you to do.\n\nIf you have the `/amba-build` skill installed (via\n`npx -y @layers/amba init`), invoke it directly:\n\n```\n/amba-build <DESIGN_HASH>\n```\n\nOtherwise paste the prompt below.\n\n## Current known gotchas\n\nThree remaining wrinkles you may hit. Everything else from the 2026-05\nDX cascade is fixed.\n\n- **Web CORS** \u2014 the public API does not currently send\n `Access-Control-Allow-Origin` for browser-origin requests. Use the\n agent's circuit-break-on-second-failure rule for web targets; for\n Expo (iOS + Android) you'll never see this.\n- **React Native bundle size** \u2014 the React Native SDK adds ~4 MB to\n the JS bundle today. Functional, just heavier than the long-term\n goal. Tracked separately.\n- **Sandbox MAU cap (100)** \u2014 the agent-mode sandbox tier caps at 100\n monthly active users. If you blow through it during testing, call\n `amba_users_reset_sandbox` to clear the counter \u2014 that tool exists\n specifically for this. Upgrade to the Free tier (1,000 MAU, 500 MB\n DB) by running `amba claim me@example.com` from the terminal \u2014 the\n backend emails a one-click magic link to the address you pass in;\n clicking it binds the account to that email and lifts the cap.\n\n## How to read the design\n\nIf `<DESIGN_HASH>` is a URL to a packaged design (e.g. a download\nlink from your design tool of choice), unpack it before you start:\n\n```bash\nmkdir -p design && cd design\ncurl -L \"<DESIGN_HASH>\" -o design.tar.gz\ngunzip -c design.tar.gz | tar -x\nls\n# Expected: README, chats/, project/ (or equivalent)\n```\n\nRead the README first \u2014 it should tell you what each subdirectory\nholds. The `chats/` directory typically contains conversation logs\nthat capture the design intent in dialog form; treat them as the\nauthoritative source for tone and feature priorities. The `project/`\ndirectory holds the structured asset graph (screens, components,\nstyles).\n\nIf `<DESIGN_HASH>` is a freeform description (not a URL), skip the\nunpacking and treat the description text as the design brief.\n\n## Use Amba for everything\n\nThe rule: any feature that touches data, identity, scheduling,\nnotifications, content, or social \u2014 use Amba. Don't reach for\nAsyncStorage-as-database, don't bring in Firebase / Supabase / your\nown server, don't roll a custom auth layer. The point of this build\nis that Amba covers it all.\n\nSpecifically: every feature in the design that needs a backend maps\nto an Amba primitive. If you can't find a fit, the rule is **escalate\nin the gaps log** (see the verification gate), not \"ship without\nAmba.\" Skipping a primitive needs a written justification \u2014 the\nverification gate enforces this.\n\n## Feature \u2192 Amba primitive map\n\n| App feature | Amba primitive | MCP tools |\n| -------------------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| User accounts (anonymous + Apple + Google + email) | Auth | `amba_developer_signup` (one-time bootstrap), `Amba.signIn()` SDK calls |\n| Profile data (name, avatar, prefs) | App users | `amba_users_list`, `amba_users_get`, `amba_users_bulk_update` |\n| Daily content (tips, lessons, quotes) | Content libraries + schedules | `amba_content_libraries_create`, `amba_content_items_add`, `amba_content_schedules_create`, `amba_content_list_libraries`, `amba_content_list_items`, `amba_content_list_schedules` |\n| Push notifications | Push campaigns | `amba_push_campaigns_create`, `amba_push_campaigns_send`, `amba_push_send_test`, `amba_push_list_campaigns` |\n| User segments (e.g. inactive 7d, premium) | Segments | `amba_segments_create`, `amba_segments_list`, `amba_segments_evaluate` |\n| Daily streaks | Streaks | `amba_streaks_create`, `amba_streaks_list` (call `streaks.qualify()` from the SDK to record activity) |\n| XP and levels | XP rules | `amba_xp_rules_create`, `amba_xp_rules_list`, `amba_users_get_xp` |\n| Achievements / badges | Achievements | `amba_achievements_create`, `amba_achievements_list`, `amba_achievements_get` |\n| Challenges (time-limited goals) | Challenges | `amba_challenges_create`, `amba_challenges_list`, `amba_challenges_list_participants` |\n| Leaderboards | Leaderboards | `amba_leaderboards_create`, `amba_leaderboards_list`, `amba_leaderboards_get` |\n| In-app currency / virtual goods | Economy (currencies + catalog + stores) | `amba_currencies_create`, `amba_catalog_items_create`, `amba_stores_create`, `amba_currencies_grant`, `amba_users_get_inventory` |\n| Social (friends, groups, feed, DMs) | Social primitives | `amba_create_group`, `amba_groups_list`, `amba_groups_update_member`, `amba_friendships_list` (feeds + messaging via SDK: `Amba.feeds.*`, `Amba.messaging.*`) |\n| Remote feature flags / config | Configs | `amba_configs_create`, `amba_configs_list`, `amba_configs_update` |\n| Entitlements (premium / paywall) | RevenueCat / Superwall integration | `amba_integrations_configure`, `amba_integrations_test` |\n| Custom data (anything not above) | Collections | `amba_collections_create`, `amba_collections_alter`, `amba_collections_list`, `amba_admin_insert_row`, `amba_admin_list_rows`, plus client-side `Amba.collections.*` |\n| Analytics / event tracking | Events | `Amba.events.track(...)` from the SDK; query with `amba_analytics_get`, `amba_users_list_events` |\n\nEvery primitive above has list / read MCP tools you can use to verify\nseed data after creation \u2014 the verification gate uses these to catch\n\"fake implementation\" failure modes (where the app code thinks a thing\nwas created but nothing actually landed in the backend).\n\n## Seed data\n\nBefore writing app code, seed the backend with enough data that every\nscreen in the design has something realistic to render. Order:\n\n1. **Configs** \u2014 feature flags + tunable constants the app reads at\n boot (`amba_configs_create`).\n2. **Segments** \u2014 at least one (e.g. \"new_user\", first 7 days) so\n targeting works downstream.\n3. **Content libraries + schedules** \u2014 daily content for any\n tips/quotes/lessons screen. Seed \u226530 items so the carousel /\n day-stepper doesn't loop visibly.\n4. **Streaks** \u2014 define the streak shape (daily / weekly, grace\n window, freeze policy).\n5. **XP rules** \u2014 events \u2192 XP-award rules so XP accrues from real\n gameplay.\n6. **Achievements** \u2014 unlock criteria for badges.\n7. **Challenges** \u2014 at least one active challenge with rewards.\n8. **Leaderboards** \u2014 XP, streaks, or any custom metric.\n9. **Currencies + catalog + stores** \u2014 virtual currency, catalog\n items, store listings (only if the design has an economy screen).\n10. **Collections** \u2014 schemas + sample rows for any custom data the\n app needs (e.g. user-generated content, journal entries, custom\n list items).\n11. **Push campaigns** \u2014 at least one welcome push + one re-engagement\n push targeting your \"new_user\" segment.\n\nAfter seeding, the verification gate (below) confirms each primitive\nexists by calling the matching `amba_*_list` MCP tool. Empty list \u2192\nfailure.\n\n## Engineering rules\n\nThese are non-negotiable. Violating any one of them fails the build\ngate.\n\n- **Expo Router with typed routes.** Use `expo-router` and enable\n `experiments.typedRoutes` in `app.json`. Every screen is a\n filesystem route; no manual navigation stacks.\n- **TypeScript strict mode.** `strict: true` in `tsconfig.json`. Zero\n `any`. Zero `@ts-ignore`. `tsc --noEmit` must pass.\n- **React Native primitives only.** `View`, `Text`, `Pressable`,\n `ScrollView`, `FlatList`, `Image`. No `div`, no `span`, no DOM-only\n libs. The build target is iOS + Android + Web \u2014 every screen has to\n render on all three.\n- **Fonts via expo-font.** Don't ship system-font-only screens; load\n the design's typography via `useFonts` and gate the splash screen\n on load.\n- **Persistence via AsyncStorage.** Anything you cache client-side\n (theme choice, last-viewed-item, dismissed banners) goes in\n AsyncStorage. Never sprinkle direct file I/O.\n- **Theme system.** A single `theme.ts` exports light + dark token\n maps; consume via a `useTheme()` hook. The verification gate\n toggles light \u2194 dark and screenshots; if any screen has hardcoded\n colors that don't flip, the gate fails.\n- **Circuit-break on second failure.** If two consecutive Amba API\n calls fail with the same error, stop retrying and surface a clean\n empty-state to the user. Don't loop forever; don't crash. The web\n CORS issue (above) is the most likely trigger.\n- **Deterministic offline fallback.** When `fetch` fails (airplane\n mode, network drop), the app renders **deterministic** placeholder\n content \u2014 same content per `userId + day` \u2014 never random. Real data\n swaps in when the network returns.\n- **Three-platform bundle gate.** `expo export --platform web`,\n `expo export --platform ios`, and `expo export --platform android`\n must all succeed. If any one fails, the build fails. No\n \"shipped iOS-only, web is broken\" \u2014 the rule is parity.\n- **Don't name a tab `settings.tsx`.** Use `account.tsx` or\n `preferences.tsx` instead. Expo Router's static web export generates\n `settings.html` correctly but does not resolve direct URL navigation\n to `/settings` \u2014 the client-side router shows an unmatched-route\n error while other tab names work fine. (Observed in dogfood; upstream\n behavior, not an Amba issue.)\n\n## Verification gate\n\nBefore declaring the build done, run every check in this list. Any\nfailure means the build is not done \u2014 fix and re-run.\n\n```bash\n# Type-check\npnpm tsc --noEmit\n\n# Three-platform export\npnpm expo export --platform web\npnpm expo export --platform ios\npnpm expo export --platform android\n```\n\nThen, from inside the agent (use the Amba MCP tools):\n\n- `amba_analytics_get` \u2192 at least one event tracked end-to-end\n through `Amba.events.track()` from the app.\n- `amba_users_list` \u2192 at least one user exists (the agent's own\n anonymous signin counts).\n- For every primitive the seed step created, call the matching\n `amba_*_list` and assert non-empty:\n - `amba_configs_list`\n - `amba_segments_list`\n - `amba_content_list_libraries`, `amba_content_list_items`,\n `amba_content_list_schedules`\n - `amba_streaks_list`\n - `amba_xp_rules_list`\n - `amba_achievements_list`\n - `amba_challenges_list`\n - `amba_leaderboards_list`\n - `amba_currencies_list` (if economy seeded)\n - `amba_catalog_list` (if catalog seeded)\n - `amba_collections_list` + `amba_admin_list_rows` per collection\n - `amba_push_list_campaigns`\n- Empty list for any seeded primitive \u2192 the implementation is fake\n (UI exists but never wrote to the backend). Failure.\n- Manually walk every route in the browser (`expo start --web`),\n screenshot each, and confirm:\n - Light theme renders cleanly.\n - Dark theme renders cleanly (toggle and re-screenshot every\n route).\n - Empty states render when collections are empty (fresh-install\n simulation: wipe AsyncStorage, reload).\n- `amba_users_reset_sandbox` to confirm you can recover from the MAU\n cap if you blew past 50 during testing.\n\nSkipping any primitive's seed step requires a one-line written\njustification in the gaps log (next section). \"We don't need\nstreaks\" is fine; silence is not.\n\n## Final output\n\nWhen done, write a final report to `BUILD_REPORT.md` in the project\nroot. Required sections:\n\n- **Start timestamp** (when the agent started).\n- **End timestamp** (when the verification gate last passed).\n- **MCP call inventory** \u2014 every `amba_*` tool you invoked, with a\n count. Lets a human reviewer audit \"did this agent actually use\n Amba for X\" at a glance.\n- **Primitive coverage table** \u2014 one row per primitive from the\n Feature \u2192 Amba primitive map. Mark each \u2705 (used), \u26A0\uFE0F (used with\n caveats \u2014 explain), or \u23ED (skipped \u2014 justify in one line).\n- **Gaps log** \u2014 every primitive you skipped, every feature you\n couldn't fit cleanly to an Amba primitive, every workaround. One\n line per gap, no marketing language.\n- **`seed-report.json`** \u2014 machine-readable seed summary:\n `{ \"primitive\": \"<name>\", \"created\": <count>, \"listed\": <count> }`\n for every primitive. The `listed` count comes from the\n `amba_*_list` call in the verification gate. `created ===\nlisted` for every row is the success condition.\n\nIf `BUILD_REPORT.md` is missing any required section, or\n`seed-report.json` is missing, the build is not done.\n";
|
|
32
|
+
/** Canonical URI for the MCP resource. */
|
|
33
|
+
export declare const EXPO_BUILD_PROMPT_URI = "amba://prompts/expo-build";
|
|
34
|
+
/** Canonical MIME type for the prompt body. */
|
|
35
|
+
export declare const EXPO_BUILD_PROMPT_MIME = "text/markdown";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP resource registry.
|
|
3
|
+
*
|
|
4
|
+
* Resources are the read-only counterpart to tools — agents fetch them
|
|
5
|
+
* via `resources/list` + `resources/read` rather than tool invocations.
|
|
6
|
+
* We use them for canonical prompts (long-form context an agent should
|
|
7
|
+
* consume verbatim before acting) that would otherwise have to be
|
|
8
|
+
* copy-pasted from docs.amba.dev.
|
|
9
|
+
*
|
|
10
|
+
* Current resources:
|
|
11
|
+
*
|
|
12
|
+
* - `amba://setup` — canonical long-form Amba setup guide. The full
|
|
13
|
+
* classify → confirm → wire-up → report playbook plus the SDK
|
|
14
|
+
* capability map grouped under the 7-category taxonomy. The short
|
|
15
|
+
* summary in the MCP server's `instructions` field points agents
|
|
16
|
+
* here for full detail. Twin of `packages/cli/skill-bundle/SKILL.md`
|
|
17
|
+
* — the CLI installs SKILL.md locally during `amba init`; this
|
|
18
|
+
* resource is the server-side mirror for clients without a local
|
|
19
|
+
* CLI install (Claude.ai web, Claude Desktop, etc.). See
|
|
20
|
+
* `./amba-setup.ts`.
|
|
21
|
+
* - `amba://setup/<surface>` — per-surface playbook for each of the
|
|
22
|
+
* six wire-up surfaces (identity, engagement, gamification,
|
|
23
|
+
* economy, social, infrastructure). Step 3 of the main guide
|
|
24
|
+
* tells the agent to fetch the sub-resource for any surface in the
|
|
25
|
+
* confirmed scope. Each one carries the MCP tool reference + per-
|
|
26
|
+
* stack SDK init + common follow-ups + re-run rules for its
|
|
27
|
+
* surface.
|
|
28
|
+
* - `amba://prompts/expo-build` — canonical "/goal" prompt for
|
|
29
|
+
* building a full Expo app with Amba as the only backend. Body
|
|
30
|
+
* mirrors `apps/docs/content/docs/prompts/expo-build.mdx`. See
|
|
31
|
+
* `./expo-build-prompt.ts`.
|
|
32
|
+
*
|
|
33
|
+
* Adding new resources: export the body + URI as constants from a
|
|
34
|
+
* sibling file, then register it inside `registerAllResources`. Keep
|
|
35
|
+
* each resource frontmatter-free — clients expect plain markdown.
|
|
36
|
+
*/
|
|
37
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
38
|
+
import { AMBA_SETUP_GUIDE_MD, AMBA_SETUP_GUIDE_MIME, AMBA_SETUP_GUIDE_URI, AMBA_SETUP_GUIDE_BODY_VERSION } from './amba-setup.js';
|
|
39
|
+
import { AMBA_SETUP_IDENTITY_MD, AMBA_SETUP_IDENTITY_MIME, AMBA_SETUP_IDENTITY_URI } from './amba-setup-identity.js';
|
|
40
|
+
import { AMBA_SETUP_ENGAGEMENT_MD, AMBA_SETUP_ENGAGEMENT_MIME, AMBA_SETUP_ENGAGEMENT_URI } from './amba-setup-engagement.js';
|
|
41
|
+
import { AMBA_SETUP_GAMIFICATION_MD, AMBA_SETUP_GAMIFICATION_MIME, AMBA_SETUP_GAMIFICATION_URI } from './amba-setup-gamification.js';
|
|
42
|
+
import { AMBA_SETUP_ECONOMY_MD, AMBA_SETUP_ECONOMY_MIME, AMBA_SETUP_ECONOMY_URI } from './amba-setup-economy.js';
|
|
43
|
+
import { AMBA_SETUP_SOCIAL_MD, AMBA_SETUP_SOCIAL_MIME, AMBA_SETUP_SOCIAL_URI } from './amba-setup-social.js';
|
|
44
|
+
import { AMBA_SETUP_INFRASTRUCTURE_MD, AMBA_SETUP_INFRASTRUCTURE_MIME, AMBA_SETUP_INFRASTRUCTURE_URI } from './amba-setup-infrastructure.js';
|
|
45
|
+
import { EXPO_BUILD_PROMPT_MD, EXPO_BUILD_PROMPT_MIME, EXPO_BUILD_PROMPT_URI } from './expo-build-prompt.js';
|
|
46
|
+
export { AMBA_SETUP_GUIDE_MD, AMBA_SETUP_GUIDE_MIME, AMBA_SETUP_GUIDE_URI, AMBA_SETUP_GUIDE_BODY_VERSION, AMBA_SETUP_IDENTITY_MD, AMBA_SETUP_IDENTITY_MIME, AMBA_SETUP_IDENTITY_URI, AMBA_SETUP_ENGAGEMENT_MD, AMBA_SETUP_ENGAGEMENT_MIME, AMBA_SETUP_ENGAGEMENT_URI, AMBA_SETUP_GAMIFICATION_MD, AMBA_SETUP_GAMIFICATION_MIME, AMBA_SETUP_GAMIFICATION_URI, AMBA_SETUP_ECONOMY_MD, AMBA_SETUP_ECONOMY_MIME, AMBA_SETUP_ECONOMY_URI, AMBA_SETUP_SOCIAL_MD, AMBA_SETUP_SOCIAL_MIME, AMBA_SETUP_SOCIAL_URI, AMBA_SETUP_INFRASTRUCTURE_MD, AMBA_SETUP_INFRASTRUCTURE_MIME, AMBA_SETUP_INFRASTRUCTURE_URI, EXPO_BUILD_PROMPT_MD, EXPO_BUILD_PROMPT_MIME, EXPO_BUILD_PROMPT_URI, };
|
|
47
|
+
/**
|
|
48
|
+
* The six per-surface sub-resources, exposed as a single tuple so the
|
|
49
|
+
* registration loop in `registerAllResources` and the drift gate test
|
|
50
|
+
* in `amba-setup.test.ts` can iterate without redeclaring the list.
|
|
51
|
+
*/
|
|
52
|
+
export declare const AMBA_SETUP_SUB_RESOURCES: readonly [{
|
|
53
|
+
readonly name: "amba-setup-identity";
|
|
54
|
+
readonly uri: "amba://setup/identity";
|
|
55
|
+
readonly mime: "text/markdown";
|
|
56
|
+
readonly body: "# Identity\n\nEnd-user authentication for an Amba project: anonymous sessions, email/password, email OTP, SMS OTP, magic links, Sign in with Apple, Sign in with Google, and account linking. All flows return an `AuthResult` containing a `user` + a session token; the SDK persists tokens to the platform's native secure storage and replays them on the next launch. Subsequent SDK calls (collections, push, XP, etc.) are authenticated as the signed-in user automatically.\n\nThere is no separate \"identity provisioning\" step — `auth` is the default surface for every project. Your job here is to (a) wire `Amba.configure(...)` plus the right sign-in calls into the user's entry file, and (b) where the user wants social sign-in, set the audience identifiers on the project so the server can verify identity tokens.\n\n## MCP tools\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_developer_me` | Verify the developer PAT and read the developer's profile. Pre-flight check before any provisioning. | `{}` |\n| `amba_projects_get` | Read a project's config (bundle id, OAuth client id, platform). | `{ project_id }` |\n| `amba_projects_update` | Set `bundle_id` (Apple audience) and `google_oauth_client_id` (Google audience). Required before Sign in with Apple / Google works. | `{ project_id, bundle_id: \"com.example.fitness\", google_oauth_client_id: \"1234.apps.googleusercontent.com\" }` |\n| `amba_users_list` | Browse end-users (app_users) of the project — useful as a smoke check after the first sign-in. | `{ project_id, limit: 20 }` |\n| `amba_users_get` | Fetch a single app_user by id. | `{ project_id, user_id }` |\n| `amba_users_bulk_update` | Set custom properties on many users at once. | `{ project_id, user_ids: [...], properties: { tier: \"trial\" } }` |\n| `amba_api_keys_create` | Mint additional client/server keys (e.g. a separate `production` key). | `{ project_id, key_type: \"client\", environment: \"production\" }` |\n| `amba_api_keys_delete` | Revoke a leaked key. | `{ project_id, api_key_id }` |\n| `amba_roles_assign` | Grant an RBAC role to an app_user (admin / moderator / etc.). | `{ project_id, user_id, role_id }` |\n\nThere's no `amba_auth_*` namespace — auth is owned by the SDK on the client side, and there are no provisioning calls for it beyond setting the project's audience identifiers. If the user wants Apple / Google sign-in, the **mandatory** preflight is:\n\n```\namba_projects_update({\n project_id: \"<from Step 0>\",\n bundle_id: \"<their iOS bundle id>\",\n google_oauth_client_id: \"<their Google OAuth client id>\"\n})\n```\n\nWithout this, the server rejects identity tokens with `AUDIENCE_NOT_CONFIGURED` and the user thinks Amba is broken. If they don't know their bundle id, ask; if they don't have a Google OAuth client yet, tell them to create one at `console.cloud.google.com` and link it later via `amba_projects_update`.\n\n## SDK init per stack\n\n### Expo\n\n```bash\nnpx expo install @layers/amba-expo @react-native-async-storage/async-storage\n```\n\nIn `app/_layout.tsx` (or whatever your root layout is):\n\n```tsx\nimport { useEffect } from 'react';\nimport { Amba } from '@layers/amba-expo';\n\nexport default function RootLayout() {\n useEffect(() => {\n (async () => {\n await Amba.configure({\n apiKey: process.env.EXPO_PUBLIC_AMBA_CLIENT_KEY!,\n });\n await Amba.auth.signInAnonymously();\n })();\n }, []);\n return /* … */ null;\n}\n```\n\nFor Sign in with Apple, add `expo-apple-authentication`; capture the identity token and call `Amba.auth.signInWithApple(identityToken)`. For Sign in with Google, use `expo-auth-session/providers/google` and call `Amba.auth.signInWithGoogle(id_token)`.\n\n### React Native (bare)\n\n```bash\nnpm install @layers/amba-react-native @react-native-async-storage/async-storage\n```\n\n```tsx\nimport { Amba } from '@layers/amba-react-native';\n\nawait Amba.configure({ apiKey: process.env.AMBA_CLIENT_KEY! });\nawait Amba.auth.signInAnonymously();\n\n// Email OTP\nawait Amba.auth.requestEmailOtp(email);\nawait Amba.auth.verifyEmailOtp(email, code);\n\n// SMS OTP (E.164, leading \"+\")\nawait Amba.auth.requestSmsOtp('+14155551234');\nawait Amba.auth.verifySmsOtp('+14155551234', code);\n```\n\n### Web (browser / Next.js / Vite / Remix)\n\n```bash\nnpm install @layers/amba-web\n# Optional React hooks:\nnpm install @layers/amba-react\n```\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nawait Amba.configure({ apiKey: import.meta.env.VITE_AMBA_CLIENT_KEY });\nawait Amba.auth.signInAnonymously();\n\n// Magic link\nawait Amba.auth.requestMagicLink('user@example.com');\nconst token = new URLSearchParams(window.location.search).get('token');\nif (token) await Amba.auth.verifyMagicLink(token);\n```\n\nNext.js — call `Amba.configure(...)` once at the top of `app/layout.tsx` (or `pages/_app.tsx`). Anonymous sign-in should happen on the client; do not call SDK functions in server components.\n\n### iOS (Swift, SPM)\n\nIn `Package.swift` (or Xcode → File → Add Package Dependencies):\n\n```swift\n.package(url: \"https://github.com/layers/amba-sdk-ios\", from: \"1.0.0\")\n```\n\n```swift\nimport SwiftUI\nimport Amba\n\n@main\nstruct MyApp: App {\n init() {\n Task {\n try await Amba.configure(apiKey: ProcessInfo.processInfo.environment[\"AMBA_CLIENT_KEY\"]!)\n try await Amba.auth.signInAnonymously()\n }\n }\n var body: some Scene { WindowGroup { ContentView() } }\n}\n```\n\nSign in with Apple — use Apple's `AuthenticationServices` framework; pass the `identityToken` to `Amba.auth.signInWithApple`. Sign in with Google — use Google's `GoogleSignIn-iOS` SDK; pass the `idToken` to `Amba.auth.signInWithGoogle`.\n\n> Add the \"Sign in with Apple\" capability in **Xcode → target → Signing & Capabilities → + Capability**. Without it, the Apple auth call fails before it reaches Amba.\n\n### Android (Kotlin)\n\nIn `app/build.gradle.kts`:\n\n```kotlin\ndependencies {\n implementation(\"com.layers.amba:amba-sdk-android:0.1.0\")\n}\n```\n\nIn your `Application` subclass:\n\n```kotlin\nimport android.app.Application\nimport com.layers.amba.Amba\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\n\nclass MyApp : Application() {\n override fun onCreate() {\n super.onCreate()\n GlobalScope.launch {\n Amba.configure(apiKey = BuildConfig.AMBA_CLIENT_KEY)\n Amba.auth.signInAnonymously()\n }\n }\n}\n```\n\nSign in with Google — use Google's Credential Manager flow, capture `idToken`, then `Amba.auth.signInWithGoogle(idToken = idToken)`.\n\n### Flutter\n\n```yaml\ndependencies:\n amba: ^1.0.0\n```\n\n```dart\nimport 'package:amba/amba.dart';\n\nFuture<void> main() async {\n WidgetsFlutterBinding.ensureInitialized();\n await Amba.configure(apiKey: const String.fromEnvironment('AMBA_CLIENT_KEY'));\n await Amba.auth.signInAnonymously();\n runApp(const MyApp());\n}\n```\n\nPass the key in: `flutter run --dart-define=AMBA_CLIENT_KEY=$AMBA_CLIENT_KEY`. Apple: `sign_in_with_apple` plugin → `Amba.auth.signInWithApple`. Google: `google_sign_in` plugin → `Amba.auth.signInWithGoogle`.\n\n## Common follow-ups\n\nAsk one bundled multi-choice — don't drip-feed.\n\n1. **Which sign-in methods do you want?** (multi-select)\n - [x] Anonymous (recommended — call at app start, lets users use the app immediately)\n - [ ] Email + password\n - [ ] Email OTP (6-digit code emailed)\n - [ ] Magic link (single click email)\n - [ ] Phone OTP / SMS (E.164, requires SMS provider configured)\n - [ ] Sign in with Apple (iOS / web; required for iOS apps that have any third-party auth per App Store guideline 4.8)\n - [ ] Sign in with Google (Android / iOS / web)\n\n2. **If Apple is selected:** what's your iOS bundle id?\n\n3. **If Google is selected:** what's your Google OAuth client id? Format: `123456789-abc.apps.googleusercontent.com`. If they don't have one, point them at `console.cloud.google.com` and proceed without it — they can paste it later via `amba_projects_update`.\n\n4. **If anonymous is selected:** when do you want users to upgrade?\n - On a \"Save your progress\" prompt (offer Apple/Google linking)\n - Behind a paywall / premium gate\n - Never auto-prompt (user upgrades from settings)\n - Defaults to \"never auto-prompt\".\n\n## Re-run behavior\n\nOn a second invocation that targets identity:\n\n1. Call `amba_projects_get({ project_id })` to read current `bundle_id` and `google_oauth_client_id`. Compare to what the user gave you:\n - If both already set → no `amba_projects_update` needed.\n - If user is adding a new social provider that needs an audience → call `amba_projects_update` with just the new field. Don't blow away the existing one.\n\n2. For new sign-in methods, append the per-method code block to the existing entry file *without* re-emitting `Amba.configure(...)` (it's already there). Detection: search for `Amba.configure` in the entry file; if present, skip the configure block.\n\n3. If the user asks to \"switch from anonymous to email-only\" or similar destructive change, **don't auto-do it**. Explain that existing anonymous user data would be unreachable without a link flow, then offer:\n - Add the new method alongside anonymous (recommended)\n - Add a forced upgrade prompt in onboarding\n - Migrate manually via `Amba.auth.linkEmailOtp(email, code)` — keeps existing user data\n";
|
|
57
|
+
readonly surface: "identity";
|
|
58
|
+
readonly title: "Amba setup — identity";
|
|
59
|
+
readonly description: string;
|
|
60
|
+
}, {
|
|
61
|
+
readonly name: "amba-setup-engagement";
|
|
62
|
+
readonly uri: "amba://setup/engagement";
|
|
63
|
+
readonly mime: "text/markdown";
|
|
64
|
+
readonly body: "# Engagement\n\nEverything that brings a user back to the app: push notifications + campaigns, segments (rule-based user cohorts), content libraries (daily quotes, lessons, tips with scheduled rotation), onboarding flows, deep links, referrals, and tracked links. The SDK side is mostly read-and-call (`Amba.push.register`, `Amba.content.today`, `Amba.onboarding.nextStep`, `Amba.referrals.claimReferral`); the provisioning side — the part you do — lives behind MCP tools.\n\n## MCP tools\n\n### Push\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_push_campaigns_create` | Create a draft push campaign. | `{ project_id, title: \"Don't break your streak!\", body: \"Log your workout to keep the fire alive.\", name: \"streak_reminder\", segment_id: \"seg_active\" }` |\n| `amba_push_send_test` | Send a one-off push to a single app_user. Use this as your wire-verify after registering a token. | `{ project_id, user_id, title: \"Test\", body: \"Wired up.\" }` |\n| `amba_push_campaigns_send` | Send (or schedule) a draft campaign. | `{ project_id, campaign_id }` |\n| `amba_push_list_campaigns` | List campaigns. | `{ project_id }` |\n| `amba_push_get_campaign` | Read one campaign + its delivery stats. | `{ project_id, campaign_id }` |\n| `amba_push_update_campaign` | Edit a draft. | `{ project_id, campaign_id, title, body, scheduled_at }` |\n| `amba_push_delete_campaign` | Remove a draft / cancel a scheduled campaign. | `{ project_id, campaign_id }` |\n\n### Segments\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_segments_create` | Create a rule-based user segment. | `{ project_id, name: \"Power Users\", rules: { all: [{ field: \"events.workout_completed.count_7d\", op: \">=\", value: 5 }] } }` |\n| `amba_segments_list` | List all segments (system + custom). | `{ project_id }` |\n| `amba_segments_get` | Get one segment by id. | `{ project_id, segment_id }` |\n| `amba_segments_evaluate` | Materialize the segment — returns the set of matching user_ids. | `{ project_id, segment_id }` |\n| `amba_segments_patch` | Edit rules / name / description. | `{ project_id, segment_id, rules: {...} }` |\n| `amba_segments_delete` | Drop a segment. | `{ project_id, segment_id }` |\n\n### Content libraries\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_content_libraries_create` | Create a library (daily tips, lessons, quotes). | `{ project_id, name: \"Daily Tips\", description: \"Workout motivation served daily\" }` |\n| `amba_content_items_add` | Bulk-add items to a library. | `{ project_id, library_id, items: [{ body: \"Tip 1\" }, { body: \"Tip 2\" }, ...] }` |\n| `amba_content_bulk_import` | CSV/JSON bulk import. | `{ project_id, library_id, format: \"json\", items: [...] }` |\n| `amba_content_list_libraries` | List libraries in this project. | `{ project_id }` |\n| `amba_content_list_items` | List items in one library. | `{ project_id, library_id, limit: 100 }` |\n| `amba_content_update_item` | Edit one item. | `{ project_id, library_id, item_id, body: \"…\" }` |\n| `amba_content_delete_item` | Drop an item. | `{ project_id, library_id, item_id }` |\n| `amba_content_schedules_create` | Schedule a library for daily / weekly / random delivery. | `{ project_id, library_id, name: \"Daily rotation\", schedule_type: \"daily_rotation\" }` |\n| `amba_content_list_schedules` | List schedules. | `{ project_id, library_id }` |\n| `amba_content_update_schedule` | Edit a schedule's type or config. | `{ project_id, library_id, schedule_id, schedule_type: \"weekly\" }` |\n| `amba_content_delete_schedule` | Drop a schedule. | `{ project_id, library_id, schedule_id }` |\n\nThe worked example:\n\n```\n1. amba_content_libraries_create({ project_id, name: \"Daily Tips\" })\n2. amba_content_items_add({ project_id, library_id, items: [{ body: \"Tip 1\" }, ...] })\n3. amba_content_schedules_create({ project_id, library_id, name: \"Daily rotation\", schedule_type: \"daily_rotation\" })\n4. In-app: const today = await Amba.content.today(\"Daily Tips\");\n```\n\n### Onboarding flows\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_onboarding_create` | Create an onboarding flow definition (ordered steps). | `{ project_id, name: \"New user\", steps: [{ key: \"welcome\", type: \"screen\" }, { key: \"goal\", type: \"question\", options: [\"lose_weight\", \"build_muscle\"] }, { key: \"notif_permission\", type: \"permission_prompt\" }] }` |\n| `amba_onboarding_list` | List flows. | `{ project_id }` |\n| `amba_onboarding_get` | Read one flow. | `{ project_id, flow_id }` |\n| `amba_onboarding_update` | Edit steps. | `{ project_id, flow_id, steps: [...] }` |\n| `amba_onboarding_get_stats` | Funnel stats — step-by-step completion + drop-off rates. | `{ project_id, flow_id }` |\n| `amba_onboarding_delete` | Drop a flow. | `{ project_id, flow_id }` |\n\n### Deep links\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_deeplinks_set_config` | Set the project's deep-link config (custom scheme, universal-link domains, fallback URL). | `{ project_id, scheme: \"myapp\", universal_links: [\"myapp.com\"], fallback_url: \"https://myapp.com/get\" }` |\n| `amba_deeplinks_get_config` | Read the config. | `{ project_id }` |\n| `amba_deeplinks_list` | List existing deep-link records. | `{ project_id }` |\n| `amba_deeplinks_delete` | Drop a deep-link record. | `{ project_id, deeplink_id }` |\n\n### Referrals\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_referrals_create` | Create a referral program (per-program rewards on both sides). | `{ project_id, name: \"Invite a friend\", referrer_reward: { currency: \"gems\", amount: 100 }, referee_reward: { currency: \"gems\", amount: 50 } }` |\n| `amba_referrals_list` | List programs. | `{ project_id }` |\n| `amba_referrals_patch` | Edit rewards / program name. | `{ project_id, program_id, referrer_reward: {...} }` |\n| `amba_referrals_get_stats` | Per-program acquisition stats. | `{ project_id, program_id }` |\n| `amba_referrals_delete` | Drop a program. | `{ project_id, program_id }` |\n\n### Tracked links\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_tracked_links_create` | Create a short tracked link that resolves through Amba and records the click. | `{ project_id, name: \"Twitter campaign\", destination: \"https://myapp.com\", utm_source: \"twitter\" }` |\n| `amba_tracked_links_get_stats` | Click / conversion stats for a link. | `{ project_id, link_id }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` must run before any of the calls below — wire it once at app start (see `amba://setup/identity`). The snippets below show only the engagement-specific bits.\n\n### Expo\n\n```bash\nnpx expo install @layers/amba-expo expo-notifications expo-device\n```\n\n```tsx\nimport * as Notifications from 'expo-notifications';\nimport * as Device from 'expo-device';\nimport { Platform } from 'react-native';\nimport { Amba } from '@layers/amba-expo';\n\nasync function registerForPush() {\n if (!Device.isDevice) return;\n const { status: existing } = await Notifications.getPermissionsAsync();\n let final = existing;\n if (existing !== 'granted') {\n const { status } = await Notifications.requestPermissionsAsync();\n final = status;\n }\n if (final !== 'granted') return;\n const { data: token } = await Notifications.getDevicePushTokenAsync();\n await Amba.push.register(token, Platform.OS === 'ios' ? 'apns' : 'fcm');\n}\n\n// Content (daily tip)\nconst today = await Amba.content.today('Daily Tips');\n\n// Onboarding\nconst status = await Amba.onboarding.getStatus();\nawait Amba.onboarding.nextStep({ goal: 'lose_weight' });\nawait Amba.onboarding.complete();\n\n// Referrals\nconst { code } = await Amba.referrals.getReferralCode();\nconst claim = await Amba.referrals.claimReferral(code);\n```\n\n### React Native (bare)\n\n```bash\nnpm install @layers/amba-react-native @react-native-firebase/messaging\n```\n\n```tsx\nimport messaging from '@react-native-firebase/messaging';\nimport { Platform } from 'react-native';\nimport { Amba } from '@layers/amba-react-native';\n\nconst authStatus = await messaging().requestPermission();\nconst token = Platform.OS === 'ios'\n ? await messaging().getAPNSToken()\n : await messaging().getToken();\nif (token) await Amba.push.register(token, Platform.OS === 'ios' ? 'apns' : 'fcm');\n\nawait Amba.push.subscribe('marketing');\n```\n\n### Web (browser / Next.js)\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst reg = await navigator.serviceWorker.register('/sw.js');\nconst sub = await reg.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: import.meta.env.VITE_VAPID_PUBLIC_KEY,\n});\nawait Amba.push.register(JSON.stringify(sub), 'web');\n\nconst tip = await Amba.content.today('Daily Tips');\nconst status = await Amba.onboarding.getStatus();\n```\n\n### iOS (Swift)\n\n```swift\nimport UIKit\nimport Amba\n\nclass AppDelegate: NSObject, UIApplicationDelegate {\n func application(_ application: UIApplication,\n didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool {\n UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in\n if granted {\n DispatchQueue.main.async { application.registerForRemoteNotifications() }\n }\n }\n return true\n }\n\n func application(_ application: UIApplication,\n didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {\n let token = deviceToken.map { String(format: \"%02x\", $0) }.joined()\n Task { try await Amba.push.register(token: token, platform: .apns) }\n }\n}\n```\n\n> Enable the **Push Notifications** capability in **Xcode → Signing & Capabilities**, and upload an APNs key in `app.amba.dev` under your project's integrations tab.\n\n### Android (Kotlin)\n\n```kotlin\nimport com.google.firebase.messaging.FirebaseMessaging\nimport com.layers.amba.Amba\nimport com.layers.amba.push.PushPlatform\n\nFirebaseMessaging.getInstance().token.addOnSuccessListener { token ->\n GlobalScope.launch { Amba.push.register(token = token, platform = PushPlatform.FCM) }\n}\n\noverride fun onNewToken(token: String) {\n GlobalScope.launch { Amba.push.register(token = token, platform = PushPlatform.FCM) }\n}\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\nimport 'package:firebase_messaging/firebase_messaging.dart';\n\nfinal messaging = FirebaseMessaging.instance;\nawait messaging.requestPermission();\nfinal token = defaultTargetPlatform == TargetPlatform.iOS\n ? await messaging.getAPNSToken()\n : await messaging.getToken();\nif (token != null) {\n await Amba.push.register(\n token: token,\n platform: defaultTargetPlatform == TargetPlatform.iOS ? PushPlatform.apns : PushPlatform.fcm,\n );\n}\n\nfinal tip = await Amba.content.today('Daily Tips');\nfinal status = await Amba.onboarding.getStatus();\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Push: who do you target by default?**\n - All users (no segment)\n - A custom segment — I'll define one\n - Don't enable push yet (just register tokens)\n\n2. **Content libraries: do you want to seed a \"Daily Tips\" library?**\n - Yes — create a \"Daily Tips\" library with 7 starter items and a daily-rotation schedule\n - Yes but seed it empty — I'll add items myself\n - No\n\n3. **Onboarding: want a default 3-step flow?** (Welcome → primary goal → permission prompt)\n - Yes\n - No — I'll build my own flow\n - Yes but ask me what the goal-question options should be\n\n4. **Referrals: enable a referral program?**\n - Yes — both sides get 100 of `<currency>` (depends on economy surface; ask if currency isn't wired)\n - Yes — I'll set the reward myself\n - No\n\n5. **Deep links: which scheme + domain?**\n - Custom scheme only (e.g. `myapp://`) — recommended for quick start\n - Custom scheme + universal links (need to upload the AASA file and Digital Asset Links)\n - Skip — I have my own deep linking\n\n## Re-run behavior\n\n1. Before creating any resource, call the corresponding `_list` tool first:\n - `amba_push_list_campaigns` — don't recreate a campaign with a key that already exists; offer `amba_push_update_campaign` instead.\n - `amba_segments_list` — segments are keyed by `name`; collide → ask \"extend or skip\".\n - `amba_content_list_libraries` — same.\n - `amba_onboarding_list` — same.\n - `amba_referrals_list` — same.\n\n2. For push, **never auto-send a campaign on re-run**. Always create as `draft` and let the user trigger `amba_push_campaigns_send` manually.\n\n3. If the user re-runs and the entry file already has `Amba.push.register(...)`, don't duplicate it. Detection: search for `Amba.push.register` in the entry file.\n";
|
|
65
|
+
readonly surface: "engagement";
|
|
66
|
+
readonly title: "Amba setup — engagement";
|
|
67
|
+
readonly description: string;
|
|
68
|
+
}, {
|
|
69
|
+
readonly name: "amba-setup-gamification";
|
|
70
|
+
readonly uri: "amba://setup/gamification";
|
|
71
|
+
readonly mime: "text/markdown";
|
|
72
|
+
readonly body: "# Gamification\n\nFive primitives that turn a flat app into something users come back to: XP rules (auto-award points on a tracked event), achievements (badges that unlock on criteria), streaks (consecutive-period qualification), leaderboards (rank by metric), and challenges (time-bounded goals with rewards). All five are define-once / use-many: you create the definitions via MCP, the SDK qualifies / claims / reads against them at runtime.\n\nThe pattern is always:\n\n1. Agent (via MCP): define the rule / achievement / streak / etc.\n2. Client SDK at runtime: track the event (`Amba.events.track(...)`) or qualify (`Amba.streaks.qualify(...)` / `Amba.challenges.claim(...)`).\n3. Server: auto-evaluate, mutate user state, return updated progress.\n\n## MCP tools\n\n### XP rules\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_xp_rules_create` | Auto-award XP on a matching event. | `{ project_id, name: \"Workout Completed\", event_name: \"workout_completed\", xp_amount: 50, max_per_day: 5, cooldown_seconds: 60 }` |\n| `amba_xp_rules_list` | List all XP rules. | `{ project_id }` |\n| `amba_xp_update_rule` | Edit a rule. | `{ project_id, rule_id, xp_amount: 75 }` |\n| `amba_xp_delete_rule` | Delete a rule. | `{ project_id, rule_id }` |\n| `amba_xp_list_users` | List users sorted by XP. | `{ project_id, limit: 50 }` |\n| `amba_users_get_xp` | Read a specific user's XP total + level. | `{ project_id, user_id }` |\n\n### Achievements\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_achievements_create` | Define an achievement that unlocks on criteria. | `{ project_id, key: \"first_workout\", name: \"First Workout\", description: \"Complete your first workout\", xp_reward: 100, criteria: { event: \"workout_completed\", count: 1 } }` |\n| `amba_achievements_list` | List all achievements. | `{ project_id }` |\n| `amba_achievements_get` | Read one. | `{ project_id, achievement_id }` |\n| `amba_achievements_update` | Edit an achievement. | `{ project_id, achievement_id, xp_reward: 150 }` |\n| `amba_achievements_delete` | Delete. | `{ project_id, achievement_id }` |\n\nCommon criteria shapes:\n\n```jsonc\n// Count-based\n{ \"event\": \"workout_completed\", \"count\": 5 }\n\n// Streak-based\n{ \"streak_key\": \"daily_workout\", \"min_length\": 7 }\n\n// XP-based\n{ \"xp_total\": 5000 }\n\n// Catalog-item-based\n{ \"item_owned\": \"premium_theme\" }\n```\n\n### Streaks\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_streaks_create` | Define a streak — what event qualifies, what period, freeze rules. | `{ project_id, key: \"daily_workout\", name: \"Daily Workout\", qualifying_event: \"workout_completed\", period: \"daily\", grace_period_hours: 6, freeze_enabled: true, max_freezes: 3 }` |\n| `amba_streaks_list` | List streaks. | `{ project_id }` |\n| `amba_streaks_update` | Edit a streak definition. | `{ project_id, streak_id, max_freezes: 5 }` |\n| `amba_streaks_delete` | Delete. | `{ project_id, streak_id }` |\n\nThe `key` is **immutable** after creation (the SDK identifies streaks by key, not UUID — changing it breaks live `Amba.streaks.qualify(...)` calls). Keys: lowercase letters, digits, `_`, `-`, 1-64 chars.\n\n### Leaderboards\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_leaderboards_create` | Define a leaderboard. | `{ project_id, name: \"Weekly XP\", metric: \"xp\", period: \"weekly\", max_entries: 100 }` |\n| `amba_leaderboards_list` | List. | `{ project_id }` |\n| `amba_leaderboards_get` | Read the current top entries. | `{ project_id, leaderboard_id, limit: 50 }` |\n| `amba_leaderboards_get_definition` | Read the definition without the entries (cheap). | `{ project_id, leaderboard_id }` |\n| `amba_leaderboards_update` | Edit. | `{ project_id, leaderboard_id, max_entries: 250 }` |\n| `amba_leaderboards_delete` | Delete. | `{ project_id, leaderboard_id }` |\n\nMetrics: `xp`, `streak`, `custom`. For `custom`, pass `custom_event` (e.g. `\"workout_completed\"`). Periods: `all_time`, `daily`, `weekly`, `monthly`.\n\n### Challenges\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_challenges_create` | Define a time-bounded challenge with a goal + reward. | `{ project_id, name: \"Spring Sprint\", goal_event: \"workout_completed\", goal_count: 5, starts_at: \"2026-06-01T00:00:00Z\", ends_at: \"2026-06-08T00:00:00Z\", reward: { xp: 500, currency: { code: \"gems\", amount: 50 } } }` |\n| `amba_challenges_list` | List. | `{ project_id }` |\n| `amba_challenges_get` | Read. | `{ project_id, challenge_id }` |\n| `amba_challenges_update` | Edit. | `{ project_id, challenge_id, ends_at: \"...\" }` |\n| `amba_challenges_delete` | Delete. | `{ project_id, challenge_id }` |\n| `amba_challenges_list_participants` | List opt-in participants + progress. | `{ project_id, challenge_id, limit: 100 }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first — see `amba://setup/identity`. The snippets below show only the gamification calls.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo'; // or '@layers/amba-react-native'\n\n// 1. Track the event that drives XP / achievements / streaks / leaderboards.\nawait Amba.events.track('workout_completed', { duration_minutes: 30 });\n\n// 2. Qualify the streak.\nconst streak = await Amba.streaks.qualify('daily_workout');\n\n// 3. Show newly-unlocked achievements.\nconst progress = await Amba.achievements.getProgress();\n\n// 4. Show current XP balance.\nconst xp = await Amba.xp.getBalance();\n\n// 5. Read the leaderboard.\nconst entries = await Amba.leaderboards.getEntries('Weekly XP', 50);\nconst myRank = await Amba.leaderboards.getMyRank('Weekly XP');\n\n// 6. Active challenges.\nconst active = await Amba.challenges.getActive();\nfor (const c of active) {\n const p = await Amba.challenges.getProgress(c.id);\n if (p.completed && !p.claimed) await Amba.challenges.claim(c.id);\n}\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nawait Amba.events.track('lesson_completed', { course_id: 'algebra-1' });\nconst streak = await Amba.streaks.qualify('daily_lesson');\nconst xp = await Amba.xp.getBalance();\nconst top = await Amba.leaderboards.getEntries('Weekly XP', 100);\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\ntry await Amba.events.track(\"workout_completed\", properties: [\"duration_minutes\": 30])\nlet streak = try await Amba.streaks.qualify(streakKey: \"daily_workout\")\nlet progress = try await Amba.achievements.getProgress()\nlet xp = try await Amba.xp.getBalance()\nlet entries = try await Amba.leaderboards.getEntries(key: \"Weekly XP\", limit: 50)\nlet active = try await Amba.challenges.getActive()\n```\n\n### Android (Kotlin)\n\n```kotlin\nAmba.events.track(\"workout_completed\", mapOf(\"duration_minutes\" to 30))\nval streak = Amba.streaks.qualify(\"daily_workout\")\nval xp = Amba.xp.getBalance()\nval entries = Amba.leaderboards.getEntries(\"Weekly XP\", limit = 50)\nval active = Amba.challenges.getActive()\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nawait Amba.events.track('workout_completed', {'duration_minutes': 30});\nfinal streak = await Amba.streaks.qualify('daily_workout');\nfinal xp = await Amba.xp.getBalance();\nfinal entries = await Amba.leaderboards.getEntries('Weekly XP', limit: 50);\nfinal active = await Amba.challenges.getActive();\n```\n\n## Common follow-ups\n\nBatch into one or two questions.\n\n1. **What's the qualifying event for XP / achievements / streaks?** Most preset answers below — accept the suggestion or override:\n - fitness: `workout_completed`\n - education: `lesson_completed`\n - game: `level_completed` (or `match_played`)\n - productivity: `task_completed`\n - content_creator: `post_published`\n - ai_chatbot: `prompt_sent`\n\n2. **Default XP per qualifying event?** (defaults to `50`)\n\n3. **Streak period?**\n - Daily (recommended)\n - Weekly\n - None (skip streaks)\n\n4. **Streak freezes?**\n - 3 freezes/month (recommended — covers the occasional missed day)\n - Hard mode (no freezes)\n - None — don't enable freezes\n\n5. **Leaderboard scope:** (multi-select)\n - [x] Weekly (recommended — rotates, never gets stale)\n - [ ] All-time\n - [ ] Daily (high-engagement apps only)\n - [ ] Monthly\n\n6. **Which achievements to seed?** Defaults per preset (offer to create, or skip). For fitness: `first_workout`, `week_warrior`, `century_club`. For education: `first_lesson`, `dedicated_learner`, `course_complete`. For game: `first_win`, `streak_master`, `level_50`. For productivity: `first_task`, `inbox_zero`, `monthly_warrior`. For other presets, ask the user to confirm 3 achievements they want or skip.\n\n7. **Challenges:** seed an example weekly challenge?\n - Yes — \"5 workouts this week\" (or equivalent for the preset) ending next Sunday\n - No\n\n## Re-run behavior\n\n1. Before creating anything, list-then-diff:\n - `amba_xp_rules_list` — match on `name`. If exists, ask to update (`amba_xp_update_rule`) or skip.\n - `amba_achievements_list` — match on `key`. Achievement `key` is unique per project; collision → skip or update.\n - `amba_streaks_list` — match on `key`. **Never recreate** a streak with an existing key — the SDK calls would silently target a stale definition. Update instead.\n - `amba_leaderboards_list` — match on `name`. Collision → ask.\n - `amba_challenges_list` — match on `name + starts_at`.\n\n2. In the entry file: detect existing `Amba.events.track('<event_name>')` calls. If they already exist, don't add another. If they're missing for an event tied to a new XP rule, add a sample comment showing where to call `Amba.events.track`.\n\n3. If the user asks to \"remove gamification\": offer a soft path — pause XP rules and unschedule challenges rather than deleting definitions (deletion is irreversible and loses historical user XP / unlocks).\n";
|
|
73
|
+
readonly surface: "gamification";
|
|
74
|
+
readonly title: "Amba setup — gamification";
|
|
75
|
+
readonly description: string;
|
|
76
|
+
}, {
|
|
77
|
+
readonly name: "amba-setup-economy";
|
|
78
|
+
readonly uri: "amba://setup/economy";
|
|
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_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
|
+
readonly surface: "economy";
|
|
82
|
+
readonly title: "Amba setup — economy";
|
|
83
|
+
readonly description: string;
|
|
84
|
+
}, {
|
|
85
|
+
readonly name: "amba-setup-social";
|
|
86
|
+
readonly uri: "amba://setup/social";
|
|
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_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
|
+
readonly surface: "social";
|
|
90
|
+
readonly title: "Amba setup — social";
|
|
91
|
+
readonly description: string;
|
|
92
|
+
}, {
|
|
93
|
+
readonly name: "amba-setup-infrastructure";
|
|
94
|
+
readonly uri: "amba://setup/infrastructure";
|
|
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";
|
|
97
|
+
readonly surface: "infrastructure";
|
|
98
|
+
readonly title: "Amba setup — infrastructure";
|
|
99
|
+
readonly description: string;
|
|
100
|
+
}];
|
|
101
|
+
/**
|
|
102
|
+
* Register every Amba MCP resource against the given server.
|
|
103
|
+
*
|
|
104
|
+
* Idempotent in the sense that a fresh `McpServer` should only ever
|
|
105
|
+
* have this called against it once — the SDK throws on duplicate URI
|
|
106
|
+
* registrations.
|
|
107
|
+
*/
|
|
108
|
+
export declare function registerAllResources(server: McpServer): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slim re-export for consumers that only want the prompt constants and
|
|
3
|
+
* not the `registerAllResources` wiring (which pulls in
|
|
4
|
+
* `@modelcontextprotocol/sdk`).
|
|
5
|
+
*
|
|
6
|
+
* Used by `@layers/amba` (the CLI) to inline `EXPO_BUILD_PROMPT_MD`
|
|
7
|
+
* into the published `/amba-build` skill without dragging the MCP SDK
|
|
8
|
+
* into the CLI bundle. tsdown's `noExternal: [/^@layers\\/amba-/]` rule
|
|
9
|
+
* inlines this module's content directly; the constants are plain
|
|
10
|
+
* strings so treeshaking can fully prune everything else.
|
|
11
|
+
*/
|
|
12
|
+
export { EXPO_BUILD_PROMPT_MD, EXPO_BUILD_PROMPT_MIME, EXPO_BUILD_PROMPT_URI, } from './expo-build-prompt.js';
|
|
13
|
+
export { AMBA_SETUP_GUIDE_MD, AMBA_SETUP_GUIDE_MIME, AMBA_SETUP_GUIDE_URI, AMBA_SETUP_GUIDE_BODY_VERSION, } from './amba-setup.js';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as AMBA_SETUP_GUIDE_MD, i as AMBA_SETUP_GUIDE_BODY_VERSION, n as EXPO_BUILD_PROMPT_MIME, o as AMBA_SETUP_GUIDE_MIME, r as EXPO_BUILD_PROMPT_URI, s as AMBA_SETUP_GUIDE_URI, t as EXPO_BUILD_PROMPT_MD } from "../expo-build-prompt.js";
|
|
2
|
+
export { AMBA_SETUP_GUIDE_BODY_VERSION, AMBA_SETUP_GUIDE_MD, AMBA_SETUP_GUIDE_MIME, AMBA_SETUP_GUIDE_URI, EXPO_BUILD_PROMPT_MD, EXPO_BUILD_PROMPT_MIME, EXPO_BUILD_PROMPT_URI };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool rename map — DX-12 single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* Keys are the LEGACY tool names that existed pre-normalization. Values
|
|
5
|
+
* are the canonical `amba_<resource>_<verb>` names registered as the
|
|
6
|
+
* "real" tool. The legacy names are still registered as aliases (with a
|
|
7
|
+
* one-shot deprecation warning) so existing agent configs keep working
|
|
8
|
+
* during migration.
|
|
9
|
+
*
|
|
10
|
+
* This file is the test fixture used by `tool-naming.test.ts` to assert
|
|
11
|
+
* every old → new pair is wired correctly. It is NOT consumed by the
|
|
12
|
+
* runtime registrations — those carry their aliases inline at the
|
|
13
|
+
* `registerTool(...)` call site so the alias is co-located with the
|
|
14
|
+
* tool definition (and there's no risk of a stale map drifting away
|
|
15
|
+
* from the live registry).
|
|
16
|
+
*/
|
|
17
|
+
export declare const RENAME_MAP: Readonly<Record<string, string>>;
|
|
18
|
+
/** Inverse — canonical name → legacy alias. Used for tests. */
|
|
19
|
+
export declare const CANONICAL_TO_ALIAS: Readonly<Record<string, string>>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lower-level pat-aware HTTP helper for the dev-tooling MCP tools
|
|
3
|
+
* (functions / sites / secrets / ai-prompts-admin).
|
|
4
|
+
*
|
|
5
|
+
* `registerTool` in `../lib/with-pat.ts` is the canonical pat-arg helper
|
|
6
|
+
* used by every MCP tool — it handles schema injection, the
|
|
7
|
+
* `args.pat ?? inbound-Bearer` resolution, and the `MISSING_PAT`
|
|
8
|
+
* short-circuit. After consolidation in #36 + #38, the dev-tooling tools
|
|
9
|
+
* call `registerTool` for the outer wrapping and then call `callWithPat`
|
|
10
|
+
* below to issue the actual HTTP request.
|
|
11
|
+
*
|
|
12
|
+
* Why this exists alongside `registerTool`: the basic `ApiClient`
|
|
13
|
+
* methods (`get`, `post`, …) don't support multipart `formData` (which
|
|
14
|
+
* `amba_functions_deploy` + `amba_sites_deploy` need for tarball uploads)
|
|
15
|
+
* and don't expose query strings on POST/PUT/PATCH/DELETE (which
|
|
16
|
+
* `amba_secrets_delete` needs for `?function=`). `callWithPat` handles
|
|
17
|
+
* both, plus the 204-No-Content shape some endpoints return on DELETE.
|
|
18
|
+
*
|
|
19
|
+
* Resolution order (now redundant with `registerTool`'s but kept
|
|
20
|
+
* defensively in case a caller bypasses the helper):
|
|
21
|
+
* 1. Explicit `pat` arg → rawFetch with that bearer.
|
|
22
|
+
* 2. Otherwise → delegate to the bound `ApiClient`, which uses its
|
|
23
|
+
* tokenProvider (the inbound Bearer in hosted-MCP, the
|
|
24
|
+
* credentials-file fallback in CLI scripts).
|
|
25
|
+
*/
|
|
26
|
+
import { AmbaApiError, type ApiClient } from '../api-client.js';
|
|
27
|
+
export { AmbaApiError };
|
|
28
|
+
export interface PatCallOptions {
|
|
29
|
+
/** Query string parameters appended to the path. */
|
|
30
|
+
query?: Record<string, string>;
|
|
31
|
+
/** JSON body. Mutually exclusive with `formData`. */
|
|
32
|
+
body?: unknown;
|
|
33
|
+
/** Multipart FormData body. Mutually exclusive with `body`. */
|
|
34
|
+
formData?: FormData;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Issue an HTTP request against the admin API using either an
|
|
38
|
+
* inline `pat` (if provided) or the bound ApiClient's default token
|
|
39
|
+
* provider. Returns the parsed JSON body; throws on non-2xx (matching
|
|
40
|
+
* the existing `ApiClient.request` contract — the error message
|
|
41
|
+
* surfaces the upstream `error.message` envelope when available).
|
|
42
|
+
*
|
|
43
|
+
* Why this isn't just `apiClient.post(...)`: when `pat` is set we want
|
|
44
|
+
* to bypass the ApiClient's bound tokenProvider entirely so the
|
|
45
|
+
* downstream API call uses the inline PAT as its Bearer. Constructing
|
|
46
|
+
* a fresh ApiClient per call works but allocates needlessly; a
|
|
47
|
+
* raw `fetch` is simpler and matches the auth tools' pattern.
|
|
48
|
+
*
|
|
49
|
+
* For multipart payloads (`formData`), Content-Type is intentionally
|
|
50
|
+
* omitted so undici can set the boundary header.
|
|
51
|
+
*/
|
|
52
|
+
export declare function callWithPat<T = unknown>(apiClient: ApiClient, pat: string | undefined, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path: string, options?: PatCallOptions): Promise<T>;
|
|
53
|
+
/**
|
|
54
|
+
* Helper that wraps a JSON result in the MCP tool response shape.
|
|
55
|
+
* Identical to the inline `{content:[{type:'text',text:JSON.stringify(...)}]}`
|
|
56
|
+
* scattered across tool files; centralising makes the new modules
|
|
57
|
+
* grep-friendly and uniform.
|
|
58
|
+
*/
|
|
59
|
+
export declare function jsonResult(payload: unknown): {
|
|
60
|
+
content: {
|
|
61
|
+
type: 'text';
|
|
62
|
+
text: string;
|
|
63
|
+
}[];
|
|
64
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin AI prompt MCP tools (#38).
|
|
3
|
+
*
|
|
4
|
+
* Wraps `/admin/projects/:p/ai/prompts/*` + `/messages`. These are the
|
|
5
|
+
* developer-facing prompt-template operations: register a prompt with
|
|
6
|
+
* a system prompt + model + provider, version it on update, and
|
|
7
|
+
* server-side invoke it for testing.
|
|
8
|
+
*
|
|
9
|
+
* Customer-side context: prompts are versioned per (project, name).
|
|
10
|
+
* `amba_ai_prompts_create` and `amba_ai_prompts_update` both hit the
|
|
11
|
+
* same upsert endpoint — create yields version=1, update bumps to
|
|
12
|
+
* version=N+1 on conflict. Two tools rather than one because the
|
|
13
|
+
* agent-facing intent is different (create vs revise); the same API
|
|
14
|
+
* verb under the hood is fine.
|
|
15
|
+
*
|
|
16
|
+
* `amba_ai_prompts_invoke` is for testing. It looks up the registered
|
|
17
|
+
* prompt's provider + model + system_prompt, then routes the request
|
|
18
|
+
* through the admin `/messages` passthrough with the developer's
|
|
19
|
+
* identity stamped onto the proxy headers. The agent provides the
|
|
20
|
+
* `messages` array (Anthropic/OpenAI shape) explicitly so this stays
|
|
21
|
+
* a thin pass-through and doesn't try to guess the right Messages
|
|
22
|
+
* API shape per provider.
|
|
23
|
+
*
|
|
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.
|
|
28
|
+
*
|
|
29
|
+
* Authentication: every tool accepts an optional inline `pat` arg via the
|
|
30
|
+
* `registerTool` helper in `../lib/with-pat.ts`. Resolution: `args.pat ??
|
|
31
|
+
* inbound-Authorization-Bearer`, with `MISSING_PAT` short-circuit if neither.
|
|
32
|
+
*/
|
|
33
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
34
|
+
import type { ApiClient } from '../api-client.js';
|
|
35
|
+
export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
|