@layers/amba-mcp 4.0.4 → 4.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +486 -88
- package/dist/lib/annotations.d.ts +0 -7
- package/dist/tools/event-catalog.d.ts +20 -0
- package/dist/tools/functions.d.ts +1 -1
- package/dist/tools/leagues.d.ts +1 -1
- package/dist/tools/operations.d.ts +24 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -370,7 +370,8 @@ const WRITE_VERBS = new Set([
|
|
|
370
370
|
"purchase",
|
|
371
371
|
"signup",
|
|
372
372
|
"login",
|
|
373
|
-
"refresh"
|
|
373
|
+
"refresh",
|
|
374
|
+
"track"
|
|
374
375
|
]);
|
|
375
376
|
/**
|
|
376
377
|
* Split a tool name into lowercase verb-candidate tokens, dropping the
|
|
@@ -386,7 +387,17 @@ function toolNameTokens(name) {
|
|
|
386
387
|
* test suite asserts it doesn't, so a `null` at runtime means a new tool
|
|
387
388
|
* used an unrecognised verb and needs a verb-set entry here.
|
|
388
389
|
*/
|
|
390
|
+
/**
|
|
391
|
+
* Per-tool classification overrides for names whose verb token is a noun
|
|
392
|
+
* elsewhere. `catalog` is a NOUN in catalog-management tools
|
|
393
|
+
* (`amba_create_catalog_item`, `amba_set_item_price`), so it can't be a global
|
|
394
|
+
* read verb — but `amba_events_catalog` is a pure read/discovery tool. Override
|
|
395
|
+
* it explicitly rather than polluting the verb sets.
|
|
396
|
+
*/
|
|
397
|
+
const VERB_CLASS_OVERRIDES = { amba_events_catalog: "read" };
|
|
389
398
|
function classifyToolVerb(name) {
|
|
399
|
+
const override = VERB_CLASS_OVERRIDES[name];
|
|
400
|
+
if (override) return override;
|
|
390
401
|
const tokens = toolNameTokens(name);
|
|
391
402
|
let sawRead = false;
|
|
392
403
|
let sawWrite = false;
|
|
@@ -613,7 +624,7 @@ function registerPublicTool(server, name, description, schema, handler, aliases
|
|
|
613
624
|
}
|
|
614
625
|
//#endregion
|
|
615
626
|
//#region src/tools/projects.ts
|
|
616
|
-
function registerTools$
|
|
627
|
+
function registerTools$35(server, apiClient) {
|
|
617
628
|
registerTool(server, apiClient, "amba_projects_list", "List all Amba projects owned by the authenticated developer. Returns project id, name, bundle_id, platform, and environment.", {}, async (_, { client }) => {
|
|
618
629
|
const result = await client.get("/projects");
|
|
619
630
|
return { content: [{
|
|
@@ -631,7 +642,7 @@ function registerTools$33(server, apiClient) {
|
|
|
631
642
|
registerTool(server, apiClient, "amba_projects_create", "Create a new Amba project. A project represents a mobile app and contains all its engagement configuration (push, segments, config, content, streaks).", {
|
|
632
643
|
name: z.string().describe("Human-readable project name (e.g. \"My Fitness App\")"),
|
|
633
644
|
bundle_id: z.string().optional().describe("App bundle identifier (e.g. \"com.example.myapp\"). Doubles as the audience the server expects on Apple Sign In identity tokens — set this if your app uses Sign in with Apple, or sign-in will reject with AUDIENCE_NOT_CONFIGURED."),
|
|
634
|
-
google_oauth_client_id: z.string().optional().describe("Google OAuth 2.0 client id from Google
|
|
645
|
+
google_oauth_client_id: z.string().optional().describe("Google OAuth 2.0 client id from your Google OAuth configuration. Doubles as the audience the server expects on Google Sign In id tokens — set this if your app uses Sign in with Google, or sign-in will reject with AUDIENCE_NOT_CONFIGURED. The client id is a public identifier (not a secret) — same value that ships in the app binary."),
|
|
635
646
|
platform: z.enum([
|
|
636
647
|
"ios",
|
|
637
648
|
"android",
|
|
@@ -720,7 +731,7 @@ function registerTools$33(server, apiClient) {
|
|
|
720
731
|
}
|
|
721
732
|
//#endregion
|
|
722
733
|
//#region src/tools/push.ts
|
|
723
|
-
function registerTools$
|
|
734
|
+
function registerTools$34(server, apiClient) {
|
|
724
735
|
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.", {
|
|
725
736
|
project_id: z.string().describe("The project ID"),
|
|
726
737
|
title: z.string().describe("Push notification title shown to the user"),
|
|
@@ -850,7 +861,7 @@ const segmentRulesSchema = z.object({
|
|
|
850
861
|
operator: z.enum(["AND", "OR"]).describe("Logical operator combining conditions"),
|
|
851
862
|
conditions: z.array(segmentConditionSchema).describe("Array of filter conditions")
|
|
852
863
|
});
|
|
853
|
-
function registerTools$
|
|
864
|
+
function registerTools$33(server, apiClient) {
|
|
854
865
|
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\").", {
|
|
855
866
|
project_id: z.string().describe("The project ID"),
|
|
856
867
|
include_system: z.boolean().optional().describe("Include built-in system segments in the result. Defaults to false.")
|
|
@@ -935,7 +946,7 @@ const configConditionSchema = z.object({
|
|
|
935
946
|
percentage: z.number().optional().describe("Percentage rollout (0-100)"),
|
|
936
947
|
value: z.unknown().describe("Override value for this condition")
|
|
937
948
|
});
|
|
938
|
-
function registerTools$
|
|
949
|
+
function registerTools$32(server, apiClient) {
|
|
939
950
|
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).", {
|
|
940
951
|
project_id: z.string().describe("The project ID"),
|
|
941
952
|
include_system: z.boolean().optional().describe("Include platform-provided default config keys in the result. Defaults to false.")
|
|
@@ -1013,7 +1024,7 @@ const contentItemSchema = z.object({
|
|
|
1013
1024
|
metadata: z.record(z.unknown()).optional().describe("Arbitrary metadata key-value pairs"),
|
|
1014
1025
|
is_premium: z.boolean().optional().describe("Whether this content requires a premium entitlement")
|
|
1015
1026
|
});
|
|
1016
|
-
function registerTools$
|
|
1027
|
+
function registerTools$31(server, apiClient) {
|
|
1017
1028
|
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`.", {
|
|
1018
1029
|
project_id: z.string().describe("The project ID"),
|
|
1019
1030
|
name: z.string().describe("Library name (e.g. \"Daily Motivation\", \"Workout Tips\")"),
|
|
@@ -1069,14 +1080,16 @@ function registerTools$29(server, apiClient) {
|
|
|
1069
1080
|
"random",
|
|
1070
1081
|
"sequential"
|
|
1071
1082
|
]).describe("How content items are selected for delivery"),
|
|
1072
|
-
config: z.record(z.unknown()).optional().describe("Schedule-specific configuration (e.g. delivery time, timezone)")
|
|
1073
|
-
|
|
1083
|
+
config: z.record(z.unknown()).optional().describe("Schedule-specific configuration (e.g. delivery time, timezone)"),
|
|
1084
|
+
batch_size: z.number().int().min(1).max(100).optional().describe("How many items to publish per interval. Defaults to 1 (one item per run). Set to N to publish N items each interval — e.g. \"today's 3 tips\" — without hand-rolling a date-wrap rotation client-side. The selected items are delivered as an array the in-app SDK reads.")
|
|
1085
|
+
}, async ({ project_id, library_id, name, schedule_type, config, batch_size }, { client }) => {
|
|
1074
1086
|
const payload = {
|
|
1075
1087
|
library_id,
|
|
1076
1088
|
name,
|
|
1077
1089
|
schedule_type
|
|
1078
1090
|
};
|
|
1079
1091
|
if (config !== void 0) payload.config = config;
|
|
1092
|
+
if (batch_size !== void 0) payload.batch_size = batch_size;
|
|
1080
1093
|
const result = await client.post(`/projects/${project_id}/content/schedules`, payload);
|
|
1081
1094
|
return { content: [{
|
|
1082
1095
|
type: "text",
|
|
@@ -1150,14 +1163,16 @@ function registerTools$29(server, apiClient) {
|
|
|
1150
1163
|
cron: z.string().optional().describe("Cron expression (5-field) for delivery timing"),
|
|
1151
1164
|
timezone: z.string().optional().describe("IANA timezone (e.g. \"America/Los_Angeles\")"),
|
|
1152
1165
|
segment_id: z.string().nullable().optional().describe("Target segment, or null to broadcast to all users"),
|
|
1153
|
-
is_active: z.boolean().optional().describe("Whether the schedule is active")
|
|
1154
|
-
|
|
1166
|
+
is_active: z.boolean().optional().describe("Whether the schedule is active"),
|
|
1167
|
+
batch_size: z.number().int().min(1).max(100).optional().describe("How many items to publish per interval (1–100). Set to N to switch this schedule to publishing N items each run instead of one.")
|
|
1168
|
+
}, async ({ project_id, schedule_id, name, cron, timezone, segment_id, is_active, batch_size }, { client }) => {
|
|
1155
1169
|
const payload = {};
|
|
1156
1170
|
if (name !== void 0) payload.name = name;
|
|
1157
1171
|
if (cron !== void 0) payload.cron = cron;
|
|
1158
1172
|
if (timezone !== void 0) payload.timezone = timezone;
|
|
1159
1173
|
if (segment_id !== void 0) payload.segment_id = segment_id;
|
|
1160
1174
|
if (is_active !== void 0) payload.is_active = is_active;
|
|
1175
|
+
if (batch_size !== void 0) payload.batch_size = batch_size;
|
|
1161
1176
|
const result = await client.patch(`/projects/${project_id}/content/schedules/${schedule_id}`, payload);
|
|
1162
1177
|
return { content: [{
|
|
1163
1178
|
type: "text",
|
|
@@ -1188,7 +1203,7 @@ function registerTools$29(server, apiClient) {
|
|
|
1188
1203
|
}
|
|
1189
1204
|
//#endregion
|
|
1190
1205
|
//#region src/tools/streaks.ts
|
|
1191
|
-
function registerTools$
|
|
1206
|
+
function registerTools$30(server, apiClient) {
|
|
1192
1207
|
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.", {
|
|
1193
1208
|
project_id: z.string().describe("The project ID"),
|
|
1194
1209
|
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`."),
|
|
@@ -1280,7 +1295,7 @@ function registerTools$28(server, apiClient) {
|
|
|
1280
1295
|
//#endregion
|
|
1281
1296
|
//#region src/tools/integrations.ts
|
|
1282
1297
|
const providerEnum = z.enum(INTEGRATION_PROVIDERS);
|
|
1283
|
-
function registerTools$
|
|
1298
|
+
function registerTools$29(server, apiClient) {
|
|
1284
1299
|
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.", {
|
|
1285
1300
|
project_id: z.string().describe("The project ID"),
|
|
1286
1301
|
provider: providerEnum.describe("Integration provider name"),
|
|
@@ -1350,7 +1365,7 @@ const PERIOD_DAYS = {
|
|
|
1350
1365
|
"30d": "30",
|
|
1351
1366
|
"90d": "90"
|
|
1352
1367
|
};
|
|
1353
|
-
function registerTools$
|
|
1368
|
+
function registerTools$28(server, apiClient) {
|
|
1354
1369
|
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.", {
|
|
1355
1370
|
project_id: z.string().describe("The project ID"),
|
|
1356
1371
|
period: z.enum([
|
|
@@ -1372,8 +1387,58 @@ function registerTools$26(server, apiClient) {
|
|
|
1372
1387
|
}, ["amba_get_analytics"]);
|
|
1373
1388
|
}
|
|
1374
1389
|
//#endregion
|
|
1390
|
+
//#region src/lib/tool-result.ts
|
|
1391
|
+
/**
|
|
1392
|
+
* Shared MCP tool result helpers.
|
|
1393
|
+
*
|
|
1394
|
+
* Every tool emits the same `{ content: [{ type: 'text', text: <json> }] }`
|
|
1395
|
+
* envelope. Two helpers centralize that:
|
|
1396
|
+
*
|
|
1397
|
+
* - [`jsonResult`] — wraps an arbitrary payload.
|
|
1398
|
+
* - [`passthroughResult`] — flattens an upstream HTTP response
|
|
1399
|
+
* (status + parsed body) into the same envelope. Status is written
|
|
1400
|
+
* LAST so a colliding top-level `status` field in the API response
|
|
1401
|
+
* cannot shadow the HTTP status — agents look at `parsed.status` to
|
|
1402
|
+
* distinguish 2xx from 4xx/5xx.
|
|
1403
|
+
*
|
|
1404
|
+
* Lives in `src/lib/` (vs. `src/tools/_helpers.ts`) to set the same
|
|
1405
|
+
* cross-cutting-helper precedent as `src/lib/with-pat.ts` (task #36).
|
|
1406
|
+
* `tools/*` files stay strictly tool registrations.
|
|
1407
|
+
*/
|
|
1408
|
+
/**
|
|
1409
|
+
* Wire-shape every MCP tool handler returns.
|
|
1410
|
+
*
|
|
1411
|
+
* The MCP SDK's `tool()` callback signature is structurally typed and
|
|
1412
|
+
* carries an open index signature for `_meta` etc. Declaring our return
|
|
1413
|
+
* type as a plain `{ content: [...] }` interface won't satisfy that
|
|
1414
|
+
* structural check — so the helpers' return type is left as the actual
|
|
1415
|
+
* inferred shape (no explicit interface) and consumers rely on the
|
|
1416
|
+
* inference + the SDK's structural compatibility. If we ever want a
|
|
1417
|
+
* named alias, write it as a type-alias over the inferred shape rather
|
|
1418
|
+
* than a closed interface.
|
|
1419
|
+
*/
|
|
1420
|
+
/** Wrap an arbitrary payload as a JSON-text tool result. */
|
|
1421
|
+
function jsonResult$1(payload) {
|
|
1422
|
+
return { content: [{
|
|
1423
|
+
type: "text",
|
|
1424
|
+
text: JSON.stringify(payload, null, 2)
|
|
1425
|
+
}] };
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Flatten an upstream HTTP response into the agent-facing tool payload.
|
|
1429
|
+
* Body fields are spread FIRST so a future top-level `status` key in
|
|
1430
|
+
* the API response cannot shadow the HTTP `status` — agents read
|
|
1431
|
+
* `parsed.status` to distinguish 2xx from 4xx/5xx.
|
|
1432
|
+
*/
|
|
1433
|
+
function passthroughResult(result) {
|
|
1434
|
+
return jsonResult$1({
|
|
1435
|
+
...result.body ?? {},
|
|
1436
|
+
status: result.status
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
//#endregion
|
|
1375
1440
|
//#region src/tools/users.ts
|
|
1376
|
-
function registerTools$
|
|
1441
|
+
function registerTools$27(server, apiClient) {
|
|
1377
1442
|
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.", {
|
|
1378
1443
|
project_id: z.string().describe("The project ID"),
|
|
1379
1444
|
limit: z.number().optional().describe("Maximum number of users to return (default 50)"),
|
|
@@ -1492,6 +1557,23 @@ function registerTools$25(server, apiClient) {
|
|
|
1492
1557
|
text: JSON.stringify(result, null, 2)
|
|
1493
1558
|
}] };
|
|
1494
1559
|
});
|
|
1560
|
+
registerTool(server, apiClient, "amba_users_create_cohort", [
|
|
1561
|
+
"Mint N anonymous app_users at once, all sharing the same properties (and, optionally, the same segment memberships) in a single call.",
|
|
1562
|
+
"The seed-a-test-cohort primitive: use it to populate a leaderboard, dry-run a push segment, or stand up a batch of users tagged `cohort: beta`.",
|
|
1563
|
+
"Each user is anonymous-style (no external identity) — a shared-properties cohort has no per-user identity by definition. Pass `segment_ids` to enrol every minted user into those segments.",
|
|
1564
|
+
"Capped at 500 per call (split larger seeds into multiple calls). Returns `{ user_ids, created_count }`.",
|
|
1565
|
+
"Errors: 400 INVALID_COUNT (out of 1–500) / INVALID_SEGMENT_ID (segment does not exist), 500 COHORT_FAILED."
|
|
1566
|
+
].join(" "), {
|
|
1567
|
+
project_id: z.string().describe("The project ID."),
|
|
1568
|
+
count: z.number().int().min(1).max(500).describe("How many users to mint (1–500). Larger counts return INVALID_COUNT."),
|
|
1569
|
+
properties: z.record(z.unknown()).optional().describe("Properties stamped identically onto every minted user (JSON object). Optional."),
|
|
1570
|
+
segment_ids: z.array(z.string()).optional().describe("Segment UUIDs every minted user is enrolled into. Optional.")
|
|
1571
|
+
}, async ({ project_id, count, properties, segment_ids }, { client }) => {
|
|
1572
|
+
const payload = { count };
|
|
1573
|
+
if (properties !== void 0) payload.properties = properties;
|
|
1574
|
+
if (segment_ids !== void 0) payload.segment_ids = segment_ids;
|
|
1575
|
+
return jsonResult$1(await client.post(`/projects/${project_id}/users/cohort`, payload));
|
|
1576
|
+
});
|
|
1495
1577
|
registerTool(server, apiClient, "amba_users_delete", [
|
|
1496
1578
|
"Hard-delete a single app_user. Pass exactly one of `user_id`",
|
|
1497
1579
|
"(uuid) or `anonymous_id` (string). Returns the deleted id plus",
|
|
@@ -1562,6 +1644,31 @@ function registerTools$25(server, apiClient) {
|
|
|
1562
1644
|
text: JSON.stringify(result, null, 2)
|
|
1563
1645
|
}] };
|
|
1564
1646
|
});
|
|
1647
|
+
registerTool(server, apiClient, "amba_users_reset_user", [
|
|
1648
|
+
"Reset ONE app user to a brand-new state WITHOUT deleting them. Clears",
|
|
1649
|
+
"that single user's gamification (XP + ledger, achievements,",
|
|
1650
|
+
"leaderboard entries, challenges), economy (currency balances +",
|
|
1651
|
+
"transactions, inventory, entitlements), streaks, engagement/telemetry",
|
|
1652
|
+
"events, sessions, onboarding, roles, segment memberships, push tokens",
|
|
1653
|
+
"and their social participation (friendships, group + conversation",
|
|
1654
|
+
"memberships, messages, reviews, feed activity) — but keeps the",
|
|
1655
|
+
"app_users row itself (email, anonymous_id, properties survive). This",
|
|
1656
|
+
"is the scalpel to amba_users_reset_sandbox’s sledgehammer: reset a",
|
|
1657
|
+
"single stuck/test user on a production project without touching a",
|
|
1658
|
+
"seeded cohort. Developer-authored config (currencies, achievement",
|
|
1659
|
+
"definitions, catalog) is never touched — the user goes to zero, the",
|
|
1660
|
+
"project does not change. Pass `delete:true` to ALSO remove the",
|
|
1661
|
+
"app_users row at the end (equivalent to amba_users_delete). Returns a",
|
|
1662
|
+
"per-table summary of what was cleared. 404 USER_NOT_FOUND if no row",
|
|
1663
|
+
"matched."
|
|
1664
|
+
].join(" "), {
|
|
1665
|
+
project_id: z.string().describe("The project ID"),
|
|
1666
|
+
user_id: z.string().describe("The app user UUID to reset."),
|
|
1667
|
+
delete: z.boolean().optional().describe("When true, also delete the app_users row after clearing its state (full removal). Defaults to false — the user is kept and only reset.")
|
|
1668
|
+
}, async ({ project_id, user_id, delete: alsoDelete }, { client }) => {
|
|
1669
|
+
const query = alsoDelete ? { delete: "true" } : void 0;
|
|
1670
|
+
return jsonResult$1(await client.delete(`/projects/${project_id}/users/${user_id}/reset`, query));
|
|
1671
|
+
});
|
|
1565
1672
|
registerTool(server, apiClient, "amba_sessions_analytics", "Get session analytics (DAU, active users, total sessions, average and median session duration) for a project across a rolling window.", {
|
|
1566
1673
|
project_id: z.string().describe("The project ID"),
|
|
1567
1674
|
period: z.enum([
|
|
@@ -1778,7 +1885,7 @@ final dailyLimit = await Amba.config.get<int>('daily_limit', defaultValue: 10);
|
|
|
1778
1885
|
final streak = await Amba.streaks.get('daily_login');
|
|
1779
1886
|
print('Current streak: \${streak.currentCount}');`
|
|
1780
1887
|
};
|
|
1781
|
-
function registerTools$
|
|
1888
|
+
function registerTools$26(server, apiClient) {
|
|
1782
1889
|
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([
|
|
1783
1890
|
"ios",
|
|
1784
1891
|
"android",
|
|
@@ -1863,7 +1970,7 @@ const criteriaSchema = z.object({
|
|
|
1863
1970
|
property_key: z.string().optional().describe("User property key (required for property_value type)"),
|
|
1864
1971
|
target_value: z.number().describe("Target value to unlock the achievement")
|
|
1865
1972
|
});
|
|
1866
|
-
function registerTools$
|
|
1973
|
+
function registerTools$25(server, apiClient) {
|
|
1867
1974
|
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.", {
|
|
1868
1975
|
project_id: z.string().describe("The project ID"),
|
|
1869
1976
|
key: z.string().describe("Unique key for this achievement (e.g. \"first_workout\", \"streak_master_7\")"),
|
|
@@ -1968,7 +2075,7 @@ function registerTools$23(server, apiClient) {
|
|
|
1968
2075
|
}
|
|
1969
2076
|
//#endregion
|
|
1970
2077
|
//#region src/tools/challenges.ts
|
|
1971
|
-
function registerTools$
|
|
2078
|
+
function registerTools$24(server, apiClient) {
|
|
1972
2079
|
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.", {
|
|
1973
2080
|
project_id: z.string().describe("The project ID"),
|
|
1974
2081
|
name: z.string().describe("Challenge name (e.g. \"7-Day Fitness Sprint\", \"XP Weekend Blitz\")"),
|
|
@@ -2079,7 +2186,7 @@ function registerTools$22(server, apiClient) {
|
|
|
2079
2186
|
}
|
|
2080
2187
|
//#endregion
|
|
2081
2188
|
//#region src/tools/economy.ts
|
|
2082
|
-
function registerTools$
|
|
2189
|
+
function registerTools$23(server, apiClient) {
|
|
2083
2190
|
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.", {
|
|
2084
2191
|
project_id: z.string().describe("The project ID"),
|
|
2085
2192
|
code: z.string().describe("Unique currency code (e.g. \"gold\", \"gems\", \"hearts\")"),
|
|
@@ -2327,6 +2434,44 @@ function registerTools$21(server, apiClient) {
|
|
|
2327
2434
|
text: JSON.stringify(result, null, 2)
|
|
2328
2435
|
}] };
|
|
2329
2436
|
});
|
|
2437
|
+
registerTool(server, apiClient, "amba_entitlements_grant", [
|
|
2438
|
+
"Grant or refresh a subscription entitlement directly on a user — the same `user_entitlements` row mobile store webhooks (Apple/Play) write to, so a server-side grant and an in-app purchase land in one unified place your client SDK reads with `Amba.entitlements`.",
|
|
2439
|
+
"Covers the non-mobile cases that have no webhook: Stripe/Paddle web checkout, manual support grants, gift codes, legacy migrations. Set is_active=false to revoke (ToS violations, refunds).",
|
|
2440
|
+
"Upserts on (user, entitlement_id) — a second call for the same pair refreshes the existing row in place rather than duplicating. Omit a nullable field to preserve it; pass null to clear it."
|
|
2441
|
+
].join(" "), {
|
|
2442
|
+
project_id: z.string().describe("The project ID"),
|
|
2443
|
+
app_user_id: z.string().describe("The app_user UUID to grant the entitlement to"),
|
|
2444
|
+
entitlement_id: z.string().describe("Entitlement identifier (e.g. \"premium\", \"pro_annual\"). Required."),
|
|
2445
|
+
is_active: z.boolean().optional().describe("Whether the entitlement is active. Defaults to true on grant; pass false to revoke."),
|
|
2446
|
+
product_id: z.string().nullable().optional().describe("Store product identifier (e.g. \"stripe_premium_yr\"). Pass null to clear."),
|
|
2447
|
+
store: z.enum([
|
|
2448
|
+
"app_store",
|
|
2449
|
+
"play_store",
|
|
2450
|
+
"stripe"
|
|
2451
|
+
]).nullable().optional().describe("Originating store. Pass null to clear."),
|
|
2452
|
+
purchase_date: z.string().nullable().optional().describe("ISO-8601 purchase timestamp. Pass null to clear."),
|
|
2453
|
+
expiration_date: z.string().nullable().optional().describe("ISO-8601 expiration timestamp. Pass null to clear (e.g. for a lifetime grant)."),
|
|
2454
|
+
period_type: z.enum([
|
|
2455
|
+
"trial",
|
|
2456
|
+
"intro",
|
|
2457
|
+
"normal"
|
|
2458
|
+
]).nullable().optional().describe("Billing period type. Pass null to clear."),
|
|
2459
|
+
raw_data: z.record(z.unknown()).nullable().optional().describe("Opaque JSON blob for audit (e.g. the raw webhook payload). Pass null to reset to {}.")
|
|
2460
|
+
}, async ({ project_id, app_user_id, entitlement_id, is_active, product_id, store, purchase_date, expiration_date, period_type, raw_data }, { client }) => {
|
|
2461
|
+
const payload = { entitlement_id };
|
|
2462
|
+
if (is_active !== void 0) payload.is_active = is_active;
|
|
2463
|
+
if (product_id !== void 0) payload.product_id = product_id;
|
|
2464
|
+
if (store !== void 0) payload.store = store;
|
|
2465
|
+
if (purchase_date !== void 0) payload.purchase_date = purchase_date;
|
|
2466
|
+
if (expiration_date !== void 0) payload.expiration_date = expiration_date;
|
|
2467
|
+
if (period_type !== void 0) payload.period_type = period_type;
|
|
2468
|
+
if (raw_data !== void 0) payload.raw_data = raw_data;
|
|
2469
|
+
const result = await client.post(`/projects/${project_id}/users/${app_user_id}/entitlements`, payload);
|
|
2470
|
+
return { content: [{
|
|
2471
|
+
type: "text",
|
|
2472
|
+
text: JSON.stringify(result, null, 2)
|
|
2473
|
+
}] };
|
|
2474
|
+
});
|
|
2330
2475
|
registerTool(server, apiClient, "amba_users_get_inventory", "View a user's inventory including all owned items and quantities.", {
|
|
2331
2476
|
project_id: z.string().describe("The project ID"),
|
|
2332
2477
|
app_user_id: z.string().describe("The user ID to look up")
|
|
@@ -2612,10 +2757,27 @@ function registerTools$21(server, apiClient) {
|
|
|
2612
2757
|
text: JSON.stringify(result, null, 2)
|
|
2613
2758
|
}] };
|
|
2614
2759
|
}, ["amba_get_currency_transactions"]);
|
|
2760
|
+
registerTool(server, apiClient, "amba_currencies_get_user_balance", [
|
|
2761
|
+
"Read a user's current per-currency balances directly — the live value your client SDK and paywall logic see, returned in one call instead of replaying and summing the transaction ledger.",
|
|
2762
|
+
"Returns one row per currency the user holds (balance, lifetime_earned, lifetime_spent, last_recharged_at), or an empty array if they hold none. Pass currency_id to narrow to a single currency.",
|
|
2763
|
+
"Pair with amba_currencies_grant / amba_currencies_spend to verify a balance change landed, or with amba_currencies_get_transactions to reconcile the balance against its ledger history."
|
|
2764
|
+
].join(" "), {
|
|
2765
|
+
project_id: z.string().describe("The project ID"),
|
|
2766
|
+
user_id: z.string().describe("The app user ID"),
|
|
2767
|
+
currency_id: z.string().optional().describe("Filter to one currency (definition UUID).")
|
|
2768
|
+
}, async ({ project_id, user_id, currency_id }, { client }) => {
|
|
2769
|
+
const query = {};
|
|
2770
|
+
if (currency_id !== void 0) query.currency_id = currency_id;
|
|
2771
|
+
const result = await client.get(`/projects/${project_id}/currencies/balances/${user_id}`, query);
|
|
2772
|
+
return { content: [{
|
|
2773
|
+
type: "text",
|
|
2774
|
+
text: JSON.stringify(result, null, 2)
|
|
2775
|
+
}] };
|
|
2776
|
+
});
|
|
2615
2777
|
}
|
|
2616
2778
|
//#endregion
|
|
2617
2779
|
//#region src/tools/leaderboards.ts
|
|
2618
|
-
function registerTools$
|
|
2780
|
+
function registerTools$22(server, apiClient) {
|
|
2619
2781
|
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)`.", {
|
|
2620
2782
|
project_id: z.string().describe("The project ID"),
|
|
2621
2783
|
name: z.string().describe("Leaderboard name (e.g. \"Top XP Earners\", \"Weekly Streak Leaders\")"),
|
|
@@ -2727,10 +2889,10 @@ function registerTools$20(server, apiClient) {
|
|
|
2727
2889
|
* cohorts that the rollover workflow reshuffles. These tools provision and
|
|
2728
2890
|
* inspect leagues; the admin routes are mounted at
|
|
2729
2891
|
* `/v1/admin/projects/:projectId/leagues`. Members are assigned by the weekly
|
|
2730
|
-
* rollover (the
|
|
2892
|
+
* rollover (the scheduled weekly rollover) and score live off `xp_awarded`
|
|
2731
2893
|
* engagement events.
|
|
2732
2894
|
*/
|
|
2733
|
-
function registerTools$
|
|
2895
|
+
function registerTools$21(server, apiClient) {
|
|
2734
2896
|
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.", {
|
|
2735
2897
|
project_id: z.string().describe("The project ID"),
|
|
2736
2898
|
name: z.string().describe("League tier name, e.g. \"Bronze\", \"Silver\", \"Gold\""),
|
|
@@ -2793,7 +2955,7 @@ function registerTools$19(server, apiClient) {
|
|
|
2793
2955
|
}
|
|
2794
2956
|
//#endregion
|
|
2795
2957
|
//#region src/tools/platform.ts
|
|
2796
|
-
function registerTools$
|
|
2958
|
+
function registerTools$20(server, apiClient) {
|
|
2797
2959
|
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).", {
|
|
2798
2960
|
project_id: z.string().describe("The project ID"),
|
|
2799
2961
|
name: z.string().describe("Flow name (e.g. \"Welcome Flow\", \"Premium Onboarding\")"),
|
|
@@ -3369,7 +3531,7 @@ function registerTools$18(server, apiClient) {
|
|
|
3369
3531
|
}
|
|
3370
3532
|
//#endregion
|
|
3371
3533
|
//#region src/tools/social.ts
|
|
3372
|
-
function registerTools$
|
|
3534
|
+
function registerTools$19(server, apiClient) {
|
|
3373
3535
|
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 }) => {
|
|
3374
3536
|
const result = await client.get(`/projects/${project_id}/friends/stats`);
|
|
3375
3537
|
return { content: [{
|
|
@@ -3694,7 +3856,7 @@ function registerTools$17(server, apiClient) {
|
|
|
3694
3856
|
}
|
|
3695
3857
|
//#endregion
|
|
3696
3858
|
//#region src/tools/xp.ts
|
|
3697
|
-
function registerTools$
|
|
3859
|
+
function registerTools$18(server, apiClient) {
|
|
3698
3860
|
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.", {
|
|
3699
3861
|
project_id: z.string().describe("The project ID"),
|
|
3700
3862
|
name: z.string().describe("Rule name (e.g. \"Workout Completed\", \"Daily Login Bonus\")"),
|
|
@@ -3817,7 +3979,7 @@ function registerTools$16(server, apiClient) {
|
|
|
3817
3979
|
}
|
|
3818
3980
|
//#endregion
|
|
3819
3981
|
//#region src/tools/events.ts
|
|
3820
|
-
function registerTools$
|
|
3982
|
+
function registerTools$17(server, apiClient) {
|
|
3821
3983
|
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.", {
|
|
3822
3984
|
project_id: z.string().describe("The project ID"),
|
|
3823
3985
|
since: z.string().optional().describe("ISO-8601 lower bound for occurred_at. Defaults to 24h ago."),
|
|
@@ -3857,6 +4019,84 @@ function registerTools$15(server, apiClient) {
|
|
|
3857
4019
|
text: JSON.stringify(result, null, 2)
|
|
3858
4020
|
}] };
|
|
3859
4021
|
});
|
|
4022
|
+
registerTool(server, apiClient, "amba_events_track", [
|
|
4023
|
+
"Track an engagement event on behalf of a user from the server side — the one call that drives the whole reactive pipeline: streak qualification, currency grant rules, XP awards, achievements, segment membership, and any webhooks subscribed to the event all fan out from this single write.",
|
|
4024
|
+
"Use it to dogfood the event→XP→achievement loop without a device: emit the exact event your app will (e.g. \"mission_completed\" with {xp: 100}) and watch the downstream effects land, then read them back with amba_users_get_xp / amba_achievements_list.",
|
|
4025
|
+
"Pass `event_id` for exactly-once semantics — a re-POST with the same id is a silent no-op and never double-credits a streak or grant. Omit `app_user_id` for a project-scope event (no end-user attribution).",
|
|
4026
|
+
"Send one event, or a `batch` of up to the per-request cap, in a single round-trip. Returns `{data: {tracked, inserted, skipped}}` — `skipped` counts idempotent replays."
|
|
4027
|
+
].join(" "), {
|
|
4028
|
+
project_id: z.string().describe("The project ID"),
|
|
4029
|
+
event_name: z.string().describe("Event name (e.g. \"mission_completed\", \"lesson_finished\"). Required for a single event.").optional(),
|
|
4030
|
+
app_user_id: z.string().nullable().optional().describe("The app_user UUID this event belongs to. Omit or pass null for a project-scope event with no end-user attribution."),
|
|
4031
|
+
properties: z.record(z.unknown()).optional().describe("Arbitrary event properties (e.g. {xp: 100, level: 3}). Read by grant/XP rules."),
|
|
4032
|
+
occurred_at: z.string().optional().describe("ISO-8601 timestamp the event occurred. Defaults to now."),
|
|
4033
|
+
event_id: z.string().optional().describe("Idempotency key (≤255 chars). A re-POST with the same id is a silent no-op — no double-credit."),
|
|
4034
|
+
batch: z.array(z.object({
|
|
4035
|
+
event_name: z.string().describe("Event name. Required."),
|
|
4036
|
+
app_user_id: z.string().nullable().optional().describe("The app_user UUID. Omit or pass null for a project-scope event."),
|
|
4037
|
+
properties: z.record(z.unknown()).optional().describe("Arbitrary event properties."),
|
|
4038
|
+
occurred_at: z.string().optional().describe("ISO-8601 occurrence timestamp."),
|
|
4039
|
+
event_id: z.string().optional().describe("Idempotency key for this event.")
|
|
4040
|
+
})).optional().describe("Track many events in one call. When provided, the top-level single-event fields are ignored.")
|
|
4041
|
+
}, async ({ project_id, event_name, app_user_id, properties, occurred_at, event_id, batch }, { client }) => {
|
|
4042
|
+
let payload;
|
|
4043
|
+
if (batch !== void 0) payload = { events: batch };
|
|
4044
|
+
else {
|
|
4045
|
+
const single = { event_name };
|
|
4046
|
+
if (app_user_id !== void 0) single.app_user_id = app_user_id;
|
|
4047
|
+
if (properties !== void 0) single.properties = properties;
|
|
4048
|
+
if (occurred_at !== void 0) single.occurred_at = occurred_at;
|
|
4049
|
+
if (event_id !== void 0) single.event_id = event_id;
|
|
4050
|
+
payload = single;
|
|
4051
|
+
}
|
|
4052
|
+
return jsonResult$1(await client.post(`/projects/${project_id}/events`, payload));
|
|
4053
|
+
});
|
|
4054
|
+
}
|
|
4055
|
+
//#endregion
|
|
4056
|
+
//#region src/tools/event-catalog.ts
|
|
4057
|
+
function registerTools$16(server, apiClient) {
|
|
4058
|
+
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.", {
|
|
4059
|
+
project_id: z.string().describe("The project ID"),
|
|
4060
|
+
plane: z.enum(["app", "control"]).optional().describe("Filter to one plane. Omit for both.")
|
|
4061
|
+
}, async ({ project_id, plane }, { client }) => {
|
|
4062
|
+
const qs = plane ? `?plane=${plane}` : "";
|
|
4063
|
+
const result = await client.get(`/projects/${project_id}/webhooks/catalog${qs}`);
|
|
4064
|
+
return { content: [{
|
|
4065
|
+
type: "text",
|
|
4066
|
+
text: JSON.stringify(result, null, 2)
|
|
4067
|
+
}] };
|
|
4068
|
+
});
|
|
4069
|
+
registerTool(server, apiClient, "amba_control_webhooks_create", "Subscribe a webhook to CONTROL-plane lifecycle events for this project — provisioning, deploys, domains, billing. Amba sends an HMAC-signed POST to target_url whenever a matching event fires. event_name is an exact event (`project.provisioned`), a namespace wildcard (`domain.*`), or all control events (`*`). The signing secret is returned ONCE — copy it into your receiver. target_url must be https:// (http:// only for localhost). Use amba_events_catalog (plane:'control') to see what's available. For APP events use amba_webhooks_create instead.", {
|
|
4070
|
+
project_id: z.string().describe("The project ID"),
|
|
4071
|
+
event_name: z.string().describe("Control event to subscribe to: exact (`project.provisioned`), wildcard (`domain.*`), or `*`."),
|
|
4072
|
+
target_url: z.string().describe("HTTPS endpoint that receives the signed POST. http:// only for localhost.")
|
|
4073
|
+
}, async ({ project_id, event_name, target_url }, { client }) => {
|
|
4074
|
+
const result = await client.post(`/projects/${project_id}/control-webhooks`, {
|
|
4075
|
+
event_name,
|
|
4076
|
+
target_url
|
|
4077
|
+
});
|
|
4078
|
+
return { content: [{
|
|
4079
|
+
type: "text",
|
|
4080
|
+
text: JSON.stringify(result, null, 2)
|
|
4081
|
+
}] };
|
|
4082
|
+
});
|
|
4083
|
+
registerTool(server, apiClient, "amba_control_webhooks_list", "List this project's control-plane webhook subscriptions (most recent first). Secrets are never included.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
4084
|
+
const result = await client.get(`/projects/${project_id}/control-webhooks`);
|
|
4085
|
+
return { content: [{
|
|
4086
|
+
type: "text",
|
|
4087
|
+
text: JSON.stringify(result, null, 2)
|
|
4088
|
+
}] };
|
|
4089
|
+
});
|
|
4090
|
+
registerTool(server, apiClient, "amba_control_webhooks_delete", "Delete a control-plane webhook subscription by id.", {
|
|
4091
|
+
project_id: z.string().describe("The project ID"),
|
|
4092
|
+
id: z.string().describe("The subscription id")
|
|
4093
|
+
}, async ({ project_id, id }, { client }) => {
|
|
4094
|
+
const result = await client.delete(`/projects/${project_id}/control-webhooks/${id}`);
|
|
4095
|
+
return { content: [{
|
|
4096
|
+
type: "text",
|
|
4097
|
+
text: JSON.stringify(result, null, 2)
|
|
4098
|
+
}] };
|
|
4099
|
+
});
|
|
3860
4100
|
}
|
|
3861
4101
|
//#endregion
|
|
3862
4102
|
//#region src/tools/funnels.ts
|
|
@@ -3884,7 +4124,7 @@ const funnelStepSchema = z.object({
|
|
|
3884
4124
|
event: z.string().describe("Event name to match (engagement event_name)."),
|
|
3885
4125
|
filters: z.array(funnelFilterSchema).optional().describe("Optional property predicates, ANDed together, that an event must satisfy.")
|
|
3886
4126
|
});
|
|
3887
|
-
function registerTools$
|
|
4127
|
+
function registerTools$15(server, apiClient) {
|
|
3888
4128
|
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 }) => {
|
|
3889
4129
|
const result = await client.get(`/projects/${project_id}/funnels`);
|
|
3890
4130
|
return { content: [{
|
|
@@ -3978,56 +4218,6 @@ function registerTools$14(server, apiClient) {
|
|
|
3978
4218
|
});
|
|
3979
4219
|
}
|
|
3980
4220
|
//#endregion
|
|
3981
|
-
//#region src/lib/tool-result.ts
|
|
3982
|
-
/**
|
|
3983
|
-
* Shared MCP tool result helpers.
|
|
3984
|
-
*
|
|
3985
|
-
* Every tool emits the same `{ content: [{ type: 'text', text: <json> }] }`
|
|
3986
|
-
* envelope. Two helpers centralize that:
|
|
3987
|
-
*
|
|
3988
|
-
* - [`jsonResult`] — wraps an arbitrary payload.
|
|
3989
|
-
* - [`passthroughResult`] — flattens an upstream HTTP response
|
|
3990
|
-
* (status + parsed body) into the same envelope. Status is written
|
|
3991
|
-
* LAST so a colliding top-level `status` field in the API response
|
|
3992
|
-
* cannot shadow the HTTP status — agents look at `parsed.status` to
|
|
3993
|
-
* distinguish 2xx from 4xx/5xx.
|
|
3994
|
-
*
|
|
3995
|
-
* Lives in `src/lib/` (vs. `src/tools/_helpers.ts`) to set the same
|
|
3996
|
-
* cross-cutting-helper precedent as `src/lib/with-pat.ts` (task #36).
|
|
3997
|
-
* `tools/*` files stay strictly tool registrations.
|
|
3998
|
-
*/
|
|
3999
|
-
/**
|
|
4000
|
-
* Wire-shape every MCP tool handler returns.
|
|
4001
|
-
*
|
|
4002
|
-
* The MCP SDK's `tool()` callback signature is structurally typed and
|
|
4003
|
-
* carries an open index signature for `_meta` etc. Declaring our return
|
|
4004
|
-
* type as a plain `{ content: [...] }` interface won't satisfy that
|
|
4005
|
-
* structural check — so the helpers' return type is left as the actual
|
|
4006
|
-
* inferred shape (no explicit interface) and consumers rely on the
|
|
4007
|
-
* inference + the SDK's structural compatibility. If we ever want a
|
|
4008
|
-
* named alias, write it as a type-alias over the inferred shape rather
|
|
4009
|
-
* than a closed interface.
|
|
4010
|
-
*/
|
|
4011
|
-
/** Wrap an arbitrary payload as a JSON-text tool result. */
|
|
4012
|
-
function jsonResult$1(payload) {
|
|
4013
|
-
return { content: [{
|
|
4014
|
-
type: "text",
|
|
4015
|
-
text: JSON.stringify(payload, null, 2)
|
|
4016
|
-
}] };
|
|
4017
|
-
}
|
|
4018
|
-
/**
|
|
4019
|
-
* Flatten an upstream HTTP response into the agent-facing tool payload.
|
|
4020
|
-
* Body fields are spread FIRST so a future top-level `status` key in
|
|
4021
|
-
* the API response cannot shadow the HTTP `status` — agents read
|
|
4022
|
-
* `parsed.status` to distinguish 2xx from 4xx/5xx.
|
|
4023
|
-
*/
|
|
4024
|
-
function passthroughResult(result) {
|
|
4025
|
-
return jsonResult$1({
|
|
4026
|
-
...result.body ?? {},
|
|
4027
|
-
status: result.status
|
|
4028
|
-
});
|
|
4029
|
-
}
|
|
4030
|
-
//#endregion
|
|
4031
4221
|
//#region src/tools/auth.ts
|
|
4032
4222
|
async function authFetch(apiClient, options) {
|
|
4033
4223
|
const url = `${apiClient.getApiRoot()}${options.path}`;
|
|
@@ -4150,7 +4340,7 @@ function enrichedAuthResult(result, agentInstructions) {
|
|
|
4150
4340
|
status: result.status
|
|
4151
4341
|
});
|
|
4152
4342
|
}
|
|
4153
|
-
function registerTools$
|
|
4343
|
+
function registerTools$14(server, apiClient) {
|
|
4154
4344
|
registerPublicTool(server, "amba_developer_signup", [
|
|
4155
4345
|
"Create a new Amba developer account. Returns a long-lived Personal Access Token (PAT)",
|
|
4156
4346
|
"plus a real isolated Amba project (provisioning asynchronously), plus ready-to-paste",
|
|
@@ -4359,10 +4549,10 @@ const sdkOrderSchema = z.array(z.object({
|
|
|
4359
4549
|
})).describe("Ordering. `[{column: \"created_at\", direction: \"desc\"}, ...]`.");
|
|
4360
4550
|
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.");
|
|
4361
4551
|
const setSchema = z.record(z.unknown()).describe("Column-value map. Server-managed columns (id, created_at, etc.) are rejected.");
|
|
4362
|
-
function registerTools$
|
|
4552
|
+
function registerTools$13(server, apiClient) {
|
|
4363
4553
|
registerTool(server, apiClient, "amba_collections_create", [
|
|
4364
4554
|
"Create a new collection (schema-first Postgres table) in a project.",
|
|
4365
|
-
"The
|
|
4555
|
+
"The schema change is applied server-side via an async workflow.",
|
|
4366
4556
|
"Authenticates as the developer/agent — pass `pat` (the Personal Access Token returned by amba_developer_signup) or send it as the inbound Bearer.",
|
|
4367
4557
|
"Returns the workflow id + version + status. Failure responses include `details.workflow_id` for debugging.",
|
|
4368
4558
|
"Errors: 400 RESERVED_NAME / INVALID_COLLECTION_SCHEMA / COLLECTION_MIGRATION_FAILED, 401 MISSING_PAT."
|
|
@@ -4435,6 +4625,25 @@ function registerTools$12(server, apiClient) {
|
|
|
4435
4625
|
bearer: pat
|
|
4436
4626
|
}));
|
|
4437
4627
|
}, ["amba_delete_collection"]);
|
|
4628
|
+
registerTool(server, apiClient, "amba_collections_reset_data", [
|
|
4629
|
+
"Reset a collection: empty ALL of its rows in one call WITHOUT dropping the collection — the schema, columns and indexes survive, only the data is cleared. Use this to wipe a fixture/test collection back to empty between runs without re-creating it (use amba_collections_delete to drop the collection itself).",
|
|
4630
|
+
"Hard by default — rows are permanently removed. Pass `hard:false` to soft-delete instead (sets deleted_at so rows stay recoverable).",
|
|
4631
|
+
"Authenticates as the developer/agent — pass `pat` or send as inbound Bearer.",
|
|
4632
|
+
"Returns the number of rows cleared. Errors: 404 COLLECTION_NOT_FOUND if the collection does not exist."
|
|
4633
|
+
].join(" "), {
|
|
4634
|
+
project_id: z.string().describe("The project ID."),
|
|
4635
|
+
name: z.string().describe("Collection name whose rows to clear."),
|
|
4636
|
+
hard: z.boolean().optional().describe("true (default) permanently deletes every row; false soft-deletes (sets deleted_at) so rows remain recoverable.")
|
|
4637
|
+
}, async ({ project_id, name, hard }, { pat }) => {
|
|
4638
|
+
const query = {};
|
|
4639
|
+
if (hard === false) query.hard = "false";
|
|
4640
|
+
return passthroughResult(await adminFetch(apiClient, {
|
|
4641
|
+
method: "DELETE",
|
|
4642
|
+
path: `/projects/${project_id}/collections/${encodeURIComponent(name)}/data`,
|
|
4643
|
+
query,
|
|
4644
|
+
bearer: pat
|
|
4645
|
+
}));
|
|
4646
|
+
});
|
|
4438
4647
|
registerTool(server, apiClient, "amba_collections_alter", [
|
|
4439
4648
|
"Alter a collection: exactly one of `add_column`, `add_index`, `change_type`, `rename_column`, `drop_column`, or `relax_user_id` per call (the underlying saga is built around one SQL blob per workflow).",
|
|
4440
4649
|
"Drop-column is destructive — requires `confirm` to equal the dropped column name.",
|
|
@@ -4482,13 +4691,15 @@ function registerTools$12(server, apiClient) {
|
|
|
4482
4691
|
registerTool(server, apiClient, "amba_admin_insert_row", [
|
|
4483
4692
|
"Insert a row directly into a collection from the developer/agent side — BYPASSES auto-RLS.",
|
|
4484
4693
|
"Server-managed columns (id, created_at, updated_at, deleted_at) are stripped from the body. `user_id` is honored if present (admins can create rows attributed to any app_user).",
|
|
4694
|
+
"For developer-seeded GLOBAL content (question banks, lookup tables, daily content) on a shared:true collection, pass as_system=true to write a row with NO owner (user_id = NULL). Rejected with NOT_SHARED_COLLECTION on a non-shared collection — create it with shared:true (or PATCH relax_user_id:true) first.",
|
|
4485
4695
|
"Atomic upsert: pass on_conflict=\"ignore\" (return the existing row, no change) or \"update\" (merge) plus conflict_target (the unique-index columns). Replaces read-then-write races.",
|
|
4486
4696
|
"Authenticates as the developer/agent — pass `pat` or send as inbound Bearer.",
|
|
4487
|
-
"Errors: 400 INVALID_COLUMN / INVALID_CONFLICT_TARGET, 404 COLLECTION_NOT_FOUND, 500 CREATE_FAILED."
|
|
4697
|
+
"Errors: 400 INVALID_COLUMN / INVALID_CONFLICT_TARGET / NOT_SHARED_COLLECTION, 404 COLLECTION_NOT_FOUND, 500 CREATE_FAILED."
|
|
4488
4698
|
].join(" "), {
|
|
4489
4699
|
project_id: z.string().describe("The project ID."),
|
|
4490
4700
|
name: z.string().describe("Collection name."),
|
|
4491
4701
|
row: z.record(z.unknown()).describe("Column-value map for the new row. Server-managed columns are stripped."),
|
|
4702
|
+
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."),
|
|
4492
4703
|
on_conflict: z.enum([
|
|
4493
4704
|
"error",
|
|
4494
4705
|
"ignore",
|
|
@@ -4496,15 +4707,19 @@ function registerTools$12(server, apiClient) {
|
|
|
4496
4707
|
]).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."),
|
|
4497
4708
|
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\"."),
|
|
4498
4709
|
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.")
|
|
4499
|
-
}, async ({ project_id, name, row, on_conflict, conflict_target, return_minimal }, { pat }) => {
|
|
4710
|
+
}, async ({ project_id, name, row, as_system, on_conflict, conflict_target, return_minimal }, { pat }) => {
|
|
4500
4711
|
const query = {};
|
|
4501
4712
|
if (on_conflict !== void 0) query.on_conflict = on_conflict;
|
|
4502
4713
|
if (conflict_target !== void 0) query.conflict_target = conflict_target.join(",");
|
|
4503
4714
|
if (return_minimal) query.return = "minimal";
|
|
4715
|
+
const requestBody = as_system ? {
|
|
4716
|
+
...row,
|
|
4717
|
+
as_system: true
|
|
4718
|
+
} : row;
|
|
4504
4719
|
const result = await adminFetch(apiClient, {
|
|
4505
4720
|
method: "POST",
|
|
4506
4721
|
path: `/projects/${project_id}/collections/${encodeURIComponent(name)}/rows`,
|
|
4507
|
-
body:
|
|
4722
|
+
body: requestBody,
|
|
4508
4723
|
query: Object.keys(query).length > 0 ? query : void 0,
|
|
4509
4724
|
bearer: pat
|
|
4510
4725
|
});
|
|
@@ -4606,6 +4821,96 @@ function registerTools$12(server, apiClient) {
|
|
|
4606
4821
|
bearer: pat
|
|
4607
4822
|
}));
|
|
4608
4823
|
});
|
|
4824
|
+
registerTool(server, apiClient, "amba_admin_update_row", [
|
|
4825
|
+
"Update a single row by id from the developer/agent side — BYPASSES the per-user row scoping the client tools enforce, so it can edit ANY row regardless of which app_user owns it.",
|
|
4826
|
+
"Pass `set` (a column→value map of the fields to write). Server-managed columns (id, created_at, updated_at, deleted_at) are rejected with 400 PROTECTED_COLUMN.",
|
|
4827
|
+
"Compare-and-set (optimistic concurrency): pass `expected` (a column→expected-value map) and the update applies ONLY if the row currently still matches those values — otherwise 409 PRECONDITION_FAILED and nothing is written. Null-safe, so `expected: {status: null}` matches a NULL column. Use this for safe read-modify-write (read the row, then update guarded by the values you read) without a transaction.",
|
|
4828
|
+
"For updating many rows by a filter instead of one id, use amba_admin_bulk_update.",
|
|
4829
|
+
"Authenticates as the developer/agent — pass `pat` or send as inbound Bearer.",
|
|
4830
|
+
"Errors: 400 PROTECTED_COLUMN / INVALID_COLUMN / INVALID_BODY, 404 NOT_FOUND (no such row id), 409 PRECONDITION_FAILED (expected mismatch), 404 COLLECTION_NOT_FOUND."
|
|
4831
|
+
].join(" "), {
|
|
4832
|
+
project_id: z.string().describe("The project ID."),
|
|
4833
|
+
name: z.string().describe("Collection name."),
|
|
4834
|
+
id: z.string().describe("Row UUID to update."),
|
|
4835
|
+
set: setSchema.describe("Column-value map to write. Server-managed columns are rejected."),
|
|
4836
|
+
expected: z.record(z.unknown()).optional().describe("Compare-and-set precondition: column→expected-value map. The update applies only if the row currently matches every entry; otherwise 409 PRECONDITION_FAILED and no write occurs. Null-safe (an entry of null matches a NULL column).")
|
|
4837
|
+
}, async ({ project_id, name, id, set, expected }, { pat }) => {
|
|
4838
|
+
return passthroughResult(await adminFetch(apiClient, {
|
|
4839
|
+
method: "PATCH",
|
|
4840
|
+
path: `/projects/${project_id}/collections/${encodeURIComponent(name)}/rows/${encodeURIComponent(id)}`,
|
|
4841
|
+
body: expected !== void 0 ? {
|
|
4842
|
+
set,
|
|
4843
|
+
expected
|
|
4844
|
+
} : { set },
|
|
4845
|
+
bearer: pat
|
|
4846
|
+
}));
|
|
4847
|
+
});
|
|
4848
|
+
registerTool(server, apiClient, "amba_admin_delete_row", [
|
|
4849
|
+
"Delete a single row by id from the developer/agent side — BYPASSES the per-user row scoping the client tools enforce, so it can delete ANY row regardless of which app_user owns it.",
|
|
4850
|
+
"Soft-delete by default (sets deleted_at = NOW() so the row stays recoverable and still appears in admin_list_rows with include_deleted=true). Pass hard=true for a permanent, irreversible DELETE that removes the row entirely.",
|
|
4851
|
+
"Deleting an already-soft-deleted row returns `already_deleted: true` (200), not an error.",
|
|
4852
|
+
"For deleting many rows by a filter instead of one id, use amba_admin_bulk_delete.",
|
|
4853
|
+
"Authenticates as the developer/agent — pass `pat` or send as inbound Bearer.",
|
|
4854
|
+
"Errors: 404 NOT_FOUND (no such row id), 404 COLLECTION_NOT_FOUND, 400 INVALID_UUID."
|
|
4855
|
+
].join(" "), {
|
|
4856
|
+
project_id: z.string().describe("The project ID."),
|
|
4857
|
+
name: z.string().describe("Collection name."),
|
|
4858
|
+
id: z.string().describe("Row UUID to delete."),
|
|
4859
|
+
hard: z.boolean().optional().describe("false (default) soft-deletes (sets deleted_at so the row stays recoverable); true permanently removes the row (irreversible).")
|
|
4860
|
+
}, async ({ project_id, name, id, hard }, { pat }) => {
|
|
4861
|
+
return passthroughResult(await adminFetch(apiClient, {
|
|
4862
|
+
method: "DELETE",
|
|
4863
|
+
path: `/projects/${project_id}/collections/${encodeURIComponent(name)}/rows/${encodeURIComponent(id)}`,
|
|
4864
|
+
query: hard === true ? { hard: "true" } : void 0,
|
|
4865
|
+
bearer: pat
|
|
4866
|
+
}));
|
|
4867
|
+
});
|
|
4868
|
+
registerTool(server, apiClient, "amba_admin_bulk_update", [
|
|
4869
|
+
"Update EVERY row matching a `where` filter in one statement, from the developer/agent side — BYPASSES the per-user row scoping the client tools enforce (updates rows across all app_users).",
|
|
4870
|
+
"`where` is REQUIRED — a bulk update with no filter (which would touch the entire collection) is rejected with 400 WHERE_REQUIRED. To update a single row by id, use amba_admin_update_row instead.",
|
|
4871
|
+
"`set` is the column→value map applied to every matched row. Server-managed columns are rejected with 400 PROTECTED_COLUMN.",
|
|
4872
|
+
"Authenticates as the developer/agent — pass `pat` or send as inbound Bearer.",
|
|
4873
|
+
"Returns `{data: {updated: <n>, ids: [...]}}`. Errors: 400 WHERE_REQUIRED / PROTECTED_COLUMN / INVALID_COLUMN, 404 COLLECTION_NOT_FOUND."
|
|
4874
|
+
].join(" "), {
|
|
4875
|
+
project_id: z.string().describe("The project ID."),
|
|
4876
|
+
name: z.string().describe("Collection name."),
|
|
4877
|
+
set: setSchema.describe("Column-value map applied to every matched row. Required."),
|
|
4878
|
+
where: adminFindWhereSchema.describe("Server WhereClause selecting which rows to update. REQUIRED — there is no \"update all\" shortcut; pass a filter that matches the rows you intend to change.")
|
|
4879
|
+
}, async ({ project_id, name, set, where }, { pat }) => {
|
|
4880
|
+
return passthroughResult(await adminFetch(apiClient, {
|
|
4881
|
+
method: "PATCH",
|
|
4882
|
+
path: `/projects/${project_id}/collections/${encodeURIComponent(name)}/rows`,
|
|
4883
|
+
body: {
|
|
4884
|
+
set,
|
|
4885
|
+
where
|
|
4886
|
+
},
|
|
4887
|
+
bearer: pat
|
|
4888
|
+
}));
|
|
4889
|
+
});
|
|
4890
|
+
registerTool(server, apiClient, "amba_admin_bulk_delete", [
|
|
4891
|
+
"Delete EVERY row matching a `where` filter in one statement, from the developer/agent side — BYPASSES the per-user row scoping the client tools enforce (deletes rows across all app_users).",
|
|
4892
|
+
"`where` is REQUIRED — a bulk delete with no filter is rejected. To delete a single row by id, use amba_admin_delete_row.",
|
|
4893
|
+
"Foot-gun guard: `confirm` MUST equal the exact number of rows the `where` filter currently matches. Preview that count first with amba_admin_list_rows (or the count endpoint), then pass it as `confirm`. If the live count differs (e.g. a concurrent write changed it), the delete aborts with 400 CONFIRMATION_MISMATCH and nothing is removed.",
|
|
4894
|
+
"Soft-delete by default (sets deleted_at). Pass hard=true for a permanent, irreversible DELETE of every matched row.",
|
|
4895
|
+
"Authenticates as the developer/agent — pass `pat` or send as inbound Bearer.",
|
|
4896
|
+
"Returns `{data: {deleted: <n>, ids: [...], mode}}`. Errors: 400 WHERE_REQUIRED / CONFIRMATION_REQUIRED / CONFIRMATION_MISMATCH, 404 COLLECTION_NOT_FOUND."
|
|
4897
|
+
].join(" "), {
|
|
4898
|
+
project_id: z.string().describe("The project ID."),
|
|
4899
|
+
name: z.string().describe("Collection name."),
|
|
4900
|
+
where: adminFindWhereSchema.describe("Server WhereClause selecting which rows to delete. REQUIRED."),
|
|
4901
|
+
confirm: z.number().int().nonnegative().describe("The exact number of rows the `where` filter currently matches (preview via amba_admin_list_rows). The delete proceeds only if the live count equals this; otherwise 400 CONFIRMATION_MISMATCH and nothing is deleted."),
|
|
4902
|
+
hard: z.boolean().optional().describe("false (default) soft-deletes (sets deleted_at, recoverable); true permanently removes every matched row (irreversible).")
|
|
4903
|
+
}, async ({ project_id, name, where, confirm, hard }, { pat }) => {
|
|
4904
|
+
const query = { confirm: String(confirm) };
|
|
4905
|
+
if (hard === true) query.hard = "true";
|
|
4906
|
+
return passthroughResult(await adminFetch(apiClient, {
|
|
4907
|
+
method: "DELETE",
|
|
4908
|
+
path: `/projects/${project_id}/collections/${encodeURIComponent(name)}/rows`,
|
|
4909
|
+
body: { where },
|
|
4910
|
+
query,
|
|
4911
|
+
bearer: pat
|
|
4912
|
+
}));
|
|
4913
|
+
});
|
|
4609
4914
|
server.tool("amba_client_insert_row", [
|
|
4610
4915
|
"Insert a row into a collection as the signed-in app_user. Auto-RLS injects `user_id = <session app_user>` server-side; the body cannot override it.",
|
|
4611
4916
|
"Atomic upsert: pass on_conflict=\"ignore\" (return existing row, HTTP 200) or \"update\" (merge) plus conflict_target (unique-index columns, may include user_id). Removes read-then-write races (duplicate profiles, split rooms, etc.).",
|
|
@@ -4863,6 +5168,47 @@ function registerTools$12(server, apiClient) {
|
|
|
4863
5168
|
...session_token !== void 0 && { sessionToken: session_token }
|
|
4864
5169
|
}));
|
|
4865
5170
|
});
|
|
5171
|
+
server.tool("amba_client_aggregate_rows", [
|
|
5172
|
+
"Run a group-by aggregation over a collection as the signed-in app_user — server-side, no rows shipped to the client.",
|
|
5173
|
+
"Supports count / sum / avg / min / max, optionally grouped by one or more columns. count without a column = COUNT(*).",
|
|
5174
|
+
"Auto-RLS — the aggregate covers only rows the app_user owns (plus shared/unowned rows) AND'd with the live deleted_at filter; pass include_deleted=true to include soft-deleted rows.",
|
|
5175
|
+
"Roll a leaderboard, a spend total, a per-category count, or a daily streak summary in one round-trip instead of paging every row. Pairs naturally with amba_client_find_rows for the drill-down.",
|
|
5176
|
+
"Authenticates as the end-user — pass `api_key` + `session_token`.",
|
|
5177
|
+
"Returns `{data: [...]}` — one row per group (or a single row when group_by is omitted), each aggregation under its alias."
|
|
5178
|
+
].join(" "), {
|
|
5179
|
+
name: z.string().describe("Collection name."),
|
|
5180
|
+
select: z.array(z.object({
|
|
5181
|
+
fn: z.enum([
|
|
5182
|
+
"count",
|
|
5183
|
+
"sum",
|
|
5184
|
+
"avg",
|
|
5185
|
+
"min",
|
|
5186
|
+
"max"
|
|
5187
|
+
]),
|
|
5188
|
+
column: z.string().optional().describe("Column to aggregate. Required for sum/avg/min/max; omit for COUNT(*)."),
|
|
5189
|
+
as: z.string().optional().describe("Output alias (defaults to fn or fn_column).")
|
|
5190
|
+
})).min(1).describe("One or more aggregations to compute."),
|
|
5191
|
+
group_by: z.union([z.string(), z.array(z.string())]).optional().describe("Column(s) to group by. Omit for a whole-collection aggregate."),
|
|
5192
|
+
filter: sdkFilterSchema.optional(),
|
|
5193
|
+
where: adminFindWhereSchema.optional(),
|
|
5194
|
+
include_deleted: z.boolean().optional().describe("Include soft-deleted rows. Default FALSE."),
|
|
5195
|
+
api_key: z.string().describe("Project client X-Api-Key."),
|
|
5196
|
+
session_token: z.string().optional().describe("App_user session Bearer. Required by the route.")
|
|
5197
|
+
}, async ({ name, select, group_by, filter, where, include_deleted, api_key, session_token }) => {
|
|
5198
|
+
if (!api_key) return MISSING_API_KEY_ERROR;
|
|
5199
|
+
const body = { select };
|
|
5200
|
+
if (group_by !== void 0) body.group_by = group_by;
|
|
5201
|
+
if (filter !== void 0) body.filter = filter;
|
|
5202
|
+
if (where !== void 0) body.where = where;
|
|
5203
|
+
if (include_deleted !== void 0) body.include_deleted = include_deleted;
|
|
5204
|
+
return passthroughResult(await clientFetch(apiClient, {
|
|
5205
|
+
method: "POST",
|
|
5206
|
+
path: `/collections/${encodeURIComponent(name)}/aggregate`,
|
|
5207
|
+
body,
|
|
5208
|
+
apiKey: api_key,
|
|
5209
|
+
...session_token !== void 0 && { sessionToken: session_token }
|
|
5210
|
+
}));
|
|
5211
|
+
});
|
|
4866
5212
|
}
|
|
4867
5213
|
//#endregion
|
|
4868
5214
|
//#region src/tools/_pat.ts
|
|
@@ -4978,7 +5324,7 @@ function jsonResult(payload) {
|
|
|
4978
5324
|
* of a generic upstream 400.
|
|
4979
5325
|
*/
|
|
4980
5326
|
const MAX_BUNDLE_BYTES = 10 * 1024 * 1024;
|
|
4981
|
-
function registerTools$
|
|
5327
|
+
function registerTools$12(server, apiClient) {
|
|
4982
5328
|
registerTool(server, apiClient, "amba_functions_deploy", [
|
|
4983
5329
|
"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`.",
|
|
4984
5330
|
"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).",
|
|
@@ -5106,7 +5452,7 @@ function registerTools$11(server, apiClient) {
|
|
|
5106
5452
|
}
|
|
5107
5453
|
//#endregion
|
|
5108
5454
|
//#region src/tools/sites.ts
|
|
5109
|
-
function registerTools$
|
|
5455
|
+
function registerTools$11(server, apiClient) {
|
|
5110
5456
|
registerTool(server, apiClient, "amba_sites_deploy", [
|
|
5111
5457
|
"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.",
|
|
5112
5458
|
"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`.",
|
|
@@ -5198,7 +5544,7 @@ function registerTools$10(server, apiClient) {
|
|
|
5198
5544
|
}
|
|
5199
5545
|
//#endregion
|
|
5200
5546
|
//#region src/tools/domains.ts
|
|
5201
|
-
function registerTools$
|
|
5547
|
+
function registerTools$10(server, apiClient) {
|
|
5202
5548
|
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(" "), {
|
|
5203
5549
|
project_id: z.string().describe("The Amba project ID."),
|
|
5204
5550
|
query: z.string().describe("A keyword (e.g. \"getunbury\") or a full domain (e.g. \"unbury.com\")."),
|
|
@@ -5253,6 +5599,39 @@ function registerTools$9(server, apiClient) {
|
|
|
5253
5599
|
});
|
|
5254
5600
|
}
|
|
5255
5601
|
//#endregion
|
|
5602
|
+
//#region src/tools/operations.ts
|
|
5603
|
+
function registerTools$9(server, apiClient) {
|
|
5604
|
+
registerTool(server, apiClient, "amba_operations_get", [
|
|
5605
|
+
"Poll an async operation by its operation_id to see whether it has finished.",
|
|
5606
|
+
"Returns the operation `status` (pending | running | succeeded | failed); on `failed`, `failed_reason` says why, and on `succeeded`, `result` holds the outcome.",
|
|
5607
|
+
"Use this after a tool returns an `operation_id` (e.g. amba_domains_purchase): re-call until status is succeeded or failed instead of re-running the action."
|
|
5608
|
+
].join(" "), {
|
|
5609
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5610
|
+
operation_id: z.string().describe("The operation handle id returned by the tool that started the work.")
|
|
5611
|
+
}, async ({ project_id, operation_id }, { pat }) => {
|
|
5612
|
+
return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/operations/${encodeURIComponent(operation_id)}`));
|
|
5613
|
+
});
|
|
5614
|
+
registerTool(server, apiClient, "amba_operations_list", ["List async operations for a project, newest first. Optionally filter by `kind` (e.g. \"domain_purchase\") and/or `status` (pending | running | succeeded | failed).", "Read-only — useful to find an in-flight operation or review recent ones."].join(" "), {
|
|
5615
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5616
|
+
kind: z.string().optional().describe("Filter to one operation kind, e.g. \"domain_purchase\"."),
|
|
5617
|
+
status: z.enum([
|
|
5618
|
+
"pending",
|
|
5619
|
+
"running",
|
|
5620
|
+
"succeeded",
|
|
5621
|
+
"failed"
|
|
5622
|
+
]).optional().describe("Filter to one lifecycle status."),
|
|
5623
|
+
limit: z.number().int().min(1).max(200).optional().describe("Page size (1-200, default 50)."),
|
|
5624
|
+
offset: z.number().int().min(0).optional().describe("Skip rows (default 0).")
|
|
5625
|
+
}, async ({ project_id, kind, status, limit, offset }, { pat }) => {
|
|
5626
|
+
const query = {};
|
|
5627
|
+
if (kind !== void 0) query.kind = kind;
|
|
5628
|
+
if (status !== void 0) query.status = status;
|
|
5629
|
+
if (limit !== void 0) query.limit = String(limit);
|
|
5630
|
+
if (offset !== void 0) query.offset = String(offset);
|
|
5631
|
+
return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/operations`, { query }));
|
|
5632
|
+
});
|
|
5633
|
+
}
|
|
5634
|
+
//#endregion
|
|
5256
5635
|
//#region src/tools/secrets.ts
|
|
5257
5636
|
function registerTools$8(server, apiClient) {
|
|
5258
5637
|
registerTool(server, apiClient, "amba_secrets_set", [
|
|
@@ -7846,7 +8225,9 @@ const TOOL_CATEGORY = {
|
|
|
7846
8225
|
amba_users_delete: "identity",
|
|
7847
8226
|
amba_users_export: "identity",
|
|
7848
8227
|
amba_users_bulk_update: "identity",
|
|
8228
|
+
amba_users_create_cohort: "identity",
|
|
7849
8229
|
amba_users_reset_sandbox: "identity",
|
|
8230
|
+
amba_users_reset_user: "identity",
|
|
7850
8231
|
amba_users_events_export: "identity",
|
|
7851
8232
|
amba_users_list_events: "identity",
|
|
7852
8233
|
amba_api_keys_create: "identity",
|
|
@@ -7993,6 +8374,7 @@ const TOOL_CATEGORY = {
|
|
|
7993
8374
|
amba_currencies_spend: "economy",
|
|
7994
8375
|
amba_currencies_get_transactions: "economy",
|
|
7995
8376
|
amba_get_currency_transactions: "economy",
|
|
8377
|
+
amba_currencies_get_user_balance: "economy",
|
|
7996
8378
|
amba_currency_grant_rules_create: "economy",
|
|
7997
8379
|
amba_currency_grant_rules_list: "economy",
|
|
7998
8380
|
amba_currency_grant_rules_delete: "economy",
|
|
@@ -8026,6 +8408,7 @@ const TOOL_CATEGORY = {
|
|
|
8026
8408
|
amba_stores_delete_listing: "economy",
|
|
8027
8409
|
amba_inventory_grant_item: "economy",
|
|
8028
8410
|
amba_inventory_revoke_item: "economy",
|
|
8411
|
+
amba_entitlements_grant: "economy",
|
|
8029
8412
|
amba_grant_item: "economy",
|
|
8030
8413
|
amba_users_get_inventory: "economy",
|
|
8031
8414
|
amba_get_user_inventory: "economy",
|
|
@@ -8086,6 +8469,7 @@ const TOOL_CATEGORY = {
|
|
|
8086
8469
|
amba_moderation_list_trust: "social",
|
|
8087
8470
|
amba_events_list: "analytics",
|
|
8088
8471
|
amba_events_count: "analytics",
|
|
8472
|
+
amba_events_track: "analytics",
|
|
8089
8473
|
amba_analytics_get: "analytics",
|
|
8090
8474
|
amba_get_analytics: "analytics",
|
|
8091
8475
|
amba_sessions_list: "analytics",
|
|
@@ -8128,7 +8512,12 @@ const TOOL_CATEGORY = {
|
|
|
8128
8512
|
amba_alter_collection: "infrastructure",
|
|
8129
8513
|
amba_collections_delete: "infrastructure",
|
|
8130
8514
|
amba_delete_collection: "infrastructure",
|
|
8515
|
+
amba_collections_reset_data: "infrastructure",
|
|
8131
8516
|
amba_admin_insert_row: "infrastructure",
|
|
8517
|
+
amba_admin_update_row: "infrastructure",
|
|
8518
|
+
amba_admin_delete_row: "infrastructure",
|
|
8519
|
+
amba_admin_bulk_update: "infrastructure",
|
|
8520
|
+
amba_admin_bulk_delete: "infrastructure",
|
|
8132
8521
|
amba_admin_list_rows: "infrastructure",
|
|
8133
8522
|
amba_admin_aggregate_rows: "infrastructure",
|
|
8134
8523
|
amba_client_insert_row: "infrastructure",
|
|
@@ -8137,6 +8526,7 @@ const TOOL_CATEGORY = {
|
|
|
8137
8526
|
amba_client_count_rows: "infrastructure",
|
|
8138
8527
|
amba_client_find_rows: "infrastructure",
|
|
8139
8528
|
amba_client_find_nearest_rows: "infrastructure",
|
|
8529
|
+
amba_client_aggregate_rows: "infrastructure",
|
|
8140
8530
|
amba_client_update_row: "infrastructure",
|
|
8141
8531
|
amba_client_delete_row: "infrastructure",
|
|
8142
8532
|
amba_functions_list: "infrastructure",
|
|
@@ -8175,6 +8565,8 @@ const TOOL_CATEGORY = {
|
|
|
8175
8565
|
amba_domains_check: "infrastructure",
|
|
8176
8566
|
amba_domains_purchase: "infrastructure",
|
|
8177
8567
|
amba_domains_list: "infrastructure",
|
|
8568
|
+
amba_operations_get: "infrastructure",
|
|
8569
|
+
amba_operations_list: "infrastructure",
|
|
8178
8570
|
amba_media_upload: "infrastructure",
|
|
8179
8571
|
amba_upload_media: "infrastructure",
|
|
8180
8572
|
amba_media_list: "infrastructure",
|
|
@@ -8240,6 +8632,10 @@ const TOOL_CATEGORY = {
|
|
|
8240
8632
|
amba_webhooks_deliveries_list: "infrastructure",
|
|
8241
8633
|
amba_webhooks_deliveries_get: "infrastructure",
|
|
8242
8634
|
amba_webhooks_deliveries_replay: "infrastructure",
|
|
8635
|
+
amba_events_catalog: "infrastructure",
|
|
8636
|
+
amba_control_webhooks_create: "infrastructure",
|
|
8637
|
+
amba_control_webhooks_list: "infrastructure",
|
|
8638
|
+
amba_control_webhooks_delete: "infrastructure",
|
|
8243
8639
|
amba_email_templates_create: "engagement",
|
|
8244
8640
|
amba_email_templates_list: "engagement",
|
|
8245
8641
|
amba_email_templates_get: "engagement",
|
|
@@ -8275,7 +8671,9 @@ function getToolCategory(toolName) {
|
|
|
8275
8671
|
* called against it once.
|
|
8276
8672
|
*/
|
|
8277
8673
|
function registerAllTools(server, apiClient) {
|
|
8278
|
-
registerTools$
|
|
8674
|
+
registerTools$14(server, apiClient);
|
|
8675
|
+
registerTools$35(server, apiClient);
|
|
8676
|
+
registerTools$34(server, apiClient);
|
|
8279
8677
|
registerTools$33(server, apiClient);
|
|
8280
8678
|
registerTools$32(server, apiClient);
|
|
8281
8679
|
registerTools$31(server, apiClient);
|
|
@@ -8295,7 +8693,7 @@ function registerAllTools(server, apiClient) {
|
|
|
8295
8693
|
registerTools$17(server, apiClient);
|
|
8296
8694
|
registerTools$16(server, apiClient);
|
|
8297
8695
|
registerTools$15(server, apiClient);
|
|
8298
|
-
registerTools$
|
|
8696
|
+
registerTools$13(server, apiClient);
|
|
8299
8697
|
registerTools$12(server, apiClient);
|
|
8300
8698
|
registerTools$11(server, apiClient);
|
|
8301
8699
|
registerTools$10(server, apiClient);
|
|
@@ -80,13 +80,6 @@ export type ToolVerbClass = 'read' | 'destructive' | 'write';
|
|
|
80
80
|
* `amba_` prefix. Exported for the test suite.
|
|
81
81
|
*/
|
|
82
82
|
export declare function toolNameTokens(name: string): string[];
|
|
83
|
-
/**
|
|
84
|
-
* Classify a tool name into its behavioural class by PRECEDENCE
|
|
85
|
-
* (destructive > read > write). Returns `null` if no token matches any
|
|
86
|
-
* known verb — that should never happen for a real Amba tool and the
|
|
87
|
-
* test suite asserts it doesn't, so a `null` at runtime means a new tool
|
|
88
|
-
* used an unrecognised verb and needs a verb-set entry here.
|
|
89
|
-
*/
|
|
90
83
|
export declare function classifyToolVerb(name: string): ToolVerbClass | null;
|
|
91
84
|
/**
|
|
92
85
|
* Derive a human-readable Title-Case title from the tool name. The
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event catalog + control-plane webhook tools — discover what a project can
|
|
3
|
+
* emit, and subscribe webhooks to PLATFORM lifecycle events.
|
|
4
|
+
*
|
|
5
|
+
* amba_events_catalog GET /webhooks/catalog
|
|
6
|
+
* amba_control_webhooks_create POST /control-webhooks
|
|
7
|
+
* amba_control_webhooks_list GET /control-webhooks
|
|
8
|
+
* amba_control_webhooks_delete DELETE /control-webhooks/:id
|
|
9
|
+
*
|
|
10
|
+
* Tenant/app-event subscription tools live in `webhooks.ts` (those subscribe to
|
|
11
|
+
* APP events like `economy.currency.spent`). These cover CONTROL-plane events
|
|
12
|
+
* about the project itself — provisioning, deploys, domains, billing — which is
|
|
13
|
+
* what an autonomous provisioning agent wants to react to.
|
|
14
|
+
*
|
|
15
|
+
* Descriptions stay provider-neutral and nudge toward the production pattern
|
|
16
|
+
* (discover the catalog → wire the lifecycle events) without overclaiming.
|
|
17
|
+
*/
|
|
18
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
19
|
+
import type { ApiClient } from '../api-client.js';
|
|
20
|
+
export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* deploy a bundled script, list deployments, describe one, delete a
|
|
6
6
|
* function (cascade), and full schedule lifecycle (create, pause,
|
|
7
7
|
* resume, trigger now). Logs read is the last operation in the set —
|
|
8
|
-
*
|
|
8
|
+
* Function logs for a single function over a bounded time
|
|
9
9
|
* range.
|
|
10
10
|
*
|
|
11
11
|
* Wire details:
|
package/dist/tools/leagues.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { ApiClient } from '../api-client.js';
|
|
|
6
6
|
* cohorts that the rollover workflow reshuffles. These tools provision and
|
|
7
7
|
* inspect leagues; the admin routes are mounted at
|
|
8
8
|
* `/v1/admin/projects/:projectId/leagues`. Members are assigned by the weekly
|
|
9
|
-
* rollover (the
|
|
9
|
+
* rollover (the scheduled weekly rollover) and score live off `xp_awarded`
|
|
10
10
|
* engagement events.
|
|
11
11
|
*/
|
|
12
12
|
export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation-handle MCP tools — poll an async operation to done | failed.
|
|
3
|
+
*
|
|
4
|
+
* Several Amba actions kick off work that may take a moment (e.g. buying a
|
|
5
|
+
* domain). Those endpoints return an `operation_id`; pass it to
|
|
6
|
+
* `amba_operations_get` to read the current status without re-triggering the
|
|
7
|
+
* action. Poll until `status` is `succeeded` or `failed` — on `failed`,
|
|
8
|
+
* `failed_reason` explains why.
|
|
9
|
+
*
|
|
10
|
+
* Tools:
|
|
11
|
+
* - `amba_operations_get` — read one operation's status (poll to done).
|
|
12
|
+
* - `amba_operations_list` — list operations, optionally filtered by kind
|
|
13
|
+
* and/or status.
|
|
14
|
+
*
|
|
15
|
+
* Both are READ-ONLY — they never start or change an operation, only report
|
|
16
|
+
* it. The producing tool (e.g. `amba_domains_purchase`) is what creates the
|
|
17
|
+
* operation and returns its id.
|
|
18
|
+
*
|
|
19
|
+
* Authentication: every tool accepts an optional inline `pat` via the
|
|
20
|
+
* `registerTool` helper (see `../lib/with-pat.ts`).
|
|
21
|
+
*/
|
|
22
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
23
|
+
import type { ApiClient } from '../api-client.js';
|
|
24
|
+
export declare function registerTools(server: McpServer, apiClient: ApiClient): void;
|