@layers/amba-mcp 4.0.8 → 4.0.9

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.
@@ -51,6 +51,8 @@ export interface CollectionPolicy {
51
51
  export interface AppMcpClientAuth {
52
52
  apiKey: string;
53
53
  sessionToken?: string;
54
+ /** Verified by the client API on every request; never trusted locally. */
55
+ groupContext?: string;
54
56
  baseUrl: string;
55
57
  }
56
58
  export interface RegisterCollectionToolsOptions {
@@ -26,12 +26,14 @@
26
26
  * named `recipes`.
27
27
  */
28
28
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
29
+ import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
29
30
  import { type AppMcpClientAuth } from './collection-tools.js';
30
31
  /** One exposed function from the app-MCP manifest. */
31
32
  export interface AppMcpManifestFunction {
32
33
  name: string;
33
34
  description?: string | null;
34
35
  input_schema?: Record<string, unknown> | null;
36
+ annotations?: ToolAnnotations | null;
35
37
  public?: boolean;
36
38
  invoke_url: string;
37
39
  }
package/dist/index.js CHANGED
@@ -641,9 +641,9 @@ function registerTool(server, apiClient, name, description, schema, handler, ali
641
641
  * `amba_developer_login`, `amba_developer_refresh`) — anything else
642
642
  * should go through `registerTool`.
643
643
  */
644
- function registerPublicTool(server, name, description, schema, handler, aliases = []) {
645
- server.tool(name, description, schema, deriveToolAnnotations(name), handler);
646
- for (const alias of aliases) server.tool(alias, description, schema, deriveToolAnnotations(alias), (async (args) => {
644
+ function registerPublicTool(server, name, description, schema, handler, aliases = [], annotations) {
645
+ server.tool(name, description, schema, annotations ?? deriveToolAnnotations(name), handler);
646
+ for (const alias of aliases) server.tool(alias, description, schema, annotations ?? deriveToolAnnotations(alias), (async (args) => {
647
647
  warnDeprecatedAlias(alias, name);
648
648
  return handler(args);
649
649
  }));
@@ -1489,6 +1489,13 @@ function jsonResult$1(payload) {
1489
1489
  text: JSON.stringify(payload, null, 2)
1490
1490
  }] };
1491
1491
  }
1492
+ /** Preserve an HTTP response wrapper while mapping failures to MCP isError. */
1493
+ function httpResult(result) {
1494
+ return {
1495
+ ...jsonResult$1(result),
1496
+ ...result.status >= 400 ? { isError: true } : {}
1497
+ };
1498
+ }
1492
1499
  /**
1493
1500
  * Flatten an upstream HTTP response into the agent-facing tool payload.
1494
1501
  * Body fields are spread FIRST so a future top-level `status` key in
@@ -1496,10 +1503,13 @@ function jsonResult$1(payload) {
1496
1503
  * `parsed.status` to distinguish 2xx from 4xx/5xx.
1497
1504
  */
1498
1505
  function passthroughResult(result) {
1499
- return jsonResult$1({
1500
- ...result.body ?? {},
1501
- status: result.status
1502
- });
1506
+ return {
1507
+ ...jsonResult$1({
1508
+ ...result.body !== null && typeof result.body === "object" ? result.body : {},
1509
+ status: result.status
1510
+ }),
1511
+ ...result.status >= 400 ? { isError: true } : {}
1512
+ };
1503
1513
  }
1504
1514
  //#endregion
1505
1515
  //#region src/tools/users.ts
@@ -5755,14 +5765,17 @@ function registerTools$18(server, apiClient) {
5755
5765
  "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).",
5756
5766
  "Optional `rate_limit` declares a per-function rate-limit config (validated server-side). Pass null/omit for no rate limit.",
5757
5767
  "Optional `public` (default false) deploys the function WITHOUT the X-Api-Key gate, so third-party webhook senders (RevenueCat, Stripe, GitHub, Slack) that cannot attach a custom header can reach it. SECURITY: when public, YOU must verify the webhook signature (HMAC) inside the function body — the runtime no longer requires a caller credential. `public: true` only skips the key requirement; rate-limiting and the per-request signed identity context still apply. Leave it false for functions only your own app calls.",
5768
+ "Optional `forward_authorization` (default false) forwards a verified client Bearer to customer code. X-Api-Key and platform internal-trigger credentials are always stripped. Enable only when the function must validate or proxy the end-user session.",
5758
5769
  "Returns the function deployment row + the public URL (`fn_url`: `https://{project_slug}.fn.amba.host/{name}`)."
5759
5770
  ].join(" "), {
5760
5771
  project_id: z.string().describe("The Amba project ID."),
5761
5772
  name: z.string().describe("Function name. Lowercase, /^[a-z][a-z0-9_-]*$/, ≤58 chars."),
5762
5773
  code: z.string().describe("Bundled JavaScript module source (one entry file)."),
5763
5774
  rate_limit: z.unknown().optional().describe("Optional rate-limit config object. Shape: see @layers/amba-shared:RateLimitConfig."),
5764
- public: z.boolean().optional().describe("Deploy the function as PUBLIC (no X-Api-Key required) so external webhook senders can reach it. Default false. When true, you MUST verify the webhook HMAC signature inside the function body — public skips the credential gate only; rate-limiting and signed identity context still apply.")
5765
- }, async ({ project_id, name, code, rate_limit, public: isPublic }, { pat }) => {
5775
+ public: z.boolean().optional().describe("Deploy the function as PUBLIC (no X-Api-Key required) so external webhook senders can reach it. Default false. When true, you MUST verify the webhook HMAC signature inside the function body — public skips the credential gate only; rate-limiting and signed identity context still apply."),
5776
+ forward_authorization: z.boolean().optional().describe("Forward a verified client Authorization Bearer to customer code. Default false. X-Api-Key and platform internal credentials are never forwarded."),
5777
+ mcp: z.unknown().optional().describe("Optional app-MCP tool config: {description?, input_schema?, annotations?}. Safety annotations support readOnlyHint, destructiveHint, idempotentHint, and openWorldHint booleans.")
5778
+ }, async ({ project_id, name, code, rate_limit, public: isPublic, forward_authorization: forwardAuthorization, mcp }, { pat }) => {
5766
5779
  const byteLen = Buffer.byteLength(code, "utf8");
5767
5780
  if (byteLen > MAX_BUNDLE_BYTES) throw new Error(`Bundle size ${byteLen} bytes exceeds ${MAX_BUNDLE_BYTES}-byte cap (10 MiB).`);
5768
5781
  const form = new FormData();
@@ -5770,6 +5783,8 @@ function registerTools$18(server, apiClient) {
5770
5783
  const metadata = { name };
5771
5784
  if (rate_limit !== void 0 && rate_limit !== null) metadata.rate_limit = rate_limit;
5772
5785
  if (isPublic === true) metadata.public = true;
5786
+ if (forwardAuthorization === true) metadata.forward_authorization = true;
5787
+ if (mcp !== void 0) metadata.mcp = mcp;
5773
5788
  form.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }), "metadata.json");
5774
5789
  return jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/functions/deploy`, { formData: form }));
5775
5790
  }, ["amba_deploy_function"]);
@@ -5859,6 +5874,9 @@ function registerTools$18(server, apiClient) {
5859
5874
  deleted: true
5860
5875
  });
5861
5876
  });
5877
+ registerTool(server, apiClient, "amba_functions_list_schedules", "List function schedules with their cron expression, timezone, pause state, next fire time, and most recent fire time. Rows degrade with enriched=false if one provider describe call fails.", { project_id: z.string().describe("The Amba project ID.") }, async ({ project_id }, { pat }) => {
5878
+ return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/functions/schedules`));
5879
+ });
5862
5880
  registerTool(server, apiClient, "amba_functions_schedule", [
5863
5881
  "Register a cron Schedule for a function. The schedule is keyed deterministically per (project, function) — re-calling with a different cron/timezone replaces the prior schedule rather than creating a duplicate.",
5864
5882
  "Cron must be a standard 5- or 6-field whitespace-separated expression. `timezone` defaults to \"UTC\" if omitted. Timezones are IANA strings (e.g. \"America/Los_Angeles\").",
@@ -10148,6 +10166,7 @@ const TOOL_CATEGORY = {
10148
10166
  amba_functions_get_logs: "infrastructure",
10149
10167
  amba_get_function_logs: "infrastructure",
10150
10168
  amba_functions_schedule: "infrastructure",
10169
+ amba_functions_list_schedules: "infrastructure",
10151
10170
  amba_schedule_function: "infrastructure",
10152
10171
  amba_functions_pause_schedule: "infrastructure",
10153
10172
  amba_pause_function_schedule: "infrastructure",
@@ -10706,6 +10725,7 @@ async function adminFetch(apiClient, bearer, call) {
10706
10725
  async function clientFetch(clientAuth, call) {
10707
10726
  const headers = { "X-Api-Key": clientAuth.apiKey };
10708
10727
  if (clientAuth.sessionToken) headers["Authorization"] = `Bearer ${clientAuth.sessionToken}`;
10728
+ if (clientAuth.groupContext) headers["X-Group-Context"] = clientAuth.groupContext;
10709
10729
  return proxyFetch(clientAuth.baseUrl, headers, call);
10710
10730
  }
10711
10731
  /**
@@ -11067,23 +11087,23 @@ function registerFunctionToolsFor(server, options) {
11067
11087
  });
11068
11088
  if (declaredShape) {
11069
11089
  registerPublicTool(server, toolName, description, declaredShape, async (args) => {
11070
- return jsonResult$1(await invokeFunction({
11090
+ return httpResult(await invokeFunction({
11071
11091
  invokeUrl: fn.invoke_url,
11072
11092
  clientAuth,
11073
11093
  body: args
11074
11094
  }));
11075
- });
11095
+ }, [], fn.annotations ?? void 0);
11076
11096
  return;
11077
11097
  }
11078
11098
  registerPublicTool(server, toolName, description, GENERIC_FUNCTION_SHAPE, async (args) => {
11079
11099
  const { body, query } = args;
11080
- return jsonResult$1(await invokeFunction({
11100
+ return httpResult(await invokeFunction({
11081
11101
  invokeUrl: fn.invoke_url,
11082
11102
  clientAuth,
11083
11103
  body,
11084
11104
  ...query ? { query } : {}
11085
11105
  }));
11086
- });
11106
+ }, [], fn.annotations ?? void 0);
11087
11107
  }
11088
11108
  //#endregion
11089
11109
  //#region src/auto/index.ts
@@ -11222,7 +11242,8 @@ async function registerAutoMcpTools(server, apiClient, options) {
11222
11242
  fn,
11223
11243
  clientAuth: {
11224
11244
  apiKey: clientAuth.apiKey,
11225
- ...clientAuth.sessionToken ? { sessionToken: clientAuth.sessionToken } : {}
11245
+ ...clientAuth.sessionToken ? { sessionToken: clientAuth.sessionToken } : {},
11246
+ ...clientAuth.groupContext ? { groupContext: clientAuth.groupContext } : {}
11226
11247
  }
11227
11248
  });
11228
11249
  return {
@@ -34,6 +34,17 @@ export declare function jsonResult(payload: unknown): {
34
34
  text: string;
35
35
  }[];
36
36
  };
37
+ /** Preserve an HTTP response wrapper while mapping failures to MCP isError. */
38
+ export declare function httpResult(result: {
39
+ status: number;
40
+ body: unknown;
41
+ }): {
42
+ isError?: boolean | undefined;
43
+ content: {
44
+ type: "text";
45
+ text: string;
46
+ }[];
47
+ };
37
48
  /**
38
49
  * Flatten an upstream HTTP response into the agent-facing tool payload.
39
50
  * Body fields are spread FIRST so a future top-level `status` key in
@@ -44,6 +55,7 @@ export declare function passthroughResult(result: {
44
55
  status: number;
45
56
  body: unknown;
46
57
  }): {
58
+ isError?: boolean | undefined;
47
59
  content: {
48
60
  type: "text";
49
61
  text: string;
@@ -41,6 +41,7 @@
41
41
  * the same call shape without the pat machinery.
42
42
  */
43
43
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
44
+ import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
44
45
  import { z, type ZodRawShape } from 'zod';
45
46
  import type { ApiClient } from '../api-client.js';
46
47
  /** Wire-shape returned by an MCP tool handler. */
@@ -49,6 +50,8 @@ export interface ToolResult {
49
50
  type: 'text';
50
51
  text: string;
51
52
  }>;
53
+ /** MCP-native failure signal. Tool transports still return HTTP 200. */
54
+ isError?: boolean;
52
55
  }
53
56
  /** Context passed to handlers registered via `registerTool`. */
54
57
  export interface WithPatHandlerContext {
@@ -122,4 +125,6 @@ export declare function registerPublicTool<S extends ZodRawShape>(server: McpSer
122
125
  * warning per alias per process. No active auth-tool rename uses this
123
126
  * yet, but the parameter exists so future renames stay consistent.
124
127
  */
125
- aliases?: readonly string[]): void;
128
+ aliases?: readonly string[],
129
+ /** Explicit app-declared safety hints; absent keeps conservative derivation. */
130
+ annotations?: ToolAnnotations): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layers/amba-mcp",
3
- "version": "4.0.8",
3
+ "version": "4.0.9",
4
4
  "license": "Apache-2.0",
5
5
  "engines": {
6
6
  "node": ">=22"