@zivis/mcp 0.1.12 → 0.1.17

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.
@@ -1,6 +1,19 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
1
4
  import { resolveAuth } from "./auth/index.js";
2
- import { detectProjectBinding, resolveConfigFromBinding, resolveMcpSessionForWorkspace, } from "./project-binding.js";
5
+ import { detectProjectBinding, resolveConfigFromBinding, resolveMcpSessionForWorkspace, sanitizeBindingOverrides, } from "./project-binding.js";
3
6
  import { DEFAULT_CONFIG } from "./types.js";
7
+ const USER_AGENT = (() => {
8
+ try {
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../package.json"), "utf-8"));
11
+ return `@zivis/mcp/${pkg.version ?? "0.0.0"}`;
12
+ }
13
+ catch {
14
+ return "@zivis/mcp/0.0.0";
15
+ }
16
+ })();
4
17
  export class ApiClient {
5
18
  config;
6
19
  constructor(config = DEFAULT_CONFIG) {
@@ -12,16 +25,12 @@ export class ApiClient {
12
25
  if (!detected) {
13
26
  return this.config;
14
27
  }
15
- const { binding } = detected;
28
+ const { binding, filePath } = detected;
16
29
  const fromBinding = resolveConfigFromBinding(ws);
17
30
  return {
18
31
  ...this.config,
19
32
  session: resolveMcpSessionForWorkspace(this.config, ws, detected, fromBinding),
20
- ...(binding.apiBaseUrl ? { apiBaseUrl: binding.apiBaseUrl } : {}),
21
- ...(binding.oauthApiUrl ? { oauthApiUrl: binding.oauthApiUrl } : {}),
22
- ...(binding.oauthClientId ? { oauthClientId: binding.oauthClientId } : {}),
23
- ...(binding.keychainPrefix ? { keychainPrefix: binding.keychainPrefix } : {}),
24
- ...(binding.configDir ? { configDir: binding.configDir } : {}),
33
+ ...sanitizeBindingOverrides(binding, filePath),
25
34
  };
26
35
  }
27
36
  buildAuthHeaders(creds) {
@@ -50,7 +59,7 @@ export class ApiClient {
50
59
  const url = `${apiBaseUrl}${path}`;
51
60
  const headers = {
52
61
  "Content-Type": "application/json",
53
- "User-Agent": "@zivis-ai/cli",
62
+ "User-Agent": USER_AGENT,
54
63
  ...this.buildAuthHeaders(creds),
55
64
  };
56
65
  const fetchOptions = { method, headers };
@@ -88,7 +97,7 @@ export class ApiClient {
88
97
  }
89
98
  const url = `${effectiveConfig.apiBaseUrl}${path}`;
90
99
  const headers = {
91
- "User-Agent": "@zivis-ai/cli",
100
+ "User-Agent": USER_AGENT,
92
101
  ...this.buildAuthHeaders(creds),
93
102
  };
94
103
  const response = await fetch(url, { method: "GET", headers });
@@ -3,7 +3,7 @@
3
3
  "pack_id": "zivis-public",
4
4
  "pack_name": "ZIVIS Public Pattern Pack",
5
5
  "version": "0.2.0",
6
- "built_at": "2026-09-01T21:19:00.226Z",
6
+ "built_at": "2026-09-08T13:37:26.490Z",
7
7
  "tier": "customer_safe",
8
8
  "description": "ZIVIS-curated public pattern pack — capsules + inference prompts evaluated locally on the user's machine.",
9
9
  "capsules": [
@@ -3,6 +3,7 @@ export declare function detectProjectBinding(cwd: string): {
3
3
  binding: ProjectBinding;
4
4
  filePath: string;
5
5
  } | null;
6
+ export declare function sanitizeBindingOverrides(binding: ProjectBinding, filePath: string): Partial<Pick<ZivisConfig, "apiBaseUrl" | "oauthApiUrl" | "oauthClientId">>;
6
7
  export declare function resolveConfigFromBinding(cwd?: string): ZivisConfig;
7
8
  export declare function resolveMcpSessionForWorkspace(incoming: ZivisConfig, workspacePath: string, detected: ReturnType<typeof detectProjectBinding>, fromBinding: ZivisConfig): string;
8
9
  export declare function validateProjectBinding(binding: ProjectBinding, filePath: string, config: ZivisConfig): void;
@@ -27,19 +27,63 @@ export function detectProjectBinding(cwd) {
27
27
  }
28
28
  return null;
29
29
  }
30
+ const TRUSTED_BINDING_HOST = "zivis.ai";
31
+ const TRUSTED_BINDING_HOST_SUFFIX = ".zivis.ai";
32
+ const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
33
+ function isTrustedBindingUrl(raw, envAnchor) {
34
+ if (envAnchor && raw === envAnchor)
35
+ return true;
36
+ let parsed;
37
+ try {
38
+ parsed = new URL(raw);
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ const host = parsed.hostname.toLowerCase();
44
+ if (LOOPBACK_HOSTS.has(host))
45
+ return true;
46
+ if (parsed.protocol !== "https:")
47
+ return false;
48
+ return host === TRUSTED_BINDING_HOST || host.endsWith(TRUSTED_BINDING_HOST_SUFFIX);
49
+ }
50
+ function describeHost(raw) {
51
+ try {
52
+ return new URL(raw).host;
53
+ }
54
+ catch {
55
+ return "<unparseable url>";
56
+ }
57
+ }
58
+ export function sanitizeBindingOverrides(binding, filePath) {
59
+ const out = {};
60
+ const gate = (field, value, envName) => {
61
+ if (!value)
62
+ return;
63
+ if (isTrustedBindingUrl(value, process.env[envName])) {
64
+ out[field] = value;
65
+ return;
66
+ }
67
+ console.error(`[zivis] WARNING: ignoring untrusted "${field}" in ${filePath} ` +
68
+ `(host: ${describeHost(value)}). A repo binding may only point at ` +
69
+ `https://*.zivis.ai, localhost, or the exact value of ${envName}. ` +
70
+ `Falling back to the configured/default host.`);
71
+ };
72
+ gate("apiBaseUrl", binding.apiBaseUrl, "ZIVIS_API_URL");
73
+ gate("oauthApiUrl", binding.oauthApiUrl, "ZIVIS_OAUTH_API_URL");
74
+ if (binding.oauthClientId)
75
+ out.oauthClientId = binding.oauthClientId;
76
+ return out;
77
+ }
30
78
  export function resolveConfigFromBinding(cwd) {
31
79
  const detected = detectProjectBinding(cwd ?? process.cwd());
32
80
  if (!detected)
33
81
  return DEFAULT_CONFIG;
34
- const { binding } = detected;
82
+ const { binding, filePath } = detected;
35
83
  return {
36
84
  ...DEFAULT_CONFIG,
37
85
  ...(binding.session ? { session: binding.session } : {}),
38
- ...(binding.apiBaseUrl ? { apiBaseUrl: binding.apiBaseUrl } : {}),
39
- ...(binding.oauthApiUrl ? { oauthApiUrl: binding.oauthApiUrl } : {}),
40
- ...(binding.oauthClientId ? { oauthClientId: binding.oauthClientId } : {}),
41
- ...(binding.keychainPrefix ? { keychainPrefix: binding.keychainPrefix } : {}),
42
- ...(binding.configDir ? { configDir: binding.configDir } : {}),
86
+ ...sanitizeBindingOverrides(binding, filePath),
43
87
  };
44
88
  }
45
89
  export function resolveMcpSessionForWorkspace(incoming, workspacePath, detected, fromBinding) {
@@ -1,6 +1,6 @@
1
1
  export function registerAuditDependenciesPrompt(server) {
2
2
  server.registerPrompt("audit-dependencies", {
3
- description: "Check the open source libraries this project depends on for security risks. No account setup needed.",
3
+ description: "Check the open source libraries this project depends on for security risks (requires a connected ZIVIS project — run `zivis init` first if needed).",
4
4
  argsSchema: {},
5
5
  }, async () => ({
6
6
  messages: [
@@ -12,33 +12,21 @@ export function registerAuditDependenciesPrompt(server) {
12
12
 
13
13
  Please do the following:
14
14
 
15
- 1. Look at the dependency files in this project. In priority order, check:
16
- - package.json (look for the "dependencies" and "devDependencies" keys)
17
- - requirements.txt or pyproject.toml
18
- - go.mod
19
- - Any other manifest files you can see
15
+ 1. Check whether this repo is connected to ZIVIS: look for \`.zivis/project.json\`. If it's missing, run \`zivis init\` in the terminal first (interactive login + one-time setup) and only continue once that succeeds.
20
16
 
21
- 2. From those files, find all external dependencies that have a GitHub repository.
22
- - Skip internal packages (those starting with "@my-company/" or similar private namespaces).
23
- - Skip built-in packages (e.g. node built-ins like "fs", "path").
17
+ 2. Run \`zivis test dependencies\` in the terminal. This loads the ZIVIS dependency-risk methodology (reachability triage against the resolved lockfile — not just a CVE list) plus this project's prior ZIVIS security context, and hands you the grounding to do the actual analysis yourself.
24
18
 
25
- 3. For each external dependency that has a public GitHub repo, call \`zivis_check_repo_trust\`
26
- with the owner/repo string (e.g. "openai/openai-node").
19
+ 3. Follow the methodology it prints: build the dependency worklist from your lockfile, trace direct-runtime dependencies for reachability (is the vulnerable function actually imported, called, and fed attacker-controlled input?), and classify each as reachable-confirmed / reachable-but-contingent / not-imported / not-applicable / indeterminate.
27
20
 
28
- 4. After checking each one, give me a summary table with columns:
29
- - Package name
30
- - Trust grade (A+ through F)
31
- - Risk level (Safe / Low risk / Moderate / High / Critical)
32
- - Any critical or high findings (one line each)
21
+ 4. Give me a summary table with columns: package, installed version, CVE(s), reachability verdict, and one-line reasoning.
33
22
 
34
- 5. Flag any package with grade C or below and explain in plain English why it's risky.
23
+ 5. For anything reachable-confirmed, record it as a finding through the run's normal reporting flow so it's durable ZIVIS history, not just terminal output.
35
24
 
36
- 6. At the end, suggest the two highest-priority actions to take based on the results.
25
+ 6. At the end, name any transitive-only dependencies you didn't trace this pass (an explicit coverage gap, not a silent pass) and suggest the two highest-priority actions.
37
26
 
38
27
  Ground rules:
39
- - Process dependencies one at a time so I can follow along.
40
- - If a package doesn't have a GitHub repo, skip it and say "(no GitHub repo found)".
41
- - Don't make up trust data — only use what zivis_check_repo_trust returns.
28
+ - This requires a connected ZIVIS project — do not fabricate a trust score or skip straight to a verdict if \`zivis init\`/\`zivis test dependencies\` fails; report what went wrong instead.
29
+ - A CVE with no confirmed reachable call site is not the same as a confirmed vulnerability — say so explicitly rather than flattening the distinction.
42
30
  - Speak in plain English. "High risk" is better than "critical severity threat vector".`,
43
31
  },
44
32
  },
package/dist/server.js CHANGED
@@ -55,8 +55,6 @@ import { THREAT_LIST_RELEVANT_CAPSULES_NAME, THREAT_LIST_RELEVANT_CAPSULES_DESCR
55
55
  import { THREAT_GET_CAPSULE_NAME, THREAT_GET_CAPSULE_DESCRIPTION, THREAT_GET_CAPSULE_SCHEMA, createThreatGetCapsuleHandler, } from "./tools/threat-get-capsule.js";
56
56
  import { THREAT_GET_INFERENCE_PROMPT_NAME, THREAT_GET_INFERENCE_PROMPT_DESCRIPTION, THREAT_GET_INFERENCE_PROMPT_SCHEMA, createThreatGetInferencePromptHandler, } from "./tools/threat-get-inference-prompt.js";
57
57
  import { THREAT_RUN_MATCHER_NAME, THREAT_RUN_MATCHER_DESCRIPTION, THREAT_RUN_MATCHER_SCHEMA, createThreatRunMatcherHandler, } from "./tools/threat-run-matcher.js";
58
- import { CHECK_REPO_TRUST_NAME, CHECK_REPO_TRUST_DESCRIPTION, CHECK_REPO_TRUST_SCHEMA, createCheckRepoTrustHandler, } from "./tools/check-repo-trust.js";
59
- import { GET_OSS_ZAT_NAME, GET_OSS_ZAT_DESCRIPTION, GET_OSS_ZAT_SCHEMA, createGetOssZatHandler, } from "./tools/get-oss-zat.js";
60
58
  import { VERIFY_TRUST_MARK_NAME, VERIFY_TRUST_MARK_DESCRIPTION, VERIFY_TRUST_MARK_SCHEMA, createVerifyTrustMarkHandler, } from "./tools/verify-trust-mark.js";
61
59
  import { GET_TRUST_KEYS_NAME, GET_TRUST_KEYS_DESCRIPTION, GET_TRUST_KEYS_SCHEMA, createGetTrustKeysHandler, } from "./tools/get-trust-keys.js";
62
60
  import { INSPECT_ZAT_NAME, INSPECT_ZAT_DESCRIPTION, INSPECT_ZAT_SCHEMA, createInspectZatHandler, } from "./tools/inspect-zat.js";
@@ -142,14 +140,6 @@ export async function startServer(incoming = DEFAULT_CONFIG) {
142
140
  description: THREAT_RUN_MATCHER_DESCRIPTION,
143
141
  inputSchema: THREAT_RUN_MATCHER_SCHEMA,
144
142
  }, createThreatRunMatcherHandler());
145
- server.registerTool(CHECK_REPO_TRUST_NAME, {
146
- description: CHECK_REPO_TRUST_DESCRIPTION,
147
- inputSchema: CHECK_REPO_TRUST_SCHEMA,
148
- }, createCheckRepoTrustHandler(config));
149
- server.registerTool(GET_OSS_ZAT_NAME, {
150
- description: GET_OSS_ZAT_DESCRIPTION,
151
- inputSchema: GET_OSS_ZAT_SCHEMA,
152
- }, createGetOssZatHandler(config));
153
143
  server.registerTool(VERIFY_TRUST_MARK_NAME, {
154
144
  description: VERIFY_TRUST_MARK_DESCRIPTION,
155
145
  inputSchema: VERIFY_TRUST_MARK_SCHEMA,
@@ -44,18 +44,12 @@ export function createCheckProjectHandler(apiClient, _config) {
44
44
  {
45
45
  id: "run_auth_init",
46
46
  label: "Connect this project to your ZIVIS org",
47
- tool: "zivis_check_project",
48
- why: "Run 'zivis auth init' in your terminal to create the binding, then re-check.",
47
+ tool: "cli_command",
48
+ args_hint: { command: "zivis init" },
49
+ why: "Every ZIVIS command requires a bound Application — this creates the binding, then re-check.",
49
50
  requires_tier: "free",
50
51
  },
51
- {
52
- id: "check_deps_no_auth",
53
- label: "Check dependencies without a binding (no auth needed)",
54
- tool: "zivis_check_repo_trust",
55
- why: "You can check any GitHub repo for security issues right now — no binding required.",
56
- requires_tier: "free",
57
- },
58
- ], "Project is not bound to a ZIVIS org. Run 'zivis auth init' in your terminal to fix this.");
52
+ ], "Project is not bound to a ZIVIS org. Run 'zivis init' in your terminal to fix this.");
59
53
  }
60
54
  const { binding, filePath } = detected;
61
55
  const sessionMatches = currentSession === binding.session;
@@ -161,8 +155,9 @@ export function createCheckProjectHandler(apiClient, _config) {
161
155
  {
162
156
  id: "check_deps",
163
157
  label: "Check the open source libraries you depend on",
164
- tool: "zivis_check_repo_trust",
165
- why: "Free and fast catches risky AI SDKs and known-bad dependencies.",
158
+ tool: "cli_command",
159
+ args_hint: { command: "zivis test dependencies" },
160
+ why: "Runs the ZIVIS dependency-risk methodology against your resolved lockfile — reachability triage, not just a CVE list.",
166
161
  requires_tier: "free",
167
162
  },
168
163
  ], `Project is correctly connected to ${binding.orgName ?? "your ZIVIS org"}.`);
@@ -54,7 +54,7 @@ export declare function createRunReportHandler(apiClient: ApiClient): (params: R
54
54
  }[];
55
55
  }>;
56
56
  export declare const RUN_COMPLETE_NAME = "zivis_run_complete";
57
- export declare const RUN_COMPLETE_DESCRIPTION = "Complete a pending run with its final result envelope (ZIV-219 + ZIV-32) \u2014 the run's immutable close-out.\n\n`result` is the SAME envelope shape `zivis run complete --input` accepts: { summary?, coverage[]?, findings[]?, retests[]?, evidence[]?, observations[]?, generatedArtifacts?, risk_candidates? } \u2014 validated server-side, not here. It may safely repeat items already posted via zivis_run_report; the shared fingerprint/content-hash/observation-key upserts converge instead of duplicating. The response reports what was created/updated/matched, plus (on a fresh completion) the Application's Trust Room link, when one is provisioned.";
57
+ export declare const RUN_COMPLETE_DESCRIPTION = "Complete a pending run with its final result envelope (ZIV-219 + ZIV-32) \u2014 the run's immutable close-out.\n\n`result` is the SAME envelope shape `zivis run complete --input` accepts: { summary?, coverage[]?, findings[]?, retests[]?, evidence[]?, observations[]?, generatedArtifacts?, risk_candidates? } \u2014 validated server-side, not here. It may safely repeat items already posted via zivis_run_report; the shared fingerprint/content-hash/observation-key upserts converge instead of duplicating. The response reports what was created/updated/matched, plus the Application's stable Assurance Overview URL (`applicationAssuranceUrl`).";
58
58
  export declare const RUN_COMPLETE_SCHEMA: {
59
59
  run_id: z.ZodString;
60
60
  result: z.ZodRecord<z.ZodString, z.ZodUnknown>;
@@ -124,7 +124,7 @@ export function createRunReportHandler(apiClient) {
124
124
  export const RUN_COMPLETE_NAME = "zivis_run_complete";
125
125
  export const RUN_COMPLETE_DESCRIPTION = `Complete a pending run with its final result envelope (ZIV-219 + ZIV-32) — the run's immutable close-out.
126
126
 
127
- \`result\` is the SAME envelope shape \`zivis run complete --input\` accepts: { summary?, coverage[]?, findings[]?, retests[]?, evidence[]?, observations[]?, generatedArtifacts?, risk_candidates? } — validated server-side, not here. It may safely repeat items already posted via zivis_run_report; the shared fingerprint/content-hash/observation-key upserts converge instead of duplicating. The response reports what was created/updated/matched, plus (on a fresh completion) the Application's Trust Room link, when one is provisioned.`;
127
+ \`result\` is the SAME envelope shape \`zivis run complete --input\` accepts: { summary?, coverage[]?, findings[]?, retests[]?, evidence[]?, observations[]?, generatedArtifacts?, risk_candidates? } — validated server-side, not here. It may safely repeat items already posted via zivis_run_report; the shared fingerprint/content-hash/observation-key upserts converge instead of duplicating. The response reports what was created/updated/matched, plus the Application's stable Assurance Overview URL (\`applicationAssuranceUrl\`).`;
128
128
  export const RUN_COMPLETE_SCHEMA = {
129
129
  run_id: z.string().min(1).describe("The runId returned by zivis_run_start. Must be pending — completing an already-completed run replays idempotently."),
130
130
  result: z
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export declare const DISCOVER_LOCAL_INFRA_NAME = "zivis_discover_local_infra";
3
- export declare const DISCOVER_LOCAL_INFRA_DESCRIPTION = "Discover AI infrastructure in a local project directory by reading Docker, compose, and dependency files.\n\nAnalyzes the project to understand what services are defined, which are running, and what AI/LLM frameworks are in use. Returns structured recommendations for red team testing via zivis_local_scan.\n\nWhat it reads:\n- docker-compose.yml / docker-compose.yaml / compose.yml / compose.yaml\n- Dockerfile(s) in the project root and subdirectories\n- .env files (structure only \u2014 values are redacted for safety)\n- package.json, requirements.txt, pyproject.toml, go.mod (to detect AI frameworks)\n\nWhat it checks:\n- Docker daemon status\n- Currently running containers (docker compose ps)\n- Port mappings and exposed services\n\nWhat it returns:\n- List of services with ports and images\n- Running vs stopped status per service\n- Detected AI/LLM patterns (OpenAI, Anthropic, LangChain, etc.)\n- Suggested target_url, chat_endpoint, target_type for zivis_local_scan\n- Whether the zivis-local-runner Docker image is available\n\nThis tool makes NO network requests and requires NO authentication. It only reads local files and queries the Docker daemon.";
3
+ export declare const DISCOVER_LOCAL_INFRA_DESCRIPTION = "Discover AI infrastructure in a local project directory by reading Docker, compose, and dependency files.\n\nAnalyzes the project to understand what services are defined, which are running, and what AI/LLM frameworks are in use. Returns a suggested target shape (URL, chat endpoint) for the coding agent to use as grounding context before running `zivis test ai`.\n\nWhat it reads:\n- docker-compose.yml / docker-compose.yaml / compose.yml / compose.yaml\n- Dockerfile(s) in the project root and subdirectories\n- .env files (structure only \u2014 values are redacted for safety)\n- package.json, requirements.txt, pyproject.toml, go.mod (to detect AI frameworks)\n\nWhat it checks:\n- Docker daemon status\n- Currently running containers (docker compose ps)\n- Port mappings and exposed services\n\nWhat it returns:\n- List of services with ports and images\n- Running vs stopped status per service\n- Detected AI/LLM patterns (OpenAI, Anthropic, LangChain, etc.)\n- Suggested target_url/target_type/chat_endpoint \u2014 informational context, not a tool call to make\n- Whether the zivis-local-runner Docker image is available\n\nFor actually testing the discovered AI system, run `zivis test ai` in the terminal \u2014 this tool only maps what's running locally.\n\nThis tool makes NO network requests and requires NO authentication. It only reads local files and queries the Docker daemon.";
4
4
  export declare const DISCOVER_LOCAL_INFRA_SCHEMA: {
5
5
  project_dir: z.ZodOptional<z.ZodString>;
6
6
  };
@@ -7,7 +7,7 @@ const execAsync = promisify(exec);
7
7
  export const DISCOVER_LOCAL_INFRA_NAME = "zivis_discover_local_infra";
8
8
  export const DISCOVER_LOCAL_INFRA_DESCRIPTION = `Discover AI infrastructure in a local project directory by reading Docker, compose, and dependency files.
9
9
 
10
- Analyzes the project to understand what services are defined, which are running, and what AI/LLM frameworks are in use. Returns structured recommendations for red team testing via zivis_local_scan.
10
+ Analyzes the project to understand what services are defined, which are running, and what AI/LLM frameworks are in use. Returns a suggested target shape (URL, chat endpoint) for the coding agent to use as grounding context before running \`zivis test ai\`.
11
11
 
12
12
  What it reads:
13
13
  - docker-compose.yml / docker-compose.yaml / compose.yml / compose.yaml
@@ -24,9 +24,11 @@ What it returns:
24
24
  - List of services with ports and images
25
25
  - Running vs stopped status per service
26
26
  - Detected AI/LLM patterns (OpenAI, Anthropic, LangChain, etc.)
27
- - Suggested target_url, chat_endpoint, target_type for zivis_local_scan
27
+ - Suggested target_url/target_type/chat_endpoint — informational context, not a tool call to make
28
28
  - Whether the zivis-local-runner Docker image is available
29
29
 
30
+ For actually testing the discovered AI system, run \`zivis test ai\` in the terminal — this tool only maps what's running locally.
31
+
30
32
  This tool makes NO network requests and requires NO authentication. It only reads local files and queries the Docker daemon.`;
31
33
  export const DISCOVER_LOCAL_INFRA_SCHEMA = {
32
34
  project_dir: z
@@ -127,7 +129,7 @@ export function createDiscoverLocalInfraHandler() {
127
129
  aiFrameworks: { detected: [], envHints: [] },
128
130
  runnerImage: { available: false },
129
131
  suggestion: null,
130
- localScanCommand: null,
132
+ nextStep: null,
131
133
  };
132
134
  try {
133
135
  const { stdout } = await execAsync("docker version --format '{{.Server.Version}}'", {
@@ -344,19 +346,10 @@ export function createDiscoverLocalInfraHandler() {
344
346
  }
345
347
  result.suggestion = generateSuggestion(result.compose.services, result.aiFrameworks.detected, result.aiFrameworks.envHints, result.dockerfiles);
346
348
  if (result.suggestion) {
347
- const parts = [`target_url="${result.suggestion.target_url}"`];
348
- parts.push(`target_type="${result.suggestion.target_type}"`);
349
- if (result.suggestion.chat_endpoint) {
350
- parts.push(`chat_endpoint="${result.suggestion.chat_endpoint}"`);
351
- }
352
- if (result.suggestion.request_template) {
353
- parts.push(`request_template='${result.suggestion.request_template}'`);
354
- }
355
- if (result.suggestion.response_path) {
356
- parts.push(`response_path="${result.suggestion.response_path}"`);
357
- }
358
- parts.push(`scan_depth="standard"`);
359
- result.localScanCommand = `zivis_local_scan(${parts.join(", ")})`;
349
+ result.nextStep =
350
+ `Detected ${result.suggestion.target_type} target at ${result.suggestion.target_url}` +
351
+ (result.suggestion.chat_endpoint ? ` (chat endpoint: ${result.suggestion.chat_endpoint})` : "") +
352
+ `. Run \`zivis test ai\` to test it.`;
360
353
  }
361
354
  return {
362
355
  content: [
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import type { ApiClient } from "../api-client.js";
3
3
  import type { ZivisConfig } from "../types.js";
4
4
  export declare const GET_STARTED_NAME = "zivis_get_started";
5
- export declare const GET_STARTED_DESCRIPTION = "Call this FIRST on any turn where the user asks about security, vulnerabilities, code review, deployment readiness, threat modeling, dependencies, or \"what should I do next.\" Idempotent and safe to call repeatedly \u2014 detects prior inspect runs and never re-inspects unnecessarily. Prefer this over calling zivis_inspect directly.\n\nUse this when the user says any of:\n- \"what should I do for security on this app\"\n- \"uhh, where do I start\"\n- \"is my app safe\" / \"is my chatbot safe\"\n- \"I need to do security stuff before launch\"\n- \"I have an audit / pen test / customer asking about security\"\n- \"check my dependencies / libraries / repos\"\n- \"how do I use ZIVIS\"\n- \"what can ZIVIS do for me\"\n\nThis tool inspects the current project state \u2014 binding, application, scans, findings \u2014\nAND if a recent 'zivis inspect' artifact exists for this repo, evaluates the local\nthreat library against it to surface architectural patterns ZIVIS thinks may apply\n(self-consistency without context isolation, late org filter, privilege separation, etc.).\nThe 'relevant_threats' field in the response is what makes this tool different from a\ngeneric security menu: ZIVIS is reading the user's actual codebase via the graph\nartifact and naming threats by their architectural shape.\n\nAlways speak to the user in threat-model language: components, flows, trust boundaries,\nattack scenarios. Even when they don't yet know those terms \u2014 that's how they learn.\n\nOutput is a structured JSON object. Present recommended_next_steps as a numbered list\nto the user, in order, and ask them to pick one. If relevant_threats is non-empty, lead\nwith that \u2014 it's the most concrete thing ZIVIS knows about their repo. Use the label\nand why fields verbatim; do not invent new steps. If the user is unsure which to pick,\nrecommend the item flagged in if_user_unsure.";
5
+ export declare const GET_STARTED_DESCRIPTION = "Call this FIRST on any turn where the user asks about security, vulnerabilities, code review, deployment readiness, threat modeling, dependencies, or \"what should I do next.\" Idempotent and safe to call repeatedly \u2014 detects prior inspect runs and never re-inspects unnecessarily. Prefer this over calling zivis_inspect directly.\n\nUse this when the user says any of:\n- \"what should I do for security on this app\"\n- \"uhh, where do I start\"\n- \"is my app safe\" / \"is my chatbot safe\"\n- \"I need to do security stuff before launch\"\n- \"I have an audit / pen test / customer asking about security\"\n- \"check my dependencies / libraries / repos\"\n- \"how do I use ZIVIS\"\n- \"what can ZIVIS do for me\"\n\nThis tool inspects the current project state \u2014 binding, application, scans, findings \u2014\nAND if a recent 'zivis inspect' artifact exists for this repo, evaluates the local\nthreat library against it to surface architectural patterns ZIVIS thinks may apply\n(self-consistency without context isolation, late org filter, privilege separation, etc.).\nThe 'relevant_threats' field in the response is what makes this tool different from a\ngeneric security menu: ZIVIS is reading the user's actual codebase via the graph\nartifact and naming threats by their architectural shape.\n\nSpeak in plain developer language by default \u2014 do not introduce jargon (actor, STRIDE,\nkill chain, TTPs) unless the user already used it. Naming the actual product concepts\n(\"your threat model\", `zivis threatmodel`) is fine when they are the literal thing being\ndiscussed; that is a product noun, not jargon to avoid.\n\nOutput is a structured JSON object. Present recommended_next_steps as a numbered list\nto the user, in order, and ask them to pick one. If relevant_threats is non-empty, lead\nwith that \u2014 it's the most concrete thing ZIVIS knows about their repo. Use the label\nand why fields verbatim; do not invent new steps. If the user is unsure which to pick,\nrecommend the item flagged in if_user_unsure.";
6
6
  export declare const GET_STARTED_SCHEMA: {
7
7
  concern: z.ZodOptional<z.ZodString>;
8
8
  cwd: z.ZodOptional<z.ZodString>;