@praxis.guard/auditor-cli 0.0.61 → 0.0.63

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.
Files changed (106) hide show
  1. package/dist/approval/mcp-flow-create.d.ts +3 -0
  2. package/dist/approval/mcp-flow-create.d.ts.map +1 -0
  3. package/dist/approval/mcp-flow-create.js +76 -0
  4. package/dist/approval/mcp-flow-create.js.map +1 -0
  5. package/dist/approval/mcp-flow-existing.d.ts +14 -0
  6. package/dist/approval/mcp-flow-existing.d.ts.map +1 -0
  7. package/dist/approval/mcp-flow-existing.js +43 -0
  8. package/dist/approval/mcp-flow-existing.js.map +1 -0
  9. package/dist/approval/mcp-flow-outcome.d.ts +5 -0
  10. package/dist/approval/mcp-flow-outcome.d.ts.map +1 -0
  11. package/dist/approval/mcp-flow-outcome.js +19 -0
  12. package/dist/approval/mcp-flow-outcome.js.map +1 -0
  13. package/dist/approval/mcp-flow-pending.d.ts +7 -0
  14. package/dist/approval/mcp-flow-pending.d.ts.map +1 -0
  15. package/dist/approval/mcp-flow-pending.js +25 -0
  16. package/dist/approval/mcp-flow-pending.js.map +1 -0
  17. package/dist/approval/mcp-flow-redeem.d.ts +13 -0
  18. package/dist/approval/mcp-flow-redeem.d.ts.map +1 -0
  19. package/dist/approval/mcp-flow-redeem.js +20 -0
  20. package/dist/approval/mcp-flow-redeem.js.map +1 -0
  21. package/dist/approval/mcp-flow.d.ts +6 -28
  22. package/dist/approval/mcp-flow.d.ts.map +1 -1
  23. package/dist/approval/mcp-flow.js +21 -129
  24. package/dist/approval/mcp-flow.js.map +1 -1
  25. package/dist/approval/mcp-flow.types.d.ts +42 -0
  26. package/dist/approval/mcp-flow.types.d.ts.map +1 -0
  27. package/dist/approval/mcp-flow.types.js +2 -0
  28. package/dist/approval/mcp-flow.types.js.map +1 -0
  29. package/dist/cli/doctor-adapter-lines.d.ts +4 -0
  30. package/dist/cli/doctor-adapter-lines.d.ts.map +1 -0
  31. package/dist/cli/doctor-adapter-lines.js +47 -0
  32. package/dist/cli/doctor-adapter-lines.js.map +1 -0
  33. package/dist/cli/doctor-notifications.d.ts +5 -0
  34. package/dist/cli/doctor-notifications.d.ts.map +1 -0
  35. package/dist/cli/doctor-notifications.js +10 -0
  36. package/dist/cli/doctor-notifications.js.map +1 -0
  37. package/dist/cli/doctor-remote.d.ts +3 -0
  38. package/dist/cli/doctor-remote.d.ts.map +1 -0
  39. package/dist/cli/doctor-remote.js +48 -0
  40. package/dist/cli/doctor-remote.js.map +1 -0
  41. package/dist/cli/doctor.d.ts +1 -12
  42. package/dist/cli/doctor.d.ts.map +1 -1
  43. package/dist/cli/doctor.js +46 -111
  44. package/dist/cli/doctor.js.map +1 -1
  45. package/dist/mcp/evaluate-guard-approval.d.ts +47 -0
  46. package/dist/mcp/evaluate-guard-approval.d.ts.map +1 -0
  47. package/dist/mcp/evaluate-guard-approval.js +80 -0
  48. package/dist/mcp/evaluate-guard-approval.js.map +1 -0
  49. package/dist/mcp/evaluate-guard-policy.d.ts +3 -0
  50. package/dist/mcp/evaluate-guard-policy.d.ts.map +1 -0
  51. package/dist/mcp/evaluate-guard-policy.js +6 -0
  52. package/dist/mcp/evaluate-guard-policy.js.map +1 -0
  53. package/dist/mcp/evaluate-guard-proposal.d.ts +16 -0
  54. package/dist/mcp/evaluate-guard-proposal.d.ts.map +1 -0
  55. package/dist/mcp/evaluate-guard-proposal.js +57 -0
  56. package/dist/mcp/evaluate-guard-proposal.js.map +1 -0
  57. package/dist/mcp/evaluate-guard-run.d.ts +23 -0
  58. package/dist/mcp/evaluate-guard-run.d.ts.map +1 -0
  59. package/dist/mcp/evaluate-guard-run.js +75 -0
  60. package/dist/mcp/evaluate-guard-run.js.map +1 -0
  61. package/dist/mcp/evaluate-guard-telemetry.d.ts +22 -0
  62. package/dist/mcp/evaluate-guard-telemetry.d.ts.map +1 -0
  63. package/dist/mcp/evaluate-guard-telemetry.js +38 -0
  64. package/dist/mcp/evaluate-guard-telemetry.js.map +1 -0
  65. package/dist/mcp/evaluate-guard-tier.d.ts +18 -0
  66. package/dist/mcp/evaluate-guard-tier.d.ts.map +1 -0
  67. package/dist/mcp/evaluate-guard-tier.js +42 -0
  68. package/dist/mcp/evaluate-guard-tier.js.map +1 -0
  69. package/dist/mcp/evaluate-guard.d.ts +2 -5
  70. package/dist/mcp/evaluate-guard.d.ts.map +1 -1
  71. package/dist/mcp/evaluate-guard.js +18 -195
  72. package/dist/mcp/evaluate-guard.js.map +1 -1
  73. package/dist/mcp/gateway-call-handler.d.ts +9 -0
  74. package/dist/mcp/gateway-call-handler.d.ts.map +1 -0
  75. package/dist/mcp/gateway-call-handler.js +61 -0
  76. package/dist/mcp/gateway-call-handler.js.map +1 -0
  77. package/dist/mcp/gateway-call-results.d.ts +5 -0
  78. package/dist/mcp/gateway-call-results.d.ts.map +1 -0
  79. package/dist/mcp/gateway-call-results.js +14 -0
  80. package/dist/mcp/gateway-call-results.js.map +1 -0
  81. package/dist/mcp/gateway-connection-lib.d.ts +54 -0
  82. package/dist/mcp/gateway-connection-lib.d.ts.map +1 -0
  83. package/dist/mcp/gateway-connection-lib.js +39 -0
  84. package/dist/mcp/gateway-connection-lib.js.map +1 -0
  85. package/dist/mcp/gateway-connection.d.ts +16 -0
  86. package/dist/mcp/gateway-connection.d.ts.map +1 -0
  87. package/dist/mcp/gateway-connection.js +54 -0
  88. package/dist/mcp/gateway-connection.js.map +1 -0
  89. package/dist/mcp/gateway-log.d.ts +5 -0
  90. package/dist/mcp/gateway-log.d.ts.map +1 -0
  91. package/dist/mcp/gateway-log.js +9 -0
  92. package/dist/mcp/gateway-log.js.map +1 -0
  93. package/dist/mcp/gateway.d.ts +2 -20
  94. package/dist/mcp/gateway.d.ts.map +1 -1
  95. package/dist/mcp/gateway.js +18 -186
  96. package/dist/mcp/gateway.js.map +1 -1
  97. package/dist/policies.v1.json +6 -0
  98. package/native/darwin-amd64/praxis-guard-daemon +0 -0
  99. package/native/darwin-amd64/praxis-guard-hook +0 -0
  100. package/native/darwin-arm64/praxis-guard-daemon +0 -0
  101. package/native/darwin-arm64/praxis-guard-hook +0 -0
  102. package/native/linux-amd64/praxis-guard-daemon +0 -0
  103. package/native/linux-amd64/praxis-guard-hook +0 -0
  104. package/native/linux-arm64/praxis-guard-daemon +0 -0
  105. package/native/linux-arm64/praxis-guard-hook +0 -0
  106. package/package.json +1 -1
@@ -0,0 +1,61 @@
1
+ import { parseExposedToolName } from "./gateway-config.js";
2
+ import { evaluateGatewayCall } from "./gateway-evaluate.js";
3
+ import { gatewayBlockedResult, gatewayTextResult } from "./gateway-call-results.js";
4
+ import { logGatewayErr, truncGatewayErr } from "./gateway-log.js";
5
+ async function forwardWithReconnect(input) {
6
+ const { conn, serverId, tool, args, tier } = input;
7
+ try {
8
+ return await conn.callTool(tool, args);
9
+ }
10
+ catch (e) {
11
+ logGatewayErr(`downstream "${serverId}" call failed: ${truncGatewayErr(e)}; reconnecting`);
12
+ const ok = await conn.reconnect();
13
+ if (ok && tier === "READ") {
14
+ try {
15
+ return await conn.callTool(tool, args);
16
+ }
17
+ catch (e2) {
18
+ return gatewayTextResult(`Praxis gateway: downstream "${serverId}" error after reconnect: ${truncGatewayErr(e2)}`, true);
19
+ }
20
+ }
21
+ return gatewayTextResult(`Praxis gateway: downstream "${serverId}" call failed` +
22
+ (ok ? " (reconnected; not retried to avoid double-execution)" : " and is unavailable") +
23
+ `: ${truncGatewayErr(e)}`, true);
24
+ }
25
+ }
26
+ export async function handleGatewayCallTool(input) {
27
+ try {
28
+ const parsed = parseExposedToolName(input.exposed, input.ids);
29
+ if (!parsed) {
30
+ return gatewayTextResult(`Praxis gateway: unknown tool "${input.exposed}".`, true);
31
+ }
32
+ const conn = input.byId.get(parsed.serverId);
33
+ if (!conn) {
34
+ return gatewayTextResult(`Praxis gateway: downstream "${parsed.serverId}" is not connected.`, true);
35
+ }
36
+ if (!conn.tools.some((t) => t.name === parsed.tool)) {
37
+ return gatewayTextResult(`Praxis gateway: "${parsed.serverId}" does not expose a tool named "${parsed.tool}".`, true);
38
+ }
39
+ const decision = await evaluateGatewayCall({
40
+ serverId: parsed.serverId,
41
+ tool: parsed.tool,
42
+ toolInput: input.args,
43
+ });
44
+ if (decision.decision === "block") {
45
+ return gatewayBlockedResult(parsed.serverId, parsed.tool, decision);
46
+ }
47
+ return forwardWithReconnect({
48
+ conn,
49
+ serverId: parsed.serverId,
50
+ tool: parsed.tool,
51
+ args: input.args,
52
+ tier: decision.tier,
53
+ });
54
+ }
55
+ catch (e) {
56
+ logGatewayErr(`gating error for "${input.exposed}": ${truncGatewayErr(e)}`);
57
+ return gatewayTextResult("Praxis gateway: guard evaluation failed; blocking this call (fail-closed). " +
58
+ "To restore direct tools: run `auditor setup gateway --revert --apply`, then relaunch Claude Desktop.", true);
59
+ }
60
+ }
61
+ //# sourceMappingURL=gateway-call-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-call-handler.js","sourceRoot":"","sources":["../../src/mcp/gateway-call-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { evaluateGatewayCall } from "./gateway-evaluate.js";
3
+ export declare function gatewayTextResult(text: string, isError: boolean): CallToolResult;
4
+ export declare function gatewayBlockedResult(serverId: string, tool: string, d: Awaited<ReturnType<typeof evaluateGatewayCall>>): CallToolResult;
5
+ //# sourceMappingURL=gateway-call-results.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-call-results.d.ts","sourceRoot":"","sources":["../../src/mcp/gateway-call-results.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,cAAc,CAEhF;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,GACjD,cAAc,CAehB"}
@@ -0,0 +1,14 @@
1
+ export function gatewayTextResult(text, isError) {
2
+ return { content: [{ type: "text", text }], isError };
3
+ }
4
+ export function gatewayBlockedResult(serverId, tool, d) {
5
+ const reason = d.reasons[0] ?? "policy";
6
+ if (d.inlineApproval) {
7
+ return gatewayTextResult(`Praxis guard requires human approval for ${serverId}__${tool} (tier=${d.tier}). ` +
8
+ `Approve at ${d.inlineApproval.open_url} then retry this exact tool call. ` +
9
+ `(request ${d.inlineApproval.request_id})`, true);
10
+ }
11
+ return gatewayTextResult(`Praxis guard blocked ${serverId}__${tool} (tier=${d.tier}): ${reason}. ` +
12
+ (d.tier === "DESTRUCTIVE" ? "Destructive actions cannot be auto-approved." : ""), true);
13
+ }
14
+ //# sourceMappingURL=gateway-call-results.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-call-results.js","sourceRoot":"","sources":["../../src/mcp/gateway-call-results.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,OAAgB;IAC9D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,QAAgB,EAChB,IAAY,EACZ,CAAkD;IAElD,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC;IACxC,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;QACrB,OAAO,iBAAiB,CACtB,4CAA4C,QAAQ,KAAK,IAAI,UAAU,CAAC,CAAC,IAAI,KAAK;YAChF,cAAc,CAAC,CAAC,cAAc,CAAC,QAAQ,oCAAoC;YAC3E,YAAY,CAAC,CAAC,cAAc,CAAC,UAAU,GAAG,EAC5C,IAAI,CACL,CAAC;IACJ,CAAC;IACD,OAAO,iBAAiB,CACtB,wBAAwB,QAAQ,KAAK,IAAI,UAAU,CAAC,CAAC,IAAI,MAAM,MAAM,IAAI;QACvE,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,8CAA8C,CAAC,CAAC,CAAC,EAAE,CAAC,EAClF,IAAI,CACL,CAAC;AACJ,CAAC"}
@@ -0,0 +1,54 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import type { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import type { GatewayServer } from "./gateway-config.js";
4
+ import type { GatewayConnection } from "./gateway-connection.js";
5
+ export declare const GATEWAY_CONNECT_TIMEOUT_MS = 10000;
6
+ export declare function listToolsWithTimeout(client: Client, serverId: string): Promise<{
7
+ [x: string]: unknown;
8
+ tools: {
9
+ inputSchema: {
10
+ [x: string]: unknown;
11
+ type: "object";
12
+ properties?: Record<string, object> | undefined;
13
+ required?: string[] | undefined;
14
+ };
15
+ name: string;
16
+ description?: string | undefined;
17
+ outputSchema?: {
18
+ [x: string]: unknown;
19
+ type: "object";
20
+ properties?: Record<string, object> | undefined;
21
+ required?: string[] | undefined;
22
+ } | undefined;
23
+ annotations?: {
24
+ title?: string | undefined;
25
+ readOnlyHint?: boolean | undefined;
26
+ destructiveHint?: boolean | undefined;
27
+ idempotentHint?: boolean | undefined;
28
+ openWorldHint?: boolean | undefined;
29
+ } | undefined;
30
+ execution?: {
31
+ taskSupport?: "optional" | "required" | "forbidden" | undefined;
32
+ } | undefined;
33
+ _meta?: Record<string, unknown> | undefined;
34
+ icons?: {
35
+ src: string;
36
+ mimeType?: string | undefined;
37
+ sizes?: string[] | undefined;
38
+ theme?: "light" | "dark" | undefined;
39
+ }[] | undefined;
40
+ title?: string | undefined;
41
+ }[];
42
+ _meta?: {
43
+ [x: string]: unknown;
44
+ progressToken?: string | number | undefined;
45
+ "io.modelcontextprotocol/related-task"?: {
46
+ taskId: string;
47
+ } | undefined;
48
+ } | undefined;
49
+ nextCursor?: string | undefined;
50
+ }>;
51
+ export declare function connectClientWithTimeout(client: Client, spawn: () => StdioClientTransport, serverId: string, label: string): Promise<void>;
52
+ export declare function newGatewayClient(server: GatewayServer): Client;
53
+ export declare function refreshGatewayConnection(server: GatewayServer, spawn: () => StdioClientTransport, conn: GatewayConnection, attachListChanged: (client: Client) => void, notifyToolsChanged: () => void): Promise<Client | null>;
54
+ //# sourceMappingURL=gateway-connection-lib.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-connection-lib.d.ts","sourceRoot":"","sources":["../../src/mcp/gateway-connection-lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAGtF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAkBjD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;sBAoC44lB,CAAC;oBAA8D,CAAC;;;mBAAwF,CAAC;oBAA8C,CAAC;;;sBAAoG,CAAC;oBAA8D,CAAC;;mBAA0E,CAAC;iBAAyB,CAAC;wBAAkD,CAAC;2BAAsD,CAAC;0BAAqD,CAAC;yBAAoD,CAAC;;iBAAuE,CAAC;uBAA+B,CAAC;;aAAiG,CAAC;aAAwD,CAAC;;oBAAyD,CAAC;iBAA2C,CAAC;iBAA6C,CAAC;;aAA8E,CAAC;;;;qBAAgH,CAAC;8CAAiF,CAAC;;;;;GAlCryoB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,oBAAoB,EACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAE9D;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,MAAM,oBAAoB,EACjC,IAAI,EAAE,iBAAiB,EACvB,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,EAC3C,kBAAkB,EAAE,MAAM,IAAI,GAC7B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYxB"}
@@ -0,0 +1,39 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { AUDITOR_CLI_VERSION } from "../runtime/version.js";
3
+ export const GATEWAY_CONNECT_TIMEOUT_MS = 10_000;
4
+ function withTimeout(p, ms, label) {
5
+ return new Promise((resolve, reject) => {
6
+ const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
7
+ p.then((v) => {
8
+ clearTimeout(t);
9
+ resolve(v);
10
+ }, (e) => {
11
+ clearTimeout(t);
12
+ reject(e);
13
+ });
14
+ });
15
+ }
16
+ export async function listToolsWithTimeout(client, serverId) {
17
+ return withTimeout(client.listTools(), GATEWAY_CONNECT_TIMEOUT_MS, `listTools ${serverId}`);
18
+ }
19
+ export async function connectClientWithTimeout(client, spawn, serverId, label) {
20
+ await withTimeout(client.connect(spawn()), GATEWAY_CONNECT_TIMEOUT_MS, `${label} ${serverId}`);
21
+ }
22
+ export function newGatewayClient(server) {
23
+ return new Client({ name: `praxis-gateway-client-${server.id}`, version: AUDITOR_CLI_VERSION });
24
+ }
25
+ export async function refreshGatewayConnection(server, spawn, conn, attachListChanged, notifyToolsChanged) {
26
+ const client = newGatewayClient(server);
27
+ try {
28
+ await connectClientWithTimeout(client, spawn, server.id, "reconnect");
29
+ const listed = await listToolsWithTimeout(client, server.id);
30
+ conn.tools = listed.tools;
31
+ attachListChanged(client);
32
+ notifyToolsChanged();
33
+ return client;
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ //# sourceMappingURL=gateway-connection-lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-connection-lib.js","sourceRoot":"","sources":["../../src/mcp/gateway-connection-lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAGnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAI5D,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAEjD,SAAS,WAAW,CAAI,CAAa,EAAE,EAAU,EAAE,KAAa;IAC9D,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtF,CAAC,CAAC,IAAI,CACJ,CAAC,CAAC,EAAE,EAAE;YACJ,YAAY,CAAC,CAAC,CAAC,CAAC;YAChB,OAAO,CAAC,CAAC,CAAC,CAAC;QACb,CAAC,EACD,CAAC,CAAC,EAAE,EAAE;YACJ,YAAY,CAAC,CAAC,CAAC,CAAC;YAChB,MAAM,CAAC,CAAC,CAAC,CAAC;QACZ,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAAc,EAAE,QAAgB;IACzE,OAAO,WAAW,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,0BAA0B,EAAE,aAAa,QAAQ,EAAE,CAAC,CAAC;AAC9F,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAc,EACd,KAAiC,EACjC,QAAgB,EAChB,KAAa;IAEb,MAAM,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,0BAA0B,EAAE,GAAG,KAAK,IAAI,QAAQ,EAAE,CAAC,CAAC;AACjG,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAqB;IACpD,OAAO,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,yBAAyB,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;AAClG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAqB,EACrB,KAAiC,EACjC,IAAuB,EACvB,iBAA2C,EAC3C,kBAA8B;IAE9B,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,wBAAwB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAC7D,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC1B,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC1B,kBAAkB,EAAE,CAAC;QACrB,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { type CallToolResult, type Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { GatewayServer } from "./gateway-config.js";
3
+ /**
4
+ * One downstream MCP server, abstracted so tests can inject fakes without
5
+ * spawning real subprocesses. The real implementation is `connectStdioServer`.
6
+ */
7
+ export interface GatewayConnection {
8
+ readonly id: string;
9
+ tools: Tool[];
10
+ callTool(tool: string, args: Record<string, unknown>): Promise<CallToolResult>;
11
+ reconnect(): Promise<boolean>;
12
+ onToolsChanged?(listener: () => void): void;
13
+ }
14
+ /** Real stdio-backed connection to a downstream MCP server. */
15
+ export declare function connectStdioServer(server: GatewayServer): Promise<GatewayConnection>;
16
+ //# sourceMappingURL=gateway-connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-connection.d.ts","sourceRoot":"","sources":["../../src/mcp/gateway-connection.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,IAAI,EACV,MAAM,oCAAoC,CAAC;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/E,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,cAAc,CAAC,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAC7C;AAkBD,+DAA+D;AAC/D,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA8C1F"}
@@ -0,0 +1,54 @@
1
+ import { StdioClientTransport, getDefaultEnvironment, } from "@modelcontextprotocol/sdk/client/stdio.js";
2
+ import { ToolListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js";
3
+ import { logGatewayErr, truncGatewayErr } from "./gateway-log.js";
4
+ import { connectClientWithTimeout, listToolsWithTimeout, newGatewayClient, refreshGatewayConnection, } from "./gateway-connection-lib.js";
5
+ function attachListChangedHandler(client, conn, getListener) {
6
+ client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
7
+ try {
8
+ const re = await listToolsWithTimeout(client, conn.id);
9
+ conn.tools = re.tools;
10
+ getListener()?.();
11
+ }
12
+ catch (e) {
13
+ logGatewayErr(`tools/list_changed refresh for "${conn.id}" failed: ${truncGatewayErr(e)}`);
14
+ }
15
+ });
16
+ }
17
+ /** Real stdio-backed connection to a downstream MCP server. */
18
+ export async function connectStdioServer(server) {
19
+ const spawn = () => new StdioClientTransport({
20
+ command: server.command,
21
+ args: server.args,
22
+ env: server.env ? { ...getDefaultEnvironment(), ...server.env } : undefined,
23
+ });
24
+ let client = newGatewayClient(server);
25
+ let toolsChangedListener = null;
26
+ const getListener = () => toolsChangedListener;
27
+ await connectClientWithTimeout(client, spawn, server.id, "connect");
28
+ const listed = await listToolsWithTimeout(client, server.id);
29
+ let conn;
30
+ const wireListChanged = (fresh) => attachListChangedHandler(fresh, conn, getListener);
31
+ conn = {
32
+ id: server.id,
33
+ tools: listed.tools,
34
+ onToolsChanged(listener) {
35
+ toolsChangedListener = listener;
36
+ },
37
+ async callTool(tool, args) {
38
+ return (await client.callTool({ name: tool, arguments: args }));
39
+ },
40
+ async reconnect() {
41
+ await client.close().catch(() => { });
42
+ const next = await refreshGatewayConnection(server, spawn, conn, wireListChanged, () => toolsChangedListener?.());
43
+ if (!next) {
44
+ logGatewayErr(`reconnect to "${server.id}" failed`);
45
+ return false;
46
+ }
47
+ client = next;
48
+ return true;
49
+ },
50
+ };
51
+ wireListChanged(client);
52
+ return conn;
53
+ }
54
+ //# sourceMappingURL=gateway-connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-connection.js","sourceRoot":"","sources":["../../src/mcp/gateway-connection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** stderr only — stdout is the MCP protocol channel. */
2
+ export declare function logGatewayErr(line: string): void;
3
+ /** Bound downstream-supplied error text so a hostile downstream can't flood or spoof the result. */
4
+ export declare function truncGatewayErr(e: unknown): string;
5
+ //# sourceMappingURL=gateway-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-log.d.ts","sourceRoot":"","sources":["../../src/mcp/gateway-log.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEhD;AAED,oGAAoG;AACpG,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAElD"}
@@ -0,0 +1,9 @@
1
+ /** stderr only — stdout is the MCP protocol channel. */
2
+ export function logGatewayErr(line) {
3
+ process.stderr.write(`[auditor-gateway] ${line}\n`);
4
+ }
5
+ /** Bound downstream-supplied error text so a hostile downstream can't flood or spoof the result. */
6
+ export function truncGatewayErr(e) {
7
+ return String(e).slice(0, 300);
8
+ }
9
+ //# sourceMappingURL=gateway-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-log.js","sourceRoot":"","sources":["../../src/mcp/gateway-log.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,IAAI,IAAI,CAAC,CAAC;AACtD,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,eAAe,CAAC,CAAU;IACxC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACjC,CAAC"}
@@ -1,24 +1,6 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
- import { type CallToolResult, type Tool } from "@modelcontextprotocol/sdk/types.js";
3
- /**
4
- * One downstream MCP server, abstracted so tests can inject fakes without
5
- * spawning real subprocesses. The real implementation is `connectStdioServer`.
6
- */
7
- export interface GatewayConnection {
8
- readonly id: string;
9
- /** Tools advertised by this downstream server (fetched at connect). */
10
- tools: Tool[];
11
- callTool(tool: string, args: Record<string, unknown>): Promise<CallToolResult>;
12
- /** Re-establish the underlying transport after a failure. Returns false if it cannot. */
13
- reconnect(): Promise<boolean>;
14
- /**
15
- * Register a callback fired AFTER this connection refreshes its tool slice in
16
- * response to a downstream `tools/list_changed` (or a reconnect-driven refresh).
17
- * `createGatewayServer` uses it to re-emit the gateway's own `list_changed`
18
- * upstream. Optional so in-process test fakes need not implement it.
19
- */
20
- onToolsChanged?(listener: () => void): void;
21
- }
2
+ import { type GatewayConnection } from "./gateway-connection.js";
3
+ export type { GatewayConnection } from "./gateway-connection.js";
22
4
  /**
23
5
  * Build the low-level MCP `Server` that fronts the given downstream connections.
24
6
  * Exported for tests (inject fake connections); `runMcpGateway` wires the real ones.
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../src/mcp/gateway.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAEnE,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,IAAI,EACV,MAAM,oCAAoC,CAAC;AAuB5C;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/E,yFAAyF;IACzF,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B;;;;;OAKG;IACH,cAAc,CAAC,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAC7C;AAwHD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAyG5E;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC9E"}
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../src/mcp/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAmBnE,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAmC5E;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B9E"}
@@ -1,116 +1,14 @@
1
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
- import { StdioClientTransport, getDefaultEnvironment, } from "@modelcontextprotocol/sdk/client/stdio.js";
3
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import { CallToolRequestSchema, ListToolsRequestSchema, ToolListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js";
3
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
6
4
  import { mkdirSync } from "node:fs";
7
5
  import path from "node:path";
8
6
  import { AUDITOR_CLI_VERSION } from "../runtime/version.js";
9
7
  import { GUARD_DIR } from "../bridge/guard-storage-root.js";
10
- import { exposedToolName, loadGatewayConfig, parseExposedToolName, resolveGatewayStorageRoot, } from "./gateway-config.js";
11
- import { evaluateGatewayCall } from "./gateway-evaluate.js";
12
- const CONNECT_TIMEOUT_MS = 10_000;
13
- /** stderr only stdout is the MCP protocol channel. */
14
- function logErr(line) {
15
- process.stderr.write(`[auditor-gateway] ${line}\n`);
16
- }
17
- function withTimeout(p, ms, label) {
18
- return new Promise((resolve, reject) => {
19
- const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
20
- p.then((v) => {
21
- clearTimeout(t);
22
- resolve(v);
23
- }, (e) => {
24
- clearTimeout(t);
25
- reject(e);
26
- });
27
- });
28
- }
29
- /**
30
- * Register the downstream `tools/list_changed` handler on `client` (the CURRENT
31
- * one; call again after a reconnect with the fresh client). On notification:
32
- * re-list (bounded), swap the connection's slice, then fire `getListener` so the
33
- * gateway re-emits list_changed upstream. A failed refresh keeps the prior slice
34
- * (no crash); the next call's reconnect path heals the connection.
35
- */
36
- function attachListChangedHandler(client, conn, getListener) {
37
- client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
38
- try {
39
- const re = await withTimeout(client.listTools(), CONNECT_TIMEOUT_MS, `listTools ${conn.id}`);
40
- conn.tools = re.tools;
41
- getListener()?.();
42
- }
43
- catch (e) {
44
- logErr(`tools/list_changed refresh for "${conn.id}" failed: ${truncErr(e)}`);
45
- }
46
- });
47
- }
48
- /** Real stdio-backed connection to a downstream MCP server. */
49
- async function connectStdioServer(server) {
50
- const spawn = () => new StdioClientTransport({
51
- command: server.command,
52
- args: server.args,
53
- env: server.env ? { ...getDefaultEnvironment(), ...server.env } : undefined,
54
- });
55
- let client = new Client({ name: `praxis-gateway-client-${server.id}`, version: AUDITOR_CLI_VERSION });
56
- // Set when createGatewayServer wires this connection (re-emits list_changed upstream).
57
- let toolsChangedListener = null;
58
- const getListener = () => toolsChangedListener;
59
- await withTimeout(client.connect(spawn()), CONNECT_TIMEOUT_MS, `connect ${server.id}`);
60
- const listed = await withTimeout(client.listTools(), CONNECT_TIMEOUT_MS, `listTools ${server.id}`);
61
- const conn = {
62
- id: server.id,
63
- tools: listed.tools,
64
- onToolsChanged(listener) {
65
- toolsChangedListener = listener;
66
- },
67
- async callTool(tool, args) {
68
- return (await client.callTool({ name: tool, arguments: args }));
69
- },
70
- async reconnect() {
71
- try {
72
- // Close the old client first so the dead subprocess + pipes are reaped;
73
- // otherwise a flapping downstream leaks one child process per failure.
74
- await client.close().catch(() => { });
75
- client = new Client({
76
- name: `praxis-gateway-client-${server.id}`,
77
- version: AUDITOR_CLI_VERSION,
78
- });
79
- await withTimeout(client.connect(spawn()), CONNECT_TIMEOUT_MS, `reconnect ${server.id}`);
80
- const re = await withTimeout(client.listTools(), CONNECT_TIMEOUT_MS, `listTools ${server.id}`);
81
- conn.tools = re.tools;
82
- // Fresh client needs its own handler; the tool set may have changed across
83
- // the gap, so propagate the refreshed slice upstream.
84
- attachListChangedHandler(client, conn, getListener);
85
- toolsChangedListener?.();
86
- return true;
87
- }
88
- catch (e) {
89
- logErr(`reconnect to "${server.id}" failed: ${String(e)}`);
90
- return false;
91
- }
92
- },
93
- };
94
- attachListChangedHandler(client, conn, getListener);
95
- return conn;
96
- }
97
- function textResult(text, isError) {
98
- return { content: [{ type: "text", text }], isError };
99
- }
100
- /** Bound downstream-supplied error text so a hostile downstream can't flood or spoof the result. */
101
- function truncErr(e) {
102
- return String(e).slice(0, 300);
103
- }
104
- function blockedResult(serverId, tool, d) {
105
- const reason = d.reasons[0] ?? "policy";
106
- if (d.inlineApproval) {
107
- return textResult(`Praxis guard requires human approval for ${serverId}__${tool} (tier=${d.tier}). ` +
108
- `Approve at ${d.inlineApproval.open_url} then retry this exact tool call. ` +
109
- `(request ${d.inlineApproval.request_id})`, true);
110
- }
111
- return textResult(`Praxis guard blocked ${serverId}__${tool} (tier=${d.tier}): ${reason}. ` +
112
- (d.tier === "DESTRUCTIVE" ? "Destructive actions cannot be auto-approved." : ""), true);
113
- }
8
+ import { exposedToolName, loadGatewayConfig, resolveGatewayStorageRoot, } from "./gateway-config.js";
9
+ import { handleGatewayCallTool } from "./gateway-call-handler.js";
10
+ import { connectStdioServer, } from "./gateway-connection.js";
11
+ import { logGatewayErr, truncGatewayErr } from "./gateway-log.js";
114
12
  /**
115
13
  * Build the low-level MCP `Server` that fronts the given downstream connections.
116
14
  * Exported for tests (inject fake connections); `runMcpGateway` wires the real ones.
@@ -120,10 +18,7 @@ function blockedResult(serverId, tool, d) {
120
18
  export function createGatewayServer(connections) {
121
19
  const byId = new Map(connections.map((c) => [c.id, c]));
122
20
  const ids = connections.map((c) => c.id);
123
- const server = new Server({ name: "praxis-guard-gateway", version: AUDITOR_CLI_VERSION },
124
- // listChanged: true tells the host (Desktop) to listen for tools/list_changed
125
- // so downstream tools that appear after login (auth-gated) become visible.
126
- { capabilities: { tools: { listChanged: true } } });
21
+ const server = new Server({ name: "praxis-guard-gateway", version: AUDITOR_CLI_VERSION }, { capabilities: { tools: { listChanged: true } } });
127
22
  server.setRequestHandler(ListToolsRequestSchema, async () => {
128
23
  const tools = [];
129
24
  for (const conn of connections) {
@@ -133,75 +28,17 @@ export function createGatewayServer(connections) {
133
28
  }
134
29
  return { tools };
135
30
  });
136
- // When any downstream refreshes its tool slice, re-emit the gateway's own
137
- // list_changed upstream so the host re-fetches the combined namespaced set.
138
31
  for (const conn of connections) {
139
32
  conn.onToolsChanged?.(() => {
140
- server.sendToolListChanged().catch((e) => logErr(`sendToolListChanged failed: ${truncErr(e)}`));
33
+ server.sendToolListChanged().catch((e) => logGatewayErr(`sendToolListChanged failed: ${truncGatewayErr(e)}`));
141
34
  });
142
35
  }
143
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
144
- // Fail closed: any unexpected throw in parsing/gating (e.g. a corrupt policy
145
- // file making evaluateGatewayCall reject) blocks the call rather than letting
146
- // it slip through. Mirrors the hook's failClosedBeforeMcpHookErrorResponse.
147
- try {
148
- const exposed = request.params.name;
149
- const args = (request.params.arguments ?? {});
150
- const parsed = parseExposedToolName(exposed, ids);
151
- if (!parsed) {
152
- return textResult(`Praxis gateway: unknown tool "${exposed}".`, true);
153
- }
154
- const conn = byId.get(parsed.serverId);
155
- if (!conn) {
156
- return textResult(`Praxis gateway: downstream "${parsed.serverId}" is not connected.`, true);
157
- }
158
- // Only proxy tools the downstream actually advertised. Without this, any
159
- // `knownServer__anything` would be forwarded (and classified skipped->allow),
160
- // proxying tools the downstream never listed. The gateway owns this check, not
161
- // the downstream (found in QA: ISSUE-001).
162
- if (!conn.tools.some((t) => t.name === parsed.tool)) {
163
- return textResult(`Praxis gateway: "${parsed.serverId}" does not expose a tool named "${parsed.tool}".`, true);
164
- }
165
- const decision = await evaluateGatewayCall({
166
- serverId: parsed.serverId,
167
- tool: parsed.tool,
168
- toolInput: args,
169
- });
170
- if (decision.decision === "block") {
171
- return blockedResult(parsed.serverId, parsed.tool, decision);
172
- }
173
- // allow -> forward, returning the downstream result VERBATIM (decision D5).
174
- try {
175
- return await conn.callTool(parsed.tool, args);
176
- }
177
- catch (e) {
178
- logErr(`downstream "${parsed.serverId}" call failed: ${truncErr(e)}; reconnecting`);
179
- const ok = await conn.reconnect();
180
- // Retry ONLY idempotent (READ) calls. A redeemed MUTATE/DESTRUCTIVE must not
181
- // be auto-replayed: if the first attempt reached the downstream and mutated
182
- // state before the transport died, a blind retry double-applies it. Reconnect
183
- // still heals the connection for the next call.
184
- if (ok && decision.tier === "READ") {
185
- try {
186
- return await conn.callTool(parsed.tool, args);
187
- }
188
- catch (e2) {
189
- return textResult(`Praxis gateway: downstream "${parsed.serverId}" error after reconnect: ${truncErr(e2)}`, true);
190
- }
191
- }
192
- return textResult(`Praxis gateway: downstream "${parsed.serverId}" call failed` +
193
- (ok ? " (reconnected; not retried to avoid double-execution)" : " and is unavailable") +
194
- `: ${truncErr(e)}`, true);
195
- }
196
- }
197
- catch (e) {
198
- logErr(`gating error for "${request.params?.name}": ${truncErr(e)}`);
199
- // Blast-radius escape hatch: the gateway fronts every Desktop connector, so a
200
- // fail-closed bug blocks all tools. Name the recovery path in the result.
201
- return textResult("Praxis gateway: guard evaluation failed; blocking this call (fail-closed). " +
202
- "To restore direct tools: run `auditor setup gateway --revert --apply`, then relaunch Claude Desktop.", true);
203
- }
204
- });
36
+ server.setRequestHandler(CallToolRequestSchema, async (request) => handleGatewayCallTool({
37
+ exposed: request.params.name,
38
+ args: (request.params.arguments ?? {}),
39
+ ids,
40
+ byId,
41
+ }));
205
42
  return server;
206
43
  }
207
44
  /**
@@ -211,33 +48,28 @@ export function createGatewayServer(connections) {
211
48
  * stdio. Blocks until disconnect.
212
49
  */
213
50
  export async function runMcpGateway(explicitConfigPath) {
214
- // Create the dedicated gateway guard storage marker BEFORE any evaluation so
215
- // resolveGuardStorageRoot resolves ticket/approval/audit state to the gateway's
216
- // own root (not $HOME). Without this marker the walk-up would land on $HOME and
217
- // re-share the namespace with hook hosts (decision D9). Best-effort: a failure
218
- // here must not stop the gateway from serving.
219
51
  try {
220
52
  mkdirSync(path.join(resolveGatewayStorageRoot(), GUARD_DIR), { recursive: true });
221
53
  }
222
54
  catch (e) {
223
- logErr(`could not create gateway storage root: ${truncErr(e)}`);
55
+ logGatewayErr(`could not create gateway storage root: ${truncGatewayErr(e)}`);
224
56
  }
225
57
  const { servers, errors } = await loadGatewayConfig(explicitConfigPath);
226
58
  for (const e of errors)
227
- logErr(e);
59
+ logGatewayErr(e);
228
60
  const settled = await Promise.allSettled(servers.map((s) => connectStdioServer(s)));
229
61
  const connections = [];
230
62
  settled.forEach((r, i) => {
231
63
  if (r.status === "fulfilled") {
232
64
  connections.push(r.value);
233
- logErr(`connected "${servers[i].id}" (${r.value.tools.length} tools)`);
65
+ logGatewayErr(`connected "${servers[i].id}" (${r.value.tools.length} tools)`);
234
66
  }
235
67
  else {
236
- logErr(`skipping "${servers[i].id}": ${String(r.reason)}`);
68
+ logGatewayErr(`skipping "${servers[i].id}": ${String(r.reason)}`);
237
69
  }
238
70
  });
239
71
  if (connections.length === 0) {
240
- logErr("no downstream servers connected; gateway will expose no tools. " +
72
+ logGatewayErr("no downstream servers connected; gateway will expose no tools. " +
241
73
  "To restore direct connectors: run `auditor setup gateway --revert --apply`, then relaunch Claude Desktop.");
242
74
  }
243
75
  const server = createGatewayServer(connections);