@sheafhq/mcp-tools 0.0.0-stage → 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 +43 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +15 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +200 -0
- package/dist/tools.d.ts +15 -0
- package/dist/tools.js +142 -0
- package/package.json +46 -4
package/README.md
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sheafhq/mcp-tools
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server exposing three Sheaf read tools to any MCP-compatible agent:
|
|
4
|
+
|
|
5
|
+
| Tool | Purpose |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `sheaf_chain_head` | Fetch the current head of a Sheaf chain (seq + hash). |
|
|
8
|
+
| `sheaf_get_proof_for` | Fetch a Merkle inclusion proof + on-chain anchor reference for a specific event. |
|
|
9
|
+
| `sheaf_verify_bundle` | Verify a Sheaf receipt bundle end-to-end (signature, Merkle inclusion, anchor tx). |
|
|
10
|
+
|
|
11
|
+
All three are **read-only and free**. No wallet, no payment, no mutation.
|
|
12
|
+
|
|
13
|
+
For event *capture* (transparently recording every tool call an agent makes), use the companion package [`@sheafhq/mcp-recorder`](https://www.npmjs.com/package/@sheafhq/mcp-recorder). It stays a pure proxy; this package is a separate, opt-in tool surface.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @sheafhq/mcp-tools
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Use with Claude Desktop
|
|
22
|
+
|
|
23
|
+
Add to `claude_desktop_config.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"sheaf-tools": {
|
|
29
|
+
"command": "sheaf-mcp-tools"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Env vars
|
|
36
|
+
|
|
37
|
+
| Var | Default | Purpose |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| `SHEAF_RECORDER_URL` | `https://sheaf-recorder.goldencalf.workers.dev` | Recorder host to query. |
|
|
40
|
+
| `SHEAF_BASE_RPC` | `https://mainnet.base.org` | Base mainnet RPC used by `sheaf_verify_bundle` to read the anchor tx. |
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI entry for sheaf-mcp-tools. Spawned by an MCP-compatible client
|
|
3
|
+
// (Claude Desktop, Cline, custom runtime) and speaks JSON-RPC over stdio.
|
|
4
|
+
//
|
|
5
|
+
// Environment variables (all optional):
|
|
6
|
+
// SHEAF_RECORDER_URL where to query (default: production recorder)
|
|
7
|
+
// SHEAF_BASE_RPC Base mainnet RPC for anchor verification
|
|
8
|
+
// (default: https://mainnet.base.org)
|
|
9
|
+
import { runServer } from "./server.js";
|
|
10
|
+
const ctx = {
|
|
11
|
+
recorderUrl: process.env.SHEAF_RECORDER_URL ??
|
|
12
|
+
"https://sheaf-recorder.goldencalf.workers.dev",
|
|
13
|
+
baseRpc: process.env.SHEAF_BASE_RPC ?? "https://mainnet.base.org",
|
|
14
|
+
};
|
|
15
|
+
runServer(ctx);
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Stdio MCP server. Reads JSON-RPC lines from stdin, writes responses to
|
|
2
|
+
// stdout. No SSE / HTTP transport — this is designed to be launched by an
|
|
3
|
+
// MCP-compatible client (Claude Desktop, Cline, etc.) and speak JSON-RPC
|
|
4
|
+
// directly over stdio.
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import { chainHead, getProofFor, verifyBundle, } from "./tools.js";
|
|
7
|
+
// Tool definitions sent in reply to tools/list. The decider model chooses
|
|
8
|
+
// among these entirely from the description + inputSchema — so the
|
|
9
|
+
// descriptions below are the product, not a doc. Every field documented;
|
|
10
|
+
// an example call + example response in each description.
|
|
11
|
+
const TOOL_DEFS = [
|
|
12
|
+
{
|
|
13
|
+
name: "sheaf_chain_head",
|
|
14
|
+
description: [
|
|
15
|
+
"Return the current head (latest sequence number + hash) of a Sheaf chain.",
|
|
16
|
+
"",
|
|
17
|
+
"WHEN TO USE: You have a chain_id (an agent-run identifier recorded on Sheaf) and want to check what the latest event is, how many events exist, or whether the chain exists at all. Common uses: verify that your own agent's events reached the recorder; synchronize local state after a crash; prove to an auditor that no events were recorded on a given chain.",
|
|
18
|
+
"",
|
|
19
|
+
"WHEN NOT TO USE: You want to verify a specific receipt — use sheaf_verify_bundle instead. You want to prove an event is anchored on-chain — use sheaf_get_proof_for. You want to CREATE a new event — this tool is read-only; events are created transparently by the @sheafhq/mcp-recorder proxy.",
|
|
20
|
+
"",
|
|
21
|
+
"EXAMPLE CALL:",
|
|
22
|
+
' { "chain_id": "agent-run-2026-11-11-smoke" }',
|
|
23
|
+
"",
|
|
24
|
+
"EXAMPLE RESPONSE:",
|
|
25
|
+
' { "chain_id": "agent-run-2026-11-11-smoke", "head_seq": 42, "head_hash": "26795706...4951f12", "event_count": 43, "created_at_unix": 1794367540, "updated_at_unix": 1794367611, "source_url": "https://sheaf-recorder.goldencalf.workers.dev/chains/agent-run-2026-11-11-smoke/head" }',
|
|
26
|
+
"",
|
|
27
|
+
"If the chain does not exist (no events recorded), returns { head: null, note: ... } with HTTP 200. Free; no payment required.",
|
|
28
|
+
].join("\n"),
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
chain_id: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Stable identifier the recorder uses to group related events in order. Typically matches what the operator set in SHEAF_CHAIN_ID when running the recorder. Required.",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ["chain_id"],
|
|
38
|
+
additionalProperties: false,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "sheaf_get_proof_for",
|
|
43
|
+
description: [
|
|
44
|
+
"Return a Merkle inclusion proof for one specific event (chain_id + seq) along with its on-chain anchor reference.",
|
|
45
|
+
"",
|
|
46
|
+
"WHEN TO USE: You want to prove to a third party that a specific recorded event is included in the Sheaf chain and committed on Base mainnet. Common uses: attach compliance evidence to an agent output; selectively disclose one action from a longer chain; let an external auditor independently check the proof against the on-chain anchor.",
|
|
47
|
+
"",
|
|
48
|
+
"WHEN NOT TO USE: You want to verify a receipt the other party already sent you — use sheaf_verify_bundle. You want to query chain state generally — use sheaf_chain_head. You want to buy the bundled + self-contained artifact — call GET /bundle/:chain_id/:seq on the recorder with an x402 payment ($0.001 USDC on Base); this tool returns the proof components for free but does not include the full signed envelope.",
|
|
49
|
+
"",
|
|
50
|
+
"EXAMPLE CALL:",
|
|
51
|
+
' { "chain_id": "agent-run-2026-11-11-smoke", "seq": 7 }',
|
|
52
|
+
"",
|
|
53
|
+
"EXAMPLE RESPONSE (anchored):",
|
|
54
|
+
' { "status": "anchored", "chain_id": "agent-run-...", "seq": 7, "epoch_id": 42, "leaf": "sha256:...", "leaf_index": 3, "merkle_root": "...", "siblings": [{"hash": "...", "side": "right"}, ...], "anchor": { "tx_hash": "0xc65349...", "block": 55369354, "explorer": "https://basescan.org/tx/0xc65349..." }, "verification_note": "..." }',
|
|
55
|
+
"",
|
|
56
|
+
"If the event exists but is not yet in a sealed epoch, returns { status: \"not_yet_anchored\", envelope_hash: ..., note: ... }; retry after the next epoch anchor. Free; no payment required.",
|
|
57
|
+
].join("\n"),
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
chain_id: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Chain identifier. Required.",
|
|
64
|
+
},
|
|
65
|
+
seq: {
|
|
66
|
+
type: "integer",
|
|
67
|
+
minimum: 0,
|
|
68
|
+
description: "Sequence number of the event within the chain (0-indexed). Required.",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
required: ["chain_id", "seq"],
|
|
72
|
+
additionalProperties: false,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "sheaf_verify_bundle",
|
|
77
|
+
description: [
|
|
78
|
+
"Verify a Sheaf receipt bundle end-to-end: signature, Merkle inclusion, and on-chain anchor. Pure read; makes one RPC call to Base to confirm the anchor transaction, no payment required.",
|
|
79
|
+
"",
|
|
80
|
+
"WHEN TO USE: A counterparty, auditor, or upstream agent gave you a Sheaf bundle (version sheaf-bundle/1 from the paid /bundle endpoint, or the legacy {envelope, inclusion_proof, anchor_tx_hash} shape) and you need to confirm it is authentic and intact BEFORE acting on its contents. Common uses: gate agent workflows on independently verified evidence; log a receipt into your own audit trail only after validation; reject counterfeit or tampered bundles before they influence downstream decisions.",
|
|
81
|
+
"",
|
|
82
|
+
"WHEN NOT TO USE: You just want to look up what events exist — use sheaf_chain_head. You want a proof but don't have a bundle — use sheaf_get_proof_for. You want to CREATE a bundle for one of your own events — call GET /bundle/:chain_id/:seq on the recorder with an x402 payment.",
|
|
83
|
+
"",
|
|
84
|
+
"EXAMPLE CALL:",
|
|
85
|
+
' { "bundle": { "version": "sheaf-bundle/1", "envelope": {...}, "envelope_hash": "...", "anchor": {...}, "issuer": {...}, ... } }',
|
|
86
|
+
"",
|
|
87
|
+
"EXAMPLE RESPONSE:",
|
|
88
|
+
' { "valid": true, "checks": { "anchor_on_chain": true, "anchor_from_authorized_wallet": true, "merkle_root_matches_on_chain": true, "inclusion_proof_reproduces_root": true, "envelope_hash_matches_leaf": true, "recorder_signature_verifies": true, "chain_prev_hash_matches": null }, "anchor_block": 55369354, "recorder": "https://sheaf-recorder.goldencalf.workers.dev", "anchorer_checked": "0x08A22c..." }',
|
|
89
|
+
"",
|
|
90
|
+
"If any check fails, returns { valid: false, checks: {...}, reason: \"...\" } with the specific invariant that failed. Treat valid=false as evidence of tampering, forgery, or wrong-issuer verification — do not use the bundle's contents.",
|
|
91
|
+
].join("\n"),
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
bundle: {
|
|
96
|
+
type: "object",
|
|
97
|
+
description: "The Sheaf bundle to verify. Accepts the sheaf-bundle/1 shape (preferred) or the legacy { envelope, inclusion_proof, anchor_tx_hash } shape. Required.",
|
|
98
|
+
},
|
|
99
|
+
anchorer_address: {
|
|
100
|
+
type: "string",
|
|
101
|
+
description: "Optional. 0x-prefixed Ethereum address expected to have sent the anchor tx. Defaults to the canonical sheaf-recorder anchorer. Only override this if verifying against a non-default Sheaf deployment.",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
required: ["bundle"],
|
|
105
|
+
additionalProperties: false,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
function ok(id, result) {
|
|
110
|
+
return { jsonrpc: "2.0", id, result };
|
|
111
|
+
}
|
|
112
|
+
function err(id, code, message) {
|
|
113
|
+
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
114
|
+
}
|
|
115
|
+
async function handle(req, ctx) {
|
|
116
|
+
const id = req.id ?? null;
|
|
117
|
+
switch (req.method) {
|
|
118
|
+
case "initialize":
|
|
119
|
+
return ok(id, {
|
|
120
|
+
protocolVersion: "2025-03-26",
|
|
121
|
+
capabilities: { tools: {} },
|
|
122
|
+
serverInfo: { name: "sheaf-mcp-tools", version: "0.1.0" },
|
|
123
|
+
});
|
|
124
|
+
case "tools/list":
|
|
125
|
+
return ok(id, { tools: TOOL_DEFS });
|
|
126
|
+
case "tools/call": {
|
|
127
|
+
const params = (req.params ?? {});
|
|
128
|
+
const args = params.arguments ?? {};
|
|
129
|
+
try {
|
|
130
|
+
let data;
|
|
131
|
+
switch (params.name) {
|
|
132
|
+
case "sheaf_chain_head":
|
|
133
|
+
data = await chainHead(ctx, args);
|
|
134
|
+
break;
|
|
135
|
+
case "sheaf_get_proof_for":
|
|
136
|
+
data = await getProofFor(ctx, args);
|
|
137
|
+
break;
|
|
138
|
+
case "sheaf_verify_bundle":
|
|
139
|
+
data = await verifyBundle(ctx, args);
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
return ok(id, {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: `unknown tool: ${params.name}`,
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
isError: true,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return ok(id, {
|
|
153
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
154
|
+
isError: false,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
return ok(id, {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: "text",
|
|
162
|
+
text: `error: ${e.message}`,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
isError: true,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
case "notifications/initialized":
|
|
170
|
+
case "notifications/cancelled":
|
|
171
|
+
// Notifications have no response.
|
|
172
|
+
return null;
|
|
173
|
+
default:
|
|
174
|
+
return err(id, -32601, `method not found: ${req.method}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export function runServer(ctx) {
|
|
178
|
+
const rl = createInterface({ input: process.stdin });
|
|
179
|
+
rl.on("line", (line) => {
|
|
180
|
+
if (!line.trim())
|
|
181
|
+
return;
|
|
182
|
+
let req;
|
|
183
|
+
try {
|
|
184
|
+
req = JSON.parse(line);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Do not await inline — concurrent in-flight requests are allowed.
|
|
190
|
+
handle(req, ctx)
|
|
191
|
+
.then((res) => {
|
|
192
|
+
if (res)
|
|
193
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
194
|
+
})
|
|
195
|
+
.catch((e) => {
|
|
196
|
+
process.stdout.write(JSON.stringify(err(req.id ?? null, -32603, e.message)) +
|
|
197
|
+
"\n");
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ToolContext = {
|
|
2
|
+
recorderUrl: string;
|
|
3
|
+
baseRpc: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function chainHead(ctx: ToolContext, args: {
|
|
6
|
+
chain_id: string;
|
|
7
|
+
}): Promise<unknown>;
|
|
8
|
+
export declare function getProofFor(ctx: ToolContext, args: {
|
|
9
|
+
chain_id: string;
|
|
10
|
+
seq: number;
|
|
11
|
+
}): Promise<unknown>;
|
|
12
|
+
export declare function verifyBundle(ctx: ToolContext, args: {
|
|
13
|
+
bundle: unknown;
|
|
14
|
+
anchorer_address?: string;
|
|
15
|
+
}): Promise<unknown>;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Tool implementations. Each returns either a plain object (serialized to
|
|
2
|
+
// JSON text content for the MCP response) or throws, in which case the
|
|
3
|
+
// server translates it to an MCP error response.
|
|
4
|
+
//
|
|
5
|
+
// All three tools are READ-only against the Sheaf recorder. No wallet, no
|
|
6
|
+
// settlement, no mutation. Safe to expose to any agent.
|
|
7
|
+
import { verify } from "@sheafhq/verify";
|
|
8
|
+
export async function chainHead(ctx, args) {
|
|
9
|
+
if (!args.chain_id || typeof args.chain_id !== "string") {
|
|
10
|
+
throw new Error("chain_id (string) is required");
|
|
11
|
+
}
|
|
12
|
+
const url = `${ctx.recorderUrl}/chains/${encodeURIComponent(args.chain_id)}/head`;
|
|
13
|
+
const r = await fetch(url);
|
|
14
|
+
if (r.status === 404) {
|
|
15
|
+
return {
|
|
16
|
+
chain_id: args.chain_id,
|
|
17
|
+
head: null,
|
|
18
|
+
note: "No events have been recorded on this chain yet.",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (!r.ok) {
|
|
22
|
+
throw new Error(`recorder returned HTTP ${r.status}`);
|
|
23
|
+
}
|
|
24
|
+
const body = (await r.json());
|
|
25
|
+
return {
|
|
26
|
+
chain_id: body.chain_id,
|
|
27
|
+
head_seq: body.head_seq,
|
|
28
|
+
head_hash: body.head_hash,
|
|
29
|
+
event_count: body.event_count,
|
|
30
|
+
created_at_unix: body.created_at,
|
|
31
|
+
updated_at_unix: body.updated_at,
|
|
32
|
+
source_url: url,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export async function getProofFor(ctx, args) {
|
|
36
|
+
if (!args.chain_id || typeof args.chain_id !== "string") {
|
|
37
|
+
throw new Error("chain_id (string) is required");
|
|
38
|
+
}
|
|
39
|
+
if (!Number.isInteger(args.seq) || args.seq < 0) {
|
|
40
|
+
throw new Error("seq (non-negative integer) is required");
|
|
41
|
+
}
|
|
42
|
+
// First find out which epoch the event is in.
|
|
43
|
+
const evUrl = `${ctx.recorderUrl}/events/${encodeURIComponent(args.chain_id)}/${args.seq}`;
|
|
44
|
+
const evRes = await fetch(evUrl);
|
|
45
|
+
if (evRes.status === 404) {
|
|
46
|
+
throw new Error(`no event at ${args.chain_id}/${args.seq}`);
|
|
47
|
+
}
|
|
48
|
+
if (!evRes.ok) {
|
|
49
|
+
throw new Error(`recorder returned HTTP ${evRes.status} on event lookup`);
|
|
50
|
+
}
|
|
51
|
+
const ev = (await evRes.json());
|
|
52
|
+
if (ev.epoch_id === null) {
|
|
53
|
+
return {
|
|
54
|
+
status: "not_yet_anchored",
|
|
55
|
+
chain_id: args.chain_id,
|
|
56
|
+
seq: args.seq,
|
|
57
|
+
envelope_hash: ev.envelope_hash,
|
|
58
|
+
note: "Event is signed and chained but not yet included in an on-chain anchor. Call again after the next epoch anchor to get a Merkle inclusion proof.",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const proofUrl = `${ctx.recorderUrl}/epoch/${ev.epoch_id}/proof/${encodeURIComponent(args.chain_id)}/${args.seq}`;
|
|
62
|
+
const proofRes = await fetch(proofUrl);
|
|
63
|
+
if (!proofRes.ok) {
|
|
64
|
+
throw new Error(`recorder returned HTTP ${proofRes.status} on proof fetch`);
|
|
65
|
+
}
|
|
66
|
+
const proof = (await proofRes.json());
|
|
67
|
+
return {
|
|
68
|
+
status: "anchored",
|
|
69
|
+
chain_id: proof.chain_id,
|
|
70
|
+
seq: proof.seq,
|
|
71
|
+
epoch_id: proof.epoch_id,
|
|
72
|
+
leaf: proof.leaf,
|
|
73
|
+
leaf_index: proof.leaf_index,
|
|
74
|
+
merkle_root: proof.merkle_root,
|
|
75
|
+
siblings: proof.siblings,
|
|
76
|
+
anchor: proof.anchor,
|
|
77
|
+
verification_note: proof.verification_note,
|
|
78
|
+
source_url: proofUrl,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export async function verifyBundle(ctx, args) {
|
|
82
|
+
if (!args.bundle || typeof args.bundle !== "object") {
|
|
83
|
+
throw new Error("bundle (object) is required");
|
|
84
|
+
}
|
|
85
|
+
const b = args.bundle;
|
|
86
|
+
// Normalize both shapes to the one @sheafhq/verify expects.
|
|
87
|
+
let verifyInput;
|
|
88
|
+
if (b.version === "sheaf-bundle/1" && b.envelope && b.anchor && b.envelope_hash) {
|
|
89
|
+
verifyInput = {
|
|
90
|
+
envelope: b.envelope,
|
|
91
|
+
inclusion_proof: {
|
|
92
|
+
leaf: b.envelope_hash,
|
|
93
|
+
leaf_index: b.anchor.leaf_index ?? 0,
|
|
94
|
+
merkle_root: b.anchor.merkle_root ?? "",
|
|
95
|
+
siblings: b.anchor.siblings ?? [],
|
|
96
|
+
},
|
|
97
|
+
anchor_tx_hash: b.anchor.tx_hash ?? "",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
else if (b.envelope && b.inclusion_proof && b.anchor_tx_hash) {
|
|
101
|
+
verifyInput = {
|
|
102
|
+
envelope: b.envelope,
|
|
103
|
+
inclusion_proof: b.inclusion_proof,
|
|
104
|
+
anchor_tx_hash: b.anchor_tx_hash,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw new Error("bundle shape not recognized; expected sheaf-bundle/1 or legacy {envelope, inclusion_proof, anchor_tx_hash}");
|
|
109
|
+
}
|
|
110
|
+
// Pull recorder public key. Prefer the key embedded in the bundle so the
|
|
111
|
+
// verification is self-contained; fall back to /.well-known if missing.
|
|
112
|
+
let recorderKey = b.issuer?.public_key_base64 ?? "";
|
|
113
|
+
if (!recorderKey) {
|
|
114
|
+
const r = await fetch(`${ctx.recorderUrl}/.well-known/sheaf-public-key`);
|
|
115
|
+
if (!r.ok) {
|
|
116
|
+
throw new Error(`could not fetch recorder public key: HTTP ${r.status}`);
|
|
117
|
+
}
|
|
118
|
+
const pk = (await r.json());
|
|
119
|
+
const active = pk.keys.find((k) => k.key_id === pk.active_key_id);
|
|
120
|
+
if (!active)
|
|
121
|
+
throw new Error("recorder public key not found in keyring");
|
|
122
|
+
recorderKey = active.public_key_base64;
|
|
123
|
+
}
|
|
124
|
+
// Anchorer address: caller may override; else default to the canonical
|
|
125
|
+
// sheaf-recorder anchorer. (Kept hardcoded here rather than exposing it
|
|
126
|
+
// as another remote fetch — any mismatch means the caller is verifying
|
|
127
|
+
// against the wrong issuer, which is itself a useful failure mode.)
|
|
128
|
+
const anchorer = args.anchorer_address ?? "0x08A22c6899a8b2eccA4b2B752a89eD498d7b2100";
|
|
129
|
+
const result = await verify(verifyInput, {
|
|
130
|
+
base_rpc: ctx.baseRpc,
|
|
131
|
+
recorder_public_key_base64: recorderKey,
|
|
132
|
+
expected_anchorer_address: anchorer,
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
valid: result.valid,
|
|
136
|
+
checks: result.checks,
|
|
137
|
+
reason: result.reason,
|
|
138
|
+
anchor_block: result.anchor_block,
|
|
139
|
+
recorder: ctx.recorderUrl,
|
|
140
|
+
anchorer_checked: anchorer,
|
|
141
|
+
};
|
|
142
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sheafhq/mcp-tools",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "MCP server exposing Sheaf read tools (chain_head, get_proof_for, verify_bundle). Lets any MCP-compatible agent query the Sheaf recorder and verify receipts offline. Pairs with @sheafhq/mcp-recorder (transparent event capture) but ships as a separate, opt-in tool surface so the recorder stays purely transparent.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/cli.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"sheaf-mcp-tools": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": ["dist", "README.md"],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"sheaf",
|
|
17
|
+
"mcp",
|
|
18
|
+
"mcp-server",
|
|
19
|
+
"audit",
|
|
20
|
+
"attestation",
|
|
21
|
+
"agent",
|
|
22
|
+
"verification",
|
|
23
|
+
"receipt",
|
|
24
|
+
"merkle-proof",
|
|
25
|
+
"evidence"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/sheafhq/sheaf.git",
|
|
31
|
+
"directory": "packages/mcp-tools"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/sheafhq/sheaf/tree/main/packages/mcp-tools",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/sheafhq/sheaf/issues"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@sheafhq/verify": "^0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"typescript": "^5.6.0",
|
|
45
|
+
"@types/node": "^22.0.0",
|
|
46
|
+
"@anthropic-ai/sdk": "^0.33.0"
|
|
47
|
+
}
|
|
48
|
+
}
|