@layers/amba-mcp 1.0.1 → 4.0.2

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.
@@ -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_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_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\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_collections_create` | Create a typed collection. | `{ project_id, name: \"todos\", columns: [{ name: \"title\", type: \"text\", nullable: false }, { name: \"done\", type: \"boolean\", nullable: false, default: false }, { name: \"due_at\", type: \"timestamptz\", nullable: true }] }` |\n| `amba_collections_list` | List collections in this project. | `{ project_id }` |\n| `amba_collections_get` | Read one collection's schema. | `{ project_id, collection_name: \"todos\" }` |\n| `amba_collections_alter` | Add / drop columns, add / drop indexes. | `{ project_id, collection_name: \"todos\", add_columns: [{ name: \"priority\", type: \"int\", nullable: true }] }` |\n| `amba_collections_delete` | Drop the table (destructive). | `{ project_id, collection_name }` |\n| `amba_admin_insert_row` | Insert a row as the developer (bypasses user-scope). | `{ project_id, collection: \"todos\", row: { title: \"Sample\", done: false } }` |\n| `amba_admin_list_rows` | Read rows as the developer. | `{ project_id, collection: \"todos\", limit: 100 }` |\n| `amba_client_insert_row` | Insert as an end-user. Requires `api_key` + `session_token`. | `{ project_id, api_key, session_token, collection: \"todos\", row: {...} }` |\n| `amba_client_list_rows` | Read as an end-user (auto user-scoped). | `{ project_id, api_key, session_token, collection: \"todos\" }` |\n| `amba_client_get_row` | Get one row by id (end-user). | `{ project_id, api_key, session_token, collection, row_id }` |\n| `amba_client_update_row` | Update one row (end-user). | `{ project_id, api_key, session_token, collection, row_id, patch: {...} }` |\n| `amba_client_delete_row` | Delete one row (end-user). | `{ project_id, api_key, session_token, collection, row_id }` |\n| `amba_client_count_rows` | Count rows matching a filter. | `{ project_id, api_key, session_token, collection, filter: {...} }` |\n| `amba_client_find_rows` | Filter / sort / paginate rows. | `{ project_id, api_key, session_token, collection, filter: {...}, order_by: [...], limit: 50 }` |\n| `amba_client_find_nearest_rows` | Vector-similarity search (rows with a `vector(<dim>)` column). | `{ project_id, api_key, session_token, collection, vector_column: \"embedding\", query_vector: [...], k: 10 }` |\n\nColumn types: `text`, `int`, `bigint`, `float`, `boolean`, `timestamptz`, `date`, `json`, `jsonb`, `uuid`, `vector(<dim>)` (e.g. `vector(1536)` for OpenAI embeddings).\n\n### Functions (serverless code)\n\nRun user code in a sandbox triggered by HTTP, cron, or webhook. The function gets the tenant connection automatically via injected env.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_functions_deploy` | Deploy a function from source. | `{ project_id, name: \"send_welcome_email\", runtime: \"node22\", source: \"export default async (req) => { ... }\", trigger: { type: \"http\" } }` |\n| `amba_functions_list` | List functions. | `{ project_id }` |\n| `amba_functions_get` | Read function metadata. | `{ project_id, function_id }` |\n| `amba_functions_get_logs` | Recent invocation logs. | `{ project_id, function_id, limit: 100 }` |\n| `amba_functions_delete` | Delete a function. | `{ project_id, function_id }` |\n| `amba_functions_schedule` | Attach a cron schedule. | `{ project_id, function_id, cron: \"0 9 * * *\", timezone: \"America/Los_Angeles\" }` |\n| `amba_functions_pause_schedule` | Pause a scheduled trigger without deleting it. | `{ project_id, function_id }` |\n| `amba_functions_resume_schedule` | Resume. | `{ project_id, function_id }` |\n| `amba_functions_trigger_schedule` | Fire a scheduled function ad-hoc (testing). | `{ project_id, function_id }` |\n\n### AI prompts\n\nManaged LLM templates: stored prompt with model + system message + variables, callable by name from the SDK. The actual LLM call is rewritten per-tenant — the customer's API keys (Anthropic / OpenAI) live in the tenant secrets, never on the device.\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_ai_prompts_create` | Create a prompt template. | `{ project_id, key: \"summarize\", model: \"claude-opus-4-5\", system: \"Summarize the user's text in 2 sentences.\", variables: [\"text\"] }` |\n| `amba_ai_prompts_list` | List prompts. | `{ project_id }` |\n| `amba_ai_prompts_get` | Read one prompt. | `{ project_id, key }` |\n| `amba_ai_prompts_update` | Edit a prompt. | `{ project_id, key, system: \"...\" }` |\n| `amba_ai_prompts_invoke` | Invoke a prompt server-side (admin testing). | `{ project_id, key, variables: { text: \"...\" } }` |\n| `amba_ai_prompts_delete` | Delete. | `{ project_id, key }` |\n\n### Analytics + events + sessions\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_analytics_get` | Top-level metrics dashboard (MAU, DAU, retention). | `{ project_id, period: \"7d\" }` |\n| `amba_events_list` | Browse raw events. | `{ project_id, limit: 100, since: \"2026-05-19T00:00:00Z\" }` |\n| `amba_events_count` | Count events matching a filter. | `{ project_id, event: \"workout_completed\", since: \"...\" }` |\n| `amba_sessions_list` | List user sessions. | `{ project_id, limit: 50 }` |\n| `amba_sessions_analytics` | Session-level metrics. | `{ project_id, period: \"7d\" }` |\n| `amba_users_list_events` | Per-user event history. | `{ project_id, user_id }` |\n| `amba_users_export` | Export the full user list. | `{ project_id, format: \"csv\" }` |\n\n### Secrets + configs + integrations\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_secrets_set` | Set a tenant secret (encrypted at rest). | `{ project_id, name: \"OPENAI_API_KEY\", value: \"sk-...\" }` |\n| `amba_secrets_get` | Read a secret (returns `\"<redacted>\"` unless explicitly requested). | `{ project_id, name }` |\n| `amba_secrets_list` | List secret names. | `{ project_id }` |\n| `amba_secrets_delete` | Delete. | `{ project_id, name }` |\n| `amba_configs_create` | Create a runtime config value (read from SDK as `Amba.config.fetch()`). | `{ project_id, key: \"primary_color\", value: \"#ff0066\", segment_id: null }` |\n| `amba_configs_list` | List configs. | `{ project_id }` |\n| `amba_configs_update` | Edit. | `{ project_id, config_id, value: \"...\" }` |\n| `amba_configs_delete` | Delete. | `{ project_id, config_id }` |\n| `amba_integrations_list` | List third-party integrations. | `{ project_id }` |\n| `amba_integrations_configure` | Configure a provider. | `{ project_id, provider: \"revenuecat\", config: { webhook_secret: \"...\", default_offering: \"...\" } }` |\n| `amba_integrations_set` | Set/replace integration config wholesale. | `{ project_id, provider, config }` |\n| `amba_integrations_patch` | Patch one field. | `{ project_id, provider, patch: { webhook_secret: \"...\" } }` |\n| `amba_integrations_test` | Send a test event to a configured provider. | `{ project_id, provider }` |\n\n### Media (file storage + CDN)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_media_upload` | Upload a file (returns a tenant-scoped URL). | `{ project_id, name: \"logo.png\", content_type: \"image/png\", data: \"<base64>\" }` |\n| `amba_media_list` | List files. | `{ project_id, folder: \"/\", limit: 100 }` |\n| `amba_media_delete` | Delete a file. | `{ project_id, file_id }` |\n| `amba_media_create_folder` | Create a logical folder. | `{ project_id, path: \"/uploads/avatars\" }` |\n| `amba_media_list_folders` | List folders. | `{ project_id }` |\n| `amba_media_delete_folder` | Delete a folder (must be empty). | `{ project_id, path }` |\n\n### Sites (static asset hosting)\n\n| Tool | Purpose | Example args |\n| --- | --- | --- |\n| `amba_sites_deploy` | Deploy a static site bundle (zip / tar). | `{ project_id, name: \"marketing\", bundle: \"<base64>\", index: \"index.html\" }` |\n| `amba_sites_list` | List sites. | `{ project_id }` |\n| `amba_sites_get` | Read a site. | `{ project_id, site_id }` |\n| `amba_sites_add_domain` | Attach a custom domain. | `{ project_id, site_id, domain: \"marketing.example.com\" }` |\n| `amba_sites_list_domains` | List domains on a site. | `{ project_id, site_id }` |\n| `amba_sites_remove_domain` | Detach a domain. | `{ project_id, site_id, domain }` |\n| `amba_sites_delete` | Delete a site. | `{ project_id, site_id }` |\n\n## SDK init per stack\n\n`Amba.configure(...)` runs first. The infrastructure surfaces — collections, AI, config, flags, events — are SDK-side reads; the snippets below show what the client calls look like.\n\n### Expo / React Native\n\n```tsx\nimport { Amba } from '@layers/amba-expo';\n\n// Collections — typed table, user-scoped reads + writes\ntype Todo = { id: string; title: string; done: boolean; created_at: string };\n\nconst { data: todos } = await Amba.collections.find<Todo>('todos', {\n filter: Amba.collections.where.eq('done', false),\n order: [{ column: 'created_at', direction: 'desc' }],\n limit: 50,\n});\n\nconst newTodo = await Amba.collections.insert('todos', { title: 'Ship the app', done: false });\nawait Amba.collections.update('todos', newTodo.id, { done: true });\nawait Amba.collections.delete('todos', newTodo.id);\n\n// AI — call a managed prompt\nconst response = await Amba.ai.anthropic.messages.create({\n prompt_key: 'summarize',\n variables: { text: 'A long article about backend services …' },\n});\n\n// Track an analytics event\nawait Amba.events.track('button_clicked', { button: 'cta' });\n\n// Read runtime config\nconst config = await Amba.config.fetch();\n\n// Read a feature flag\nconst showBeta = await Amba.flags.get('beta_feature');\n\n// Diagnostics — wire-verify\nconst ping = await Amba.diagnostics.ping();\nif (!ping.ok) console.error('Amba misconfigured:', ping);\n```\n\n### Web\n\n```ts\nimport { Amba } from '@layers/amba-web';\n\nconst { data: todos } = await Amba.collections.find('todos', {\n filter: Amba.collections.where.eq('done', false),\n limit: 50,\n});\nawait Amba.collections.insert('todos', { title: 'Ship', done: false });\nawait Amba.events.track('page_view', { path: location.pathname });\n```\n\nWith `@layers/amba-react`:\n\n```tsx\nimport { useCollection, useFlag } from '@layers/amba-react';\n\nfunction TodoList() {\n const { data: todos, loading, refetch } = useCollection<{ id: string; title: string }>('todos');\n const showArchive = useFlag('archive_todos');\n if (loading) return <Spinner />;\n return (\n <ul>\n {todos?.map(t => <li key={t.id}>{t.title}</li>)}\n {showArchive && <ArchiveButton onArchive={refetch} />}\n </ul>\n );\n}\n```\n\n### iOS (Swift)\n\n```swift\nimport Amba\n\nstruct Todo: Codable {\n let id: String\n let title: String\n let done: Bool\n}\n\nlet response = try await Amba.collections.find(\"todos\", as: Todo.self)\n_ = try await Amba.collections.insert(\"todos\", row: [\"title\": \"Ship\", \"done\": false])\n\nlet config = try await Amba.config.fetch()\nlet showBeta = try await Amba.flags.get(name: \"beta_feature\")\ntry await Amba.events.track(\"app_opened\", properties: [\"source\": \"deep_link\"])\n\nlet reply = try await Amba.ai.anthropic.messages.create(\n promptKey: \"summarize\",\n variables: [\"text\": \"A long article...\"]\n)\n```\n\n### Android (Kotlin)\n\n```kotlin\ndata class Todo(val id: String, val title: String, val done: Boolean)\n\nval todos = Amba.collections.find<Todo>(\"todos\")\nAmba.collections.insert(\"todos\", mapOf(\"title\" to \"Ship\", \"done\" to false))\n\nval config = Amba.config.fetch()\nval showBeta = Amba.flags.get(\"beta_feature\")\nAmba.events.track(\"app_opened\", mapOf(\"source\" to \"deep_link\"))\n```\n\n### Flutter\n\n```dart\nimport 'package:amba/amba.dart';\n\nfinal response = await Amba.collections.find('todos', limit: 50);\nawait Amba.collections.insert('todos', {'title': 'Ship', 'done': false});\nfinal config = await Amba.config.fetch();\nfinal showBeta = await Amba.flags.get('beta_feature');\nawait Amba.events.track('app_opened', {'source': 'deep_link'});\n```\n\n## Common follow-ups\n\nBatch.\n\n1. **Custom data tables (collections):** any domain-specific tables to create?\n - Yes — I'll list them. (For each: name + columns + types.)\n - No, just use the canned Amba surfaces (auth, push, gamification, etc.)\n - Auto-create from the existing code's models — read `lib/models/`, `src/types/`, `Models/`, infer column lists, confirm with me.\n\n2. **Custom backend logic (functions):** any server-side code to deploy?\n - Yes — describe what it should do. (Then offer to scaffold a function template and deploy.)\n - No\n\n3. **AI features:** want managed LLM prompts?\n - Yes — what's the use case? (summarize, translate, classify, generate, custom)\n - No\n\n4. **Analytics:** which tracker do you want?\n - Only Amba's built-in events (recommended — already wired)\n - Amba + Mixpanel / PostHog / Segment forwarding (configure via `amba_integrations_configure`)\n - None (rarely useful — events drive XP / achievements / streaks; disabling cripples gamification)\n\n5. **Third-party integrations to set up:**\n - [ ] RevenueCat (IAP / subscriptions on iOS + Android)\n - [ ] Superwall (paywall A/B)\n - [ ] Resend (transactional email)\n - [ ] Stripe (web payments / subscriptions)\n - [ ] Mixpanel / PostHog / Segment (analytics forwarding)\n - [ ] OpenAI / Anthropic (LLM keys — required for `Amba.ai.*` calls)\n\n6. **Feature flags:** seed any starter flags?\n - Yes — wire `beta_feature` (off by default) so I can ship the wiring before the feature exists\n - No\n\n7. **Static site:** want a marketing page hosted under your tenant subdomain?\n - Yes — scaffold and deploy a 1-page index\n - No\n\n## Re-run behavior\n\n1. Before creating:\n - `amba_collections_list` — match on `name`. Collisions: never silently recreate (data loss). Offer `amba_collections_alter` to add new columns instead.\n - `amba_functions_list` — match on `name`. Collisions: ask to redeploy (with the new source) or skip.\n - `amba_ai_prompts_list` — match on `key`. Same.\n - `amba_integrations_list` — match on `provider`. Same.\n - `amba_configs_list` — match on `key`. Same.\n\n2. **Never call `amba_collections_delete` on re-run unless the user explicitly asks** — this drops the underlying table and every row in it across every user of the tenant.\n\n3. For functions: re-deploying replaces source in place (versioned server-side). It's safe to call `amba_functions_deploy` with the same name + new source.\n\n4. For integrations: if a provider is already configured, prefer `amba_integrations_patch` (partial update) over `amba_integrations_set` (full replace).\n\n5. Secrets: don't list secret values in chat output, even on read. Just confirm \"OPENAI_API_KEY is set\" / \"not set\".\n";
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;
@@ -16,18 +16,41 @@
16
16
  * server's public-tools allowlist lets signup/login/refresh through
17
17
  * unauthenticated.
18
18
  * 2. The signup response includes:
19
- * - `pat` — a long-lived Personal Access Token. Use the
20
- * PAT for agent flows: pass it as the inbound
21
- * Bearer for every subsequent MCP call. Survives
22
- * until rotated.
23
- * - `project` — a real isolated Neon-backed project provisioning
24
- * asynchronously. Includes project_id, client_key,
19
+ * - `pat` — a long-lived Personal Access Token.
20
+ * - `project` — a real isolated Amba project (provisioning
21
+ * asynchronously). Includes project_id, client_key,
25
22
  * server_key, provisioning_status, and verify_url.
26
- * Poll `amba_get_provisioning_status` until the
23
+ * Poll `amba_projects_get_provisioning_status` until the
27
24
  * workflow completes (~10s) before issuing
28
25
  * client-plane traffic.
29
- * 3. Re-issue subsequent MCP calls with `Authorization: Bearer <pat>`.
30
- * All downstream tools (`amba_create_project`, etc.) now work.
26
+ * - `mcp_config` — ready-to-paste config snippets for Claude Code,
27
+ * Cursor, and Windsurf. Each entry carries a
28
+ * `path_hint` (where the file usually lives) and a
29
+ * `snippet` (the JSON to merge into that file under
30
+ * `mcpServers.amba`), with the PAT pre-baked into
31
+ * the Bearer header.
32
+ * - `agent_instructions` — short prose telling the calling agent
33
+ * exactly what to do: keep calling tools in this
34
+ * same session with the freshly-minted `pat` arg,
35
+ * and write the snippet to the customer's MCP
36
+ * config for future sessions.
37
+ * 3. Same-session: the PAT is already in hand. Pass it as `pat` on every
38
+ * subsequent `amba_*` tool call this session — `packages/mcp/src/lib/with-pat.ts`
39
+ * injects a `pat` argument on every non-public tool that overrides
40
+ * the inbound Bearer for that one call. No restart, no waiting.
41
+ * 4. Future sessions: the calling agent writes the matching snippet to
42
+ * the customer's MCP client config file (Claude Code: `~/.claude.json`
43
+ * or project-local `.mcp.json`; Cursor: `~/.cursor/mcp.json` or
44
+ * `.cursor/mcp.json`; Windsurf: `~/.codeium/windsurf/mcp_config.json`).
45
+ * Merge with any existing `mcpServers` block rather than overwriting.
46
+ * The next time the MCP client starts, the static
47
+ * `Authorization: Bearer <pat>` header takes over automatically — the
48
+ * `pat` arg becomes optional. The customer does nothing.
49
+ *
50
+ * Browser-based MCP clients (Claude.ai web) cannot read a static config
51
+ * file — they discover the OAuth authorization server instead. For those
52
+ * clients the agent should direct the customer to https://mcp.amba.dev/authorize
53
+ * and complete the OAuth 2.1 + PKCE handshake.
31
54
  *
32
55
  * The PAT can be rotated via `amba_developer_rotate_pat`. The old PAT
33
56
  * stops working immediately on rotation (may take up to 30s to propagate).
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Per-project billing tools — the "pricing-as-API" unlock for agentic
3
+ * onboarding. An AI agent provisioning new features should be able to ask
4
+ * the platform what tier the project is on, how much headroom is left on
5
+ * each metered axis, and what (if anything) needs a human in the loop
6
+ * before the next step.
7
+ *
8
+ * Three tools land here:
9
+ *
10
+ * - `amba_billing_status` — live per-project state (call before
11
+ * provisioning data-heavy workloads).
12
+ * - `amba_billing_tiers` — static catalog of tiers + overage rates
13
+ * (hardcoded so reasoning works offline
14
+ * and stays stable when the API is down).
15
+ * - `amba_billing_set_ceiling` — write-side: cap the monthly bill or
16
+ * remove the cap. Surface to the human
17
+ * before calling.
18
+ *
19
+ * The status + set-ceiling tools call REST endpoints that W2-B is
20
+ * shipping in parallel (`GET/PUT /v1/admin/projects/:id/billing/...`).
21
+ * If those endpoints aren't live yet the calls will surface as 404s
22
+ * via the standard `AmbaApiError` path — no special-casing here.
23
+ *
24
+ * No vendor leakage: field names + descriptions stay in Amba's
25
+ * customer-facing vocabulary (tier, headroom, paused_at, spend_ceiling).
26
+ */
27
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
28
+ import type { ApiClient } from '../api-client.js';
29
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Collection MCP tools — admin (schema + admin-side row CRUD) and
3
+ * client (end-user row CRUD with auto-RLS) surfaces wrapped 1:1 from
4
+ * `apps/api/src/routes/admin/collections.ts`,
5
+ * `apps/api/src/routes/admin/collection-rows.ts`, and
6
+ * `apps/api/src/routes/client/collections.ts`.
7
+ *
8
+ * Two distinct authentication models split the toolset:
9
+ *
10
+ * - **Admin tools** (`amba_collections_create`, `amba_collections_alter`,
11
+ * `amba_admin_insert_row`, etc.) authenticate the **developer/agent**
12
+ * and accept an optional `pat` argument. When omitted, the handler
13
+ * falls back to the inbound Bearer the hosted MCP server forwards
14
+ * into `ApiClient` (or the CLI's `~/.amba/credentials.json`). Admin
15
+ * operations bypass auto-RLS — the developer sees / mutates every
16
+ * row regardless of `user_id`.
17
+ *
18
+ * - **Client tools** (`amba_client_*`) authenticate the **end-user**
19
+ * and require `api_key` (the project's client X-Api-Key) plus an
20
+ * optional `session_token` (the end-user's session Bearer). The
21
+ * server enforces auto-RLS — every read/write is scoped to the
22
+ * signed-in app_user, no opt-out. Most client tools require a
23
+ * `session_token` because the route's `clientSessionAuth` middleware
24
+ * demands one.
25
+ *
26
+ * Wire shape mirrors the route handlers exactly. See the route source
27
+ * for the authoritative contract — these tools are a thin pass-through
28
+ * so future route changes don't force an MCP rewrite.
29
+ *
30
+ * Pat-arg pattern: this file inlines the `pat ?? apiClient.resolveTokenOrNull()`
31
+ * dance described in `packages/mcp/src/lib/with-pat.ts` (task #36's
32
+ * central helper). When that helper lands, this file should migrate to
33
+ * `registerTool(...)` without changing tool semantics.
34
+ */
35
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
36
+ import type { ApiClient } from '../api-client.js';
37
+ export declare function registerTools(server: McpServer, apiClient: ApiClient): void;