@auctra/mcp-gateway 0.1.3 → 0.1.5

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,10 +1,15 @@
1
1
  # @auctra/mcp-gateway
2
2
 
3
- Stage 0 MCP gateway adapter. Wrap MCP tool calls with Auctra **Authority Gate** (`action.execute`) before execution.
3
+ Stage 0 MCP gateway adapter. Wrap MCP tool calls **and installs** with Auctra **Authority Gate** (`action.execute`) before execution.
4
4
 
5
5
  ```ts
6
6
  import { Auctra } from "@auctra/sdk";
7
- import { gateMcpToolCall, withAuctraGate } from "@auctra/mcp-gateway";
7
+ import {
8
+ gateMcpToolCall,
9
+ gateMcpInstall,
10
+ withAuctraGate,
11
+ declareMcpInstallMetadata,
12
+ } from "@auctra/mcp-gateway";
8
13
 
9
14
  const client = new Auctra({ apiKey: process.env.AUCTRA_API_KEY! });
10
15
 
@@ -25,6 +30,56 @@ const sendEmail = withAuctraGate({
25
30
 
26
31
  Blocked or approval-required tool calls throw `McpToolBlockedError` with the decision evidence hash when present.
27
32
 
33
+ ## NL→SQL data scope (`gateMcpSqlQuery`)
34
+
35
+ Declare requested vs authorized query scope so the production gate can emit `SQL_SCOPE_EXCEEDED`:
36
+
37
+ ```ts
38
+ import { gateMcpSqlQuery } from "@auctra/mcp-gateway";
39
+
40
+ await gateMcpSqlQuery({
41
+ client,
42
+ agentId: "agent-uuid",
43
+ toolName: "customer_query",
44
+ requestedScope: "customers.*",
45
+ authorizedScope: "customers.aggregate_metrics",
46
+ claimedIntentId: intent.id,
47
+ intentAnchorToken: intent.anchor_token,
48
+ });
49
+ ```
50
+
51
+ ## Engine 15 — MCP / skill install attestation
52
+
53
+ High-impact installs require `mcp_install` + HMAC `supply_chain_attestation` on the gate.
54
+ Use the **same secret** as Auctra `SUPPLY_CHAIN_ATTESTATION_SECRET` (falls back to `TOOL_ATTESTATION_SECRET`).
55
+
56
+ ```ts
57
+ await gateMcpInstall({
58
+ client,
59
+ agentId: "agent-uuid",
60
+ organizationId: "org-uuid",
61
+ secret: process.env.SUPPLY_CHAIN_ATTESTATION_SECRET!,
62
+ install: { name: "trusted-mcp", version: "1.2.0", kind: "mcp" },
63
+ manifestBody: { tools: ["send_email"] }, // hashed when contentHash omitted
64
+ claimedIntentId: intent.id,
65
+ intentAnchorToken: intent.anchor_token,
66
+ });
67
+
68
+ // Or attach metadata yourself on any action.execute:
69
+ const metadata = declareMcpInstallMetadata({
70
+ organizationId: "org-uuid",
71
+ secret: process.env.SUPPLY_CHAIN_ATTESTATION_SECRET!,
72
+ install: { name: "trusted-mcp", kind: "mcp" },
73
+ manifestBody: packageJson,
74
+ });
75
+ ```
76
+
77
+ Also exported: `hashMcpPackageManifest`, `signSupplyChainAttestation`.
78
+
79
+ ## Engine 03 — tool response attestation
80
+
81
+ `withAuctraGate({ attestation: { organizationId, secret } })` HMAC-signs the tool body as `tool_attestation` for the next consequential `action.execute`.
82
+
28
83
  ## Stdio MCP server
29
84
 
30
85
  Run the package as a real MCP stdio server:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@auctra/mcp-gateway",
3
- "version": "0.1.3",
4
- "description": "Stage 0 MCP gateway adapter — gate MCP tool calls through Auctra action.execute",
3
+ "version": "0.1.5",
4
+ "description": "MCP gateway — gate tool calls and MCP/skill installs through Auctra action.execute",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./src/index.ts",
package/src/index.js CHANGED
@@ -45,6 +45,97 @@ export function signToolResponseAttestation(input) {
45
45
  };
46
46
  }
47
47
 
48
+ /** Stable sha256 of an MCP/skill package manifest (Engine 15). */
49
+ export function hashMcpPackageManifest(manifest) {
50
+ const payload = typeof manifest === "string" ? manifest : JSON.stringify(manifest ?? null);
51
+ return `sha256:${createHash("sha256").update(payload).digest("hex")}`;
52
+ }
53
+
54
+ function installAttestationKey(organizationId, secret) {
55
+ return `${secret}:install:${organizationId}`;
56
+ }
57
+
58
+ /**
59
+ * HMAC-sign an MCP/skill install (Engine 15).
60
+ * Must match Auctra gate signSupplyChainAttestation / evaluateAgentSupplyChain.
61
+ */
62
+ export function signSupplyChainAttestation(input) {
63
+ const issuedAt = input.issuedAt ?? new Date().toISOString();
64
+ const contentHash = input.contentHash.trim();
65
+ const packageName = input.packageName.trim();
66
+ const signature = createHmac("sha256", installAttestationKey(input.organizationId, input.secret))
67
+ .update(`${packageName}\n${contentHash}\n${issuedAt}`)
68
+ .digest("hex");
69
+ return {
70
+ content_hash: contentHash,
71
+ package_name: packageName,
72
+ issued_at: issuedAt,
73
+ signature,
74
+ alg: "hmac-sha256",
75
+ key_id: input.keyId ?? "org-default",
76
+ };
77
+ }
78
+
79
+ /** Metadata bag for action.execute — Engine 15 mcp_install + supply_chain_attestation. */
80
+ export function declareMcpInstallMetadata(input) {
81
+ const name = input.install.name.trim();
82
+ const contentHash =
83
+ input.install.contentHash?.trim() ||
84
+ hashMcpPackageManifest(input.manifestBody ?? { name, version: input.install.version ?? null });
85
+ const kind = input.install.kind ?? "mcp";
86
+ return {
87
+ mcp_install: {
88
+ name,
89
+ ...(input.install.version ? { version: input.install.version } : {}),
90
+ content_hash: contentHash,
91
+ kind,
92
+ },
93
+ supply_chain_attestation: signSupplyChainAttestation({
94
+ organizationId: input.organizationId,
95
+ packageName: name,
96
+ contentHash,
97
+ secret: input.secret,
98
+ issuedAt: input.issuedAt,
99
+ keyId: input.keyId,
100
+ }),
101
+ };
102
+ }
103
+
104
+ /** Gate an MCP/skill install through action.execute with Engine 15 attestation. */
105
+ export async function gateMcpInstall(input) {
106
+ const metadata = declareMcpInstallMetadata({
107
+ organizationId: input.organizationId,
108
+ secret: input.secret,
109
+ install: input.install,
110
+ manifestBody: input.manifestBody,
111
+ });
112
+ const result = await input.client.action.execute({
113
+ agentId: input.agentId,
114
+ actionType: input.actionType ?? "execute_tool",
115
+ claimedIntentId: input.claimedIntentId,
116
+ intentAnchorToken: input.intentAnchorToken,
117
+ payload: {
118
+ tool: "mcp_install",
119
+ package: input.install.name,
120
+ version: input.install.version ?? null,
121
+ },
122
+ action: {
123
+ target: `mcp-install:${input.install.name}`,
124
+ description: `Install MCP/skill package ${input.install.name}`,
125
+ riskLevel: input.riskLevel ?? "high",
126
+ metadata,
127
+ },
128
+ });
129
+
130
+ if (result.decision !== "allowed") {
131
+ const evidenceId = evidenceIdFromResult(result);
132
+ const message = evidenceId ? `${result.reason} (evidence: ${evidenceId})` : result.reason;
133
+ throw new McpToolBlockedError(message, result.decision, result.evidence);
134
+ }
135
+
136
+ return { ...result, metadata };
137
+ }
138
+
48
139
  /** Provenance for a tool/MCP result — attach on the next consequential action.execute. */
49
140
  export function declareToolResponseInput(input) {
50
141
  return {
package/src/index.ts CHANGED
@@ -53,6 +53,22 @@ export type ToolAttestation = {
53
53
  key_id?: string;
54
54
  };
55
55
 
56
+ export type SupplyChainAttestation = {
57
+ content_hash: string;
58
+ package_name: string;
59
+ issued_at: string;
60
+ signature: string;
61
+ alg: "hmac-sha256";
62
+ key_id?: string;
63
+ };
64
+
65
+ export type McpInstallManifest = {
66
+ name: string;
67
+ version?: string;
68
+ contentHash?: string;
69
+ kind?: "mcp" | "skill" | "tool_schema" | "model";
70
+ };
71
+
56
72
  export class McpToolBlockedError extends Error {
57
73
  readonly decision: "blocked" | "require_approval";
58
74
  readonly evidence?: Awaited<ReturnType<ExecuteActionClient["action"]["execute"]>>["evidence"];
@@ -115,6 +131,136 @@ export function signToolResponseAttestation(input: {
115
131
  };
116
132
  }
117
133
 
134
+ /** Stable sha256 of an MCP/skill package manifest (Engine 15). */
135
+ export function hashMcpPackageManifest(manifest: unknown): string {
136
+ const payload = typeof manifest === "string" ? manifest : JSON.stringify(manifest ?? null);
137
+ return `sha256:${createHash("sha256").update(payload).digest("hex")}`;
138
+ }
139
+
140
+ function installAttestationKey(organizationId: string, secret: string) {
141
+ // Must match Auctra `installSecret` in supply-chain.server.ts
142
+ return `${secret}:install:${organizationId}`;
143
+ }
144
+
145
+ /**
146
+ * HMAC-sign an MCP/skill install (Engine 15).
147
+ * Must match Auctra gate `signSupplyChainAttestation` / `evaluateAgentSupplyChain`.
148
+ * Use the same secret as `SUPPLY_CHAIN_ATTESTATION_SECRET` (or `TOOL_ATTESTATION_SECRET`) on the gate.
149
+ */
150
+ export function signSupplyChainAttestation(input: {
151
+ organizationId: string;
152
+ packageName: string;
153
+ contentHash: string;
154
+ secret: string;
155
+ issuedAt?: string;
156
+ keyId?: string;
157
+ }): SupplyChainAttestation {
158
+ const issuedAt = input.issuedAt ?? new Date().toISOString();
159
+ const contentHash = input.contentHash.trim();
160
+ const packageName = input.packageName.trim();
161
+ const signature = createHmac("sha256", installAttestationKey(input.organizationId, input.secret))
162
+ .update(`${packageName}\n${contentHash}\n${issuedAt}`)
163
+ .digest("hex");
164
+ return {
165
+ content_hash: contentHash,
166
+ package_name: packageName,
167
+ issued_at: issuedAt,
168
+ signature,
169
+ alg: "hmac-sha256",
170
+ key_id: input.keyId ?? "org-default",
171
+ };
172
+ }
173
+
174
+ /** Metadata bag for action.execute — Engine 15 mcp_install + supply_chain_attestation. */
175
+ export function declareMcpInstallMetadata(input: {
176
+ organizationId: string;
177
+ secret: string;
178
+ install: McpInstallManifest;
179
+ /** Raw manifest/schema used to derive content_hash when install.contentHash omitted. */
180
+ manifestBody?: unknown;
181
+ issuedAt?: string;
182
+ keyId?: string;
183
+ }): {
184
+ mcp_install: {
185
+ name: string;
186
+ version?: string;
187
+ content_hash: string;
188
+ kind: "mcp" | "skill" | "tool_schema" | "model";
189
+ };
190
+ supply_chain_attestation: SupplyChainAttestation;
191
+ } {
192
+ const name = input.install.name.trim();
193
+ const contentHash =
194
+ input.install.contentHash?.trim() ||
195
+ hashMcpPackageManifest(input.manifestBody ?? { name, version: input.install.version ?? null });
196
+ const kind = input.install.kind ?? "mcp";
197
+ return {
198
+ mcp_install: {
199
+ name,
200
+ ...(input.install.version ? { version: input.install.version } : {}),
201
+ content_hash: contentHash,
202
+ kind,
203
+ },
204
+ supply_chain_attestation: signSupplyChainAttestation({
205
+ organizationId: input.organizationId,
206
+ packageName: name,
207
+ contentHash,
208
+ secret: input.secret,
209
+ issuedAt: input.issuedAt,
210
+ keyId: input.keyId,
211
+ }),
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Gate an MCP/skill install through action.execute with Engine 15 attestation attached.
217
+ * Prefer action types matching install/register (high+ requires attestation on the gate).
218
+ */
219
+ export async function gateMcpInstall(input: {
220
+ client: ExecuteActionClient;
221
+ agentId: string;
222
+ organizationId: string;
223
+ secret: string;
224
+ install: McpInstallManifest;
225
+ manifestBody?: unknown;
226
+ actionType?: string;
227
+ claimedIntentId?: string;
228
+ intentAnchorToken?: string;
229
+ riskLevel?: string;
230
+ }) {
231
+ const metadata = declareMcpInstallMetadata({
232
+ organizationId: input.organizationId,
233
+ secret: input.secret,
234
+ install: input.install,
235
+ manifestBody: input.manifestBody,
236
+ });
237
+ const result = await input.client.action.execute({
238
+ agentId: input.agentId,
239
+ actionType: input.actionType ?? "execute_tool",
240
+ claimedIntentId: input.claimedIntentId,
241
+ intentAnchorToken: input.intentAnchorToken,
242
+ payload: {
243
+ tool: "mcp_install",
244
+ package: input.install.name,
245
+ version: input.install.version ?? null,
246
+ },
247
+ action: {
248
+ target: `mcp-install:${input.install.name}`,
249
+ description: `Install MCP/skill package ${input.install.name}`,
250
+ riskLevel: input.riskLevel ?? "high",
251
+ metadata,
252
+ },
253
+ });
254
+
255
+ if (result.decision !== "allowed") {
256
+ const evidenceId = evidenceIdFromResult(result);
257
+ const message = evidenceId ? `${result.reason} (evidence: ${evidenceId})` : result.reason;
258
+ throw new McpToolBlockedError(message, result.decision, result.evidence);
259
+ }
260
+
261
+ return { ...result, metadata };
262
+ }
263
+
118
264
  /**
119
265
  * Provenance for a tool/MCP result — attach on the *next* consequential
120
266
  * action.execute so Engine 03 treats the output as untrusted evidence.
@@ -203,3 +349,62 @@ export function withAuctraGate<TArgs extends Record<string, unknown>, TResult>(
203
349
  };
204
350
  };
205
351
  }
352
+
353
+ /**
354
+ * Gate an NL→SQL / database tool call with declared data scope metadata.
355
+ * Production path: same action.execute gate companies use — requested ⊆ authorized.
356
+ */
357
+ export async function gateMcpSqlQuery(input: {
358
+ client: ExecuteActionClient;
359
+ agentId: string;
360
+ toolName: string;
361
+ requestedScope: string | string[];
362
+ authorizedScope?: string | string[];
363
+ args?: Record<string, unknown>;
364
+ actionType?: string;
365
+ claimedIntentId?: string;
366
+ intentAnchorToken?: string;
367
+ }) {
368
+ const result = await input.client.action.execute({
369
+ agentId: input.agentId,
370
+ actionType: input.actionType ?? "execute_sql",
371
+ claimedIntentId: input.claimedIntentId,
372
+ intentAnchorToken: input.intentAnchorToken,
373
+ payload: {
374
+ tool: input.toolName,
375
+ arguments: input.args ?? {},
376
+ resource: Array.isArray(input.requestedScope)
377
+ ? input.requestedScope[0]
378
+ : input.requestedScope,
379
+ },
380
+ action: {
381
+ target: `mcp-sql:${input.toolName}`,
382
+ description: `SQL/query tool ${input.toolName}`,
383
+ metadata: {
384
+ sql: {
385
+ requested_scope: input.requestedScope,
386
+ ...(input.authorizedScope != null
387
+ ? { authorized_scope: input.authorizedScope }
388
+ : {}),
389
+ },
390
+ inputs: [
391
+ {
392
+ source: `mcp:${input.toolName}:sql`,
393
+ source_type: "mcp_tool_response",
394
+ trust_level: "untrusted",
395
+ authority: "none",
396
+ claimed_influence: "privilege_escalation",
397
+ },
398
+ ],
399
+ },
400
+ },
401
+ });
402
+
403
+ if (result.decision !== "allowed") {
404
+ const evidenceId = evidenceIdFromResult(result);
405
+ const message = evidenceId ? `${result.reason} (evidence: ${evidenceId})` : result.reason;
406
+ throw new McpToolBlockedError(message, result.decision, result.evidence);
407
+ }
408
+
409
+ return result;
410
+ }