@auctra/mcp-gateway 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @auctra/mcp-gateway
2
2
 
3
- Stage 0 MCP gateway adapter. Wrap MCP tool calls with Auctra `action.execute` before execution.
3
+ Stage 0 MCP gateway adapter. Wrap MCP tool calls with Auctra **Authority Gate** (`action.execute`) before execution.
4
4
 
5
5
  ```ts
6
6
  import { Auctra } from "@auctra/sdk";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auctra/mcp-gateway",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Stage 0 MCP gateway adapter — gate MCP tool calls through Auctra action.execute",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -35,5 +35,8 @@
35
35
  },
36
36
  "engines": {
37
37
  "node": ">=20"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.15.0"
38
41
  }
39
42
  }
package/src/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { createHash, createHmac } from "node:crypto";
2
+
1
3
  export class McpToolBlockedError extends Error {
2
4
  constructor(message, decision, evidence) {
3
5
  super(message);
@@ -15,6 +17,34 @@ function evidenceIdFromResult(result) {
15
17
  return result.evidence_payload?.evidence_id ?? result.action_request_id ?? null;
16
18
  }
17
19
 
20
+ /** Stable sha256 of a tool response body for provenance + attestation. */
21
+ export function hashToolResponseBody(body) {
22
+ const payload = typeof body === "string" ? body : JSON.stringify(body ?? null);
23
+ return `sha256:${createHash("sha256").update(payload).digest("hex")}`;
24
+ }
25
+
26
+ /**
27
+ * HMAC-sign a tool response content hash (Business+).
28
+ * Must match Auctra gate evaluateToolAttestation.
29
+ */
30
+ export function signToolResponseAttestation(input) {
31
+ const issuedAt = input.issuedAt ?? new Date().toISOString();
32
+ const contentHash = input.contentHash.trim();
33
+ const toolName = input.toolName.trim();
34
+ const key = `${input.secret}:${input.organizationId}`;
35
+ const signature = createHmac("sha256", key)
36
+ .update(`${toolName}\n${contentHash}\n${issuedAt}`)
37
+ .digest("hex");
38
+ return {
39
+ content_hash: contentHash,
40
+ tool_name: toolName,
41
+ issued_at: issuedAt,
42
+ signature,
43
+ alg: "hmac-sha256",
44
+ key_id: input.keyId ?? "org-default",
45
+ };
46
+ }
47
+
18
48
  /** Provenance for a tool/MCP result — attach on the next consequential action.execute. */
19
49
  export function declareToolResponseInput(input) {
20
50
  return {
@@ -66,6 +96,22 @@ export async function gateMcpToolCall(input) {
66
96
  export function withAuctraGate(input) {
67
97
  return async (args) => {
68
98
  await gateMcpToolCall({ ...input, args });
69
- return input.execute(args);
99
+ const result = await input.execute(args);
100
+ if (!input.attestation) return result;
101
+ const contentHash = hashToolResponseBody(result);
102
+ const tool_attestation = signToolResponseAttestation({
103
+ organizationId: input.attestation.organizationId,
104
+ toolName: input.toolName,
105
+ contentHash,
106
+ secret: input.attestation.secret,
107
+ });
108
+ return {
109
+ result,
110
+ provenance: declareToolResponseInput({
111
+ toolName: input.toolName,
112
+ contentHash,
113
+ }),
114
+ tool_attestation,
115
+ };
70
116
  };
71
117
  }
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { createHash, createHmac } from "node:crypto";
2
+
1
3
  export type ExecuteActionClient = {
2
4
  action: {
3
5
  execute(input: {
@@ -42,6 +44,15 @@ export type McpToolGateInput = {
42
44
  intentAnchorToken?: string;
43
45
  };
44
46
 
47
+ export type ToolAttestation = {
48
+ content_hash: string;
49
+ tool_name: string;
50
+ issued_at: string;
51
+ signature: string;
52
+ alg: "hmac-sha256";
53
+ key_id?: string;
54
+ };
55
+
45
56
  export class McpToolBlockedError extends Error {
46
57
  readonly decision: "blocked" | "require_approval";
47
58
  readonly evidence?: Awaited<ReturnType<ExecuteActionClient["action"]["execute"]>>["evidence"];
@@ -68,6 +79,42 @@ function evidenceIdFromResult(
68
79
  return result.evidence_payload?.evidence_id ?? result.action_request_id ?? null;
69
80
  }
70
81
 
82
+ /** Stable sha256 of a tool response body for provenance + attestation. */
83
+ export function hashToolResponseBody(body: unknown): string {
84
+ const payload = typeof body === "string" ? body : JSON.stringify(body ?? null);
85
+ return `sha256:${createHash("sha256").update(payload).digest("hex")}`;
86
+ }
87
+
88
+ /**
89
+ * HMAC-sign a tool response content hash (Business+).
90
+ * Must match Auctra gate `signToolResponseAttestation` / `evaluateToolAttestation`.
91
+ */
92
+ export function signToolResponseAttestation(input: {
93
+ organizationId: string;
94
+ toolName: string;
95
+ contentHash: string;
96
+ /** Org attestation secret (same value configured as TOOL_ATTESTATION_SECRET on Auctra). */
97
+ secret: string;
98
+ issuedAt?: string;
99
+ keyId?: string;
100
+ }): ToolAttestation {
101
+ const issuedAt = input.issuedAt ?? new Date().toISOString();
102
+ const contentHash = input.contentHash.trim();
103
+ const toolName = input.toolName.trim();
104
+ const key = `${input.secret}:${input.organizationId}`;
105
+ const signature = createHmac("sha256", key)
106
+ .update(`${toolName}\n${contentHash}\n${issuedAt}`)
107
+ .digest("hex");
108
+ return {
109
+ content_hash: contentHash,
110
+ tool_name: toolName,
111
+ issued_at: issuedAt,
112
+ signature,
113
+ alg: "hmac-sha256",
114
+ key_id: input.keyId ?? "org-default",
115
+ };
116
+ }
117
+
71
118
  /**
72
119
  * Provenance for a tool/MCP result — attach on the *next* consequential
73
120
  * action.execute so Engine 03 treats the output as untrusted evidence.
@@ -128,10 +175,31 @@ export async function gateMcpToolCall(input: McpToolGateInput) {
128
175
  export function withAuctraGate<TArgs extends Record<string, unknown>, TResult>(
129
176
  input: McpToolGateInput & {
130
177
  execute: (args: TArgs) => Promise<TResult> | TResult;
178
+ /** When set with organizationId, HMAC-attest the tool response body (Business+). */
179
+ attestation?: {
180
+ organizationId: string;
181
+ secret: string;
182
+ };
131
183
  },
132
184
  ) {
133
185
  return async (args: TArgs) => {
134
186
  await gateMcpToolCall({ ...input, args });
135
- return input.execute(args);
187
+ const result = await input.execute(args);
188
+ if (!input.attestation) return result;
189
+ const contentHash = hashToolResponseBody(result);
190
+ const tool_attestation = signToolResponseAttestation({
191
+ organizationId: input.attestation.organizationId,
192
+ toolName: input.toolName,
193
+ contentHash,
194
+ secret: input.attestation.secret,
195
+ });
196
+ return {
197
+ result,
198
+ provenance: declareToolResponseInput({
199
+ toolName: input.toolName,
200
+ contentHash,
201
+ }),
202
+ tool_attestation,
203
+ };
136
204
  };
137
205
  }