@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.
- package/dist/approval/mcp-flow-create.d.ts +3 -0
- package/dist/approval/mcp-flow-create.d.ts.map +1 -0
- package/dist/approval/mcp-flow-create.js +76 -0
- package/dist/approval/mcp-flow-create.js.map +1 -0
- package/dist/approval/mcp-flow-existing.d.ts +14 -0
- package/dist/approval/mcp-flow-existing.d.ts.map +1 -0
- package/dist/approval/mcp-flow-existing.js +43 -0
- package/dist/approval/mcp-flow-existing.js.map +1 -0
- package/dist/approval/mcp-flow-outcome.d.ts +5 -0
- package/dist/approval/mcp-flow-outcome.d.ts.map +1 -0
- package/dist/approval/mcp-flow-outcome.js +19 -0
- package/dist/approval/mcp-flow-outcome.js.map +1 -0
- package/dist/approval/mcp-flow-pending.d.ts +7 -0
- package/dist/approval/mcp-flow-pending.d.ts.map +1 -0
- package/dist/approval/mcp-flow-pending.js +25 -0
- package/dist/approval/mcp-flow-pending.js.map +1 -0
- package/dist/approval/mcp-flow-redeem.d.ts +13 -0
- package/dist/approval/mcp-flow-redeem.d.ts.map +1 -0
- package/dist/approval/mcp-flow-redeem.js +20 -0
- package/dist/approval/mcp-flow-redeem.js.map +1 -0
- package/dist/approval/mcp-flow.d.ts +6 -28
- package/dist/approval/mcp-flow.d.ts.map +1 -1
- package/dist/approval/mcp-flow.js +21 -129
- package/dist/approval/mcp-flow.js.map +1 -1
- package/dist/approval/mcp-flow.types.d.ts +42 -0
- package/dist/approval/mcp-flow.types.d.ts.map +1 -0
- package/dist/approval/mcp-flow.types.js +2 -0
- package/dist/approval/mcp-flow.types.js.map +1 -0
- package/dist/cli/doctor-adapter-lines.d.ts +4 -0
- package/dist/cli/doctor-adapter-lines.d.ts.map +1 -0
- package/dist/cli/doctor-adapter-lines.js +47 -0
- package/dist/cli/doctor-adapter-lines.js.map +1 -0
- package/dist/cli/doctor-notifications.d.ts +5 -0
- package/dist/cli/doctor-notifications.d.ts.map +1 -0
- package/dist/cli/doctor-notifications.js +10 -0
- package/dist/cli/doctor-notifications.js.map +1 -0
- package/dist/cli/doctor-remote.d.ts +3 -0
- package/dist/cli/doctor-remote.d.ts.map +1 -0
- package/dist/cli/doctor-remote.js +48 -0
- package/dist/cli/doctor-remote.js.map +1 -0
- package/dist/cli/doctor.d.ts +1 -12
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +46 -111
- package/dist/cli/doctor.js.map +1 -1
- package/dist/mcp/evaluate-guard-approval.d.ts +47 -0
- package/dist/mcp/evaluate-guard-approval.d.ts.map +1 -0
- package/dist/mcp/evaluate-guard-approval.js +80 -0
- package/dist/mcp/evaluate-guard-approval.js.map +1 -0
- package/dist/mcp/evaluate-guard-policy.d.ts +3 -0
- package/dist/mcp/evaluate-guard-policy.d.ts.map +1 -0
- package/dist/mcp/evaluate-guard-policy.js +6 -0
- package/dist/mcp/evaluate-guard-policy.js.map +1 -0
- package/dist/mcp/evaluate-guard-proposal.d.ts +16 -0
- package/dist/mcp/evaluate-guard-proposal.d.ts.map +1 -0
- package/dist/mcp/evaluate-guard-proposal.js +57 -0
- package/dist/mcp/evaluate-guard-proposal.js.map +1 -0
- package/dist/mcp/evaluate-guard-run.d.ts +23 -0
- package/dist/mcp/evaluate-guard-run.d.ts.map +1 -0
- package/dist/mcp/evaluate-guard-run.js +75 -0
- package/dist/mcp/evaluate-guard-run.js.map +1 -0
- package/dist/mcp/evaluate-guard-telemetry.d.ts +22 -0
- package/dist/mcp/evaluate-guard-telemetry.d.ts.map +1 -0
- package/dist/mcp/evaluate-guard-telemetry.js +38 -0
- package/dist/mcp/evaluate-guard-telemetry.js.map +1 -0
- package/dist/mcp/evaluate-guard-tier.d.ts +18 -0
- package/dist/mcp/evaluate-guard-tier.d.ts.map +1 -0
- package/dist/mcp/evaluate-guard-tier.js +42 -0
- package/dist/mcp/evaluate-guard-tier.js.map +1 -0
- package/dist/mcp/evaluate-guard.d.ts +2 -5
- package/dist/mcp/evaluate-guard.d.ts.map +1 -1
- package/dist/mcp/evaluate-guard.js +18 -195
- package/dist/mcp/evaluate-guard.js.map +1 -1
- package/dist/mcp/gateway-call-handler.d.ts +9 -0
- package/dist/mcp/gateway-call-handler.d.ts.map +1 -0
- package/dist/mcp/gateway-call-handler.js +61 -0
- package/dist/mcp/gateway-call-handler.js.map +1 -0
- package/dist/mcp/gateway-call-results.d.ts +5 -0
- package/dist/mcp/gateway-call-results.d.ts.map +1 -0
- package/dist/mcp/gateway-call-results.js +14 -0
- package/dist/mcp/gateway-call-results.js.map +1 -0
- package/dist/mcp/gateway-connection-lib.d.ts +54 -0
- package/dist/mcp/gateway-connection-lib.d.ts.map +1 -0
- package/dist/mcp/gateway-connection-lib.js +39 -0
- package/dist/mcp/gateway-connection-lib.js.map +1 -0
- package/dist/mcp/gateway-connection.d.ts +16 -0
- package/dist/mcp/gateway-connection.d.ts.map +1 -0
- package/dist/mcp/gateway-connection.js +54 -0
- package/dist/mcp/gateway-connection.js.map +1 -0
- package/dist/mcp/gateway-log.d.ts +5 -0
- package/dist/mcp/gateway-log.d.ts.map +1 -0
- package/dist/mcp/gateway-log.js +9 -0
- package/dist/mcp/gateway-log.js.map +1 -0
- package/dist/mcp/gateway.d.ts +2 -20
- package/dist/mcp/gateway.d.ts.map +1 -1
- package/dist/mcp/gateway.js +18 -186
- package/dist/mcp/gateway.js.map +1 -1
- package/dist/policies.v1.json +6 -0
- package/native/darwin-amd64/praxis-guard-daemon +0 -0
- package/native/darwin-amd64/praxis-guard-hook +0 -0
- package/native/darwin-arm64/praxis-guard-daemon +0 -0
- package/native/darwin-arm64/praxis-guard-hook +0 -0
- package/native/linux-amd64/praxis-guard-daemon +0 -0
- package/native/linux-amd64/praxis-guard-hook +0 -0
- package/native/linux-arm64/praxis-guard-daemon +0 -0
- package/native/linux-arm64/praxis-guard-hook +0 -0
- 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"}
|
package/dist/mcp/gateway.d.ts
CHANGED
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
-
import { type
|
|
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":"
|
|
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"}
|
package/dist/mcp/gateway.js
CHANGED
|
@@ -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,
|
|
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,
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
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) =>
|
|
33
|
+
server.sendToolListChanged().catch((e) => logGatewayErr(`sendToolListChanged failed: ${truncGatewayErr(e)}`));
|
|
141
34
|
});
|
|
142
35
|
}
|
|
143
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
+
logGatewayErr(`connected "${servers[i].id}" (${r.value.tools.length} tools)`);
|
|
234
66
|
}
|
|
235
67
|
else {
|
|
236
|
-
|
|
68
|
+
logGatewayErr(`skipping "${servers[i].id}": ${String(r.reason)}`);
|
|
237
69
|
}
|
|
238
70
|
});
|
|
239
71
|
if (connections.length === 0) {
|
|
240
|
-
|
|
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);
|