@agentwares/agentguard 0.1.5 → 0.1.6

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/dist/index.js CHANGED
@@ -845,6 +845,7 @@ var ProxyRuntime = class {
845
845
  guard;
846
846
  agent;
847
847
  defaultRunId;
848
+ configured;
848
849
  sessions = /* @__PURE__ */ new Set();
849
850
  connections = /* @__PURE__ */ new Map();
850
851
  exposed = [];
@@ -861,6 +862,7 @@ var ProxyRuntime = class {
861
862
  this.guard = opts.guard;
862
863
  this.agent = opts.agent;
863
864
  this.defaultRunId = opts.defaultRunId;
865
+ this.configured = opts.configured ?? true;
864
866
  this.log = opts.log ?? ((line) => process.stderr.write(`[agentguard] ${line}
865
867
  `));
866
868
  this.connectTimeoutMs = opts.connectTimeoutMs ?? 15e3;
@@ -1328,251 +1330,24 @@ async function withTimeout(promise, ms, what) {
1328
1330
  }
1329
1331
 
1330
1332
  // src/proxy/server.ts
1331
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1333
+ import { McpServer as McpServer3 } from "@modelcontextprotocol/sdk/server/mcp.js";
1332
1334
  import {
1333
- CallToolRequestSchema,
1335
+ CallToolRequestSchema as CallToolRequestSchema2,
1334
1336
  CompleteRequestSchema,
1335
1337
  GetPromptRequestSchema,
1336
1338
  ListPromptsRequestSchema,
1337
1339
  ListResourceTemplatesRequestSchema,
1338
1340
  ListResourcesRequestSchema,
1339
- ListToolsRequestSchema,
1341
+ ListToolsRequestSchema as ListToolsRequestSchema2,
1340
1342
  ReadResourceRequestSchema,
1341
1343
  SetLevelRequestSchema,
1342
- SubscribeRequestSchema,
1343
- UnsubscribeRequestSchema
1344
+ SubscribeRequestSchema as SubscribeRequestSchema2,
1345
+ UnsubscribeRequestSchema as UnsubscribeRequestSchema2
1344
1346
  } from "@modelcontextprotocol/sdk/types.js";
1345
1347
  import {
1346
1348
  keyFromHeaders,
1347
1349
  resolveAgent
1348
1350
  } from "@agentwares/agentguard-core";
1349
- var PROXY_VERSION = version();
1350
- function headerValue(extra, name) {
1351
- const raw = extra.requestInfo?.headers?.[name] ?? extra.requestInfo?.headers?.[name.toLowerCase()];
1352
- if (Array.isArray(raw)) return raw[0];
1353
- return typeof raw === "string" ? raw : void 0;
1354
- }
1355
- function headersOf(extra) {
1356
- const out = {};
1357
- for (const [k, v] of Object.entries(extra.requestInfo?.headers ?? {}))
1358
- out[k.toLowerCase()] = Array.isArray(v) ? v[0] : v;
1359
- return out;
1360
- }
1361
- function runIdFor(extra, meta, fallback) {
1362
- const header = headerValue(extra, "x-run-id");
1363
- if (header && header.trim()) return header.trim().slice(0, 128);
1364
- const fromMeta = meta?.runId ?? meta?.["agentguard/runId"] ?? meta?.run_id;
1365
- if (typeof fromMeta === "string" && fromMeta.trim()) return fromMeta.trim().slice(0, 128);
1366
- if (extra.sessionId) return `sess_${extra.sessionId.slice(0, 12)}`;
1367
- return fallback;
1368
- }
1369
- function toCallToolResult(result, tool) {
1370
- const meta = {
1371
- agentguard: {
1372
- class: result.decision.classification.class,
1373
- verb: result.decision.classification.verb,
1374
- mode: result.decision.mode,
1375
- outcome: result.outcome,
1376
- dryRun: result.faked,
1377
- seq: result.entry.seq,
1378
- run_id: result.entry.run_id
1379
- }
1380
- };
1381
- const hasOutputSchema = Boolean(tool?.tool.outputSchema);
1382
- if (result.error) {
1383
- const upstream2 = result.value;
1384
- if (upstream2 && upstream2.content) {
1385
- return { ...upstream2, isError: true, _meta: { ...upstream2._meta ?? {}, ...meta } };
1386
- }
1387
- const body = result.error;
1388
- return {
1389
- isError: true,
1390
- content: [{ type: "text", text: JSON.stringify(body) }],
1391
- ...hasOutputSchema ? {} : { structuredContent: body },
1392
- _meta: { ...meta, httpStatus: httpStatusFor(body.code) }
1393
- };
1394
- }
1395
- if (result.faked) {
1396
- const value = result.value;
1397
- const isObject = value !== null && typeof value === "object" && !Array.isArray(value);
1398
- return {
1399
- content: [
1400
- { type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
1401
- ],
1402
- ...isObject ? { structuredContent: value } : {},
1403
- _meta: meta
1404
- };
1405
- }
1406
- const upstream = result.value;
1407
- return { ...upstream, _meta: { ...upstream._meta ?? {}, ...meta } };
1408
- }
1409
- function httpStatusFor(code) {
1410
- switch (code) {
1411
- case "KILLED":
1412
- case "TOOL_DENIED":
1413
- case "APPROVAL_DENIED":
1414
- return 403;
1415
- case "APPROVAL_REQUIRED":
1416
- return 202;
1417
- case "CAP_EXCEEDED":
1418
- case "LOOP_DETECTED":
1419
- return 429;
1420
- case "UNKNOWN_TOOL":
1421
- return 404;
1422
- case "UPSTREAM_ERROR":
1423
- return 502;
1424
- default:
1425
- return 500;
1426
- }
1427
- }
1428
- function annotateTool(t, mode) {
1429
- return {
1430
- ...t.tool,
1431
- name: t.name,
1432
- _meta: {
1433
- ...t.tool._meta ?? {},
1434
- agentguard: {
1435
- upstream: t.upstream,
1436
- class: t.classification.class,
1437
- verb: t.classification.verb,
1438
- mode
1439
- }
1440
- }
1441
- };
1442
- }
1443
- function killedError(state) {
1444
- return Object.assign(
1445
- new Error(`agentguard kill switch is on (${state.source ?? "?"}: ${state.reason ?? ""})`),
1446
- { code: -32e3 }
1447
- );
1448
- }
1449
- function createDownstreamServer(runtime, opts = {}) {
1450
- const server = new McpServer(
1451
- { name: "agentguard", version: PROXY_VERSION, title: "agentguard policy proxy" },
1452
- {
1453
- capabilities: {
1454
- tools: { listChanged: true },
1455
- resources: { subscribe: true, listChanged: true },
1456
- prompts: { listChanged: true },
1457
- logging: {},
1458
- completions: {}
1459
- },
1460
- instructions: `Tool calls pass through agentguard (${runtime.policy.mode} mode). Errors carry { code, cause, fix, retryable }; follow "fix". CAP_EXCEEDED, LOOP_DETECTED and KILLED are not retryable: stop and report to the user. APPROVAL_REQUIRED: relay the approval command to the user, then retry the identical call once.`
1461
- }
1462
- );
1463
- const session = {
1464
- id: crypto.randomUUID(),
1465
- server,
1466
- lastActive: Date.now(),
1467
- inflight: /* @__PURE__ */ new Map()
1468
- };
1469
- runtime.attach(session);
1470
- const previousOnClose = server.server.onclose;
1471
- server.server.onclose = () => {
1472
- previousOnClose?.();
1473
- runtime.detach(session);
1474
- };
1475
- const touch = () => {
1476
- session.lastActive = Date.now();
1477
- };
1478
- const agentFor = async (extra) => {
1479
- const key = keyFromHeaders(headersOf(extra));
1480
- if (!key) return { agent: void 0 };
1481
- const resolved = await resolveAgent(runtime.policy, { key });
1482
- if (resolved.error) {
1483
- return {
1484
- agent: null,
1485
- error: {
1486
- code: "TOOL_DENIED",
1487
- cause: "the agent key in Authorization / X-Agentguard-Key is not in agentguard.yaml",
1488
- fix: "use a key created with `agentguard key create <agent>`, or omit the key to use the global policy",
1489
- retryable: false
1490
- }
1491
- };
1492
- }
1493
- return { agent: resolved.agent };
1494
- };
1495
- const low = server.server;
1496
- low.setRequestHandler(ListToolsRequestSchema, async (_req, extra) => {
1497
- touch();
1498
- const { agent } = await agentFor(extra);
1499
- const scope = agent === void 0 ? runtime.agent : agent;
1500
- return {
1501
- tools: runtime.listTools(scope).map((t) => annotateTool(t, scope?.mode ?? runtime.policy.mode))
1502
- };
1503
- });
1504
- low.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
1505
- touch();
1506
- const { agent, error } = await agentFor(extra);
1507
- const runId = runIdFor(extra, req.params._meta, opts.defaultRunId ?? runtime.defaultRunId);
1508
- if (error) {
1509
- return {
1510
- isError: true,
1511
- content: [{ type: "text", text: JSON.stringify(error) }],
1512
- structuredContent: error,
1513
- _meta: { httpStatus: 401 }
1514
- };
1515
- }
1516
- const progressToken = req.params._meta?.progressToken;
1517
- const { result, tool } = await runtime.callTool(req.params.name, req.params.arguments ?? {}, {
1518
- runId,
1519
- agent,
1520
- sessionId: extra.sessionId ?? session.id,
1521
- session,
1522
- requestId: extra.requestId,
1523
- onprogress: progressToken === void 0 ? void 0 : (p) => {
1524
- void extra.sendNotification({
1525
- method: "notifications/progress",
1526
- params: { progressToken, ...p }
1527
- });
1528
- }
1529
- });
1530
- return toCallToolResult(result, tool);
1531
- });
1532
- const guardRead = async () => {
1533
- const state = await runtime.guard.killState();
1534
- if (state.killed) throw killedError(state);
1535
- };
1536
- low.setRequestHandler(ListResourcesRequestSchema, async () => {
1537
- touch();
1538
- return runtime.listResources();
1539
- });
1540
- low.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
1541
- touch();
1542
- return runtime.listResourceTemplates();
1543
- });
1544
- low.setRequestHandler(ReadResourceRequestSchema, async (req) => {
1545
- touch();
1546
- await guardRead();
1547
- return runtime.readResource(req.params.uri);
1548
- });
1549
- low.setRequestHandler(SubscribeRequestSchema, async (req) => {
1550
- await runtime.subscribe(req.params.uri, true);
1551
- return {};
1552
- });
1553
- low.setRequestHandler(UnsubscribeRequestSchema, async (req) => {
1554
- await runtime.subscribe(req.params.uri, false);
1555
- return {};
1556
- });
1557
- low.setRequestHandler(ListPromptsRequestSchema, async () => {
1558
- touch();
1559
- return runtime.listPrompts();
1560
- });
1561
- low.setRequestHandler(GetPromptRequestSchema, async (req) => {
1562
- touch();
1563
- await guardRead();
1564
- return runtime.getPrompt(req.params.name, req.params.arguments);
1565
- });
1566
- low.setRequestHandler(CompleteRequestSchema, async (req) => {
1567
- touch();
1568
- return runtime.complete(req.params);
1569
- });
1570
- low.setRequestHandler(SetLevelRequestSchema, async (req) => {
1571
- await runtime.setLevel(req.params.level);
1572
- return {};
1573
- });
1574
- return server;
1575
- }
1576
1351
 
1577
1352
  // ../../packages/mcp-kit/dist/chunk-7LRFDQNC.js
1578
1353
  import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
@@ -1580,8 +1355,8 @@ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
1580
1355
  import {
1581
1356
  CreateMessageResultSchema,
1582
1357
  ElicitResultSchema,
1583
- SubscribeRequestSchema as SubscribeRequestSchema2,
1584
- UnsubscribeRequestSchema as UnsubscribeRequestSchema2
1358
+ SubscribeRequestSchema,
1359
+ UnsubscribeRequestSchema
1585
1360
  } from "@modelcontextprotocol/sdk/types.js";
1586
1361
  import { z } from "zod";
1587
1362
 
@@ -1691,11 +1466,11 @@ var StubVerifier = class {
1691
1466
  // ../../packages/mcp-kit/dist/chunk-7LRFDQNC.js
1692
1467
  import { z as z2 } from "zod";
1693
1468
  import { z as z3 } from "zod";
1694
- import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
1469
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1695
1470
  import {
1696
- CallToolRequestSchema as CallToolRequestSchema2,
1471
+ CallToolRequestSchema,
1697
1472
  ErrorCode,
1698
- ListToolsRequestSchema as ListToolsRequestSchema2,
1473
+ ListToolsRequestSchema,
1699
1474
  McpError
1700
1475
  } from "@modelcontextprotocol/sdk/types.js";
1701
1476
  import { z as z4 } from "zod";
@@ -1798,6 +1573,10 @@ function errorResult(err, opts = {}) {
1798
1573
  _meta: meta
1799
1574
  };
1800
1575
  }
1576
+ function createToolContext(init = {}) {
1577
+ const { headers, ...rest } = init;
1578
+ return { headers: headers ?? new Headers(), ...rest };
1579
+ }
1801
1580
  var TOOL_NAME_PATTERN = /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/;
1802
1581
  var MIN_NAMESPACE_LENGTH = 3;
1803
1582
  var MIN_DESCRIPTION_LENGTH = 20;
@@ -1824,7 +1603,7 @@ function isCallToolResult(value) {
1824
1603
  (block) => isPlainObject(block) && typeof block.type === "string"
1825
1604
  );
1826
1605
  }
1827
- function toCallToolResult2(value) {
1606
+ function toCallToolResult(value) {
1828
1607
  if (isCallToolResult(value)) return value;
1829
1608
  const text = typeof value === "string" ? value : JSON.stringify(value ?? null, null, 2);
1830
1609
  const result = { content: [{ type: "text", text }] };
@@ -1890,9 +1669,9 @@ function defineTool(opts) {
1890
1669
  details: { issues: checked.error.issues }
1891
1670
  });
1892
1671
  }
1893
- return toCallToolResult2(checked.data);
1672
+ return toCallToolResult(checked.data);
1894
1673
  }
1895
- return toCallToolResult2(value);
1674
+ return toCallToolResult(value);
1896
1675
  } catch (err) {
1897
1676
  return fail2(err);
1898
1677
  }
@@ -2279,7 +2058,7 @@ function createMcpServer(opts) {
2279
2058
  if (byName.has(tool.name)) throw new Error(`duplicate tool name: ${tool.name}`);
2280
2059
  byName.set(tool.name, tool);
2281
2060
  }
2282
- const server = new McpServer2(
2061
+ const server = new McpServer(
2283
2062
  { name: opts.name, version: opts.version, title: opts.title, websiteUrl: opts.websiteUrl },
2284
2063
  { instructions: opts.instructions, capabilities: { logging: {}, ...opts.capabilities } }
2285
2064
  );
@@ -2300,7 +2079,7 @@ function createMcpServer(opts) {
2300
2079
  );
2301
2080
  }
2302
2081
  if (byName.size > 0) {
2303
- server.server.setRequestHandler(ListToolsRequestSchema2, async () => {
2082
+ server.server.setRequestHandler(ListToolsRequestSchema, async () => {
2304
2083
  const enabled = [...byName.values()].filter(
2305
2084
  (tool) => registered.get(tool.name)?.enabled !== false
2306
2085
  );
@@ -2312,7 +2091,7 @@ function createMcpServer(opts) {
2312
2091
  }))
2313
2092
  };
2314
2093
  });
2315
- server.server.setRequestHandler(CallToolRequestSchema2, async (request, extra) => {
2094
+ server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
2316
2095
  const name = request.params.name;
2317
2096
  const tool = byName.get(name);
2318
2097
  const entry = registered.get(name);
@@ -2514,152 +2293,667 @@ function createStatefulHandler({ factory, cors, transportBase }, options) {
2514
2293
  previousOnClose?.();
2515
2294
  if (transport.sessionId) sessions.delete(transport.sessionId);
2516
2295
  };
2517
- const response = withCors(await transport.handleRequest(request), cors);
2518
- if (!transport.sessionId) server.close().catch(() => void 0);
2519
- return response;
2520
- } catch (err) {
2521
- server.close().catch(() => void 0);
2522
- return errorResponse(err, cors);
2296
+ const response = withCors(await transport.handleRequest(request), cors);
2297
+ if (!transport.sessionId) server.close().catch(() => void 0);
2298
+ return response;
2299
+ } catch (err) {
2300
+ server.close().catch(() => void 0);
2301
+ return errorResponse(err, cors);
2302
+ }
2303
+ };
2304
+ }
2305
+ function createHttpHandler(opts, transportOptions = {}) {
2306
+ const { factory, transport: t } = splitOptions(opts, transportOptions);
2307
+ const cors = corsHeaders(t.cors);
2308
+ const sessions = t.sessions ? typeof t.sessions === "object" ? t.sessions : {} : void 0;
2309
+ const context = {
2310
+ factory,
2311
+ cors,
2312
+ transportBase: {
2313
+ enableJsonResponse: t.jsonResponse ?? sessions === void 0,
2314
+ allowedHosts: t.allowedHosts,
2315
+ allowedOrigins: t.allowedOrigins,
2316
+ enableDnsRebindingProtection: t.enableDnsRebindingProtection
2317
+ }
2318
+ };
2319
+ const handler = sessions ? createStatefulHandler(context, sessions) : createStatelessHandler(context);
2320
+ const allow = sessions ? "GET, POST, DELETE, OPTIONS" : "POST, OPTIONS";
2321
+ return async (request) => {
2322
+ if (request.method === "OPTIONS") {
2323
+ return new Response(null, { status: 204, headers: { Allow: allow, ...cors } });
2324
+ }
2325
+ return handler(request);
2326
+ };
2327
+ }
2328
+ function localhostHosts(port) {
2329
+ const hosts = ["localhost", "127.0.0.1", "[::1]"];
2330
+ return [...hosts, ...hosts.map((h) => `${h}:${port}`)];
2331
+ }
2332
+ function handleHealth(info) {
2333
+ const tools = typeof info.tools === "number" ? info.tools : info.tools.length;
2334
+ return jsonResponse({ ok: true, name: info.name, version: info.version, tools }, 200);
2335
+ }
2336
+ async function serveNodeHttp(opts) {
2337
+ const http = await import("http");
2338
+ const host = opts.host ?? "127.0.0.1";
2339
+ const path2 = opts.path ?? "/mcp";
2340
+ const server = http.createServer((req, res) => {
2341
+ void (async () => {
2342
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? host}`);
2343
+ let response;
2344
+ if (url.pathname === path2) {
2345
+ const controller = new AbortController();
2346
+ res.on("close", () => {
2347
+ if (!res.writableFinished) controller.abort();
2348
+ });
2349
+ const headers = new Headers();
2350
+ for (const [key, value] of Object.entries(req.headers)) {
2351
+ if (Array.isArray(value)) for (const item of value) headers.append(key, item);
2352
+ else if (typeof value === "string") headers.set(key, value);
2353
+ }
2354
+ const method = req.method ?? "GET";
2355
+ let body;
2356
+ if (method !== "GET" && method !== "HEAD") {
2357
+ const chunks = [];
2358
+ for await (const chunk of req) chunks.push(chunk);
2359
+ body = new Uint8Array(Buffer.concat(chunks));
2360
+ }
2361
+ response = await opts.handler(
2362
+ new Request(url, { method, headers, body, signal: controller.signal })
2363
+ );
2364
+ } else if (url.pathname === "/health" && opts.health) {
2365
+ response = opts.health();
2366
+ } else {
2367
+ response = new Response(
2368
+ JSON.stringify({
2369
+ code: "NOT_FOUND",
2370
+ cause: `no route for ${url.pathname}`,
2371
+ fix: `POST JSON-RPC to ${path2}`,
2372
+ retryable: false
2373
+ }),
2374
+ { status: 404, headers: { "Content-Type": "application/json" } }
2375
+ );
2376
+ }
2377
+ const outHeaders = {};
2378
+ response.headers.forEach((value, key) => {
2379
+ outHeaders[key] = value;
2380
+ });
2381
+ res.writeHead(response.status, outHeaders);
2382
+ if (!response.body) {
2383
+ res.end();
2384
+ return;
2385
+ }
2386
+ const reader = response.body.getReader();
2387
+ try {
2388
+ for (; ; ) {
2389
+ const { done, value } = await reader.read();
2390
+ if (done) break;
2391
+ res.write(value);
2392
+ }
2393
+ } catch {
2394
+ }
2395
+ res.end();
2396
+ })().catch((err) => {
2397
+ if (!res.headersSent) res.writeHead(500, { "Content-Type": "application/json" });
2398
+ res.end(
2399
+ JSON.stringify({
2400
+ code: "INTERNAL",
2401
+ cause: err instanceof Error ? err.message : String(err),
2402
+ fix: "retry; if it persists, report the request id",
2403
+ retryable: false
2404
+ })
2405
+ );
2406
+ });
2407
+ });
2408
+ await new Promise((resolve5, reject) => {
2409
+ server.once("error", reject);
2410
+ server.listen(opts.port ?? 8765, host, () => resolve5());
2411
+ });
2412
+ const address = server.address();
2413
+ const port = typeof address === "object" && address ? address.port : opts.port ?? 8765;
2414
+ return {
2415
+ port,
2416
+ url: `http://${host}:${port}${path2}`,
2417
+ close: () => new Promise((resolve5, reject) => {
2418
+ server.closeAllConnections();
2419
+ server.close((err) => err ? reject(err) : resolve5());
2420
+ })
2421
+ };
2422
+ }
2423
+
2424
+ // ../../packages/mcp-kit/dist/index.js
2425
+ import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
2426
+ async function serveStdio(serverOrOpts) {
2427
+ const server = serverOrOpts instanceof McpServer2 ? serverOrOpts : createMcpServer(serverOrOpts);
2428
+ const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
2429
+ const transport = new StdioServerTransport();
2430
+ await server.connect(transport);
2431
+ await new Promise((resolve5) => {
2432
+ const previous = transport.onclose;
2433
+ transport.onclose = () => {
2434
+ previous?.();
2435
+ resolve5();
2436
+ };
2437
+ });
2438
+ }
2439
+
2440
+ // src/proxy/own-tools.ts
2441
+ import {
2442
+ buildReport,
2443
+ latestRunId,
2444
+ listRuns,
2445
+ parseAuditJsonl,
2446
+ renderReportMarkdown,
2447
+ verifyChain
2448
+ } from "@agentwares/agentguard-core";
2449
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
2450
+ import { z as z5 } from "zod";
2451
+ var INIT_COMMAND = "npx -y @agentwares/agentguard init";
2452
+ var READ_ONLY = {
2453
+ readOnlyHint: true,
2454
+ destructiveHint: false,
2455
+ idempotentHint: true,
2456
+ openWorldHint: false
2457
+ };
2458
+ var runIdInput = z5.string().min(1).max(128).optional().describe(
2459
+ "Run to read, as it appears in the audit log (for example run_20261007093015_ab12, or the X-Run-Id an HTTP client sent). Omit it for the most recent run."
2460
+ );
2461
+ var counters = z5.record(z5.string(), z5.number()).describe(
2462
+ "counter name (tool_calls, writes, deletes, emails, spend_usd or a custom one) \u2192 value"
2463
+ );
2464
+ function readAudit(path2) {
2465
+ if (!existsSync(path2)) return { exists: false, entries: [] };
2466
+ try {
2467
+ return { exists: true, entries: parseAuditJsonl(readFileSync2(path2, "utf8")) };
2468
+ } catch (err) {
2469
+ return { exists: true, entries: [], error: err instanceof Error ? err.message : String(err) };
2470
+ }
2471
+ }
2472
+ function unreadable(path2, error) {
2473
+ return toolError({
2474
+ code: "AUDIT_UNREADABLE",
2475
+ cause: `the audit log at ${path2} could not be read: ${error}`,
2476
+ fix: "call agentguard_verify_audit_log to see where it breaks; a hand-edited or truncated line has to be restored from a copy",
2477
+ retryable: false
2478
+ });
2479
+ }
2480
+ function numbers(caps) {
2481
+ const out = {};
2482
+ for (const [k, v] of Object.entries(caps)) if (typeof v === "number") out[k] = v;
2483
+ return out;
2484
+ }
2485
+ function nextStep(runtime) {
2486
+ if (!runtime.configured)
2487
+ return `agentguard is guarding nothing: no policy file at ${runtime.loaded.path}. Run \`${INIT_COMMAND}\` in the project directory: it writes agentguard.yaml in dry-run mode and routes the project's MCP servers through agentguard (backup kept, \`--undo\` restores). Then restart the MCP client.`;
2488
+ if (runtime.policy.upstreams.length === 0)
2489
+ return `${runtime.loaded.path} lists no upstream servers, so agentguard is guarding nothing. Add the MCP servers your agent uses under \`upstreams:\` in that file (it has commented examples), then restart the MCP client.`;
2490
+ return "agentguard is running; run your agent, then read agentguard_get_report.";
2491
+ }
2492
+ var StatusOutput = z5.object({
2493
+ configured: z5.boolean().describe(
2494
+ "true when a policy file was found and loaded; false means agentguard guards nothing"
2495
+ ),
2496
+ policy_path: z5.string().describe("absolute path of the policy file read, or looked for"),
2497
+ mode: z5.enum(["dry-run", "enforce"]).describe("dry-run fakes writes and spends and records them; enforce lets allowed calls run"),
2498
+ upstreams: z5.array(
2499
+ z5.object({
2500
+ name: z5.string().describe("upstream name from the policy"),
2501
+ connected: z5.boolean().describe("whether agentguard is connected to it"),
2502
+ tools: z5.number().int().describe("tools it exposes"),
2503
+ transport: z5.enum(["stdio", "http"]).describe("how agentguard reaches it"),
2504
+ error: z5.string().optional().describe("why it is not connected")
2505
+ })
2506
+ ).describe("the MCP servers this proxy fronts; empty means none are configured"),
2507
+ run_id: z5.string().nullable().describe(
2508
+ "run the usage is for: the requested run_id, else the latest run in the log, else null"
2509
+ ),
2510
+ caps: z5.object({ per_run: counters, per_day: counters }).describe("limits from the policy; a counter that is absent has no cap"),
2511
+ used: z5.object({ per_run: counters, per_day: counters }).describe("what that run, and today, have used against the caps"),
2512
+ remaining: z5.record(
2513
+ z5.string(),
2514
+ z5.object({ per_run: z5.number().optional(), per_day: z5.number().optional() })
2515
+ ).describe("budget left per capped counter, per run and per day"),
2516
+ kill_switch: z5.object({
2517
+ on: z5.boolean().describe("true halts every tool call with KILLED"),
2518
+ reason: z5.string().optional().describe("why it was pulled"),
2519
+ source: z5.string().optional().describe("file, env or memory")
2520
+ }).describe("the kill switch state"),
2521
+ pending_approvals: z5.number().int().describe("destructive calls waiting for a human decision"),
2522
+ audit_log: z5.object({
2523
+ path: z5.string().describe("absolute path of the hash-chained JSONL audit log"),
2524
+ entries: z5.number().int().describe("entries in it; 0 when it does not exist yet")
2525
+ }).describe("where every tool call is recorded"),
2526
+ next_step: z5.string().describe("the one thing to do next, with the command when there is one")
2527
+ });
2528
+ var ReportOutput = z5.object({
2529
+ configured: z5.boolean().describe("false when no policy file was found (agentguard guards nothing)"),
2530
+ audit_log_path: z5.string().describe("absolute path of the audit log the report was built from"),
2531
+ run_id: z5.string().nullable().describe("the run reported on; null when the log has no runs"),
2532
+ runs_in_log: z5.number().int().describe("how many runs the audit log holds"),
2533
+ tool_calls: z5.number().int().describe("tool calls in this run"),
2534
+ would_have: z5.object({
2535
+ creates: z5.number().int(),
2536
+ updates: z5.number().int(),
2537
+ deletes: z5.number().int(),
2538
+ sends: z5.number().int(),
2539
+ executes: z5.number().int(),
2540
+ spends: z5.number().int(),
2541
+ spend_usd: z5.number()
2542
+ }).describe("what dry-run mode faked: what the run would have done had the calls executed"),
2543
+ did: z5.object({
2544
+ writes: z5.number().int(),
2545
+ deletes: z5.number().int(),
2546
+ sends: z5.number().int(),
2547
+ spend_usd: z5.number()
2548
+ }).describe("what actually executed upstream"),
2549
+ halts: z5.array(
2550
+ z5.object({
2551
+ seq: z5.number().int().describe("audit entry number"),
2552
+ ts: z5.string().describe("ISO time of the call"),
2553
+ code: z5.string().describe("CAP_EXCEEDED, LOOP_DETECTED, KILLED, APPROVAL_REQUIRED, \u2026"),
2554
+ tool: z5.string().describe("the tool that was stopped"),
2555
+ cause: z5.string().describe("why")
2556
+ })
2557
+ ).describe("every call agentguard stopped, in order"),
2558
+ top_tools: z5.array(z5.object({ tool: z5.string(), calls: z5.number().int(), class: z5.string() })).describe("the most-called tools with their class (read, write, spend)"),
2559
+ markdown: z5.string().describe("the same report `agentguard report` prints, as Markdown")
2560
+ });
2561
+ var VerifyOutput = z5.object({
2562
+ configured: z5.boolean().describe("false when no policy file was found (agentguard guards nothing)"),
2563
+ path: z5.string().describe("absolute path of the audit log checked"),
2564
+ exists: z5.boolean().describe("false when no call has been recorded yet; an absent log verifies"),
2565
+ ok: z5.boolean().describe("true when every entry's hash and prev_hash recompute"),
2566
+ entries: z5.number().int().describe("entries checked"),
2567
+ head_hash: z5.string().nullable().describe(
2568
+ "hash of the last entry; record it with the count to detect entries cut from the end"
2569
+ ),
2570
+ broken_at: z5.number().int().nullable().describe("seq of the first entry that does not verify"),
2571
+ reason: z5.string().nullable().describe("why verification failed")
2572
+ });
2573
+ function createOwnTools(runtime) {
2574
+ const status = defineTool({
2575
+ name: "agentguard_get_status",
2576
+ title: "agentguard status",
2577
+ description: "Report what agentguard is guarding right now: the policy file it loaded (or that none was found), dry-run or enforce mode, each upstream MCP server and whether it connected, a run's cap usage and remaining budget, the kill switch, and pending approvals. Call it first to learn whether agentguard is set up; next_step says what to do when it is not. For what a run did, use agentguard_get_report; to check the log was not edited, agentguard_verify_audit_log. Reads local files only and changes nothing.",
2578
+ input: z5.strictObject({ run_id: runIdInput }),
2579
+ output: StatusOutput,
2580
+ annotations: { title: "agentguard status", ...READ_ONLY },
2581
+ async handler({ run_id }) {
2582
+ const { loaded, guard } = runtime;
2583
+ const audit = readAudit(loaded.auditPath);
2584
+ if (audit.error) throw unreadable(loaded.auditPath, audit.error);
2585
+ const runId = run_id ?? latestRunId(audit.entries) ?? null;
2586
+ const state = await guard.status(runId ?? "(none yet)");
2587
+ const pending = await guard.approvals.list("pending");
2588
+ return {
2589
+ configured: runtime.configured,
2590
+ policy_path: loaded.path,
2591
+ mode: runtime.policy.mode,
2592
+ upstreams: runtime.upstreamStatus(),
2593
+ run_id: runId,
2594
+ caps: { per_run: numbers(state.caps.per_run), per_day: numbers(state.caps.per_day) },
2595
+ used: state.usage,
2596
+ remaining: state.remaining,
2597
+ kill_switch: {
2598
+ on: state.killed.killed,
2599
+ ...state.killed.reason ? { reason: state.killed.reason } : {},
2600
+ ...state.killed.source ? { source: state.killed.source } : {}
2601
+ },
2602
+ pending_approvals: pending.length,
2603
+ audit_log: { path: loaded.auditPath, entries: audit.entries.length },
2604
+ next_step: nextStep(runtime)
2605
+ };
2606
+ }
2607
+ });
2608
+ const report = defineTool({
2609
+ name: "agentguard_get_report",
2610
+ title: "agentguard run report",
2611
+ description: "Summarize one run from agentguard's audit log: how many tool calls, what dry-run mode faked (records it would have created, updated or deleted, messages sent, dollars spent), what really ran upstream, and every call agentguard halted with its code and cause. Defaults to the most recent run. Use it after an agent run, before switching a policy from dry-run to enforce; for live caps and the kill switch use agentguard_get_status. Read-only: reads the audit file and does not record this call.",
2612
+ input: z5.strictObject({ run_id: runIdInput }),
2613
+ output: ReportOutput,
2614
+ annotations: { title: "agentguard run report", ...READ_ONLY },
2615
+ handler({ run_id }) {
2616
+ const { loaded } = runtime;
2617
+ const audit = readAudit(loaded.auditPath);
2618
+ if (audit.error) throw unreadable(loaded.auditPath, audit.error);
2619
+ const runs = listRuns(audit.entries);
2620
+ if (run_id && !runs.some((r) => r.runId === run_id)) {
2621
+ throw toolError({
2622
+ code: "NOT_FOUND",
2623
+ cause: `no run "${run_id}" in the audit log at ${loaded.auditPath}`,
2624
+ fix: `call again without run_id for the latest run${runs.length ? `; runs in the log: ${runs.slice(0, 5).map((r) => r.runId).join(", ")}` : "; the log has no runs yet"}`,
2625
+ retryable: false
2626
+ });
2627
+ }
2628
+ const runId = run_id ?? latestRunId(audit.entries) ?? null;
2629
+ const built = buildReport(audit.entries, { runId: runId ?? void 0 });
2630
+ const markdown = runId ? renderReportMarkdown(built) : `# agentguard report
2631
+
2632
+ No runs recorded in ${loaded.auditPath} yet. ${nextStep(runtime)}`;
2633
+ return {
2634
+ configured: runtime.configured,
2635
+ audit_log_path: loaded.auditPath,
2636
+ run_id: runId,
2637
+ runs_in_log: runs.length,
2638
+ tool_calls: runId ? audit.entries.filter((e) => e.run_id === runId).length : 0,
2639
+ would_have: {
2640
+ creates: built.wouldHave.creates,
2641
+ updates: built.wouldHave.updates,
2642
+ deletes: built.wouldHave.deletes,
2643
+ sends: built.wouldHave.sends,
2644
+ executes: built.wouldHave.executes,
2645
+ spends: built.wouldHave.spends,
2646
+ spend_usd: built.wouldHave.spendUsd
2647
+ },
2648
+ did: {
2649
+ writes: built.did.writes,
2650
+ deletes: built.did.deletes,
2651
+ sends: built.did.sends,
2652
+ spend_usd: built.did.spendUsd
2653
+ },
2654
+ halts: built.halts,
2655
+ top_tools: built.topTools,
2656
+ markdown
2657
+ };
2658
+ }
2659
+ });
2660
+ const verify = defineTool({
2661
+ name: "agentguard_verify_audit_log",
2662
+ title: "Verify agentguard audit log",
2663
+ description: "Recompute the hash chain of agentguard's audit log (JSONL, one entry per tool call) and say whether any entry was edited, removed from the middle or reordered, naming the first broken entry. Returns the entry count and head hash; record both elsewhere, because entries cut from the end of the file still verify. Use it before trusting agentguard_get_report. Takes no arguments: it checks the log the loaded policy names. Read-only; a log that does not exist yet verifies as empty.",
2664
+ input: z5.strictObject({}),
2665
+ output: VerifyOutput,
2666
+ annotations: { title: "Verify agentguard audit log", ...READ_ONLY },
2667
+ async handler() {
2668
+ const { loaded } = runtime;
2669
+ const audit = readAudit(loaded.auditPath);
2670
+ if (audit.error) {
2671
+ return {
2672
+ configured: runtime.configured,
2673
+ path: loaded.auditPath,
2674
+ exists: true,
2675
+ ok: false,
2676
+ entries: 0,
2677
+ head_hash: null,
2678
+ broken_at: null,
2679
+ reason: audit.error
2680
+ };
2681
+ }
2682
+ const result = await verifyChain(audit.entries);
2683
+ return {
2684
+ configured: runtime.configured,
2685
+ path: loaded.auditPath,
2686
+ exists: audit.exists,
2687
+ ok: result.ok,
2688
+ entries: result.entries,
2689
+ // an empty chain's head is the genesis hash, which is not an entry anyone can record
2690
+ head_hash: result.entries > 0 ? result.head ?? null : null,
2691
+ broken_at: result.brokenAt ?? null,
2692
+ reason: result.reason ?? null
2693
+ };
2694
+ }
2695
+ });
2696
+ return [status, report, verify];
2697
+ }
2698
+ function ownToolsActive(runtime) {
2699
+ return runtime.upstreamStatus().length === 0;
2700
+ }
2701
+ function ownToolList(tools) {
2702
+ return listToolManifest(tools);
2703
+ }
2704
+ async function callOwnTool(tools, name, args, requestId) {
2705
+ const tool = tools.find((t) => t.name === name);
2706
+ if (!tool) return void 0;
2707
+ return tool.invoke(args, createToolContext({ requestId }));
2708
+ }
2709
+
2710
+ // src/proxy/server.ts
2711
+ var PROXY_VERSION = version();
2712
+ function headerValue(extra, name) {
2713
+ const raw = extra.requestInfo?.headers?.[name] ?? extra.requestInfo?.headers?.[name.toLowerCase()];
2714
+ if (Array.isArray(raw)) return raw[0];
2715
+ return typeof raw === "string" ? raw : void 0;
2716
+ }
2717
+ function headersOf(extra) {
2718
+ const out = {};
2719
+ for (const [k, v] of Object.entries(extra.requestInfo?.headers ?? {}))
2720
+ out[k.toLowerCase()] = Array.isArray(v) ? v[0] : v;
2721
+ return out;
2722
+ }
2723
+ function runIdFor(extra, meta, fallback) {
2724
+ const header = headerValue(extra, "x-run-id");
2725
+ if (header && header.trim()) return header.trim().slice(0, 128);
2726
+ const fromMeta = meta?.runId ?? meta?.["agentguard/runId"] ?? meta?.run_id;
2727
+ if (typeof fromMeta === "string" && fromMeta.trim()) return fromMeta.trim().slice(0, 128);
2728
+ if (extra.sessionId) return `sess_${extra.sessionId.slice(0, 12)}`;
2729
+ return fallback;
2730
+ }
2731
+ function toCallToolResult2(result, tool) {
2732
+ const meta = {
2733
+ agentguard: {
2734
+ class: result.decision.classification.class,
2735
+ verb: result.decision.classification.verb,
2736
+ mode: result.decision.mode,
2737
+ outcome: result.outcome,
2738
+ dryRun: result.faked,
2739
+ seq: result.entry.seq,
2740
+ run_id: result.entry.run_id
2741
+ }
2742
+ };
2743
+ const hasOutputSchema = Boolean(tool?.tool.outputSchema);
2744
+ if (result.error) {
2745
+ const upstream2 = result.value;
2746
+ if (upstream2 && upstream2.content) {
2747
+ return { ...upstream2, isError: true, _meta: { ...upstream2._meta ?? {}, ...meta } };
2748
+ }
2749
+ const body = result.error;
2750
+ return {
2751
+ isError: true,
2752
+ content: [{ type: "text", text: JSON.stringify(body) }],
2753
+ ...hasOutputSchema ? {} : { structuredContent: body },
2754
+ _meta: { ...meta, httpStatus: httpStatusFor(body.code) }
2755
+ };
2756
+ }
2757
+ if (result.faked) {
2758
+ const value = result.value;
2759
+ const isObject = value !== null && typeof value === "object" && !Array.isArray(value);
2760
+ return {
2761
+ content: [
2762
+ { type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }
2763
+ ],
2764
+ ...isObject ? { structuredContent: value } : {},
2765
+ _meta: meta
2766
+ };
2767
+ }
2768
+ const upstream = result.value;
2769
+ return { ...upstream, _meta: { ...upstream._meta ?? {}, ...meta } };
2770
+ }
2771
+ function httpStatusFor(code) {
2772
+ switch (code) {
2773
+ case "KILLED":
2774
+ case "TOOL_DENIED":
2775
+ case "APPROVAL_DENIED":
2776
+ return 403;
2777
+ case "APPROVAL_REQUIRED":
2778
+ return 202;
2779
+ case "CAP_EXCEEDED":
2780
+ case "LOOP_DETECTED":
2781
+ return 429;
2782
+ case "UNKNOWN_TOOL":
2783
+ return 404;
2784
+ case "UPSTREAM_ERROR":
2785
+ return 502;
2786
+ default:
2787
+ return 500;
2788
+ }
2789
+ }
2790
+ function annotateTool(t, mode) {
2791
+ return {
2792
+ ...t.tool,
2793
+ name: t.name,
2794
+ _meta: {
2795
+ ...t.tool._meta ?? {},
2796
+ agentguard: {
2797
+ upstream: t.upstream,
2798
+ class: t.classification.class,
2799
+ verb: t.classification.verb,
2800
+ mode
2801
+ }
2802
+ }
2803
+ };
2804
+ }
2805
+ function killedError(state) {
2806
+ return Object.assign(
2807
+ new Error(`agentguard kill switch is on (${state.source ?? "?"}: ${state.reason ?? ""})`),
2808
+ { code: -32e3 }
2809
+ );
2810
+ }
2811
+ function serverInstructions(runtime) {
2812
+ if (ownToolsActive(runtime))
2813
+ return `agentguard is an MCP policy proxy, and this one fronts no upstream MCP servers${runtime.configured ? "" : ` (no policy file at ${runtime.loaded.path})`}, so it serves only its own read-only tools: agentguard_get_status (what it guards, caps, kill switch), agentguard_get_report (what a run did or would have done) and agentguard_verify_audit_log. To guard the user's MCP servers, run \`${INIT_COMMAND}\` in the project directory and restart the client.`;
2814
+ return `Tool calls pass through agentguard (${runtime.policy.mode} mode). Errors carry { code, cause, fix, retryable }; follow "fix". CAP_EXCEEDED, LOOP_DETECTED and KILLED are not retryable: stop and report to the user. APPROVAL_REQUIRED: relay the approval command to the user, then retry the identical call once.`;
2815
+ }
2816
+ function createDownstreamServer(runtime, opts = {}) {
2817
+ const server = new McpServer3(
2818
+ { name: "agentguard", version: PROXY_VERSION, title: "agentguard policy proxy" },
2819
+ {
2820
+ capabilities: {
2821
+ tools: { listChanged: true },
2822
+ resources: { subscribe: true, listChanged: true },
2823
+ prompts: { listChanged: true },
2824
+ logging: {},
2825
+ completions: {}
2826
+ },
2827
+ instructions: serverInstructions(runtime)
2828
+ }
2829
+ );
2830
+ const ownTools = createOwnTools(runtime);
2831
+ const session = {
2832
+ id: crypto.randomUUID(),
2833
+ server,
2834
+ lastActive: Date.now(),
2835
+ inflight: /* @__PURE__ */ new Map()
2836
+ };
2837
+ runtime.attach(session);
2838
+ const previousOnClose = server.server.onclose;
2839
+ server.server.onclose = () => {
2840
+ previousOnClose?.();
2841
+ runtime.detach(session);
2842
+ };
2843
+ const touch = () => {
2844
+ session.lastActive = Date.now();
2845
+ };
2846
+ const agentFor = async (extra) => {
2847
+ const key = keyFromHeaders(headersOf(extra));
2848
+ if (!key) return { agent: void 0 };
2849
+ const resolved = await resolveAgent(runtime.policy, { key });
2850
+ if (resolved.error) {
2851
+ return {
2852
+ agent: null,
2853
+ error: {
2854
+ code: "TOOL_DENIED",
2855
+ cause: "the agent key in Authorization / X-Agentguard-Key is not in agentguard.yaml",
2856
+ fix: "use a key created with `agentguard key create <agent>`, or omit the key to use the global policy",
2857
+ retryable: false
2858
+ }
2859
+ };
2523
2860
  }
2861
+ return { agent: resolved.agent };
2524
2862
  };
2525
- }
2526
- function createHttpHandler(opts, transportOptions = {}) {
2527
- const { factory, transport: t } = splitOptions(opts, transportOptions);
2528
- const cors = corsHeaders(t.cors);
2529
- const sessions = t.sessions ? typeof t.sessions === "object" ? t.sessions : {} : void 0;
2530
- const context = {
2531
- factory,
2532
- cors,
2533
- transportBase: {
2534
- enableJsonResponse: t.jsonResponse ?? sessions === void 0,
2535
- allowedHosts: t.allowedHosts,
2536
- allowedOrigins: t.allowedOrigins,
2537
- enableDnsRebindingProtection: t.enableDnsRebindingProtection
2863
+ const low = server.server;
2864
+ low.setRequestHandler(ListToolsRequestSchema2, async (_req, extra) => {
2865
+ touch();
2866
+ const { agent } = await agentFor(extra);
2867
+ const scope = agent === void 0 ? runtime.agent : agent;
2868
+ if (ownToolsActive(runtime)) return { tools: ownToolList(ownTools) };
2869
+ return {
2870
+ tools: runtime.listTools(scope).map((t) => annotateTool(t, scope?.mode ?? runtime.policy.mode))
2871
+ };
2872
+ });
2873
+ low.setRequestHandler(CallToolRequestSchema2, async (req, extra) => {
2874
+ touch();
2875
+ if (ownToolsActive(runtime)) {
2876
+ const own = await callOwnTool(
2877
+ ownTools,
2878
+ req.params.name,
2879
+ req.params.arguments ?? {},
2880
+ String(extra.requestId)
2881
+ );
2882
+ if (own) return own;
2538
2883
  }
2539
- };
2540
- const handler = sessions ? createStatefulHandler(context, sessions) : createStatelessHandler(context);
2541
- const allow = sessions ? "GET, POST, DELETE, OPTIONS" : "POST, OPTIONS";
2542
- return async (request) => {
2543
- if (request.method === "OPTIONS") {
2544
- return new Response(null, { status: 204, headers: { Allow: allow, ...cors } });
2884
+ const { agent, error } = await agentFor(extra);
2885
+ const runId = runIdFor(extra, req.params._meta, opts.defaultRunId ?? runtime.defaultRunId);
2886
+ if (error) {
2887
+ return {
2888
+ isError: true,
2889
+ content: [{ type: "text", text: JSON.stringify(error) }],
2890
+ structuredContent: error,
2891
+ _meta: { httpStatus: 401 }
2892
+ };
2545
2893
  }
2546
- return handler(request);
2547
- };
2548
- }
2549
- function localhostHosts(port) {
2550
- const hosts = ["localhost", "127.0.0.1", "[::1]"];
2551
- return [...hosts, ...hosts.map((h) => `${h}:${port}`)];
2552
- }
2553
- function handleHealth(info) {
2554
- const tools = typeof info.tools === "number" ? info.tools : info.tools.length;
2555
- return jsonResponse({ ok: true, name: info.name, version: info.version, tools }, 200);
2556
- }
2557
- async function serveNodeHttp(opts) {
2558
- const http = await import("http");
2559
- const host = opts.host ?? "127.0.0.1";
2560
- const path2 = opts.path ?? "/mcp";
2561
- const server = http.createServer((req, res) => {
2562
- void (async () => {
2563
- const url = new URL(req.url ?? "/", `http://${req.headers.host ?? host}`);
2564
- let response;
2565
- if (url.pathname === path2) {
2566
- const controller = new AbortController();
2567
- res.on("close", () => {
2568
- if (!res.writableFinished) controller.abort();
2894
+ const progressToken = req.params._meta?.progressToken;
2895
+ const { result, tool } = await runtime.callTool(req.params.name, req.params.arguments ?? {}, {
2896
+ runId,
2897
+ agent,
2898
+ sessionId: extra.sessionId ?? session.id,
2899
+ session,
2900
+ requestId: extra.requestId,
2901
+ onprogress: progressToken === void 0 ? void 0 : (p) => {
2902
+ void extra.sendNotification({
2903
+ method: "notifications/progress",
2904
+ params: { progressToken, ...p }
2569
2905
  });
2570
- const headers = new Headers();
2571
- for (const [key, value] of Object.entries(req.headers)) {
2572
- if (Array.isArray(value)) for (const item of value) headers.append(key, item);
2573
- else if (typeof value === "string") headers.set(key, value);
2574
- }
2575
- const method = req.method ?? "GET";
2576
- let body;
2577
- if (method !== "GET" && method !== "HEAD") {
2578
- const chunks = [];
2579
- for await (const chunk of req) chunks.push(chunk);
2580
- body = new Uint8Array(Buffer.concat(chunks));
2581
- }
2582
- response = await opts.handler(
2583
- new Request(url, { method, headers, body, signal: controller.signal })
2584
- );
2585
- } else if (url.pathname === "/health" && opts.health) {
2586
- response = opts.health();
2587
- } else {
2588
- response = new Response(
2589
- JSON.stringify({
2590
- code: "NOT_FOUND",
2591
- cause: `no route for ${url.pathname}`,
2592
- fix: `POST JSON-RPC to ${path2}`,
2593
- retryable: false
2594
- }),
2595
- { status: 404, headers: { "Content-Type": "application/json" } }
2596
- );
2597
- }
2598
- const outHeaders = {};
2599
- response.headers.forEach((value, key) => {
2600
- outHeaders[key] = value;
2601
- });
2602
- res.writeHead(response.status, outHeaders);
2603
- if (!response.body) {
2604
- res.end();
2605
- return;
2606
- }
2607
- const reader = response.body.getReader();
2608
- try {
2609
- for (; ; ) {
2610
- const { done, value } = await reader.read();
2611
- if (done) break;
2612
- res.write(value);
2613
- }
2614
- } catch {
2615
2906
  }
2616
- res.end();
2617
- })().catch((err) => {
2618
- if (!res.headersSent) res.writeHead(500, { "Content-Type": "application/json" });
2619
- res.end(
2620
- JSON.stringify({
2621
- code: "INTERNAL",
2622
- cause: err instanceof Error ? err.message : String(err),
2623
- fix: "retry; if it persists, report the request id",
2624
- retryable: false
2625
- })
2626
- );
2627
2907
  });
2908
+ return toCallToolResult2(result, tool);
2628
2909
  });
2629
- await new Promise((resolve5, reject) => {
2630
- server.once("error", reject);
2631
- server.listen(opts.port ?? 8765, host, () => resolve5());
2632
- });
2633
- const address = server.address();
2634
- const port = typeof address === "object" && address ? address.port : opts.port ?? 8765;
2635
- return {
2636
- port,
2637
- url: `http://${host}:${port}${path2}`,
2638
- close: () => new Promise((resolve5, reject) => {
2639
- server.closeAllConnections();
2640
- server.close((err) => err ? reject(err) : resolve5());
2641
- })
2910
+ const guardRead = async () => {
2911
+ const state = await runtime.guard.killState();
2912
+ if (state.killed) throw killedError(state);
2642
2913
  };
2643
- }
2644
-
2645
- // ../../packages/mcp-kit/dist/index.js
2646
- import { McpServer as McpServer3 } from "@modelcontextprotocol/sdk/server/mcp.js";
2647
- async function serveStdio(serverOrOpts) {
2648
- const server = serverOrOpts instanceof McpServer3 ? serverOrOpts : createMcpServer(serverOrOpts);
2649
- const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
2650
- const transport = new StdioServerTransport();
2651
- await server.connect(transport);
2652
- await new Promise((resolve5) => {
2653
- const previous = transport.onclose;
2654
- transport.onclose = () => {
2655
- previous?.();
2656
- resolve5();
2657
- };
2914
+ low.setRequestHandler(ListResourcesRequestSchema, async () => {
2915
+ touch();
2916
+ return runtime.listResources();
2917
+ });
2918
+ low.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
2919
+ touch();
2920
+ return runtime.listResourceTemplates();
2921
+ });
2922
+ low.setRequestHandler(ReadResourceRequestSchema, async (req) => {
2923
+ touch();
2924
+ await guardRead();
2925
+ return runtime.readResource(req.params.uri);
2926
+ });
2927
+ low.setRequestHandler(SubscribeRequestSchema2, async (req) => {
2928
+ await runtime.subscribe(req.params.uri, true);
2929
+ return {};
2930
+ });
2931
+ low.setRequestHandler(UnsubscribeRequestSchema2, async (req) => {
2932
+ await runtime.subscribe(req.params.uri, false);
2933
+ return {};
2934
+ });
2935
+ low.setRequestHandler(ListPromptsRequestSchema, async () => {
2936
+ touch();
2937
+ return runtime.listPrompts();
2938
+ });
2939
+ low.setRequestHandler(GetPromptRequestSchema, async (req) => {
2940
+ touch();
2941
+ await guardRead();
2942
+ return runtime.getPrompt(req.params.name, req.params.arguments);
2943
+ });
2944
+ low.setRequestHandler(CompleteRequestSchema, async (req) => {
2945
+ touch();
2946
+ return runtime.complete(req.params);
2947
+ });
2948
+ low.setRequestHandler(SetLevelRequestSchema, async (req) => {
2949
+ await runtime.setLevel(req.params.level);
2950
+ return {};
2658
2951
  });
2952
+ return server;
2659
2953
  }
2660
2954
 
2661
2955
  // src/proxy/http.ts
2662
- import { writeFileSync, unlinkSync, existsSync, mkdirSync } from "fs";
2956
+ import { writeFileSync, unlinkSync, existsSync as existsSync2, mkdirSync } from "fs";
2663
2957
  import { createServer } from "http";
2664
2958
  import { join } from "path";
2665
2959
  import { FileKillSwitch } from "@agentwares/agentguard-core/node";
@@ -2857,7 +3151,7 @@ async function startHttpProxy(opts) {
2857
3151
  port,
2858
3152
  token,
2859
3153
  close: () => new Promise((resolve5, reject) => {
2860
- if (opts.registerFile !== false && existsSync(regFile)) {
3154
+ if (opts.registerFile !== false && existsSync2(regFile)) {
2861
3155
  try {
2862
3156
  unlinkSync(regFile);
2863
3157
  } catch {
@@ -2878,7 +3172,7 @@ function unauthorized() {
2878
3172
  }
2879
3173
 
2880
3174
  // src/context.ts
2881
- import { existsSync as existsSync2 } from "fs";
3175
+ import { existsSync as existsSync3 } from "fs";
2882
3176
  import { resolve } from "path";
2883
3177
  import { Guard } from "@agentwares/agentguard-core";
2884
3178
  import {
@@ -2888,7 +3182,7 @@ import {
2888
3182
  FileStateStore,
2889
3183
  loadPolicyFile
2890
3184
  } from "@agentwares/agentguard-core/node";
2891
- import { existsSync as exists, readFileSync as readFileSync2 } from "fs";
3185
+ import { existsSync as exists, readFileSync as readFileSync3 } from "fs";
2892
3186
 
2893
3187
  // ../../node_modules/.pnpm/postal-mime@2.7.5/node_modules/postal-mime/src/decode-strings.js
2894
3188
  var textEncoder = new TextEncoder();
@@ -3272,7 +3566,7 @@ function readRegistration(loaded) {
3272
3566
  const file = registrationPath(loaded.stateDir);
3273
3567
  if (!exists(file)) return void 0;
3274
3568
  try {
3275
- const reg = JSON.parse(readFileSync2(file, "utf8"));
3569
+ const reg = JSON.parse(readFileSync3(file, "utf8"));
3276
3570
  return reg;
3277
3571
  } catch {
3278
3572
  return void 0;
@@ -3288,7 +3582,7 @@ function describeError(err) {
3288
3582
  }
3289
3583
 
3290
3584
  // src/configs.ts
3291
- import { copyFileSync, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
3585
+ import { copyFileSync, existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
3292
3586
  import { homedir as homedir2, platform } from "os";
3293
3587
  import { join as join2, resolve as resolve2 } from "path";
3294
3588
  function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.env) {
@@ -3321,6 +3615,12 @@ function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.
3321
3615
  serversKey: "servers"
3322
3616
  },
3323
3617
  { kind: "generic", label: "mcp.json", path: join2(cwd, "mcp.json"), serversKey: "mcpServers" },
3618
+ {
3619
+ kind: "gemini-project",
3620
+ label: "Gemini CLI (project .gemini/settings.json)",
3621
+ path: join2(cwd, ".gemini", "settings.json"),
3622
+ serversKey: "mcpServers"
3623
+ },
3324
3624
  { kind: "claude-desktop", label: "Claude Desktop", path: desktop, serversKey: "mcpServers" },
3325
3625
  {
3326
3626
  kind: "cursor-user",
@@ -3339,11 +3639,20 @@ function candidateConfigs(cwd = process.cwd(), home = homedir2(), env = process.
3339
3639
  label: "Claude Code (user ~/.claude.json)",
3340
3640
  path: join2(home, ".claude.json"),
3341
3641
  serversKey: "mcpServers"
3642
+ },
3643
+ {
3644
+ kind: "gemini-user",
3645
+ label: "Gemini CLI (user ~/.gemini/settings.json)",
3646
+ path: join2(home, ".gemini", "settings.json"),
3647
+ serversKey: "mcpServers"
3342
3648
  }
3343
3649
  ];
3344
3650
  }
3651
+ function isGeminiConfig(file) {
3652
+ return file.kind === "gemini-project" || file.kind === "gemini-user" || /[/\\]\.gemini[/\\]settings\.json$/.test(file.path);
3653
+ }
3345
3654
  function detectClientConfigs(cwd, home, env) {
3346
- return candidateConfigs(cwd, home, env).filter((c) => existsSync3(c.path));
3655
+ return candidateConfigs(cwd, home, env).filter((c) => existsSync4(c.path));
3347
3656
  }
3348
3657
  function describeConfigPath(path2) {
3349
3658
  const abs = resolve2(path2);
@@ -3351,7 +3660,7 @@ function describeConfigPath(path2) {
3351
3660
  if (known) return known;
3352
3661
  let serversKey = "mcpServers";
3353
3662
  try {
3354
- const parsed = JSON.parse(readFileSync3(abs, "utf8"));
3663
+ const parsed = JSON.parse(readFileSync4(abs, "utf8"));
3355
3664
  if (parsed.servers && !parsed.mcpServers) serversKey = "servers";
3356
3665
  } catch {
3357
3666
  }
@@ -3363,7 +3672,7 @@ function describeConfigPath(path2) {
3363
3672
  };
3364
3673
  }
3365
3674
  function readServers(file) {
3366
- const raw = JSON.parse(readFileSync3(file.path, "utf8"));
3675
+ const raw = JSON.parse(readFileSync4(file.path, "utf8"));
3367
3676
  const servers = raw[file.serversKey];
3368
3677
  if (!servers || typeof servers !== "object") return {};
3369
3678
  return servers;
@@ -3382,10 +3691,11 @@ function serversToUpstreams(servers) {
3382
3691
  continue;
3383
3692
  }
3384
3693
  const name = rawName.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^[^a-zA-Z0-9]+/, "") || "upstream";
3385
- if (entry.url) {
3694
+ const url = typeof entry.httpUrl === "string" ? entry.httpUrl : entry.url;
3695
+ if (url) {
3386
3696
  upstreams.push({
3387
3697
  name,
3388
- url: entry.url,
3698
+ url,
3389
3699
  headers: entry.headers ?? {},
3390
3700
  args: [],
3391
3701
  env: {},
@@ -3420,34 +3730,34 @@ function proxyEntry(file, policyPath, opts = {}) {
3420
3730
  return entry;
3421
3731
  }
3422
3732
  function rewriteClientConfig(file, policyPath, opts = {}) {
3423
- const raw = JSON.parse(readFileSync3(file.path, "utf8"));
3733
+ const raw = JSON.parse(readFileSync4(file.path, "utf8"));
3424
3734
  const servers = raw[file.serversKey] ?? {};
3425
3735
  const replaced = Object.keys(servers).filter((n) => !isProxyEntry(servers[n]));
3426
3736
  const backup = backupPath(file);
3427
- if (!existsSync3(backup)) copyFileSync(file.path, backup);
3737
+ if (!existsSync4(backup)) copyFileSync(file.path, backup);
3428
3738
  raw[file.serversKey] = { [PROXY_SERVER_NAME]: proxyEntry(file, policyPath, opts) };
3429
3739
  writeFileSync2(file.path, JSON.stringify(raw, null, 2) + "\n");
3430
3740
  return { backup, replaced };
3431
3741
  }
3432
3742
  function restoreClientConfig(file) {
3433
3743
  const backup = backupPath(file);
3434
- if (!existsSync3(backup)) return false;
3744
+ if (!existsSync4(backup)) return false;
3435
3745
  copyFileSync(backup, file.path);
3436
3746
  return true;
3437
3747
  }
3438
3748
  function mergeServerIntoConfig(file, name, entry) {
3439
- const raw = JSON.parse(readFileSync3(file.path, "utf8"));
3749
+ const raw = JSON.parse(readFileSync4(file.path, "utf8"));
3440
3750
  const servers = raw[file.serversKey] ?? {};
3441
3751
  const replaced = name in servers;
3442
3752
  const backup = backupPath(file);
3443
- if (!existsSync3(backup)) copyFileSync(file.path, backup);
3753
+ if (!existsSync4(backup)) copyFileSync(file.path, backup);
3444
3754
  raw[file.serversKey] = { ...servers, [name]: entry };
3445
3755
  writeFileSync2(file.path, JSON.stringify(raw, null, 2) + "\n");
3446
3756
  return { backup, replaced };
3447
3757
  }
3448
3758
 
3449
3759
  // src/commands/init.ts
3450
- import { existsSync as existsSync4, writeFileSync as writeFileSync3 } from "fs";
3760
+ import { existsSync as existsSync5, writeFileSync as writeFileSync3 } from "fs";
3451
3761
  import { relative, resolve as resolve3 } from "path";
3452
3762
  import YAML from "yaml";
3453
3763
 
@@ -3534,6 +3844,10 @@ async function toolsCommand(args, io) {
3534
3844
  io.err(
3535
3845
  `${u.connected ? "ok " : "down"} ${u.name}: ${u.connected ? `${u.tools} tools` : u.error}`
3536
3846
  );
3847
+ if (result.upstreams.length === 0)
3848
+ io.err(
3849
+ "no upstreams in the policy: an MCP client sees agentguard's own read-only tools instead (agentguard_get_status, agentguard_get_report, agentguard_verify_audit_log)"
3850
+ );
3537
3851
  for (const line of formatToolTable(result.tools)) io.out(line);
3538
3852
  const counts = result.tools.reduce(
3539
3853
  (acc, t) => ({ ...acc, [t.classification.class]: (acc[t.classification.class] ?? 0) + 1 }),
@@ -3701,7 +4015,7 @@ async function initCommand(args, io) {
3701
4015
  `found ${c.path} (${c.label}, user-level) \u2014 not touched; pass --client ${JSON.stringify(c.path)} to route it through agentguard`
3702
4016
  );
3703
4017
  }
3704
- if (!chosen.every((c) => existsSync4(c.path))) {
4018
+ if (!chosen.every((c) => existsSync5(c.path))) {
3705
4019
  io.err(`no MCP config at ${chosen.map((c) => c.path).join(", ")}`);
3706
4020
  return 1;
3707
4021
  }
@@ -3716,11 +4030,11 @@ async function initCommand(args, io) {
3716
4030
  }
3717
4031
  if (chosen.length === 0)
3718
4032
  io.out(
3719
- "found no MCP config (Claude Code .mcp.json, Cursor, VS Code, Claude Desktop, Windsurf) \u2014 writing a policy with no upstreams"
4033
+ "found no MCP config (Claude Code .mcp.json, Cursor, VS Code, Claude Desktop, Windsurf, Gemini CLI) \u2014 writing a policy with no upstreams"
3720
4034
  );
3721
4035
  const { upstreams, skipped } = serversToUpstreams(servers);
3722
4036
  for (const s of skipped) io.err(`skipped ${s.name}: ${s.reason}`);
3723
- if (skipped.some((s) => s.reason.includes("already")) && upstreams.length === 0 && existsSync4(policyPath)) {
4037
+ if (skipped.some((s) => s.reason.includes("already")) && upstreams.length === 0 && existsSync5(policyPath)) {
3724
4038
  io.out(
3725
4039
  `already initialized: ${rel(policyPath)} exists and ${chosen.map((c) => rel(c.path)).join(", ")} already points at agentguard`
3726
4040
  );
@@ -3747,7 +4061,7 @@ async function initCommand(args, io) {
3747
4061
  }
3748
4062
  }
3749
4063
  const mode = flagString(args, "mode") ?? "dry-run";
3750
- if (existsSync4(policyPath) && !flagBool(args, "force")) {
4064
+ if (existsSync5(policyPath) && !flagBool(args, "force")) {
3751
4065
  io.out(`kept ${rel(policyPath)} (exists; pass --force to regenerate)`);
3752
4066
  } else {
3753
4067
  writeFileSync3(policyPath, renderStarterPolicy({ upstreams, tools: probed, mode, probeErrors }));
@@ -3875,7 +4189,7 @@ async function permissionDiffCommand(args, io) {
3875
4189
  }
3876
4190
 
3877
4191
  // src/fixtures/crm-server.ts
3878
- import { z as z5 } from "zod";
4192
+ import { z as z6 } from "zod";
3879
4193
  function seedState() {
3880
4194
  const contacts = /* @__PURE__ */ new Map();
3881
4195
  for (const [id, name, email] of [
@@ -3890,18 +4204,18 @@ function seedState() {
3890
4204
  var counter = 0;
3891
4205
  var nextId = (prefix) => `${prefix}_${(++counter).toString(36).padStart(4, "0")}`;
3892
4206
  function crmTools(state) {
3893
- const contactSchema = z5.object({
3894
- id: z5.string(),
3895
- name: z5.string(),
3896
- email: z5.string(),
3897
- updated_at: z5.string()
4207
+ const contactSchema = z6.object({
4208
+ id: z6.string(),
4209
+ name: z6.string(),
4210
+ email: z6.string(),
4211
+ updated_at: z6.string()
3898
4212
  });
3899
4213
  return [
3900
4214
  defineTool({
3901
4215
  name: "crm_list_contacts",
3902
4216
  description: "List every contact in the CRM. Read-only; returns id, name, email and updated_at.",
3903
- input: z5.object({ limit: z5.number().int().min(1).max(500).default(100) }),
3904
- output: z5.object({ contacts: z5.array(contactSchema), total: z5.number() }),
4217
+ input: z6.object({ limit: z6.number().int().min(1).max(500).default(100) }),
4218
+ output: z6.object({ contacts: z6.array(contactSchema), total: z6.number() }),
3905
4219
  annotations: { readOnlyHint: true },
3906
4220
  handler: ({ limit }) => ({
3907
4221
  contacts: [...state.contacts.values()].slice(0, limit),
@@ -3911,7 +4225,7 @@ function crmTools(state) {
3911
4225
  defineTool({
3912
4226
  name: "crm_get_contact",
3913
4227
  description: "Fetch one contact by id. Read-only; NOT_FOUND when the id does not exist.",
3914
- input: z5.object({ id: z5.string() }),
4228
+ input: z6.object({ id: z6.string() }),
3915
4229
  output: contactSchema,
3916
4230
  annotations: { readOnlyHint: true },
3917
4231
  handler: ({ id }) => {
@@ -3928,7 +4242,7 @@ function crmTools(state) {
3928
4242
  defineTool({
3929
4243
  name: "crm_search",
3930
4244
  description: "Search contacts by name or email substring. Returns matching contacts (no annotations on purpose).",
3931
- input: z5.object({ query: z5.string() }),
4245
+ input: z6.object({ query: z6.string() }),
3932
4246
  handler: ({ query }) => ({
3933
4247
  contacts: [...state.contacts.values()].filter(
3934
4248
  (c) => `${c.name} ${c.email}`.toLowerCase().includes(query.toLowerCase())
@@ -3938,8 +4252,8 @@ function crmTools(state) {
3938
4252
  defineTool({
3939
4253
  name: "crm_create_contact",
3940
4254
  description: "Create a contact. Returns the new id and created_at timestamp.",
3941
- input: z5.object({ name: z5.string(), email: z5.string() }),
3942
- output: z5.object({ id: z5.string(), created_at: z5.string() }),
4255
+ input: z6.object({ name: z6.string(), email: z6.string() }),
4256
+ output: z6.object({ id: z6.string(), created_at: z6.string() }),
3943
4257
  annotations: { readOnlyHint: false, destructiveHint: false },
3944
4258
  handler: ({ name, email }) => {
3945
4259
  const id = nextId("c");
@@ -3952,11 +4266,11 @@ function crmTools(state) {
3952
4266
  defineTool({
3953
4267
  name: "crm_update_contact",
3954
4268
  description: "Update fields on a contact. Returns the id and updated_at; NOT_FOUND for unknown ids.",
3955
- input: z5.object({
3956
- id: z5.string(),
3957
- fields: z5.object({ name: z5.string().optional(), email: z5.string().optional() })
4269
+ input: z6.object({
4270
+ id: z6.string(),
4271
+ fields: z6.object({ name: z6.string().optional(), email: z6.string().optional() })
3958
4272
  }),
3959
- output: z5.object({ id: z5.string(), updated_at: z5.string() }),
4273
+ output: z6.object({ id: z6.string(), updated_at: z6.string() }),
3960
4274
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
3961
4275
  handler: ({ id, fields }) => {
3962
4276
  const c = state.contacts.get(id);
@@ -3974,8 +4288,8 @@ function crmTools(state) {
3974
4288
  defineTool({
3975
4289
  name: "crm_delete_contact",
3976
4290
  description: "Permanently delete a contact. Irreversible. Returns { deleted, id }.",
3977
- input: z5.object({ id: z5.string() }),
3978
- output: z5.object({ deleted: z5.boolean(), id: z5.string() }),
4291
+ input: z6.object({ id: z6.string() }),
4292
+ output: z6.object({ deleted: z6.boolean(), id: z6.string() }),
3979
4293
  annotations: { readOnlyHint: false, destructiveHint: true },
3980
4294
  handler: ({ id }) => {
3981
4295
  const existed = state.contacts.delete(id);
@@ -3986,8 +4300,8 @@ function crmTools(state) {
3986
4300
  defineTool({
3987
4301
  name: "crm_send_email",
3988
4302
  description: "Send an email to a contact through the CRM. Returns the message id and sent_at.",
3989
- input: z5.object({ to: z5.string(), subject: z5.string(), body: z5.string() }),
3990
- output: z5.object({ message_id: z5.string(), sent_at: z5.string() }),
4303
+ input: z6.object({ to: z6.string(), subject: z6.string(), body: z6.string() }),
4304
+ output: z6.object({ message_id: z6.string(), sent_at: z6.string() }),
3991
4305
  handler: ({ to, subject }) => {
3992
4306
  state.emails.push({ to, subject });
3993
4307
  state.mutations.push({ tool: "crm_send_email", args: { to, subject } });
@@ -3997,12 +4311,12 @@ function crmTools(state) {
3997
4311
  defineTool({
3998
4312
  name: "crm_charge_card",
3999
4313
  description: "Charge a customer's card on file (amount in cents). Returns the charge id and amount_usd.",
4000
- input: z5.object({
4001
- customer_id: z5.string(),
4002
- amount_cents: z5.number().int().positive(),
4003
- currency: z5.string().default("usd")
4314
+ input: z6.object({
4315
+ customer_id: z6.string(),
4316
+ amount_cents: z6.number().int().positive(),
4317
+ currency: z6.string().default("usd")
4004
4318
  }),
4005
- output: z5.object({ charge_id: z5.string(), amount_usd: z5.number() }),
4319
+ output: z6.object({ charge_id: z6.string(), amount_usd: z6.number() }),
4006
4320
  handler: ({ customer_id, amount_cents }) => {
4007
4321
  const amount_usd = amount_cents / 100;
4008
4322
  state.charges.push({ customer_id, amount_usd });
@@ -4013,7 +4327,7 @@ function crmTools(state) {
4013
4327
  defineTool({
4014
4328
  name: "crm_frobnicate",
4015
4329
  description: "A tool with no annotations and no recognizable verb \u2014 exists to test `unknown` handling.",
4016
- input: z5.object({}),
4330
+ input: z6.object({}),
4017
4331
  handler: () => {
4018
4332
  state.mutations.push({ tool: "crm_frobnicate", args: {} });
4019
4333
  return { frobnicated: true };
@@ -4022,7 +4336,7 @@ function crmTools(state) {
4022
4336
  defineTool({
4023
4337
  name: "crm_fail",
4024
4338
  description: "Always returns a structured UPSTREAM_ERROR result, for error-path tests.",
4025
- input: z5.object({}),
4339
+ input: z6.object({}),
4026
4340
  annotations: { readOnlyHint: true },
4027
4341
  handler: () => {
4028
4342
  throw toolError("UPSTREAM_ERROR", "the CRM is having a bad day", "retry in a minute", {
@@ -4179,7 +4493,7 @@ ${steps.length} calls; halts: ${halts.join(", ") || "none"}. Now run: agentguard
4179
4493
  }
4180
4494
 
4181
4495
  // src/commands/connect.ts
4182
- import { existsSync as existsSync5 } from "fs";
4496
+ import { existsSync as existsSync6 } from "fs";
4183
4497
  import { relative as relative2 } from "path";
4184
4498
  var DEFAULT_PROXY_URL = "https://agentwares-agentguard-proxy.vercel.app";
4185
4499
  function fail(e) {
@@ -4257,11 +4571,12 @@ async function connectCommand(args, io, fetchImpl = fetch) {
4257
4571
  return 1;
4258
4572
  }
4259
4573
  for (const c of chosen) {
4260
- if (!existsSync5(c.path)) {
4574
+ if (!existsSync6(c.path)) {
4261
4575
  io.err(`no MCP config at ${c.path}`);
4262
4576
  return 1;
4263
4577
  }
4264
- const { backup, replaced } = mergeServerIntoConfig(c, PROXY_SERVER_NAME, entry);
4578
+ const forClient = isGeminiConfig(c) ? { httpUrl: entry.url, ...entry.headers ? { headers: entry.headers } : {} } : entry;
4579
+ const { backup, replaced } = mergeServerIntoConfig(c, PROXY_SERVER_NAME, forClient);
4265
4580
  io.out(
4266
4581
  `${replaced ? "updated" : "wrote "} ${rel(c.path)} (${c.label}) \u2192 ${PROXY_SERVER_NAME} = ${conn.mcpUrl}`
4267
4582
  );
@@ -4376,13 +4691,13 @@ async function approvalsCommand(args, io) {
4376
4691
  }
4377
4692
 
4378
4693
  // src/commands/keys.ts
4379
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
4694
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
4380
4695
  import YAML2, { isMap, isSeq, YAMLSeq } from "yaml";
4381
4696
  import { generateAgentKey, hashAgentKey } from "@agentwares/agentguard-core";
4382
4697
  async function keyCommand(args, io) {
4383
4698
  const [sub, name] = args.positionals;
4384
4699
  const path2 = policyPathFrom(args, io);
4385
- const doc = YAML2.parseDocument(readFileSync4(path2, "utf8"));
4700
+ const doc = YAML2.parseDocument(readFileSync5(path2, "utf8"));
4386
4701
  let agents = doc.get("agents");
4387
4702
  if (!isSeq(agents)) {
4388
4703
  agents = new YAMLSeq();
@@ -4465,14 +4780,65 @@ async function keyCommand(args, io) {
4465
4780
  }
4466
4781
 
4467
4782
  // src/commands/proxy.ts
4468
- import { resolveAgent as resolveAgent2 } from "@agentwares/agentguard-core";
4783
+ import { existsSync as existsSync7 } from "fs";
4784
+ import { dirname } from "path";
4785
+ import { Guard as Guard3, defaultPolicy, resolveAgent as resolveAgent2 } from "@agentwares/agentguard-core";
4786
+ import { FileKillSwitch as FileKillSwitch3, resolvePath } from "@agentwares/agentguard-core/node";
4469
4787
  function newRunId(now = /* @__PURE__ */ new Date()) {
4470
4788
  return `run_${now.toISOString().replace(/[-:.TZ]/g, "").slice(0, 14)}_${Math.random().toString(36).slice(2, 6)}`;
4471
4789
  }
4790
+ function unconfiguredPolicy(args, io) {
4791
+ if (flagString(args, "config") !== void 0 || io.env.AGENTGUARD_CONFIG) return void 0;
4792
+ const path2 = policyPathFrom(args, io);
4793
+ if (existsSync7(path2)) return void 0;
4794
+ const policy = defaultPolicy();
4795
+ const baseDir = dirname(path2);
4796
+ return {
4797
+ policy,
4798
+ path: path2,
4799
+ baseDir,
4800
+ stateDir: resolvePath(policy.state.dir, baseDir),
4801
+ auditPath: resolvePath(policy.audit.path, baseDir),
4802
+ killPath: resolvePath(policy.kill.file, baseDir)
4803
+ };
4804
+ }
4805
+ async function serveOverStdio(runtime) {
4806
+ const server = createDownstreamServer(runtime);
4807
+ const stop = async () => {
4808
+ await runtime.close();
4809
+ };
4810
+ process.once("SIGINT", () => void stop().then(() => process.exit(0)));
4811
+ process.once("SIGTERM", () => void stop().then(() => process.exit(0)));
4812
+ await serveStdio(server);
4813
+ await stop();
4814
+ return 0;
4815
+ }
4472
4816
  async function proxyCommand(args, io) {
4473
- const loaded = loadPolicyFor(args, io);
4474
4817
  const log = (line) => io.err(`[agentguard] ${line}`);
4475
4818
  const modeOverride = flagString(args, "mode");
4819
+ const http = flagBool(args, "http") === true;
4820
+ const unconfigured = http ? void 0 : unconfiguredPolicy(args, io);
4821
+ if (unconfigured) {
4822
+ if (modeOverride === "dry-run" || modeOverride === "enforce")
4823
+ unconfigured.policy.mode = modeOverride;
4824
+ const runtime2 = new ProxyRuntime({
4825
+ loaded: unconfigured,
4826
+ guard: new Guard3({
4827
+ policy: unconfigured.policy,
4828
+ env: io.env,
4829
+ kill: new FileKillSwitch3(unconfigured.killPath)
4830
+ }),
4831
+ configured: false,
4832
+ defaultRunId: flagString(args, "run-id") ?? newRunId(),
4833
+ log
4834
+ });
4835
+ await runtime2.start();
4836
+ log(
4837
+ `no policy file at ${unconfigured.path}: serving agentguard's own read-only tools only. To guard your MCP servers, run \`${INIT_COMMAND}\` in the project directory.`
4838
+ );
4839
+ return serveOverStdio(runtime2);
4840
+ }
4841
+ const loaded = loadPolicyFor(args, io);
4476
4842
  if (modeOverride === "dry-run" || modeOverride === "enforce") loaded.policy.mode = modeOverride;
4477
4843
  const agentName = flagString(args, "agent");
4478
4844
  let agent = void 0;
@@ -4486,7 +4852,6 @@ async function proxyCommand(args, io) {
4486
4852
  }
4487
4853
  agent = resolved.agent;
4488
4854
  }
4489
- const http = flagBool(args, "http") === true;
4490
4855
  let approvalBase;
4491
4856
  const fileGuard = createFileGuard(loaded, {
4492
4857
  log,
@@ -4512,59 +4877,55 @@ async function proxyCommand(args, io) {
4512
4877
  log(
4513
4878
  `kill switch is ON (${killed.reason}); every call returns KILLED until \`agentguard resume\``
4514
4879
  );
4880
+ if (runtime.upstreamStatus().length === 0)
4881
+ log(
4882
+ `${loaded.path} lists no upstreams: serving agentguard's own read-only tools only. Add servers under \`upstreams:\` to guard them.`
4883
+ );
4515
4884
  if (http) {
4516
- const server2 = await startHttpProxy({
4885
+ const server = await startHttpProxy({
4517
4886
  runtime,
4518
4887
  port: flagNumber(args, "port") ?? 8788,
4519
4888
  host: flagString(args, "host") ?? "127.0.0.1",
4520
4889
  token: io.env.AGENTGUARD_HTTP_TOKEN
4521
4890
  });
4522
- approvalBase = { baseUrl: server2.url.replace(/\/mcp$/, ""), token: server2.token };
4891
+ approvalBase = { baseUrl: server.url.replace(/\/mcp$/, ""), token: server.token };
4523
4892
  log(
4524
- `Streamable HTTP at ${server2.url} \xB7 GET ${approvalBase.baseUrl}/health \xB7 control token in ${loaded.stateDir}/http.json`
4893
+ `Streamable HTTP at ${server.url} \xB7 GET ${approvalBase.baseUrl}/health \xB7 control token in ${loaded.stateDir}/http.json`
4525
4894
  );
4526
4895
  log(
4527
- `point your agent at ${server2.url} (send X-Run-Id per run; Authorization: Bearer <agent key> for scoped agents)`
4896
+ `point your agent at ${server.url} (send X-Run-Id per run; Authorization: Bearer <agent key> for scoped agents)`
4528
4897
  );
4529
- const stop2 = async () => {
4530
- await server2.close();
4898
+ const stop = async () => {
4899
+ await server.close();
4531
4900
  await runtime.close();
4532
4901
  process.exit(0);
4533
4902
  };
4534
- process.once("SIGINT", () => void stop2());
4535
- process.once("SIGTERM", () => void stop2());
4903
+ process.once("SIGINT", () => void stop());
4904
+ process.once("SIGTERM", () => void stop());
4536
4905
  await new Promise(() => void 0);
4537
4906
  }
4538
- const server = createDownstreamServer(runtime);
4539
- const stop = async () => {
4540
- await runtime.close();
4541
- };
4542
- process.once("SIGINT", () => void stop().then(() => process.exit(0)));
4543
- process.once("SIGTERM", () => void stop().then(() => process.exit(0)));
4544
- await serveStdio(server);
4545
- await stop();
4546
- return 0;
4907
+ return serveOverStdio(runtime);
4547
4908
  }
4548
4909
 
4549
4910
  // src/commands/report.ts
4550
4911
  import { resolve as resolve4 } from "path";
4551
4912
  import {
4552
- buildReport,
4553
- latestRunId,
4554
- listRuns,
4913
+ buildReport as buildReport2,
4914
+ latestRunId as latestRunId2,
4915
+ listRuns as listRuns2,
4555
4916
  renderMutationDiff,
4556
- renderReportMarkdown
4917
+ renderReportMarkdown as renderReportMarkdown2
4557
4918
  } from "@agentwares/agentguard-core";
4558
4919
  import { readAuditFile, verifyAuditFile } from "@agentwares/agentguard-core/node";
4559
4920
  function pickRun(args, entries) {
4560
4921
  if (flagBool(args, "all")) return void 0;
4561
- return flagString(args, "run") ?? latestRunId(entries);
4922
+ return flagString(args, "run") ?? latestRunId2(entries);
4562
4923
  }
4563
4924
  async function reportCommand(args, io) {
4564
4925
  const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
4565
4926
  const entries = readAuditFile(loaded.auditPath);
4566
4927
  const runId = pickRun(args, entries);
4567
- const report = buildReport(entries, { runId });
4928
+ const report = buildReport2(entries, { runId });
4568
4929
  if (flagBool(args, "json")) {
4569
4930
  io.out(
4570
4931
  JSON.stringify(
@@ -4583,12 +4944,12 @@ async function reportCommand(args, io) {
4583
4944
  );
4584
4945
  return 0;
4585
4946
  }
4586
- io.out(renderReportMarkdown(report));
4947
+ io.out(renderReportMarkdown2(report));
4587
4948
  const chain = await verifyAuditFile(loaded.auditPath);
4588
4949
  io.out(
4589
4950
  chain.ok ? `Audit chain: ${chain.entries} entries, verified (${loaded.auditPath}).` : `Audit chain BROKEN at entry ${chain.brokenAt}: ${chain.reason}`
4590
4951
  );
4591
- if (runId && listRuns(entries).length > 1) io.out(`Other runs: agentguard report --all`);
4952
+ if (runId && listRuns2(entries).length > 1) io.out(`Other runs: agentguard report --all`);
4592
4953
  return 0;
4593
4954
  }
4594
4955
  async function diffCommand(args, io) {
@@ -4615,7 +4976,7 @@ async function statusCommand(args, io) {
4615
4976
  const loaded = loadPolicyFor(args, io, { allowMissingEnv: true });
4616
4977
  const { guard, kill } = createFileGuard(loaded, { alerts: false, env: io.env });
4617
4978
  const entries = readAuditFile(loaded.auditPath);
4618
- const runId = flagString(args, "run") ?? latestRunId(entries) ?? "(none yet)";
4979
+ const runId = flagString(args, "run") ?? latestRunId2(entries) ?? "(none yet)";
4619
4980
  const status = await guard.status(runId);
4620
4981
  const killed = kill.check();
4621
4982
  const reg = readRegistration(loaded);
@@ -4784,6 +5145,6 @@ export {
4784
5145
  seedState,
4785
5146
  serversToUpstreams,
4786
5147
  startHttpProxy,
4787
- toCallToolResult
5148
+ toCallToolResult2 as toCallToolResult
4788
5149
  };
4789
5150
  //# sourceMappingURL=index.js.map