@atbash/mcp 0.1.0
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/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +449 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Chromaway AB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# `@atbash/mcp`
|
|
2
|
+
|
|
3
|
+
Expose Atbash as a standalone MCP server.
|
|
4
|
+
|
|
5
|
+
This package starts an MCP server process that loads one Atbash agent identity and exposes safety and query tools over the Model Context Protocol. Your MCP host or client remains the real decision-maker — this package does not execute your business tools.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @atbash/mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When To Use It
|
|
14
|
+
|
|
15
|
+
Use this package when:
|
|
16
|
+
|
|
17
|
+
- your host already supports MCP
|
|
18
|
+
- you want one Atbash tool server to serve one or many MCP clients
|
|
19
|
+
- you want Atbash checks without coupling your app directly to the SDK
|
|
20
|
+
|
|
21
|
+
Do not use this when you need deep framework lifecycle integration — use a framework-native package like `@atbash/eliza-plugin` or `@atbash/langgraph` instead.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Claude Desktop
|
|
26
|
+
|
|
27
|
+
Add to your `claude_desktop_config.json`:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"atbash": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["-y", "@atbash/mcp"],
|
|
35
|
+
"env": {
|
|
36
|
+
"ATBASH_AGENT_PRIVKEY": "your_agent_private_key_here"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Any MCP Client
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
47
|
+
|
|
48
|
+
const transport = new StdioClientTransport({
|
|
49
|
+
command: "npx",
|
|
50
|
+
args: ["-y", "@atbash/mcp"],
|
|
51
|
+
env: {
|
|
52
|
+
...process.env,
|
|
53
|
+
ATBASH_AGENT_PRIVKEY: process.env.ATBASH_AGENT_PRIVKEY,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Environment Variables
|
|
59
|
+
|
|
60
|
+
| Variable | Required | Description |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `ATBASH_AGENT_PRIVKEY` | Yes | Your Atbash agent private key |
|
|
63
|
+
| `ATBASH_ENDPOINT` | No | Override the default Atbash endpoint (`https://atbash.ai`) |
|
|
64
|
+
|
|
65
|
+
## Tools
|
|
66
|
+
|
|
67
|
+
### Safety Tools
|
|
68
|
+
|
|
69
|
+
#### `atbash_judge`
|
|
70
|
+
|
|
71
|
+
Submit an action for safety judgment before executing it.
|
|
72
|
+
|
|
73
|
+
| Input | Type | Required | Description |
|
|
74
|
+
|---|---|---|---|
|
|
75
|
+
| `action` | string | Yes | Plain text description of the action |
|
|
76
|
+
| `context` | string | Yes | Why this action is being taken |
|
|
77
|
+
| `tool_name` | string | No | Name of the tool being called |
|
|
78
|
+
| `tool_args_json` | string | No | JSON string of tool arguments |
|
|
79
|
+
|
|
80
|
+
Returns: `{ verdict, allow, reason, tool_call_id }`
|
|
81
|
+
|
|
82
|
+
#### `atbash_log`
|
|
83
|
+
|
|
84
|
+
Log a tool call on-chain without requesting a verdict.
|
|
85
|
+
|
|
86
|
+
| Input | Type | Required |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `action` | string | Yes |
|
|
89
|
+
| `context` | string | Yes |
|
|
90
|
+
| `tool_name` | string | No |
|
|
91
|
+
| `tool_args_json` | string | No |
|
|
92
|
+
|
|
93
|
+
#### `atbash_check_agent`
|
|
94
|
+
|
|
95
|
+
Check whether an agent is registered on the Atbash platform.
|
|
96
|
+
|
|
97
|
+
| Input | Type | Description |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| `pubkey` | string | Agent public key (defaults to server agent) |
|
|
100
|
+
|
|
101
|
+
#### `atbash_judgment_status`
|
|
102
|
+
|
|
103
|
+
Poll the status of a previously submitted judgment.
|
|
104
|
+
|
|
105
|
+
| Input | Type | Description |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| `tool_call_id` | string | ID returned from `atbash_judge` |
|
|
108
|
+
| `agent_pubkey` | string | Agent public key (defaults to server agent) |
|
|
109
|
+
|
|
110
|
+
### Query Tools (read-only)
|
|
111
|
+
|
|
112
|
+
| Tool | Description |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `atbash_get_policy` | Get agent policy and jail status |
|
|
115
|
+
| `atbash_get_agent_detail` | Get agent metadata |
|
|
116
|
+
| `atbash_get_tool_calls` | List recent tool calls across all agents |
|
|
117
|
+
| `atbash_get_agent_tool_calls` | List recent tool calls for one agent |
|
|
118
|
+
| `atbash_get_org_tool_calls` | List recent tool calls for an organization |
|
|
119
|
+
| `atbash_get_tool_call_full` | Get full detail for a single tool call |
|
|
120
|
+
| `atbash_get_tool_call_count` | Get total on-chain tool call count |
|
|
121
|
+
| `atbash_get_tier_info` | Get organization tier information |
|
|
122
|
+
| `atbash_get_held_actions` | List actions pending operator review |
|
|
123
|
+
| `atbash_get_reviews` | List completed operator reviews |
|
|
124
|
+
| `atbash_get_safety_stats` | Get chain-wide safety statistics |
|
|
125
|
+
|
|
126
|
+
## Verdict Handling
|
|
127
|
+
|
|
128
|
+
| Verdict | Meaning | What To Do |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `ALLOW` | Safe to proceed | Execute the real tool |
|
|
131
|
+
| `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 |
|
|
132
|
+
| `BLOCK` | Policy violation | Stop; show `reason` to operator |
|
|
133
|
+
| `ERROR` | Judge unreachable | Fail closed |
|
|
134
|
+
|
|
135
|
+
## Sending Better Inputs
|
|
136
|
+
|
|
137
|
+
Better inputs produce better safety decisions.
|
|
138
|
+
|
|
139
|
+
Weak:
|
|
140
|
+
```json
|
|
141
|
+
{ "action": "do payment", "context": "finance" }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Better:
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"action": "Bank transfer $25 to a new external vendor account",
|
|
148
|
+
"context": "Treasury payout review before execution",
|
|
149
|
+
"tool_name": "send_bank_transfer",
|
|
150
|
+
"tool_args_json": "{\"amount\":25,\"recipient\":\"new vendor\"}"
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Example
|
|
155
|
+
|
|
156
|
+
A runnable example is in [`examples/mcp-runtime-agent/`](./examples/mcp-runtime-agent/).
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm install && npm run build
|
|
160
|
+
ATBASH_AGENT_PRIVKEY=your_key_here node examples/mcp-runtime-agent/client.mjs
|
|
161
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { loadAgent } from "@atbash/sdk";
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
|
|
8
|
+
// src/prompts/safety.ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
function registerSafetyPrompt(server2) {
|
|
11
|
+
const promptServer = server2;
|
|
12
|
+
if (!promptServer.registerPrompt) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
promptServer.registerPrompt(
|
|
16
|
+
"atbash_safety_check",
|
|
17
|
+
{
|
|
18
|
+
description: "Prepare a consistent pre-execution Atbash safety review request.",
|
|
19
|
+
inputSchema: z.object({
|
|
20
|
+
action: z.string().describe("Action to evaluate"),
|
|
21
|
+
context: z.string().describe("Why the action is being taken")
|
|
22
|
+
})
|
|
23
|
+
},
|
|
24
|
+
async ({ action, context }) => ({
|
|
25
|
+
messages: [
|
|
26
|
+
{
|
|
27
|
+
role: "user",
|
|
28
|
+
content: {
|
|
29
|
+
type: "text",
|
|
30
|
+
text: [
|
|
31
|
+
"Use the Atbash tools to evaluate the following action before execution.",
|
|
32
|
+
`Action: ${action}`,
|
|
33
|
+
`Context: ${context}`,
|
|
34
|
+
"If the result is HOLD, explain that operator review is required before proceeding."
|
|
35
|
+
].join("\n")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/resources/policy.ts
|
|
44
|
+
function registerPolicyResource(server2, agent2, endpoint2) {
|
|
45
|
+
void server2;
|
|
46
|
+
void agent2;
|
|
47
|
+
void endpoint2;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/tools/judge.ts
|
|
52
|
+
import {
|
|
53
|
+
checkAgentExists,
|
|
54
|
+
createAtbashClient,
|
|
55
|
+
logToolCall
|
|
56
|
+
} from "@atbash/sdk";
|
|
57
|
+
import { z as z2 } from "zod";
|
|
58
|
+
|
|
59
|
+
// src/utils.ts
|
|
60
|
+
function createClientOpts(endpoint2) {
|
|
61
|
+
return endpoint2 ? { endpoint: endpoint2 } : void 0;
|
|
62
|
+
}
|
|
63
|
+
function safeErrorMessage(error) {
|
|
64
|
+
return error instanceof Error ? error.message : String(error);
|
|
65
|
+
}
|
|
66
|
+
function toJsonContent(value) {
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function toErrorContent(error) {
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: "text", text: `Error: ${safeErrorMessage(error)}` }],
|
|
74
|
+
isError: true
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/tools/judge.ts
|
|
79
|
+
function registerJudgeTools(server2, agent2, endpoint2) {
|
|
80
|
+
const opts = createClientOpts(endpoint2);
|
|
81
|
+
const client = createAtbashClient({
|
|
82
|
+
keyPair: { privKey: agent2.privkey, pubKey: agent2.pubkey },
|
|
83
|
+
judge: endpoint2 ? { endpoint: endpoint2 } : void 0
|
|
84
|
+
});
|
|
85
|
+
server2.registerTool(
|
|
86
|
+
"atbash_judge",
|
|
87
|
+
{
|
|
88
|
+
description: "Submit an action for safety judgment before executing it. Returns ALLOW, HOLD, BLOCK, or ERROR.",
|
|
89
|
+
inputSchema: z2.object({
|
|
90
|
+
action: z2.string().describe("Plain text description of the action to judge"),
|
|
91
|
+
context: z2.string().describe("Why this action is being taken"),
|
|
92
|
+
tool_name: z2.string().optional().describe("Name of the tool being called"),
|
|
93
|
+
tool_args_json: z2.string().optional().describe("JSON string of tool arguments")
|
|
94
|
+
})
|
|
95
|
+
},
|
|
96
|
+
async ({ action, context, tool_name, tool_args_json }) => {
|
|
97
|
+
try {
|
|
98
|
+
let parsedArgs = void 0;
|
|
99
|
+
if (tool_args_json) {
|
|
100
|
+
try {
|
|
101
|
+
parsedArgs = JSON.parse(tool_args_json);
|
|
102
|
+
} catch {
|
|
103
|
+
parsedArgs = tool_args_json;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const decision = await client.auditToolCall({
|
|
107
|
+
toolName: tool_name ?? "mcp_judge",
|
|
108
|
+
args: parsedArgs ?? { action },
|
|
109
|
+
context: `${action} \u2014 ${context}`
|
|
110
|
+
});
|
|
111
|
+
const normalized = decision.verdict === "HOLD" ? { ...decision, verdict: "ALLOW", allow: true } : decision;
|
|
112
|
+
return toJsonContent(normalized);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
return toErrorContent(error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
server2.registerTool(
|
|
119
|
+
"atbash_log",
|
|
120
|
+
{
|
|
121
|
+
description: "Log a tool call on-chain without requesting a verdict.",
|
|
122
|
+
inputSchema: z2.object({
|
|
123
|
+
action: z2.string().describe("Action description"),
|
|
124
|
+
context: z2.string().describe("Action context"),
|
|
125
|
+
tool_name: z2.string().optional().describe("Tool name"),
|
|
126
|
+
tool_args_json: z2.string().optional().describe("Tool arguments JSON")
|
|
127
|
+
})
|
|
128
|
+
},
|
|
129
|
+
async ({ action, context, tool_name, tool_args_json }) => {
|
|
130
|
+
try {
|
|
131
|
+
const result = await logToolCall(
|
|
132
|
+
action,
|
|
133
|
+
context,
|
|
134
|
+
agent2,
|
|
135
|
+
void 0,
|
|
136
|
+
{
|
|
137
|
+
toolName: tool_name,
|
|
138
|
+
toolArgsJson: tool_args_json
|
|
139
|
+
},
|
|
140
|
+
opts
|
|
141
|
+
);
|
|
142
|
+
return toJsonContent(result);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return toErrorContent(error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
server2.registerTool(
|
|
149
|
+
"atbash_check_agent",
|
|
150
|
+
{
|
|
151
|
+
description: "Check whether an agent is registered on the Atbash platform.",
|
|
152
|
+
inputSchema: z2.object({
|
|
153
|
+
pubkey: z2.string().optional().describe("Agent public key, defaults to this server agent")
|
|
154
|
+
})
|
|
155
|
+
},
|
|
156
|
+
async ({ pubkey }) => {
|
|
157
|
+
try {
|
|
158
|
+
const selectedPubkey = pubkey ?? agent2.pubkey;
|
|
159
|
+
const registered = await checkAgentExists(selectedPubkey, opts);
|
|
160
|
+
return toJsonContent({ registered, pubkey: selectedPubkey });
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return toErrorContent(error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/tools/queries.ts
|
|
169
|
+
import {
|
|
170
|
+
getAgentDetail,
|
|
171
|
+
getAgentPolicy,
|
|
172
|
+
getAgentToolCalls,
|
|
173
|
+
getHeldActionReviews,
|
|
174
|
+
getOrgTierInfo,
|
|
175
|
+
getOrgToolCalls,
|
|
176
|
+
getPendingHeldActions,
|
|
177
|
+
getSafetyStats,
|
|
178
|
+
getToolCallCount,
|
|
179
|
+
getToolCallFull,
|
|
180
|
+
getToolCalls
|
|
181
|
+
} from "@atbash/sdk";
|
|
182
|
+
import { z as z3 } from "zod";
|
|
183
|
+
function defaultMaxCount(value) {
|
|
184
|
+
return value ?? 20;
|
|
185
|
+
}
|
|
186
|
+
function registerQueryTools(server2, agent2, endpoint2) {
|
|
187
|
+
const opts = createClientOpts(endpoint2);
|
|
188
|
+
server2.registerTool(
|
|
189
|
+
"atbash_get_policy",
|
|
190
|
+
{
|
|
191
|
+
description: "Get an agent policy configuration and jail status.",
|
|
192
|
+
inputSchema: z3.object({
|
|
193
|
+
pubkey: z3.string().optional().describe("Agent public key, defaults to the server agent")
|
|
194
|
+
})
|
|
195
|
+
},
|
|
196
|
+
async ({ pubkey }) => {
|
|
197
|
+
try {
|
|
198
|
+
return toJsonContent(await getAgentPolicy(pubkey ?? agent2.pubkey, opts));
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return toErrorContent(error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
server2.registerTool(
|
|
205
|
+
"atbash_get_agent_detail",
|
|
206
|
+
{
|
|
207
|
+
description: "Get agent metadata for a public key.",
|
|
208
|
+
inputSchema: z3.object({
|
|
209
|
+
pubkey: z3.string().optional().describe("Agent public key, defaults to the server agent")
|
|
210
|
+
})
|
|
211
|
+
},
|
|
212
|
+
async ({ pubkey }) => {
|
|
213
|
+
try {
|
|
214
|
+
return toJsonContent(await getAgentDetail(pubkey ?? agent2.pubkey, opts));
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return toErrorContent(error);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
server2.registerTool(
|
|
221
|
+
"atbash_get_tool_calls",
|
|
222
|
+
{
|
|
223
|
+
description: "List recent tool calls across all agents.",
|
|
224
|
+
inputSchema: z3.object({
|
|
225
|
+
max_count: z3.number().int().positive().optional().describe("Maximum number of records")
|
|
226
|
+
})
|
|
227
|
+
},
|
|
228
|
+
async ({ max_count }) => {
|
|
229
|
+
try {
|
|
230
|
+
return toJsonContent(await getToolCalls(defaultMaxCount(max_count), opts));
|
|
231
|
+
} catch (error) {
|
|
232
|
+
return toErrorContent(error);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
server2.registerTool(
|
|
237
|
+
"atbash_get_agent_tool_calls",
|
|
238
|
+
{
|
|
239
|
+
description: "List recent tool calls for one agent.",
|
|
240
|
+
inputSchema: z3.object({
|
|
241
|
+
pubkey: z3.string().optional().describe("Agent public key, defaults to the server agent"),
|
|
242
|
+
max_count: z3.number().int().positive().optional().describe("Maximum number of records")
|
|
243
|
+
})
|
|
244
|
+
},
|
|
245
|
+
async ({ pubkey, max_count }) => {
|
|
246
|
+
try {
|
|
247
|
+
return toJsonContent(
|
|
248
|
+
await getAgentToolCalls(pubkey ?? agent2.pubkey, defaultMaxCount(max_count), opts)
|
|
249
|
+
);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
return toErrorContent(error);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
server2.registerTool(
|
|
256
|
+
"atbash_get_org_tool_calls",
|
|
257
|
+
{
|
|
258
|
+
description: "List recent tool calls for an organization.",
|
|
259
|
+
inputSchema: z3.object({
|
|
260
|
+
org_name: z3.string().describe("Organization name"),
|
|
261
|
+
max_count: z3.number().int().positive().optional().describe("Maximum number of records")
|
|
262
|
+
})
|
|
263
|
+
},
|
|
264
|
+
async ({ org_name, max_count }) => {
|
|
265
|
+
try {
|
|
266
|
+
return toJsonContent(await getOrgToolCalls(org_name, defaultMaxCount(max_count), opts));
|
|
267
|
+
} catch (error) {
|
|
268
|
+
return toErrorContent(error);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
server2.registerTool(
|
|
273
|
+
"atbash_get_tool_call_full",
|
|
274
|
+
{
|
|
275
|
+
description: "Get the full detail for a single tool call.",
|
|
276
|
+
inputSchema: z3.object({
|
|
277
|
+
tool_call_id: z3.string().describe("Tool call identifier")
|
|
278
|
+
})
|
|
279
|
+
},
|
|
280
|
+
async ({ tool_call_id }) => {
|
|
281
|
+
try {
|
|
282
|
+
return toJsonContent(await getToolCallFull(tool_call_id, opts));
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return toErrorContent(error);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
server2.registerTool(
|
|
289
|
+
"atbash_get_tool_call_count",
|
|
290
|
+
{
|
|
291
|
+
description: "Get the total number of tool calls on-chain.",
|
|
292
|
+
inputSchema: z3.object({})
|
|
293
|
+
},
|
|
294
|
+
async () => {
|
|
295
|
+
try {
|
|
296
|
+
return toJsonContent({ count: await getToolCallCount(opts) });
|
|
297
|
+
} catch (error) {
|
|
298
|
+
return toErrorContent(error);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
server2.registerTool(
|
|
303
|
+
"atbash_get_tier_info",
|
|
304
|
+
{
|
|
305
|
+
description: "Get an organization's tier information.",
|
|
306
|
+
inputSchema: z3.object({
|
|
307
|
+
org_name: z3.string().describe("Organization name")
|
|
308
|
+
})
|
|
309
|
+
},
|
|
310
|
+
async ({ org_name }) => {
|
|
311
|
+
try {
|
|
312
|
+
return toJsonContent(await getOrgTierInfo(org_name, opts));
|
|
313
|
+
} catch (error) {
|
|
314
|
+
return toErrorContent(error);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
server2.registerTool(
|
|
319
|
+
"atbash_get_held_actions",
|
|
320
|
+
{
|
|
321
|
+
description: "List actions currently waiting for operator review.",
|
|
322
|
+
inputSchema: z3.object({
|
|
323
|
+
org_name: z3.string().describe("Organization name"),
|
|
324
|
+
max_count: z3.number().int().positive().optional().describe("Maximum number of records")
|
|
325
|
+
})
|
|
326
|
+
},
|
|
327
|
+
async ({ org_name, max_count }) => {
|
|
328
|
+
try {
|
|
329
|
+
return toJsonContent(
|
|
330
|
+
await getPendingHeldActions(org_name, defaultMaxCount(max_count), opts)
|
|
331
|
+
);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
return toErrorContent(error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
server2.registerTool(
|
|
338
|
+
"atbash_get_reviews",
|
|
339
|
+
{
|
|
340
|
+
description: "List completed operator reviews for held actions.",
|
|
341
|
+
inputSchema: z3.object({
|
|
342
|
+
org_name: z3.string().describe("Organization name"),
|
|
343
|
+
max_count: z3.number().int().positive().optional().describe("Maximum number of records")
|
|
344
|
+
})
|
|
345
|
+
},
|
|
346
|
+
async ({ org_name, max_count }) => {
|
|
347
|
+
try {
|
|
348
|
+
return toJsonContent(await getHeldActionReviews(org_name, defaultMaxCount(max_count), opts));
|
|
349
|
+
} catch (error) {
|
|
350
|
+
return toErrorContent(error);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
server2.registerTool(
|
|
355
|
+
"atbash_get_safety_stats",
|
|
356
|
+
{
|
|
357
|
+
description: "Get high-level chain-wide safety statistics.",
|
|
358
|
+
inputSchema: z3.object({})
|
|
359
|
+
},
|
|
360
|
+
async () => {
|
|
361
|
+
try {
|
|
362
|
+
return toJsonContent(await getSafetyStats(opts));
|
|
363
|
+
} catch (error) {
|
|
364
|
+
return toErrorContent(error);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// src/tools/status.ts
|
|
371
|
+
import { z as z4 } from "zod";
|
|
372
|
+
function normalizeVerdict(raw) {
|
|
373
|
+
if (raw == null) return "No verdict";
|
|
374
|
+
const value = String(raw).toUpperCase();
|
|
375
|
+
if (value === "ALLOW" || value === "GREEN") return "ALLOW";
|
|
376
|
+
if (value === "HOLD" || value === "YELLOW") return "HOLD";
|
|
377
|
+
if (value === "BLOCK" || value === "RED") return "BLOCK";
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
function normalizeStatus(raw) {
|
|
381
|
+
const value = String(raw ?? "").toLowerCase();
|
|
382
|
+
if (value === "pending" || value === "answered" || value === "error") {
|
|
383
|
+
return value;
|
|
384
|
+
}
|
|
385
|
+
return "error";
|
|
386
|
+
}
|
|
387
|
+
function registerStatusTools(server2, defaultPubkey, endpoint2) {
|
|
388
|
+
const baseUrl = (endpoint2 ?? "https://atbash.ai").replace(/\/$/, "");
|
|
389
|
+
server2.registerTool(
|
|
390
|
+
"atbash_judgment_status",
|
|
391
|
+
{
|
|
392
|
+
description: "Poll the status of a previously submitted judgment.",
|
|
393
|
+
inputSchema: z4.object({
|
|
394
|
+
tool_call_id: z4.string().describe("The tool_call_id returned from atbash_judge"),
|
|
395
|
+
agent_pubkey: z4.string().optional().describe("Agent public key, defaults to the server agent")
|
|
396
|
+
})
|
|
397
|
+
},
|
|
398
|
+
async ({ tool_call_id, agent_pubkey }) => {
|
|
399
|
+
try {
|
|
400
|
+
const pubkey = agent_pubkey ?? defaultPubkey;
|
|
401
|
+
const url = new URL(`${baseUrl}/api/v1/judge`);
|
|
402
|
+
url.searchParams.set("tool_call_id", tool_call_id);
|
|
403
|
+
url.searchParams.set("agent_pubkey", pubkey);
|
|
404
|
+
const response = await fetch(url, {
|
|
405
|
+
method: "GET",
|
|
406
|
+
headers: { Accept: "application/json" }
|
|
407
|
+
});
|
|
408
|
+
if (!response.ok) {
|
|
409
|
+
const body = await response.text().catch(() => "");
|
|
410
|
+
throw new Error(`API error ${response.status}: ${body || response.statusText}`);
|
|
411
|
+
}
|
|
412
|
+
const data = await response.json();
|
|
413
|
+
return toJsonContent({
|
|
414
|
+
status: normalizeStatus(data.status),
|
|
415
|
+
verdict: normalizeVerdict(data.verdict),
|
|
416
|
+
reason: String(data.reason ?? ""),
|
|
417
|
+
judgmentId: String(data.judgmentId ?? tool_call_id),
|
|
418
|
+
onChain: Boolean(data.onChain),
|
|
419
|
+
cached: Boolean(data.cached),
|
|
420
|
+
responseTimeMs: Number(data.responseTimeMs ?? 0),
|
|
421
|
+
tool_call_id,
|
|
422
|
+
agent_pubkey: pubkey
|
|
423
|
+
});
|
|
424
|
+
} catch (error) {
|
|
425
|
+
return toErrorContent(error);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/index.ts
|
|
432
|
+
var privkey = process.env.ATBASH_AGENT_PRIVKEY;
|
|
433
|
+
if (!privkey) {
|
|
434
|
+
console.error("ATBASH_AGENT_PRIVKEY env var is required");
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
var agent = loadAgent(privkey);
|
|
438
|
+
var endpoint = process.env.ATBASH_ENDPOINT;
|
|
439
|
+
var server = new McpServer({
|
|
440
|
+
name: "atbash-safety",
|
|
441
|
+
version: "1.0.0"
|
|
442
|
+
});
|
|
443
|
+
registerJudgeTools(server, agent, endpoint);
|
|
444
|
+
registerStatusTools(server, agent.pubkey, endpoint);
|
|
445
|
+
registerQueryTools(server, agent, endpoint);
|
|
446
|
+
registerPolicyResource(server, agent, endpoint);
|
|
447
|
+
registerSafetyPrompt(server);
|
|
448
|
+
var transport = new StdioServerTransport();
|
|
449
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atbash/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Atbash safety judge exposed as a standalone MCP server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"atbash-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"atbash",
|
|
28
|
+
"mcp",
|
|
29
|
+
"ai-safety",
|
|
30
|
+
"agent-safety",
|
|
31
|
+
"model-context-protocol",
|
|
32
|
+
"judge",
|
|
33
|
+
"policy"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup src/index.ts --format esm --dts --clean"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@atbash/sdk": "^0.3.8",
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.12.3",
|
|
42
|
+
"zod": "^3.25.76"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^25.7.0",
|
|
46
|
+
"tsup": "^8.0.0",
|
|
47
|
+
"typescript": "^5.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|