@audienti/cli 0.1.31 → 0.1.32

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/.mcp.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "mcpServers": {
3
+ "audienti": {
4
+ "title": "Audienti",
5
+ "description": "Bridge MCP clients to Audienti's app-hosted /mcp endpoint for accounts, plays, prospects, lists, analytics, and operator workflows.",
6
+ "cwd": ".",
7
+ "command": "node",
8
+ "args": [
9
+ "./bin/audienti-mcp.js"
10
+ ]
11
+ }
12
+ }
13
+ }
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to the Audienti CLI are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.32] - 2026-08-11
8
+
9
+ ### Added
10
+
11
+ - Add `audienti auth login` for browser-based local authentication through a loopback callback.
12
+ - Add the `audienti-mcp` stdio bridge and Codex MCP manifest for Audienti's app-hosted MCP endpoint, including stage analytics and cohort list creation.
13
+
7
14
  ## [0.1.31] - 2026-08-10
8
15
 
9
16
  ### Added
package/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Audienti CLI
2
2
 
3
3
  Audienti CLI is the agent-first command-line client for the Audienti production
4
- API. It lets local coding agents and operators inspect accounts, create and
5
- manage plays, import prospects, build lists, manage task reminders, and work
6
- supported operator flows.
4
+ API and the local bridge for Audienti's app-hosted MCP endpoint. It lets local
5
+ coding agents and operators inspect accounts,
6
+ create and manage plays, import prospects, build lists, manage task reminders,
7
+ and work supported operator flows.
7
8
 
8
9
  ## Install
9
10
 
@@ -25,16 +26,26 @@ For one-off use, run `npx @audienti/cli --help`.
25
26
 
26
27
  ## Authenticate
27
28
 
28
- Create an Audienti API token through the product, then configure this machine:
29
+ Authenticate through the browser, then configure the default account context:
29
30
 
30
31
  ```bash
31
- audienti auth token <token>
32
+ audienti auth login
32
33
  audienti accounts list --json
33
34
  audienti accounts select <acct_id>
34
35
  audienti users list
35
36
  audienti users select <account_user_id|email|name|me>
36
37
  ```
37
38
 
39
+ `audienti auth login` opens Audienti in your browser, creates an API token from
40
+ your signed-in web session, sends it back to a temporary `127.0.0.1` callback,
41
+ and stores it in the local CLI config after validating it.
42
+
43
+ If you already have a token, you can still configure it directly:
44
+
45
+ ```bash
46
+ audienti auth token <token>
47
+ ```
48
+
38
49
  The CLI writes its local configuration to `~/.config/audienti/config.json` with
39
50
  owner-only permissions. Do not place a production token in an agent prompt,
40
51
  repository file, issue, or CI secret.
@@ -55,6 +66,71 @@ Use `--json` whenever another program or agent will consume the result. Inspect
55
66
  the target state before mutations, and use the command-specific help before
56
67
  creating, changing, or deleting data.
57
68
 
69
+ ## MCP
70
+
71
+ Audienti serves MCP from the app at `/mcp`. The package ships a small stdio
72
+ bridge for MCP hosts that need a local command:
73
+
74
+ ```bash
75
+ audienti-mcp
76
+ ```
77
+
78
+ Use it this way:
79
+
80
+ 1. Install the package.
81
+ 2. Run `audienti auth login`.
82
+ 3. Run `audienti accounts select <acct_id>`.
83
+ 4. Configure your MCP host with the packaged `.mcp.json`, or point it at the
84
+ `audienti-mcp` command.
85
+ 5. In the MCP host, list tools and call the account-scoped tool you need.
86
+
87
+ The bridge reads the same `~/.config/audienti/config.json` as the CLI, forwards
88
+ MCP JSON-RPC messages to the app, and injects the selected account when a tool
89
+ call does not include `account_id`. If you want a tool to run against a
90
+ different account, pass `account_id` in that tool call.
91
+
92
+ MCP clients that can call HTTP directly may use the hosted endpoint instead of
93
+ the stdio bridge:
94
+
95
+ ```bash
96
+ curl https://app.audienti.com/mcp \
97
+ -H "Authorization: Bearer $AUDIENTI_API_TOKEN" \
98
+ -H "Content-Type: application/json" \
99
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
100
+ ```
101
+
102
+ Example tool call:
103
+
104
+ ```json
105
+ {
106
+ "jsonrpc": "2.0",
107
+ "id": 2,
108
+ "method": "tools/call",
109
+ "params": {
110
+ "name": "analytics.stages",
111
+ "arguments": {
112
+ "account_id": "acct_...",
113
+ "query": {
114
+ "interval": "weekly",
115
+ "cohort_start_date": "2026-07-01",
116
+ "cohort_end_date": "2026-07-31"
117
+ }
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
123
+ Useful MCP tools to start with:
124
+
125
+ - `auth.me` confirms the token identity.
126
+ - `accounts.list` shows accessible accounts.
127
+ - `setup.play_preflight` returns LinkedIn connected-account readiness and setup
128
+ or mapping URLs.
129
+ - `offers.create`, `icps.create`, and `motions.create` set up the offer, ICP,
130
+ and play.
131
+ - `analytics.stages` returns stage conversion cohorts and stage aging.
132
+ - `analytics.cohort_lists.create` materializes event cohorts as reusable lists.
133
+
58
134
  Common inspection commands:
59
135
 
60
136
  ```bash
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runMcpServer } from "../src/mcp.js";
4
+
5
+ await runMcpServer();
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "Agent-first command-line client for Audienti.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "audienti": "./bin/audienti.js"
7
+ "audienti": "./bin/audienti.js",
8
+ "audienti-mcp": "./bin/audienti-mcp.js"
8
9
  },
9
10
  "files": [
10
11
  "bin/",
11
12
  "src/",
12
13
  "skills/",
14
+ ".mcp.json",
13
15
  "install",
14
16
  "README.md",
15
17
  "LICENSE",
@@ -1,12 +1,14 @@
1
1
  ---
2
2
  name: audienti
3
- description: Use when the user wants to operate Audienti through the production CLI, including account selection, plays, prospect imports, lists, message previews, or supported operator outcomes.
3
+ description: Use when the user wants to operate Audienti through the production CLI or app-hosted MCP endpoint, including account selection, plays, prospect imports, lists, message previews, analytics, or supported operator outcomes.
4
4
  ---
5
5
 
6
- # Audienti CLI
6
+ # Audienti CLI and MCP
7
7
 
8
- Use the installed `audienti` command as the production contract. Do not build a
9
- parallel wrapper or call undocumented API endpoints.
8
+ Use the installed `audienti` command or Audienti's app-hosted MCP endpoint as
9
+ the production contract. The packaged `audienti-mcp` command is only a local
10
+ stdio bridge for MCP hosts. Do not build a parallel wrapper or call
11
+ undocumented API endpoints.
10
12
 
11
13
  ## Setup
12
14
 
@@ -23,9 +25,15 @@ curl -fsSL https://cli.audienti.com/install | bash
23
25
  ```
24
26
 
25
27
  3. Authentication is explicit and per machine. Do not ask a user to paste a
26
- production token into chat, a repository file, an issue, or a CI secret. Use
27
- the existing `audienti auth token` flow only after the user supplies a token
28
- through an approved secure channel.
28
+ production token into chat, a repository file, an issue, or a CI secret. Prefer
29
+ browser login:
30
+
31
+ ```bash
32
+ audienti auth login
33
+ ```
34
+
35
+ Use `audienti auth token` only after the user supplies a token through an
36
+ approved secure channel.
29
37
 
30
38
  4. Start with discovery, not mutation:
31
39
 
@@ -35,6 +43,39 @@ audienti accounts list --json
35
43
  audienti help agent-workflows
36
44
  ```
37
45
 
46
+ For MCP hosts, configure the packaged `.mcp.json` or run:
47
+
48
+ ```bash
49
+ audienti-mcp
50
+ ```
51
+
52
+ The bridge reads the same local config as the CLI and forwards MCP JSON-RPC to
53
+ the app-hosted `/mcp` endpoint. Before starting the bridge, select account
54
+ context:
55
+
56
+ ```bash
57
+ audienti auth login
58
+ audienti accounts list --json
59
+ audienti accounts select <acct_id>
60
+ audienti users select me
61
+ ```
62
+
63
+ If the MCP host can call HTTP directly, use `POST /mcp` with the same bearer
64
+ token as the API. Call `tools/list` first, then call named tools with
65
+ `tools/call`. Account-scoped tools accept `account_id`; the local bridge injects
66
+ the selected CLI account when it is omitted.
67
+
68
+ Useful MCP tools:
69
+
70
+ - `auth.me` confirms the token identity.
71
+ - `accounts.list` shows accessible accounts.
72
+ - `setup.play_preflight` returns connected-account readiness and setup or
73
+ mapping URLs.
74
+ - `offers.create`, `icps.create`, and `motions.create` set up the offer, ICP,
75
+ and play.
76
+ - `analytics.stages` returns stage conversion cohorts and stage aging.
77
+ - `analytics.cohort_lists.create` materializes event cohorts as reusable lists.
78
+
38
79
  ## Operating Rules
39
80
 
40
81
  - Use `--json` whenever another agent or tool will consume the response.
package/src/api-client.js CHANGED
@@ -40,6 +40,13 @@ export class AudientiClient {
40
40
  return this.requestJson("/api/v1/accounts.json");
41
41
  }
42
42
 
43
+ mcp(message) {
44
+ return this.requestJson("/mcp", {
45
+ method: "POST",
46
+ body: message
47
+ });
48
+ }
49
+
43
50
  users(accountId) {
44
51
  return this.requestJson(accountPath(accountId, ["users"]));
45
52
  }
package/src/cli.js CHANGED
@@ -1,4 +1,7 @@
1
1
  import { parseArgs } from "node:util";
2
+ import { createServer } from "node:http";
3
+ import { spawn } from "node:child_process";
4
+ import { randomBytes } from "node:crypto";
2
5
  import { readFile } from "node:fs/promises";
3
6
  import { basename, join } from "node:path";
4
7
  import { ApiError, AudientiClient, DEFAULT_HOST, normalizeHost } from "./api-client.js";
@@ -17,6 +20,7 @@ const DEFAULT_LIST_LIMIT = 20;
17
20
  const API_MAX_LIST_LIMIT = 100;
18
21
  const DEFAULT_LOOKUP_TIMEOUT_SECONDS = 60;
19
22
  const DEFAULT_LOOKUP_POLL_INTERVAL_SECONDS = 2;
23
+ const DEFAULT_AUTH_LOGIN_TIMEOUT_SECONDS = 180;
20
24
  const DEFAULT_WRITER_TEST_RUN_TIMEOUT_SECONDS = 180;
21
25
  const DEFAULT_WRITER_TEST_RUN_POLL_INTERVAL_SECONDS = 2;
22
26
  const PACKAGE_NAME = "@audienti/cli";
@@ -182,6 +186,7 @@ async function dispatch(argv, context) {
182
186
  const normalizedResource = normalizeResource(resource);
183
187
 
184
188
  if (normalizedResource === "auth" && action === "token") return authToken(rest, context);
189
+ if (normalizedResource === "auth" && action === "login") return authLogin(rest, context);
185
190
  if (normalizedResource === "auth" && action === "status") return authStatus(rest, context, { accountOverride });
186
191
  if (normalizedResource === "auth" && action === "logout") return authLogout(rest, context);
187
192
  if (normalizedResource === "config" && action === "list") return configList(rest, context);
@@ -406,12 +411,148 @@ async function authToken(args, context) {
406
411
  writeLine(context.stdout, "Run `audienti accounts list` to choose an account.");
407
412
  }
408
413
 
414
+ async function authLogin(args, context) {
415
+ const { values, positionals } = parseCommandArgs(args, {
416
+ ...jsonOptions(),
417
+ host: { type: "string" },
418
+ "timeout-seconds": { type: "string" },
419
+ "no-open": { type: "boolean" }
420
+ });
421
+
422
+ if (positionals.length > 0) {
423
+ throw new CommandError("Usage: audienti auth login [--host https://app.audienti.com] [--timeout-seconds <n>] [--no-open] [--json]");
424
+ }
425
+
426
+ const host = normalizeHost(values.host || DEFAULT_HOST);
427
+ const timeoutSeconds = normalizeOptionalPositiveInteger(values["timeout-seconds"], "--timeout-seconds") || DEFAULT_AUTH_LOGIN_TIMEOUT_SECONDS;
428
+ const state = randomBytes(24).toString("hex");
429
+ const callback = await createAuthCallbackServer({ state, context });
430
+ const authUrl = new URL("/cli/auth", host);
431
+ authUrl.searchParams.set("redirect_uri", callback.redirectUri);
432
+ authUrl.searchParams.set("state", state);
433
+
434
+ if (values.json) {
435
+ writeJson(context.stdout, {
436
+ kind: "auth_login_start",
437
+ auth_url: authUrl.toString(),
438
+ callback_url: callback.redirectUri,
439
+ timeout_seconds: timeoutSeconds
440
+ });
441
+ } else {
442
+ writeLine(context.stdout, "Open this URL to authenticate Audienti CLI:");
443
+ writeLine(context.stdout, authUrl.toString());
444
+ }
445
+
446
+ if (!values["no-open"]) openBrowser(authUrl.toString(), context);
447
+
448
+ let payload;
449
+ try {
450
+ payload = await callback.wait(timeoutSeconds);
451
+ } finally {
452
+ await callback.close();
453
+ }
454
+
455
+ const client = new AudientiClient({ host: payload.host || host, token: payload.token, fetchImpl: context.fetchImpl });
456
+ const user = await client.me();
457
+ await writeConfig({
458
+ host: client.host,
459
+ token: payload.token,
460
+ accountId: payload.account_id || undefined,
461
+ accountName: payload.account_name || undefined
462
+ }, { env: context.env });
463
+
464
+ const result = {
465
+ kind: "auth_login_complete",
466
+ host: client.host,
467
+ user: user?.name || payload.user_name || payload.user_email || user?.email || null,
468
+ account_id: payload.account_id || null,
469
+ account_name: payload.account_name || null
470
+ };
471
+
472
+ if (values.json) return writeJson(context.stdout, result);
473
+
474
+ writeLine(context.stdout, `Authenticated to ${client.host} as ${result.user || "current user"}.`);
475
+ if (result.account_id) writeLine(context.stdout, `Selected account ${result.account_name || result.account_id} (${result.account_id}).`);
476
+ if (!result.account_id) writeLine(context.stdout, "Run `audienti accounts list` to choose an account.");
477
+ }
478
+
479
+ async function createAuthCallbackServer({ state, context }) {
480
+ let resolvePayload;
481
+ let rejectPayload;
482
+ const waitPromise = new Promise((resolve, reject) => {
483
+ resolvePayload = resolve;
484
+ rejectPayload = reject;
485
+ });
486
+
487
+ const server = createServer((request, response) => {
488
+ const url = new URL(request.url, "http://127.0.0.1");
489
+ if (url.pathname !== "/callback") {
490
+ response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
491
+ response.end("Not found.");
492
+ return;
493
+ }
494
+
495
+ const payload = Object.fromEntries(url.searchParams.entries());
496
+ if (payload.state !== state) {
497
+ response.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
498
+ response.end("Audienti CLI authentication failed: state mismatch.");
499
+ rejectPayload(new CommandError("Browser authentication state mismatch."));
500
+ return;
501
+ }
502
+
503
+ if (!payload.token) {
504
+ response.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
505
+ response.end("Audienti CLI authentication failed: missing token.");
506
+ rejectPayload(new CommandError("Browser authentication callback did not include a token."));
507
+ return;
508
+ }
509
+
510
+ response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
511
+ response.end("<!doctype html><title>Audienti CLI authenticated</title><p>Audienti CLI is authenticated. You can close this window.</p>");
512
+ resolvePayload(payload);
513
+ });
514
+
515
+ await new Promise((resolve, reject) => {
516
+ server.once("error", reject);
517
+ server.listen(0, "127.0.0.1", resolve);
518
+ });
519
+
520
+ const { port } = server.address();
521
+
522
+ return {
523
+ redirectUri: `http://127.0.0.1:${port}/callback`,
524
+ wait(timeoutSeconds) {
525
+ let timeoutId;
526
+ const timeoutPromise = new Promise((_, reject) => {
527
+ timeoutId = setTimeout(() => reject(new CommandError(`Timed out waiting for browser authentication after ${timeoutSeconds} seconds.`)), timeoutSeconds * 1000);
528
+ });
529
+
530
+ return Promise.race([waitPromise, timeoutPromise]).finally(() => clearTimeout(timeoutId));
531
+ },
532
+ close() {
533
+ return new Promise((resolve) => server.close(() => resolve()));
534
+ }
535
+ };
536
+ }
537
+
538
+ function openBrowser(url, context) {
539
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
540
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
541
+
542
+ try {
543
+ const child = spawn(command, args, { detached: true, stdio: "ignore" });
544
+ child.unref();
545
+ } catch (error) {
546
+ writeLine(context.stderr, `Could not open browser automatically: ${error.message}`);
547
+ }
548
+ }
549
+
409
550
  async function authStatus(args, context, { accountOverride } = {}) {
410
551
  assertNoPositionals(args, "Usage: audienti auth status [--account <acct_id>]");
411
552
 
412
553
  const config = await readConfig({ env: context.env });
413
554
  if (!config.token) {
414
- writeLine(context.stdout, "Not authenticated. Run `audienti auth token <token>`.");
555
+ writeLine(context.stdout, "Not authenticated. Run `audienti auth login`.");
415
556
  return 1;
416
557
  }
417
558
 
@@ -2833,7 +2974,7 @@ function assertNoPositionals(args, usageText) {
2833
2974
  async function requireAuthenticatedConfig(context) {
2834
2975
  const config = await readConfig({ env: context.env });
2835
2976
  if (!config.token) {
2836
- throw new CommandError("Not authenticated. Run `audienti auth token <token>`.");
2977
+ throw new CommandError("Not authenticated. Run `audienti auth login` or `audienti auth token <token>`.");
2837
2978
  }
2838
2979
 
2839
2980
  return config;
@@ -5982,6 +6123,7 @@ const HELP_TOPICS = new Map([
5982
6123
  " audienti <command> [options]",
5983
6124
  "",
5984
6125
  "Start:",
6126
+ " audienti auth login Sign in through the browser",
5985
6127
  " audienti auth token <token> Save an API token",
5986
6128
  " audienti accounts list See accounts available to this token",
5987
6129
  " audienti accounts select <acct_id> Use one account by default",
@@ -6121,6 +6263,7 @@ const HELP_TOPICS = new Map([
6121
6263
 
6122
6264
  ["auth", [
6123
6265
  "Usage:",
6266
+ " audienti auth login [--host <url>] [--timeout-seconds <n>] [--no-open] [--json]",
6124
6267
  " audienti auth token <token> [--host <url>]",
6125
6268
  " audienti auth status",
6126
6269
  " audienti auth logout",
@@ -6128,11 +6271,32 @@ const HELP_TOPICS = new Map([
6128
6271
  "Status: implemented",
6129
6272
  "",
6130
6273
  "Commands:",
6274
+ " audienti auth login Open Audienti in a browser and save the returned token",
6131
6275
  " audienti auth token <token> Validate and save a bearer API token",
6132
6276
  " audienti auth status Check live auth and show selected account",
6133
6277
  " audienti auth logout Delete local CLI auth config",
6134
6278
  "",
6135
- "Run `audienti auth token help` for token input shape."
6279
+ "Run `audienti auth login help` for browser authentication."
6280
+ ].join("\n")],
6281
+
6282
+ ["auth login", [
6283
+ "Usage:",
6284
+ " audienti auth login [--host <url>] [--timeout-seconds <n>] [--no-open] [--json]",
6285
+ "",
6286
+ "Status: implemented",
6287
+ "",
6288
+ "Options:",
6289
+ " --host <url> Audienti host. Default: https://app.audienti.com",
6290
+ " --timeout-seconds <n> Wait budget before the command fails. Default: 180",
6291
+ " --no-open Print the URL without opening a browser",
6292
+ " --json Print machine-readable start and completion payloads",
6293
+ "",
6294
+ "Flow:",
6295
+ " Starts a temporary 127.0.0.1 callback server, opens /cli/auth in the browser,",
6296
+ " and saves the returned API token to ~/.config/audienti/config.json after validation.",
6297
+ "",
6298
+ "Example:",
6299
+ " audienti auth login --host https://app.audienti.com"
6136
6300
  ].join("\n")],
6137
6301
 
6138
6302
  ["auth token", [
@@ -8911,7 +9075,7 @@ const HELP_TOPICS = new Map([
8911
9075
  " Give a local coding agent the shortest safe path through the common Audienti production workflows.",
8912
9076
  "",
8913
9077
  "1. Authenticate and select an account",
8914
- " audienti auth token <token>",
9078
+ " audienti auth login",
8915
9079
  " audienti accounts list",
8916
9080
  " audienti accounts select <acct_id>",
8917
9081
  " audienti users list",
package/src/mcp.js ADDED
@@ -0,0 +1,103 @@
1
+ import { ApiError, AudientiClient, DEFAULT_HOST } from "./api-client.js";
2
+ import { readConfig } from "./config.js";
3
+
4
+ export async function runMcpServer({
5
+ input = process.stdin,
6
+ output = process.stdout,
7
+ env = process.env,
8
+ fetchImpl = globalThis.fetch
9
+ } = {}) {
10
+ input.setEncoding("utf8");
11
+
12
+ let buffer = "";
13
+ for await (const chunk of input) {
14
+ buffer += chunk;
15
+ const lines = buffer.split(/\r?\n/);
16
+ buffer = lines.pop() || "";
17
+
18
+ for (const line of lines) {
19
+ if (!line.trim()) continue;
20
+ const response = await handleRawMessage(line, { env, fetchImpl });
21
+ if (response) output.write(`${JSON.stringify(response)}\n`);
22
+ }
23
+ }
24
+ }
25
+
26
+ export async function handleRawMessage(line, options = {}) {
27
+ try {
28
+ return handleMcpRequest(JSON.parse(line), options);
29
+ } catch (error) {
30
+ return errorResponse(null, -32700, error.message);
31
+ }
32
+ }
33
+
34
+ export async function handleMcpRequest(message, { env = process.env, fetchImpl = globalThis.fetch } = {}) {
35
+ if (!message || message.jsonrpc !== "2.0") {
36
+ return errorResponse(message?.id ?? null, -32600, "Invalid JSON-RPC request.");
37
+ }
38
+
39
+ if (message.id === undefined || message.id === null) return null;
40
+
41
+ try {
42
+ return await remoteMcpResponse(message, { env, fetchImpl });
43
+ } catch (error) {
44
+ return errorResponse(message.id, -32603, toolErrorMessage(error), toolErrorPayload(error));
45
+ }
46
+ }
47
+
48
+ async function remoteMcpResponse(message, { env = process.env, fetchImpl = globalThis.fetch } = {}) {
49
+ const config = await readConfig({ env });
50
+ if (!config.token) throw new Error("Not authenticated. Run `audienti auth login` or `audienti auth token <token>`.");
51
+
52
+ const client = new AudientiClient({
53
+ host: config.host || DEFAULT_HOST,
54
+ token: config.token,
55
+ fetchImpl
56
+ });
57
+
58
+ return client.mcp(withSelectedAccount(message, config));
59
+ }
60
+
61
+ function withSelectedAccount(message, config) {
62
+ if (message.method !== "tools/call" || !config.accountId) return message;
63
+
64
+ const params = message.params || {};
65
+ const args = params.arguments || {};
66
+ if (args.account_id) return message;
67
+
68
+ return {
69
+ ...message,
70
+ params: {
71
+ ...params,
72
+ arguments: {
73
+ ...args,
74
+ account_id: config.accountId
75
+ }
76
+ }
77
+ };
78
+ }
79
+
80
+ function errorResponse(id, code, message, data) {
81
+ return {
82
+ jsonrpc: "2.0",
83
+ id,
84
+ error: {
85
+ code,
86
+ message,
87
+ ...(data === undefined ? {} : { data })
88
+ }
89
+ };
90
+ }
91
+
92
+ function toolErrorMessage(error) {
93
+ if (error instanceof ApiError && error.status) return `${error.message} (HTTP ${error.status})`;
94
+ return error.message;
95
+ }
96
+
97
+ function toolErrorPayload(error) {
98
+ return {
99
+ error: error.message,
100
+ status: error instanceof ApiError ? error.status || null : null,
101
+ body: error instanceof ApiError ? error.body || null : null
102
+ };
103
+ }