@cantinasecurity/apex-cli 0.1.10 → 0.1.11

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.
@@ -7,6 +7,8 @@ description: Use when a user wants to run Apex scans, inspect findings, export r
7
7
 
8
8
  This skill is bundled with Apex CLI and can be installed into the current repository with `apex setup claude`.
9
9
 
10
+ `apex setup claude` configures the MCP server with client attribution so Apex can distinguish Claude MCP usage from direct CLI usage. Users can inspect or disable anonymous local usage telemetry with `apex telemetry status` and `apex telemetry disable`.
11
+
10
12
  ## Instructions
11
13
 
12
14
  Use Apex through the MCP server when the `apex-*` tools are available.
@@ -39,6 +41,7 @@ If the Apex MCP server is not configured, fall back to the local CLI:
39
41
  - Use `sourceMode: "remote"` only when the user explicitly wants to forbid local snapshot fallbacks.
40
42
  - When checking a scan that is not the workspace binding's latest scan, pass `scanId` to `apex-status`; use `apex scans` or `apex-scans` first if you need to discover scan IDs.
41
43
  - Finding comments, feedback, and fix review scan starts use the same Apex device-login credentials as read tools. If a write tool reports missing auth, re-run `apex-auth-status` and complete `apex-auth-start` / `apex-auth-wait` instead of asking for browser cookies or auth tokens.
44
+ - Anonymous telemetry records only sanitized command/tool metadata such as command names, enum modes, counts, durations, success/failure categories, CLI version, and client integration. It must not include raw repository paths, scan IDs, finding IDs, comments, file paths, PR URLs, or tokens.
42
45
  - Invalid finding feedback requires `dismissalReason`; valid feedback can include `suggestedSeverity`, including `extreme`.
43
46
  - Fix PR callback feedback requires valid feedback with `labels: ["fixed"]` and `fixPrUrls`; start the fix review scan with `apex-finding-fix-review` after saving that feedback.
44
47
  - Finding identifiers such as `KERN2-25` resolve against the selected or latest scan for the current workspace binding. Pass an explicit `scanId` when needed, or use the finding UUID directly.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Cantina agent plugins for security review workflows.",
9
- "version": "0.1.10"
9
+ "version": "0.1.11"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -14,10 +14,10 @@
14
14
  "source": {
15
15
  "source": "npm",
16
16
  "package": "@cantinasecurity/apex-cli",
17
- "version": "0.1.10"
17
+ "version": "0.1.11"
18
18
  },
19
19
  "description": "Run Apex security scans and review findings from Claude Code.",
20
- "version": "0.1.10",
20
+ "version": "0.1.11",
21
21
  "author": {
22
22
  "name": "Cantina",
23
23
  "email": "support@cantina.xyz"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apex-cli",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Run Apex security scans and review findings from Claude Code.",
5
5
  "author": {
6
6
  "name": "Cantina",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apex-cli",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Run Apex security scans and review findings from Codex.",
5
5
  "author": {
6
6
  "name": "Cantina",
package/.mcp.claude.json CHANGED
@@ -5,9 +5,13 @@
5
5
  "args": [
6
6
  "-y",
7
7
  "-p",
8
- "@cantinasecurity/apex-cli@0.1.10",
8
+ "@cantinasecurity/apex-cli@0.1.11",
9
9
  "apex-mcp"
10
- ]
10
+ ],
11
+ "env": {
12
+ "APEX_MCP_CLIENT": "claude",
13
+ "APEX_CLIENT_INTEGRATION": "claude"
14
+ }
11
15
  }
12
16
  }
13
17
  }
package/.mcp.codex.json CHANGED
@@ -4,8 +4,12 @@
4
4
  "args": [
5
5
  "-y",
6
6
  "-p",
7
- "@cantinasecurity/apex-cli@0.1.10",
7
+ "@cantinasecurity/apex-cli@0.1.11",
8
8
  "apex-mcp"
9
- ]
9
+ ],
10
+ "env": {
11
+ "APEX_MCP_CLIENT": "codex",
12
+ "APEX_CLIENT_INTEGRATION": "codex"
13
+ }
10
14
  }
11
15
  }
package/MARKETPLACE.md CHANGED
@@ -13,7 +13,7 @@ This package is prepared as both a Claude Code plugin and a Codex plugin. The pu
13
13
  The plugin MCP configs launch the pinned npm CLI package with:
14
14
 
15
15
  ```bash
16
- npx -y -p @cantinasecurity/apex-cli@0.1.10 apex-mcp
16
+ npx -y -p @cantinasecurity/apex-cli@0.1.11 apex-mcp
17
17
  ```
18
18
 
19
19
  That keeps marketplace installs independent of a user's global `apex` install.
package/README.md CHANGED
@@ -149,6 +149,7 @@ Supported shell commands:
149
149
  - `apex login`
150
150
  - `apex logout`
151
151
  - `apex setup [all|codex|claude|copilot]`
152
+ - `apex telemetry [status|enable|disable]`
152
153
  - `apex update`
153
154
  - `apex connect github`
154
155
  - `apex connect gitlab`
@@ -221,18 +222,24 @@ If Apex is installed globally, prefer:
221
222
  apex setup
222
223
  ```
223
224
 
224
- That registers Apex for installed Codex CLI, Claude Code, and GitHub Copilot CLI clients automatically.
225
+ That registers Apex for installed Codex CLI, Claude Code, and GitHub Copilot CLI clients automatically. Codex and Claude registrations also set `APEX_MCP_CLIENT` and `APEX_CLIENT_INTEGRATION` so Apex can distinguish agent-driven MCP usage from direct CLI usage.
225
226
 
226
227
  If you want to wire clients manually instead, Apex ships a stable `apex-mcp` binary. For Codex:
227
228
 
228
229
  ```bash
229
- codex mcp add apex -- apex-mcp
230
+ codex mcp add apex \
231
+ --env APEX_MCP_CLIENT=codex \
232
+ --env APEX_CLIENT_INTEGRATION=codex \
233
+ -- apex-mcp
230
234
  ```
231
235
 
232
236
  For Claude Code:
233
237
 
234
238
  ```bash
235
- claude mcp add --scope user apex -- apex-mcp
239
+ claude mcp add --scope user \
240
+ -e APEX_MCP_CLIENT=claude \
241
+ -e APEX_CLIENT_INTEGRATION=claude \
242
+ apex -- apex-mcp
236
243
  ```
237
244
 
238
245
  For GitHub Copilot CLI:
@@ -247,7 +254,11 @@ For any other MCP client, configure it to launch:
247
254
  {
248
255
  "mcpServers": {
249
256
  "apex": {
250
- "command": "apex-mcp"
257
+ "command": "apex-mcp",
258
+ "env": {
259
+ "APEX_MCP_CLIENT": "custom-mcp-client",
260
+ "APEX_CLIENT_INTEGRATION": "custom-mcp-client"
261
+ }
251
262
  }
252
263
  }
253
264
  }
@@ -259,7 +270,11 @@ From a local checkout during development, prefer the repo-local binary so the MC
259
270
  {
260
271
  "mcpServers": {
261
272
  "apex": {
262
- "command": "/path/to/apex-cli/bin/apex-mcp"
273
+ "command": "/path/to/apex-cli/bin/apex-mcp",
274
+ "env": {
275
+ "APEX_MCP_CLIENT": "local-dev",
276
+ "APEX_CLIENT_INTEGRATION": "local-dev"
277
+ }
263
278
  }
264
279
  }
265
280
  }
@@ -273,7 +288,11 @@ If you need to launch through `pnpm`, use `--silent`:
273
288
  "apex": {
274
289
  "command": "pnpm",
275
290
  "args": ["--silent", "mcp"],
276
- "cwd": "/path/to/apex-cli"
291
+ "cwd": "/path/to/apex-cli",
292
+ "env": {
293
+ "APEX_MCP_CLIENT": "local-dev",
294
+ "APEX_CLIENT_INTEGRATION": "local-dev"
295
+ }
277
296
  }
278
297
  }
279
298
  }
@@ -297,6 +316,60 @@ For Codex-style clients, the packaged skill can be installed with `apex setup co
297
316
 
298
317
  For Claude Code, the packaged project skill can be installed into the current repository with `apex setup claude`. The repo-local source lives at `.claude/skills/apex-cli/SKILL.md`. Anthropic documents project skills as filesystem directories under `.claude/skills/<name>/SKILL.md`, and the Claude Agent SDK uses the same location when the `Skill` tool is enabled.
299
318
 
319
+ ## Usage Telemetry
320
+
321
+ Apex CLI emits first-party, privacy-preserving usage telemetry so Cantina can understand how people use the direct CLI, the interactive shell, and MCP tools in Codex, Claude, GitHub Copilot CLI, or other clients.
322
+
323
+ Telemetry is enabled by default and can be disabled locally:
324
+
325
+ ```bash
326
+ apex telemetry status
327
+ apex telemetry disable
328
+ apex telemetry enable
329
+ ```
330
+
331
+ Environment opt-outs override local config:
332
+
333
+ ```bash
334
+ APEX_TELEMETRY_DISABLED=1 apex scan
335
+ # also honored: APEX_DISABLE_TELEMETRY=1 or DO_NOT_TRACK=1
336
+ ```
337
+
338
+ Telemetry records lifecycle events such as command/tool start and completion, duration, success/failure category, CLI version, Node/platform basics, anonymous install/session IDs, and sanitized command metadata. It also adds attribution headers to Apex API requests, including surface (`cli`, `interactive_shell`, or `mcp`), client integration, invocation ID, command/tool name, and CLI version.
339
+
340
+ Telemetry does not send raw cwd paths, repository URLs, finding IDs, scan IDs, PR URLs, comments, file paths, tokens, or raw flag values. Sensitive inputs are reduced to booleans, counts, enum values, or length buckets.
341
+
342
+ Telemetry event posts do not include bearer tokens. The telemetry endpoint is `POST /api/cli/v1/telemetry/events` with a batch payload:
343
+
344
+ ```json
345
+ {
346
+ "events": [
347
+ {
348
+ "schemaVersion": 1,
349
+ "event": "apex.invocation.completed",
350
+ "cliVersion": "0.1.11",
351
+ "invocation": {
352
+ "surface": "mcp",
353
+ "command": "apex-scan",
354
+ "mcpTool": "apex-scan",
355
+ "metadata": {
356
+ "mode": "pr",
357
+ "pullRequestCount": 1,
358
+ "cwdProvided": true
359
+ }
360
+ },
361
+ "client": {
362
+ "integration": "codex"
363
+ },
364
+ "outcome": {
365
+ "success": true,
366
+ "durationMs": 1234
367
+ }
368
+ }
369
+ ]
370
+ }
371
+ ```
372
+
300
373
  ## Plugin And Marketplace Packaging
301
374
 
302
375
  The npm package also includes marketplace-ready plugin artifacts:
@@ -305,7 +378,7 @@ The npm package also includes marketplace-ready plugin artifacts:
305
378
  - `.claude-plugin/plugin.json` and `.mcp.claude.json` for Claude Code plugin installs
306
379
  - `.claude-plugin/marketplace.json` for a Claude marketplace entry backed by the public npm package
307
380
 
308
- These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.10 apex-mcp`, so users do not need to install `apex` globally before enabling the plugin.
381
+ These plugin installs launch the pinned npm package with `npx -y -p @cantinasecurity/apex-cli@0.1.11 apex-mcp`, so users do not need to install `apex` globally before enabling the plugin.
309
382
 
310
383
  The repository also includes `.agents/plugins/marketplace.json` for local Codex marketplace testing from a checkout.
311
384
 
package/dist/apex.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { ApexApiClient, formatApiError } from "./api-client.js";
2
2
  import { parseArgs } from "./args.js";
3
- import { commandCancelScan, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingFeedback, commandFindingFixReview, commandFindings, commandLogin, commandLogout, commandScan, commandScans, commandSetup, commandStatus, commandUpdate, commandWorkspace, commandWorkspaceUse, commandWorkspaces, } from "./commands.js";
3
+ import { commandCancelScan, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingFeedback, commandFindingFixReview, commandFindings, commandLogin, commandLogout, commandScan, commandScans, commandSetup, commandStatus, commandTelemetry, commandUpdate, commandWorkspace, commandWorkspaceUse, commandWorkspaces, } from "./commands.js";
4
4
  import { CLI_HELP_TEXT } from "./help.js";
5
5
  import { runMcpServer } from "./mcp.js";
6
6
  import { runInteractiveShell } from "./shell.js";
7
+ import { createCliTelemetryInvocation, emitInvocationCompleted, emitInvocationStarted, withTelemetryContext, } from "./telemetry.js";
7
8
  import { maybePromptForUpdate } from "./update.js";
8
- async function main() {
9
- const parsed = parseArgs(process.argv.slice(2));
9
+ async function dispatch(parsed) {
10
10
  if (parsed.flags.help === true || parsed.command === "help") {
11
11
  process.stdout.write(CLI_HELP_TEXT);
12
12
  return;
@@ -45,6 +45,9 @@ async function main() {
45
45
  case "setup":
46
46
  await commandSetup(cwd, parsed.flags, parsed.subcommand);
47
47
  return;
48
+ case "telemetry":
49
+ await commandTelemetry(parsed.flags, parsed.subcommand);
50
+ return;
48
51
  case "doctor":
49
52
  await commandDoctor(client, cwd, parsed.flags);
50
53
  return;
@@ -117,6 +120,21 @@ async function main() {
117
120
  throw new Error(`Unknown command: ${parsed.command}`);
118
121
  }
119
122
  }
123
+ async function main() {
124
+ const parsed = parseArgs(process.argv.slice(2));
125
+ const invocation = createCliTelemetryInvocation(parsed);
126
+ emitInvocationStarted(invocation);
127
+ await withTelemetryContext(invocation, async () => {
128
+ try {
129
+ await dispatch(parsed);
130
+ emitInvocationCompleted(invocation);
131
+ }
132
+ catch (error) {
133
+ emitInvocationCompleted(invocation, error);
134
+ throw error;
135
+ }
136
+ });
137
+ }
120
138
  function getFlagValue(value) {
121
139
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
122
140
  }
@@ -1,4 +1,5 @@
1
1
  import { clearCredentials, loadConfig, loadCredentials, saveCredentials } from "./config.js";
2
+ import { getTelemetryRequestHeaders } from "./telemetry.js";
2
3
  export class ApiError extends Error {
3
4
  status;
4
5
  body;
@@ -84,7 +85,11 @@ export class ApexApiClient {
84
85
  async request(path, options = {}) {
85
86
  const baseUrl = await this.getBaseUrl();
86
87
  const headers = new Headers(options.headers ?? {});
88
+ const telemetryHeaders = await getTelemetryRequestHeaders();
87
89
  let credentials = options.auth === false ? null : await this.refreshIfNeeded().catch(() => null);
90
+ for (const [key, value] of Object.entries(telemetryHeaders)) {
91
+ headers.set(key, value);
92
+ }
88
93
  if (options.json !== undefined) {
89
94
  headers.set("Content-Type", "application/json");
90
95
  }
package/dist/commands.js CHANGED
@@ -12,6 +12,7 @@ import { chooseCompany, createWorkspaceBinding, ensureAuthenticated, remediateMi
12
12
  import { createWorkspaceBindingFromSummary, fetchCompanyCredits, fetchCompanyWorkspaces, findWorkspaceByRef, } from "./workspaces.js";
13
13
  import { loadWorkspaceBinding, saveWorkspaceBinding } from "./workspace-binding.js";
14
14
  import { commandSetup as runCliSetup } from "./setup.js";
15
+ import { getTelemetryStatus, setTelemetryEnabled, } from "./telemetry.js";
15
16
  import { commandUpdate as runCliUpdate } from "./update.js";
16
17
  import { mkdir, writeFile } from "node:fs/promises";
17
18
  import path from "node:path";
@@ -496,6 +497,41 @@ export async function commandUpdate(flags) {
496
497
  export async function commandSetup(cwd, flags, target) {
497
498
  return runCliSetup(cwd, flags, target);
498
499
  }
500
+ function formatTelemetryDisabledBy(disabledBy) {
501
+ if (!disabledBy)
502
+ return "not disabled";
503
+ if (disabledBy === "config")
504
+ return "local config";
505
+ return disabledBy.replace(/^env:/, "environment variable ");
506
+ }
507
+ export async function commandTelemetry(flags, target) {
508
+ let payload;
509
+ switch (target ?? "status") {
510
+ case "status":
511
+ payload = await getTelemetryStatus("cli");
512
+ break;
513
+ case "enable":
514
+ payload = await setTelemetryEnabled(true);
515
+ break;
516
+ case "disable":
517
+ payload = await setTelemetryEnabled(false);
518
+ break;
519
+ default:
520
+ throw new Error("Usage: apex telemetry [status|enable|disable]");
521
+ }
522
+ if (isJsonMode(flags)) {
523
+ printJson(payload);
524
+ return payload;
525
+ }
526
+ logLine(`Telemetry: ${payload.enabled ? "enabled" : "disabled"}`, flags);
527
+ logLine(`Disabled by: ${formatTelemetryDisabledBy(payload.disabledBy)}`, flags);
528
+ logLine(`Endpoint: ${payload.endpointPath}`, flags);
529
+ logLine(`Client integration: ${payload.client.integration}`, flags);
530
+ if (payload.installId) {
531
+ logLine(`Install ID: ${payload.installId}`, flags);
532
+ }
533
+ return payload;
534
+ }
499
535
  export async function commandDoctor(client, cwd, flags) {
500
536
  const me = await ensureAuthenticated(client, flags);
501
537
  try {
package/dist/config.js CHANGED
@@ -32,12 +32,16 @@ export async function loadConfig() {
32
32
  version: 1,
33
33
  baseUrl: DEFAULT_BASE_URL,
34
34
  defaultCompanyId: null,
35
+ telemetryDisabled: config?.telemetryDisabled ?? null,
36
+ telemetryInstallId: config?.telemetryInstallId ?? null,
35
37
  };
36
38
  }
37
39
  return {
38
40
  version: 1,
39
41
  baseUrl: config.baseUrl,
40
42
  defaultCompanyId: config.defaultCompanyId ?? null,
43
+ telemetryDisabled: config.telemetryDisabled ?? null,
44
+ telemetryInstallId: config.telemetryInstallId ?? null,
41
45
  };
42
46
  }
43
47
  export async function saveConfig(config) {
package/dist/help.js CHANGED
@@ -23,6 +23,8 @@ export const CLI_HELP_TEXT = `Usage:
23
23
  apex mcp Start the Apex MCP server over stdio
24
24
  apex setup [all|codex|claude|copilot]
25
25
  Configure Apex for Codex, Claude Code, and GitHub Copilot CLI
26
+ apex telemetry [status|enable|disable]
27
+ Show or change local anonymous usage telemetry settings
26
28
  apex update Update the local Apex CLI install
27
29
  apex connect github Open the GitHub connection flow
28
30
  apex connect gitlab Open the GitLab connection flow
@@ -66,6 +68,7 @@ Tips:
66
68
  Invalid finding feedback requires --dismissal-reason.
67
69
  Fix review scans require valid feedback with --label fixed and at least one --fix-pr-url, then apex findings fix-review.
68
70
  Finding identifiers such as KERN2-25 resolve against the selected scan; pass --scan or use the finding UUID directly when needed.
71
+ Anonymous usage telemetry helps Apex understand CLI, MCP, Codex, Claude, and Copilot usage. Disable it with apex telemetry disable or APEX_TELEMETRY_DISABLED=1.
69
72
  Quote workspace names that contain spaces:
70
73
  apex workspace use "Core Platform"
71
74
  `;
@@ -90,6 +93,8 @@ Commands:
90
93
  /cancel-scan [scan-id] Cancel a running or most recent scan
91
94
  /status [scan-id] Show progress for the most recent or selected scan
92
95
  /doctor Validate auth, repos, connections, and workspace binding
96
+ /telemetry [status|enable|disable]
97
+ Show or change local anonymous usage telemetry settings
93
98
  /update Update the local Apex CLI install and exit the shell
94
99
  /logout Sign out locally and exit the shell
95
100
  /repos List detected repositories
@@ -112,5 +117,6 @@ Tips:
112
117
  Invalid finding feedback requires a dismissal reason.
113
118
  Fix review scans require fixed valid feedback with a Fix PR URL. Use scripted CLI or MCP for attaching Fix PR URLs.
114
119
  Use scripted CLI flags for advanced feedback options such as suggested severity or dismissal reason.
120
+ Anonymous usage telemetry can be disabled with /telemetry disable or APEX_TELEMETRY_DISABLED=1.
115
121
  Quote workspace names that contain spaces: /workspace use "Core Platform"
116
122
  `;
package/dist/mcp.js CHANGED
@@ -6,6 +6,7 @@ import { getMe, logout, startDeviceLogin, waitForDeviceLoginApproval } from "./a
6
6
  import { ApexApiClient, formatApiError } from "./api-client.js";
7
7
  import { commandCancelScan, commandConnect, commandCredits, commandDoctor, commandExportFindings, commandFindingComment, commandFindingFeedback, commandFindingFixReview, commandFindings, commandScan, commandScans, commandStatus, commandWorkspace, commandWorkspaceUse, commandWorkspaces, } from "./commands.js";
8
8
  import { CLI_HELP_TEXT } from "./help.js";
9
+ import { createMcpServerTelemetryInvocation, createMcpTelemetryInvocation, emitInvocationCompleted, emitInvocationStarted, emitMcpServerStarted, withTelemetryContext, } from "./telemetry.js";
9
10
  import { APEX_CLI_VERSION } from "./version.js";
10
11
  const MCP_WORKFLOW_GUIDE = `Use Apex through these tools instead of shelling out to the CLI.
11
12
 
@@ -139,14 +140,20 @@ function errorResult(toolName, error) {
139
140
  isError: true,
140
141
  };
141
142
  }
142
- async function runTool(toolName, action, summary) {
143
- try {
144
- const value = await action();
145
- return successResult(summary(value), value);
146
- }
147
- catch (error) {
148
- return errorResult(toolName, error);
149
- }
143
+ async function runTool(toolName, action, summary, input = {}) {
144
+ const invocation = createMcpTelemetryInvocation(toolName, input);
145
+ emitInvocationStarted(invocation);
146
+ return withTelemetryContext(invocation, async () => {
147
+ try {
148
+ const value = await action();
149
+ emitInvocationCompleted(invocation);
150
+ return successResult(summary(value), value);
151
+ }
152
+ catch (error) {
153
+ emitInvocationCompleted(invocation, error);
154
+ return errorResult(toolName, error);
155
+ }
156
+ });
150
157
  }
151
158
  async function requireAuthenticated(client) {
152
159
  const me = await getMe(client);
@@ -235,7 +242,7 @@ function registerTools(server) {
235
242
  return "Device login expired before approval.";
236
243
  }
237
244
  return "Device login is still pending.";
238
- }));
245
+ }, { deviceCode, intervalSeconds, expiresAt, timeoutSeconds }));
239
246
  server.registerTool("apex-logout", {
240
247
  title: "Log Out Of Apex",
241
248
  description: "Clear the locally stored Apex session.",
@@ -268,7 +275,13 @@ function registerTools(server) {
268
275
  cwd: targetCwd,
269
276
  ...payload,
270
277
  };
271
- }, (value) => `Apex doctor completed for ${String(value.cwd)}.`));
278
+ }, (value) => `Apex doctor completed for ${String(value.cwd)}.`, {
279
+ cwd,
280
+ company,
281
+ workspaceName,
282
+ repoPaths,
283
+ sourceMode,
284
+ }));
272
285
  server.registerTool("apex-credits", {
273
286
  title: "Get Apex Credits",
274
287
  description: "Show scan credits plus audit and fix review scan entitlements for the active or selected company.",
@@ -287,7 +300,10 @@ function registerTools(server) {
287
300
  cwd: targetCwd,
288
301
  ...payload,
289
302
  };
290
- }, (value) => `Fetched Apex credits for ${String(value.cwd)}.`));
303
+ }, (value) => `Fetched Apex credits for ${String(value.cwd)}.`, {
304
+ cwd,
305
+ company,
306
+ }));
291
307
  server.registerTool("apex-workspace", {
292
308
  title: "Get Current Apex Workspace Binding",
293
309
  description: "Show the current Apex workspace bound to a directory, if any.",
@@ -301,7 +317,9 @@ function registerTools(server) {
301
317
  cwd: targetCwd,
302
318
  ...payload,
303
319
  };
304
- }, (value) => `Loaded workspace binding for ${String(value.cwd)}.`));
320
+ }, (value) => `Loaded workspace binding for ${String(value.cwd)}.`, {
321
+ cwd,
322
+ }));
305
323
  server.registerTool("apex-workspaces", {
306
324
  title: "List Apex Workspaces",
307
325
  description: "List workspaces available to the active or selected company.",
@@ -320,7 +338,10 @@ function registerTools(server) {
320
338
  cwd: targetCwd,
321
339
  ...payload,
322
340
  };
323
- }, (value) => `Listed Apex workspaces for ${String(value.cwd)}.`));
341
+ }, (value) => `Listed Apex workspaces for ${String(value.cwd)}.`, {
342
+ cwd,
343
+ company,
344
+ }));
324
345
  server.registerTool("apex-workspace-use", {
325
346
  title: "Bind Directory To Apex Workspace",
326
347
  description: "Bind a directory to an existing Apex workspace by id, prefix, or name.",
@@ -340,7 +361,11 @@ function registerTools(server) {
340
361
  cwd: targetCwd,
341
362
  ...payload,
342
363
  };
343
- }, (value) => `Bound ${String(value.cwd)} to an Apex workspace.`));
364
+ }, (value) => `Bound ${String(value.cwd)} to an Apex workspace.`, {
365
+ cwd,
366
+ company,
367
+ workspaceRef,
368
+ }));
344
369
  server.registerTool("apex-scan", {
345
370
  title: "Start Apex Scan",
346
371
  description: "Start a new Apex scan for the provided cwd by default. Pass repoPaths only to scan explicit alternate local roots. Use mode audit for audit scans and mode pr for GitHub pull request scans.",
@@ -378,7 +403,16 @@ function registerTools(server) {
378
403
  cwd: targetCwd,
379
404
  ...payload,
380
405
  };
381
- }, (value) => `Started an Apex scan for ${String(value.cwd)}.`));
406
+ }, (value) => `Started an Apex scan for ${String(value.cwd)}.`, {
407
+ cwd,
408
+ company,
409
+ workspaceName,
410
+ repoPaths,
411
+ mode,
412
+ pullRequests,
413
+ sourceMode,
414
+ force,
415
+ }));
382
416
  server.registerTool("apex-status", {
383
417
  title: "Get Apex Scan Status",
384
418
  description: "Show progress for the most recent or selected Apex scan in a directory.",
@@ -395,7 +429,10 @@ function registerTools(server) {
395
429
  cwd: targetCwd,
396
430
  ...payload,
397
431
  };
398
- }, (value) => `Fetched scan status for ${String(value.cwd)}.`));
432
+ }, (value) => `Fetched scan status for ${String(value.cwd)}.`, {
433
+ cwd,
434
+ scanId,
435
+ }));
399
436
  server.registerTool("apex-scans", {
400
437
  title: "List Apex Scans",
401
438
  description: "List scans for the Apex workspace bound to a directory.",
@@ -411,7 +448,9 @@ function registerTools(server) {
411
448
  cwd: targetCwd,
412
449
  ...payload,
413
450
  };
414
- }, (value) => `Listed scans for ${String(value.cwd)}.`));
451
+ }, (value) => `Listed scans for ${String(value.cwd)}.`, {
452
+ cwd,
453
+ }));
415
454
  server.registerTool("apex-cancel-scan", {
416
455
  title: "Cancel Apex Scan",
417
456
  description: "Cancel a running Apex scan in the bound workspace.",
@@ -428,7 +467,10 @@ function registerTools(server) {
428
467
  cwd: targetCwd,
429
468
  ...payload,
430
469
  };
431
- }, (value) => `Cancelled the selected Apex scan for ${String(value.cwd)}.`));
470
+ }, (value) => `Cancelled the selected Apex scan for ${String(value.cwd)}.`, {
471
+ cwd,
472
+ scanId,
473
+ }));
432
474
  server.registerTool("apex-findings", {
433
475
  title: "List Apex Findings",
434
476
  description: "List findings for the latest or selected Apex scan in a directory.",
@@ -449,7 +491,11 @@ function registerTools(server) {
449
491
  cwd: targetCwd,
450
492
  ...payload,
451
493
  };
452
- }, (value) => `Fetched Apex findings for ${String(value.cwd)}.`));
494
+ }, (value) => `Fetched Apex findings for ${String(value.cwd)}.`, {
495
+ cwd,
496
+ scanId,
497
+ limit,
498
+ }));
453
499
  server.registerTool("apex-finding-comment", {
454
500
  title: "Add Apex Finding Comment",
455
501
  description: "Add a comment or note to an Apex finding using the current Apex login.",
@@ -470,7 +516,13 @@ function registerTools(server) {
470
516
  cwd: targetCwd,
471
517
  ...payload,
472
518
  };
473
- }, (value) => `Added a finding comment for ${String(value.findingRef)}.`));
519
+ }, (value) => `Added a finding comment for ${String(value.findingRef)}.`, {
520
+ cwd,
521
+ findingRef,
522
+ content,
523
+ parentCommentId,
524
+ scanId,
525
+ }));
474
526
  server.registerTool("apex-finding-feedback", {
475
527
  title: "Leave Apex Finding Feedback",
476
528
  description: "Leave valid or invalid feedback on an Apex finding using the current Apex login. To attach a fix PR, send status valid with label fixed and fixPrUrls.",
@@ -508,7 +560,17 @@ function registerTools(server) {
508
560
  ...payload,
509
561
  };
510
562
  }, (value) => `Submitted ${String((value.feedback?.feedbackType ??
511
- "finding"))} feedback for ${String(value.findingRef)}.`));
563
+ "finding"))} feedback for ${String(value.findingRef)}.`, {
564
+ cwd,
565
+ findingRef,
566
+ status,
567
+ comment,
568
+ labels,
569
+ fixPrUrls,
570
+ suggestedSeverity,
571
+ dismissalReason,
572
+ scanId,
573
+ }));
512
574
  server.registerTool("apex-finding-fix-review", {
513
575
  title: "Start Apex Finding Fix Review Scan",
514
576
  description: "Start a fix review scan for a finding after fixed feedback with one or more Fix PR URLs has been saved.",
@@ -527,7 +589,11 @@ function registerTools(server) {
527
589
  cwd: targetCwd,
528
590
  ...payload,
529
591
  };
530
- }, (value) => `Started fix review scan for ${String(value.findingRef)}.`));
592
+ }, (value) => `Started fix review scan for ${String(value.findingRef)}.`, {
593
+ cwd,
594
+ findingRef,
595
+ scanId,
596
+ }));
531
597
  server.registerTool("apex-export-findings", {
532
598
  title: "Export Apex Findings",
533
599
  description: "Export findings for the latest or selected Apex scan to a file on disk.",
@@ -556,7 +622,12 @@ function registerTools(server) {
556
622
  scan: payload.scan,
557
623
  outputPath: payload.outputPath,
558
624
  };
559
- }, (value) => `Exported Apex findings for ${String(value.cwd)}.`));
625
+ }, (value) => `Exported Apex findings for ${String(value.cwd)}.`, {
626
+ cwd,
627
+ scanId,
628
+ format,
629
+ output,
630
+ }));
560
631
  server.registerTool("apex-connect-provider", {
561
632
  title: "Get Apex Provider Connection URL",
562
633
  description: "Return the browser URL a user needs to connect GitHub or GitLab access for Apex. This tool never opens a browser.",
@@ -576,16 +647,22 @@ function registerTools(server) {
576
647
  cwd: targetCwd,
577
648
  ...payload,
578
649
  };
579
- }, (value) => `Fetched the ${String(value.provider)} connection URL.`));
650
+ }, (value) => `Fetched the ${String(value.provider)} connection URL.`, {
651
+ cwd,
652
+ company,
653
+ provider,
654
+ }));
580
655
  }
581
656
  export async function runMcpServer() {
582
657
  const server = new McpServer({
583
658
  name: "apex-cli",
584
659
  version: APEX_CLI_VERSION,
585
660
  });
661
+ const invocation = createMcpServerTelemetryInvocation();
586
662
  registerResources(server);
587
663
  registerTools(server);
588
664
  const transport = new StdioServerTransport();
665
+ emitMcpServerStarted(invocation);
589
666
  await server.connect(transport);
590
667
  }
591
668
  export const testing = {