@layers/amba-mcp 4.0.6 → 4.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto/collection-tools.d.ts +42 -20
- package/dist/auto/describe.d.ts +21 -3
- package/dist/auto/function-tools.d.ts +51 -0
- package/dist/auto/index.d.ts +65 -21
- package/dist/auto/json-schema-to-zod.d.ts +37 -0
- package/dist/expo-build-prompt.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1556 -401
- package/dist/resources/amba-setup-infrastructure.d.ts +5 -4
- package/dist/resources/amba-setup.d.ts +1 -1
- package/dist/resources/expo-build-prompt.d.ts +1 -1
- package/dist/resources/index.d.ts +1 -1
- package/dist/tools/affiliate.d.ts +12 -0
- package/dist/tools/agent-checkout.d.ts +17 -0
- package/dist/tools/ai-prompts-admin.d.ts +3 -2
- package/dist/tools/app-mcp.d.ts +22 -0
- package/dist/tools/domains.d.ts +2 -0
- package/dist/tools/orgs.d.ts +8 -0
- package/dist/tools/payments.d.ts +1 -0
- package/dist/tools/promotion.d.ts +5 -1
- package/dist/tools/secrets.d.ts +3 -4
- package/dist/tools/service-accounts.d.ts +10 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { INTEGRATION_PROVIDERS, integrationConfigSummary } from "@layers/amba-shared";
|
|
6
|
+
import { INTEGRATION_PROVIDERS, getBindingReservationReason, integrationConfigSummary } from "@layers/amba-shared";
|
|
7
7
|
//#region src/auth.ts
|
|
8
8
|
const CREDENTIALS_PATH = join(homedir(), ".amba", "credentials.json");
|
|
9
9
|
async function loadCredentials() {
|
|
@@ -316,7 +316,8 @@ const READ_VERBS = new Set([
|
|
|
316
316
|
"diff",
|
|
317
317
|
"drift",
|
|
318
318
|
"balance",
|
|
319
|
-
"payouts"
|
|
319
|
+
"payouts",
|
|
320
|
+
"capabilities"
|
|
320
321
|
]);
|
|
321
322
|
/**
|
|
322
323
|
* Destructive verbs. A tool whose name contains one of these MAY perform
|
|
@@ -329,7 +330,8 @@ const DESTRUCTIVE_VERBS = new Set([
|
|
|
329
330
|
"remove",
|
|
330
331
|
"revoke",
|
|
331
332
|
"drop",
|
|
332
|
-
"reset"
|
|
333
|
+
"reset",
|
|
334
|
+
"ban"
|
|
333
335
|
]);
|
|
334
336
|
/**
|
|
335
337
|
* Write verbs — mutations that are additive / non-destructive (create a
|
|
@@ -351,6 +353,7 @@ const WRITE_VERBS = new Set([
|
|
|
351
353
|
"deploy",
|
|
352
354
|
"send",
|
|
353
355
|
"add",
|
|
356
|
+
"attach",
|
|
354
357
|
"configure",
|
|
355
358
|
"invoke",
|
|
356
359
|
"schedule",
|
|
@@ -358,6 +361,8 @@ const WRITE_VERBS = new Set([
|
|
|
358
361
|
"upload",
|
|
359
362
|
"rotate",
|
|
360
363
|
"reprovision",
|
|
364
|
+
"provision",
|
|
365
|
+
"payout",
|
|
361
366
|
"transfer",
|
|
362
367
|
"pause",
|
|
363
368
|
"resume",
|
|
@@ -376,12 +381,20 @@ const WRITE_VERBS = new Set([
|
|
|
376
381
|
"define",
|
|
377
382
|
"map",
|
|
378
383
|
"adopt",
|
|
384
|
+
"apply",
|
|
379
385
|
"purchase",
|
|
380
386
|
"signup",
|
|
381
387
|
"login",
|
|
382
388
|
"refresh",
|
|
383
389
|
"track",
|
|
384
|
-
"charge"
|
|
390
|
+
"charge",
|
|
391
|
+
"refund",
|
|
392
|
+
"enable",
|
|
393
|
+
"accept",
|
|
394
|
+
"generate",
|
|
395
|
+
"enroll",
|
|
396
|
+
"request",
|
|
397
|
+
"clawback"
|
|
385
398
|
]);
|
|
386
399
|
/**
|
|
387
400
|
* Split a tool name into lowercase verb-candidate tokens, dropping the
|
|
@@ -445,7 +458,8 @@ const NOUN_LIKE_VERBS = new Set([
|
|
|
445
458
|
"schedule",
|
|
446
459
|
"me",
|
|
447
460
|
"balance",
|
|
448
|
-
"payouts"
|
|
461
|
+
"payouts",
|
|
462
|
+
"capabilities"
|
|
449
463
|
]);
|
|
450
464
|
/**
|
|
451
465
|
* The verbs eligible to be a title's leading word: every classification
|
|
@@ -636,15 +650,15 @@ function registerPublicTool(server, name, description, schema, handler, aliases
|
|
|
636
650
|
}
|
|
637
651
|
//#endregion
|
|
638
652
|
//#region src/tools/projects.ts
|
|
639
|
-
function registerTools$
|
|
640
|
-
registerTool(server, apiClient, "amba_projects_list", "List all Amba projects owned by the authenticated developer. Returns project id, name, bundle_id, platform, and
|
|
653
|
+
function registerTools$42(server, apiClient) {
|
|
654
|
+
registerTool(server, apiClient, "amba_projects_list", "List all Amba projects owned by the authenticated developer. Returns project id, name, bundle_id, platform, environment, the persisted billing `tier`, and `effective_tier` (the runtime entitlement tier; comped projects are effectively enterprise).", {}, async (_, { client }) => {
|
|
641
655
|
const result = await client.get("/projects");
|
|
642
656
|
return { content: [{
|
|
643
657
|
type: "text",
|
|
644
658
|
text: JSON.stringify(result, null, 2)
|
|
645
659
|
}] };
|
|
646
660
|
}, ["amba_list_projects"]);
|
|
647
|
-
registerTool(server, apiClient, "amba_projects_get", "Get detailed information about a specific Amba project by its ID, including API keys, integrations, and
|
|
661
|
+
registerTool(server, apiClient, "amba_projects_get", "Get detailed information about a specific Amba project by its ID, including API keys, integrations, configuration, persisted billing `tier`, and runtime `effective_tier`. Use `effective_tier` for quota/feature decisions; `tier` remains the stored subscription classification.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
648
662
|
const result = await client.get(`/projects/${project_id}`);
|
|
649
663
|
return { content: [{
|
|
650
664
|
type: "text",
|
|
@@ -743,7 +757,7 @@ function registerTools$37(server, apiClient) {
|
|
|
743
757
|
}
|
|
744
758
|
//#endregion
|
|
745
759
|
//#region src/tools/push.ts
|
|
746
|
-
function registerTools$
|
|
760
|
+
function registerTools$41(server, apiClient) {
|
|
747
761
|
registerTool(server, apiClient, "amba_push_campaigns_create", "Create a new push notification campaign for a project. The campaign starts in \"draft\" status. You can optionally target a segment and schedule delivery.", {
|
|
748
762
|
project_id: z.string().describe("The project ID"),
|
|
749
763
|
title: z.string().describe("Push notification title shown to the user"),
|
|
@@ -873,7 +887,7 @@ const segmentRulesSchema = z.object({
|
|
|
873
887
|
operator: z.enum(["AND", "OR"]).describe("Logical operator combining conditions"),
|
|
874
888
|
conditions: z.array(segmentConditionSchema).describe("Array of filter conditions")
|
|
875
889
|
});
|
|
876
|
-
function registerTools$
|
|
890
|
+
function registerTools$40(server, apiClient) {
|
|
877
891
|
registerTool(server, apiClient, "amba_segments_list", "List user segments for a project. Segments define groups of users based on rules (e.g. \"active in last 7 days\", \"premium users\"). Returns only project-custom segments by default. Pass include_system: true to also include the built-in system segments (e.g. \"all\", \"active_7d\", \"premium\").", {
|
|
878
892
|
project_id: z.string().describe("The project ID"),
|
|
879
893
|
include_system: z.boolean().optional().describe("Include built-in system segments in the result. Defaults to false.")
|
|
@@ -958,7 +972,7 @@ const configConditionSchema = z.object({
|
|
|
958
972
|
percentage: z.number().optional().describe("Percentage rollout (0-100)"),
|
|
959
973
|
value: z.unknown().describe("Override value for this condition")
|
|
960
974
|
});
|
|
961
|
-
function registerTools$
|
|
975
|
+
function registerTools$39(server, apiClient) {
|
|
962
976
|
registerTool(server, apiClient, "amba_remote_configs_list", "List remote-config keys for a project. Returns key, value, value_type, description, conditions, and version per active row. Remote config lets you change app behavior without deploying an update. Returns only project-created config keys by default. Pass include_system: true to also include platform-provided defaults (e.g. app_version_min, maintenance_mode).", {
|
|
963
977
|
project_id: z.string().describe("The project ID"),
|
|
964
978
|
include_system: z.boolean().optional().describe("Include platform-provided default config keys in the result. Defaults to false.")
|
|
@@ -1036,7 +1050,7 @@ const contentItemSchema = z.object({
|
|
|
1036
1050
|
metadata: z.record(z.unknown()).optional().describe("Arbitrary metadata key-value pairs"),
|
|
1037
1051
|
is_premium: z.boolean().optional().describe("Whether this content requires a premium entitlement")
|
|
1038
1052
|
});
|
|
1039
|
-
function registerTools$
|
|
1053
|
+
function registerTools$38(server, apiClient) {
|
|
1040
1054
|
registerTool(server, apiClient, "amba_content_libraries_create", "Create a content library for a project. A content library is a collection of content items (tips, quotes, articles) that can be delivered on a schedule to keep users engaged. The library `name` IS the channel handle the in-app SDK uses: `Amba.content.library(name)` reads this library (names are unique). The response echoes it as `channel`.", {
|
|
1041
1055
|
project_id: z.string().describe("The project ID"),
|
|
1042
1056
|
name: z.string().describe("Library name (e.g. \"Daily Motivation\", \"Workout Tips\")"),
|
|
@@ -1254,7 +1268,7 @@ function registerTools$33(server, apiClient) {
|
|
|
1254
1268
|
}
|
|
1255
1269
|
//#endregion
|
|
1256
1270
|
//#region src/tools/streaks.ts
|
|
1257
|
-
function registerTools$
|
|
1271
|
+
function registerTools$37(server, apiClient) {
|
|
1258
1272
|
registerTool(server, apiClient, "amba_streaks_create", "Create a streak definition for a project. Streaks track consecutive user engagement (e.g. daily logins, workout completions). Supports configurable periods, grace periods, and freeze mechanics.", {
|
|
1259
1273
|
project_id: z.string().describe("The project ID"),
|
|
1260
1274
|
key: z.string().regex(/^[a-z0-9_-]{1,64}$/).optional().describe("Stable identifier used by the SDK to qualify a streak without knowing its UUID: `Amba.streaks.qualify(\"daily_login\")`. Lowercase letters, digits, underscore, hyphen, 1-64 characters. Immutable after creation — changing it breaks live SDK calls. When omitted the server derives one from `name`."),
|
|
@@ -1346,8 +1360,8 @@ function registerTools$32(server, apiClient) {
|
|
|
1346
1360
|
//#endregion
|
|
1347
1361
|
//#region src/tools/integrations.ts
|
|
1348
1362
|
const providerEnum = z.enum(INTEGRATION_PROVIDERS);
|
|
1349
|
-
function registerTools$
|
|
1350
|
-
registerTool(server, apiClient, "amba_integrations_configure", "Configure (create or replace) a third-party integration for a project. Supported providers: \"apns\" (Apple Push), \"fcm\" (Firebase Cloud Messaging), \"revenuecat\" (subscription management), \"superwall\" (paywall management). Each provider requires specific config fields. Push credentials (.p8 / service-account JSON) are passed inline here and stored server-side automatically — no separate secrets step.", {
|
|
1363
|
+
function registerTools$36(server, apiClient) {
|
|
1364
|
+
registerTool(server, apiClient, "amba_integrations_configure", "Configure (create or replace) a third-party integration for a project. Supported providers: \"apns\" (Apple Push), \"fcm\" (Firebase Cloud Messaging), \"revenuecat\" (subscription management), \"superwall\" (paywall management), \"stripe_billing\" (web subscriptions through the app's own Stripe Billing account — web purchases grant the same Amba entitlements as mobile ones; the response includes the webhook_url to register in the Stripe dashboard), \"google_play\" (a Play Console service account with the Android Publisher permission — lets `amba_monetization_apply` create one-time Play products directly, price included). Each provider requires specific config fields. Push credentials (.p8 / service-account JSON) are passed inline here and stored server-side automatically — no separate secrets step.", {
|
|
1351
1365
|
project_id: z.string().describe("The project ID"),
|
|
1352
1366
|
provider: providerEnum.describe("Integration provider name"),
|
|
1353
1367
|
config: z.record(z.unknown()).describe("Provider-specific configuration. " + integrationConfigSummary())
|
|
@@ -1416,7 +1430,7 @@ const PERIOD_DAYS = {
|
|
|
1416
1430
|
"30d": "30",
|
|
1417
1431
|
"90d": "90"
|
|
1418
1432
|
};
|
|
1419
|
-
function registerTools$
|
|
1433
|
+
function registerTools$35(server, apiClient) {
|
|
1420
1434
|
registerTool(server, apiClient, "amba_analytics_get", "Get daily analytics for a project. Returns per-day event counts and unique-user counts for the requested window, plus totals. Use this to verify that end-to-end event tracking is working after seeding and testing.", {
|
|
1421
1435
|
project_id: z.string().describe("The project ID"),
|
|
1422
1436
|
period: z.enum([
|
|
@@ -1489,7 +1503,7 @@ function passthroughResult(result) {
|
|
|
1489
1503
|
}
|
|
1490
1504
|
//#endregion
|
|
1491
1505
|
//#region src/tools/users.ts
|
|
1492
|
-
function registerTools$
|
|
1506
|
+
function registerTools$34(server, apiClient) {
|
|
1493
1507
|
registerTool(server, apiClient, "amba_users_list", "List app users for a project. Supports pagination with offset and limit, plus optional search (matches email / external_id / display_name with ILIKE) and segment filter. Returns user profiles including external_id, email, display_name, properties, and activity timestamps.", {
|
|
1494
1508
|
project_id: z.string().describe("The project ID"),
|
|
1495
1509
|
limit: z.number().optional().describe("Maximum number of users to return (default 50)"),
|
|
@@ -1936,7 +1950,7 @@ final dailyLimit = await Amba.config.get<int>('daily_limit', defaultValue: 10);
|
|
|
1936
1950
|
final streak = await Amba.streaks.get('daily_login');
|
|
1937
1951
|
print('Current streak: \${streak.currentCount}');`
|
|
1938
1952
|
};
|
|
1939
|
-
function registerTools$
|
|
1953
|
+
function registerTools$33(server, apiClient) {
|
|
1940
1954
|
registerTool(server, apiClient, "amba_sdk_get_setup_instructions", "Get platform-specific code snippets and setup instructions for integrating the Amba client SDK into a mobile app. Supports iOS (Swift), Android (Kotlin), React Native, Expo, and Flutter.", { platform: z.enum([
|
|
1941
1955
|
"ios",
|
|
1942
1956
|
"android",
|
|
@@ -2021,7 +2035,7 @@ const criteriaSchema = z.object({
|
|
|
2021
2035
|
property_key: z.string().optional().describe("User property key (required for property_value type)"),
|
|
2022
2036
|
target_value: z.number().describe("Target value to unlock the achievement")
|
|
2023
2037
|
});
|
|
2024
|
-
function registerTools$
|
|
2038
|
+
function registerTools$32(server, apiClient) {
|
|
2025
2039
|
registerTool(server, apiClient, "amba_achievements_create", "Create an achievement/badge definition. Achievements unlock automatically when users meet the criteria (e.g. track 5 workouts, reach a 7-day streak, earn 5000 XP). Can optionally award bonus XP on unlock.", {
|
|
2026
2040
|
project_id: z.string().describe("The project ID"),
|
|
2027
2041
|
key: z.string().describe("Unique key for this achievement (e.g. \"first_workout\", \"streak_master_7\")"),
|
|
@@ -2126,7 +2140,7 @@ function registerTools$27(server, apiClient) {
|
|
|
2126
2140
|
}
|
|
2127
2141
|
//#endregion
|
|
2128
2142
|
//#region src/tools/challenges.ts
|
|
2129
|
-
function registerTools$
|
|
2143
|
+
function registerTools$31(server, apiClient) {
|
|
2130
2144
|
registerTool(server, apiClient, "amba_challenges_create", "Create a time-limited challenge. Challenges are events that run between start_at and end_at, where users try to reach a goal (e.g. track 10 workouts this week, earn 500 XP in 3 days). Can reward XP and/or unlock an achievement on completion.", {
|
|
2131
2145
|
project_id: z.string().describe("The project ID"),
|
|
2132
2146
|
name: z.string().describe("Challenge name (e.g. \"7-Day Fitness Sprint\", \"XP Weekend Blitz\")"),
|
|
@@ -2237,7 +2251,7 @@ function registerTools$26(server, apiClient) {
|
|
|
2237
2251
|
}
|
|
2238
2252
|
//#endregion
|
|
2239
2253
|
//#region src/tools/economy.ts
|
|
2240
|
-
function registerTools$
|
|
2254
|
+
function registerTools$30(server, apiClient) {
|
|
2241
2255
|
registerTool(server, apiClient, "amba_currencies_create", "Create a virtual currency for a project. Currencies can be soft (earned through gameplay) or premium (purchased with real money). Supports auto-recharge for time-gated mechanics like hearts/energy.", {
|
|
2242
2256
|
project_id: z.string().describe("The project ID"),
|
|
2243
2257
|
code: z.string().describe("Unique currency code (e.g. \"gold\", \"gems\", \"hearts\")"),
|
|
@@ -2540,7 +2554,7 @@ function registerTools$25(server, apiClient) {
|
|
|
2540
2554
|
text: JSON.stringify(result, null, 2)
|
|
2541
2555
|
}] };
|
|
2542
2556
|
});
|
|
2543
|
-
registerTool(server, apiClient, "amba_products_create", ["Declare a sellable subscription product and the entitlement it grants. `store_product_refs` maps each store to its own identifier ({ \"app_store\": \"com.app.pro.yearly\", \"play_store\": \"pro_yearly\", \"web\": \"price_123\" }) so one Amba product resolves on whichever store a purchase came from.", "When `grants_entitlement_id` is set, an in-app purchase OR a server grant carrying this product unlocks that entitlement automatically — that resolution is what
|
|
2557
|
+
registerTool(server, apiClient, "amba_products_create", ["Declare a sellable subscription product and the entitlement it grants. `store_product_refs` maps each store to its own identifier ({ \"app_store\": \"com.app.pro.yearly\", \"play_store\": \"pro_yearly\", \"web\": \"price_123\" }) so one Amba product resolves on whichever store a purchase came from.", "When `grants_entitlement_id` is set, an in-app purchase OR a server grant carrying this product unlocks that entitlement automatically — that resolution is what lets every purchase path (mobile stores, web billing, server grants) land on the same entitlement set."].join(" "), {
|
|
2544
2558
|
project_id: z.string().describe("The project ID"),
|
|
2545
2559
|
product_id: z.string().describe("Stable, neutral product key (e.g. \"pro_yearly\"). Required."),
|
|
2546
2560
|
grants_entitlement_id: z.string().optional().describe("The entitlement key this product unlocks (must be defined first)."),
|
|
@@ -2928,8 +2942,8 @@ const objectTypeSchema = z.enum([
|
|
|
2928
2942
|
"package",
|
|
2929
2943
|
"paywall"
|
|
2930
2944
|
]);
|
|
2931
|
-
function registerTools$
|
|
2932
|
-
registerTool(server, apiClient, "amba_monetization_plan", "Preview the monetization plan for a project: a three-way diff between the declared subscription config (entitlements, products, offerings, packages, paywalls), the last-adopted baseline, and the live config in the connected provider (RevenueCat). Returns the op-list an apply WOULD perform (create/update/archive/attach/detach/set-current), a store-floor preflight (
|
|
2945
|
+
function registerTools$29(server, apiClient) {
|
|
2946
|
+
registerTool(server, apiClient, "amba_monetization_plan", "Preview the monetization plan for a project: a three-way diff between the declared subscription config (entitlements, products, offerings, packages, paywalls), the last-adopted baseline, and the live config in the connected provider (RevenueCat). Returns the op-list an apply WOULD perform (create/update/archive/attach/detach/set-current — including store-product creation steps for declared products that do not exist upstream yet), a store-floor preflight (blocking gaps: dangling product references, or no provider app connected for a store), a `human_floor` checklist (store-side steps no API can do — store SKUs outside the App Store, pricing, agreements/banking/tax, review — each with its exact console location, ready to relay to the developer), and any out-of-band drift. READ-ONLY — computes the plan; applies nothing. Use this before changing monetization so you can see exactly what differs.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
2933
2947
|
const result = await client.get(`/projects/${project_id}/monetization/plan`);
|
|
2934
2948
|
return { content: [{
|
|
2935
2949
|
type: "text",
|
|
@@ -2957,10 +2971,10 @@ function registerTools$24(server, apiClient) {
|
|
|
2957
2971
|
text: JSON.stringify(result, null, 2)
|
|
2958
2972
|
}] };
|
|
2959
2973
|
});
|
|
2960
|
-
registerTool(server, apiClient, "amba_monetization_apply", "Apply the declared monetization config to the connected provider (RevenueCat). Additive ops apply automatically: CREATE entitlements/offerings/packages/paywall-DRAFTS, ATTACH products to new packages/entitlements, update display metadata. DESTRUCTIVE ops (detach a product, archive an entitlement/offering, remove a package) are GATED:
|
|
2974
|
+
registerTool(server, apiClient, "amba_monetization_apply", "Apply the declared monetization config to the connected provider (RevenueCat). DURABLE + ASYNC: this starts the apply and returns an `operation_id` — poll it with `amba_operations_get` until `succeeded` | `failed` (writes are paced against the provider's rate limits, so large plans take a few minutes; the apply survives restarts and retries safely via per-op idempotency keys). Additive ops apply automatically: CREATE entitlements/offerings/packages/paywall-DRAFTS, ATTACH products to new packages/entitlements, update display metadata. DESTRUCTIVE ops (detach a product, archive an entitlement/offering/product, remove a package) are GATED behind a confirmation of the exact plan_hash: pass `confirm` = plan_hash up front, or call apply without it to get `confirm_required: true` (the apply then WAITS for confirmation — re-call apply with `confirm` to approve that pending apply; a confirmation that no longer matches the plan is rejected and nothing destructive runs). STORE-PRODUCT steps go further: declared products missing upstream are CREATED through the provider (registered with RevenueCat, and — with store_metadata.push_to_store — created as REAL store products: App Store products in App Store Connect when they declare duration + subscription_group_name; Google Play ONE-TIME products in Google Play directly when they declare play_price_micros + play_currency, with the price set as part of the create — connect a `google_play` integration first), but these store catalog writes NEVER run off an up-front `confirm`: the apply always waits, you review the pending store steps plus the returned `human_floor` checklist (App Store pricing, agreements/banking/tax, review — with exact console locations), then approve by re-calling apply with `confirm`. Setting the current/default offering, publishing a paywall live, store SKUs apply can't create (Play subscriptions + other stores), and App Store pricing are NOT API-driven — they're returned in `refused`/`human_floor` with the exact dashboard step. Pass the `plan_hash` from a fresh `amba_monetization_plan`; apply refuses a stale plan_hash (re-plan), hard-fails on an unresolvable store reference, enforces the ordering invariant (never archives the live current offering; never detaches a product a live customer resolves unless `allow_detach_live`), reads back every write, and STOPS on the first failure without rolling back.", {
|
|
2961
2975
|
project_id: z.string().describe("The project ID"),
|
|
2962
2976
|
plan_hash: z.string().describe("The plan_hash returned by a fresh amba_monetization_plan. Apply refuses if the provider drifted off this hash."),
|
|
2963
|
-
confirm: z.string().optional().describe("Set to the SAME plan_hash to confirm and EXECUTE the gated destructive ops (detach/archive/remove).
|
|
2977
|
+
confirm: z.string().optional().describe("Set to the SAME plan_hash to confirm and EXECUTE the gated destructive ops (detach/archive/remove). When a plan has gated ops and confirm is omitted, the apply waits for confirmation (re-call apply with confirm to approve it); plans without gated ops apply without confirmation. Store-product creation steps ALWAYS wait — an up-front confirm never auto-runs a store catalog write; approve the started apply by re-calling with confirm."),
|
|
2964
2978
|
allow_detach_live: z.boolean().optional().describe("Accept detaching a product a live customer currently resolves. Off by default (the apply refuses such a detach to protect live purchases)."),
|
|
2965
2979
|
reconcile: z.enum(["amba", "adopt"]).optional().describe("Out-of-band drift mode: 'amba' (default) reverts the provider to the declared config on confirm; 'adopt' pulls the live change back into the declared config instead.")
|
|
2966
2980
|
}, async ({ project_id, plan_hash, confirm, allow_detach_live, reconcile }, { client }) => {
|
|
@@ -2994,7 +3008,7 @@ function registerTools$24(server, apiClient) {
|
|
|
2994
3008
|
}
|
|
2995
3009
|
//#endregion
|
|
2996
3010
|
//#region src/tools/leaderboards.ts
|
|
2997
|
-
function registerTools$
|
|
3011
|
+
function registerTools$28(server, apiClient) {
|
|
2998
3012
|
registerTool(server, apiClient, "amba_leaderboards_create", "Create a leaderboard definition. Leaderboards rank users by XP, streak length, or a custom metric. Supports all-time, daily, weekly, and monthly periods with configurable max entries. The returned `key` (which equals `name`) is the exact value the in-app SDK passes to `Amba.leaderboards.getEntries(key)` / `getRank(key)`.", {
|
|
2999
3013
|
project_id: z.string().describe("The project ID"),
|
|
3000
3014
|
name: z.string().describe("Leaderboard name (e.g. \"Top XP Earners\", \"Weekly Streak Leaders\")"),
|
|
@@ -3109,7 +3123,7 @@ function registerTools$23(server, apiClient) {
|
|
|
3109
3123
|
* rollover (the scheduled weekly rollover) and score live off `xp_awarded`
|
|
3110
3124
|
* engagement events.
|
|
3111
3125
|
*/
|
|
3112
|
-
function registerTools$
|
|
3126
|
+
function registerTools$27(server, apiClient) {
|
|
3113
3127
|
registerTool(server, apiClient, "amba_leagues_create", "Create a league tier (e.g. \"Bronze\", tier_order=1). Leagues are the promote/demote ladder: each week users are grouped into cohorts of `cohort_size`, the top `promote_count` move up a tier and the bottom `demote_count` move down. Create one league per tier (lowest tier_order = entry tier). Members are assigned by the weekly rollover, not at create time.", {
|
|
3114
3128
|
project_id: z.string().describe("The project ID"),
|
|
3115
3129
|
name: z.string().describe("League tier name, e.g. \"Bronze\", \"Silver\", \"Gold\""),
|
|
@@ -3172,7 +3186,7 @@ function registerTools$22(server, apiClient) {
|
|
|
3172
3186
|
}
|
|
3173
3187
|
//#endregion
|
|
3174
3188
|
//#region src/tools/platform.ts
|
|
3175
|
-
function registerTools$
|
|
3189
|
+
function registerTools$26(server, apiClient) {
|
|
3176
3190
|
registerTool(server, apiClient, "amba_onboarding_create", "Create an onboarding flow for a project. Onboarding flows define step-by-step experiences for new users (e.g. welcome screens, permission prompts, feature tours).", {
|
|
3177
3191
|
project_id: z.string().describe("The project ID"),
|
|
3178
3192
|
name: z.string().describe("Flow name (e.g. \"Welcome Flow\", \"Premium Onboarding\")"),
|
|
@@ -3825,7 +3839,7 @@ function registerTools$21(server, apiClient) {
|
|
|
3825
3839
|
}
|
|
3826
3840
|
//#endregion
|
|
3827
3841
|
//#region src/tools/social.ts
|
|
3828
|
-
function registerTools$
|
|
3842
|
+
function registerTools$25(server, apiClient) {
|
|
3829
3843
|
registerTool(server, apiClient, "amba_friendships_get_stats", "Get friendship statistics for a project. Returns total friendships, pending requests, accepted friendships, and blocked count.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
3830
3844
|
const result = await client.get(`/projects/${project_id}/friends/stats`);
|
|
3831
3845
|
return { content: [{
|
|
@@ -4150,7 +4164,7 @@ function registerTools$20(server, apiClient) {
|
|
|
4150
4164
|
}
|
|
4151
4165
|
//#endregion
|
|
4152
4166
|
//#region src/tools/xp.ts
|
|
4153
|
-
function registerTools$
|
|
4167
|
+
function registerTools$24(server, apiClient) {
|
|
4154
4168
|
registerTool(server, apiClient, "amba_xp_rules_create", "Create an XP rule that auto-awards XP when a matching engagement event is tracked. For example, award 50 XP every time a user completes a workout, with optional daily caps and cooldowns.", {
|
|
4155
4169
|
project_id: z.string().describe("The project ID"),
|
|
4156
4170
|
name: z.string().describe("Rule name (e.g. \"Workout Completed\", \"Daily Login Bonus\")"),
|
|
@@ -4273,7 +4287,7 @@ function registerTools$19(server, apiClient) {
|
|
|
4273
4287
|
}
|
|
4274
4288
|
//#endregion
|
|
4275
4289
|
//#region src/tools/events.ts
|
|
4276
|
-
function registerTools$
|
|
4290
|
+
function registerTools$23(server, apiClient) {
|
|
4277
4291
|
registerTool(server, apiClient, "amba_events_list", "List engagement events for a project (most recent first) with cursor pagination on (occurred_at, id). Defaults to the last 24 hours when `since` is omitted. Pass `next_cursor` from a previous page back as `cursor` to continue.", {
|
|
4278
4292
|
project_id: z.string().describe("The project ID"),
|
|
4279
4293
|
since: z.string().optional().describe("ISO-8601 lower bound for occurred_at. Defaults to 24h ago."),
|
|
@@ -4364,7 +4378,7 @@ function registerTools$18(server, apiClient) {
|
|
|
4364
4378
|
}
|
|
4365
4379
|
//#endregion
|
|
4366
4380
|
//#region src/tools/event-catalog.ts
|
|
4367
|
-
function registerTools$
|
|
4381
|
+
function registerTools$22(server, apiClient) {
|
|
4368
4382
|
registerTool(server, apiClient, "amba_events_catalog", "Discover every event you can subscribe a webhook to — both APP events (inside your app: users, gamification, economy, social, content) and CONTROL-plane lifecycle events (about the project itself: provisioning, deploys, domains, billing). Returns each event's type, plane, payload example, and status (`live` = emitted today, `planned` = catalogued, not yet wired). Production pattern: after standing up a project, call this, then subscribe to the lifecycle events you care about (e.g. `domain.registered`, `site.deploy.completed`) with amba_control_webhooks_create and to app events with amba_webhooks_create. Subscriptions match an exact name, a namespace wildcard (`economy.*`), or all (`*`). Pass plane to filter.", {
|
|
4369
4383
|
project_id: z.string().describe("The project ID"),
|
|
4370
4384
|
plane: z.enum(["app", "control"]).optional().describe("Filter to one plane. Omit for both.")
|
|
@@ -4434,7 +4448,7 @@ const funnelStepSchema = z.object({
|
|
|
4434
4448
|
event: z.string().describe("Event name to match (engagement event_name)."),
|
|
4435
4449
|
filters: z.array(funnelFilterSchema).optional().describe("Optional property predicates, ANDed together, that an event must satisfy.")
|
|
4436
4450
|
});
|
|
4437
|
-
function registerTools$
|
|
4451
|
+
function registerTools$21(server, apiClient) {
|
|
4438
4452
|
registerTool(server, apiClient, "amba_funnels_list", "List conversion funnels for a project. A funnel is a saved, ordered sequence of steps (event + optional property filters) plus a conversion window. Returns the definitions only — use amba_funnels_query to compute per-step counts.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
4439
4453
|
const result = await client.get(`/projects/${project_id}/funnels`);
|
|
4440
4454
|
return { content: [{
|
|
@@ -4650,7 +4664,7 @@ function enrichedAuthResult(result, agentInstructions) {
|
|
|
4650
4664
|
status: result.status
|
|
4651
4665
|
});
|
|
4652
4666
|
}
|
|
4653
|
-
function registerTools$
|
|
4667
|
+
function registerTools$20(server, apiClient) {
|
|
4654
4668
|
registerPublicTool(server, "amba_developer_signup", [
|
|
4655
4669
|
"Create a new Amba developer account. Returns a long-lived Personal Access Token (PAT)",
|
|
4656
4670
|
"plus a real isolated Amba project (provisioning asynchronously), plus ready-to-paste",
|
|
@@ -4668,7 +4682,34 @@ function registerTools$15(server, apiClient) {
|
|
|
4668
4682
|
].join(" "), {
|
|
4669
4683
|
email: z.string().email().describe("Developer email address. Will be lowercased."),
|
|
4670
4684
|
password: z.string().min(8).describe("Password, minimum 8 characters."),
|
|
4671
|
-
name: z.string().optional().describe("Optional display name.")
|
|
4685
|
+
name: z.string().optional().describe("Optional display name."),
|
|
4686
|
+
referral_code: z.string().optional().describe("Optional Amba partner referral code the developer arrived with (the `ref` on a partner's link). Attributes a later paid upgrade to that partner; ignored if not a live partner code.")
|
|
4687
|
+
}, async ({ email, password, name, referral_code }) => {
|
|
4688
|
+
const body = {
|
|
4689
|
+
email,
|
|
4690
|
+
password
|
|
4691
|
+
};
|
|
4692
|
+
if (name !== void 0) body.name = name;
|
|
4693
|
+
if (referral_code !== void 0) body.referral_code = referral_code;
|
|
4694
|
+
return enrichedAuthResult(await authFetch(apiClient, {
|
|
4695
|
+
method: "POST",
|
|
4696
|
+
path: "/auth/developer/signup",
|
|
4697
|
+
body
|
|
4698
|
+
}), SIGNUP_AGENT_INSTRUCTIONS);
|
|
4699
|
+
});
|
|
4700
|
+
registerPublicTool(server, "amba_affiliate_signup", [
|
|
4701
|
+
"Join an affiliate program from zero: create an Amba account + org and get a long-lived PAT",
|
|
4702
|
+
"plus ready-to-paste MCP-client config in `mcp_config`. An affiliate IS an Amba org — after",
|
|
4703
|
+
"signing up, give your org id (from amba_orgs_list) to the program owner so they can enroll you",
|
|
4704
|
+
"(amba_affiliate_invite with affiliate_org_id), then set up payouts with",
|
|
4705
|
+
"amba_affiliate_my_payout_account_create. Agent: pass the returned `pat` inline on subsequent calls",
|
|
4706
|
+
"and write the mcp_config snippet to the customer's MCP config for future sessions.",
|
|
4707
|
+
"Email is lowercased; password must be at least 8 characters."
|
|
4708
|
+
].join(" "), {
|
|
4709
|
+
email: z.string().email().describe("Affiliate email address. Will be lowercased."),
|
|
4710
|
+
password: z.string().min(8).describe("Password, minimum 8 characters."),
|
|
4711
|
+
name: z.string().optional().describe("Optional display name."),
|
|
4712
|
+
invite_token: z.string().optional().describe("Optional affiliate invite token (for reference).")
|
|
4672
4713
|
}, async ({ email, password, name }) => {
|
|
4673
4714
|
const body = {
|
|
4674
4715
|
email,
|
|
@@ -4765,6 +4806,7 @@ async function adminFetch$1(apiClient, options) {
|
|
|
4765
4806
|
Accept: "application/json"
|
|
4766
4807
|
};
|
|
4767
4808
|
if (options.body !== void 0) headers["Content-Type"] = "application/json";
|
|
4809
|
+
if (options.idempotencyKey !== void 0) headers["Idempotency-Key"] = options.idempotencyKey;
|
|
4768
4810
|
const res = await fetch(url, {
|
|
4769
4811
|
method: options.method,
|
|
4770
4812
|
headers,
|
|
@@ -4796,7 +4838,7 @@ function catalogShapeWarning(row) {
|
|
|
4796
4838
|
const ownership = "user_id" in row ? `attributed to user_id "${String(row["user_id"])}"` : "attributed to the seeding user (or rejected if the collection requires user_id)";
|
|
4797
4839
|
return [`⚠️ This row has catalog-shaped column(s) [${hit.join(", ")}] but is being inserted into a per-user collection, so it will be ${ownership} — other users will NOT see it via the client SDK.`, "If this is shared/global content (a catalog, articles, a media library), use a Content Library (amba_content_libraries_create) or create the collection with shared:true so its rows are readable by every user. Proceeding anyway."].join(" ");
|
|
4798
4840
|
}
|
|
4799
|
-
async function clientFetch(apiClient, options) {
|
|
4841
|
+
async function clientFetch$1(apiClient, options) {
|
|
4800
4842
|
let url = `${apiClient.getApiRoot()}/client${options.path}`;
|
|
4801
4843
|
if (options.query && Object.keys(options.query).length > 0) url += `?${new URLSearchParams(options.query).toString()}`;
|
|
4802
4844
|
const headers = {
|
|
@@ -4805,6 +4847,7 @@ async function clientFetch(apiClient, options) {
|
|
|
4805
4847
|
};
|
|
4806
4848
|
if (options.body !== void 0) headers["Content-Type"] = "application/json";
|
|
4807
4849
|
if (options.sessionToken) headers["Authorization"] = `Bearer ${options.sessionToken}`;
|
|
4850
|
+
if (options.idempotencyKey !== void 0) headers["Idempotency-Key"] = options.idempotencyKey;
|
|
4808
4851
|
const res = await fetch(url, {
|
|
4809
4852
|
method: options.method,
|
|
4810
4853
|
headers,
|
|
@@ -4831,26 +4874,70 @@ const MISSING_API_KEY_ERROR = jsonResult$1({
|
|
|
4831
4874
|
message: "Client tools require `api_key` (the project client X-Api-Key). Mint one via amba_api_keys_create with key_type=\"client\"."
|
|
4832
4875
|
}
|
|
4833
4876
|
});
|
|
4877
|
+
const COLLECTION_COLUMN_TYPES = [
|
|
4878
|
+
"uuid",
|
|
4879
|
+
"text",
|
|
4880
|
+
"integer",
|
|
4881
|
+
"bigint",
|
|
4882
|
+
"numeric",
|
|
4883
|
+
"boolean",
|
|
4884
|
+
"timestamptz",
|
|
4885
|
+
"date",
|
|
4886
|
+
"jsonb",
|
|
4887
|
+
"vector",
|
|
4888
|
+
"text[]",
|
|
4889
|
+
"integer[]",
|
|
4890
|
+
"bigint[]",
|
|
4891
|
+
"numeric[]",
|
|
4892
|
+
"boolean[]",
|
|
4893
|
+
"uuid[]"
|
|
4894
|
+
];
|
|
4895
|
+
const CHANGE_TYPE_TARGET_TYPES = [
|
|
4896
|
+
"uuid",
|
|
4897
|
+
"text",
|
|
4898
|
+
"integer",
|
|
4899
|
+
"bigint",
|
|
4900
|
+
"numeric",
|
|
4901
|
+
"boolean",
|
|
4902
|
+
"timestamptz",
|
|
4903
|
+
"date",
|
|
4904
|
+
"jsonb",
|
|
4905
|
+
"text[]",
|
|
4906
|
+
"integer[]",
|
|
4907
|
+
"bigint[]",
|
|
4908
|
+
"numeric[]",
|
|
4909
|
+
"boolean[]",
|
|
4910
|
+
"uuid[]"
|
|
4911
|
+
];
|
|
4834
4912
|
const columnSchema = z.object({
|
|
4835
|
-
name: z.string().describe("Column name. Must match /^[a-z][a-z0-9_]*$/."),
|
|
4836
|
-
type: z.
|
|
4837
|
-
nullable: z.boolean().optional().describe("Whether the column accepts NULL. Defaults to true."),
|
|
4838
|
-
|
|
4839
|
-
|
|
4913
|
+
name: z.string().describe("Column name. Must match /^[a-z][a-z0-9_]*$/. Server-managed names (id, user_id, created_at, updated_at, deleted_at) and the query keywords and/or/not are reserved."),
|
|
4914
|
+
type: z.enum(COLLECTION_COLUMN_TYPES).describe("Column type. Scalars: text, integer, bigint, numeric, boolean, timestamptz, date, jsonb, uuid. Arrays: text[], integer[], bigint[], numeric[], boolean[], uuid[] — array columns unlock the contains/contained_by/overlaps query operators. `vector` requires `dimension`. Column defaults are not supported — set values at insert time."),
|
|
4915
|
+
nullable: z.boolean().optional().describe("Whether the column accepts NULL. Defaults to false — columns are NOT NULL unless nullable is true."),
|
|
4916
|
+
dimension: z.number().int().min(1).max(4096).optional().describe("Required for `vector` columns: the embedding dimension (1–4096), e.g. 1536 for OpenAI text-embedding-3-small. Rejected on non-vector columns."),
|
|
4917
|
+
references: z.object({
|
|
4918
|
+
table: z.string().describe("Target table, e.g. app_users."),
|
|
4919
|
+
column: z.string().optional().describe("Target column. Defaults to id."),
|
|
4920
|
+
onDelete: z.enum([
|
|
4921
|
+
"CASCADE",
|
|
4922
|
+
"RESTRICT",
|
|
4923
|
+
"SET NULL",
|
|
4924
|
+
"NO ACTION"
|
|
4925
|
+
]).optional().describe("Cascade behavior. Defaults to RESTRICT.")
|
|
4926
|
+
}).optional().describe("Optional foreign key. Not allowed on vector columns. (Every collection already gets a user_id FK to app_users automatically — no need to declare it.)")
|
|
4840
4927
|
});
|
|
4841
4928
|
const indexSchema = z.object({
|
|
4842
4929
|
name: z.string().optional().describe("Index name. Defaults to a generated value."),
|
|
4843
|
-
columns: z.array(z.string()).describe("Column
|
|
4844
|
-
unique: z.boolean().optional().describe("Whether the index enforces uniqueness."),
|
|
4845
|
-
|
|
4846
|
-
"
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
"
|
|
4852
|
-
"ivfflat"
|
|
4853
|
-
|
|
4930
|
+
columns: z.array(z.string()).min(1).describe("Column entries — each is \"<col>\" or \"<col> desc\" (ascending is the default). For a vector index, exactly one entry naming the vector column."),
|
|
4931
|
+
unique: z.boolean().optional().describe("Whether the index enforces uniqueness. Not supported on vector indexes."),
|
|
4932
|
+
vector: z.object({
|
|
4933
|
+
method: z.enum(["ivfflat", "hnsw"]).optional().describe("Index method. Default ivfflat (fast build); hnsw trades slower writes for better recall on read-heavy workloads."),
|
|
4934
|
+
distance: z.enum([
|
|
4935
|
+
"cosine",
|
|
4936
|
+
"l2",
|
|
4937
|
+
"inner_product"
|
|
4938
|
+
]).optional().describe("Distance operator the index optimizes for. Default cosine. Must match the `distance` used by find-nearest queries against the column, or the index is skipped."),
|
|
4939
|
+
lists: z.number().int().min(1).max(1e5).optional().describe("ivfflat only: number of inverted lists. Default 100; sqrt(row count) is a good rule of thumb.")
|
|
4940
|
+
}).optional().describe("Vector index spec — set it (even as {}) to build a similarity index on a vector column for find-nearest. Omit for a standard B-tree index; non-vector indexes never need it.")
|
|
4854
4941
|
});
|
|
4855
4942
|
const sdkFilterSchema = z.unknown().describe("SDK-shaped filter. Either a leaf `{column, op, value}` (op ∈ eq|ne|gt|gte|lt|lte|in|not_in|like|ilike|is_null|is_not_null) or a combinator `{and: [...]}` / `{or: [...]}` / `{not: {...}}`.");
|
|
4856
4943
|
const sdkOrderSchema = z.array(z.object({
|
|
@@ -4859,9 +4946,10 @@ const sdkOrderSchema = z.array(z.object({
|
|
|
4859
4946
|
})).describe("Ordering. `[{column: \"created_at\", direction: \"desc\"}, ...]`.");
|
|
4860
4947
|
const adminFindWhereSchema = z.unknown().describe("Server WhereClause shape: column-keyed FieldOps, e.g. {tag: {eq: \"x\"}, count: {gte: 5}} plus optional `and|or|not` combinators.");
|
|
4861
4948
|
const setSchema = z.record(z.unknown()).describe("Column-value map. Server-managed columns (id, created_at, etc.) are rejected.");
|
|
4862
|
-
function registerTools$
|
|
4949
|
+
function registerTools$19(server, apiClient) {
|
|
4863
4950
|
registerTool(server, apiClient, "amba_collections_create", [
|
|
4864
4951
|
"Create a new collection (schema-first Postgres table) in a project.",
|
|
4952
|
+
"Ownership: by default every row is private to the user who created it. For team/org data, pass owner_scope:'group' with read_policy:'member' and write_policy:'member:admin' — rows are owned by a group and any member reads while admins write, enforced server-side via the group's membership so you don't reimplement authorization in app code.",
|
|
4865
4953
|
"The schema change is applied server-side via an async workflow.",
|
|
4866
4954
|
"Authenticates as the developer/agent — pass `pat` (the Personal Access Token returned by amba_developer_signup) or send it as the inbound Bearer.",
|
|
4867
4955
|
"Returns the workflow id + version + status. Failure responses include `details.workflow_id` for debugging.",
|
|
@@ -4871,14 +4959,20 @@ function registerTools$14(server, apiClient) {
|
|
|
4871
4959
|
name: z.string().describe("Collection name. Must match /^[a-z][a-z0-9_]*$/. Reserved prefixes (amba_, coll_amba_) are rejected."),
|
|
4872
4960
|
columns: z.array(columnSchema).describe("Column definitions. Server adds id/user_id/created_at/updated_at/deleted_at."),
|
|
4873
4961
|
indexes: z.array(indexSchema).optional().describe("Optional extra indexes (server creates a primary key + user_id index by default)."),
|
|
4874
|
-
shared: z.boolean().optional().describe("Set true for developer-seeded GLOBAL content not owned by any single user (question banks, content libraries, lookup tables). Makes user_id NULLABLE so rows can be inserted without a user_id. Defaults to false (every row requires a user_id).")
|
|
4875
|
-
|
|
4962
|
+
shared: z.boolean().optional().describe("Set true for developer-seeded GLOBAL content not owned by any single user (question banks, content libraries, lookup tables). Makes user_id NULLABLE so rows can be inserted without a user_id. Defaults to false (every row requires a user_id)."),
|
|
4963
|
+
owner_scope: z.enum(["user", "group"]).optional().describe("Row ownership model. 'user' (default) — each row is private to the user who created it (profiles, drafts, per-user state). 'group' — rows are owned by a team (a group_id) and shared with its members; pair with read_policy:'member' / write_policy:'member:admin'. Reach for 'group' the moment two users must read or write the same rows (team workspaces, org-shared data) — Amba resolves membership server-side so you can delete your app-side authorization layer."),
|
|
4964
|
+
read_policy: z.string().optional().describe("Who can READ rows — Postgres-grade row security, declared once and enforced on every query. 'owner' (default, private), 'public' (every signed-in user), 'member' (any member of the owning group; requires owner_scope:'group'), 'member:<role>' (members at role >= <role>, where owner>admin>member>viewer)."),
|
|
4965
|
+
write_policy: z.string().optional().describe("Who can WRITE rows. 'owner' (default), 'authenticated' (any signed-in user may insert), 'member' (any member of the owning group), 'member:admin' (the classic 'members read, admins write'; requires owner_scope:'group'). Reads and writes are independent.")
|
|
4966
|
+
}, async ({ project_id, name, columns, indexes, shared, owner_scope, read_policy, write_policy }, { pat }) => {
|
|
4876
4967
|
const body = {
|
|
4877
4968
|
name,
|
|
4878
4969
|
columns
|
|
4879
4970
|
};
|
|
4880
4971
|
if (indexes !== void 0) body.indexes = indexes;
|
|
4881
4972
|
if (shared !== void 0) body.shared = shared;
|
|
4973
|
+
if (owner_scope !== void 0) body.owner_scope = owner_scope;
|
|
4974
|
+
if (read_policy !== void 0) body.read_policy = read_policy;
|
|
4975
|
+
if (write_policy !== void 0) body.write_policy = write_policy;
|
|
4882
4976
|
return passthroughResult(await adminFetch$1(apiClient, {
|
|
4883
4977
|
method: "POST",
|
|
4884
4978
|
path: `/projects/${project_id}/collections`,
|
|
@@ -4955,7 +5049,8 @@ function registerTools$14(server, apiClient) {
|
|
|
4955
5049
|
}));
|
|
4956
5050
|
});
|
|
4957
5051
|
registerTool(server, apiClient, "amba_collections_alter", [
|
|
4958
|
-
"Alter a collection: exactly one of `add_column`, `add_index`, `change_type`, `rename_column`, `drop_column`, or `
|
|
5052
|
+
"Alter a collection: exactly one of `add_column`, `add_index`, `change_type`, `rename_column`, `drop_column`, `relax_user_id`, or `set_policy` per call (the underlying saga is built around one SQL blob per workflow; set_policy is a metadata-only write applied without the saga).",
|
|
5053
|
+
"set_policy changes an existing collection's access policy: read_policy ('owner'|'public'|'member'|'member:<role>') and/or write_policy ('owner'|'authenticated'|'member'|'member:admin'). A 'member' policy requires the collection to have been created with owner_scope:'group' (owner scope is fixed at create time).",
|
|
4959
5054
|
"Drop-column is destructive — requires `confirm` to equal the dropped column name.",
|
|
4960
5055
|
"change_type changes an existing column to a new type. It REWRITES THE WHOLE TABLE under an exclusive lock — the collection is briefly unreadable/unwritable while it runs, so prefer low-traffic windows for large collections. Casts that are not trivially safe (e.g. text→integer) require a `using` cast expression; known-safe casts (anything→text, integer→bigint, integer/bigint→numeric) do not. Without a `using` for an unsafe cast you get a 400 up front rather than a mid-migration failure.",
|
|
4961
5056
|
"rename_column renames a column (metadata-only, fast). It BREAKS any app code referencing the old name — re-run `amba types generate` and update call sites afterward. The new name can’t collide with a server-managed column (id, user_id, created_at, updated_at, deleted_at) or a reserved query keyword (and, or, not).",
|
|
@@ -4969,8 +5064,8 @@ function registerTools$14(server, apiClient) {
|
|
|
4969
5064
|
add_index: indexSchema.optional().describe("Index to add. Mutually exclusive with the other alter operations."),
|
|
4970
5065
|
change_type: z.object({
|
|
4971
5066
|
column: z.string().describe("Existing column to change."),
|
|
4972
|
-
to_type: z.
|
|
4973
|
-
from_type: z.
|
|
5067
|
+
to_type: z.enum(CHANGE_TYPE_TARGET_TYPES).describe("Target type — any column type except vector (text, integer, bigint, numeric, boolean, timestamptz, date, jsonb, uuid, or an array type like text[]). To move a column to vector, add a new vector column instead."),
|
|
5068
|
+
from_type: z.enum(COLLECTION_COLUMN_TYPES).optional().describe("Optional current type. When provided, known-safe widening casts (integer→bigint, integer/bigint→numeric) skip the `using` requirement."),
|
|
4974
5069
|
using: z.string().optional().describe("SQL cast expression for the USING clause, e.g. \"score::integer\". Required for casts that are not trivially safe; omit only for anything→text or a known-safe widening cast.")
|
|
4975
5070
|
}).optional().describe("Change a column’s type. REWRITES THE WHOLE TABLE under an exclusive lock (briefly unreadable/unwritable). Mutually exclusive with the other alter operations."),
|
|
4976
5071
|
rename_column: z.object({
|
|
@@ -4979,8 +5074,12 @@ function registerTools$14(server, apiClient) {
|
|
|
4979
5074
|
}).optional().describe("Rename a column (metadata-only, fast). BREAKS code referencing the old name — re-run `amba types generate` afterward. Mutually exclusive with the other alter operations."),
|
|
4980
5075
|
drop_column: z.string().optional().describe("Column name to drop. Requires `confirm` query param to match."),
|
|
4981
5076
|
relax_user_id: z.boolean().optional().describe("Set true to drop the user_id NOT NULL constraint so this collection can hold shared/global rows (NULL user_id). Mutually exclusive with the other operations."),
|
|
5077
|
+
set_policy: z.object({
|
|
5078
|
+
read_policy: z.string().optional(),
|
|
5079
|
+
write_policy: z.string().optional()
|
|
5080
|
+
}).optional().describe("Change the access policy (metadata-only). read_policy: 'owner'|'public'|'member'|'member:<role>'; write_policy: 'owner'|'authenticated'|'member'|'member:admin'. 'member' policies require a group-owned collection (created with owner_scope:'group'). Mutually exclusive with the other operations."),
|
|
4982
5081
|
confirm: z.string().optional().describe("Required when drop_column is set; must equal the column name.")
|
|
4983
|
-
}, async ({ project_id, name, add_column, add_index, change_type, rename_column, drop_column, relax_user_id, confirm }, { pat }) => {
|
|
5082
|
+
}, async ({ project_id, name, add_column, add_index, change_type, rename_column, drop_column, relax_user_id, set_policy, confirm }, { pat }) => {
|
|
4984
5083
|
const body = {};
|
|
4985
5084
|
if (add_column !== void 0) body.add_column = add_column;
|
|
4986
5085
|
if (add_index !== void 0) body.add_index = add_index;
|
|
@@ -4988,6 +5087,7 @@ function registerTools$14(server, apiClient) {
|
|
|
4988
5087
|
if (rename_column !== void 0) body.rename_column = rename_column;
|
|
4989
5088
|
if (drop_column !== void 0) body.drop_column = drop_column;
|
|
4990
5089
|
if (relax_user_id !== void 0) body.relax_user_id = relax_user_id;
|
|
5090
|
+
if (set_policy !== void 0) body.set_policy = set_policy;
|
|
4991
5091
|
const query = {};
|
|
4992
5092
|
if (drop_column !== void 0 && confirm !== void 0) query.confirm = confirm;
|
|
4993
5093
|
return passthroughResult(await adminFetch$1(apiClient, {
|
|
@@ -5009,6 +5109,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5009
5109
|
project_id: z.string().describe("The project ID."),
|
|
5010
5110
|
name: z.string().describe("Collection name."),
|
|
5011
5111
|
row: z.record(z.unknown()).describe("Column-value map for the new row. Server-managed columns are stripped."),
|
|
5112
|
+
idempotency_key: z.string().min(1).max(255).optional().describe("Request-level idempotency key, sent as the Idempotency-Key header. This is separate from any row column named idempotency_key."),
|
|
5012
5113
|
as_system: z.boolean().optional().describe("Write a developer-owned GLOBAL row (user_id = NULL) on a shared:true collection — for content the whole app reads (question banks, lookup tables) rather than data owned by one end-user. Mutually exclusive with a row.user_id. Rejected on non-shared collections."),
|
|
5013
5114
|
on_conflict: z.enum([
|
|
5014
5115
|
"error",
|
|
@@ -5017,7 +5118,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5017
5118
|
]).optional().describe("Conflict policy. \"error\" (default) fails on a unique-constraint clash; \"ignore\" returns the existing row (HTTP 200) unchanged; \"update\" merges the provided columns. Requires conflict_target."),
|
|
5018
5119
|
conflict_target: z.array(z.string()).optional().describe("Columns that form the unique index to conflict on (e.g. [\"user_id\",\"kind\"]). Required when on_conflict is \"ignore\" or \"update\"."),
|
|
5019
5120
|
return_minimal: z.boolean().optional().describe("When true, the response returns only the inserted row id instead of the full row. Use this for rows with large columns (big JSON blobs) to avoid bloating the response — essential for bulk migrations where echoing full rows exhausts the token budget.")
|
|
5020
|
-
}, async ({ project_id, name, row, as_system, on_conflict, conflict_target, return_minimal }, { pat }) => {
|
|
5121
|
+
}, async ({ project_id, name, row, idempotency_key, as_system, on_conflict, conflict_target, return_minimal }, { pat }) => {
|
|
5021
5122
|
const query = {};
|
|
5022
5123
|
if (on_conflict !== void 0) query.on_conflict = on_conflict;
|
|
5023
5124
|
if (conflict_target !== void 0) query.conflict_target = conflict_target.join(",");
|
|
@@ -5031,7 +5132,8 @@ function registerTools$14(server, apiClient) {
|
|
|
5031
5132
|
path: `/projects/${project_id}/collections/${encodeURIComponent(name)}/rows`,
|
|
5032
5133
|
body: requestBody,
|
|
5033
5134
|
query: Object.keys(query).length > 0 ? query : void 0,
|
|
5034
|
-
bearer: pat
|
|
5135
|
+
bearer: pat,
|
|
5136
|
+
...idempotency_key !== void 0 && { idempotencyKey: idempotency_key }
|
|
5035
5137
|
});
|
|
5036
5138
|
const warning = catalogShapeWarning(row);
|
|
5037
5139
|
const base = passthroughResult(result);
|
|
@@ -5235,6 +5337,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5235
5337
|
].join(" "), {
|
|
5236
5338
|
name: z.string().describe("Collection name."),
|
|
5237
5339
|
row: z.record(z.unknown()).describe("Column-value map. Server-managed columns and user_id are stripped/overridden."),
|
|
5340
|
+
idempotency_key: z.string().min(1).max(255).optional().describe("Request-level idempotency key, sent as the Idempotency-Key header. This is separate from any row column named idempotency_key."),
|
|
5238
5341
|
on_conflict: z.enum([
|
|
5239
5342
|
"error",
|
|
5240
5343
|
"ignore",
|
|
@@ -5243,18 +5346,19 @@ function registerTools$14(server, apiClient) {
|
|
|
5243
5346
|
conflict_target: z.array(z.string()).optional().describe("Unique-index columns to conflict on (e.g. [\"user_id\",\"kind\"]). Required for ignore/update."),
|
|
5244
5347
|
api_key: z.string().describe("Project client X-Api-Key. End-user tools authenticate via this + an optional session token, NOT via a developer PAT."),
|
|
5245
5348
|
session_token: z.string().optional().describe("Optional app_user session Bearer token. Required by the route's clientSessionAuth.")
|
|
5246
|
-
}, async ({ name, row, on_conflict, conflict_target, api_key, session_token }) => {
|
|
5349
|
+
}, async ({ name, row, idempotency_key, on_conflict, conflict_target, api_key, session_token }) => {
|
|
5247
5350
|
if (!api_key) return MISSING_API_KEY_ERROR;
|
|
5248
5351
|
const query = {};
|
|
5249
5352
|
if (on_conflict !== void 0) query.on_conflict = on_conflict;
|
|
5250
5353
|
if (conflict_target !== void 0) query.conflict_target = conflict_target.join(",");
|
|
5251
|
-
return passthroughResult(await clientFetch(apiClient, {
|
|
5354
|
+
return passthroughResult(await clientFetch$1(apiClient, {
|
|
5252
5355
|
method: "POST",
|
|
5253
5356
|
path: `/collections/${encodeURIComponent(name)}`,
|
|
5254
5357
|
body: row,
|
|
5255
5358
|
...Object.keys(query).length > 0 && { query },
|
|
5256
5359
|
apiKey: api_key,
|
|
5257
|
-
...session_token !== void 0 && { sessionToken: session_token }
|
|
5360
|
+
...session_token !== void 0 && { sessionToken: session_token },
|
|
5361
|
+
...idempotency_key !== void 0 && { idempotencyKey: idempotency_key }
|
|
5258
5362
|
}));
|
|
5259
5363
|
});
|
|
5260
5364
|
server.tool("amba_client_list_rows", [
|
|
@@ -5282,7 +5386,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5282
5386
|
if (cursor !== void 0) findQuery.cursor = cursor;
|
|
5283
5387
|
if (select !== void 0) findQuery.select = select;
|
|
5284
5388
|
if (include_deleted !== void 0) findQuery.includeDeleted = include_deleted;
|
|
5285
|
-
return passthroughResult(await clientFetch(apiClient, {
|
|
5389
|
+
return passthroughResult(await clientFetch$1(apiClient, {
|
|
5286
5390
|
method: "GET",
|
|
5287
5391
|
path: `/collections/${encodeURIComponent(name)}`,
|
|
5288
5392
|
query: Object.keys(findQuery).length > 0 ? { query: JSON.stringify(findQuery) } : void 0,
|
|
@@ -5301,7 +5405,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5301
5405
|
session_token: z.string().optional().describe("App_user session Bearer. Required by the route.")
|
|
5302
5406
|
}, async ({ name, id, api_key, session_token }) => {
|
|
5303
5407
|
if (!api_key) return MISSING_API_KEY_ERROR;
|
|
5304
|
-
return passthroughResult(await clientFetch(apiClient, {
|
|
5408
|
+
return passthroughResult(await clientFetch$1(apiClient, {
|
|
5305
5409
|
method: "GET",
|
|
5306
5410
|
path: `/collections/${encodeURIComponent(name)}/${encodeURIComponent(id)}`,
|
|
5307
5411
|
apiKey: api_key,
|
|
@@ -5328,7 +5432,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5328
5432
|
}, async ({ name, id, where, set, expected, limit, objects, api_key, session_token }) => {
|
|
5329
5433
|
if (!api_key) return MISSING_API_KEY_ERROR;
|
|
5330
5434
|
const objectsQuery = objects === "replace" ? { objects: "replace" } : void 0;
|
|
5331
|
-
if (id !== void 0) return passthroughResult(await clientFetch(apiClient, {
|
|
5435
|
+
if (id !== void 0) return passthroughResult(await clientFetch$1(apiClient, {
|
|
5332
5436
|
method: "PATCH",
|
|
5333
5437
|
path: `/collections/${encodeURIComponent(name)}/${encodeURIComponent(id)}`,
|
|
5334
5438
|
body: expected !== void 0 ? {
|
|
@@ -5344,7 +5448,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5344
5448
|
where: where ?? {}
|
|
5345
5449
|
};
|
|
5346
5450
|
if (limit !== void 0) body.limit = limit;
|
|
5347
|
-
return passthroughResult(await clientFetch(apiClient, {
|
|
5451
|
+
return passthroughResult(await clientFetch$1(apiClient, {
|
|
5348
5452
|
method: "PATCH",
|
|
5349
5453
|
path: `/collections/${encodeURIComponent(name)}`,
|
|
5350
5454
|
body,
|
|
@@ -5366,7 +5470,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5366
5470
|
session_token: z.string().optional().describe("App_user session Bearer. Required by the route.")
|
|
5367
5471
|
}, async ({ name, id, where, limit, api_key, session_token }) => {
|
|
5368
5472
|
if (!api_key) return MISSING_API_KEY_ERROR;
|
|
5369
|
-
if (id !== void 0) return passthroughResult(await clientFetch(apiClient, {
|
|
5473
|
+
if (id !== void 0) return passthroughResult(await clientFetch$1(apiClient, {
|
|
5370
5474
|
method: "DELETE",
|
|
5371
5475
|
path: `/collections/${encodeURIComponent(name)}/${encodeURIComponent(id)}`,
|
|
5372
5476
|
apiKey: api_key,
|
|
@@ -5374,7 +5478,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5374
5478
|
}));
|
|
5375
5479
|
const body = { where: where ?? {} };
|
|
5376
5480
|
if (limit !== void 0) body.limit = limit;
|
|
5377
|
-
return passthroughResult(await clientFetch(apiClient, {
|
|
5481
|
+
return passthroughResult(await clientFetch$1(apiClient, {
|
|
5378
5482
|
method: "DELETE",
|
|
5379
5483
|
path: `/collections/${encodeURIComponent(name)}`,
|
|
5380
5484
|
body,
|
|
@@ -5397,7 +5501,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5397
5501
|
const findQuery = {};
|
|
5398
5502
|
if (where !== void 0) findQuery.where = where;
|
|
5399
5503
|
if (include_deleted !== void 0) findQuery.includeDeleted = include_deleted;
|
|
5400
|
-
return passthroughResult(await clientFetch(apiClient, {
|
|
5504
|
+
return passthroughResult(await clientFetch$1(apiClient, {
|
|
5401
5505
|
method: "GET",
|
|
5402
5506
|
path: `/collections/${encodeURIComponent(name)}/count`,
|
|
5403
5507
|
query: Object.keys(findQuery).length > 0 ? { query: JSON.stringify(findQuery) } : void 0,
|
|
@@ -5437,7 +5541,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5437
5541
|
if (cursor !== void 0) body.cursor = cursor;
|
|
5438
5542
|
if (select !== void 0) body.select = select;
|
|
5439
5543
|
if (include_deleted !== void 0) body.include_deleted = include_deleted;
|
|
5440
|
-
return passthroughResult(await clientFetch(apiClient, {
|
|
5544
|
+
return passthroughResult(await clientFetch$1(apiClient, {
|
|
5441
5545
|
method: "POST",
|
|
5442
5546
|
path: `/collections/${encodeURIComponent(name)}/find`,
|
|
5443
5547
|
body,
|
|
@@ -5481,7 +5585,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5481
5585
|
if (where !== void 0) body.where = where;
|
|
5482
5586
|
if (cursor !== void 0) body.cursor = cursor;
|
|
5483
5587
|
if (include_deleted !== void 0) body.include_deleted = include_deleted;
|
|
5484
|
-
return passthroughResult(await clientFetch(apiClient, {
|
|
5588
|
+
return passthroughResult(await clientFetch$1(apiClient, {
|
|
5485
5589
|
method: "POST",
|
|
5486
5590
|
path: `/collections/${encodeURIComponent(name)}/find-nearest`,
|
|
5487
5591
|
body,
|
|
@@ -5522,7 +5626,7 @@ function registerTools$14(server, apiClient) {
|
|
|
5522
5626
|
if (filter !== void 0) body.filter = filter;
|
|
5523
5627
|
if (where !== void 0) body.where = where;
|
|
5524
5628
|
if (include_deleted !== void 0) body.include_deleted = include_deleted;
|
|
5525
|
-
return passthroughResult(await clientFetch(apiClient, {
|
|
5629
|
+
return passthroughResult(await clientFetch$1(apiClient, {
|
|
5526
5630
|
method: "POST",
|
|
5527
5631
|
path: `/collections/${encodeURIComponent(name)}/aggregate`,
|
|
5528
5632
|
body,
|
|
@@ -5645,7 +5749,7 @@ function jsonResult(payload) {
|
|
|
5645
5749
|
* of a generic upstream 400.
|
|
5646
5750
|
*/
|
|
5647
5751
|
const MAX_BUNDLE_BYTES = 10 * 1024 * 1024;
|
|
5648
|
-
function registerTools$
|
|
5752
|
+
function registerTools$18(server, apiClient) {
|
|
5649
5753
|
registerTool(server, apiClient, "amba_functions_deploy", [
|
|
5650
5754
|
"Deploy a function to an Amba project. The supplied `code` is a fully bundled JavaScript module (one entry file) — the deploy endpoint uploads it server-side and records a new active deployment row, superseding any prior active version for the same `name`.",
|
|
5651
5755
|
"Function name must match /^[a-z][a-z0-9_-]*$/ and be ≤58 chars. Bundle size cap: 10 MiB (enforced client-side too — oversize uploads are rejected before the round-trip).",
|
|
@@ -5718,6 +5822,42 @@ function registerTools$13(server, apiClient) {
|
|
|
5718
5822
|
throw err;
|
|
5719
5823
|
}
|
|
5720
5824
|
}, ["amba_delete_function"]);
|
|
5825
|
+
registerTool(server, apiClient, "amba_function_domains_attach", [
|
|
5826
|
+
"Attach one exact custom hostname to one deployed function. The complete incoming path and query string are preserved when the hostname routes to the function.",
|
|
5827
|
+
"MVP constraints: exact lowercase hostnames only; no wildcards or path rewrites. Attaching a subdomain does NOT create a `www` hostname automatically.",
|
|
5828
|
+
"The response contains provider-neutral certificate/ownership status plus DNS validation instructions. Publish the returned CNAME and validation records, then call `amba_function_domains_refresh` until `live=true` (both statuses active and routing reconciliation successful).",
|
|
5829
|
+
"MVP limits use the effective project tier (free 1, pro 5, scale 20, enterprise or comped 50), with five attach attempts per project per hour. An unverified claim becomes eligible for exact-host reclaim after 24 hours; Worker route/KV allocation waits for active ownership.",
|
|
5830
|
+
"Podcast/feed clients cannot supply an Amba API key. Their function must be deployed with `public: true`; validate any application-specific private token in the handler."
|
|
5831
|
+
].join(" "), {
|
|
5832
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5833
|
+
name: z.string().describe("Deployed function name."),
|
|
5834
|
+
hostname: z.string().describe("One exact lowercase hostname, for example feeds.podpod.host.")
|
|
5835
|
+
}, async ({ project_id, name, hostname }, { pat }) => {
|
|
5836
|
+
return jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/functions/${encodeURIComponent(name)}/domains`, { body: { hostname } }));
|
|
5837
|
+
});
|
|
5838
|
+
registerTool(server, apiClient, "amba_function_domains_list", "List exact custom hostnames attached to one function. Status is provider-neutral: `live=true` only when `cert_status` and `ownership_status` are active and required routing reconciliation succeeded.", {
|
|
5839
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5840
|
+
name: z.string().describe("Deployed function name.")
|
|
5841
|
+
}, async ({ project_id, name }, { pat }) => {
|
|
5842
|
+
return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/functions/${encodeURIComponent(name)}/domains`));
|
|
5843
|
+
});
|
|
5844
|
+
registerTool(server, apiClient, "amba_function_domains_refresh", "Refresh DNS ownership and certificate state for one exact function hostname. Publish any returned validation records and repeat until `live=true`.", {
|
|
5845
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5846
|
+
name: z.string().describe("Deployed function name."),
|
|
5847
|
+
hostname: z.string().describe("Exact attached hostname to refresh.")
|
|
5848
|
+
}, async ({ project_id, name, hostname }, { pat }) => {
|
|
5849
|
+
return jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/functions/${encodeURIComponent(name)}/domains/${encodeURIComponent(hostname)}/refresh`));
|
|
5850
|
+
});
|
|
5851
|
+
registerTool(server, apiClient, "amba_function_domains_remove", "Detach one exact custom hostname from a function. Requests to the detached hostname fail closed after routing propagation.", {
|
|
5852
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5853
|
+
name: z.string().describe("Deployed function name."),
|
|
5854
|
+
hostname: z.string().describe("Exact attached hostname to detach.")
|
|
5855
|
+
}, async ({ project_id, name, hostname }, { pat }) => {
|
|
5856
|
+
return jsonResult(await callWithPat(apiClient, pat, "DELETE", `/projects/${encodeURIComponent(project_id)}/functions/${encodeURIComponent(name)}/domains/${encodeURIComponent(hostname)}`) ?? {
|
|
5857
|
+
hostname,
|
|
5858
|
+
deleted: true
|
|
5859
|
+
});
|
|
5860
|
+
});
|
|
5721
5861
|
registerTool(server, apiClient, "amba_functions_schedule", [
|
|
5722
5862
|
"Register a cron Schedule for a function. The schedule is keyed deterministically per (project, function) — re-calling with a different cron/timezone replaces the prior schedule rather than creating a duplicate.",
|
|
5723
5863
|
"Cron must be a standard 5- or 6-field whitespace-separated expression. `timezone` defaults to \"UTC\" if omitted. Timezones are IANA strings (e.g. \"America/Los_Angeles\").",
|
|
@@ -5756,24 +5896,29 @@ function registerTools$13(server, apiClient) {
|
|
|
5756
5896
|
registerTool(server, apiClient, "amba_functions_get_logs", [
|
|
5757
5897
|
"Read function logs for one function over a bounded time range.",
|
|
5758
5898
|
"Default range: last 1 hour. Max range: last 24 hours. `since` and `until` are ISO 8601 timestamps.",
|
|
5759
|
-
"Default limit: 100 events. Max: 1000. Larger spans require operator-side queries — this tool is for \"tail my function\" UX."
|
|
5899
|
+
"Default limit: 100 events. Max: 1000. Larger spans require operator-side queries — this tool is for \"tail my function\" UX.",
|
|
5900
|
+
"Debugging one request? `since_request_id` starts the range at the oldest event mentioning that request id (\"everything from this request onward\"); `request_id` instead filters to only events mentioning it. The two compose."
|
|
5760
5901
|
].join(" "), {
|
|
5761
5902
|
project_id: z.string().describe("The Amba project ID."),
|
|
5762
5903
|
name: z.string().describe("Function name to read logs for."),
|
|
5763
5904
|
since: z.string().optional().describe("ISO 8601 lower bound (inclusive). Defaults to `until - 1h`."),
|
|
5764
5905
|
until: z.string().optional().describe("ISO 8601 upper bound (exclusive). Defaults to now."),
|
|
5765
|
-
limit: z.number().int().min(1).max(1e3).optional().describe("Max events (1-1000, default 100).")
|
|
5766
|
-
|
|
5906
|
+
limit: z.number().int().min(1).max(1e3).optional().describe("Max events (1-1000, default 100)."),
|
|
5907
|
+
request_id: z.string().optional().describe("Only return events whose log/exception text mentions this request id (r_...)."),
|
|
5908
|
+
since_request_id: z.string().optional().describe("Anchor the range start at the oldest event mentioning this request id (r_...). Searched within the last 24h, or within since/until when given. 404s if no event mentions it.")
|
|
5909
|
+
}, async ({ project_id, name, since, until, limit, request_id, since_request_id }, { pat }) => {
|
|
5767
5910
|
const query = {};
|
|
5768
5911
|
if (since !== void 0) query.since = since;
|
|
5769
5912
|
if (until !== void 0) query.until = until;
|
|
5770
5913
|
if (limit !== void 0) query.limit = String(limit);
|
|
5914
|
+
if (request_id !== void 0) query.request_id = request_id;
|
|
5915
|
+
if (since_request_id !== void 0) query.since_request_id = since_request_id;
|
|
5771
5916
|
return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/functions/${encodeURIComponent(name)}/logs`, { query }));
|
|
5772
5917
|
}, ["amba_get_function_logs"]);
|
|
5773
5918
|
}
|
|
5774
5919
|
//#endregion
|
|
5775
5920
|
//#region src/tools/sites.ts
|
|
5776
|
-
function registerTools$
|
|
5921
|
+
function registerTools$17(server, apiClient) {
|
|
5777
5922
|
registerTool(server, apiClient, "amba_sites_deploy", [
|
|
5778
5923
|
"Deploy a static site to an Amba project. Creates the site if it does not yet exist, then uploads the supplied files as a new deployment.",
|
|
5779
5924
|
"The deployment is immutable — its files live forever under the site's storage prefix — and the site flips to serve the new deployment atomically. Older deployments are kept for rollback via `amba_sites_list`.",
|
|
@@ -5842,6 +5987,14 @@ function registerTools$12(server, apiClient) {
|
|
|
5842
5987
|
}, async ({ project_id, name }, { pat }) => {
|
|
5843
5988
|
return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/sites/${encodeURIComponent(name)}/domains`));
|
|
5844
5989
|
}, ["amba_list_site_domains"]);
|
|
5990
|
+
registerTool(server, apiClient, "amba_sites_refresh_domain", "Force a fresh ownership and certificate lifecycle check for a site hostname. Returns both normalized states plus current validation records.", {
|
|
5991
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5992
|
+
name: z.string().describe("Site name."),
|
|
5993
|
+
hostname: z.string().describe("Hostname to refresh. Lowercased at the tool boundary.")
|
|
5994
|
+
}, async ({ project_id, name, hostname }, { pat }) => {
|
|
5995
|
+
const normalisedHostname = hostname.toLowerCase();
|
|
5996
|
+
return jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/sites/${encodeURIComponent(name)}/domains/${encodeURIComponent(normalisedHostname)}/refresh-cert`));
|
|
5997
|
+
}, ["amba_refresh_site_domain"]);
|
|
5845
5998
|
registerTool(server, apiClient, "amba_sites_remove_domain", "Detach a custom hostname from a site. The upstream binding is dropped server-side; the DB row is removed; the host:<hostname> KV entry is cleared. Idempotent — removing a hostname that is no longer attached returns success.", {
|
|
5846
5999
|
project_id: z.string().describe("The Amba project ID."),
|
|
5847
6000
|
name: z.string().describe("Site name."),
|
|
@@ -5865,7 +6018,7 @@ function registerTools$12(server, apiClient) {
|
|
|
5865
6018
|
}
|
|
5866
6019
|
//#endregion
|
|
5867
6020
|
//#region src/tools/domains.ts
|
|
5868
|
-
function registerTools$
|
|
6021
|
+
function registerTools$16(server, apiClient) {
|
|
5869
6022
|
registerTool(server, apiClient, "amba_domains_search", ["Search for available domains to buy through Amba. Pass a keyword or a full domain; returns candidate domains with availability and first-year price (USD).", "Free — searching never costs anything. Use this to help a user pick a name, then `amba_domains_check` to price specific picks and `amba_domains_purchase` to buy."].join(" "), {
|
|
5870
6023
|
project_id: z.string().describe("The Amba project ID."),
|
|
5871
6024
|
query: z.string().describe("A keyword (e.g. \"getunbury\") or a full domain (e.g. \"unbury.com\")."),
|
|
@@ -5918,10 +6071,63 @@ function registerTools$11(server, apiClient) {
|
|
|
5918
6071
|
if (offset !== void 0) query.offset = String(offset);
|
|
5919
6072
|
return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/domains`, { query }));
|
|
5920
6073
|
});
|
|
6074
|
+
registerTool(server, apiClient, "amba_domains_email_status", "Get inbound email-routing status for a domain purchased through Amba.", {
|
|
6075
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
6076
|
+
domain: z.string().describe("Purchased domain, e.g. \"example.com\".")
|
|
6077
|
+
}, async ({ project_id, domain }, { pat }) => jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/domains/${encodeURIComponent(domain.toLowerCase())}/email`)));
|
|
6078
|
+
registerTool(server, apiClient, "amba_domains_email_enable", ["Enable free inbound email routing for a domain purchased through Amba.", "This safely refuses when the domain already has MX records for another mail provider, so it cannot silently replace an existing mailbox service."].join(" "), {
|
|
6079
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
6080
|
+
domain: z.string().describe("Purchased domain, e.g. \"example.com\".")
|
|
6081
|
+
}, async ({ project_id, domain }, { pat }) => jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/domains/${encodeURIComponent(domain.toLowerCase())}/email/enable`, { body: {} })));
|
|
6082
|
+
registerTool(server, apiClient, "amba_domains_email_destinations_add", ["Add a mailbox that can receive forwarded mail for a purchased domain.", "The mailbox owner must click the verification email before a forward can activate; pending results return action_required:true."].join(" "), {
|
|
6083
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
6084
|
+
domain: z.string().describe("Purchased domain."),
|
|
6085
|
+
email: z.string().email().describe("External mailbox that will receive forwarded mail.")
|
|
6086
|
+
}, async ({ project_id, domain, email }, { pat }) => jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/domains/${encodeURIComponent(domain.toLowerCase())}/email/destinations`, { body: { email: email.toLowerCase() } })));
|
|
6087
|
+
registerTool(server, apiClient, "amba_domains_email_destinations_list", "List only this project’s forwarding destinations associated with a purchased domain.", {
|
|
6088
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
6089
|
+
domain: z.string().describe("Purchased domain.")
|
|
6090
|
+
}, async ({ project_id, domain }, { pat }) => jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/domains/${encodeURIComponent(domain.toLowerCase())}/email/destinations`)));
|
|
6091
|
+
registerTool(server, apiClient, "amba_domains_email_destinations_get", "Poll a forwarding destination after its owner clicks the verification email.", {
|
|
6092
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
6093
|
+
domain: z.string().describe("Purchased domain."),
|
|
6094
|
+
destination_id: z.string().describe("Destination ID returned by the add/list tool.")
|
|
6095
|
+
}, async ({ project_id, domain, destination_id }, { pat }) => jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/domains/${encodeURIComponent(domain.toLowerCase())}/email/destinations/${encodeURIComponent(destination_id)}`)));
|
|
6096
|
+
registerTool(server, apiClient, "amba_domains_email_forwards_set", ["Create or update a literal email forward such as support@example.com → owner@gmail.com.", "The tool enables routing if safe and starts destination verification when needed. If action_required:true is returned, have the mailbox owner click the verification email, poll the destination, then repeat this same call."].join(" "), {
|
|
6097
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
6098
|
+
domain: z.string().describe("Purchased domain."),
|
|
6099
|
+
source: z.string().describe("Local part or full address on the domain, e.g. \"support\" or \"support@example.com\"."),
|
|
6100
|
+
destination: z.string().email().describe("Verified external mailbox to forward into.")
|
|
6101
|
+
}, async ({ project_id, domain, source, destination }, { pat }) => jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/domains/${encodeURIComponent(domain.toLowerCase())}/email/forwards`, { body: {
|
|
6102
|
+
source,
|
|
6103
|
+
destination: destination.toLowerCase()
|
|
6104
|
+
} })));
|
|
6105
|
+
registerTool(server, apiClient, "amba_domains_email_forwards_list", "List literal email forwards configured on a purchased domain.", {
|
|
6106
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
6107
|
+
domain: z.string().describe("Purchased domain.")
|
|
6108
|
+
}, async ({ project_id, domain }, { pat }) => jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/domains/${encodeURIComponent(domain.toLowerCase())}/email/forwards`)));
|
|
6109
|
+
registerTool(server, apiClient, "amba_domains_email_forwards_delete", "Delete a literal email forward from a purchased domain.", {
|
|
6110
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
6111
|
+
domain: z.string().describe("Purchased domain."),
|
|
6112
|
+
forward_id: z.string().describe("Forward ID returned by the set/list tool.")
|
|
6113
|
+
}, async ({ project_id, domain, forward_id }, { pat }) => jsonResult(await callWithPat(apiClient, pat, "DELETE", `/projects/${encodeURIComponent(project_id)}/domains/${encodeURIComponent(domain.toLowerCase())}/email/forwards/${encodeURIComponent(forward_id)}`)));
|
|
6114
|
+
registerTool(server, apiClient, "amba_domains_email_catch_all_get", "Get the catch-all forwarding rule for a purchased domain.", {
|
|
6115
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
6116
|
+
domain: z.string().describe("Purchased domain.")
|
|
6117
|
+
}, async ({ project_id, domain }, { pat }) => jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/domains/${encodeURIComponent(domain.toLowerCase())}/email/catch-all`)));
|
|
6118
|
+
registerTool(server, apiClient, "amba_domains_email_catch_all_set", ["Enable, change, or disable catch-all forwarding for every unmatched address on a purchased domain.", "When enabling, destination is required and must be verified. A pending mailbox returns action_required:true."].join(" "), {
|
|
6119
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
6120
|
+
domain: z.string().describe("Purchased domain."),
|
|
6121
|
+
enabled: z.boolean().describe("True to enable/update catch-all; false to disable it."),
|
|
6122
|
+
destination: z.string().email().optional().describe("External mailbox. Required when enabled=true; omit when disabling.")
|
|
6123
|
+
}, async ({ project_id, domain, enabled, destination }, { pat }) => jsonResult(await callWithPat(apiClient, pat, "PUT", `/projects/${encodeURIComponent(project_id)}/domains/${encodeURIComponent(domain.toLowerCase())}/email/catch-all`, { body: {
|
|
6124
|
+
enabled,
|
|
6125
|
+
...destination ? { destination: destination.toLowerCase() } : {}
|
|
6126
|
+
} })));
|
|
5921
6127
|
}
|
|
5922
6128
|
//#endregion
|
|
5923
6129
|
//#region src/tools/operations.ts
|
|
5924
|
-
function registerTools$
|
|
6130
|
+
function registerTools$15(server, apiClient) {
|
|
5925
6131
|
registerTool(server, apiClient, "amba_operations_get", [
|
|
5926
6132
|
"Poll an async operation by its operation_id to see whether it has finished.",
|
|
5927
6133
|
"Returns the operation `status` (pending | running | succeeded | failed); on `failed`, `failed_reason` says why, and on `succeeded`, `result` holds the outcome.",
|
|
@@ -5954,17 +6160,24 @@ function registerTools$10(server, apiClient) {
|
|
|
5954
6160
|
}
|
|
5955
6161
|
//#endregion
|
|
5956
6162
|
//#region src/tools/secrets.ts
|
|
5957
|
-
|
|
6163
|
+
const SET_SECRET_NAME = z.string().regex(/^[A-Z][A-Z0-9_]{0,62}$/, "Secret name must match /^[A-Z][A-Z0-9_]{0,62}$/ (uppercase, starts with a letter, max 63 characters)").superRefine((name, ctx) => {
|
|
6164
|
+
const reason = getBindingReservationReason(name);
|
|
6165
|
+
if (reason !== null) ctx.addIssue({
|
|
6166
|
+
code: z.ZodIssueCode.custom,
|
|
6167
|
+
message: reason
|
|
6168
|
+
});
|
|
6169
|
+
});
|
|
6170
|
+
function registerTools$14(server, apiClient) {
|
|
5958
6171
|
registerTool(server, apiClient, "amba_secrets_set", [
|
|
5959
6172
|
"Set or rotate a secret. Omit `function` for a PROJECT-WIDE secret (one value visible to every function in the project, including ones deployed later) — set it once instead of repeating per function. Pass `function` to scope the secret to just that function.",
|
|
5960
6173
|
"Writes a new version to the backing secret store and enqueues the sync. Subsequent function invocations see the new value once sync_status flips to \"synced\" (typically <30s).",
|
|
5961
6174
|
"Secret name must match /^[A-Z][A-Z0-9_]{0,62}$/. Function name (when given) must match /^[a-z][a-z0-9_-]{0,57}$/.",
|
|
5962
|
-
"
|
|
6175
|
+
"The complete AMBA_ and EDGE_ namespaces, plus the exact names STORAGE and EDGE_DB_PROXY, are platform-managed. MCP rejects them before an API/provider call; the API also returns 400 RESERVED_BINDING.",
|
|
5963
6176
|
"Secrets may be set BEFORE deploying the function — the sync drains once a deployment lands, so you can set every secret first and deploy once.",
|
|
5964
6177
|
"Value cap: 64 KiB. Plaintext is never returned afterwards."
|
|
5965
6178
|
].join(" "), {
|
|
5966
6179
|
project_id: z.string().describe("The Amba project ID."),
|
|
5967
|
-
name:
|
|
6180
|
+
name: SET_SECRET_NAME.describe("Secret name. UPPER_SNAKE_CASE. The AMBA_ and EDGE_ prefixes and exact names STORAGE and EDGE_DB_PROXY are reserved."),
|
|
5968
6181
|
value: z.string().describe("Secret value. Stored encrypted at rest; never returned in plaintext afterwards."),
|
|
5969
6182
|
function: z.string().optional().describe("Function this secret binds to. OMIT for a project-wide secret (applied to every function in the project).")
|
|
5970
6183
|
}, async ({ project_id, name, value, function: functionName }, { pat }) => {
|
|
@@ -5996,7 +6209,7 @@ function registerTools$9(server, apiClient) {
|
|
|
5996
6209
|
return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/secrets`));
|
|
5997
6210
|
}, ["amba_list_secrets"]);
|
|
5998
6211
|
registerTool(server, apiClient, "amba_secrets_delete", [
|
|
5999
|
-
"Delete a secret. Removes the
|
|
6212
|
+
"Delete a secret. Removes the backing-store value and durably queues runtime-binding revocation across every affected Worker script. Provider failures retry automatically; the control row remains visible with a revocation status until cleanup succeeds.",
|
|
6000
6213
|
"Pass `function` to delete a function-scoped secret; OMIT it to delete the project-wide secret of this name.",
|
|
6001
6214
|
"Idempotent: deleting a secret that no longer exists succeeds."
|
|
6002
6215
|
].join(" "), {
|
|
@@ -6008,6 +6221,7 @@ function registerTools$9(server, apiClient) {
|
|
|
6008
6221
|
try {
|
|
6009
6222
|
return jsonResult(await callWithPat(apiClient, pat, "DELETE", `/projects/${encodeURIComponent(project_id)}/secrets/${encodeURIComponent(name)}`, functionName === void 0 ? {} : { query: { function: functionName } }) ?? {
|
|
6010
6223
|
deleted: true,
|
|
6224
|
+
revocation_queued: true,
|
|
6011
6225
|
name,
|
|
6012
6226
|
function: scope
|
|
6013
6227
|
});
|
|
@@ -6153,9 +6367,9 @@ function normalizeContentBlocks(content, provider) {
|
|
|
6153
6367
|
* Map a registered prompt's `messages` into a single provider-native format AND
|
|
6154
6368
|
* (optionally) attach extra `images` to the last user message.
|
|
6155
6369
|
*
|
|
6156
|
-
*
|
|
6157
|
-
*
|
|
6158
|
-
*
|
|
6370
|
+
* `amba_ai_prompts_invoke` keeps its provider-shaped admin-test envelope for
|
|
6371
|
+
* backwards compatibility. Any VENDOR-NEUTRAL `{type:'image', url|data}` block
|
|
6372
|
+
* — whether it came
|
|
6159
6373
|
* from the `images` convenience param OR was put directly in `messages` by the
|
|
6160
6374
|
* caller — must be mapped to the provider's native shape HERE.
|
|
6161
6375
|
*
|
|
@@ -6266,7 +6480,7 @@ function geminiContentsFromMessages(messages, images) {
|
|
|
6266
6480
|
}
|
|
6267
6481
|
return contents.filter((c) => c.parts.length > 0);
|
|
6268
6482
|
}
|
|
6269
|
-
function registerTools$
|
|
6483
|
+
function registerTools$13(server, apiClient) {
|
|
6270
6484
|
registerTool(server, apiClient, "amba_ai_prompts_create", [
|
|
6271
6485
|
"Register a new AI prompt template on a project. Returns the persisted prompt row (name, version, provider, model, client_invokable).",
|
|
6272
6486
|
"Name must match /^[a-z][a-z0-9_-]{0,127}$/. Provider must be \"anthropic\", \"openai\", \"mistral\", or \"gemini\" and must already be registered via `amba_ai_providers_set`. Model is the provider-native model id (e.g. \"claude-sonnet-4-5-20250929\", \"gpt-4o-2024-08-06\", \"gemini-2.5-flash\").",
|
|
@@ -6334,9 +6548,9 @@ function registerTools$8(server, apiClient) {
|
|
|
6334
6548
|
}, ["amba_delete_ai_prompt"]);
|
|
6335
6549
|
registerTool(server, apiClient, "amba_ai_prompts_invoke", [
|
|
6336
6550
|
"Server-side test invocation of a registered AI prompt. Looks up the prompt's provider + model + system_prompt + max_tokens, then proxies the call through the admin AI gateway using the customer's registered provider key. Works for every provider — Anthropic / OpenAI / Mistral build a Messages-style body; Gemini builds its contents[] body automatically.",
|
|
6337
|
-
"Provide `messages` as a simple `[{role: \"user\", content: \"...\"}]` array (roles: \"user\"/\"assistant\"/\"system\"; for Gemini, \"assistant\" maps to \"model\" and \"system\" is taken from the prompt).
|
|
6551
|
+
"Provide `messages` as a simple `[{role: \"user\", content: \"...\"}]` array (roles: \"user\"/\"assistant\"/\"system\"; for Gemini, \"assistant\" maps to \"model\" and \"system\" is taken from the prompt). This server/admin path works even when `client_invokable=false` and enforces the named prompt budget, rate limit, and usage attribution.",
|
|
6338
6552
|
"To send IMAGES to a vision-capable model, pass `images` (each `{url}` or `{data, mime}`) — they attach to the last user message, mapped to the provider's native image format for you. The prompt's model must be vision-capable.",
|
|
6339
|
-
"`extra_body` lets you pass
|
|
6553
|
+
"`extra_body` lets you pass allowlisted provider-native tuning parameters (temperature, top_p, response_format, …). The registered prompt model and configured max-token cap remain authoritative."
|
|
6340
6554
|
].join(" "), {
|
|
6341
6555
|
project_id: z.string().describe("The Amba project ID."),
|
|
6342
6556
|
name: z.string().describe("Registered prompt name."),
|
|
@@ -6418,7 +6632,7 @@ function registerTools$8(server, apiClient) {
|
|
|
6418
6632
|
body.generationConfig = gc;
|
|
6419
6633
|
} else for (const [k, v] of Object.entries(extra_body)) body[k] = v;
|
|
6420
6634
|
}
|
|
6421
|
-
return jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/ai/
|
|
6635
|
+
return jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/ai/prompts/${encodeURIComponent(name)}/invoke`, { body: {
|
|
6422
6636
|
provider: prompt.provider,
|
|
6423
6637
|
body
|
|
6424
6638
|
} }));
|
|
@@ -6462,7 +6676,7 @@ const PROVIDER_ENUM = z.enum([
|
|
|
6462
6676
|
"mistral",
|
|
6463
6677
|
"gemini"
|
|
6464
6678
|
]);
|
|
6465
|
-
function registerTools$
|
|
6679
|
+
function registerTools$12(server, apiClient) {
|
|
6466
6680
|
registerTool(server, apiClient, "amba_ai_providers_set", [
|
|
6467
6681
|
"Register or rotate the upstream AI provider API key for a project. This is the key the AI gateway uses to call the model on your behalf when you invoke a prompt or `Amba.ai.*`.",
|
|
6468
6682
|
"Call this BEFORE `amba_ai_prompts_create` — a prompt references a registered provider, and invocations fail with `provider_not_configured` (424) until a key is set.",
|
|
@@ -6502,7 +6716,7 @@ const STATUS_ENUM = z.enum([
|
|
|
6502
6716
|
"paused",
|
|
6503
6717
|
"ended"
|
|
6504
6718
|
]).describe("active = assigns + exposes new users; paused = serves existing assignments but issues no new ones; ended = read-only.");
|
|
6505
|
-
function registerTools$
|
|
6719
|
+
function registerTools$11(server, apiClient) {
|
|
6506
6720
|
registerTool(server, apiClient, "amba_experiments_create", [
|
|
6507
6721
|
"Create an A/B experiment: a stable `key`, a display `name`, and >= 2 weighted variants.",
|
|
6508
6722
|
"End-users are bucketed into a STICKY variant the first time they request an assignment",
|
|
@@ -6605,25 +6819,28 @@ function registerTools$6(server, apiClient) {
|
|
|
6605
6819
|
}
|
|
6606
6820
|
//#endregion
|
|
6607
6821
|
//#region src/tools/promotion.ts
|
|
6608
|
-
function registerTools$
|
|
6822
|
+
function registerTools$10(server, apiClient) {
|
|
6609
6823
|
registerTool(server, apiClient, "amba_project_export", [
|
|
6610
6824
|
"Export a declarative bundle of a project's reusable configuration so it can be re-created in another project (e.g. promote dev → prod) in one operation.",
|
|
6611
|
-
"The bundle includes (each section only when present): remote configs, collection SCHEMAS (column defs + indexes, no rows), content libraries + their items,
|
|
6612
|
-
"It carries NO secrets, NO per-user data, and NO end-user rows. Pass the returned `bundle` to `amba_project_import` against the target project."
|
|
6825
|
+
"The bundle includes (each section only when present): remote configs, collection SCHEMAS (column defs + indexes, no rows), content libraries + their items, the full gamification set — currencies, achievements, streaks, challenges, leaderboards, and xp rules — as DEFINITIONS only, plus integrations (push providers et al.) as non-secret config: bundle id, team/key ids, environment travel; `.p8` keys, service-account JSON, and API keys are stripped and marked `credentials: \"stripped\"`.",
|
|
6826
|
+
"It carries NO secrets, NO per-user data, and NO end-user rows — safe to save to disk, commit, or diff. Pass the returned `bundle` to `amba_project_import` against the target project; if both projects belong to the same developer, import with `include_integration_credentials: true` and the credentials are copied server-side too, so push works on the target without re-uploading keys."
|
|
6613
6827
|
].join(" "), { project_id: z.string().describe("The Amba project ID to export configuration FROM.") }, async ({ project_id }, { pat }) => {
|
|
6614
6828
|
return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/promotion/export`));
|
|
6615
6829
|
});
|
|
6616
6830
|
registerTool(server, apiClient, "amba_project_import", [
|
|
6617
|
-
"Import a declarative configuration bundle (from `amba_project_export`) into a project. Idempotently creates remote configs, collections, content libraries + items,
|
|
6618
|
-
"mode=\"skip_existing\" (default) leaves any entity that already exists (by name/key/code) untouched; mode=\"merge\" refreshes the definition of existing entities. Either mode is safe to run twice — re-import never duplicates.",
|
|
6619
|
-
"
|
|
6831
|
+
"Import a declarative configuration bundle (from `amba_project_export`) into a project. Idempotently creates remote configs, collections, content libraries + items, gamification definitions (currencies, achievements, streaks, challenges, leaderboards, xp rules), and integrations.",
|
|
6832
|
+
"mode=\"skip_existing\" (default) leaves any entity that already exists (by name/key/code) untouched; mode=\"merge\" refreshes the definition of existing entities. Either mode is safe to run twice — re-import never duplicates, and merge never clobbers credentials an existing integration already has.",
|
|
6833
|
+
"Integrations: bundles never carry credential material. By default each imported integration lands inactive in a `pending_credentials` state and the response tells you exactly which call uploads the credentials (`amba_integrations_configure`). When the source and target projects belong to the SAME developer, pass `include_integration_credentials: true` instead — the stored credentials are copied server-side between the projects (never through this conversation) and the integrations activate immediately, making dev → prod push promotion a single call. Cross-developer copies are refused with a 403.",
|
|
6834
|
+
"Returns a per-section summary of { created, skipped, updated } plus a per-integration status list. Collections are create-if-absent only (an existing collection is skipped; alter the schema via the dedicated collection tools)."
|
|
6620
6835
|
].join(" "), {
|
|
6621
6836
|
project_id: z.string().describe("The Amba project ID to import configuration INTO."),
|
|
6622
6837
|
bundle: z.record(z.unknown()).describe("The bundle object returned by `amba_project_export` (its `data` field)."),
|
|
6623
|
-
mode: z.enum(["merge", "skip_existing"]).optional().describe("Conflict resolution. \"skip_existing\" (default) no-ops on a name/key/code conflict; \"merge\" updates the existing definition.")
|
|
6624
|
-
|
|
6838
|
+
mode: z.enum(["merge", "skip_existing"]).optional().describe("Conflict resolution. \"skip_existing\" (default) no-ops on a name/key/code conflict; \"merge\" updates the existing definition."),
|
|
6839
|
+
include_integration_credentials: z.boolean().optional().describe("Copy integration credentials (push keys, provider API keys) server-side from the bundle's source project and activate the integrations on the target. Only allowed when both projects belong to the same developer; otherwise the import is refused. Omit (default) to create integrations in a pending_credentials state and upload credentials separately via `amba_integrations_configure`.")
|
|
6840
|
+
}, async ({ project_id, bundle, mode, include_integration_credentials }, { pat }) => {
|
|
6625
6841
|
const body = { bundle };
|
|
6626
6842
|
if (mode !== void 0) body.mode = mode;
|
|
6843
|
+
if (include_integration_credentials !== void 0) body.include_integration_credentials = include_integration_credentials;
|
|
6627
6844
|
return jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/promotion/import`, { body }));
|
|
6628
6845
|
});
|
|
6629
6846
|
}
|
|
@@ -6703,7 +6920,11 @@ const TIER_CATALOG = {
|
|
|
6703
6920
|
unit_description: "$0.50 per 10K push"
|
|
6704
6921
|
},
|
|
6705
6922
|
db_storage_per_gb_month_usd: 1.5,
|
|
6706
|
-
media_storage_per_gb_month_usd: .1
|
|
6923
|
+
media_storage_per_gb_month_usd: .1,
|
|
6924
|
+
app_mcp_tool_calls: {
|
|
6925
|
+
per_unit_usd: .001,
|
|
6926
|
+
unit_description: "$1.00 per 1K agent tool calls"
|
|
6927
|
+
}
|
|
6707
6928
|
},
|
|
6708
6929
|
telemetry_event_overage: {
|
|
6709
6930
|
per_unit_usd: 1e-7,
|
|
@@ -6711,8 +6932,8 @@ const TIER_CATALOG = {
|
|
|
6711
6932
|
note: "Use Amba.track(name, props, { telemetry: true }) for high-volume telemetry. Cheaper, no fan-out to segments/workflows/push."
|
|
6712
6933
|
}
|
|
6713
6934
|
};
|
|
6714
|
-
function registerTools$
|
|
6715
|
-
registerTool(server, apiClient, "amba_billing_status", "Get the
|
|
6935
|
+
function registerTools$9(server, apiClient) {
|
|
6936
|
+
registerTool(server, apiClient, "amba_billing_status", "Get the persisted billing `tier`, runtime `effective_tier` (use this for quota/feature decisions), comp status, current-period usage by meter (MAU, events, push, db_storage, media_storage, agent tool calls) with per-meter cost, the billing period and its reset date, spend ceiling + percent consumed, a projected end-of-period overage, the live enforcement state (whether the project is in read-only mode — metered writes returning 402 — and which meters are over quota), and what (if any) human action is required before the agent can continue safely. Use this BEFORE provisioning new features or running data-heavy workloads so the agent can self-throttle or escalate to the human.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
6716
6937
|
const result = await client.get(`/projects/${project_id}/billing/status`);
|
|
6717
6938
|
return { content: [{
|
|
6718
6939
|
type: "text",
|
|
@@ -6725,7 +6946,7 @@ function registerTools$4(server, apiClient) {
|
|
|
6725
6946
|
text: JSON.stringify(TIER_CATALOG, null, 2)
|
|
6726
6947
|
}] };
|
|
6727
6948
|
});
|
|
6728
|
-
registerTool(server, apiClient, "amba_billing_set_ceiling", "Set or remove the monthly spend ceiling for a project. When
|
|
6949
|
+
registerTool(server, apiClient, "amba_billing_set_ceiling", "Set or remove the monthly spend ceiling for a project. When the current billing period's overage cost reaches the ceiling, the project degrades to read-only mode: metered WRITE operations (tracking events, inserting collection rows, uploads, push sends, new signups) return 402 with a machine-readable payload (code, current usage, ceiling, reset date) while reads keep working. Control-plane webhook events `billing.ceiling_warning` / `billing.ceiling_reached` fire once per period at 80% / 100% so you can alert. The new ceiling takes effect immediately. Pass ceiling_usd: null to remove the ceiling (linear overage with no cap). This is a write action — surface to the human before calling.", {
|
|
6729
6950
|
project_id: z.string().describe("The project ID"),
|
|
6730
6951
|
ceiling_usd: z.number().min(0).max(1e5).nullable().describe("Maximum monthly bill in USD, or null to disable")
|
|
6731
6952
|
}, async ({ project_id, ceiling_usd }, { client }) => {
|
|
@@ -6738,7 +6959,7 @@ function registerTools$4(server, apiClient) {
|
|
|
6738
6959
|
}
|
|
6739
6960
|
//#endregion
|
|
6740
6961
|
//#region src/tools/payments.ts
|
|
6741
|
-
function registerTools$
|
|
6962
|
+
function registerTools$8(server, apiClient) {
|
|
6742
6963
|
registerTool(server, apiClient, "amba_payments_account_create", "Create the project's connected payments account so the app can accept payments from its users (the app is the seller; Amba takes a platform fee, Stripe carries the money). Idempotent — returns the existing account if one is already set up. After this, call amba_payments_onboarding_link to finish setup. Note: payments must be enabled on the Amba platform by the platform owner before live accounts can be created.", {
|
|
6743
6964
|
project_id: z.string().describe("The project ID"),
|
|
6744
6965
|
country: z.string().length(2).optional().describe("Two-letter ISO country code for the seller (e.g. \"US\")"),
|
|
@@ -6791,6 +7012,23 @@ function registerTools$3(server, apiClient) {
|
|
|
6791
7012
|
text: JSON.stringify(result, null, 2)
|
|
6792
7013
|
}] };
|
|
6793
7014
|
});
|
|
7015
|
+
registerTool(server, apiClient, "amba_payments_refund", "Refund a payment, in full or in part. Pass the payment id (returned by amba_payments_charge) and optionally an amount in the smallest currency unit (cents) for a partial refund — omit amount to refund the full charge. The customer is refunded and BOTH the platform fee and the seller transfer are reversed proportionally, so neither Amba nor the seller is left funding it. Money-moving and not reversible — surface to the human before calling.", {
|
|
7016
|
+
project_id: z.string().describe("The project ID"),
|
|
7017
|
+
payment_id: z.string().describe("The payment id to refund (returned by amba_payments_charge)"),
|
|
7018
|
+
amount: z.number().int().positive().optional().describe("Partial refund amount in cents; omit to refund the full charge"),
|
|
7019
|
+
reason: z.enum([
|
|
7020
|
+
"duplicate",
|
|
7021
|
+
"fraudulent",
|
|
7022
|
+
"requested_by_customer"
|
|
7023
|
+
]).optional().describe("Optional refund reason"),
|
|
7024
|
+
reference: z.string().optional().describe("Opaque idempotency reference: pass the same value to make a retry safe (returns the same refund); omit it and each call issues a new refund")
|
|
7025
|
+
}, async ({ project_id, payment_id, ...body }, { client }) => {
|
|
7026
|
+
const result = await client.post(`/projects/${project_id}/payments/charges/${payment_id}/refund`, body);
|
|
7027
|
+
return { content: [{
|
|
7028
|
+
type: "text",
|
|
7029
|
+
text: JSON.stringify(result, null, 2)
|
|
7030
|
+
}] };
|
|
7031
|
+
});
|
|
6794
7032
|
registerTool(server, apiClient, "amba_payments_balance", "Read the seller's available and pending payment balance per currency. Use this to show the developer how much they've earned that hasn't yet been paid out.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
6795
7033
|
const result = await client.get(`/projects/${project_id}/payments/balance`);
|
|
6796
7034
|
return { content: [{
|
|
@@ -6811,8 +7049,29 @@ function registerTools$3(server, apiClient) {
|
|
|
6811
7049
|
});
|
|
6812
7050
|
}
|
|
6813
7051
|
//#endregion
|
|
7052
|
+
//#region src/tools/agent-checkout.ts
|
|
7053
|
+
function registerTools$7(server, apiClient) {
|
|
7054
|
+
registerTool(server, apiClient, "amba_agent_checkout_enable", "Turn agent checkout on (or off) for a project, so your users' AI agents can pay inside your app and have entitlements unlock in the same motion. Default-deny: it stays off until you enable it. Requires a connected payments account first (set one up with amba_payments_account_create). Returns the per-rail capabilities so you can see which payment methods the account can settle. Money-affecting setting — surface it to the human before calling.", {
|
|
7055
|
+
project_id: z.string().describe("The project ID"),
|
|
7056
|
+
enabled: z.boolean().describe("true to enable agent checkout, false to disable")
|
|
7057
|
+
}, async ({ project_id, enabled }, { client }) => {
|
|
7058
|
+
const result = await client.post(`/projects/${project_id}/agent-checkout/enable`, { enabled });
|
|
7059
|
+
return { content: [{
|
|
7060
|
+
type: "text",
|
|
7061
|
+
text: JSON.stringify(result, null, 2)
|
|
7062
|
+
}] };
|
|
7063
|
+
});
|
|
7064
|
+
registerTool(server, apiClient, "amba_agent_checkout_capabilities", "Read which agent-checkout settlement rails a project can offer (and, when a rail is unavailable, exactly why). Use this before enabling agent checkout, or to confirm a project is ready to accept agent-initiated payments. Returns enabled plus a per-rail availability list.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
7065
|
+
const result = await client.get(`/projects/${project_id}/agent-checkout/capabilities`);
|
|
7066
|
+
return { content: [{
|
|
7067
|
+
type: "text",
|
|
7068
|
+
text: JSON.stringify(result, null, 2)
|
|
7069
|
+
}] };
|
|
7070
|
+
});
|
|
7071
|
+
}
|
|
7072
|
+
//#endregion
|
|
6814
7073
|
//#region src/tools/invites.ts
|
|
6815
|
-
function registerTools$
|
|
7074
|
+
function registerTools$6(server, apiClient) {
|
|
6816
7075
|
registerTool(server, apiClient, "amba_projects_invite_member", "Invite a teammate by email to collaborate on this project. Returns the invite URL the human should send to their teammate. Roles: admin (can manage members + integrations), member (can write data), viewer (read-only).", {
|
|
6817
7076
|
project_id: z.string().describe("The project ID"),
|
|
6818
7077
|
email: z.string().describe("Teammate's email address"),
|
|
@@ -6861,122 +7120,167 @@ function registerTools$2(server, apiClient) {
|
|
|
6861
7120
|
});
|
|
6862
7121
|
}
|
|
6863
7122
|
//#endregion
|
|
6864
|
-
//#region src/tools/
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
7123
|
+
//#region src/tools/orgs.ts
|
|
7124
|
+
/**
|
|
7125
|
+
* Organization tools (migration 049). Orgs are the ownership + payment-entity
|
|
7126
|
+
* boundary; affiliates and app-factory child projects are all orgs. These wrap
|
|
7127
|
+
* the `/v1/admin/orgs*` and `/v1/admin/provision` routes.
|
|
7128
|
+
*/
|
|
7129
|
+
function registerTools$5(server, apiClient) {
|
|
7130
|
+
registerTool(server, apiClient, "amba_orgs_create", "Create an organization. Optionally nest it under a parent org (parent_org_id) — a child org can bill to its parent (payment_source=\"parent\") or hold its own card (payment_source=\"self\").", {
|
|
7131
|
+
name: z.string().describe("Human-readable org name"),
|
|
7132
|
+
parent_org_id: z.string().optional().describe("Parent org UUID to nest under (omit for a top-level org)"),
|
|
7133
|
+
payment_source: z.enum(["self", "parent"]).optional().describe("\"self\" = bill this org's own card; \"parent\" = bill the nearest self-paying ancestor. Default \"self\"."),
|
|
7134
|
+
slug: z.string().optional().describe("Optional unique URL slug (lowercase alphanumeric/hyphen)"),
|
|
7135
|
+
external_ref: z.string().optional().describe("Caller-stable idempotency key (unique per parent)")
|
|
7136
|
+
}, async ({ name, parent_org_id, payment_source, slug, external_ref }, { client }) => {
|
|
7137
|
+
const body = { name };
|
|
7138
|
+
if (parent_org_id !== void 0) body["parent_org_id"] = parent_org_id;
|
|
7139
|
+
if (payment_source !== void 0) body["payment_source"] = payment_source;
|
|
7140
|
+
if (slug !== void 0) body["slug"] = slug;
|
|
7141
|
+
if (external_ref !== void 0) body["external_ref"] = external_ref;
|
|
7142
|
+
const result = await client.post("/orgs", body);
|
|
6875
7143
|
return { content: [{
|
|
6876
7144
|
type: "text",
|
|
6877
7145
|
text: JSON.stringify(result, null, 2)
|
|
6878
7146
|
}] };
|
|
6879
7147
|
});
|
|
6880
|
-
registerTool(server, apiClient, "
|
|
6881
|
-
const result = await client.get(
|
|
7148
|
+
registerTool(server, apiClient, "amba_orgs_list", "List organizations the authenticated developer can access, including descendant orgs in the hierarchy.", {}, async (_, { client }) => {
|
|
7149
|
+
const result = await client.get("/orgs");
|
|
6882
7150
|
return { content: [{
|
|
6883
7151
|
type: "text",
|
|
6884
7152
|
text: JSON.stringify(result, null, 2)
|
|
6885
7153
|
}] };
|
|
6886
7154
|
});
|
|
6887
|
-
registerTool(server, apiClient, "
|
|
6888
|
-
|
|
6889
|
-
subscription_id: z.string().describe("The subscription UUID")
|
|
6890
|
-
}, async ({ project_id, subscription_id }, { client }) => {
|
|
6891
|
-
const result = await client.get(`/projects/${project_id}/webhooks/subscriptions/${subscription_id}`);
|
|
7155
|
+
registerTool(server, apiClient, "amba_orgs_get", "Get an organization by id, including its child-org and project counts and your effective role.", { org_id: z.string().describe("The org UUID") }, async ({ org_id }, { client }) => {
|
|
7156
|
+
const result = await client.get(`/orgs/${org_id}`);
|
|
6892
7157
|
return { content: [{
|
|
6893
7158
|
type: "text",
|
|
6894
7159
|
text: JSON.stringify(result, null, 2)
|
|
6895
7160
|
}] };
|
|
6896
7161
|
});
|
|
6897
|
-
registerTool(server, apiClient, "
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
}, async ({ project_id, subscription_id, event_name, target_url, status }, { client }) => {
|
|
7162
|
+
registerTool(server, apiClient, "amba_orgs_update", "Update an organization: name, slug, or payment_source. Flipping payment_source to \"self\" is the first step of graduating a child org to bill directly.", {
|
|
7163
|
+
org_id: z.string().describe("The org UUID"),
|
|
7164
|
+
name: z.string().optional().describe("New name"),
|
|
7165
|
+
slug: z.string().optional().describe("New slug"),
|
|
7166
|
+
payment_source: z.enum(["self", "parent"]).optional().describe("Billing source toggle")
|
|
7167
|
+
}, async ({ org_id, name, slug, payment_source }, { client }) => {
|
|
6904
7168
|
const body = {};
|
|
6905
|
-
if (
|
|
6906
|
-
if (
|
|
6907
|
-
if (
|
|
6908
|
-
const result = await client.patch(`/
|
|
7169
|
+
if (name !== void 0) body["name"] = name;
|
|
7170
|
+
if (slug !== void 0) body["slug"] = slug;
|
|
7171
|
+
if (payment_source !== void 0) body["payment_source"] = payment_source;
|
|
7172
|
+
const result = await client.patch(`/orgs/${org_id}`, body);
|
|
6909
7173
|
return { content: [{
|
|
6910
7174
|
type: "text",
|
|
6911
7175
|
text: JSON.stringify(result, null, 2)
|
|
6912
7176
|
}] };
|
|
6913
7177
|
});
|
|
6914
|
-
registerTool(server, apiClient, "
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
7178
|
+
registerTool(server, apiClient, "amba_orgs_invite_member", "Invite a developer to an organization by email. Returns an invite URL the recipient accepts to join with the given role.", {
|
|
7179
|
+
org_id: z.string().describe("The org UUID"),
|
|
7180
|
+
email: z.string().describe("Invitee email"),
|
|
7181
|
+
role: z.enum([
|
|
7182
|
+
"admin",
|
|
7183
|
+
"member",
|
|
7184
|
+
"viewer"
|
|
7185
|
+
]).optional().describe("Role to grant on accept (default member)"),
|
|
7186
|
+
expires_in_days: z.number().int().optional().describe("Invite expiry in days (1–30, default 7)")
|
|
7187
|
+
}, async ({ org_id, email, role, expires_in_days }, { client }) => {
|
|
7188
|
+
const body = { email };
|
|
7189
|
+
if (role !== void 0) body["role"] = role;
|
|
7190
|
+
if (expires_in_days !== void 0) body["expires_in_days"] = expires_in_days;
|
|
7191
|
+
const result = await client.post(`/orgs/${org_id}/invites`, body);
|
|
6919
7192
|
return { content: [{
|
|
6920
7193
|
type: "text",
|
|
6921
7194
|
text: JSON.stringify(result, null, 2)
|
|
6922
7195
|
}] };
|
|
6923
7196
|
});
|
|
6924
|
-
registerTool(server, apiClient, "
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
7197
|
+
registerTool(server, apiClient, "amba_orgs_transfer_owner", "Transfer ownership of an organization to another developer (who must already be reachable). Optionally remove the previous owner instead of demoting them to admin.", {
|
|
7198
|
+
org_id: z.string().describe("The org UUID"),
|
|
7199
|
+
new_owner_developer_id: z.string().describe("Developer UUID of the new owner"),
|
|
7200
|
+
remove_previous_owner: z.boolean().optional().describe("If true, remove the previous owner entirely instead of demoting to admin")
|
|
7201
|
+
}, async ({ org_id, new_owner_developer_id, remove_previous_owner }, { client }) => {
|
|
7202
|
+
const body = { new_owner_developer_id };
|
|
7203
|
+
if (remove_previous_owner !== void 0) body["remove_previous_owner"] = remove_previous_owner;
|
|
7204
|
+
const result = await client.post(`/orgs/${org_id}/transfer-ownership`, body);
|
|
6929
7205
|
return { content: [{
|
|
6930
7206
|
type: "text",
|
|
6931
7207
|
text: JSON.stringify(result, null, 2)
|
|
6932
7208
|
}] };
|
|
6933
7209
|
});
|
|
6934
|
-
registerTool(server, apiClient, "
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
7210
|
+
registerTool(server, apiClient, "amba_provision_app", "App factory: atomically create a child org + a project under it and return the project keys. IDEMPOTENT by external_ref — a retry with the same (parent_org_id, external_ref) returns the same org/project (with keys_already_issued=true, since plaintext keys are shown only once). Store the keys from the first response.", {
|
|
7211
|
+
parent_org_id: z.string().describe("Parent org UUID to create the child under"),
|
|
7212
|
+
external_ref: z.string().describe("Stable per-app key for idempotency (e.g. your customer/app id)"),
|
|
7213
|
+
org_name: z.string().optional().describe("Child org display name (defaults to external_ref)"),
|
|
7214
|
+
project_name: z.string().optional().describe("Project display name (defaults to org_name)"),
|
|
7215
|
+
bundle_id: z.string().optional().describe("Apple bundle id / Sign in with Apple audience"),
|
|
7216
|
+
google_oauth_client_id: z.string().optional().describe("Google Sign In audience client id"),
|
|
7217
|
+
platform: z.enum([
|
|
7218
|
+
"ios",
|
|
7219
|
+
"android",
|
|
7220
|
+
"all"
|
|
7221
|
+
]).optional().describe("Target platform (default all)"),
|
|
7222
|
+
billing_mode: z.enum(["self", "parent"]).optional().describe("Child org payment_source: \"parent\" (default — bill the parent) or \"self\"")
|
|
7223
|
+
}, async (args, { client }) => {
|
|
7224
|
+
const body = {
|
|
7225
|
+
parent_org_id: args.parent_org_id,
|
|
7226
|
+
external_ref: args.external_ref
|
|
7227
|
+
};
|
|
7228
|
+
for (const k of [
|
|
7229
|
+
"org_name",
|
|
7230
|
+
"project_name",
|
|
7231
|
+
"bundle_id",
|
|
7232
|
+
"google_oauth_client_id",
|
|
7233
|
+
"platform",
|
|
7234
|
+
"billing_mode"
|
|
7235
|
+
]) if (args[k] !== void 0) body[k] = args[k];
|
|
7236
|
+
const result = await client.post("/provision", body);
|
|
6939
7237
|
return { content: [{
|
|
6940
7238
|
type: "text",
|
|
6941
7239
|
text: JSON.stringify(result, null, 2)
|
|
6942
7240
|
}] };
|
|
6943
7241
|
});
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
7242
|
+
}
|
|
7243
|
+
//#endregion
|
|
7244
|
+
//#region src/tools/service-accounts.ts
|
|
7245
|
+
/**
|
|
7246
|
+
* Service-account lifecycle tools for Fork C delegated operator tokens.
|
|
7247
|
+
*
|
|
7248
|
+
* These tools intentionally cover only SA lifecycle. Delegated token minting is
|
|
7249
|
+
* an HTTP backend-to-backend call made by the partner app with the `amb_dsvc_`
|
|
7250
|
+
* secret; it is not exposed as an MCP tool.
|
|
7251
|
+
*/
|
|
7252
|
+
function registerTools$4(server, apiClient) {
|
|
7253
|
+
registerTool(server, apiClient, "amba_service_accounts_create", "Create a scoped service account under an organization. Returns the amb_dsvc_ secret once; store it in the partner backend.", {
|
|
7254
|
+
org_id: z.string().describe("Organization UUID that roots the service-account subtree"),
|
|
7255
|
+
name: z.string().describe("Human-readable service account name"),
|
|
7256
|
+
max_role: z.enum([
|
|
7257
|
+
"owner",
|
|
7258
|
+
"admin",
|
|
7259
|
+
"member",
|
|
7260
|
+
"viewer"
|
|
7261
|
+
]).describe("Maximum role this service account may delegate"),
|
|
7262
|
+
acting_developer_id: z.string().optional().describe("Optional developer UUID stamped as ownership lineage for delegated provisioning. Must be owner/admin on the org.")
|
|
7263
|
+
}, async ({ org_id, name, max_role, acting_developer_id }, { client }) => {
|
|
7264
|
+
const body = {
|
|
7265
|
+
name,
|
|
7266
|
+
max_role
|
|
7267
|
+
};
|
|
7268
|
+
if (acting_developer_id !== void 0) body["acting_developer_id"] = acting_developer_id;
|
|
7269
|
+
const result = await client.post(`/orgs/${org_id}/service-accounts`, body);
|
|
6960
7270
|
return { content: [{
|
|
6961
7271
|
type: "text",
|
|
6962
7272
|
text: JSON.stringify(result, null, 2)
|
|
6963
7273
|
}] };
|
|
6964
7274
|
});
|
|
6965
|
-
registerTool(server, apiClient, "
|
|
6966
|
-
|
|
6967
|
-
delivery_id: z.string().describe("The delivery UUID")
|
|
6968
|
-
}, async ({ project_id, delivery_id }, { client }) => {
|
|
6969
|
-
const result = await client.get(`/projects/${project_id}/webhooks/deliveries/${delivery_id}`);
|
|
7275
|
+
registerTool(server, apiClient, "amba_service_accounts_list", "List service accounts for an organization. Secrets are redacted; only prefix/last-4 and lifecycle metadata are returned.", { org_id: z.string().describe("Organization UUID") }, async ({ org_id }, { client }) => {
|
|
7276
|
+
const result = await client.get(`/orgs/${org_id}/service-accounts`);
|
|
6970
7277
|
return { content: [{
|
|
6971
7278
|
type: "text",
|
|
6972
7279
|
text: JSON.stringify(result, null, 2)
|
|
6973
7280
|
}] };
|
|
6974
7281
|
});
|
|
6975
|
-
registerTool(server, apiClient, "
|
|
6976
|
-
|
|
6977
|
-
delivery_id: z.string().describe("The delivery UUID to replay")
|
|
6978
|
-
}, async ({ project_id, delivery_id }, { client }) => {
|
|
6979
|
-
const result = await client.post(`/projects/${project_id}/webhooks/deliveries/${delivery_id}/replay`);
|
|
7282
|
+
registerTool(server, apiClient, "amba_service_accounts_revoke", "Soft-revoke a service account. Its delegated tokens become invalid after the auth cache TTL.", { service_account_id: z.string().describe("Service account UUID") }, async ({ service_account_id }, { client }) => {
|
|
7283
|
+
const result = await client.post(`/service-accounts/${service_account_id}/revoke`);
|
|
6980
7284
|
return { content: [{
|
|
6981
7285
|
type: "text",
|
|
6982
7286
|
text: JSON.stringify(result, null, 2)
|
|
@@ -6984,24 +7288,490 @@ function registerTools$1(server, apiClient) {
|
|
|
6984
7288
|
});
|
|
6985
7289
|
}
|
|
6986
7290
|
//#endregion
|
|
6987
|
-
//#region src/tools/
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7291
|
+
//#region src/tools/affiliate.ts
|
|
7292
|
+
/**
|
|
7293
|
+
* Affiliate Programs tools (migration 052). Two personas, structurally scoped:
|
|
7294
|
+
* - `amba_affiliate_*` — the program owner (admin); operates over the whole
|
|
7295
|
+
* program. PAT must manage the program's owner org.
|
|
7296
|
+
* - `amba_affiliate_my_*` — the affiliate (self-service); derives the
|
|
7297
|
+
* affiliate from the PAT's own org(s) and takes NO other-affiliate id, so
|
|
7298
|
+
* an affiliate cannot read a peer's data.
|
|
7299
|
+
* Zero-to-enrolled bootstrap is `amba_affiliate_signup` (public, in auth.ts).
|
|
7300
|
+
*/
|
|
7301
|
+
function registerTools$3(server, apiClient) {
|
|
7302
|
+
registerTool(server, apiClient, "amba_affiliate_programs_create", "Create an affiliate program owned by an org. commission is a JSON tagged-union: {\"type\":\"percentage\",\"bps\":2000} | {\"type\":\"flat\",\"amount_cents\":5000} | {\"type\":\"recurring\",\"bps\":2000,\"max_cycles\":12} | {\"type\":\"tiered\",\"bps\":1000,\"tiers\":[{\"min_conversions\":10,\"bps\":2000}]}. For multi-level programs add a \"multi_level\":[{\"level\":1,\"bps\":500}] ladder and set multi_level_enabled.", {
|
|
7303
|
+
owner_organization_id: z.string().describe("Org that owns the program"),
|
|
7304
|
+
name: z.string().describe("Program name"),
|
|
7305
|
+
commission: z.record(z.any()).describe("Commission model (tagged-union JSON)"),
|
|
7306
|
+
conversion_kind: z.enum([
|
|
7307
|
+
"event",
|
|
7308
|
+
"payment",
|
|
7309
|
+
"entitlement"
|
|
7310
|
+
]).optional().describe("Which qualifying signal counts as a conversion (default event)"),
|
|
7311
|
+
conversion_event_name: z.string().optional().describe("Tenant event name that counts as a conversion (for conversion_kind=event)"),
|
|
7312
|
+
conversion_entitlement_id: z.string().optional().describe("Entitlement id that counts (for conversion_kind=entitlement)"),
|
|
7313
|
+
attribution_model: z.enum(["first_touch", "last_touch"]).optional().describe("Which touch gets the credit (default last_touch)"),
|
|
7314
|
+
attribution_window_days: z.number().int().optional().describe("Days a touch remains attributable (0–365, default 30)"),
|
|
7315
|
+
allow_self_referral: z.boolean().optional().describe("Allow the owner org to enroll as its own affiliate (default false)"),
|
|
7316
|
+
multi_level_enabled: z.boolean().optional().describe("Pay upline overrides per the commission \"multi_level\" ladder (default false)"),
|
|
7317
|
+
currency: z.string().optional().describe("ISO currency (default usd)"),
|
|
7318
|
+
payout_schedule: z.enum([
|
|
7319
|
+
"manual",
|
|
7320
|
+
"weekly",
|
|
7321
|
+
"monthly"
|
|
7322
|
+
]).optional(),
|
|
7323
|
+
payout_threshold_cents: z.number().int().optional(),
|
|
7324
|
+
hold_period_days: z.number().int().optional().describe("Refund-window holdback before commissions become payable"),
|
|
7325
|
+
platform_fee_bps: z.number().int().optional().describe("Amba's cut on payouts (basis points)"),
|
|
7326
|
+
approval_mode: z.enum(["auto", "manual"]).optional(),
|
|
7327
|
+
funding_source: z.enum(["split_at_source", "external"]).optional().describe("Where commission money sources from (split_at_source default | external). Recorded today; the transfer mechanism is gated.")
|
|
7328
|
+
}, async (args, { client }) => {
|
|
7329
|
+
const result = await client.post("/affiliate/programs", args);
|
|
7330
|
+
return { content: [{
|
|
7331
|
+
type: "text",
|
|
7332
|
+
text: JSON.stringify(result, null, 2)
|
|
7333
|
+
}] };
|
|
7334
|
+
});
|
|
7335
|
+
registerTool(server, apiClient, "amba_affiliate_programs_list", "List affiliate programs owned by orgs you can access.", {}, async (_, { client }) => {
|
|
7336
|
+
const result = await client.get("/affiliate/programs");
|
|
7337
|
+
return { content: [{
|
|
7338
|
+
type: "text",
|
|
7339
|
+
text: JSON.stringify(result, null, 2)
|
|
7340
|
+
}] };
|
|
7341
|
+
});
|
|
7342
|
+
registerTool(server, apiClient, "amba_affiliate_programs_get", "Get an affiliate program by id.", { program_id: z.string() }, async ({ program_id }, { client }) => {
|
|
7343
|
+
const result = await client.get(`/affiliate/programs/${program_id}`);
|
|
7344
|
+
return { content: [{
|
|
7345
|
+
type: "text",
|
|
7346
|
+
text: JSON.stringify(result, null, 2)
|
|
7347
|
+
}] };
|
|
7348
|
+
});
|
|
7349
|
+
registerTool(server, apiClient, "amba_affiliate_programs_update", "Update an affiliate program (status, payout config, fee, approval mode, conversion definition, attribution, multi-level). Set status=\"active\" to launch; set multi_level_enabled=true to start paying upline overrides.", {
|
|
7350
|
+
program_id: z.string(),
|
|
7351
|
+
status: z.enum([
|
|
7352
|
+
"draft",
|
|
7353
|
+
"active",
|
|
7354
|
+
"paused",
|
|
7355
|
+
"archived"
|
|
7356
|
+
]).optional(),
|
|
7357
|
+
payout_threshold_cents: z.number().int().optional(),
|
|
7358
|
+
hold_period_days: z.number().int().optional(),
|
|
7359
|
+
platform_fee_bps: z.number().int().optional(),
|
|
7360
|
+
approval_mode: z.enum(["auto", "manual"]).optional(),
|
|
7361
|
+
funding_source: z.enum(["split_at_source", "external"]).optional(),
|
|
7362
|
+
payout_schedule: z.enum([
|
|
7363
|
+
"manual",
|
|
7364
|
+
"weekly",
|
|
7365
|
+
"monthly"
|
|
7366
|
+
]).optional(),
|
|
7367
|
+
name: z.string().optional(),
|
|
7368
|
+
conversion_kind: z.enum([
|
|
7369
|
+
"event",
|
|
7370
|
+
"payment",
|
|
7371
|
+
"entitlement"
|
|
7372
|
+
]).optional(),
|
|
7373
|
+
conversion_event_name: z.string().nullable().optional(),
|
|
7374
|
+
conversion_entitlement_id: z.string().nullable().optional(),
|
|
7375
|
+
attribution_model: z.enum(["first_touch", "last_touch"]).optional(),
|
|
7376
|
+
attribution_window_days: z.number().int().optional(),
|
|
7377
|
+
allow_self_referral: z.boolean().optional(),
|
|
7378
|
+
multi_level_enabled: z.boolean().optional()
|
|
7379
|
+
}, async ({ program_id, ...rest }, { client }) => {
|
|
7380
|
+
const result = await client.patch(`/affiliate/programs/${program_id}`, rest);
|
|
7381
|
+
return { content: [{
|
|
7382
|
+
type: "text",
|
|
7383
|
+
text: JSON.stringify(result, null, 2)
|
|
7384
|
+
}] };
|
|
7385
|
+
});
|
|
7386
|
+
registerTool(server, apiClient, "amba_affiliate_invite", "Enroll an affiliate. Pass affiliate_org_id to enroll an existing Amba org directly, or email to get guidance for the recipient to amba_affiliate_signup first. For multi-level programs, pass parent_enrollment_id to place the new affiliate under a sponsor (their upline).", {
|
|
7387
|
+
program_id: z.string(),
|
|
7388
|
+
affiliate_org_id: z.string().optional().describe("Existing affiliate org UUID to enroll"),
|
|
7389
|
+
email: z.string().optional().describe("Email of an affiliate who has not yet joined Amba"),
|
|
7390
|
+
parent_enrollment_id: z.string().optional().describe("Sponsor/upline enrollment id in this program (multi-level placement)")
|
|
7391
|
+
}, async ({ program_id, affiliate_org_id, email, parent_enrollment_id }, { client }) => {
|
|
7392
|
+
const body = {};
|
|
7393
|
+
if (affiliate_org_id !== void 0) body["affiliate_org_id"] = affiliate_org_id;
|
|
7394
|
+
if (email !== void 0) body["email"] = email;
|
|
7395
|
+
if (parent_enrollment_id !== void 0) body["parent_enrollment_id"] = parent_enrollment_id;
|
|
7396
|
+
const result = await client.post(`/affiliate/programs/${program_id}/invite`, body);
|
|
7397
|
+
return { content: [{
|
|
7398
|
+
type: "text",
|
|
7399
|
+
text: JSON.stringify(result, null, 2)
|
|
7400
|
+
}] };
|
|
7401
|
+
});
|
|
7402
|
+
registerTool(server, apiClient, "amba_affiliate_affiliates_list", "List a program’s affiliates with their conversion + commission totals.", { program_id: z.string() }, async ({ program_id }, { client }) => {
|
|
7403
|
+
const result = await client.get(`/affiliate/programs/${program_id}/affiliates`);
|
|
7404
|
+
return { content: [{
|
|
7405
|
+
type: "text",
|
|
7406
|
+
text: JSON.stringify(result, null, 2)
|
|
7407
|
+
}] };
|
|
7408
|
+
});
|
|
7409
|
+
registerTool(server, apiClient, "amba_affiliate_affiliate_approve", "Approve (activate) a pending affiliate enrollment.", {
|
|
7410
|
+
program_id: z.string(),
|
|
7411
|
+
enrollment_id: z.string()
|
|
7412
|
+
}, async ({ program_id, enrollment_id }, { client }) => {
|
|
7413
|
+
const result = await client.post(`/affiliate/programs/${program_id}/affiliates/${enrollment_id}/approve`, {});
|
|
7414
|
+
return { content: [{
|
|
7415
|
+
type: "text",
|
|
7416
|
+
text: JSON.stringify(result, null, 2)
|
|
7417
|
+
}] };
|
|
7418
|
+
});
|
|
7419
|
+
registerTool(server, apiClient, "amba_affiliate_affiliate_ban", "Ban an affiliate from a program (links die; pending conversions voided).", {
|
|
7420
|
+
program_id: z.string(),
|
|
7421
|
+
enrollment_id: z.string()
|
|
7422
|
+
}, async ({ program_id, enrollment_id }, { client }) => {
|
|
7423
|
+
const result = await client.post(`/affiliate/programs/${program_id}/affiliates/${enrollment_id}/ban`, {});
|
|
7424
|
+
return { content: [{
|
|
7425
|
+
type: "text",
|
|
7426
|
+
text: JSON.stringify(result, null, 2)
|
|
7427
|
+
}] };
|
|
7428
|
+
});
|
|
7429
|
+
registerTool(server, apiClient, "amba_affiliate_affiliate_pause", "Pause an affiliate enrollment (their links stop earning; reversible via approve).", {
|
|
7430
|
+
program_id: z.string(),
|
|
7431
|
+
enrollment_id: z.string()
|
|
7432
|
+
}, async ({ program_id, enrollment_id }, { client }) => {
|
|
7433
|
+
const result = await client.post(`/affiliate/programs/${program_id}/affiliates/${enrollment_id}/pause`, {});
|
|
7434
|
+
return { content: [{
|
|
7435
|
+
type: "text",
|
|
7436
|
+
text: JSON.stringify(result, null, 2)
|
|
7437
|
+
}] };
|
|
7438
|
+
});
|
|
7439
|
+
registerTool(server, apiClient, "amba_affiliate_affiliates_get", "Get one affiliate enrollment with its codes, conversions, and commission totals.", {
|
|
7440
|
+
program_id: z.string(),
|
|
7441
|
+
enrollment_id: z.string()
|
|
7442
|
+
}, async ({ program_id, enrollment_id }, { client }) => {
|
|
7443
|
+
const result = await client.get(`/affiliate/programs/${program_id}/affiliates/${enrollment_id}`);
|
|
7444
|
+
return { content: [{
|
|
7445
|
+
type: "text",
|
|
7446
|
+
text: JSON.stringify(result, null, 2)
|
|
7447
|
+
}] };
|
|
7448
|
+
});
|
|
7449
|
+
registerTool(server, apiClient, "amba_affiliate_conversions_list", "List conversions for a program (optionally filter by status).", {
|
|
7450
|
+
program_id: z.string(),
|
|
7451
|
+
status: z.enum([
|
|
7452
|
+
"pending",
|
|
7453
|
+
"approved",
|
|
7454
|
+
"rejected",
|
|
7455
|
+
"clawed_back"
|
|
7456
|
+
]).optional()
|
|
7457
|
+
}, async ({ program_id, status }, { client }) => {
|
|
7458
|
+
const qs = status ? `?status=${status}` : "";
|
|
7459
|
+
const result = await client.get(`/affiliate/programs/${program_id}/conversions${qs}`);
|
|
7460
|
+
return { content: [{
|
|
7461
|
+
type: "text",
|
|
7462
|
+
text: JSON.stringify(result, null, 2)
|
|
7463
|
+
}] };
|
|
7464
|
+
});
|
|
7465
|
+
registerTool(server, apiClient, "amba_affiliate_conversion_approve", "Approve a pending conversion so it accrues commission.", {
|
|
7466
|
+
program_id: z.string(),
|
|
7467
|
+
conversion_id: z.string()
|
|
7468
|
+
}, async ({ program_id, conversion_id }, { client }) => {
|
|
7469
|
+
const result = await client.post(`/affiliate/programs/${program_id}/conversions/${conversion_id}/approve`, {});
|
|
7470
|
+
return { content: [{
|
|
7471
|
+
type: "text",
|
|
7472
|
+
text: JSON.stringify(result, null, 2)
|
|
7473
|
+
}] };
|
|
7474
|
+
});
|
|
7475
|
+
registerTool(server, apiClient, "amba_affiliate_conversion_reject", "Reject a pending conversion so it never accrues commission.", {
|
|
7476
|
+
program_id: z.string(),
|
|
7477
|
+
conversion_id: z.string()
|
|
7478
|
+
}, async ({ program_id, conversion_id }, { client }) => {
|
|
7479
|
+
const result = await client.post(`/affiliate/programs/${program_id}/conversions/${conversion_id}/reject`, {});
|
|
7480
|
+
return { content: [{
|
|
7481
|
+
type: "text",
|
|
7482
|
+
text: JSON.stringify(result, null, 2)
|
|
7483
|
+
}] };
|
|
7484
|
+
});
|
|
7485
|
+
registerTool(server, apiClient, "amba_affiliate_conversion_clawback", "Claw back an approved/paid conversion (the underlying sale refunded or charged back). Pre-payout commissions are reversed; already-paid commissions become negative carry-forward netted against the affiliate’s next payout.", {
|
|
7486
|
+
program_id: z.string(),
|
|
7487
|
+
conversion_id: z.string(),
|
|
7488
|
+
reason: z.string().optional().describe("Why (e.g. refund, chargeback). Default \"refund\".")
|
|
7489
|
+
}, async ({ program_id, conversion_id, reason }, { client }) => {
|
|
7490
|
+
const body = {};
|
|
7491
|
+
if (reason !== void 0) body["reason"] = reason;
|
|
7492
|
+
const result = await client.post(`/affiliate/programs/${program_id}/conversions/${conversion_id}/clawback`, body);
|
|
7493
|
+
return { content: [{
|
|
7494
|
+
type: "text",
|
|
7495
|
+
text: JSON.stringify(result, null, 2)
|
|
7496
|
+
}] };
|
|
7497
|
+
});
|
|
7498
|
+
registerTool(server, apiClient, "amba_affiliate_analytics_get", "Program-level analytics: conversion + commission totals and an affiliate leaderboard.", { program_id: z.string() }, async ({ program_id }, { client }) => {
|
|
7499
|
+
const result = await client.get(`/affiliate/programs/${program_id}/analytics`);
|
|
7500
|
+
return { content: [{
|
|
7501
|
+
type: "text",
|
|
7502
|
+
text: JSON.stringify(result, null, 2)
|
|
7503
|
+
}] };
|
|
7504
|
+
});
|
|
7505
|
+
registerTool(server, apiClient, "amba_affiliate_leaderboard_get", "Affiliate leaderboard for a program: top affiliates by commission, with clicks + EPC.", {
|
|
7506
|
+
program_id: z.string(),
|
|
7507
|
+
limit: z.number().int().optional()
|
|
7508
|
+
}, async ({ program_id, limit }, { client }) => {
|
|
7509
|
+
const qs = limit !== void 0 ? `?limit=${limit}` : "";
|
|
7510
|
+
const result = await client.get(`/affiliate/programs/${program_id}/leaderboard${qs}`);
|
|
7511
|
+
return { content: [{
|
|
7512
|
+
type: "text",
|
|
7513
|
+
text: JSON.stringify(result, null, 2)
|
|
7514
|
+
}] };
|
|
7515
|
+
});
|
|
7516
|
+
registerTool(server, apiClient, "amba_affiliate_payouts_list", "Settlement (payout) history for a program’s affiliates: net amount, period, transfer id, status.", { program_id: z.string() }, async ({ program_id }, { client }) => {
|
|
7517
|
+
const result = await client.get(`/affiliate/programs/${program_id}/payouts`);
|
|
7518
|
+
return { content: [{
|
|
7519
|
+
type: "text",
|
|
7520
|
+
text: JSON.stringify(result, null, 2)
|
|
7521
|
+
}] };
|
|
7522
|
+
});
|
|
7523
|
+
registerTool(server, apiClient, "amba_affiliate_payout_run", "Run payouts for a program: pay each affiliate org their payable commissions over the threshold via Stripe Connect. Gated by ORG_PAYOUTS_LIVE (money movement).", { program_id: z.string() }, async ({ program_id }, { client }) => {
|
|
7524
|
+
const result = await client.post(`/affiliate/programs/${program_id}/payouts`, {});
|
|
7525
|
+
return { content: [{
|
|
7526
|
+
type: "text",
|
|
7527
|
+
text: JSON.stringify(result, null, 2)
|
|
7528
|
+
}] };
|
|
7529
|
+
});
|
|
7530
|
+
registerTool(server, apiClient, "amba_affiliate_my_programs_list", "List the affiliate programs your org(s) are enrolled in.", {}, async (_, { client }) => {
|
|
7531
|
+
const result = await client.get("/affiliate-me/programs");
|
|
7532
|
+
return { content: [{
|
|
7533
|
+
type: "text",
|
|
7534
|
+
text: JSON.stringify(result, null, 2)
|
|
7535
|
+
}] };
|
|
7536
|
+
});
|
|
7537
|
+
registerTool(server, apiClient, "amba_affiliate_my_invites_list", "List affiliate-program invitations pending for your org(s).", {}, async (_, { client }) => {
|
|
7538
|
+
const result = await client.get("/affiliate-me/invites");
|
|
7539
|
+
return { content: [{
|
|
7540
|
+
type: "text",
|
|
7541
|
+
text: JSON.stringify(result, null, 2)
|
|
7542
|
+
}] };
|
|
7543
|
+
});
|
|
7544
|
+
registerTool(server, apiClient, "amba_affiliate_my_invite_accept", "Accept an affiliate-program invitation addressed to your org (activates the enrollment and returns your referral code).", { enrollment_id: z.string() }, async ({ enrollment_id }, { client }) => {
|
|
7545
|
+
const result = await client.post(`/affiliate-me/invites/${enrollment_id}/accept`, {});
|
|
7546
|
+
return { content: [{
|
|
7547
|
+
type: "text",
|
|
7548
|
+
text: JSON.stringify(result, null, 2)
|
|
7549
|
+
}] };
|
|
7550
|
+
});
|
|
7551
|
+
registerTool(server, apiClient, "amba_affiliate_my_referral_generate", "Mint a FRESH referral code for a program you are enrolled in (rotates/replaces your current active code).", { program_id: z.string() }, async ({ program_id }, { client }) => {
|
|
7552
|
+
const result = await client.post(`/affiliate-me/programs/${program_id}/referral`, {});
|
|
7553
|
+
return { content: [{
|
|
7554
|
+
type: "text",
|
|
7555
|
+
text: JSON.stringify(result, null, 2)
|
|
7556
|
+
}] };
|
|
7557
|
+
});
|
|
7558
|
+
registerTool(server, apiClient, "amba_affiliate_my_conversions_list", "List conversions across your affiliate enrollments (optionally filter by status).", { status: z.enum([
|
|
7559
|
+
"pending",
|
|
7560
|
+
"approved",
|
|
7561
|
+
"rejected",
|
|
7562
|
+
"clawed_back"
|
|
7563
|
+
]).optional() }, async ({ status }, { client }) => {
|
|
7564
|
+
const qs = status ? `?status=${status}` : "";
|
|
7565
|
+
const result = await client.get(`/affiliate-me/conversions${qs}`);
|
|
7566
|
+
return { content: [{
|
|
7567
|
+
type: "text",
|
|
7568
|
+
text: JSON.stringify(result, null, 2)
|
|
7569
|
+
}] };
|
|
7570
|
+
});
|
|
7571
|
+
registerTool(server, apiClient, "amba_affiliate_my_payout_request", "Check what one of your orgs is owed and whether its payout account is ready. Does NOT move money — owners settle payable commissions on the program schedule; this surfaces the balance + readiness.", { org_id: z.string() }, async ({ org_id }, { client }) => {
|
|
7572
|
+
const result = await client.post("/affiliate-me/payout-request", { org_id });
|
|
7573
|
+
return { content: [{
|
|
7574
|
+
type: "text",
|
|
7575
|
+
text: JSON.stringify(result, null, 2)
|
|
7576
|
+
}] };
|
|
7577
|
+
});
|
|
7578
|
+
registerTool(server, apiClient, "amba_affiliate_my_referral_get", "Get your referral code(s) / link(s) for a program you are enrolled in.", { program_id: z.string() }, async ({ program_id }, { client }) => {
|
|
7579
|
+
const result = await client.get(`/affiliate-me/programs/${program_id}/referral`);
|
|
7580
|
+
return { content: [{
|
|
7581
|
+
type: "text",
|
|
7582
|
+
text: JSON.stringify(result, null, 2)
|
|
7583
|
+
}] };
|
|
7584
|
+
});
|
|
7585
|
+
registerTool(server, apiClient, "amba_affiliate_my_stats_get", "Your affiliate stats across all enrollments: conversions, earnings, pending vs paid.", {}, async (_, { client }) => {
|
|
7586
|
+
const result = await client.get("/affiliate-me/stats");
|
|
7587
|
+
return { content: [{
|
|
7588
|
+
type: "text",
|
|
7589
|
+
text: JSON.stringify(result, null, 2)
|
|
7590
|
+
}] };
|
|
7591
|
+
});
|
|
7592
|
+
registerTool(server, apiClient, "amba_affiliate_my_balance_get", "Your available (unpaid) and lifetime-paid commission balance, per currency.", {}, async (_, { client }) => {
|
|
7593
|
+
const result = await client.get("/affiliate-me/balance");
|
|
7594
|
+
return { content: [{
|
|
7595
|
+
type: "text",
|
|
7596
|
+
text: JSON.stringify(result, null, 2)
|
|
7597
|
+
}] };
|
|
7598
|
+
});
|
|
7599
|
+
registerTool(server, apiClient, "amba_affiliate_my_payouts_list", "Your payout/settlement history.", {}, async (_, { client }) => {
|
|
7600
|
+
const result = await client.get("/affiliate-me/payouts");
|
|
7601
|
+
return { content: [{
|
|
7602
|
+
type: "text",
|
|
7603
|
+
text: JSON.stringify(result, null, 2)
|
|
7604
|
+
}] };
|
|
7605
|
+
});
|
|
7606
|
+
registerTool(server, apiClient, "amba_affiliate_my_payout_create", "Create a Stripe Connect payout account for an org you own (so you can receive commissions).", {
|
|
7607
|
+
org_id: z.string(),
|
|
7608
|
+
country: z.string().optional().describe("2-letter country (default US)")
|
|
7609
|
+
}, async ({ org_id, country }, { client }) => {
|
|
7610
|
+
const body = { org_id };
|
|
7611
|
+
if (country !== void 0) body["country"] = country;
|
|
7612
|
+
const result = await client.post("/affiliate-me/payout-account", body);
|
|
7613
|
+
return { content: [{
|
|
7614
|
+
type: "text",
|
|
7615
|
+
text: JSON.stringify(result, null, 2)
|
|
7616
|
+
}] };
|
|
7617
|
+
});
|
|
7618
|
+
registerTool(server, apiClient, "amba_affiliate_my_payout_onboarding", "Get a hosted Stripe onboarding URL to finish setting up an org’s payout account. Surface the URL to the user as a copy box.", { org_id: z.string() }, async ({ org_id }, { client }) => {
|
|
7619
|
+
const result = await client.post("/affiliate-me/payout-account/link", { org_id });
|
|
7620
|
+
return { content: [{
|
|
7621
|
+
type: "text",
|
|
7622
|
+
text: JSON.stringify(result, null, 2)
|
|
7623
|
+
}] };
|
|
7624
|
+
});
|
|
7625
|
+
registerTool(server, apiClient, "amba_affiliate_my_payout_status", "Check whether an org’s payout account is set up.", { org_id: z.string() }, async ({ org_id }, { client }) => {
|
|
7626
|
+
const result = await client.get(`/affiliate-me/payout-account?org_id=${org_id}`);
|
|
7627
|
+
return { content: [{
|
|
7628
|
+
type: "text",
|
|
7629
|
+
text: JSON.stringify(result, null, 2)
|
|
7630
|
+
}] };
|
|
7631
|
+
});
|
|
7632
|
+
}
|
|
7633
|
+
//#endregion
|
|
7634
|
+
//#region src/tools/webhooks.ts
|
|
7635
|
+
function registerTools$2(server, apiClient) {
|
|
7636
|
+
registerTool(server, apiClient, "amba_webhooks_create", "Register an outbound webhook subscription. Amba sends an HMAC-signed POST to target_url every time an event named event_name fires. The signing secret is returned ONCE in this response — copy it into your receiver config; later reads never include it. target_url must be https:// (http:// is allowed only for localhost during development).", {
|
|
7637
|
+
project_id: z.string().describe("The project ID"),
|
|
7638
|
+
event_name: z.string().describe("Event name to subscribe to. Must match ^[a-z][a-z0-9_.-]{0,127}$ (case-insensitive)."),
|
|
7639
|
+
target_url: z.string().describe("HTTPS endpoint that receives the signed POST. http:// allowed only for localhost.")
|
|
7640
|
+
}, async ({ project_id, event_name, target_url }, { client }) => {
|
|
7641
|
+
const result = await client.post(`/projects/${project_id}/webhooks/subscriptions`, {
|
|
7642
|
+
event_name,
|
|
7643
|
+
target_url
|
|
7644
|
+
});
|
|
7645
|
+
return { content: [{
|
|
7646
|
+
type: "text",
|
|
7647
|
+
text: JSON.stringify(result, null, 2)
|
|
7648
|
+
}] };
|
|
7649
|
+
});
|
|
7650
|
+
registerTool(server, apiClient, "amba_webhooks_list", "List all webhook subscriptions for a project (most recent first). Secrets are never included — only id, event_name, target_url, status, and timestamps. status is \"active\", \"degraded\" (the delivery circuit breaker tripped on repeated failures), or \"disabled\".", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
7651
|
+
const result = await client.get(`/projects/${project_id}/webhooks/subscriptions`);
|
|
7652
|
+
return { content: [{
|
|
7653
|
+
type: "text",
|
|
7654
|
+
text: JSON.stringify(result, null, 2)
|
|
7655
|
+
}] };
|
|
7656
|
+
});
|
|
7657
|
+
registerTool(server, apiClient, "amba_webhooks_get", "Describe a single webhook subscription by id. Returns event_name, target_url, status, attempt metadata, and timestamps (no secret).", {
|
|
7658
|
+
project_id: z.string().describe("The project ID"),
|
|
7659
|
+
subscription_id: z.string().describe("The subscription UUID")
|
|
7660
|
+
}, async ({ project_id, subscription_id }, { client }) => {
|
|
7661
|
+
const result = await client.get(`/projects/${project_id}/webhooks/subscriptions/${subscription_id}`);
|
|
7662
|
+
return { content: [{
|
|
7663
|
+
type: "text",
|
|
7664
|
+
text: JSON.stringify(result, null, 2)
|
|
7665
|
+
}] };
|
|
7666
|
+
});
|
|
7667
|
+
registerTool(server, apiClient, "amba_webhooks_update", "Update a webhook subscription. Any of event_name, target_url, or status may be changed; supply at least one. status accepts only \"active\" or \"disabled\" — \"degraded\" is reserved for the automatic failure circuit breaker and cannot be set manually.", {
|
|
7668
|
+
project_id: z.string().describe("The project ID"),
|
|
7669
|
+
subscription_id: z.string().describe("The subscription UUID"),
|
|
7670
|
+
event_name: z.string().optional().describe("New event name. Must match ^[a-z][a-z0-9_.-]{0,127}$ (case-insensitive)."),
|
|
7671
|
+
target_url: z.string().optional().describe("New HTTPS endpoint. http:// allowed only for localhost."),
|
|
7672
|
+
status: z.enum(["active", "disabled"]).optional().describe("Enable or disable delivery. \"degraded\" cannot be set manually.")
|
|
7673
|
+
}, async ({ project_id, subscription_id, event_name, target_url, status }, { client }) => {
|
|
7674
|
+
const body = {};
|
|
7675
|
+
if (event_name !== void 0) body.event_name = event_name;
|
|
7676
|
+
if (target_url !== void 0) body.target_url = target_url;
|
|
7677
|
+
if (status !== void 0) body.status = status;
|
|
7678
|
+
const result = await client.patch(`/projects/${project_id}/webhooks/subscriptions/${subscription_id}`, body);
|
|
7679
|
+
return { content: [{
|
|
7680
|
+
type: "text",
|
|
7681
|
+
text: JSON.stringify(result, null, 2)
|
|
7682
|
+
}] };
|
|
7683
|
+
});
|
|
7684
|
+
registerTool(server, apiClient, "amba_webhooks_delete", "Delete a webhook subscription. No further events are delivered to its target_url after this.", {
|
|
7685
|
+
project_id: z.string().describe("The project ID"),
|
|
7686
|
+
subscription_id: z.string().describe("The subscription UUID")
|
|
7687
|
+
}, async ({ project_id, subscription_id }, { client }) => {
|
|
7688
|
+
const result = await client.delete(`/projects/${project_id}/webhooks/subscriptions/${subscription_id}`);
|
|
7689
|
+
return { content: [{
|
|
7690
|
+
type: "text",
|
|
7691
|
+
text: JSON.stringify(result, null, 2)
|
|
7692
|
+
}] };
|
|
7693
|
+
});
|
|
7694
|
+
registerTool(server, apiClient, "amba_webhooks_rotate_secret", "Rotate the HMAC signing secret for a subscription. The new secret is returned ONCE — update your receiver config with it. To avoid dropped deliveries, have your receiver accept both the old and new secret for a brief overlap window before retiring the old one.", {
|
|
7695
|
+
project_id: z.string().describe("The project ID"),
|
|
7696
|
+
subscription_id: z.string().describe("The subscription UUID")
|
|
7697
|
+
}, async ({ project_id, subscription_id }, { client }) => {
|
|
7698
|
+
const result = await client.post(`/projects/${project_id}/webhooks/subscriptions/${subscription_id}/rotate-secret`);
|
|
7699
|
+
return { content: [{
|
|
7700
|
+
type: "text",
|
|
7701
|
+
text: JSON.stringify(result, null, 2)
|
|
7702
|
+
}] };
|
|
7703
|
+
});
|
|
7704
|
+
registerTool(server, apiClient, "amba_webhooks_test", "Send a synthetic test delivery to a subscription to verify receiver setup. The test payload is tagged so it does not affect the failure circuit breaker or pollute your real event history. Returns a workflow id; poll amba_webhooks_deliveries_list shortly after for the result. Fails with 409 if the subscription is disabled.", {
|
|
7705
|
+
project_id: z.string().describe("The project ID"),
|
|
7706
|
+
subscription_id: z.string().describe("The subscription UUID")
|
|
7707
|
+
}, async ({ project_id, subscription_id }, { client }) => {
|
|
7708
|
+
const result = await client.post(`/projects/${project_id}/webhooks/subscriptions/${subscription_id}/test`);
|
|
7709
|
+
return { content: [{
|
|
7710
|
+
type: "text",
|
|
7711
|
+
text: JSON.stringify(result, null, 2)
|
|
7712
|
+
}] };
|
|
7713
|
+
});
|
|
7714
|
+
registerTool(server, apiClient, "amba_webhooks_deliveries_list", "List recent webhook delivery attempts (most recent first). Optionally filter by subscription_id and/or status. status is \"pending\", \"delivered\", \"failed\", or \"dlq\" (dead-lettered after exhausting retries). Default page size 50, max 200.", {
|
|
7715
|
+
project_id: z.string().describe("The project ID"),
|
|
7716
|
+
subscription_id: z.string().optional().describe("Filter to deliveries for a single subscription UUID."),
|
|
7717
|
+
status: z.enum([
|
|
7718
|
+
"pending",
|
|
7719
|
+
"delivered",
|
|
7720
|
+
"failed",
|
|
7721
|
+
"dlq"
|
|
7722
|
+
]).optional().describe("Filter by delivery status."),
|
|
7723
|
+
limit: z.number().optional().describe("Page size (default 50, max 200).")
|
|
7724
|
+
}, async ({ project_id, subscription_id, status, limit }, { client }) => {
|
|
7725
|
+
const query = {};
|
|
7726
|
+
if (subscription_id !== void 0) query.subscription_id = subscription_id;
|
|
7727
|
+
if (status !== void 0) query.status = status;
|
|
7728
|
+
if (limit !== void 0) query.limit = String(limit);
|
|
7729
|
+
const result = await client.get(`/projects/${project_id}/webhooks/deliveries`, query);
|
|
7730
|
+
return { content: [{
|
|
7731
|
+
type: "text",
|
|
7732
|
+
text: JSON.stringify(result, null, 2)
|
|
7733
|
+
}] };
|
|
7734
|
+
});
|
|
7735
|
+
registerTool(server, apiClient, "amba_webhooks_deliveries_get", "Describe a single webhook delivery by id, including the full attempt log, attempt count, and last error.", {
|
|
7736
|
+
project_id: z.string().describe("The project ID"),
|
|
7737
|
+
delivery_id: z.string().describe("The delivery UUID")
|
|
7738
|
+
}, async ({ project_id, delivery_id }, { client }) => {
|
|
7739
|
+
const result = await client.get(`/projects/${project_id}/webhooks/deliveries/${delivery_id}`);
|
|
7740
|
+
return { content: [{
|
|
7741
|
+
type: "text",
|
|
7742
|
+
text: JSON.stringify(result, null, 2)
|
|
7743
|
+
}] };
|
|
7744
|
+
});
|
|
7745
|
+
registerTool(server, apiClient, "amba_webhooks_deliveries_replay", "Re-enqueue a terminated (failed or dead-lettered) delivery, rebuilding the original payload from the source event. The original idempotency key is preserved so receiver-side dedup treats it as a re-delivery, not a new event. Returns 409 if the delivery is still pending, or 410 if the source event has been purged.", {
|
|
7746
|
+
project_id: z.string().describe("The project ID"),
|
|
7747
|
+
delivery_id: z.string().describe("The delivery UUID to replay")
|
|
7748
|
+
}, async ({ project_id, delivery_id }, { client }) => {
|
|
7749
|
+
const result = await client.post(`/projects/${project_id}/webhooks/deliveries/${delivery_id}/replay`);
|
|
7750
|
+
return { content: [{
|
|
7751
|
+
type: "text",
|
|
7752
|
+
text: JSON.stringify(result, null, 2)
|
|
7753
|
+
}] };
|
|
7754
|
+
});
|
|
7755
|
+
}
|
|
7756
|
+
//#endregion
|
|
7757
|
+
//#region src/tools/email.ts
|
|
7758
|
+
function registerTools$1(server, apiClient) {
|
|
7759
|
+
registerTool(server, apiClient, "amba_email_templates_create", "Create or update a transactional email template (upsert keyed by name — re-creating an existing name overwrites it). Templates are referenced by name when sending. Subject and html_body are required; text_body is an optional plain-text fallback. name must match ^[a-z][a-z0-9_]*$.", {
|
|
7760
|
+
project_id: z.string().describe("The project ID"),
|
|
7761
|
+
name: z.string().describe("Template name. Must match ^[a-z][a-z0-9_]*$."),
|
|
7762
|
+
subject: z.string().describe("Email subject line. Supports template variables."),
|
|
7763
|
+
html_body: z.string().describe("HTML body. Supports template variables."),
|
|
7764
|
+
text_body: z.string().nullable().optional().describe("Optional plain-text fallback body. Pass null to leave unset.")
|
|
7765
|
+
}, async ({ project_id, name, subject, html_body, text_body }, { client }) => {
|
|
7766
|
+
const body = {
|
|
7767
|
+
name,
|
|
7768
|
+
subject,
|
|
7769
|
+
html_body
|
|
7770
|
+
};
|
|
7771
|
+
if (text_body !== void 0) body.text_body = text_body;
|
|
7772
|
+
const result = await client.post(`/projects/${project_id}/email/templates`, body);
|
|
7773
|
+
return { content: [{
|
|
7774
|
+
type: "text",
|
|
7005
7775
|
text: JSON.stringify(result, null, 2)
|
|
7006
7776
|
}] };
|
|
7007
7777
|
});
|
|
@@ -7149,6 +7919,51 @@ function registerTools(server, apiClient) {
|
|
|
7149
7919
|
});
|
|
7150
7920
|
}
|
|
7151
7921
|
//#endregion
|
|
7922
|
+
//#region src/tools/app-mcp.ts
|
|
7923
|
+
function registerTools(server, apiClient) {
|
|
7924
|
+
registerTool(server, apiClient, "amba_app_mcp_get_config", "Read the configuration of this app's agent surface (the per-app MCP endpoint whose tools are the app's own collections and functions). Returns: enabled flag, hostname slug, auth_mode (which credential types may connect: all / client_only / developer_only), the exposure allowlist (which collections/functions are projected into agent tools — DEFAULT: none), and ready-to-use connection info (`connection.url` works today; `connection.hostname_url` appears once a slug is set, with a note on activation timing).", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
7925
|
+
const result = await client.get(`/projects/${project_id}/app-mcp/config`);
|
|
7926
|
+
return { content: [{
|
|
7927
|
+
type: "text",
|
|
7928
|
+
text: JSON.stringify(result, null, 2)
|
|
7929
|
+
}] };
|
|
7930
|
+
});
|
|
7931
|
+
registerTool(server, apiClient, "amba_app_mcp_update_config", "Update the app agent surface configuration. `enabled: false` turns the whole surface off (existing connections lose their tools on the next request). `slug` reserves a friendly hostname label for the surface (lowercase DNS label, globally unique — 409 SLUG_TAKEN on collision; null clears it). `auth_mode` restricts who may connect: \"all\" (default — developer tokens and app client keys), \"client_only\" (only the app's client keys + end-user sessions), or \"developer_only\". Provide at least one field.", {
|
|
7932
|
+
project_id: z.string().describe("The project ID"),
|
|
7933
|
+
enabled: z.boolean().optional().describe("Turn the agent surface on/off"),
|
|
7934
|
+
slug: z.string().nullable().optional().describe("Hostname label (lowercase DNS label) or null to clear"),
|
|
7935
|
+
auth_mode: z.enum([
|
|
7936
|
+
"all",
|
|
7937
|
+
"client_only",
|
|
7938
|
+
"developer_only"
|
|
7939
|
+
]).optional().describe("Which credential types may connect")
|
|
7940
|
+
}, async ({ project_id, enabled, slug, auth_mode }, { client }) => {
|
|
7941
|
+
const body = {};
|
|
7942
|
+
if (enabled !== void 0) body.enabled = enabled;
|
|
7943
|
+
if (slug !== void 0) body.slug = slug;
|
|
7944
|
+
if (auth_mode !== void 0) body.auth_mode = auth_mode;
|
|
7945
|
+
const result = await client.patch(`/projects/${project_id}/app-mcp/config`, body);
|
|
7946
|
+
return { content: [{
|
|
7947
|
+
type: "text",
|
|
7948
|
+
text: JSON.stringify(result, null, 2)
|
|
7949
|
+
}] };
|
|
7950
|
+
});
|
|
7951
|
+
registerTool(server, apiClient, "amba_app_mcp_set_exposure", "Set which collections and deployed functions the app agent surface exposes as typed tools. EXPLICIT ALLOWLIST with full-set replace semantics per provided kind: `collections: [\"recipes\"]` makes recipes the ONLY exposed collection; an empty array hides everything of that kind; an omitted kind is left unchanged. Nothing is exposed by default — after creating a collection or deploying a function, call this to make it an agent tool. Functions additionally pick up the typed input schema they declared at deploy time (metadata `mcp.input_schema`).", {
|
|
7952
|
+
project_id: z.string().describe("The project ID"),
|
|
7953
|
+
collections: z.array(z.string()).optional().describe("Full replacement list of exposed collection names"),
|
|
7954
|
+
functions: z.array(z.string()).optional().describe("Full replacement list of exposed function names")
|
|
7955
|
+
}, async ({ project_id, collections, functions }, { client }) => {
|
|
7956
|
+
const body = {};
|
|
7957
|
+
if (collections !== void 0) body.collections = collections;
|
|
7958
|
+
if (functions !== void 0) body.functions = functions;
|
|
7959
|
+
const result = await client.put(`/projects/${project_id}/app-mcp/exposure`, body);
|
|
7960
|
+
return { content: [{
|
|
7961
|
+
type: "text",
|
|
7962
|
+
text: JSON.stringify(result, null, 2)
|
|
7963
|
+
}] };
|
|
7964
|
+
});
|
|
7965
|
+
}
|
|
7966
|
+
//#endregion
|
|
7152
7967
|
//#region src/resources/amba-setup-identity.ts
|
|
7153
7968
|
/**
|
|
7154
7969
|
* `amba://setup/identity` — per-surface playbook for end-user auth.
|
|
@@ -8435,32 +9250,33 @@ const AMBA_SETUP_SOCIAL_MIME = "text/markdown";
|
|
|
8435
9250
|
* Companion to `AMBA_SETUP_GUIDE_MD`. Step 3 of the main playbook
|
|
8436
9251
|
* tells the agent to fetch this resource when wiring infrastructure.
|
|
8437
9252
|
*
|
|
8438
|
-
* Surface scope: collections (
|
|
9253
|
+
* Surface scope: collections (relational Postgres tables), functions
|
|
8439
9254
|
* (serverless), analytics, AI prompts, secrets, configs / feature
|
|
8440
9255
|
* flags, third-party integrations, media (file storage + CDN),
|
|
8441
9256
|
* sites (static asset hosting).
|
|
8442
9257
|
*
|
|
8443
9258
|
* Twin: `packages/cli/skill-bundle/references/infrastructure.md`.
|
|
8444
|
-
* Drift gate in `amba-setup.test.ts`.
|
|
8445
|
-
*
|
|
9259
|
+
* Drift gate in `amba-setup.test.ts`. Collections are named as relational
|
|
9260
|
+
* Postgres tables; the hosting supplier (Neon) and the rest of the infra
|
|
9261
|
+
* stack (Temporal / R2) stay scrubbed before exposing on the wire.
|
|
8446
9262
|
*/
|
|
8447
9263
|
const AMBA_SETUP_INFRASTRUCTURE_MD = `# Infrastructure
|
|
8448
9264
|
|
|
8449
|
-
The plumbing that sits behind every other surface:
|
|
9265
|
+
The plumbing that sits behind every other surface: relational Postgres 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 / Stripe / push credentials), media (file storage + CDN), and sites (static asset hosting at \`*.app.amba.host\`).
|
|
8450
9266
|
|
|
8451
9267
|
If 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.
|
|
8452
9268
|
|
|
8453
9269
|
## MCP tools
|
|
8454
9270
|
|
|
8455
|
-
### Collections (
|
|
9271
|
+
### Collections (relational Postgres tables)
|
|
8456
9272
|
|
|
8457
|
-
A collection is a
|
|
9273
|
+
A collection is a relational Postgres table inside the project's isolated tenant database — typed columns, foreign keys, transactions, unique indexes, and vector search. 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.
|
|
8458
9274
|
|
|
8459
9275
|
Admin tools authenticate the developer/agent (pass \`pat\` or send it as the inbound Bearer) and take \`project_id\`. Client tools authenticate an end-user and take \`api_key\` (+ \`session_token\`) — NOT \`project_id\` and NOT a \`pat\`. Every row tool names the collection with \`name\`, never \`collection\`.
|
|
8460
9276
|
|
|
8461
9277
|
| Tool | Purpose | Example args |
|
|
8462
9278
|
| --- | --- | --- |
|
|
8463
|
-
| \`amba_collections_create\` | Create a typed collection. Pass \`shared: true\` for developer-seeded GLOBAL content (question banks, lookup tables) so \`user_id\` is nullable. | \`{ project_id, name: "todos", columns: [{ name: "title", type: "text", nullable: false }, { name: "done", type: "boolean", nullable: false
|
|
9279
|
+
| \`amba_collections_create\` | Create a typed collection. Pass \`shared: true\` for developer-seeded GLOBAL content (question banks, lookup tables) so \`user_id\` is nullable. | \`{ project_id, name: "todos", columns: [{ name: "title", type: "text", nullable: false }, { name: "done", type: "boolean", nullable: false }, { name: "due_at", type: "timestamptz", nullable: true }], shared: false }\` |
|
|
8464
9280
|
| \`amba_collections_list\` | List collections in this project. | \`{ project_id }\` |
|
|
8465
9281
|
| \`amba_collections_get\` | Read one collection's schema. | \`{ project_id, name: "todos" }\` |
|
|
8466
9282
|
| \`amba_collections_alter\` | Exactly ONE of: \`add_column\`, \`add_index\`, \`drop_column\`, or \`relax_user_id\` per call. \`relax_user_id: true\` converts an existing collection to shared (drops the \`user_id\` NOT NULL). | \`{ project_id, name: "todos", add_column: { name: "priority", type: "integer", nullable: true } }\` |
|
|
@@ -8477,7 +9293,7 @@ Admin tools authenticate the developer/agent (pass \`pat\` or send it as the inb
|
|
|
8477
9293
|
| \`amba_client_find_rows\` | Filter / sort / paginate rows (SDK-shaped \`filter\`). | \`{ api_key, session_token, name: "todos", filter: {...}, order: ["created_at desc"], limit: 50 }\` |
|
|
8478
9294
|
| \`amba_client_find_nearest_rows\` | Vector-similarity search (rows with a \`vector(<dim>)\` column). | \`{ api_key, session_token, name: "todos", column: "embedding", to_vector: [...], k: 10 }\` |
|
|
8479
9295
|
|
|
8480
|
-
Column types: \`text\`, \`integer\`, \`bigint\`, \`numeric\`, \`boolean\`, \`timestamptz\`, \`date\`, \`jsonb\`, \`uuid\`, \`vector(
|
|
9296
|
+
Column types: \`text\`, \`integer\`, \`bigint\`, \`numeric\`, \`boolean\`, \`timestamptz\`, \`date\`, \`jsonb\`, \`uuid\`, \`vector\` (pass a separate \`dimension\` field, e.g. \`{ name: "embedding", type: "vector", dimension: 1536 }\` for OpenAI embeddings), plus array forms \`text[]\`, \`integer[]\`, \`bigint[]\`, \`numeric[]\`, \`boolean[]\`, \`uuid[]\`. Columns are NOT NULL unless \`nullable: true\`; column defaults are not supported (set values at insert time). Use \`integer\` (not \`int\`), \`numeric\` (not \`float\`/\`real\`/\`double\`), and \`jsonb\` (not \`json\`) — the validator rejects the aliases.
|
|
8481
9297
|
|
|
8482
9298
|
### Functions (serverless code)
|
|
8483
9299
|
|
|
@@ -8494,6 +9310,19 @@ Run user code in a sandbox triggered by HTTP, cron, or webhook. The function get
|
|
|
8494
9310
|
| \`amba_functions_pause_schedule\` | Pause a scheduled trigger without deleting it. | \`{ project_id, function_id }\` |
|
|
8495
9311
|
| \`amba_functions_resume_schedule\` | Resume. | \`{ project_id, function_id }\` |
|
|
8496
9312
|
| \`amba_functions_trigger_schedule\` | Fire a scheduled function ad-hoc (testing). | \`{ project_id, function_id }\` |
|
|
9313
|
+
| \`amba_function_domains_attach\` | Attach one exact hostname to one function; returns DNS validation instructions. | \`{ project_id, name: "feed", hostname: "feeds.example.com" }\` |
|
|
9314
|
+
| \`amba_function_domains_list\` | List provider-neutral hostname, ownership, and certificate status. | \`{ project_id, name: "feed" }\` |
|
|
9315
|
+
| \`amba_function_domains_refresh\` | Re-poll DNS ownership and certificate state. | \`{ project_id, name: "feed", hostname: "feeds.example.com" }\` |
|
|
9316
|
+
| \`amba_function_domains_remove\` | Detach an exact function hostname. | \`{ project_id, name: "feed", hostname: "feeds.example.com" }\` |
|
|
9317
|
+
|
|
9318
|
+
Function-domain routing preserves the complete incoming path and query string.
|
|
9319
|
+
It is exact-host only (no wildcard/path rewrite and no automatic \`www\` for a
|
|
9320
|
+
subdomain). Podcast/feed clients cannot attach an Amba API key, so their
|
|
9321
|
+
function must be deployed with \`public: true\` and validate any private token
|
|
9322
|
+
inside the handler. Effective-tier caps are free 1, pro 5, scale 20, and
|
|
9323
|
+
enterprise/comped 50; attach is limited to five attempts per project per hour.
|
|
9324
|
+
Unverified claims become eligible for reclaim after 24 hours, and routing
|
|
9325
|
+
resources are allocated only after ownership is active.
|
|
8497
9326
|
|
|
8498
9327
|
### AI prompts
|
|
8499
9328
|
|
|
@@ -8512,7 +9341,7 @@ Managed LLM templates: a stored prompt with provider + model + system message, i
|
|
|
8512
9341
|
| \`amba_ai_prompts_list\` | List prompts. | \`{ project_id }\` |
|
|
8513
9342
|
| \`amba_ai_prompts_get\` | Read one prompt. | \`{ project_id, name }\` |
|
|
8514
9343
|
| \`amba_ai_prompts_update\` | Edit a prompt (replaces all fields; bumps version). | \`{ project_id, name, provider, model, system_prompt: "..." }\` |
|
|
8515
|
-
| \`amba_ai_prompts_invoke\` | Invoke
|
|
9344
|
+
| \`amba_ai_prompts_invoke\` | Invoke by name server-side (admin testing; works with \`client_invokable: false\`). Uses the named gateway path, so the prompt budget, rate limit, token cap, and spend attribution are enforced. | \`{ project_id, name, messages: [{ role: "user", content: "..." }] }\` |
|
|
8516
9345
|
| \`amba_ai_prompts_delete\` | Delete. | \`{ project_id, name }\` |
|
|
8517
9346
|
|
|
8518
9347
|
### Analytics + events + sessions
|
|
@@ -8529,11 +9358,15 @@ Managed LLM templates: a stored prompt with provider + model + system message, i
|
|
|
8529
9358
|
|
|
8530
9359
|
### Secrets + configs + integrations
|
|
8531
9360
|
|
|
8532
|
-
Secrets here
|
|
9361
|
+
Secrets here become environment bindings on deployed functions. Omit \`function\`
|
|
9362
|
+
for a project-wide secret or pass it to scope the value to one function. Setting
|
|
9363
|
+
or rotating a secret queues an asynchronous update for already-deployed
|
|
9364
|
+
functions; later deployments reconcile the binding too. They are NOT where AI
|
|
9365
|
+
provider keys go (use \`amba_ai_providers_set\` for those — see AI prompts above).
|
|
8533
9366
|
|
|
8534
9367
|
| Tool | Purpose | Example args |
|
|
8535
9368
|
| --- | --- | --- |
|
|
8536
|
-
| \`amba_secrets_set\` | Set a function
|
|
9369
|
+
| \`amba_secrets_set\` | Set or rotate a function secret; omit \`function\` for project-wide scope or pass it for one function. Already-deployed functions receive it asynchronously. | \`{ project_id, name: "STRIPE_WEBHOOK_SECRET", value: "whsec_..." }\` |
|
|
8537
9370
|
| \`amba_secrets_get\` | Read a secret (returns \`"<redacted>"\` unless explicitly requested). | \`{ project_id, name }\` |
|
|
8538
9371
|
| \`amba_secrets_list\` | List secret names. | \`{ project_id }\` |
|
|
8539
9372
|
| \`amba_secrets_delete\` | Delete. | \`{ project_id, name }\` |
|
|
@@ -8570,6 +9403,23 @@ Secrets here are **function-scoped** — they become environment bindings on you
|
|
|
8570
9403
|
| \`amba_sites_remove_domain\` | Detach a domain. | \`{ project_id, site_id, domain }\` |
|
|
8571
9404
|
| \`amba_sites_delete\` | Delete a site. | \`{ project_id, site_id }\` |
|
|
8572
9405
|
|
|
9406
|
+
### Purchased domains + email forwarding
|
|
9407
|
+
|
|
9408
|
+
| Tool | Purpose | Example args |
|
|
9409
|
+
| --- | --- | --- |
|
|
9410
|
+
| \`amba_domains_search\` | Search available domains (free). | \`{ project_id, query: "myapp" }\` |
|
|
9411
|
+
| \`amba_domains_check\` | Check authoritative price + availability. | \`{ project_id, domains: ["myapp.com"] }\` |
|
|
9412
|
+
| \`amba_domains_purchase\` | Quote, then confirm, a domain purchase. | \`{ project_id, domain: "myapp.com", site: "marketing" }\` |
|
|
9413
|
+
| \`amba_domains_list\` | List purchased domains. | \`{ project_id }\` |
|
|
9414
|
+
| \`amba_domains_email_enable\` | Enable inbound routing when no MX conflict exists. | \`{ project_id, domain: "myapp.com" }\` |
|
|
9415
|
+
| \`amba_domains_email_destinations_add\` | Add a destination mailbox; returns action-required until verified. | \`{ project_id, domain: "myapp.com", email: "owner@example.net" }\` |
|
|
9416
|
+
| \`amba_domains_email_destinations_get\` | Poll destination verification. | \`{ project_id, domain: "myapp.com", destination_id }\` |
|
|
9417
|
+
| \`amba_domains_email_forwards_set\` | Create/update a literal forward. | \`{ project_id, domain: "myapp.com", source: "support", destination: "owner@example.net" }\` |
|
|
9418
|
+
| \`amba_domains_email_forwards_list\` | List literal forwards. | \`{ project_id, domain: "myapp.com" }\` |
|
|
9419
|
+
| \`amba_domains_email_forwards_delete\` | Delete a literal forward. | \`{ project_id, domain: "myapp.com", forward_id }\` |
|
|
9420
|
+
| \`amba_domains_email_catch_all_set\` | Enable/update/disable catch-all. | \`{ project_id, domain: "myapp.com", enabled: true, destination: "owner@example.net" }\` |
|
|
9421
|
+
| \`amba_domains_email_catch_all_get\` | Read catch-all state. | \`{ project_id, domain: "myapp.com" }\` |
|
|
9422
|
+
|
|
8573
9423
|
## SDK init per stack
|
|
8574
9424
|
|
|
8575
9425
|
\`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.
|
|
@@ -8710,15 +9560,13 @@ Batch.
|
|
|
8710
9560
|
|
|
8711
9561
|
4. **Analytics:** which tracker do you want?
|
|
8712
9562
|
- Only Amba's built-in events (recommended — already wired)
|
|
8713
|
-
- Amba +
|
|
9563
|
+
- Amba + your own analytics pipeline (subscribe a webhook to project events via \`amba_webhooks_create\` and forward server-side)
|
|
8714
9564
|
- None (rarely useful — events drive XP / achievements / streaks; disabling cripples gamification)
|
|
8715
9565
|
|
|
8716
9566
|
5. **Third-party integrations to set up:**
|
|
8717
9567
|
- [ ] RevenueCat (IAP / subscriptions on iOS + Android)
|
|
8718
9568
|
- [ ] Superwall (paywall A/B)
|
|
8719
|
-
- [ ]
|
|
8720
|
-
- [ ] Stripe (web payments / subscriptions)
|
|
8721
|
-
- [ ] Mixpanel / PostHog / Segment (analytics forwarding)
|
|
9569
|
+
- [ ] Stripe Billing (web subscriptions through the app's own Stripe account — provider \`stripe_billing\`)
|
|
8722
9570
|
- [ ] OpenAI / Anthropic / Mistral / Gemini LLM keys (required for \`Amba.ai.*\` — set via \`amba_ai_providers_set\`, **not** \`amba_integrations_configure\`)
|
|
8723
9571
|
|
|
8724
9572
|
6. **Feature flags:** seed any starter flags?
|
|
@@ -9066,6 +9914,7 @@ const TOOL_CATEGORY = {
|
|
|
9066
9914
|
amba_monetization_export: "economy",
|
|
9067
9915
|
amba_monetization_definitions_list: "economy",
|
|
9068
9916
|
amba_monetization_adopt: "economy",
|
|
9917
|
+
amba_monetization_apply: "economy",
|
|
9069
9918
|
amba_entitlements_define: "economy",
|
|
9070
9919
|
amba_products_create: "economy",
|
|
9071
9920
|
amba_entitlements_map_product: "economy",
|
|
@@ -9213,6 +10062,48 @@ const TOOL_CATEGORY = {
|
|
|
9213
10062
|
amba_reprovision_project: "infrastructure",
|
|
9214
10063
|
amba_projects_get_provisioning_status: "infrastructure",
|
|
9215
10064
|
amba_get_provisioning_status: "infrastructure",
|
|
10065
|
+
amba_orgs_create: "infrastructure",
|
|
10066
|
+
amba_orgs_list: "infrastructure",
|
|
10067
|
+
amba_orgs_get: "infrastructure",
|
|
10068
|
+
amba_orgs_update: "infrastructure",
|
|
10069
|
+
amba_orgs_invite_member: "infrastructure",
|
|
10070
|
+
amba_orgs_transfer_owner: "infrastructure",
|
|
10071
|
+
amba_provision_app: "infrastructure",
|
|
10072
|
+
amba_service_accounts_create: "infrastructure",
|
|
10073
|
+
amba_service_accounts_list: "infrastructure",
|
|
10074
|
+
amba_service_accounts_revoke: "infrastructure",
|
|
10075
|
+
amba_affiliate_signup: "economy",
|
|
10076
|
+
amba_affiliate_programs_create: "economy",
|
|
10077
|
+
amba_affiliate_programs_list: "economy",
|
|
10078
|
+
amba_affiliate_programs_get: "economy",
|
|
10079
|
+
amba_affiliate_programs_update: "economy",
|
|
10080
|
+
amba_affiliate_invite: "economy",
|
|
10081
|
+
amba_affiliate_affiliates_list: "economy",
|
|
10082
|
+
amba_affiliate_affiliates_get: "economy",
|
|
10083
|
+
amba_affiliate_affiliate_approve: "economy",
|
|
10084
|
+
amba_affiliate_affiliate_pause: "economy",
|
|
10085
|
+
amba_affiliate_affiliate_ban: "economy",
|
|
10086
|
+
amba_affiliate_conversions_list: "economy",
|
|
10087
|
+
amba_affiliate_conversion_approve: "economy",
|
|
10088
|
+
amba_affiliate_conversion_reject: "economy",
|
|
10089
|
+
amba_affiliate_conversion_clawback: "economy",
|
|
10090
|
+
amba_affiliate_analytics_get: "economy",
|
|
10091
|
+
amba_affiliate_leaderboard_get: "economy",
|
|
10092
|
+
amba_affiliate_payouts_list: "economy",
|
|
10093
|
+
amba_affiliate_payout_run: "economy",
|
|
10094
|
+
amba_affiliate_my_programs_list: "economy",
|
|
10095
|
+
amba_affiliate_my_referral_get: "economy",
|
|
10096
|
+
amba_affiliate_my_referral_generate: "economy",
|
|
10097
|
+
amba_affiliate_my_stats_get: "economy",
|
|
10098
|
+
amba_affiliate_my_balance_get: "economy",
|
|
10099
|
+
amba_affiliate_my_invites_list: "economy",
|
|
10100
|
+
amba_affiliate_my_invite_accept: "economy",
|
|
10101
|
+
amba_affiliate_my_conversions_list: "economy",
|
|
10102
|
+
amba_affiliate_my_payouts_list: "economy",
|
|
10103
|
+
amba_affiliate_my_payout_request: "economy",
|
|
10104
|
+
amba_affiliate_my_payout_create: "economy",
|
|
10105
|
+
amba_affiliate_my_payout_onboarding: "economy",
|
|
10106
|
+
amba_affiliate_my_payout_status: "economy",
|
|
9216
10107
|
amba_collections_create: "infrastructure",
|
|
9217
10108
|
amba_create_collection: "infrastructure",
|
|
9218
10109
|
amba_collections_list: "infrastructure",
|
|
@@ -9258,6 +10149,10 @@ const TOOL_CATEGORY = {
|
|
|
9258
10149
|
amba_resume_function_schedule: "infrastructure",
|
|
9259
10150
|
amba_functions_trigger_schedule: "infrastructure",
|
|
9260
10151
|
amba_trigger_function_schedule: "infrastructure",
|
|
10152
|
+
amba_function_domains_attach: "infrastructure",
|
|
10153
|
+
amba_function_domains_list: "infrastructure",
|
|
10154
|
+
amba_function_domains_refresh: "infrastructure",
|
|
10155
|
+
amba_function_domains_remove: "infrastructure",
|
|
9261
10156
|
amba_sites_list: "infrastructure",
|
|
9262
10157
|
amba_list_sites: "infrastructure",
|
|
9263
10158
|
amba_sites_get: "infrastructure",
|
|
@@ -9268,6 +10163,8 @@ const TOOL_CATEGORY = {
|
|
|
9268
10163
|
amba_delete_site: "infrastructure",
|
|
9269
10164
|
amba_sites_add_domain: "infrastructure",
|
|
9270
10165
|
amba_add_site_domain: "infrastructure",
|
|
10166
|
+
amba_sites_refresh_domain: "infrastructure",
|
|
10167
|
+
amba_refresh_site_domain: "infrastructure",
|
|
9271
10168
|
amba_sites_remove_domain: "infrastructure",
|
|
9272
10169
|
amba_remove_site_domain: "infrastructure",
|
|
9273
10170
|
amba_sites_list_domains: "infrastructure",
|
|
@@ -9276,6 +10173,16 @@ const TOOL_CATEGORY = {
|
|
|
9276
10173
|
amba_domains_check: "infrastructure",
|
|
9277
10174
|
amba_domains_purchase: "infrastructure",
|
|
9278
10175
|
amba_domains_list: "infrastructure",
|
|
10176
|
+
amba_domains_email_status: "infrastructure",
|
|
10177
|
+
amba_domains_email_enable: "infrastructure",
|
|
10178
|
+
amba_domains_email_destinations_add: "infrastructure",
|
|
10179
|
+
amba_domains_email_destinations_list: "infrastructure",
|
|
10180
|
+
amba_domains_email_destinations_get: "infrastructure",
|
|
10181
|
+
amba_domains_email_forwards_set: "infrastructure",
|
|
10182
|
+
amba_domains_email_forwards_list: "infrastructure",
|
|
10183
|
+
amba_domains_email_forwards_delete: "infrastructure",
|
|
10184
|
+
amba_domains_email_catch_all_get: "infrastructure",
|
|
10185
|
+
amba_domains_email_catch_all_set: "infrastructure",
|
|
9279
10186
|
amba_operations_get: "infrastructure",
|
|
9280
10187
|
amba_operations_list: "infrastructure",
|
|
9281
10188
|
amba_media_upload: "infrastructure",
|
|
@@ -9343,8 +10250,11 @@ const TOOL_CATEGORY = {
|
|
|
9343
10250
|
amba_payments_account_status: "economy",
|
|
9344
10251
|
amba_payments_set_fee: "economy",
|
|
9345
10252
|
amba_payments_charge: "economy",
|
|
10253
|
+
amba_payments_refund: "economy",
|
|
9346
10254
|
amba_payments_balance: "economy",
|
|
9347
10255
|
amba_payments_payouts: "economy",
|
|
10256
|
+
amba_agent_checkout_enable: "economy",
|
|
10257
|
+
amba_agent_checkout_capabilities: "economy",
|
|
9348
10258
|
amba_projects_invite_member: "infrastructure",
|
|
9349
10259
|
amba_projects_list_members: "infrastructure",
|
|
9350
10260
|
amba_projects_remove_member: "infrastructure",
|
|
@@ -9378,7 +10288,10 @@ const TOOL_CATEGORY = {
|
|
|
9378
10288
|
amba_remote_configs_create: "infrastructure",
|
|
9379
10289
|
amba_remote_configs_list: "infrastructure",
|
|
9380
10290
|
amba_remote_configs_update: "infrastructure",
|
|
9381
|
-
amba_remote_configs_delete: "infrastructure"
|
|
10291
|
+
amba_remote_configs_delete: "infrastructure",
|
|
10292
|
+
amba_app_mcp_get_config: "infrastructure",
|
|
10293
|
+
amba_app_mcp_update_config: "infrastructure",
|
|
10294
|
+
amba_app_mcp_set_exposure: "infrastructure"
|
|
9382
10295
|
};
|
|
9383
10296
|
/**
|
|
9384
10297
|
* Look up the canonical category for an MCP tool name. Accepts both
|
|
@@ -9585,9 +10498,10 @@ function mapCollectionSchema(schema, options = {}) {
|
|
|
9585
10498
|
* column names, types, nullability, the access policy in plain words, and
|
|
9586
10499
|
* a worked example per verb.
|
|
9587
10500
|
*
|
|
9588
|
-
* Pure string builders — snapshot-friendly, no I/O.
|
|
9589
|
-
*
|
|
9590
|
-
*
|
|
10501
|
+
* Pure string builders — snapshot-friendly, no I/O. Collections are named as
|
|
10502
|
+
* what they are — relational Postgres tables. The hosting supplier (Neon) and
|
|
10503
|
+
* the rest of the infra stack stay hidden: "collection", "row", "column",
|
|
10504
|
+
* and "function" are the customer vocabulary, never a supplier name.
|
|
9591
10505
|
*/
|
|
9592
10506
|
/** A human-friendly type label for a column kind. */
|
|
9593
10507
|
function labelForKind(kind) {
|
|
@@ -9711,10 +10625,11 @@ function describeCollectionTool(verb, ctx) {
|
|
|
9711
10625
|
`Example: { "select": [{ "fn": "count" }] }.`
|
|
9712
10626
|
].join(" ");
|
|
9713
10627
|
case "find_nearest": return [
|
|
9714
|
-
`Vector similarity search over "${collection}" — returns the rows
|
|
10628
|
+
`Vector similarity search over "${collection}" — returns the rows closest to your query, with a synthesized \`_distance\` column.`,
|
|
9715
10629
|
`This tool exists because "${collection}" has a vector column (${mapped.vectorColumns.join(", ")}).`,
|
|
10630
|
+
`Pass \`to_text\` (the server computes the embedding) or a raw \`to_vector\`; optionally narrow with a typed \`where\`.`,
|
|
9716
10631
|
policy,
|
|
9717
|
-
`Example: { "
|
|
10632
|
+
`Example: { "to_text": "cozy winter recipes", "k": 5 }.`
|
|
9718
10633
|
].join(" ");
|
|
9719
10634
|
default: return `Operate on the "${collection}" collection.`;
|
|
9720
10635
|
}
|
|
@@ -9723,29 +10638,45 @@ function describeCollectionTool(verb, ctx) {
|
|
|
9723
10638
|
function describeListCollections(namespace) {
|
|
9724
10639
|
return [
|
|
9725
10640
|
`List every collection exposed by this app's agent surface (namespace "${namespace}").`,
|
|
9726
|
-
`Each collection
|
|
9727
|
-
`
|
|
10641
|
+
`Each collection is a relational Postgres table — typed columns, foreign keys, transactions, unique indexes, and vector search —`,
|
|
10642
|
+
`projected into typed list/get/find/insert/update/delete/aggregate tools you can call directly.`,
|
|
10643
|
+
`This tool is the index. Returns each collection's name and column summary.`
|
|
9728
10644
|
].join(" ");
|
|
9729
10645
|
}
|
|
10646
|
+
/**
|
|
10647
|
+
* Generate the description string for a function tool. Honest-confidence
|
|
10648
|
+
* rule: a tool with a declared schema reads like a hand-built SDK call; a
|
|
10649
|
+
* tool WITHOUT one says so plainly instead of over-claiming.
|
|
10650
|
+
*/
|
|
10651
|
+
function describeFunctionTool(ctx) {
|
|
10652
|
+
const parts = [];
|
|
10653
|
+
if (ctx.declaredDescription) {
|
|
10654
|
+
parts.push(ctx.declaredDescription.trim());
|
|
10655
|
+
parts.push(`(Deployed function "${ctx.fn}" on this app.)`);
|
|
10656
|
+
} else parts.push(`Invoke the deployed function "${ctx.fn}" on this app.`);
|
|
10657
|
+
if (ctx.hasDeclaredSchema) parts.push("Arguments match the input schema the function declared at deploy time and are sent as the request body.");
|
|
10658
|
+
else parts.push("This function declared no input schema — pass `body` (free-form JSON sent as the request body) and/or `query` (string key/value pairs appended to the URL). Consult the function author for the expected shape.");
|
|
10659
|
+
parts.push("Runs with the same credential as this connection; the response status and body are returned as-is.");
|
|
10660
|
+
return parts.join(" ");
|
|
10661
|
+
}
|
|
9730
10662
|
/** Description for the `<ns>_whoami` meta tool. */
|
|
9731
10663
|
function describeWhoami(namespace) {
|
|
9732
10664
|
return [`Report which app project this agent surface is connected to and the scope of the current credential`, `(namespace "${namespace}"). Use it to confirm you are pointed at the right app before mutating data.`].join(" ");
|
|
9733
10665
|
}
|
|
9734
10666
|
//#endregion
|
|
9735
10667
|
//#region src/auto/collection-tools.ts
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
Accept: "application/json"
|
|
10668
|
+
async function proxyFetch(baseUrl, headers, call) {
|
|
10669
|
+
let url = `${baseUrl}${call.path}`;
|
|
10670
|
+
if (call.query && Object.keys(call.query).length > 0) url += `?${new URLSearchParams(call.query).toString()}`;
|
|
10671
|
+
const allHeaders = {
|
|
10672
|
+
Accept: "application/json",
|
|
10673
|
+
...headers
|
|
9743
10674
|
};
|
|
9744
|
-
if (
|
|
10675
|
+
if (call.body !== void 0) allHeaders["Content-Type"] = "application/json";
|
|
9745
10676
|
const res = await fetch(url, {
|
|
9746
|
-
method:
|
|
9747
|
-
headers,
|
|
9748
|
-
body:
|
|
10677
|
+
method: call.method,
|
|
10678
|
+
headers: allHeaders,
|
|
10679
|
+
body: call.body !== void 0 ? JSON.stringify(call.body) : void 0
|
|
9749
10680
|
});
|
|
9750
10681
|
let body;
|
|
9751
10682
|
try {
|
|
@@ -9761,6 +10692,16 @@ async function adminFetch(apiClient, bearer, options) {
|
|
|
9761
10692
|
body
|
|
9762
10693
|
};
|
|
9763
10694
|
}
|
|
10695
|
+
/** Admin-route fetch (mirrors tools/collections.ts adminFetch). */
|
|
10696
|
+
async function adminFetch(apiClient, bearer, call) {
|
|
10697
|
+
return proxyFetch(apiClient.getBaseUrl(), { Authorization: `Bearer ${bearer}` }, call);
|
|
10698
|
+
}
|
|
10699
|
+
/** Client-route fetch: API key + optional end-user session Bearer. */
|
|
10700
|
+
async function clientFetch(clientAuth, call) {
|
|
10701
|
+
const headers = { "X-Api-Key": clientAuth.apiKey };
|
|
10702
|
+
if (clientAuth.sessionToken) headers["Authorization"] = `Bearer ${clientAuth.sessionToken}`;
|
|
10703
|
+
return proxyFetch(clientAuth.baseUrl, headers, call);
|
|
10704
|
+
}
|
|
9764
10705
|
/**
|
|
9765
10706
|
* Tool-name fragment regex. MCP + `tool-naming.test.ts` require lowercase
|
|
9766
10707
|
* `_`-separated names. The namespace + collection are sanitized to that
|
|
@@ -9776,11 +10717,19 @@ function collectionToolName(namespace, collection, verb) {
|
|
|
9776
10717
|
return `${sanitizeNameFragment(namespace)}_${sanitizeNameFragment(collection)}_${verb}`;
|
|
9777
10718
|
}
|
|
9778
10719
|
/**
|
|
10720
|
+
* Default `order` for list/find when the caller gives none. A
|
|
10721
|
+
* deterministic single-column order is what lets the route return a
|
|
10722
|
+
* bootstrap `next_cursor` on the first page (and is what the tool
|
|
10723
|
+
* description promises: newest first).
|
|
10724
|
+
*/
|
|
10725
|
+
const DEFAULT_LIST_ORDER = "created_at desc";
|
|
10726
|
+
/**
|
|
9779
10727
|
* Register the typed CRUD tools for a single collection on `server`,
|
|
9780
|
-
* proxying to the existing
|
|
10728
|
+
* proxying to the existing authoritative routes for the requested scope.
|
|
9781
10729
|
*/
|
|
9782
10730
|
function registerCollectionToolsFor(server, apiClient, options) {
|
|
9783
|
-
const { projectId, namespace, collection, schema, policy, scope } = options;
|
|
10731
|
+
const { projectId, namespace, collection, schema, policy, scope, clientAuth } = options;
|
|
10732
|
+
if (scope === "client" && !clientAuth) throw new Error("registerCollectionToolsFor: clientAuth is required for client scope");
|
|
9784
10733
|
const mapped = mapCollectionSchema(schema, { includeUserIdInInsert: scope === "admin" });
|
|
9785
10734
|
const describeCtx = {
|
|
9786
10735
|
collection,
|
|
@@ -9789,95 +10738,107 @@ function registerCollectionToolsFor(server, apiClient, options) {
|
|
|
9789
10738
|
writePolicy: policy.write_policy,
|
|
9790
10739
|
scope
|
|
9791
10740
|
};
|
|
9792
|
-
const
|
|
10741
|
+
const encColl = encodeURIComponent(collection);
|
|
10742
|
+
const rowsRoot = scope === "admin" ? `/projects/${projectId}/collections/${encColl}/rows` : `/collections/${encColl}`;
|
|
9793
10743
|
const name = (verb) => collectionToolName(namespace, collection, verb);
|
|
9794
|
-
|
|
10744
|
+
/**
|
|
10745
|
+
* Register one verb tool. Admin scope goes through `registerTool` (the
|
|
10746
|
+
* pat-as-argument helper — the developer Bearer flows from the inbound
|
|
10747
|
+
* request or a per-call `pat` arg); client scope registers a plain tool
|
|
10748
|
+
* whose handler closes over the connection's API key + session token
|
|
10749
|
+
* (an end-user agent has no pat — its credential IS the connection).
|
|
10750
|
+
*/
|
|
10751
|
+
function addTool(verb, schemaShape, run) {
|
|
10752
|
+
if (scope === "client") {
|
|
10753
|
+
registerPublicTool(server, name(verb), describeCollectionTool(verb, describeCtx), schemaShape, async (args) => run(args, (call) => clientFetch(clientAuth, call)));
|
|
10754
|
+
return;
|
|
10755
|
+
}
|
|
10756
|
+
registerTool(server, apiClient, name(verb), describeCollectionTool(verb, describeCtx), schemaShape, async (args, { pat }) => run(args, (call) => adminFetch(apiClient, pat, call)));
|
|
10757
|
+
}
|
|
10758
|
+
/** Build the `?query=` JSON for list/find from common args. */
|
|
10759
|
+
function buildFindQuery(args) {
|
|
10760
|
+
const findQuery = {};
|
|
10761
|
+
if (args.where !== void 0) findQuery.where = args.where;
|
|
10762
|
+
findQuery.order = args.order !== void 0 ? args.order : DEFAULT_LIST_ORDER;
|
|
10763
|
+
if (args.limit !== void 0) findQuery.limit = args.limit;
|
|
10764
|
+
if (args.cursor !== void 0) findQuery.cursor = args.cursor;
|
|
10765
|
+
if (args.select !== void 0) findQuery.select = args.select;
|
|
10766
|
+
if (args.include_deleted !== void 0) findQuery.includeDeleted = args.include_deleted;
|
|
10767
|
+
return findQuery;
|
|
10768
|
+
}
|
|
10769
|
+
const listFindCommon = {
|
|
10770
|
+
order: z.union([z.string(), z.array(z.string())]).optional().describe("Order spec, e.g. \"created_at desc\". Defaults to \"created_at desc\"."),
|
|
9795
10771
|
limit: z.number().int().positive().optional().describe("Max rows (default 50)."),
|
|
9796
|
-
cursor: z.string().optional().describe("Opaque cursor from a prior page."),
|
|
9797
|
-
order: z.union([z.string(), z.array(z.string())]).optional().describe("Order spec, e.g. \"created_at desc\"."),
|
|
10772
|
+
cursor: z.string().optional().describe("Opaque cursor from a prior page (`next_cursor`). Omit for the first page."),
|
|
9798
10773
|
select: z.array(z.string()).optional().describe("Subset of columns to return."),
|
|
9799
10774
|
include_deleted: z.boolean().optional().describe("Include soft-deleted rows. Default false.")
|
|
9800
|
-
}
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
if (order !== void 0) findQuery.order = order;
|
|
9805
|
-
if (select !== void 0) findQuery.select = select;
|
|
9806
|
-
if (include_deleted !== void 0) findQuery.includeDeleted = include_deleted;
|
|
9807
|
-
return passthroughResult(await adminFetch(apiClient, pat, {
|
|
10775
|
+
};
|
|
10776
|
+
addTool("list", { ...listFindCommon }, async (args, exec) => {
|
|
10777
|
+
const findQuery = buildFindQuery(args);
|
|
10778
|
+
return passthroughResult(await exec({
|
|
9808
10779
|
method: "GET",
|
|
9809
|
-
path:
|
|
9810
|
-
query:
|
|
10780
|
+
path: rowsRoot,
|
|
10781
|
+
query: { query: JSON.stringify(findQuery) }
|
|
9811
10782
|
}));
|
|
9812
10783
|
});
|
|
9813
|
-
|
|
9814
|
-
return passthroughResult(await
|
|
10784
|
+
addTool("get", { id: z.string().describe("Row id (uuid).") }, async (args, exec) => {
|
|
10785
|
+
return passthroughResult(await exec({
|
|
9815
10786
|
method: "GET",
|
|
9816
|
-
path: `${
|
|
10787
|
+
path: `${rowsRoot}/${encodeURIComponent(String(args.id))}`
|
|
9817
10788
|
}));
|
|
9818
10789
|
});
|
|
9819
|
-
|
|
10790
|
+
addTool("find", {
|
|
9820
10791
|
where: z.object({
|
|
9821
10792
|
...mapped.whereShape,
|
|
9822
10793
|
and: z.array(z.unknown()).optional().describe("AND-combine nested where clauses."),
|
|
9823
10794
|
or: z.array(z.unknown()).optional().describe("OR-combine nested where clauses."),
|
|
9824
10795
|
not: z.unknown().optional().describe("Negate a nested where clause.")
|
|
9825
10796
|
}).partial().optional().describe("Typed, per-column filter."),
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
include_deleted: z.boolean().optional().describe("Include soft-deleted rows. Default false.")
|
|
9831
|
-
}, async ({ where, order, limit, cursor, select, include_deleted }, { pat }) => {
|
|
9832
|
-
const findQuery = {};
|
|
9833
|
-
if (where !== void 0) findQuery.where = where;
|
|
9834
|
-
if (order !== void 0) findQuery.order = order;
|
|
9835
|
-
if (limit !== void 0) findQuery.limit = limit;
|
|
9836
|
-
if (cursor !== void 0) findQuery.cursor = cursor;
|
|
9837
|
-
if (select !== void 0) findQuery.select = select;
|
|
9838
|
-
if (include_deleted !== void 0) findQuery.includeDeleted = include_deleted;
|
|
9839
|
-
return passthroughResult(await adminFetch(apiClient, pat, {
|
|
10797
|
+
...listFindCommon
|
|
10798
|
+
}, async (args, exec) => {
|
|
10799
|
+
const findQuery = buildFindQuery(args);
|
|
10800
|
+
return passthroughResult(await exec({
|
|
9840
10801
|
method: "GET",
|
|
9841
|
-
path:
|
|
9842
|
-
query:
|
|
10802
|
+
path: rowsRoot,
|
|
10803
|
+
query: { query: JSON.stringify(findQuery) }
|
|
9843
10804
|
}));
|
|
9844
10805
|
});
|
|
9845
|
-
|
|
10806
|
+
addTool("insert", {
|
|
9846
10807
|
...mapped.insertShape,
|
|
9847
10808
|
...scope === "admin" ? { as_system: z.boolean().optional().describe("Admin only. On a shared collection, insert a global row with no owner (user_id NULL). Mutually exclusive with user_id.") } : {}
|
|
9848
|
-
}, async (args,
|
|
10809
|
+
}, async (args, exec) => {
|
|
9849
10810
|
const row = {};
|
|
9850
10811
|
for (const [k, v] of Object.entries(args)) {
|
|
9851
10812
|
if (k === "pat") continue;
|
|
9852
10813
|
if (v !== void 0) row[k] = v;
|
|
9853
10814
|
}
|
|
9854
|
-
return passthroughResult(await
|
|
10815
|
+
return passthroughResult(await exec({
|
|
9855
10816
|
method: "POST",
|
|
9856
|
-
path:
|
|
10817
|
+
path: rowsRoot,
|
|
9857
10818
|
body: row
|
|
9858
10819
|
}));
|
|
9859
10820
|
});
|
|
9860
|
-
|
|
10821
|
+
addTool("update", {
|
|
9861
10822
|
id: z.string().describe("Row id (uuid) to update."),
|
|
9862
10823
|
set: z.object({ ...mapped.updateShape }).describe("Column→value map of the fields to change (merge-patch).")
|
|
9863
|
-
}, async (
|
|
9864
|
-
return passthroughResult(await
|
|
10824
|
+
}, async (args, exec) => {
|
|
10825
|
+
return passthroughResult(await exec({
|
|
9865
10826
|
method: "PATCH",
|
|
9866
|
-
path: `${
|
|
9867
|
-
body: { set }
|
|
10827
|
+
path: `${rowsRoot}/${encodeURIComponent(String(args.id))}`,
|
|
10828
|
+
body: { set: args.set }
|
|
9868
10829
|
}));
|
|
9869
10830
|
});
|
|
9870
|
-
|
|
10831
|
+
addTool("delete", {
|
|
9871
10832
|
id: z.string().describe("Row id (uuid) to delete."),
|
|
9872
|
-
hard: z.boolean().optional().describe("false (default) soft-deletes (recoverable); true permanently removes.")
|
|
9873
|
-
}, async (
|
|
9874
|
-
return passthroughResult(await
|
|
10833
|
+
...scope === "admin" ? { hard: z.boolean().optional().describe("false (default) soft-deletes (recoverable); true permanently removes.") } : {}
|
|
10834
|
+
}, async (args, exec) => {
|
|
10835
|
+
return passthroughResult(await exec({
|
|
9875
10836
|
method: "DELETE",
|
|
9876
|
-
path: `${
|
|
9877
|
-
query: hard === true ? { hard: "true" } : void 0
|
|
10837
|
+
path: `${rowsRoot}/${encodeURIComponent(String(args.id))}`,
|
|
10838
|
+
query: scope === "admin" && args.hard === true ? { hard: "true" } : void 0
|
|
9878
10839
|
}));
|
|
9879
10840
|
});
|
|
9880
|
-
|
|
10841
|
+
addTool("aggregate", {
|
|
9881
10842
|
select: z.array(z.object({
|
|
9882
10843
|
fn: z.enum([
|
|
9883
10844
|
"count",
|
|
@@ -9892,62 +10853,248 @@ function registerCollectionToolsFor(server, apiClient, options) {
|
|
|
9892
10853
|
group_by: z.union([z.string(), z.array(z.string())]).optional().describe("Column(s) to group by. Omit for a whole-collection aggregate."),
|
|
9893
10854
|
where: z.object({ ...mapped.whereShape }).partial().optional().describe("Typed, per-column filter to aggregate a subset."),
|
|
9894
10855
|
include_deleted: z.boolean().optional().describe("Include soft-deleted rows. Default false.")
|
|
9895
|
-
}, async (
|
|
9896
|
-
const body = { select };
|
|
9897
|
-
if (group_by !== void 0) body.group_by = group_by;
|
|
9898
|
-
if (where !== void 0) body.where = where;
|
|
9899
|
-
if (include_deleted !== void 0) body.includeDeleted = include_deleted;
|
|
9900
|
-
|
|
10856
|
+
}, async (args, exec) => {
|
|
10857
|
+
const body = { select: args.select };
|
|
10858
|
+
if (args.group_by !== void 0) body.group_by = args.group_by;
|
|
10859
|
+
if (args.where !== void 0) body.where = args.where;
|
|
10860
|
+
if (args.include_deleted !== void 0) if (scope === "admin") body.includeDeleted = args.include_deleted;
|
|
10861
|
+
else body.include_deleted = args.include_deleted;
|
|
10862
|
+
return passthroughResult(await exec({
|
|
9901
10863
|
method: "POST",
|
|
9902
|
-
path: `${
|
|
10864
|
+
path: `${rowsRoot}/aggregate`,
|
|
10865
|
+
body
|
|
10866
|
+
}));
|
|
10867
|
+
});
|
|
10868
|
+
if (scope === "client" && mapped.hasVector) addTool("find_nearest", {
|
|
10869
|
+
to_text: z.string().optional().describe("Find rows nearest to this text (the server computes the embedding). Exactly one of to_text / to_vector is required."),
|
|
10870
|
+
to_vector: z.array(z.number()).optional().describe("Find rows nearest to this raw vector. Exactly one of to_text / to_vector."),
|
|
10871
|
+
column: z.string().optional().describe(`Vector column to search (default "embedding"). This collection has: ${mapped.vectorColumns.join(", ")}.`),
|
|
10872
|
+
k: z.number().int().positive().optional().describe("How many rows to return (default 10, max 100)."),
|
|
10873
|
+
distance: z.enum([
|
|
10874
|
+
"cosine",
|
|
10875
|
+
"l2",
|
|
10876
|
+
"inner_product"
|
|
10877
|
+
]).optional().describe("Distance metric — must match how the vector index was created (default cosine)."),
|
|
10878
|
+
where: z.object({ ...mapped.whereShape }).partial().optional().describe("Typed, per-column filter applied on top of the nearest search."),
|
|
10879
|
+
include_deleted: z.boolean().optional().describe("Include soft-deleted rows. Default false.")
|
|
10880
|
+
}, async (args, exec) => {
|
|
10881
|
+
const body = {};
|
|
10882
|
+
if (args.to_text !== void 0) body.to_text = args.to_text;
|
|
10883
|
+
if (args.to_vector !== void 0) body.to_vector = args.to_vector;
|
|
10884
|
+
if (args.column !== void 0) body.column = args.column;
|
|
10885
|
+
if (args.k !== void 0) body.k = args.k;
|
|
10886
|
+
if (args.distance !== void 0) body.distance = args.distance;
|
|
10887
|
+
if (args.where !== void 0) body.where = args.where;
|
|
10888
|
+
if (args.include_deleted !== void 0) body.includeDeleted = args.include_deleted;
|
|
10889
|
+
return passthroughResult(await exec({
|
|
10890
|
+
method: "POST",
|
|
10891
|
+
path: `${rowsRoot}/find-nearest`,
|
|
9903
10892
|
body
|
|
9904
10893
|
}));
|
|
9905
10894
|
});
|
|
9906
10895
|
}
|
|
9907
10896
|
//#endregion
|
|
9908
|
-
//#region src/auto/
|
|
9909
|
-
/** Page size for the admin collections list (the route caps at 200). */
|
|
9910
|
-
const COLLECTION_PAGE_SIZE = 200;
|
|
10897
|
+
//#region src/auto/json-schema-to-zod.ts
|
|
9911
10898
|
/**
|
|
9912
|
-
*
|
|
9913
|
-
*
|
|
9914
|
-
*
|
|
10899
|
+
* Declared-function-input-schema → zod shape mapper (auto-MCP Phase 2,
|
|
10900
|
+
* slices C/I).
|
|
10901
|
+
*
|
|
10902
|
+
* A deployed function may DECLARE its input schema in the deploy
|
|
10903
|
+
* annotation (`mcp.input_schema`). The API validates the annotation down
|
|
10904
|
+
* to a SUPPORTED SUBSET of JSON Schema at deploy time (see
|
|
10905
|
+
* `apps/api/src/lib/function-mcp-schema.ts`); this module converts that
|
|
10906
|
+
* subset into the `ZodRawShape` the MCP tool registration consumes, so a
|
|
10907
|
+
* declared schema becomes a genuinely typed tool — the agent sees the
|
|
10908
|
+
* function's real parameters, not a `body: object` blob.
|
|
10909
|
+
*
|
|
10910
|
+
* Supported subset (deliberately mirrored with the deploy-time
|
|
10911
|
+
* validator — change both together):
|
|
10912
|
+
*
|
|
10913
|
+
* - top level: { type: "object", properties, required? }
|
|
10914
|
+
* - property types: string | number | integer | boolean | object | array
|
|
10915
|
+
* - per property: description?, enum? (strings/numbers), items?
|
|
10916
|
+
* - nesting: objects/arrays up to the validator's depth cap
|
|
10917
|
+
*
|
|
10918
|
+
* Fail-safe posture: this runs against schemas the API already validated,
|
|
10919
|
+
* but a manifest could carry an annotation written by an older/newer API.
|
|
10920
|
+
* Any construct we can't map cleanly degrades to `z.unknown()` for that
|
|
10921
|
+
* property (the tool still works; the typing is just looser) — never a
|
|
10922
|
+
* throw, because a registration-time throw would take down the whole
|
|
10923
|
+
* tool surface for one bad annotation.
|
|
9915
10924
|
*/
|
|
9916
|
-
|
|
10925
|
+
function isPlainObject(value) {
|
|
10926
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10927
|
+
}
|
|
10928
|
+
/** Convert one property schema to a zod type. Never throws. */
|
|
10929
|
+
function jsonSchemaPropertyToZod(schema) {
|
|
10930
|
+
if (!isPlainObject(schema)) return z.unknown();
|
|
10931
|
+
const en = schema["enum"];
|
|
10932
|
+
if (Array.isArray(en) && en.length > 0) {
|
|
10933
|
+
if (en.every((v) => typeof v === "string")) {
|
|
10934
|
+
const literals = en.map((v) => z.literal(v));
|
|
10935
|
+
const first = literals[0];
|
|
10936
|
+
if (literals.length === 1) return withDescription(first, schema);
|
|
10937
|
+
return withDescription(z.union([
|
|
10938
|
+
first,
|
|
10939
|
+
literals[1],
|
|
10940
|
+
...literals.slice(2)
|
|
10941
|
+
]), schema);
|
|
10942
|
+
}
|
|
10943
|
+
if (en.every((v) => typeof v === "number")) {
|
|
10944
|
+
const literals = en.map((v) => z.literal(v));
|
|
10945
|
+
const first = literals[0];
|
|
10946
|
+
if (literals.length === 1) return withDescription(first, schema);
|
|
10947
|
+
return withDescription(z.union([
|
|
10948
|
+
first,
|
|
10949
|
+
literals[1],
|
|
10950
|
+
...literals.slice(2)
|
|
10951
|
+
]), schema);
|
|
10952
|
+
}
|
|
10953
|
+
return withDescription(z.unknown(), schema);
|
|
10954
|
+
}
|
|
10955
|
+
switch (schema["type"]) {
|
|
10956
|
+
case "string": return withDescription(z.string(), schema);
|
|
10957
|
+
case "number": return withDescription(z.number(), schema);
|
|
10958
|
+
case "integer": return withDescription(z.number().int(), schema);
|
|
10959
|
+
case "boolean": return withDescription(z.boolean(), schema);
|
|
10960
|
+
case "array": {
|
|
10961
|
+
const items = schema["items"];
|
|
10962
|
+
const elem = items === void 0 ? z.unknown() : jsonSchemaPropertyToZod(items);
|
|
10963
|
+
return withDescription(z.array(elem), schema);
|
|
10964
|
+
}
|
|
10965
|
+
case "object": {
|
|
10966
|
+
const props = schema["properties"];
|
|
10967
|
+
if (!isPlainObject(props)) return withDescription(z.record(z.unknown()), schema);
|
|
10968
|
+
const required = new Set(Array.isArray(schema["required"]) ? schema["required"].filter((r) => typeof r === "string") : []);
|
|
10969
|
+
const shape = {};
|
|
10970
|
+
for (const [key, value] of Object.entries(props)) {
|
|
10971
|
+
const mapped = jsonSchemaPropertyToZod(value);
|
|
10972
|
+
shape[key] = required.has(key) ? mapped : mapped.optional();
|
|
10973
|
+
}
|
|
10974
|
+
return withDescription(z.object(shape), schema);
|
|
10975
|
+
}
|
|
10976
|
+
default: return withDescription(z.unknown(), schema);
|
|
10977
|
+
}
|
|
10978
|
+
}
|
|
10979
|
+
function withDescription(type, schema) {
|
|
10980
|
+
const description = schema["description"];
|
|
10981
|
+
if (typeof description === "string" && description.length > 0) return type.describe(description);
|
|
10982
|
+
return type;
|
|
10983
|
+
}
|
|
9917
10984
|
/**
|
|
9918
|
-
*
|
|
9919
|
-
*
|
|
9920
|
-
*
|
|
9921
|
-
*
|
|
9922
|
-
* Returns `{ ok, names }`. `ok` is `false` if ANY page (first OR a later
|
|
9923
|
-
* one) returns a non-success status — a partial list must not masquerade
|
|
9924
|
-
* as complete, or the caller would silently register only some of the
|
|
9925
|
-
* project's collections as tools. On a mid-pagination failure the
|
|
9926
|
-
* already-fetched `names` are still returned alongside `ok:false` so a
|
|
9927
|
-
* caller can surface a diagnostic that includes what it did see.
|
|
10985
|
+
* Convert a declared top-level input schema to a `ZodRawShape` (the MCP
|
|
10986
|
+
* tool registration input). Returns `null` when the schema is not the
|
|
10987
|
+
* supported `type: "object"` shape — the caller falls back to the
|
|
10988
|
+
* generic `{ body?, query? }` tool input.
|
|
9928
10989
|
*/
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
10990
|
+
function jsonSchemaToZodShape(schema) {
|
|
10991
|
+
if (!isPlainObject(schema) || schema["type"] !== "object") return null;
|
|
10992
|
+
const props = schema["properties"];
|
|
10993
|
+
if (!isPlainObject(props)) return null;
|
|
10994
|
+
const required = new Set(Array.isArray(schema["required"]) ? schema["required"].filter((r) => typeof r === "string") : []);
|
|
10995
|
+
const shape = {};
|
|
10996
|
+
for (const [key, value] of Object.entries(props)) {
|
|
10997
|
+
const mapped = jsonSchemaPropertyToZod(value);
|
|
10998
|
+
shape[key] = required.has(key) ? mapped : mapped.optional();
|
|
10999
|
+
}
|
|
11000
|
+
return shape;
|
|
11001
|
+
}
|
|
11002
|
+
//#endregion
|
|
11003
|
+
//#region src/auto/function-tools.ts
|
|
11004
|
+
/** Build the `<ns>_fn_<name>` tool name. */
|
|
11005
|
+
function functionToolName(namespace, fn) {
|
|
11006
|
+
return `${sanitizeNameFragment(namespace)}_fn_${sanitizeNameFragment(fn)}`;
|
|
11007
|
+
}
|
|
11008
|
+
/** Generic input shape for functions without a declared schema. */
|
|
11009
|
+
const GENERIC_FUNCTION_SHAPE = {
|
|
11010
|
+
body: z.unknown().optional().describe("JSON request body forwarded to the function as-is."),
|
|
11011
|
+
query: z.record(z.string()).optional().describe("String key/value pairs appended to the invocation URL as query parameters.")
|
|
11012
|
+
};
|
|
11013
|
+
/**
|
|
11014
|
+
* Invoke the function through its public execution URL with the
|
|
11015
|
+
* connection's credential. Returns `{ status, body }` — the function's
|
|
11016
|
+
* response verbatim (JSON when parseable, text otherwise).
|
|
11017
|
+
*/
|
|
11018
|
+
async function invokeFunction(input) {
|
|
11019
|
+
let url = input.invokeUrl;
|
|
11020
|
+
if (input.query && Object.keys(input.query).length > 0) {
|
|
11021
|
+
const qs = new URLSearchParams(input.query).toString();
|
|
11022
|
+
url += url.includes("?") ? `&${qs}` : `?${qs}`;
|
|
11023
|
+
}
|
|
11024
|
+
const headers = {
|
|
11025
|
+
Accept: "application/json",
|
|
11026
|
+
"X-Api-Key": input.clientAuth.apiKey
|
|
11027
|
+
};
|
|
11028
|
+
if (input.clientAuth.sessionToken) headers["Authorization"] = `Bearer ${input.clientAuth.sessionToken}`;
|
|
11029
|
+
let bodyText;
|
|
11030
|
+
if (input.body !== void 0) {
|
|
11031
|
+
headers["Content-Type"] = "application/json";
|
|
11032
|
+
bodyText = JSON.stringify(input.body);
|
|
9945
11033
|
}
|
|
11034
|
+
const res = await fetch(url, {
|
|
11035
|
+
method: "POST",
|
|
11036
|
+
headers,
|
|
11037
|
+
body: bodyText
|
|
11038
|
+
});
|
|
11039
|
+
const text = await res.text();
|
|
11040
|
+
let parsed = text;
|
|
11041
|
+
try {
|
|
11042
|
+
parsed = text.length > 0 ? JSON.parse(text) : null;
|
|
11043
|
+
} catch {}
|
|
9946
11044
|
return {
|
|
9947
|
-
|
|
9948
|
-
|
|
11045
|
+
status: res.status,
|
|
11046
|
+
body: parsed
|
|
9949
11047
|
};
|
|
9950
11048
|
}
|
|
11049
|
+
/**
|
|
11050
|
+
* Register the tool for a single exposed function on `server`.
|
|
11051
|
+
*/
|
|
11052
|
+
function registerFunctionToolsFor(server, options) {
|
|
11053
|
+
const { namespace, fn, clientAuth } = options;
|
|
11054
|
+
const toolName = functionToolName(namespace, fn.name);
|
|
11055
|
+
const declaredShape = fn.input_schema ? jsonSchemaToZodShape(fn.input_schema) : null;
|
|
11056
|
+
const description = describeFunctionTool({
|
|
11057
|
+
fn: fn.name,
|
|
11058
|
+
declaredDescription: fn.description ?? null,
|
|
11059
|
+
hasDeclaredSchema: declaredShape !== null,
|
|
11060
|
+
isPublic: fn.public === true
|
|
11061
|
+
});
|
|
11062
|
+
if (declaredShape) {
|
|
11063
|
+
registerPublicTool(server, toolName, description, declaredShape, async (args) => {
|
|
11064
|
+
return jsonResult$1(await invokeFunction({
|
|
11065
|
+
invokeUrl: fn.invoke_url,
|
|
11066
|
+
clientAuth,
|
|
11067
|
+
body: args
|
|
11068
|
+
}));
|
|
11069
|
+
});
|
|
11070
|
+
return;
|
|
11071
|
+
}
|
|
11072
|
+
registerPublicTool(server, toolName, description, GENERIC_FUNCTION_SHAPE, async (args) => {
|
|
11073
|
+
const { body, query } = args;
|
|
11074
|
+
return jsonResult$1(await invokeFunction({
|
|
11075
|
+
invokeUrl: fn.invoke_url,
|
|
11076
|
+
clientAuth,
|
|
11077
|
+
body,
|
|
11078
|
+
...query ? { query } : {}
|
|
11079
|
+
}));
|
|
11080
|
+
});
|
|
11081
|
+
}
|
|
11082
|
+
//#endregion
|
|
11083
|
+
//#region src/auto/index.ts
|
|
11084
|
+
/** Verbs a collection's typed tool set advertises (per scope + schema). */
|
|
11085
|
+
function verbsFor(scope, schema) {
|
|
11086
|
+
const verbs = [
|
|
11087
|
+
"list",
|
|
11088
|
+
"get",
|
|
11089
|
+
"find",
|
|
11090
|
+
"insert",
|
|
11091
|
+
"update",
|
|
11092
|
+
"delete",
|
|
11093
|
+
"aggregate"
|
|
11094
|
+
];
|
|
11095
|
+
if (scope === "client" && mapCollectionSchema(schema).hasVector) verbs.push("find_nearest");
|
|
11096
|
+
return verbs;
|
|
11097
|
+
}
|
|
9951
11098
|
/** Admin GET helper bound to a Bearer (no pat-arg machinery needed here). */
|
|
9952
11099
|
async function adminGet(apiClient, path) {
|
|
9953
11100
|
const res = await fetch(`${apiClient.getBaseUrl()}${path}`, {
|
|
@@ -9974,17 +11121,22 @@ async function apiClientToken(apiClient) {
|
|
|
9974
11121
|
/**
|
|
9975
11122
|
* Register the auto-MCP tool surface for one project on `server`.
|
|
9976
11123
|
*
|
|
9977
|
-
* Lists + introspects the project's collections (admin routes) and
|
|
9978
|
-
* registers a typed tool set per collection, plus the two meta tools.
|
|
9979
11124
|
* Returns the names of the collections that were exposed plus `listOk`
|
|
9980
|
-
* (false when
|
|
9981
|
-
*
|
|
9982
|
-
* truncated set never masquerades as the whole project).
|
|
11125
|
+
* (false when no manifest was available — in which case NO data tools
|
|
11126
|
+
* register and the meta tools carry the diagnostic).
|
|
9983
11127
|
*/
|
|
9984
11128
|
async function registerAutoMcpTools(server, apiClient, options) {
|
|
9985
|
-
const { projectId, scope } = options;
|
|
9986
|
-
const namespace = sanitizeNameFragment(options.namespace ?? projectId);
|
|
9987
|
-
|
|
11129
|
+
const { projectId, scope, manifest, clientAuth } = options;
|
|
11130
|
+
const namespace = sanitizeNameFragment(options.namespace ?? (manifest?.slug ? manifest.slug : projectId));
|
|
11131
|
+
if (scope === "client") registerPublicTool(server, `${namespace}_whoami`, describeWhoami(namespace), {}, async () => jsonResult$1({
|
|
11132
|
+
status: 200,
|
|
11133
|
+
project_id: projectId,
|
|
11134
|
+
scope,
|
|
11135
|
+
namespace,
|
|
11136
|
+
authenticated: Boolean(clientAuth),
|
|
11137
|
+
end_user_session: Boolean(clientAuth?.sessionToken)
|
|
11138
|
+
}));
|
|
11139
|
+
else registerTool(server, apiClient, `${namespace}_whoami`, describeWhoami(namespace), {}, async (_args, { pat }) => {
|
|
9988
11140
|
const me = await adminGet(apiClient.withToken(pat), `/projects/${projectId}`);
|
|
9989
11141
|
return jsonResult$1({
|
|
9990
11142
|
status: me.status,
|
|
@@ -9994,85 +11146,83 @@ async function registerAutoMcpTools(server, apiClient, options) {
|
|
|
9994
11146
|
authenticated: me.status >= 200 && me.status < 300
|
|
9995
11147
|
});
|
|
9996
11148
|
});
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
listOk = all.ok;
|
|
10002
|
-
collections = all.ok ? all.names : [];
|
|
10003
|
-
} catch {
|
|
10004
|
-
collections = [];
|
|
10005
|
-
listOk = false;
|
|
10006
|
-
}
|
|
10007
|
-
registerTool(server, apiClient, `${namespace}_list_collections`, describeListCollections(namespace), {}, async (_args, { pat }) => {
|
|
10008
|
-
const boundClient = apiClient.withToken(pat);
|
|
10009
|
-
const all = await listAllCollections(boundClient, projectId);
|
|
10010
|
-
const names = all.names;
|
|
10011
|
-
const described = await Promise.all(names.map(async (collection) => {
|
|
10012
|
-
let exposed = false;
|
|
10013
|
-
try {
|
|
10014
|
-
const res = await adminGet(boundClient, `/projects/${projectId}/collections/${encodeURIComponent(collection)}`);
|
|
10015
|
-
exposed = res.status >= 200 && res.status < 300 && Array.isArray(res.body.data?.columns);
|
|
10016
|
-
} catch {
|
|
10017
|
-
exposed = false;
|
|
10018
|
-
}
|
|
10019
|
-
return {
|
|
10020
|
-
collection,
|
|
10021
|
-
exposed
|
|
10022
|
-
};
|
|
10023
|
-
}));
|
|
10024
|
-
return jsonResult$1({
|
|
10025
|
-
status: all.ok ? 200 : 502,
|
|
10026
|
-
list_complete: all.ok,
|
|
11149
|
+
if (!manifest) {
|
|
11150
|
+
const diagnosticHandler = async () => jsonResult$1({
|
|
11151
|
+
status: 503,
|
|
11152
|
+
list_complete: false,
|
|
10027
11153
|
namespace,
|
|
10028
11154
|
project_id: projectId,
|
|
10029
11155
|
scope,
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
"find",
|
|
10037
|
-
"insert",
|
|
10038
|
-
"update",
|
|
10039
|
-
"delete",
|
|
10040
|
-
"aggregate"
|
|
10041
|
-
].map((v) => collectionToolName(namespace, collection, v)) : []
|
|
10042
|
-
}))
|
|
11156
|
+
error: {
|
|
11157
|
+
code: "MANIFEST_UNAVAILABLE",
|
|
11158
|
+
message: "The app tool manifest could not be loaded for this connection. Retry shortly; if it persists, verify the credential belongs to this app and that its agent surface is enabled."
|
|
11159
|
+
},
|
|
11160
|
+
collections: [],
|
|
11161
|
+
functions: []
|
|
10043
11162
|
});
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
11163
|
+
if (scope === "client") registerPublicTool(server, `${namespace}_list_collections`, describeListCollections(namespace), {}, diagnosticHandler);
|
|
11164
|
+
else registerTool(server, apiClient, `${namespace}_list_collections`, describeListCollections(namespace), {}, diagnosticHandler);
|
|
11165
|
+
return {
|
|
11166
|
+
namespace,
|
|
11167
|
+
collections: [],
|
|
11168
|
+
listOk: false
|
|
11169
|
+
};
|
|
11170
|
+
}
|
|
11171
|
+
const manifestFunctions = (manifest.functions ?? []).filter((f) => f && typeof f.name === "string" && f.name.length > 0 && typeof f.invoke_url === "string");
|
|
11172
|
+
const exposedCollections = manifest.collections.map((c) => ({
|
|
11173
|
+
collection: c.name,
|
|
11174
|
+
exposed: true,
|
|
11175
|
+
tools: verbsFor(scope, c).map((v) => collectionToolName(namespace, c.name, v))
|
|
11176
|
+
}));
|
|
11177
|
+
const exposedFunctions = manifestFunctions.map((f) => ({
|
|
11178
|
+
function: f.name,
|
|
11179
|
+
exposed: scope === "client",
|
|
11180
|
+
tools: scope === "client" ? [functionToolName(namespace, f.name)] : [],
|
|
11181
|
+
typed: Boolean(f.input_schema)
|
|
11182
|
+
}));
|
|
11183
|
+
const listCollectionsHandler = async () => jsonResult$1({
|
|
11184
|
+
status: 200,
|
|
11185
|
+
list_complete: true,
|
|
11186
|
+
namespace,
|
|
11187
|
+
project_id: projectId,
|
|
11188
|
+
scope,
|
|
11189
|
+
manifest_version: manifest.version,
|
|
11190
|
+
exposure_note: exposedCollections.length === 0 && exposedFunctions.length === 0 ? "Nothing is exposed on this agent surface yet. Exposure is an explicit allowlist the app developer manages (collections and functions are NOT exposed by default)." : void 0,
|
|
11191
|
+
collections: exposedCollections,
|
|
11192
|
+
functions: exposedFunctions
|
|
11193
|
+
});
|
|
11194
|
+
if (scope === "client") registerPublicTool(server, `${namespace}_list_collections`, describeListCollections(namespace), {}, listCollectionsHandler);
|
|
11195
|
+
else registerTool(server, apiClient, `${namespace}_list_collections`, describeListCollections(namespace), {}, listCollectionsHandler);
|
|
11196
|
+
for (const entry of manifest.collections) {
|
|
10056
11197
|
const policy = {
|
|
10057
|
-
read_policy:
|
|
10058
|
-
write_policy:
|
|
11198
|
+
read_policy: entry.read_policy ?? "owner",
|
|
11199
|
+
write_policy: entry.write_policy ?? "owner"
|
|
10059
11200
|
};
|
|
10060
11201
|
registerCollectionToolsFor(server, apiClient, {
|
|
10061
11202
|
projectId,
|
|
10062
11203
|
namespace,
|
|
10063
|
-
collection,
|
|
11204
|
+
collection: entry.name,
|
|
10064
11205
|
schema: {
|
|
10065
|
-
name:
|
|
10066
|
-
columns:
|
|
11206
|
+
name: entry.name,
|
|
11207
|
+
columns: entry.columns
|
|
10067
11208
|
},
|
|
10068
11209
|
policy,
|
|
10069
|
-
scope
|
|
11210
|
+
scope,
|
|
11211
|
+
clientAuth
|
|
10070
11212
|
});
|
|
10071
11213
|
}
|
|
11214
|
+
if (scope === "client" && clientAuth) for (const fn of manifestFunctions) registerFunctionToolsFor(server, {
|
|
11215
|
+
namespace,
|
|
11216
|
+
fn,
|
|
11217
|
+
clientAuth: {
|
|
11218
|
+
apiKey: clientAuth.apiKey,
|
|
11219
|
+
...clientAuth.sessionToken ? { sessionToken: clientAuth.sessionToken } : {}
|
|
11220
|
+
}
|
|
11221
|
+
});
|
|
10072
11222
|
return {
|
|
10073
11223
|
namespace,
|
|
10074
|
-
collections,
|
|
10075
|
-
listOk
|
|
11224
|
+
collections: manifest.collections.map((c) => c.name),
|
|
11225
|
+
listOk: true
|
|
10076
11226
|
};
|
|
10077
11227
|
}
|
|
10078
11228
|
//#endregion
|
|
@@ -10083,7 +11233,14 @@ async function registerAutoMcpTools(server, apiClient, options) {
|
|
|
10083
11233
|
* called against it once.
|
|
10084
11234
|
*/
|
|
10085
11235
|
function registerAllTools(server, apiClient) {
|
|
10086
|
-
registerTools$
|
|
11236
|
+
registerTools$20(server, apiClient);
|
|
11237
|
+
registerTools$42(server, apiClient);
|
|
11238
|
+
registerTools$5(server, apiClient);
|
|
11239
|
+
registerTools$3(server, apiClient);
|
|
11240
|
+
registerTools$41(server, apiClient);
|
|
11241
|
+
registerTools$40(server, apiClient);
|
|
11242
|
+
registerTools$39(server, apiClient);
|
|
11243
|
+
registerTools$38(server, apiClient);
|
|
10087
11244
|
registerTools$37(server, apiClient);
|
|
10088
11245
|
registerTools$36(server, apiClient);
|
|
10089
11246
|
registerTools$35(server, apiClient);
|
|
@@ -10101,11 +11258,11 @@ function registerAllTools(server, apiClient) {
|
|
|
10101
11258
|
registerTools$23(server, apiClient);
|
|
10102
11259
|
registerTools$22(server, apiClient);
|
|
10103
11260
|
registerTools$21(server, apiClient);
|
|
10104
|
-
registerTools$20(server, apiClient);
|
|
10105
11261
|
registerTools$19(server, apiClient);
|
|
10106
11262
|
registerTools$18(server, apiClient);
|
|
10107
11263
|
registerTools$17(server, apiClient);
|
|
10108
11264
|
registerTools$16(server, apiClient);
|
|
11265
|
+
registerTools$15(server, apiClient);
|
|
10109
11266
|
registerTools$14(server, apiClient);
|
|
10110
11267
|
registerTools$13(server, apiClient);
|
|
10111
11268
|
registerTools$12(server, apiClient);
|
|
@@ -10115,9 +11272,7 @@ function registerAllTools(server, apiClient) {
|
|
|
10115
11272
|
registerTools$8(server, apiClient);
|
|
10116
11273
|
registerTools$7(server, apiClient);
|
|
10117
11274
|
registerTools$6(server, apiClient);
|
|
10118
|
-
registerTools$5(server, apiClient);
|
|
10119
11275
|
registerTools$4(server, apiClient);
|
|
10120
|
-
registerTools$3(server, apiClient);
|
|
10121
11276
|
registerTools$2(server, apiClient);
|
|
10122
11277
|
registerTools$1(server, apiClient);
|
|
10123
11278
|
registerTools(server, apiClient);
|
|
@@ -10141,4 +11296,4 @@ function createApiClient(options) {
|
|
|
10141
11296
|
return new ApiClient(opts);
|
|
10142
11297
|
}
|
|
10143
11298
|
//#endregion
|
|
10144
|
-
export { AMBA_SETUP_ECONOMY_MD, AMBA_SETUP_ECONOMY_MIME, AMBA_SETUP_ECONOMY_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_GUIDE_BODY_VERSION, AMBA_SETUP_GUIDE_MD, AMBA_SETUP_GUIDE_MIME, AMBA_SETUP_GUIDE_URI, AMBA_SETUP_IDENTITY_MD, AMBA_SETUP_IDENTITY_MIME, AMBA_SETUP_IDENTITY_URI, AMBA_SETUP_INFRASTRUCTURE_MD, AMBA_SETUP_INFRASTRUCTURE_MIME, AMBA_SETUP_INFRASTRUCTURE_URI, AMBA_SETUP_SOCIAL_MD, AMBA_SETUP_SOCIAL_MIME, AMBA_SETUP_SOCIAL_URI, AMBA_SETUP_SUB_RESOURCES, ApiClient, CATEGORY_META, CATEGORY_ORDER, EXPO_BUILD_PROMPT_MD, EXPO_BUILD_PROMPT_MIME, EXPO_BUILD_PROMPT_URI, SERVER_MANAGED_COLUMNS, TOOL_CATEGORY, classifyColumn, collectionToolName, createApiClient, describeCollectionTool, describeListCollections, describeWhoami, getToolCategory, mapCollectionSchema, registerAllResources, registerAllTools, registerAutoMcpTools, registerCollectionToolsFor, sanitizeNameFragment };
|
|
11299
|
+
export { AMBA_SETUP_ECONOMY_MD, AMBA_SETUP_ECONOMY_MIME, AMBA_SETUP_ECONOMY_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_GUIDE_BODY_VERSION, AMBA_SETUP_GUIDE_MD, AMBA_SETUP_GUIDE_MIME, AMBA_SETUP_GUIDE_URI, AMBA_SETUP_IDENTITY_MD, AMBA_SETUP_IDENTITY_MIME, AMBA_SETUP_IDENTITY_URI, AMBA_SETUP_INFRASTRUCTURE_MD, AMBA_SETUP_INFRASTRUCTURE_MIME, AMBA_SETUP_INFRASTRUCTURE_URI, AMBA_SETUP_SOCIAL_MD, AMBA_SETUP_SOCIAL_MIME, AMBA_SETUP_SOCIAL_URI, AMBA_SETUP_SUB_RESOURCES, ApiClient, CATEGORY_META, CATEGORY_ORDER, EXPO_BUILD_PROMPT_MD, EXPO_BUILD_PROMPT_MIME, EXPO_BUILD_PROMPT_URI, SERVER_MANAGED_COLUMNS, TOOL_CATEGORY, classifyColumn, collectionToolName, createApiClient, describeCollectionTool, describeFunctionTool, describeListCollections, describeWhoami, functionToolName, getToolCategory, jsonSchemaToZodShape, mapCollectionSchema, registerAllResources, registerAllTools, registerAutoMcpTools, registerCollectionToolsFor, registerFunctionToolsFor, sanitizeNameFragment };
|