@atbash/atbash-mcp 0.0.12-dev.0 → 0.0.12

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.
Files changed (3) hide show
  1. package/README.md +4 -56
  2. package/dist/index.js +23 -128
  3. package/package.json +3 -7
package/README.md CHANGED
@@ -84,14 +84,7 @@ Returns: `{ verdict, allow, reason, tool_call_id }`
84
84
 
85
85
  #### `atbash_log`
86
86
 
87
- Submit a tool call through Atbash's redacting safety-judgment path. The judge
88
- logs/broadcasts the call and returns the same fail-closed decision contract as
89
- `atbash_judge`: `{ verdict, allow, reason, toolCallId }`.
90
-
91
- Despite its legacy name, this is not a distinct non-judging log operation. The
92
- pinned SDK has no server-broadcasting audit-only API; its `logToolCall()` method
93
- only creates a local signed transaction, so this MCP tool deliberately does not
94
- use or expose that local-only result as proof of persistence.
87
+ Log a tool call on-chain without requesting a verdict.
95
88
 
96
89
  | Input | Type | Required |
97
90
  |---|---|---|
@@ -137,55 +130,10 @@ Poll the status of a previously submitted judgment.
137
130
 
138
131
  | Verdict | Meaning | What To Do |
139
132
  |---|---|---|
140
- | `ALLOW` | Safe to proceed | Execute the real tool only when `allow` is also exactly `true` |
141
- | `HOLD` | Pending operator review | Do not proceed; keep `tool_call_id` to poll status later |
133
+ | `ALLOW` | Safe to proceed | Execute the real tool |
134
+ | `HOLD` | Logged for async review | Execution is allowed; the action is flagged for operator review in the background — keep `tool_call_id` to poll status later |
142
135
  | `BLOCK` | Policy violation | Stop; show `reason` to operator |
143
- | `ERROR` | Judge unreachable | Fail closed — do not proceed |
144
-
145
- Unknown or missing verdicts, and `ALLOW` responses carrying `allow: false`, are
146
- also returned with `allow: false`.
147
-
148
- ### Enforcement is advisory in MCP
149
-
150
- An MCP server exposes tools; it cannot intercept the *other* tools your client
151
- calls. So this package can tell you an action is unsafe, but it cannot stop the
152
- client from running it — the model has to honour the verdict. If a verdict is
153
- unreachable, both `atbash_judge` and `atbash_log` return
154
- `{"verdict": "ERROR", "allow": false}` with an explicit instruction not to
155
- proceed, rather than a bare transport error that is easy to mistake for a
156
- broken tool.
157
-
158
- For enforcement that a compromised or prompt-injected agent cannot talk its way
159
- past, use a plugin that wraps the tool-call boundary itself — for example
160
- `@atbash/atbash-langchain`, `@atbash/atbash-langgraph`, the OpenClaw plugin, or
161
- the Codex/ChatGPT hook, all of which refuse the call before the tool executes.
162
-
163
- An `ALLOW` also has to have a verdict behind it. `@atbash/sdk` 0.5.3-dev.0
164
- treats a judge response body with no `verdict` field as an audit-tier pass and
165
- synthesizes `{"allow": true, "verdict": "ALLOW"}` from it — including for a body
166
- that goes on to say `"action_type": "block"`. The shipped configuration points
167
- at an allowlisted endpoint, for which the SDK sets no `verifyPubKey` and so
168
- skips response-signature verification entirely, which leaves schema drift, a
169
- truncated response and a rewriting proxy all arriving as permission. This
170
- package therefore re-checks every allow against the `JudgeResult` the SDK parsed
171
- that body into, and refuses it with `{"verdict": "ERROR", "allow": false}`
172
- unless the judge sent a verdict and an `action_type` of exactly `allow`.
173
-
174
- Be precise about what that inspects, because it is one layer above the HTTP
175
- body: `action_type` is the response body's value verbatim, whereas the verdict
176
- has already been case-folded and aliased by the SDK. So `"verdict": "green"`
177
- is honoured as an `ALLOW` — `GREEN` is the judge's own alias, not drift — while
178
- an absent verdict is refused. An organization on a logging-only tier therefore
179
- sees denials rather than passes; that is deliberate, since absence of a verdict
180
- is not a verdict.
181
-
182
- The judge endpoint is resolved through `Atbash.fromConfig`, which validates
183
- `ATBASH_ENDPOINT` against the SDK's trusted allowlist / self-hosted policy. The
184
- plain `new Atbash(privkey, { endpoint })` constructor performs no such check,
185
- so an injected endpoint would be trusted outright. Startup also requires the
186
- endpoint-validation and fail-closed audit capabilities of the pinned
187
- `@atbash/sdk`; the process refuses to start if they are absent, rather than
188
- running with an inert safety gate.
136
+ | `ERROR` | Judge unreachable | Fail closed |
189
137
 
190
138
  ## Sending Better Inputs
191
139
 
package/dist/index.js CHANGED
@@ -16,10 +16,10 @@ function registerSafetyPrompt(server2) {
16
16
  "atbash_safety_check",
17
17
  {
18
18
  description: "Prepare a consistent pre-execution Atbash safety review request.",
19
- argsSchema: {
19
+ inputSchema: z.object({
20
20
  action: z.string().describe("Action to evaluate"),
21
21
  context: z.string().describe("Why the action is being taken")
22
- }
22
+ })
23
23
  },
24
24
  async ({ action, context }) => ({
25
25
  messages: [
@@ -46,86 +46,6 @@ function registerPolicyResource(server2, client2) {
46
46
  void client2;
47
47
  }
48
48
 
49
- // src/startup.ts
50
- function assertMcpSdkCapabilities(sdk) {
51
- const missing = [
52
- typeof sdk.fromConfig === "function" ? null : "fromConfig",
53
- typeof sdk.prototype?.auditToolCall === "function" ? null : "auditToolCall"
54
- ].filter((name) => name !== null);
55
- if (missing.length > 0) {
56
- throw new Error(
57
- `Incompatible @atbash/sdk: missing required MCP capabilities: ${missing.join(", ")}. Refusing to start without an active safety gate.`
58
- );
59
- }
60
- }
61
- var DECISION_VERDICTS = [
62
- "ALLOW",
63
- "HOLD",
64
- "BLOCK",
65
- "ERROR"
66
- ];
67
- function isDecisionShaped(value) {
68
- const decision = value;
69
- return typeof decision === "object" && decision !== null && typeof decision.allow === "boolean" && DECISION_VERDICTS.includes(decision.verdict);
70
- }
71
- function hardenJudgeClient(client2) {
72
- const sdkAudit = client2.auditToolCall;
73
- const sdkJudge = client2.judgeAction;
74
- async function guardedAuditToolCall(input) {
75
- let wire;
76
- const shadow = Object.create(client2);
77
- Object.defineProperty(shadow, "judgeAction", {
78
- value: async (...args) => {
79
- const result = await sdkJudge.apply(client2, args);
80
- wire = result;
81
- return result;
82
- }
83
- });
84
- const decision = await sdkAudit.call(shadow, input);
85
- if (!isDecisionShaped(decision)) {
86
- throw new Error(
87
- "judge decision is not verdict-shaped and cannot be trusted"
88
- );
89
- }
90
- if (decision.verdict !== "ALLOW" || decision.allow !== true) {
91
- return { ...decision, allow: false };
92
- }
93
- if (wire === void 0) {
94
- throw new Error(
95
- "judge returned an allow without a judge response to back it"
96
- );
97
- }
98
- if (wire.verdict !== "ALLOW" || wire.actionType !== "allow") {
99
- throw new Error(
100
- `judge response body does not carry an allow (verdict ${JSON.stringify(wire.verdict)}, action_type ${JSON.stringify(wire.actionType)})`
101
- );
102
- }
103
- return decision;
104
- }
105
- Object.defineProperty(client2, "auditToolCall", {
106
- value: guardedAuditToolCall,
107
- writable: true,
108
- configurable: true
109
- });
110
- return client2;
111
- }
112
- function createAtbashClient(env, sdk) {
113
- assertMcpSdkCapabilities(sdk);
114
- const agentKey = env.ATBASH_AGENT_PRIVKEY;
115
- if (!agentKey) {
116
- throw new Error("ATBASH_AGENT_PRIVKEY env var is required");
117
- }
118
- const endpoint = env.ATBASH_ENDPOINT?.trim();
119
- const orgName = env.ATBASH_ORG_NAME?.trim();
120
- return hardenJudgeClient(
121
- sdk.fromConfig({
122
- agentKey,
123
- ...endpoint ? { judge: { endpoint } } : {},
124
- ...orgName ? { orgName } : {}
125
- })
126
- );
127
- }
128
-
129
49
  // src/tools/judge.ts
130
50
  import { z as z2 } from "zod";
131
51
 
@@ -146,28 +66,6 @@ function toErrorContent(error) {
146
66
  }
147
67
 
148
68
  // src/tools/judge.ts
149
- function toJudgeFailureContent(error, toolName = "atbash_judge") {
150
- return {
151
- content: [
152
- {
153
- type: "text",
154
- text: JSON.stringify(
155
- {
156
- verdict: "ERROR",
157
- allow: false,
158
- reason: `Atbash judge unavailable \u2014 treat this as BLOCK and do NOT proceed with the action (fail-closed). Retry ${toolName} once the service is reachable. Cause: ${safeErrorMessage(error)}`
159
- },
160
- null,
161
- 2
162
- )
163
- }
164
- ],
165
- isError: true
166
- };
167
- }
168
- function denyUnlessExplicitlyAllowed(decision) {
169
- return decision.verdict === "ALLOW" && decision.allow === true ? decision : { ...decision, allow: false };
170
- }
171
69
  function registerJudgeTools(server2, client2) {
172
70
  server2.registerTool(
173
71
  "atbash_judge",
@@ -190,17 +88,11 @@ function registerJudgeTools(server2, client2) {
190
88
  parsedArgs = tool_args_json;
191
89
  }
192
90
  }
193
- const rawDecision = await client2.auditToolCall({
91
+ const decision = await client2.auditToolCall({
194
92
  toolName: tool_name ?? "mcp_judge",
195
93
  args: parsedArgs ?? { action },
196
94
  context: `${action} \u2014 ${context}`
197
95
  });
198
- const decision = denyUnlessExplicitlyAllowed(rawDecision);
199
- if (decision.verdict === "ERROR") {
200
- return toJudgeFailureContent(
201
- new Error(decision.reason ?? "judge returned an error verdict")
202
- );
203
- }
204
96
  if (decision.verdict === "HOLD") {
205
97
  const holdParts = [
206
98
  "Action held for operator review. The agent will not be jailed \u2014 please approve or reject this request from the Atbash dashboard, then ask the agent to try again.",
@@ -211,14 +103,14 @@ function registerJudgeTools(server2, client2) {
211
103
  }
212
104
  return toJsonContent(decision);
213
105
  } catch (error) {
214
- return toJudgeFailureContent(error);
106
+ return toErrorContent(error);
215
107
  }
216
108
  }
217
109
  );
218
110
  server2.registerTool(
219
111
  "atbash_log",
220
112
  {
221
- description: "Submit a tool call for Atbash safety judgment and logging. Returns ALLOW, HOLD, BLOCK, or ERROR.",
113
+ description: "Log a tool call on-chain without requesting a verdict.",
222
114
  inputSchema: z2.object({
223
115
  action: z2.string().describe("Action description"),
224
116
  context: z2.string().describe("Action context"),
@@ -236,29 +128,22 @@ function registerJudgeTools(server2, client2) {
236
128
  parsedArgs = tool_args_json;
237
129
  }
238
130
  }
239
- const rawDecision = await client2.auditToolCall({
131
+ const result = await client2.auditToolCall({
240
132
  toolName: tool_name ?? "mcp_log",
241
133
  args: parsedArgs ?? { action },
242
134
  context: `${action} \u2014 ${context}`
243
135
  });
244
- const decision = denyUnlessExplicitlyAllowed(rawDecision);
245
- if (decision.verdict === "ERROR") {
246
- return toJudgeFailureContent(
247
- new Error(decision.reason ?? "judge returned an error verdict"),
248
- "atbash_log"
249
- );
250
- }
251
- if (decision.verdict === "HOLD") {
136
+ if (result.verdict === "HOLD") {
252
137
  const holdParts = [
253
138
  "Action held for operator review. The agent will not be jailed \u2014 please approve or reject this request from the Atbash dashboard, then ask the agent to try again.",
254
- `Reason: ${decision.reason ?? ""}`
139
+ `Reason: ${result.reason ?? ""}`
255
140
  ];
256
- if (decision.toolCallId) holdParts.push(`Tool Call ID: ${decision.toolCallId}`);
257
- return toJsonContent({ ...decision, allow: false, reason: holdParts.join("\n") });
141
+ if (result.toolCallId) holdParts.push(`Tool Call ID: ${result.toolCallId}`);
142
+ return toJsonContent({ ...result, allow: false, reason: holdParts.join("\n") });
258
143
  }
259
- return toJsonContent(decision);
144
+ return toJsonContent(result);
260
145
  } catch (error) {
261
- return toJudgeFailureContent(error, "atbash_log");
146
+ return toErrorContent(error);
262
147
  }
263
148
  }
264
149
  );
@@ -494,11 +379,21 @@ function registerStatusTools(server2, client2) {
494
379
  }
495
380
 
496
381
  // src/index.ts
497
- var client = createAtbashClient(process.env, Atbash);
382
+ var privkey = process.env.ATBASH_AGENT_PRIVKEY;
383
+ if (!privkey) {
384
+ console.error("ATBASH_AGENT_PRIVKEY env var is required");
385
+ process.exit(1);
386
+ }
387
+ var endpoint = process.env.ATBASH_ENDPOINT;
388
+ var orgName = process.env.ATBASH_ORG_NAME;
498
389
  setupTelemetry({ enabled: true, source: "plugin:mcp" });
499
390
  process.once("beforeExit", () => shutdownTelemetry());
500
391
  process.once("SIGINT", () => shutdownTelemetry().finally(() => process.exit(0)));
501
392
  process.once("SIGTERM", () => shutdownTelemetry().finally(() => process.exit(0)));
393
+ var clientOpts = {};
394
+ if (endpoint) clientOpts.endpoint = endpoint;
395
+ if (orgName) clientOpts.orgName = orgName;
396
+ var client = new Atbash(privkey, clientOpts);
502
397
  var server = new McpServer({
503
398
  name: "atbash-safety",
504
399
  version: "1.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atbash/atbash-mcp",
3
- "version": "0.0.12-dev.0",
3
+ "version": "0.0.12",
4
4
  "description": "Atbash safety judge exposed as a standalone MCP server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,16 +39,13 @@
39
39
  },
40
40
  "scripts": {
41
41
  "build": "node scripts/clean.mjs && tsup src/index.ts --format esm --dts --clean",
42
- "typecheck": "tsc -p tsconfig.json --noEmit",
43
- "test:compile": "tsc -p tsconfig.json --outDir dist --declaration false --sourceMap false",
44
- "test": "vitest run && npm run test:compile && node --test tests/*.test.mjs",
45
42
  "example": "npm run build && npm install --prefix examples/mcp-runtime-agent && node examples/mcp-runtime-agent/client.mjs",
46
43
  "verify:package": "node scripts/release.mjs --verify-current",
47
44
  "release": "node scripts/release.mjs --channel latest",
48
45
  "release:dev": "node scripts/release.mjs --channel dev"
49
46
  },
50
47
  "dependencies": {
51
- "@atbash/sdk": "0.10.11-dev.0",
48
+ "@atbash/sdk": "0.7.1",
52
49
  "@modelcontextprotocol/sdk": "^1.12.3",
53
50
  "zod": "^3.25.76"
54
51
  },
@@ -56,7 +53,6 @@
56
53
  "@types/node": "^25.7.0",
57
54
  "semver": "7.8.5",
58
55
  "tsup": "^8.0.0",
59
- "typescript": "^5.0.0",
60
- "vitest": "^4.1.6"
56
+ "typescript": "^5.0.0"
61
57
  }
62
58
  }