@auctra/mcp-gateway 0.1.3 → 0.1.4
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 +39 -2
- package/package.json +2 -2
- package/src/index.js +91 -0
- package/src/index.ts +146 -0
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 {
|
|
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,38 @@ 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
|
+
## Engine 15 — MCP / skill install attestation
|
|
34
|
+
|
|
35
|
+
High-impact installs require `mcp_install` + HMAC `supply_chain_attestation` on the gate.
|
|
36
|
+
Use the **same secret** as Auctra `SUPPLY_CHAIN_ATTESTATION_SECRET` (falls back to `TOOL_ATTESTATION_SECRET`).
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
await gateMcpInstall({
|
|
40
|
+
client,
|
|
41
|
+
agentId: "agent-uuid",
|
|
42
|
+
organizationId: "org-uuid",
|
|
43
|
+
secret: process.env.SUPPLY_CHAIN_ATTESTATION_SECRET!,
|
|
44
|
+
install: { name: "trusted-mcp", version: "1.2.0", kind: "mcp" },
|
|
45
|
+
manifestBody: { tools: ["send_email"] }, // hashed when contentHash omitted
|
|
46
|
+
claimedIntentId: intent.id,
|
|
47
|
+
intentAnchorToken: intent.anchor_token,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Or attach metadata yourself on any action.execute:
|
|
51
|
+
const metadata = declareMcpInstallMetadata({
|
|
52
|
+
organizationId: "org-uuid",
|
|
53
|
+
secret: process.env.SUPPLY_CHAIN_ATTESTATION_SECRET!,
|
|
54
|
+
install: { name: "trusted-mcp", kind: "mcp" },
|
|
55
|
+
manifestBody: packageJson,
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Also exported: `hashMcpPackageManifest`, `signSupplyChainAttestation`.
|
|
60
|
+
|
|
61
|
+
## Engine 03 — tool response attestation
|
|
62
|
+
|
|
63
|
+
`withAuctraGate({ attestation: { organizationId, secret } })` HMAC-signs the tool body as `tool_attestation` for the next consequential `action.execute`.
|
|
64
|
+
|
|
28
65
|
## Stdio MCP server
|
|
29
66
|
|
|
30
67
|
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.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.4",
|
|
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.
|