@auctra/mcp-gateway 0.1.1

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 ADDED
@@ -0,0 +1,39 @@
1
+ # @auctra/mcp-gateway
2
+
3
+ Stage 0 MCP gateway adapter. Wrap MCP tool calls with Auctra `action.execute` before execution.
4
+
5
+ ```ts
6
+ import { Auctra } from "@auctra/sdk";
7
+ import { gateMcpToolCall, withAuctraGate } from "@auctra/mcp-gateway";
8
+
9
+ const client = new Auctra({ apiKey: process.env.AUCTRA_API_KEY! });
10
+
11
+ await gateMcpToolCall({
12
+ client,
13
+ agentId: "agent-uuid",
14
+ toolName: "send_email",
15
+ args: { to: "vendor@example.com" },
16
+ });
17
+
18
+ const sendEmail = withAuctraGate({
19
+ client,
20
+ agentId: "agent-uuid",
21
+ toolName: "send_email",
22
+ execute: async (args) => mailer.send(args),
23
+ });
24
+ ```
25
+
26
+ Blocked or approval-required tool calls throw `McpToolBlockedError` with the decision evidence hash when present.
27
+
28
+ ## Stdio MCP server
29
+
30
+ Run the package as a real MCP stdio server:
31
+
32
+ ```bash
33
+ AUCTRA_API_KEY=... AUCTRA_AGENT_ID=agent-uuid npx auctra-mcp-gateway
34
+ ```
35
+
36
+ Exposed tools:
37
+
38
+ - `gate_tool_call`
39
+ - `verify_audit_chain`
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@auctra/mcp-gateway",
3
+ "version": "0.1.1",
4
+ "description": "Stage 0 MCP gateway adapter — gate MCP tool calls through Auctra action.execute",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.js"
10
+ },
11
+ "bin": {
12
+ "auctra-mcp-gateway": "src/server.js"
13
+ },
14
+ "files": [
15
+ "src",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "node --input-type=module -e \"import('./src/index.js').then((m) => { if (typeof m.gateMcpToolCall !== 'function') throw new Error('gateMcpToolCall missing'); console.log('mcp-gateway build ok'); })\"",
20
+ "test": "npm run build && node --test test/*.test.mjs"
21
+ },
22
+ "auctra": {
23
+ "role": "implementation",
24
+ "note": "Single MCP authorization implementation. @auctra/mcp re-exports this package only."
25
+ },
26
+ "keywords": [
27
+ "auctra",
28
+ "mcp",
29
+ "authority",
30
+ "delegation"
31
+ ],
32
+ "license": "MIT",
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "engines": {
37
+ "node": ">=20"
38
+ }
39
+ }
package/src/index.js ADDED
@@ -0,0 +1,71 @@
1
+ export class McpToolBlockedError extends Error {
2
+ constructor(message, decision, evidence) {
3
+ super(message);
4
+ this.name = "McpToolBlockedError";
5
+ this.decision = decision;
6
+ this.evidence = evidence;
7
+ }
8
+ }
9
+
10
+ function resolveActionType(input) {
11
+ return input.actionType ?? input.actionTypeMap?.[input.toolName] ?? "execute_tool";
12
+ }
13
+
14
+ function evidenceIdFromResult(result) {
15
+ return result.evidence_payload?.evidence_id ?? result.action_request_id ?? null;
16
+ }
17
+
18
+ /** Provenance for a tool/MCP result — attach on the next consequential action.execute. */
19
+ export function declareToolResponseInput(input) {
20
+ return {
21
+ source: `mcp:${input.toolName}`,
22
+ source_type: "mcp_tool_response",
23
+ trust_level: "untrusted",
24
+ authority: "none",
25
+ content_hash: input.contentHash,
26
+ received_at: new Date().toISOString(),
27
+ source_id: input.sourceId,
28
+ };
29
+ }
30
+
31
+ export async function gateMcpToolCall(input) {
32
+ const result = await input.client.action.execute({
33
+ agentId: input.agentId,
34
+ actionType: resolveActionType(input),
35
+ payload: {
36
+ tool: input.toolName,
37
+ arguments: input.args ?? {},
38
+ },
39
+ claimedIntentId: input.claimedIntentId,
40
+ intentAnchorToken: input.intentAnchorToken,
41
+ action: {
42
+ target: `mcp:${input.toolName}`,
43
+ description: `MCP tool ${input.toolName}`,
44
+ metadata: {
45
+ inputs: [
46
+ {
47
+ source: `mcp:${input.toolName}:invocation`,
48
+ source_type: "mcp_tool_response",
49
+ trust_level: "untrusted",
50
+ authority: "none",
51
+ },
52
+ ],
53
+ },
54
+ },
55
+ });
56
+
57
+ if (result.decision !== "allowed") {
58
+ const evidenceId = evidenceIdFromResult(result);
59
+ const message = evidenceId ? `${result.reason} (evidence: ${evidenceId})` : result.reason;
60
+ throw new McpToolBlockedError(message, result.decision, result.evidence);
61
+ }
62
+
63
+ return result;
64
+ }
65
+
66
+ export function withAuctraGate(input) {
67
+ return async (args) => {
68
+ await gateMcpToolCall({ ...input, args });
69
+ return input.execute(args);
70
+ };
71
+ }
package/src/index.ts ADDED
@@ -0,0 +1,137 @@
1
+ export type ExecuteActionClient = {
2
+ action: {
3
+ execute(input: {
4
+ agentId: string;
5
+ actionType: string;
6
+ payload?: Record<string, unknown>;
7
+ claimedIntentId?: string;
8
+ intentAnchorToken?: string;
9
+ action?: {
10
+ target?: string;
11
+ description?: string;
12
+ riskLevel?: string;
13
+ metadata?: Record<string, unknown>;
14
+ };
15
+ }): Promise<{
16
+ decision: "allowed" | "blocked" | "require_approval";
17
+ reason: string;
18
+ action_request_id?: string;
19
+ evidence?: {
20
+ format: string;
21
+ payload_hash: string;
22
+ signed: boolean;
23
+ };
24
+ evidence_payload?: { evidence_id?: string };
25
+ }>;
26
+ };
27
+ };
28
+
29
+ /** @deprecated Use ExecuteActionClient — kept as type alias for migration notes only */
30
+ export type EvaluateActionClient = ExecuteActionClient;
31
+
32
+ export type McpToolGateInput = {
33
+ client: ExecuteActionClient;
34
+ agentId: string;
35
+ toolName: string;
36
+ args?: Record<string, unknown>;
37
+ /** Explicit action type override (wins over actionTypeMap). */
38
+ actionType?: string;
39
+ /** Optional map from MCP tool name → Auctra action_type. */
40
+ actionTypeMap?: Record<string, string>;
41
+ claimedIntentId?: string;
42
+ intentAnchorToken?: string;
43
+ };
44
+
45
+ export class McpToolBlockedError extends Error {
46
+ readonly decision: "blocked" | "require_approval";
47
+ readonly evidence?: Awaited<ReturnType<ExecuteActionClient["action"]["execute"]>>["evidence"];
48
+
49
+ constructor(
50
+ message: string,
51
+ decision: "blocked" | "require_approval",
52
+ evidence?: Awaited<ReturnType<ExecuteActionClient["action"]["execute"]>>["evidence"],
53
+ ) {
54
+ super(message);
55
+ this.name = "McpToolBlockedError";
56
+ this.decision = decision;
57
+ this.evidence = evidence;
58
+ }
59
+ }
60
+
61
+ function resolveActionType(input: McpToolGateInput) {
62
+ return input.actionType ?? input.actionTypeMap?.[input.toolName] ?? "execute_tool";
63
+ }
64
+
65
+ function evidenceIdFromResult(
66
+ result: Awaited<ReturnType<ExecuteActionClient["action"]["execute"]>>,
67
+ ) {
68
+ return result.evidence_payload?.evidence_id ?? result.action_request_id ?? null;
69
+ }
70
+
71
+ /**
72
+ * Provenance for a tool/MCP result — attach on the *next* consequential
73
+ * action.execute so Engine 03 treats the output as untrusted evidence.
74
+ */
75
+ export function declareToolResponseInput(input: {
76
+ toolName: string;
77
+ contentHash?: string;
78
+ sourceId?: string;
79
+ }) {
80
+ return {
81
+ source: `mcp:${input.toolName}`,
82
+ source_type: "mcp_tool_response" as const,
83
+ trust_level: "untrusted" as const,
84
+ authority: "none" as const,
85
+ content_hash: input.contentHash,
86
+ received_at: new Date().toISOString(),
87
+ source_id: input.sourceId,
88
+ };
89
+ }
90
+
91
+ /** Gate an MCP tool invocation through Auctra action.execute (Authority Runtime). */
92
+ export async function gateMcpToolCall(input: McpToolGateInput) {
93
+ const result = await input.client.action.execute({
94
+ agentId: input.agentId,
95
+ actionType: resolveActionType(input),
96
+ payload: {
97
+ tool: input.toolName,
98
+ arguments: input.args ?? {},
99
+ },
100
+ claimedIntentId: input.claimedIntentId,
101
+ intentAnchorToken: input.intentAnchorToken,
102
+ action: {
103
+ target: `mcp:${input.toolName}`,
104
+ description: `MCP tool ${input.toolName}`,
105
+ metadata: {
106
+ inputs: [
107
+ {
108
+ source: `mcp:${input.toolName}:invocation`,
109
+ source_type: "mcp_tool_response",
110
+ trust_level: "untrusted",
111
+ authority: "none",
112
+ },
113
+ ],
114
+ },
115
+ },
116
+ });
117
+
118
+ if (result.decision !== "allowed") {
119
+ const evidenceId = evidenceIdFromResult(result);
120
+ const message = evidenceId ? `${result.reason} (evidence: ${evidenceId})` : result.reason;
121
+ throw new McpToolBlockedError(message, result.decision, result.evidence);
122
+ }
123
+
124
+ return result;
125
+ }
126
+
127
+ /** Wrap a tool executor so every call is executed under Auctra authority first. */
128
+ export function withAuctraGate<TArgs extends Record<string, unknown>, TResult>(
129
+ input: McpToolGateInput & {
130
+ execute: (args: TArgs) => Promise<TResult> | TResult;
131
+ },
132
+ ) {
133
+ return async (args: TArgs) => {
134
+ await gateMcpToolCall({ ...input, args });
135
+ return input.execute(args);
136
+ };
137
+ }
package/src/server.js ADDED
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ import { gateMcpToolCall } from "./index.js";
3
+
4
+ const API_KEY = process.env.AUCTRA_API_KEY;
5
+ const BASE_URL = (process.env.AUCTRA_BASE_URL ?? "https://app.auctra.tech").replace(/\/$/, "");
6
+ const DEFAULT_AGENT_ID = process.env.AUCTRA_AGENT_ID ?? null;
7
+
8
+ function writeMessage(message) {
9
+ const body = Buffer.from(JSON.stringify(message), "utf8");
10
+ process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`);
11
+ process.stdout.write(body);
12
+ }
13
+
14
+ async function auctraRequest(path, body) {
15
+ if (!API_KEY) {
16
+ throw new Error("AUCTRA_API_KEY is required for the MCP gateway server");
17
+ }
18
+
19
+ const response = await fetch(`${BASE_URL}${path}`, {
20
+ method: "POST",
21
+ headers: {
22
+ authorization: `Bearer ${API_KEY}`,
23
+ "content-type": "application/json",
24
+ },
25
+ body: JSON.stringify(body),
26
+ });
27
+ const payload = await response.json().catch(() => ({}));
28
+ if (!response.ok) {
29
+ throw new Error(
30
+ typeof payload.error === "string" ? payload.error : `Auctra API error (${response.status})`,
31
+ );
32
+ }
33
+ return payload;
34
+ }
35
+
36
+ const client = {
37
+ action: {
38
+ execute(input) {
39
+ return auctraRequest("/v1/actions/execute", {
40
+ agent_id: input.agentId,
41
+ action_type: input.actionType,
42
+ payload: input.payload ?? {},
43
+ dry_run: false,
44
+ });
45
+ },
46
+ },
47
+ };
48
+
49
+ async function handleRpc(message) {
50
+ if (message.method === "initialize") {
51
+ return {
52
+ protocolVersion: "2025-06-18",
53
+ capabilities: { tools: {} },
54
+ serverInfo: { name: "auctra-mcp-gateway", version: "0.1.0" },
55
+ };
56
+ }
57
+
58
+ if (message.method === "tools/list") {
59
+ return {
60
+ tools: [
61
+ {
62
+ name: "gate_tool_call",
63
+ description: "Gate an MCP tool invocation through Auctra authority evaluation.",
64
+ inputSchema: {
65
+ type: "object",
66
+ required: ["tool_name"],
67
+ properties: {
68
+ agent_id: { type: "string", description: "Optional override for AUCTRA_AGENT_ID" },
69
+ tool_name: { type: "string" },
70
+ action_type: { type: "string", default: "execute_tool" },
71
+ args: { type: "object", additionalProperties: true },
72
+ },
73
+ },
74
+ },
75
+ {
76
+ name: "verify_audit_chain",
77
+ description:
78
+ "Verify the Auctra hash-chained audit ledger for the current API key organization.",
79
+ inputSchema: { type: "object", properties: {} },
80
+ },
81
+ ],
82
+ };
83
+ }
84
+
85
+ if (message.method === "tools/call") {
86
+ const { name, arguments: args = {} } = message.params ?? {};
87
+ if (name === "gate_tool_call") {
88
+ const agentId = args.agent_id ?? DEFAULT_AGENT_ID;
89
+ if (!agentId) throw new Error("agent_id is required or set AUCTRA_AGENT_ID");
90
+ const result = await gateMcpToolCall({
91
+ client,
92
+ agentId,
93
+ toolName: args.tool_name,
94
+ actionType: args.action_type,
95
+ args: args.args ?? {},
96
+ });
97
+ return {
98
+ content: [
99
+ {
100
+ type: "text",
101
+ text: JSON.stringify(result),
102
+ },
103
+ ],
104
+ };
105
+ }
106
+
107
+ if (name === "verify_audit_chain") {
108
+ if (!API_KEY) throw new Error("AUCTRA_API_KEY is required for audit verification");
109
+ const response = await fetch(`${BASE_URL}/v1/audit-events/verify`, {
110
+ headers: { authorization: `Bearer ${API_KEY}` },
111
+ });
112
+ const payload = await response.json().catch(() => ({}));
113
+ if (!response.ok) {
114
+ throw new Error(
115
+ typeof payload.error === "string"
116
+ ? payload.error
117
+ : `Auctra API error (${response.status})`,
118
+ );
119
+ }
120
+ return {
121
+ content: [
122
+ {
123
+ type: "text",
124
+ text: JSON.stringify(payload),
125
+ },
126
+ ],
127
+ };
128
+ }
129
+
130
+ throw new Error(`Unknown tool: ${name}`);
131
+ }
132
+
133
+ return null;
134
+ }
135
+
136
+ let buffer = Buffer.alloc(0);
137
+ process.stdin.on("data", async (chunk) => {
138
+ buffer = Buffer.concat([buffer, chunk]);
139
+
140
+ while (true) {
141
+ const headerEnd = buffer.indexOf("\r\n\r\n");
142
+ if (headerEnd === -1) break;
143
+
144
+ const headerText = buffer.slice(0, headerEnd).toString("utf8");
145
+ const match = headerText.match(/Content-Length:\s*(\d+)/i);
146
+ if (!match) {
147
+ buffer = Buffer.alloc(0);
148
+ break;
149
+ }
150
+
151
+ const length = Number(match[1]);
152
+ const total = headerEnd + 4 + length;
153
+ if (buffer.length < total) break;
154
+
155
+ const raw = buffer.slice(headerEnd + 4, total).toString("utf8");
156
+ buffer = buffer.slice(total);
157
+
158
+ const message = JSON.parse(raw);
159
+ if (!("id" in message) || !message.method) continue;
160
+
161
+ try {
162
+ const result = await handleRpc(message);
163
+ writeMessage({ jsonrpc: "2.0", id: message.id, result });
164
+ } catch (error) {
165
+ writeMessage({
166
+ jsonrpc: "2.0",
167
+ id: message.id,
168
+ error: {
169
+ code: -32000,
170
+ message: error instanceof Error ? error.message : "Unknown error",
171
+ },
172
+ });
173
+ }
174
+ }
175
+ });