@layers/amba-mcp 4.0.10 → 4.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api-client.d.ts +53 -1
- package/dist/expo-build-prompt.js +4 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +464 -157
- package/dist/lib/tool-result.d.ts +30 -0
- package/dist/lib/with-pat.d.ts +11 -1
- package/dist/resources/amba-setup.d.ts +1 -1
- package/dist/tools/billing.d.ts +5 -1
- package/dist/tools/developer-claim.d.ts +15 -0
- package/dist/tools/secrets.d.ts +2 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -48,24 +48,57 @@ function isTokenExpired(credentials) {
|
|
|
48
48
|
var AmbaApiError = class extends Error {
|
|
49
49
|
status;
|
|
50
50
|
code;
|
|
51
|
-
|
|
51
|
+
/** The API's own `error.message`, without the `API error <status>:` prefix. */
|
|
52
|
+
apiMessage;
|
|
53
|
+
/**
|
|
54
|
+
* The API's `error.details`, when the response carried one: for example a
|
|
55
|
+
* 402 `FREE_TIER_LIMIT_REACHED` names the limit, the reset date, the
|
|
56
|
+
* billing URL and the one-call upgrade there. Tool results pass it to the
|
|
57
|
+
* agent (`lib/tool-result.ts` `apiErrorResult`).
|
|
58
|
+
*/
|
|
59
|
+
details;
|
|
60
|
+
constructor(status, code, message, details) {
|
|
52
61
|
super(`API error ${status}: ${message}`);
|
|
53
62
|
this.name = "AmbaApiError";
|
|
54
63
|
this.status = status;
|
|
55
64
|
this.code = code;
|
|
65
|
+
this.apiMessage = message;
|
|
66
|
+
this.details = details;
|
|
56
67
|
}
|
|
57
68
|
};
|
|
69
|
+
/**
|
|
70
|
+
* Read a non-2xx response's `{ error: { code, message, details } }` envelope
|
|
71
|
+
* into an `AmbaApiError`. A body that is not that JSON envelope falls back to
|
|
72
|
+
* the status text.
|
|
73
|
+
*/
|
|
74
|
+
async function ambaApiErrorFromResponse(res) {
|
|
75
|
+
let errorMessage = res.statusText;
|
|
76
|
+
let errorCode;
|
|
77
|
+
let details;
|
|
78
|
+
try {
|
|
79
|
+
const err = (await res.json())?.error;
|
|
80
|
+
if (typeof err?.message === "string") errorMessage = err.message;
|
|
81
|
+
if (typeof err?.code === "string") errorCode = err.code;
|
|
82
|
+
details = err?.details;
|
|
83
|
+
} catch {}
|
|
84
|
+
return new AmbaApiError(res.status, errorCode, errorMessage, details);
|
|
85
|
+
}
|
|
58
86
|
const BASE_URL = "https://api.amba.dev/v1/admin";
|
|
59
87
|
var ApiClient = class ApiClient {
|
|
88
|
+
authProxyHeaders;
|
|
60
89
|
baseUrl;
|
|
61
90
|
apiRoot;
|
|
62
91
|
tokenProvider;
|
|
92
|
+
signupToken;
|
|
63
93
|
constructor(options) {
|
|
94
|
+
this.authProxyHeaders = { ...options?.authProxyHeaders };
|
|
64
95
|
const raw = (options?.baseUrl ?? BASE_URL).replace(/\/+$/, "");
|
|
65
96
|
if (!/\/admin$/.test(raw)) throw new Error(`ApiClient baseUrl must end in /admin (got ${JSON.stringify(raw)}). Set AMBA_API_URL=https://api.amba.dev/v1/admin (or your equivalent versioned host).`);
|
|
66
97
|
this.baseUrl = raw;
|
|
67
98
|
this.apiRoot = raw.replace(/\/admin$/, "");
|
|
68
99
|
this.tokenProvider = options?.getToken;
|
|
100
|
+
const signupToken = options?.signupToken?.trim();
|
|
101
|
+
if (signupToken) this.signupToken = signupToken;
|
|
69
102
|
}
|
|
70
103
|
/**
|
|
71
104
|
* Return a sibling `ApiClient` bound to a different Bearer token.
|
|
@@ -81,10 +114,16 @@ var ApiClient = class ApiClient {
|
|
|
81
114
|
* routing stays identical — only the token provider differs.
|
|
82
115
|
*/
|
|
83
116
|
withToken(token) {
|
|
84
|
-
|
|
117
|
+
const opts = {
|
|
85
118
|
baseUrl: this.baseUrl,
|
|
86
119
|
getToken: async () => token
|
|
87
|
-
}
|
|
120
|
+
};
|
|
121
|
+
if (this.signupToken !== void 0) opts.signupToken = this.signupToken;
|
|
122
|
+
return new ApiClient(opts);
|
|
123
|
+
}
|
|
124
|
+
/** The signup token to forward on `/auth/developer/signup`, if any. */
|
|
125
|
+
getSignupToken() {
|
|
126
|
+
return this.signupToken;
|
|
88
127
|
}
|
|
89
128
|
/**
|
|
90
129
|
* Returns the configured admin-prefixed base URL (e.g.
|
|
@@ -105,6 +144,15 @@ var ApiClient = class ApiClient {
|
|
|
105
144
|
return this.apiRoot;
|
|
106
145
|
}
|
|
107
146
|
/**
|
|
147
|
+
* Headers to add to public developer-auth calls (see
|
|
148
|
+
* `ApiClientOptions.authProxyHeaders`). Those calls always run on the
|
|
149
|
+
* client the tools were registered with, so a `withToken` sibling does not
|
|
150
|
+
* need to carry them.
|
|
151
|
+
*/
|
|
152
|
+
getAuthProxyHeaders() {
|
|
153
|
+
return { ...this.authProxyHeaders };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
108
156
|
* Resolve the current developer Bearer token, going through the configured
|
|
109
157
|
* tokenProvider (or `~/.amba/credentials.json` for the CLI fallback).
|
|
110
158
|
*
|
|
@@ -137,6 +185,23 @@ var ApiClient = class ApiClient {
|
|
|
137
185
|
if (isTokenExpired(credentials)) throw new Error("Developer access token has expired. Run \"amba login\" to re-authenticate.");
|
|
138
186
|
return credentials.access_token;
|
|
139
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Account notices (`X-Amba-Notice`, e.g. the unclaimed-sandbox claim
|
|
190
|
+
* instruction) seen on responses since the last `takeNotices()`. The
|
|
191
|
+
* `registerTool` wrapper drains them into the tool result so the agent
|
|
192
|
+
* reads them alongside the data it asked for.
|
|
193
|
+
*/
|
|
194
|
+
notices = /* @__PURE__ */ new Set();
|
|
195
|
+
/** Return and clear the notices collected since the last call. */
|
|
196
|
+
takeNotices() {
|
|
197
|
+
const out = [...this.notices];
|
|
198
|
+
this.notices.clear();
|
|
199
|
+
return out;
|
|
200
|
+
}
|
|
201
|
+
captureNotice(res) {
|
|
202
|
+
const notice = res.headers?.get?.("x-amba-notice");
|
|
203
|
+
if (notice) this.notices.add(notice);
|
|
204
|
+
}
|
|
140
205
|
async request(method, path, body, query) {
|
|
141
206
|
const token = await this.getToken();
|
|
142
207
|
let url = `${this.baseUrl}${path}`;
|
|
@@ -154,16 +219,8 @@ var ApiClient = class ApiClient {
|
|
|
154
219
|
headers,
|
|
155
220
|
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
156
221
|
});
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
let errorCode;
|
|
160
|
-
try {
|
|
161
|
-
const errorBody = await res.json();
|
|
162
|
-
errorMessage = errorBody?.error?.message ?? res.statusText;
|
|
163
|
-
errorCode = errorBody?.error?.code;
|
|
164
|
-
} catch {}
|
|
165
|
-
throw new AmbaApiError(res.status, errorCode, errorMessage);
|
|
166
|
-
}
|
|
222
|
+
this.captureNotice(res);
|
|
223
|
+
if (!res.ok) throw await ambaApiErrorFromResponse(res);
|
|
167
224
|
if (res.status === 204) return;
|
|
168
225
|
return await res.json();
|
|
169
226
|
}
|
|
@@ -206,16 +263,8 @@ var ApiClient = class ApiClient {
|
|
|
206
263
|
Accept: "text/csv, application/x-ndjson, */*"
|
|
207
264
|
}
|
|
208
265
|
});
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
let errorCode;
|
|
212
|
-
try {
|
|
213
|
-
const errorBody = await res.json();
|
|
214
|
-
errorMessage = errorBody?.error?.message ?? res.statusText;
|
|
215
|
-
errorCode = errorBody?.error?.code;
|
|
216
|
-
} catch {}
|
|
217
|
-
throw new AmbaApiError(res.status, errorCode, errorMessage);
|
|
218
|
-
}
|
|
266
|
+
this.captureNotice(res);
|
|
267
|
+
if (!res.ok) throw await ambaApiErrorFromResponse(res);
|
|
219
268
|
const contentType = res.headers.get("content-type");
|
|
220
269
|
const reader = res.body?.getReader();
|
|
221
270
|
if (!reader) {
|
|
@@ -378,6 +427,7 @@ const WRITE_VERBS = new Set([
|
|
|
378
427
|
"insert",
|
|
379
428
|
"invite",
|
|
380
429
|
"register",
|
|
430
|
+
"claim",
|
|
381
431
|
"define",
|
|
382
432
|
"map",
|
|
383
433
|
"adopt",
|
|
@@ -394,7 +444,8 @@ const WRITE_VERBS = new Set([
|
|
|
394
444
|
"generate",
|
|
395
445
|
"enroll",
|
|
396
446
|
"request",
|
|
397
|
-
"clawback"
|
|
447
|
+
"clawback",
|
|
448
|
+
"upgrade"
|
|
398
449
|
]);
|
|
399
450
|
/**
|
|
400
451
|
* Split a tool name into lowercase verb-candidate tokens, dropping the
|
|
@@ -553,8 +604,174 @@ function deriveToolAnnotations(name) {
|
|
|
553
604
|
}
|
|
554
605
|
}
|
|
555
606
|
//#endregion
|
|
607
|
+
//#region src/lib/tool-result.ts
|
|
608
|
+
/**
|
|
609
|
+
* Shared MCP tool result helpers.
|
|
610
|
+
*
|
|
611
|
+
* Every tool emits the same `{ content: [{ type: 'text', text: <json> }] }`
|
|
612
|
+
* envelope. Two helpers centralize that:
|
|
613
|
+
*
|
|
614
|
+
* - [`jsonResult`] — wraps an arbitrary payload.
|
|
615
|
+
* - [`passthroughResult`] — flattens an upstream HTTP response
|
|
616
|
+
* (status + parsed body) into the same envelope. Status is written
|
|
617
|
+
* LAST so a colliding top-level `status` field in the API response
|
|
618
|
+
* cannot shadow the HTTP status — agents look at `parsed.status` to
|
|
619
|
+
* distinguish 2xx from 4xx/5xx.
|
|
620
|
+
*
|
|
621
|
+
* Lives in `src/lib/` (vs. `src/tools/_helpers.ts`) to set the same
|
|
622
|
+
* cross-cutting-helper precedent as `src/lib/with-pat.ts` (task #36).
|
|
623
|
+
* `tools/*` files stay strictly tool registrations.
|
|
624
|
+
*/
|
|
625
|
+
/**
|
|
626
|
+
* Wire-shape every MCP tool handler returns.
|
|
627
|
+
*
|
|
628
|
+
* The MCP SDK's `tool()` callback signature is structurally typed and
|
|
629
|
+
* carries an open index signature for `_meta` etc. Declaring our return
|
|
630
|
+
* type as a plain `{ content: [...] }` interface won't satisfy that
|
|
631
|
+
* structural check — so the helpers' return type is left as the actual
|
|
632
|
+
* inferred shape (no explicit interface) and consumers rely on the
|
|
633
|
+
* inference + the SDK's structural compatibility. If we ever want a
|
|
634
|
+
* named alias, write it as a type-alias over the inferred shape rather
|
|
635
|
+
* than a closed interface.
|
|
636
|
+
*/
|
|
637
|
+
/** Wrap an arbitrary payload as a JSON-text tool result. */
|
|
638
|
+
function jsonResult$1(payload) {
|
|
639
|
+
return { content: [{
|
|
640
|
+
type: "text",
|
|
641
|
+
text: JSON.stringify(payload, null, 2)
|
|
642
|
+
}] };
|
|
643
|
+
}
|
|
644
|
+
/** Preserve an HTTP response wrapper while mapping failures to MCP isError. */
|
|
645
|
+
function httpResult(result) {
|
|
646
|
+
return {
|
|
647
|
+
...jsonResult$1(result),
|
|
648
|
+
...result.status >= 400 ? { isError: true } : {}
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Flatten an upstream HTTP response into the agent-facing tool payload.
|
|
653
|
+
* Body fields are spread FIRST so a future top-level `status` key in
|
|
654
|
+
* the API response cannot shadow the HTTP `status` — agents read
|
|
655
|
+
* `parsed.status` to distinguish 2xx from 4xx/5xx.
|
|
656
|
+
*/
|
|
657
|
+
function passthroughResult(result) {
|
|
658
|
+
return {
|
|
659
|
+
...jsonResult$1({
|
|
660
|
+
...result.body !== null && typeof result.body === "object" ? result.body : {},
|
|
661
|
+
status: result.status
|
|
662
|
+
}),
|
|
663
|
+
...result.status >= 400 ? { isError: true } : {}
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Tool result for a failed API call. The first text item is the
|
|
668
|
+
* human-readable line tools have always returned (`API error 402: ...`). The
|
|
669
|
+
* second is the API's structured error as JSON,
|
|
670
|
+
* `{ "status": 402, "error": { "code", "message", "details" } }`, so an agent
|
|
671
|
+
* can branch on `error.code` and read `details` (a free-tier 402 carries the
|
|
672
|
+
* limit, the reset date, the billing URL and the one-call upgrade there).
|
|
673
|
+
*
|
|
674
|
+
* Code mode joins an error result's text items with a newline into the
|
|
675
|
+
* exception it throws in the sandbox, so the same JSON reaches that surface
|
|
676
|
+
* after the first line of the error message.
|
|
677
|
+
*/
|
|
678
|
+
function apiErrorResult(err) {
|
|
679
|
+
const error = {
|
|
680
|
+
code: err.code ?? null,
|
|
681
|
+
message: err.apiMessage
|
|
682
|
+
};
|
|
683
|
+
if (err.details !== void 0) error.details = err.details;
|
|
684
|
+
return {
|
|
685
|
+
content: [{
|
|
686
|
+
type: "text",
|
|
687
|
+
text: err.message
|
|
688
|
+
}, {
|
|
689
|
+
type: "text",
|
|
690
|
+
text: JSON.stringify({
|
|
691
|
+
status: err.status,
|
|
692
|
+
error
|
|
693
|
+
}, null, 2)
|
|
694
|
+
}],
|
|
695
|
+
isError: true
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
//#endregion
|
|
556
699
|
//#region src/lib/with-pat.ts
|
|
557
700
|
/**
|
|
701
|
+
* Attach the account notices the API sent with this call's responses
|
|
702
|
+
* (`X-Amba-Notice`, collected by `ApiClient`), so an agent sees "claim this
|
|
703
|
+
* account" next to what it asked for. A successful result gets them as extra
|
|
704
|
+
* text items. An error result gets them as a `notices` array inside its
|
|
705
|
+
* trailing JSON error, so that item stays the last one and stays parseable
|
|
706
|
+
* (code mode joins the text items into the message of the Error it throws).
|
|
707
|
+
* A result without notices is returned unchanged.
|
|
708
|
+
*/
|
|
709
|
+
function withAccountNotices(result, client) {
|
|
710
|
+
const take = client.takeNotices;
|
|
711
|
+
const notices = typeof take === "function" ? take.call(client) : [];
|
|
712
|
+
if (notices.length === 0) return result;
|
|
713
|
+
if (result.isError) {
|
|
714
|
+
const folded = foldNoticesIntoErrorJson(result, notices);
|
|
715
|
+
if (folded) return folded;
|
|
716
|
+
}
|
|
717
|
+
return {
|
|
718
|
+
...result,
|
|
719
|
+
content: [...result.content, ...notices.map((notice) => ({
|
|
720
|
+
type: "text",
|
|
721
|
+
text: `Notice from Amba: ${notice}`
|
|
722
|
+
}))]
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
/** `result` with `notices` merged into its last text item, or null when that item is not a JSON object. */
|
|
726
|
+
function foldNoticesIntoErrorJson(result, notices) {
|
|
727
|
+
const last = result.content[result.content.length - 1];
|
|
728
|
+
if (!last) return null;
|
|
729
|
+
let parsed;
|
|
730
|
+
try {
|
|
731
|
+
parsed = JSON.parse(last.text);
|
|
732
|
+
} catch {
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
736
|
+
const payload = parsed;
|
|
737
|
+
const existing = Array.isArray(payload["notices"]) ? payload["notices"] : [];
|
|
738
|
+
const merged = {
|
|
739
|
+
...payload,
|
|
740
|
+
notices: [...existing, ...notices]
|
|
741
|
+
};
|
|
742
|
+
return {
|
|
743
|
+
...result,
|
|
744
|
+
content: [...result.content.slice(0, -1), {
|
|
745
|
+
type: "text",
|
|
746
|
+
text: JSON.stringify(merged, null, 2)
|
|
747
|
+
}]
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* True for an `AmbaApiError`. The duck-typed branch also accepts one thrown
|
|
752
|
+
* by a second copy of this package (a bundler that inlines it twice), where
|
|
753
|
+
* `instanceof` against this copy's class is false.
|
|
754
|
+
*/
|
|
755
|
+
function isAmbaApiError(err) {
|
|
756
|
+
if (err instanceof AmbaApiError) return true;
|
|
757
|
+
if (!(err instanceof Error) || err.name !== "AmbaApiError") return false;
|
|
758
|
+
const e = err;
|
|
759
|
+
return typeof e.status === "number" && typeof e.apiMessage === "string";
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Run a tool handler; a failed API call it throws becomes an error result
|
|
763
|
+
* that keeps the API's `code`, `message` and `details` (`apiErrorResult`).
|
|
764
|
+
* Any other throw propagates, and the MCP SDK reports its message.
|
|
765
|
+
*/
|
|
766
|
+
async function structuredApiErrors(run) {
|
|
767
|
+
try {
|
|
768
|
+
return await run();
|
|
769
|
+
} catch (err) {
|
|
770
|
+
if (isAmbaApiError(err)) return apiErrorResult(err);
|
|
771
|
+
throw err;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
558
775
|
* The `pat` schema entry injected into every tool that goes through
|
|
559
776
|
* `registerTool`. Exported so tests can assert on its presence.
|
|
560
777
|
*/
|
|
@@ -569,26 +786,30 @@ const PAT_ARG_SCHEMA = z.string().optional().describe([
|
|
|
569
786
|
].join(" "));
|
|
570
787
|
/**
|
|
571
788
|
* Build the MISSING_PAT short-circuit payload. Shape matches the auth
|
|
572
|
-
* tools so agents can parse `error.code` uniformly.
|
|
789
|
+
* tools so agents can parse `error.code` uniformly. It is an error result
|
|
790
|
+
* like any other failed call (code mode throws it).
|
|
573
791
|
*/
|
|
574
792
|
function missingPatResult(toolName) {
|
|
575
|
-
return {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
793
|
+
return {
|
|
794
|
+
isError: true,
|
|
795
|
+
content: [{
|
|
796
|
+
type: "text",
|
|
797
|
+
text: JSON.stringify({
|
|
798
|
+
status: 401,
|
|
799
|
+
error: {
|
|
800
|
+
code: "MISSING_PAT",
|
|
801
|
+
message: [
|
|
802
|
+
`Tool "${toolName}" requires a developer Bearer.`,
|
|
803
|
+
"Pass `pat` as a tool argument (the value returned by",
|
|
804
|
+
"`amba_developer_signup` / `amba_developer_login`). The next",
|
|
805
|
+
"agent session will pick the PAT up automatically once the",
|
|
806
|
+
"snippet in `mcp_config` is written to the customer's MCP",
|
|
807
|
+
"client config, at which point the `pat` arg becomes optional."
|
|
808
|
+
].join(" ")
|
|
809
|
+
}
|
|
810
|
+
}, null, 2)
|
|
811
|
+
}]
|
|
812
|
+
};
|
|
592
813
|
}
|
|
593
814
|
/**
|
|
594
815
|
* Register a tool that:
|
|
@@ -624,10 +845,10 @@ function registerTool(server, apiClient, name, description, schema, handler, ali
|
|
|
624
845
|
client = apiClient;
|
|
625
846
|
}
|
|
626
847
|
if (!pat) return missingPatResult(registeredName);
|
|
627
|
-
return handler(rawArgs, {
|
|
848
|
+
return withAccountNotices(await structuredApiErrors(() => handler(rawArgs, {
|
|
628
849
|
pat,
|
|
629
850
|
client
|
|
630
|
-
});
|
|
851
|
+
})), client);
|
|
631
852
|
};
|
|
632
853
|
server.tool(name, description, extendedSchema, deriveToolAnnotations(name), wrappedHandler(name));
|
|
633
854
|
for (const alias of aliases) server.tool(alias, description, extendedSchema, deriveToolAnnotations(alias), (async (rawArgs) => {
|
|
@@ -642,15 +863,16 @@ function registerTool(server, apiClient, name, description, schema, handler, ali
|
|
|
642
863
|
* should go through `registerTool`.
|
|
643
864
|
*/
|
|
644
865
|
function registerPublicTool(server, name, description, schema, handler, aliases = [], annotations) {
|
|
645
|
-
|
|
866
|
+
const wrappedHandler = (args) => structuredApiErrors(() => handler(args));
|
|
867
|
+
server.tool(name, description, schema, annotations ?? deriveToolAnnotations(name), wrappedHandler);
|
|
646
868
|
for (const alias of aliases) server.tool(alias, description, schema, annotations ?? deriveToolAnnotations(alias), (async (args) => {
|
|
647
869
|
warnDeprecatedAlias(alias, name);
|
|
648
|
-
return
|
|
870
|
+
return wrappedHandler(args);
|
|
649
871
|
}));
|
|
650
872
|
}
|
|
651
873
|
//#endregion
|
|
652
874
|
//#region src/tools/projects.ts
|
|
653
|
-
function registerTools$
|
|
875
|
+
function registerTools$43(server, apiClient) {
|
|
654
876
|
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 }) => {
|
|
655
877
|
const result = await client.get("/projects");
|
|
656
878
|
return { content: [{
|
|
@@ -761,7 +983,7 @@ function registerTools$42(server, apiClient) {
|
|
|
761
983
|
}
|
|
762
984
|
//#endregion
|
|
763
985
|
//#region src/tools/push.ts
|
|
764
|
-
function registerTools$
|
|
986
|
+
function registerTools$42(server, apiClient) {
|
|
765
987
|
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.", {
|
|
766
988
|
project_id: z.string().describe("The project ID"),
|
|
767
989
|
title: z.string().describe("Push notification title shown to the user"),
|
|
@@ -891,7 +1113,7 @@ const segmentRulesSchema = z.object({
|
|
|
891
1113
|
operator: z.enum(["AND", "OR"]).describe("Logical operator combining conditions"),
|
|
892
1114
|
conditions: z.array(segmentConditionSchema).describe("Array of filter conditions")
|
|
893
1115
|
});
|
|
894
|
-
function registerTools$
|
|
1116
|
+
function registerTools$41(server, apiClient) {
|
|
895
1117
|
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\").", {
|
|
896
1118
|
project_id: z.string().describe("The project ID"),
|
|
897
1119
|
include_system: z.boolean().optional().describe("Include built-in system segments in the result. Defaults to false.")
|
|
@@ -976,7 +1198,7 @@ const configConditionSchema = z.object({
|
|
|
976
1198
|
percentage: z.number().optional().describe("Percentage rollout (0-100)"),
|
|
977
1199
|
value: z.unknown().describe("Override value for this condition")
|
|
978
1200
|
});
|
|
979
|
-
function registerTools$
|
|
1201
|
+
function registerTools$40(server, apiClient) {
|
|
980
1202
|
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).", {
|
|
981
1203
|
project_id: z.string().describe("The project ID"),
|
|
982
1204
|
include_system: z.boolean().optional().describe("Include platform-provided default config keys in the result. Defaults to false.")
|
|
@@ -1054,7 +1276,7 @@ const contentItemSchema = z.object({
|
|
|
1054
1276
|
metadata: z.record(z.unknown()).optional().describe("Arbitrary metadata key-value pairs"),
|
|
1055
1277
|
is_premium: z.boolean().optional().describe("Whether this content requires a premium entitlement")
|
|
1056
1278
|
});
|
|
1057
|
-
function registerTools$
|
|
1279
|
+
function registerTools$39(server, apiClient) {
|
|
1058
1280
|
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`.", {
|
|
1059
1281
|
project_id: z.string().describe("The project ID"),
|
|
1060
1282
|
name: z.string().describe("Library name (e.g. \"Daily Motivation\", \"Workout Tips\")"),
|
|
@@ -1272,7 +1494,7 @@ function registerTools$38(server, apiClient) {
|
|
|
1272
1494
|
}
|
|
1273
1495
|
//#endregion
|
|
1274
1496
|
//#region src/tools/streaks.ts
|
|
1275
|
-
function registerTools$
|
|
1497
|
+
function registerTools$38(server, apiClient) {
|
|
1276
1498
|
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.", {
|
|
1277
1499
|
project_id: z.string().describe("The project ID"),
|
|
1278
1500
|
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`."),
|
|
@@ -1364,7 +1586,7 @@ function registerTools$37(server, apiClient) {
|
|
|
1364
1586
|
//#endregion
|
|
1365
1587
|
//#region src/tools/integrations.ts
|
|
1366
1588
|
const providerEnum = z.enum(INTEGRATION_PROVIDERS);
|
|
1367
|
-
function registerTools$
|
|
1589
|
+
function registerTools$37(server, apiClient) {
|
|
1368
1590
|
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.", {
|
|
1369
1591
|
project_id: z.string().describe("The project ID"),
|
|
1370
1592
|
provider: providerEnum.describe("Integration provider name"),
|
|
@@ -1434,7 +1656,7 @@ const PERIOD_DAYS = {
|
|
|
1434
1656
|
"30d": "30",
|
|
1435
1657
|
"90d": "90"
|
|
1436
1658
|
};
|
|
1437
|
-
function registerTools$
|
|
1659
|
+
function registerTools$36(server, apiClient) {
|
|
1438
1660
|
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.", {
|
|
1439
1661
|
project_id: z.string().describe("The project ID"),
|
|
1440
1662
|
period: z.enum([
|
|
@@ -1456,68 +1678,8 @@ function registerTools$35(server, apiClient) {
|
|
|
1456
1678
|
}, ["amba_get_analytics"]);
|
|
1457
1679
|
}
|
|
1458
1680
|
//#endregion
|
|
1459
|
-
//#region src/lib/tool-result.ts
|
|
1460
|
-
/**
|
|
1461
|
-
* Shared MCP tool result helpers.
|
|
1462
|
-
*
|
|
1463
|
-
* Every tool emits the same `{ content: [{ type: 'text', text: <json> }] }`
|
|
1464
|
-
* envelope. Two helpers centralize that:
|
|
1465
|
-
*
|
|
1466
|
-
* - [`jsonResult`] — wraps an arbitrary payload.
|
|
1467
|
-
* - [`passthroughResult`] — flattens an upstream HTTP response
|
|
1468
|
-
* (status + parsed body) into the same envelope. Status is written
|
|
1469
|
-
* LAST so a colliding top-level `status` field in the API response
|
|
1470
|
-
* cannot shadow the HTTP status — agents look at `parsed.status` to
|
|
1471
|
-
* distinguish 2xx from 4xx/5xx.
|
|
1472
|
-
*
|
|
1473
|
-
* Lives in `src/lib/` (vs. `src/tools/_helpers.ts`) to set the same
|
|
1474
|
-
* cross-cutting-helper precedent as `src/lib/with-pat.ts` (task #36).
|
|
1475
|
-
* `tools/*` files stay strictly tool registrations.
|
|
1476
|
-
*/
|
|
1477
|
-
/**
|
|
1478
|
-
* Wire-shape every MCP tool handler returns.
|
|
1479
|
-
*
|
|
1480
|
-
* The MCP SDK's `tool()` callback signature is structurally typed and
|
|
1481
|
-
* carries an open index signature for `_meta` etc. Declaring our return
|
|
1482
|
-
* type as a plain `{ content: [...] }` interface won't satisfy that
|
|
1483
|
-
* structural check — so the helpers' return type is left as the actual
|
|
1484
|
-
* inferred shape (no explicit interface) and consumers rely on the
|
|
1485
|
-
* inference + the SDK's structural compatibility. If we ever want a
|
|
1486
|
-
* named alias, write it as a type-alias over the inferred shape rather
|
|
1487
|
-
* than a closed interface.
|
|
1488
|
-
*/
|
|
1489
|
-
/** Wrap an arbitrary payload as a JSON-text tool result. */
|
|
1490
|
-
function jsonResult$1(payload) {
|
|
1491
|
-
return { content: [{
|
|
1492
|
-
type: "text",
|
|
1493
|
-
text: JSON.stringify(payload, null, 2)
|
|
1494
|
-
}] };
|
|
1495
|
-
}
|
|
1496
|
-
/** Preserve an HTTP response wrapper while mapping failures to MCP isError. */
|
|
1497
|
-
function httpResult(result) {
|
|
1498
|
-
return {
|
|
1499
|
-
...jsonResult$1(result),
|
|
1500
|
-
...result.status >= 400 ? { isError: true } : {}
|
|
1501
|
-
};
|
|
1502
|
-
}
|
|
1503
|
-
/**
|
|
1504
|
-
* Flatten an upstream HTTP response into the agent-facing tool payload.
|
|
1505
|
-
* Body fields are spread FIRST so a future top-level `status` key in
|
|
1506
|
-
* the API response cannot shadow the HTTP `status` — agents read
|
|
1507
|
-
* `parsed.status` to distinguish 2xx from 4xx/5xx.
|
|
1508
|
-
*/
|
|
1509
|
-
function passthroughResult(result) {
|
|
1510
|
-
return {
|
|
1511
|
-
...jsonResult$1({
|
|
1512
|
-
...result.body !== null && typeof result.body === "object" ? result.body : {},
|
|
1513
|
-
status: result.status
|
|
1514
|
-
}),
|
|
1515
|
-
...result.status >= 400 ? { isError: true } : {}
|
|
1516
|
-
};
|
|
1517
|
-
}
|
|
1518
|
-
//#endregion
|
|
1519
1681
|
//#region src/tools/users.ts
|
|
1520
|
-
function registerTools$
|
|
1682
|
+
function registerTools$35(server, apiClient) {
|
|
1521
1683
|
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.", {
|
|
1522
1684
|
project_id: z.string().describe("The project ID"),
|
|
1523
1685
|
limit: z.number().optional().describe("Maximum number of users to return (default 50)"),
|
|
@@ -1964,7 +2126,7 @@ final dailyLimit = await Amba.config.get<int>('daily_limit', defaultValue: 10);
|
|
|
1964
2126
|
final streak = await Amba.streaks.get('daily_login');
|
|
1965
2127
|
print('Current streak: \${streak.currentCount}');`
|
|
1966
2128
|
};
|
|
1967
|
-
function registerTools$
|
|
2129
|
+
function registerTools$34(server, apiClient) {
|
|
1968
2130
|
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([
|
|
1969
2131
|
"ios",
|
|
1970
2132
|
"android",
|
|
@@ -2049,7 +2211,7 @@ const criteriaSchema = z.object({
|
|
|
2049
2211
|
property_key: z.string().optional().describe("User property key (required for property_value type)"),
|
|
2050
2212
|
target_value: z.number().describe("Target value to unlock the achievement")
|
|
2051
2213
|
});
|
|
2052
|
-
function registerTools$
|
|
2214
|
+
function registerTools$33(server, apiClient) {
|
|
2053
2215
|
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.", {
|
|
2054
2216
|
project_id: z.string().describe("The project ID"),
|
|
2055
2217
|
key: z.string().describe("Unique key for this achievement (e.g. \"first_workout\", \"streak_master_7\")"),
|
|
@@ -2154,7 +2316,7 @@ function registerTools$32(server, apiClient) {
|
|
|
2154
2316
|
}
|
|
2155
2317
|
//#endregion
|
|
2156
2318
|
//#region src/tools/challenges.ts
|
|
2157
|
-
function registerTools$
|
|
2319
|
+
function registerTools$32(server, apiClient) {
|
|
2158
2320
|
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.", {
|
|
2159
2321
|
project_id: z.string().describe("The project ID"),
|
|
2160
2322
|
name: z.string().describe("Challenge name (e.g. \"7-Day Fitness Sprint\", \"XP Weekend Blitz\")"),
|
|
@@ -2265,7 +2427,7 @@ function registerTools$31(server, apiClient) {
|
|
|
2265
2427
|
}
|
|
2266
2428
|
//#endregion
|
|
2267
2429
|
//#region src/tools/economy.ts
|
|
2268
|
-
function registerTools$
|
|
2430
|
+
function registerTools$31(server, apiClient) {
|
|
2269
2431
|
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.", {
|
|
2270
2432
|
project_id: z.string().describe("The project ID"),
|
|
2271
2433
|
code: z.string().describe("Unique currency code (e.g. \"gold\", \"gems\", \"hearts\")"),
|
|
@@ -2956,7 +3118,7 @@ const objectTypeSchema = z.enum([
|
|
|
2956
3118
|
"package",
|
|
2957
3119
|
"paywall"
|
|
2958
3120
|
]);
|
|
2959
|
-
function registerTools$
|
|
3121
|
+
function registerTools$30(server, apiClient) {
|
|
2960
3122
|
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 }) => {
|
|
2961
3123
|
const result = await client.get(`/projects/${project_id}/monetization/plan`);
|
|
2962
3124
|
return { content: [{
|
|
@@ -3022,7 +3184,7 @@ function registerTools$29(server, apiClient) {
|
|
|
3022
3184
|
}
|
|
3023
3185
|
//#endregion
|
|
3024
3186
|
//#region src/tools/leaderboards.ts
|
|
3025
|
-
function registerTools$
|
|
3187
|
+
function registerTools$29(server, apiClient) {
|
|
3026
3188
|
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)`.", {
|
|
3027
3189
|
project_id: z.string().describe("The project ID"),
|
|
3028
3190
|
name: z.string().describe("Leaderboard name (e.g. \"Top XP Earners\", \"Weekly Streak Leaders\")"),
|
|
@@ -3137,7 +3299,7 @@ function registerTools$28(server, apiClient) {
|
|
|
3137
3299
|
* rollover (the scheduled weekly rollover) and score live off `xp_awarded`
|
|
3138
3300
|
* engagement events.
|
|
3139
3301
|
*/
|
|
3140
|
-
function registerTools$
|
|
3302
|
+
function registerTools$28(server, apiClient) {
|
|
3141
3303
|
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.", {
|
|
3142
3304
|
project_id: z.string().describe("The project ID"),
|
|
3143
3305
|
name: z.string().describe("League tier name, e.g. \"Bronze\", \"Silver\", \"Gold\""),
|
|
@@ -3200,7 +3362,7 @@ function registerTools$27(server, apiClient) {
|
|
|
3200
3362
|
}
|
|
3201
3363
|
//#endregion
|
|
3202
3364
|
//#region src/tools/platform.ts
|
|
3203
|
-
function registerTools$
|
|
3365
|
+
function registerTools$27(server, apiClient) {
|
|
3204
3366
|
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).", {
|
|
3205
3367
|
project_id: z.string().describe("The project ID"),
|
|
3206
3368
|
name: z.string().describe("Flow name (e.g. \"Welcome Flow\", \"Premium Onboarding\")"),
|
|
@@ -3853,7 +4015,7 @@ function registerTools$26(server, apiClient) {
|
|
|
3853
4015
|
}
|
|
3854
4016
|
//#endregion
|
|
3855
4017
|
//#region src/tools/social.ts
|
|
3856
|
-
function registerTools$
|
|
4018
|
+
function registerTools$26(server, apiClient) {
|
|
3857
4019
|
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 }) => {
|
|
3858
4020
|
const result = await client.get(`/projects/${project_id}/friends/stats`);
|
|
3859
4021
|
return { content: [{
|
|
@@ -4178,7 +4340,7 @@ function registerTools$25(server, apiClient) {
|
|
|
4178
4340
|
}
|
|
4179
4341
|
//#endregion
|
|
4180
4342
|
//#region src/tools/xp.ts
|
|
4181
|
-
function registerTools$
|
|
4343
|
+
function registerTools$25(server, apiClient) {
|
|
4182
4344
|
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.", {
|
|
4183
4345
|
project_id: z.string().describe("The project ID"),
|
|
4184
4346
|
name: z.string().describe("Rule name (e.g. \"Workout Completed\", \"Daily Login Bonus\")"),
|
|
@@ -4301,7 +4463,7 @@ function registerTools$24(server, apiClient) {
|
|
|
4301
4463
|
}
|
|
4302
4464
|
//#endregion
|
|
4303
4465
|
//#region src/tools/events.ts
|
|
4304
|
-
function registerTools$
|
|
4466
|
+
function registerTools$24(server, apiClient) {
|
|
4305
4467
|
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.", {
|
|
4306
4468
|
project_id: z.string().describe("The project ID"),
|
|
4307
4469
|
since: z.string().optional().describe("ISO-8601 lower bound for occurred_at. Defaults to 24h ago."),
|
|
@@ -4392,7 +4554,7 @@ function registerTools$23(server, apiClient) {
|
|
|
4392
4554
|
}
|
|
4393
4555
|
//#endregion
|
|
4394
4556
|
//#region src/tools/event-catalog.ts
|
|
4395
|
-
function registerTools$
|
|
4557
|
+
function registerTools$23(server, apiClient) {
|
|
4396
4558
|
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.", {
|
|
4397
4559
|
project_id: z.string().describe("The project ID"),
|
|
4398
4560
|
plane: z.enum(["app", "control"]).optional().describe("Filter to one plane. Omit for both.")
|
|
@@ -4462,7 +4624,7 @@ const funnelStepSchema = z.object({
|
|
|
4462
4624
|
event: z.string().describe("Event name to match (engagement event_name)."),
|
|
4463
4625
|
filters: z.array(funnelFilterSchema).optional().describe("Optional property predicates, ANDed together, that an event must satisfy.")
|
|
4464
4626
|
});
|
|
4465
|
-
function registerTools$
|
|
4627
|
+
function registerTools$22(server, apiClient) {
|
|
4466
4628
|
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 }) => {
|
|
4467
4629
|
const result = await client.get(`/projects/${project_id}/funnels`);
|
|
4468
4630
|
return { content: [{
|
|
@@ -4559,9 +4721,13 @@ function registerTools$21(server, apiClient) {
|
|
|
4559
4721
|
//#region src/tools/auth.ts
|
|
4560
4722
|
async function authFetch(apiClient, options) {
|
|
4561
4723
|
const url = `${apiClient.getApiRoot()}${options.path}`;
|
|
4562
|
-
const headers = {
|
|
4724
|
+
const headers = {
|
|
4725
|
+
...apiClient.getAuthProxyHeaders?.(),
|
|
4726
|
+
Accept: "application/json"
|
|
4727
|
+
};
|
|
4563
4728
|
if (options.body !== void 0) headers["Content-Type"] = "application/json";
|
|
4564
4729
|
if (options.bearer) headers["Authorization"] = `Bearer ${options.bearer}`;
|
|
4730
|
+
if (options.signupToken) headers["X-Amba-Signup-Token"] = options.signupToken;
|
|
4565
4731
|
const res = await fetch(url, {
|
|
4566
4732
|
method: options.method,
|
|
4567
4733
|
headers,
|
|
@@ -4644,6 +4810,20 @@ const LOGIN_AGENT_INSTRUCTIONS = [
|
|
|
4644
4810
|
"For browser-based MCP clients (Claude.ai web) that cannot read a static config file,",
|
|
4645
4811
|
"direct the customer to https://mcp.amba.dev/authorize to complete the OAuth flow instead."
|
|
4646
4812
|
].join(" ");
|
|
4813
|
+
/** The 403 codes signup answers with when new accounts are gated. */
|
|
4814
|
+
const SIGNUP_ACCESS_ERROR_CODES = new Set([
|
|
4815
|
+
"SIGNUPS_CLOSED",
|
|
4816
|
+
"INVITE_REQUIRED",
|
|
4817
|
+
"INVITE_INVALID"
|
|
4818
|
+
]);
|
|
4819
|
+
const SIGNUP_ACCESS_AGENT_INSTRUCTIONS = [
|
|
4820
|
+
"New Amba accounts are gated right now. On INVITE_REQUIRED or INVITE_INVALID, ask the human for their",
|
|
4821
|
+
"invite code (it starts with AMBA- and arrives in their invite email) and call this tool again with `invite_code`.",
|
|
4822
|
+
"On SIGNUPS_CLOSED, or when they have no code, request access for them with",
|
|
4823
|
+
"POST /v1/auth/developer/waitlist {\"email\",\"note\"} using their own email (or send them to",
|
|
4824
|
+
"`error.details.request_access_url`), tell them the invite code arrives by email, then stop.",
|
|
4825
|
+
"Retry only with a code the human gives you; never invent codes or switch emails."
|
|
4826
|
+
].join(" ");
|
|
4647
4827
|
/**
|
|
4648
4828
|
* Extract a usable PAT string from a signup/login response body.
|
|
4649
4829
|
*
|
|
@@ -4659,15 +4839,30 @@ function extractPat(body) {
|
|
|
4659
4839
|
if (typeof obj.access_token === "string" && obj.access_token.length > 0) return obj.access_token;
|
|
4660
4840
|
return null;
|
|
4661
4841
|
}
|
|
4842
|
+
/** The `error.code` of a gated-signup 403 body, else null. */
|
|
4843
|
+
function signupAccessErrorCode(result) {
|
|
4844
|
+
if (result.status !== 403 || result.body === null || typeof result.body !== "object") return null;
|
|
4845
|
+
const code = result.body.error?.code;
|
|
4846
|
+
return typeof code === "string" && SIGNUP_ACCESS_ERROR_CODES.has(code) ? code : null;
|
|
4847
|
+
}
|
|
4662
4848
|
/**
|
|
4663
4849
|
* Like `passthroughResult` but enriches successful signup/login bodies
|
|
4664
4850
|
* with the agent-facing `mcp_config` + `agent_instructions` block.
|
|
4665
4851
|
*
|
|
4666
|
-
*
|
|
4667
|
-
*
|
|
4668
|
-
*
|
|
4852
|
+
* Error bodies keep their API-level codes (EMAIL_EXISTS, WEAK_PASSWORD,
|
|
4853
|
+
* INVALID_CREDENTIALS, …) verbatim. A gated-signup 403 (SIGNUPS_CLOSED,
|
|
4854
|
+
* INVITE_REQUIRED, INVITE_INVALID) additionally carries
|
|
4855
|
+
* `agent_instructions` describing the invite / request-access path.
|
|
4669
4856
|
*/
|
|
4670
4857
|
function enrichedAuthResult(result, agentInstructions) {
|
|
4858
|
+
if (signupAccessErrorCode(result) !== null) return {
|
|
4859
|
+
...jsonResult$1({
|
|
4860
|
+
...result.body,
|
|
4861
|
+
agent_instructions: SIGNUP_ACCESS_AGENT_INSTRUCTIONS,
|
|
4862
|
+
status: result.status
|
|
4863
|
+
}),
|
|
4864
|
+
isError: true
|
|
4865
|
+
};
|
|
4671
4866
|
if (result.status < 200 || result.status >= 300) return passthroughResult(result);
|
|
4672
4867
|
const pat = extractPat(result.body);
|
|
4673
4868
|
if (pat === null) return passthroughResult(result);
|
|
@@ -4678,7 +4873,15 @@ function enrichedAuthResult(result, agentInstructions) {
|
|
|
4678
4873
|
status: result.status
|
|
4679
4874
|
});
|
|
4680
4875
|
}
|
|
4681
|
-
|
|
4876
|
+
const INVITE_CODE_PARAM = z.string().optional().describe("Platform invite code from the human's invite email (starts with AMBA-; case, spaces and dashes are ignored). Required while new accounts are invite-only; ask the human for it.");
|
|
4877
|
+
const SIGNUP_ACCESS_ERRORS_DESCRIPTION = [
|
|
4878
|
+
"While signups are gated it answers 403: INVITE_REQUIRED (no invite_code sent) or INVITE_INVALID",
|
|
4879
|
+
"(mistyped, expired, revoked or used up) → ask the human for their code and retry with `invite_code`;",
|
|
4880
|
+
"SIGNUPS_CLOSED → no new accounts right now. On SIGNUPS_CLOSED, or when the human has no code,",
|
|
4881
|
+
"request access via POST /v1/auth/developer/waitlist {\"email\",\"note\"} with their own email",
|
|
4882
|
+
"(or `error.details.request_access_url`), tell the human the invite code arrives by email, and stop."
|
|
4883
|
+
].join(" ");
|
|
4884
|
+
function registerTools$21(server, apiClient) {
|
|
4682
4885
|
registerPublicTool(server, "amba_developer_signup", [
|
|
4683
4886
|
"Create a new Amba developer account. Returns a long-lived Personal Access Token (PAT)",
|
|
4684
4887
|
"plus a real isolated Amba project (provisioning asynchronously), plus ready-to-paste",
|
|
@@ -4692,23 +4895,30 @@ function registerTools$20(server, apiClient) {
|
|
|
4692
4895
|
"Poll `amba_projects_get_provisioning_status` until the project flips to status='active' (~10s)",
|
|
4693
4896
|
"before issuing client-plane traffic against the returned API keys.",
|
|
4694
4897
|
"Email is normalized to lowercase. Password must be at least 8 characters.",
|
|
4695
|
-
"Errors: 409 EMAIL_EXISTS
|
|
4898
|
+
"Errors: 409 EMAIL_EXISTS (the address, or a +tag / Gmail-dot alias of it, is already registered),",
|
|
4899
|
+
"400 DISPOSABLE_EMAIL (temporary-inbox domain; use a permanent address), 400 WEAK_PASSWORD/INVALID_INPUT,",
|
|
4900
|
+
"429 if rate-limited (5/min, 50/day per IP).",
|
|
4901
|
+
SIGNUP_ACCESS_ERRORS_DESCRIPTION
|
|
4696
4902
|
].join(" "), {
|
|
4697
4903
|
email: z.string().email().describe("Developer email address. Will be lowercased."),
|
|
4698
4904
|
password: z.string().min(8).describe("Password, minimum 8 characters."),
|
|
4699
4905
|
name: z.string().optional().describe("Optional display name."),
|
|
4700
|
-
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.")
|
|
4701
|
-
|
|
4906
|
+
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."),
|
|
4907
|
+
invite_code: INVITE_CODE_PARAM
|
|
4908
|
+
}, async ({ email, password, name, referral_code, invite_code }) => {
|
|
4702
4909
|
const body = {
|
|
4703
4910
|
email,
|
|
4704
4911
|
password
|
|
4705
4912
|
};
|
|
4706
4913
|
if (name !== void 0) body.name = name;
|
|
4707
4914
|
if (referral_code !== void 0) body.referral_code = referral_code;
|
|
4915
|
+
if (invite_code !== void 0) body.invite_code = invite_code;
|
|
4916
|
+
const signupToken = apiClient.getSignupToken();
|
|
4708
4917
|
return enrichedAuthResult(await authFetch(apiClient, {
|
|
4709
4918
|
method: "POST",
|
|
4710
4919
|
path: "/auth/developer/signup",
|
|
4711
|
-
body
|
|
4920
|
+
body,
|
|
4921
|
+
...signupToken ? { signupToken } : {}
|
|
4712
4922
|
}), SIGNUP_AGENT_INSTRUCTIONS);
|
|
4713
4923
|
});
|
|
4714
4924
|
registerPublicTool(server, "amba_affiliate_signup", [
|
|
@@ -4718,22 +4928,27 @@ function registerTools$20(server, apiClient) {
|
|
|
4718
4928
|
"(amba_affiliate_invite with affiliate_org_id), then set up payouts with",
|
|
4719
4929
|
"amba_affiliate_my_payout_account_create. Agent: pass the returned `pat` inline on subsequent calls",
|
|
4720
4930
|
"and write the mcp_config snippet to the customer's MCP config for future sessions.",
|
|
4721
|
-
"Email is lowercased; password must be at least 8 characters."
|
|
4931
|
+
"Email is lowercased; password must be at least 8 characters.",
|
|
4932
|
+
SIGNUP_ACCESS_ERRORS_DESCRIPTION
|
|
4722
4933
|
].join(" "), {
|
|
4723
4934
|
email: z.string().email().describe("Affiliate email address. Will be lowercased."),
|
|
4724
4935
|
password: z.string().min(8).describe("Password, minimum 8 characters."),
|
|
4725
4936
|
name: z.string().optional().describe("Optional display name."),
|
|
4726
|
-
invite_token: z.string().optional().describe("Optional affiliate invite token (for reference).")
|
|
4727
|
-
|
|
4937
|
+
invite_token: z.string().optional().describe("Optional affiliate invite token (for reference)."),
|
|
4938
|
+
invite_code: INVITE_CODE_PARAM
|
|
4939
|
+
}, async ({ email, password, name, invite_code }) => {
|
|
4728
4940
|
const body = {
|
|
4729
4941
|
email,
|
|
4730
4942
|
password
|
|
4731
4943
|
};
|
|
4732
4944
|
if (name !== void 0) body.name = name;
|
|
4945
|
+
if (invite_code !== void 0) body.invite_code = invite_code;
|
|
4946
|
+
const signupToken = apiClient.getSignupToken();
|
|
4733
4947
|
return enrichedAuthResult(await authFetch(apiClient, {
|
|
4734
4948
|
method: "POST",
|
|
4735
4949
|
path: "/auth/developer/signup",
|
|
4736
|
-
body
|
|
4950
|
+
body,
|
|
4951
|
+
...signupToken ? { signupToken } : {}
|
|
4737
4952
|
}), SIGNUP_AGENT_INSTRUCTIONS);
|
|
4738
4953
|
});
|
|
4739
4954
|
registerPublicTool(server, "amba_developer_login", [
|
|
@@ -4742,7 +4957,8 @@ function registerTools$20(server, apiClient) {
|
|
|
4742
4957
|
"Agent: keep working in this same session by passing `pat` as an inline arg on every",
|
|
4743
4958
|
"subsequent `amba_*` tool call. Also write the matching snippet to the customer's MCP",
|
|
4744
4959
|
"config file so future sessions pick the PAT up as the inbound Bearer automatically — the customer does nothing.",
|
|
4745
|
-
"Errors: 401 INVALID_CREDENTIALS, 429 if rate-limited (10/min, 100/day per IP
|
|
4960
|
+
"Errors: 401 INVALID_CREDENTIALS, 429 if rate-limited (10/min, 100/day per IP; 20 failed",
|
|
4961
|
+
"attempts/hour, 100/day per account)."
|
|
4746
4962
|
].join(" "), {
|
|
4747
4963
|
email: z.string().email().describe("Developer email address."),
|
|
4748
4964
|
password: z.string().describe("Developer password.")
|
|
@@ -4811,6 +5027,41 @@ function registerTools$20(server, apiClient) {
|
|
|
4811
5027
|
});
|
|
4812
5028
|
}
|
|
4813
5029
|
//#endregion
|
|
5030
|
+
//#region src/tools/developer-claim.ts
|
|
5031
|
+
function registerTools$20(server, apiClient) {
|
|
5032
|
+
registerTool(server, apiClient, "amba_developer_claim", [
|
|
5033
|
+
"Claim an agent-created sandbox account for its human owner. Emails a single-use link to `email`;",
|
|
5034
|
+
"when the owner clicks it the account takes that address, becomes verified, and any unclaimed-sandbox",
|
|
5035
|
+
"hold or archive on its projects lifts immediately. Call this as soon as you know who owns the app:",
|
|
5036
|
+
"each free project of an unclaimed sandbox gets a claim notice from day 7 of its own clock, goes on hold",
|
|
5037
|
+
"on day 14, and is archived (nothing deleted) on day 90. Requires the sandbox account's PAT. Errors: 409 ALREADY_CLAIMED,",
|
|
5038
|
+
"409 EMAIL_TAKEN, 429 when called too often."
|
|
5039
|
+
].join(" "), { email: z.string().email().describe("The human owner's email address.") }, async (args, { pat }) => {
|
|
5040
|
+
const res = await fetch(`${apiClient.getApiRoot()}/auth/developer/claim`, {
|
|
5041
|
+
method: "POST",
|
|
5042
|
+
headers: {
|
|
5043
|
+
Accept: "application/json",
|
|
5044
|
+
"Content-Type": "application/json",
|
|
5045
|
+
Authorization: `Bearer ${pat}`
|
|
5046
|
+
},
|
|
5047
|
+
body: JSON.stringify({ email: args.email })
|
|
5048
|
+
});
|
|
5049
|
+
let body;
|
|
5050
|
+
try {
|
|
5051
|
+
body = await res.json();
|
|
5052
|
+
} catch {
|
|
5053
|
+
body = { error: {
|
|
5054
|
+
code: "INVALID_RESPONSE",
|
|
5055
|
+
message: res.statusText
|
|
5056
|
+
} };
|
|
5057
|
+
}
|
|
5058
|
+
return passthroughResult({
|
|
5059
|
+
status: res.status,
|
|
5060
|
+
body
|
|
5061
|
+
});
|
|
5062
|
+
});
|
|
5063
|
+
}
|
|
5064
|
+
//#endregion
|
|
4814
5065
|
//#region src/tools/collections.ts
|
|
4815
5066
|
async function adminFetch$1(apiClient, options) {
|
|
4816
5067
|
let url = `${apiClient.getBaseUrl()}${options.path}`;
|
|
@@ -5728,16 +5979,7 @@ async function rawFetch(baseUrl, bearer, method, path, options) {
|
|
|
5728
5979
|
headers,
|
|
5729
5980
|
body
|
|
5730
5981
|
});
|
|
5731
|
-
if (!res.ok)
|
|
5732
|
-
let errorMessage = res.statusText;
|
|
5733
|
-
let errorCode;
|
|
5734
|
-
try {
|
|
5735
|
-
const errBody = await res.json();
|
|
5736
|
-
errorMessage = errBody?.error?.message ?? res.statusText;
|
|
5737
|
-
errorCode = errBody?.error?.code;
|
|
5738
|
-
} catch {}
|
|
5739
|
-
throw new AmbaApiError(res.status, errorCode, errorMessage);
|
|
5740
|
-
}
|
|
5982
|
+
if (!res.ok) throw await ambaApiErrorFromResponse(res);
|
|
5741
5983
|
if (res.status === 204) return;
|
|
5742
5984
|
return await res.json();
|
|
5743
5985
|
}
|
|
@@ -5946,6 +6188,7 @@ function registerTools$17(server, apiClient) {
|
|
|
5946
6188
|
"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.",
|
|
5947
6189
|
"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`.",
|
|
5948
6190
|
"Site name must match /^[a-z][a-z0-9_-]{0,49}$/. Per-file cap: 25 MiB. The site's public URL is `https://{slug}.app.amba.host` where `slug` is `{first 8 chars of project_id}-{name}`.",
|
|
6191
|
+
"Routing follows common static-host conventions, so directory-style exports work as-is: `/a/` serves `a/index.html`; `/a` serves the exact file, then `a.html`, then 308-redirects to `/a/` when only `a/index.html` exists. Include a top-level `404.html` to answer unknown paths with status 404; without one, extensionless paths fall back to `index.html` (single-page-app mode).",
|
|
5949
6192
|
"Returns the deployment record + public URL + file/byte counts."
|
|
5950
6193
|
].join(" "), {
|
|
5951
6194
|
project_id: z.string().describe("The Amba project ID."),
|
|
@@ -6197,11 +6440,12 @@ function registerTools$14(server, apiClient) {
|
|
|
6197
6440
|
"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).",
|
|
6198
6441
|
"Secret name must match /^[A-Z][A-Z0-9_]{0,62}$/. Function name (when given) must match /^[a-z][a-z0-9_-]{0,57}$/.",
|
|
6199
6442
|
"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.",
|
|
6443
|
+
"Every function already receives AMBA_API_URL, AMBA_PROJECT_ID, AMBA_INTERNAL_TOKEN (project-scoped admin token), and AMBA_AI_GATEWAY_URL in `env`, so never set those. For your own values, use an app prefix: store the project client key as APP_CLIENT_KEY, for example.",
|
|
6200
6444
|
"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.",
|
|
6201
6445
|
"Value cap: 64 KiB. Plaintext is never returned afterwards."
|
|
6202
6446
|
].join(" "), {
|
|
6203
6447
|
project_id: z.string().describe("The Amba project ID."),
|
|
6204
|
-
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."),
|
|
6448
|
+
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; use an app prefix such as APP_ (APP_CLIENT_KEY)."),
|
|
6205
6449
|
value: z.string().describe("Secret value. Stored encrypted at rest; never returned in plaintext afterwards."),
|
|
6206
6450
|
function: z.string().optional().describe("Function this secret binds to. OMIT for a project-wide secret (applied to every function in the project).")
|
|
6207
6451
|
}, async ({ project_id, name, value, function: functionName }, { pat }) => {
|
|
@@ -6892,7 +7136,36 @@ const TIER_CATALOG = {
|
|
|
6892
7136
|
},
|
|
6893
7137
|
max_projects_per_account: 2,
|
|
6894
7138
|
sleeps_after_inactivity_days: 14,
|
|
6895
|
-
|
|
7139
|
+
database: {
|
|
7140
|
+
max_compute_units: .25,
|
|
7141
|
+
compute_cu_hours_per_month: 10,
|
|
7142
|
+
storage_mb: 512,
|
|
7143
|
+
at_compute_cap: "database pauses until the period resets or the project upgrades",
|
|
7144
|
+
at_storage_cap: "writes that add data are refused; reads and deletes keep working",
|
|
7145
|
+
upgrade_tool: "amba_billing_upgrade"
|
|
7146
|
+
},
|
|
7147
|
+
all_categories: true,
|
|
7148
|
+
free_tier_limits: {
|
|
7149
|
+
paid_features: [
|
|
7150
|
+
"custom_domains",
|
|
7151
|
+
"domain_purchase",
|
|
7152
|
+
"email_send",
|
|
7153
|
+
"email_templates",
|
|
7154
|
+
"app_builder"
|
|
7155
|
+
],
|
|
7156
|
+
auth_emails_per_day: 100,
|
|
7157
|
+
sms_otp_per_day: 10,
|
|
7158
|
+
push_campaign_sends_per_day: 3,
|
|
7159
|
+
push_direct_sends_per_day: 100,
|
|
7160
|
+
function_invocations_per_day: 1e4,
|
|
7161
|
+
function_cpu_ms_per_invocation: 50,
|
|
7162
|
+
function_schedules: 2,
|
|
7163
|
+
function_schedule_min_interval_minutes: 60,
|
|
7164
|
+
queue_messages_per_day: 1e3,
|
|
7165
|
+
tracked_links_per_day: 50,
|
|
7166
|
+
sites: "preview bar + noindex on *.app.amba.host",
|
|
7167
|
+
docs: "https://docs.amba.dev/billing#free-tier-limits"
|
|
7168
|
+
}
|
|
6896
7169
|
},
|
|
6897
7170
|
{
|
|
6898
7171
|
name: "pro",
|
|
@@ -6906,6 +7179,11 @@ const TIER_CATALOG = {
|
|
|
6906
7179
|
media_storage_mb: 1024
|
|
6907
7180
|
},
|
|
6908
7181
|
sleeps_after_inactivity_days: null,
|
|
7182
|
+
database: {
|
|
7183
|
+
max_compute_units: 1,
|
|
7184
|
+
compute_cu_hours_per_month: null,
|
|
7185
|
+
storage_mb: null
|
|
7186
|
+
},
|
|
6909
7187
|
all_categories: true
|
|
6910
7188
|
},
|
|
6911
7189
|
{
|
|
@@ -6920,6 +7198,11 @@ const TIER_CATALOG = {
|
|
|
6920
7198
|
media_storage_mb: 25600
|
|
6921
7199
|
},
|
|
6922
7200
|
sleeps_after_inactivity_days: null,
|
|
7201
|
+
database: {
|
|
7202
|
+
max_compute_units: 1,
|
|
7203
|
+
compute_cu_hours_per_month: null,
|
|
7204
|
+
storage_mb: null
|
|
7205
|
+
},
|
|
6923
7206
|
all_categories: true
|
|
6924
7207
|
},
|
|
6925
7208
|
{
|
|
@@ -6957,7 +7240,7 @@ const TIER_CATALOG = {
|
|
|
6957
7240
|
}
|
|
6958
7241
|
};
|
|
6959
7242
|
function registerTools$9(server, apiClient) {
|
|
6960
|
-
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
|
|
7243
|
+
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. The `database` block reports the database limits: compute used vs the free-tier cap in CU-hours (`compute.used_cu_hours`, `compute.limit_cu_hours`, `compute.pct`), storage in MB (`storage.used_mb`, `storage.limit_mb`), the reset date (`period.resets_at`), and `limit_reached`. At the compute cap the database pauses and every database-backed call returns 402 FREE_TIER_LIMIT_REACHED until the period resets or the project upgrades with amba_billing_upgrade. Use this BEFORE provisioning new features or running data-heavy workloads so the agent can self-throttle, and recommend the upgrade once `database.compute.pct` reaches 0.8.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
6961
7244
|
const result = await client.get(`/projects/${project_id}/billing/status`);
|
|
6962
7245
|
return { content: [{
|
|
6963
7246
|
type: "text",
|
|
@@ -6970,6 +7253,25 @@ function registerTools$9(server, apiClient) {
|
|
|
6970
7253
|
text: JSON.stringify(TIER_CATALOG, null, 2)
|
|
6971
7254
|
}] };
|
|
6972
7255
|
});
|
|
7256
|
+
registerTool(server, apiClient, "amba_billing_upgrade", "Move a project to a paid plan in one call: returns a checkout link for the project owner to open (the agent cannot complete payment). This is the call that lifts the free-tier database caps. Free projects run on a small database (0.25 compute units, 10 CU-hours of compute per billing month, 512 MB); at the compute cap the database pauses until the period resets, so an app with real users belongs on a paid plan, where there is no database compute cap. Call it when a request returns 402 FREE_TIER_LIMIT_REACHED (its `details.upgrade.mcp_args` carries the exact arguments), or when amba_billing_status shows `database.compute.pct` at 0.8 or more. Pro is the right default; Scale is for high-traffic apps. The plan applies as soon as checkout completes and a paused database resumes within seconds. This is a write action that leads to a charge: surface the link and the price to the human before calling, then give them the URL.", {
|
|
7257
|
+
project_id: z.string().describe("The project ID"),
|
|
7258
|
+
tier: z.enum(["pro", "scale"]).default("pro").describe("Paid plan: \"pro\" ($20/month) or \"scale\" ($200/month). Defaults to pro."),
|
|
7259
|
+
interval: z.enum(["month", "year"]).default("month").describe("Billing interval. Annual plans bill 12 months at a 20% discount.")
|
|
7260
|
+
}, async ({ project_id, tier, interval }, { client }) => {
|
|
7261
|
+
const url = (await client.post(`/projects/${project_id}/billing/checkout`, {
|
|
7262
|
+
tier: tier ?? "pro",
|
|
7263
|
+
interval: interval ?? "month"
|
|
7264
|
+
}))?.data?.url ?? null;
|
|
7265
|
+
return { content: [{
|
|
7266
|
+
type: "text",
|
|
7267
|
+
text: JSON.stringify({
|
|
7268
|
+
checkout_url: url,
|
|
7269
|
+
tier: tier ?? "pro",
|
|
7270
|
+
interval: interval ?? "month",
|
|
7271
|
+
next_step: url ? "Give this checkout link to the project owner. The paid plan (and the lifted database caps) apply as soon as they complete checkout." : "Checkout did not return a link; check amba_billing_status for the current subscription state."
|
|
7272
|
+
}, null, 2)
|
|
7273
|
+
}] };
|
|
7274
|
+
});
|
|
6973
7275
|
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.", {
|
|
6974
7276
|
project_id: z.string().describe("The project ID"),
|
|
6975
7277
|
ceiling_usd: z.number().min(0).max(1e5).nullable().describe("Maximum monthly bill in USD, or null to disable")
|
|
@@ -9790,6 +10092,7 @@ const TOOL_CATEGORY = {
|
|
|
9790
10092
|
amba_developer_me: "identity",
|
|
9791
10093
|
amba_developer_verify: "identity",
|
|
9792
10094
|
amba_developer_rotate_pat: "identity",
|
|
10095
|
+
amba_developer_claim: "identity",
|
|
9793
10096
|
amba_users_list: "identity",
|
|
9794
10097
|
amba_list_users: "identity",
|
|
9795
10098
|
amba_users_create: "identity",
|
|
@@ -10274,6 +10577,7 @@ const TOOL_CATEGORY = {
|
|
|
10274
10577
|
amba_billing_status: "infrastructure",
|
|
10275
10578
|
amba_billing_tiers: "infrastructure",
|
|
10276
10579
|
amba_billing_set_ceiling: "infrastructure",
|
|
10580
|
+
amba_billing_upgrade: "infrastructure",
|
|
10277
10581
|
amba_payments_account_create: "economy",
|
|
10278
10582
|
amba_payments_create_onboarding_link: "economy",
|
|
10279
10583
|
amba_payments_account_status: "economy",
|
|
@@ -11264,10 +11568,12 @@ async function registerAutoMcpTools(server, apiClient, options) {
|
|
|
11264
11568
|
* called against it once.
|
|
11265
11569
|
*/
|
|
11266
11570
|
function registerAllTools(server, apiClient) {
|
|
11571
|
+
registerTools$21(server, apiClient);
|
|
11267
11572
|
registerTools$20(server, apiClient);
|
|
11268
|
-
registerTools$
|
|
11573
|
+
registerTools$43(server, apiClient);
|
|
11269
11574
|
registerTools$5(server, apiClient);
|
|
11270
11575
|
registerTools$3(server, apiClient);
|
|
11576
|
+
registerTools$42(server, apiClient);
|
|
11271
11577
|
registerTools$41(server, apiClient);
|
|
11272
11578
|
registerTools$40(server, apiClient);
|
|
11273
11579
|
registerTools$39(server, apiClient);
|
|
@@ -11288,7 +11594,6 @@ function registerAllTools(server, apiClient) {
|
|
|
11288
11594
|
registerTools$24(server, apiClient);
|
|
11289
11595
|
registerTools$23(server, apiClient);
|
|
11290
11596
|
registerTools$22(server, apiClient);
|
|
11291
|
-
registerTools$21(server, apiClient);
|
|
11292
11597
|
registerTools$19(server, apiClient);
|
|
11293
11598
|
registerTools$18(server, apiClient);
|
|
11294
11599
|
registerTools$17(server, apiClient);
|
|
@@ -11320,6 +11625,8 @@ function registerAllTools(server, apiClient) {
|
|
|
11320
11625
|
function createApiClient(options) {
|
|
11321
11626
|
const opts = {};
|
|
11322
11627
|
if (options.baseUrl !== void 0) opts.baseUrl = options.baseUrl;
|
|
11628
|
+
if (options.signupToken !== void 0) opts.signupToken = options.signupToken;
|
|
11629
|
+
if (options.authProxyHeaders !== void 0) opts.authProxyHeaders = options.authProxyHeaders;
|
|
11323
11630
|
if (options.token !== void 0) {
|
|
11324
11631
|
const token = options.token;
|
|
11325
11632
|
opts.getToken = async () => token;
|