@sheafhq/mcp-recorder 0.0.0-stage → 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/README.md CHANGED
@@ -1,3 +1,102 @@
1
- # Temporary Holding Version
1
+ # @sheafhq/mcp-recorder
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Transparent MCP (Model Context Protocol) proxy that captures every tool call an agent makes and emits signed, hash-chained, on-chain-anchored receipts to the [Sheaf](https://github.com/sheafhq/sheaf) recorder.
4
+
5
+ **Zero-code install.** Wrap any existing MCP server in Claude Desktop (or any MCP-aware client) with a one-line config change. No modifications to the wrapped server. No modifications to the agent.
6
+
7
+ Receipts are verifiable offline by any third party using [`@sheafhq/verify`](https://www.npmjs.com/package/@sheafhq/verify).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install -g @sheafhq/mcp-recorder
13
+ ```
14
+
15
+ ## Claude Desktop setup
16
+
17
+ Edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`; Windows: `%APPDATA%\Claude\`). Wrap the servers you want to audit:
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "filesystem": {
23
+ "command": "sheaf-mcp-recorder",
24
+ "args": [
25
+ "--wrap",
26
+ "npx",
27
+ "-y",
28
+ "@modelcontextprotocol/server-filesystem",
29
+ "/Users/me/Documents"
30
+ ],
31
+ "env": {
32
+ "SHEAF_CHAIN_ID": "claude-me-filesystem",
33
+ "SHEAF_ACTOR": "claude-desktop-me"
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ Restart Claude Desktop. Every `tools/call`, `resources/read`, and `sampling/createMessage` through this server now flows through Sheaf and gets a signed, chained receipt — with the tool arguments and the response hashed (never sent raw).
41
+
42
+ ## How it works
43
+
44
+ ```
45
+ Claude (MCP client)
46
+ │ JSON-RPC over stdio
47
+ ▼
48
+ ┌──────────────────────────┐
49
+ │ sheaf-mcp-recorder │ ← this package. Transparent stdio proxy.
50
+ │ (zero-code wrapper) │
51
+ └───────────┬──────────────┘
52
+ │ JSON-RPC unchanged
53
+ ▼
54
+ ┌──────────────────────────┐
55
+ │ wrapped MCP server │ (filesystem, github, slack, …)
56
+ └───────────┬──────────────┘
57
+ │ JSON-RPC response
58
+ ▼
59
+ (tap: build envelope, POST to Sheaf recorder)
60
+ │
61
+ ▼
62
+ ┌──────────────────────────┐
63
+ │ Sheaf recorder │ signs + chains + persists
64
+ │ sheaf-recorder.*.dev │ (anchored on-chain by Sheaf)
65
+ └──────────────────────────┘
66
+ ```
67
+
68
+ Fail-open design: if the recorder is unreachable, the agent's tool calls still work. Missed events are logged to stderr; the chain re-syncs on the next successful submission.
69
+
70
+ ## Environment variables
71
+
72
+ | Var | Required | Default | Notes |
73
+ |---|---|---|---|
74
+ | `SHEAF_CHAIN_ID` | **yes** | — | Stable identifier for this agent/session's event chain |
75
+ | `SHEAF_ACTOR` | no | = `SHEAF_CHAIN_ID` | Written into every event's `actor` field |
76
+ | `SHEAF_RECORDER_URL` | no | `https://sheaf-recorder.goldencalf.workers.dev` | Point at your own instance if self-hosting |
77
+ | `SHEAF_CAPTURE_METHODS` | no | `tools/call,resources/read,sampling/createMessage` | Comma-separated JSON-RPC methods to intercept |
78
+ | `SHEAF_DEBUG` | no | — | `1` to log every event to stderr |
79
+
80
+ ## What gets recorded
81
+
82
+ For every intercepted tool call, one hash-chained event containing:
83
+
84
+ - **intent** — short description (e.g. `invoke tool write_file`)
85
+ - **actor** — from `SHEAF_ACTOR`
86
+ - **tool** — e.g. `mcp://tool/write_file`
87
+ - **inputs_hash** — `sha256(canonicalize(params))` of the tool arguments (raw args are **not** sent to the recorder)
88
+ - **outputs_hash** — same for the response, or null on error
89
+ - **result** — `ok` / `error` / `timeout` / `cancelled`
90
+ - **ts** — ISO 8601 UTC timestamp
91
+
92
+ The envelope is hash-chained (`prev_hash` → previous envelope hash) so any tamper is detectable. The recorder co-signs with Ed25519 and anchors the Merkle root on Base L2 periodically.
93
+
94
+ ## Verifying a receipt later
95
+
96
+ Any third party can verify a stored receipt using `@sheafhq/verify` — it reads the anchor transaction from Base and checks the Merkle inclusion + Ed25519 signature offline.
97
+
98
+ See the main [Sheaf ARCHITECTURE.md](https://github.com/sheafhq/sheaf/blob/main/ARCHITECTURE.md) for the full protocol.
99
+
100
+ ## License
101
+
102
+ MIT
@@ -0,0 +1,40 @@
1
+ // RFC 8785 JCS — identical to packages/verify/src/canonical.ts.
2
+ export function canonicalize(value) {
3
+ if (value === null)
4
+ return "null";
5
+ if (typeof value === "boolean")
6
+ return value ? "true" : "false";
7
+ if (typeof value === "number") {
8
+ if (!Number.isFinite(value))
9
+ throw new Error("JCS: non-finite number");
10
+ return JSON.stringify(value);
11
+ }
12
+ if (typeof value === "string")
13
+ return JSON.stringify(value);
14
+ if (Array.isArray(value)) {
15
+ return "[" + value.map(canonicalize).join(",") + "]";
16
+ }
17
+ if (typeof value === "object") {
18
+ const obj = value;
19
+ const keys = Object.keys(obj).sort();
20
+ return ("{" +
21
+ keys
22
+ .map((k) => JSON.stringify(k) + ":" + canonicalize(obj[k]))
23
+ .join(",") +
24
+ "}");
25
+ }
26
+ throw new Error(`JCS: unsupported value type: ${typeof value}`);
27
+ }
28
+ import { createHash } from "node:crypto";
29
+ export function sha256Hex(data) {
30
+ const h = createHash("sha256");
31
+ if (typeof data === "string")
32
+ h.update(data, "utf8");
33
+ else
34
+ h.update(data);
35
+ return h.digest("hex");
36
+ }
37
+ export function hashCanonical(value) {
38
+ return sha256Hex(canonicalize(value));
39
+ }
40
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QACvE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC5D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACvD,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,KAAgC,CAAC;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,CACL,GAAG;YACH,IAAI;iBACD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;iBAC1D,IAAI,CAAC,GAAG,CAAC;YACZ,GAAG,CACJ,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,gCAAgC,OAAO,KAAK,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,UAAU,SAAS,CAAC,IAAkC;IAC1D,MAAM,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;;QAChD,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpB,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AACxC,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ // CLI entry for sheaf-mcp-recorder. Usage:
3
+ // sheaf-mcp-recorder --wrap <command> [args...]
4
+ //
5
+ // Environment variables:
6
+ // SHEAF_RECORDER_URL where to POST events (default: production)
7
+ // SHEAF_CHAIN_ID required. Stable per-session / per-agent identifier.
8
+ // SHEAF_ACTOR default: SHEAF_CHAIN_ID
9
+ // SHEAF_CAPTURE_METHODS comma-separated JSON-RPC methods to capture
10
+ // (default: tools/call,resources/read,sampling/createMessage)
11
+ // SHEAF_DEBUG=1 verbose stderr logging
12
+ import { runProxy } from "./proxy.js";
13
+ const argv = process.argv.slice(2);
14
+ function die(msg, code = 2) {
15
+ console.error(`sheaf-mcp-recorder: ${msg}`);
16
+ console.error("usage: sheaf-mcp-recorder --wrap <command> [args...]\n" +
17
+ " (set SHEAF_CHAIN_ID; see package README for all env vars)");
18
+ process.exit(code);
19
+ }
20
+ const wrapIdx = argv.indexOf("--wrap");
21
+ if (wrapIdx === -1)
22
+ die("missing --wrap flag");
23
+ if (wrapIdx === argv.length - 1)
24
+ die("no command after --wrap");
25
+ const command = argv[wrapIdx + 1];
26
+ const commandArgs = argv.slice(wrapIdx + 2);
27
+ const chainId = process.env.SHEAF_CHAIN_ID;
28
+ if (!chainId)
29
+ die("SHEAF_CHAIN_ID env var is required");
30
+ const cfg = {
31
+ recorderUrl: process.env.SHEAF_RECORDER_URL ??
32
+ "https://sheaf-recorder.goldencalf.workers.dev",
33
+ chainId,
34
+ actor: process.env.SHEAF_ACTOR ?? chainId,
35
+ captureMethods: new Set((process.env.SHEAF_CAPTURE_METHODS ??
36
+ "tools/call,resources/read,sampling/createMessage")
37
+ .split(",")
38
+ .map((s) => s.trim())
39
+ .filter(Boolean)),
40
+ logDebug: process.env.SHEAF_DEBUG === "1",
41
+ };
42
+ if (cfg.logDebug) {
43
+ console.error(`[arca] wrapping: ${command} ${commandArgs.join(" ")}`);
44
+ console.error(`[arca] recorder=${cfg.recorderUrl} chain=${cfg.chainId} actor=${cfg.actor} capture=[${[...cfg.captureMethods].join(",")}]`);
45
+ }
46
+ runProxy(cfg, command, commandArgs);
47
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,2CAA2C;AAC3C,kDAAkD;AAClD,EAAE;AACF,yBAAyB;AACzB,oEAAoE;AACpE,8EAA8E;AAC9E,iDAAiD;AACjD,uEAAuE;AACvE,oFAAoF;AACpF,gDAAgD;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,SAAS,GAAG,CAAC,GAAW,EAAE,IAAI,GAAG,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,KAAK,CACX,wDAAwD;QACtD,kEAAkE,CACrE,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AACvC,IAAI,OAAO,KAAK,CAAC,CAAC;IAAE,GAAG,CAAC,qBAAqB,CAAC,CAAC;AAC/C,IAAI,OAAO,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC;IAAE,GAAG,CAAC,yBAAyB,CAAC,CAAC;AAEhE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,GAAG,CAAC,CAAE,CAAC;AACnC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;AAE5C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;AAC3C,IAAI,CAAC,OAAO;IAAE,GAAG,CAAC,oCAAoC,CAAC,CAAC;AAExD,MAAM,GAAG,GAAmB;IAC1B,WAAW,EACT,OAAO,CAAC,GAAG,CAAC,kBAAkB;QAC9B,+CAA+C;IACjD,OAAO;IACP,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO;IACzC,cAAc,EAAE,IAAI,GAAG,CACrB,CACE,OAAO,CAAC,GAAG,CAAC,qBAAqB;QACjC,kDAAkD,CACnD;SACE,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CACnB;IACD,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,GAAG;CAC1C,CAAC;AAEF,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;IACjB,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtE,OAAO,CAAC,KAAK,CACX,mBAAmB,GAAG,CAAC,WAAW,UAAU,GAAG,CAAC,OAAO,UAAU,GAAG,CAAC,KAAK,aAAa,CAAC,GAAG,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAC5H,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC"}
@@ -0,0 +1,138 @@
1
+ // Build an event envelope + submit to the Sheaf recorder. Submission is a
2
+ // background best-effort operation: it must never block the MCP proxy pipeline,
3
+ // and a recorder outage must not break the agent's tool calls.
4
+ import { canonicalize, sha256Hex } from "./canonical.js";
5
+ export class RecorderClient {
6
+ cfg;
7
+ state;
8
+ queue = Promise.resolve();
9
+ constructor(cfg) {
10
+ this.cfg = cfg;
11
+ this.state = this.fetchInitialState();
12
+ }
13
+ debug(...args) {
14
+ if (this.cfg.logDebug)
15
+ console.error("[arca]", ...args);
16
+ }
17
+ async fetchInitialState() {
18
+ try {
19
+ const url = `${this.cfg.recorderUrl}/chains/${encodeURIComponent(this.cfg.chainId)}/head`;
20
+ const r = await fetch(url);
21
+ if (r.status === 404) {
22
+ this.debug(`chain ${this.cfg.chainId} is new; starting at seq=0`);
23
+ return { seq: 0, prev_hash: null };
24
+ }
25
+ if (!r.ok) {
26
+ this.debug(`recorder /head returned ${r.status}; assuming new chain`);
27
+ return { seq: 0, prev_hash: null };
28
+ }
29
+ const body = (await r.json());
30
+ this.debug(`chain ${this.cfg.chainId} head is seq=${body.head_seq}; resuming at seq=${body.head_seq + 1}`);
31
+ return { seq: body.head_seq + 1, prev_hash: body.head_hash };
32
+ }
33
+ catch (err) {
34
+ this.debug(`could not reach recorder for initial head (${err.message}); starting at seq=0`);
35
+ return { seq: 0, prev_hash: null };
36
+ }
37
+ }
38
+ /** Queue an event submission. Returns immediately; does not block the caller. */
39
+ submit(call, response) {
40
+ this.queue = this.queue.then(() => this.submitInternal(call, response));
41
+ }
42
+ /** Wait for all queued submissions to finish. Call before process exit. */
43
+ async drain() {
44
+ await this.queue;
45
+ }
46
+ async submitInternal(call, response) {
47
+ try {
48
+ const state = await this.state;
49
+ let result = "ok";
50
+ let outputsHash = null;
51
+ if (!response)
52
+ result = "timeout";
53
+ else if (response.error)
54
+ result = "error";
55
+ else if (response.result !== undefined)
56
+ outputsHash = "sha256:" + sha256Hex(canonicalize(response.result));
57
+ const inputsHash = "sha256:" + sha256Hex(canonicalize(call.params));
58
+ const envelope = {
59
+ v: "arca/1",
60
+ event: {
61
+ intent: deriveIntent(call),
62
+ actor: this.cfg.actor,
63
+ authority: null,
64
+ tool: deriveToolName(call),
65
+ inputs_hash: inputsHash,
66
+ outputs_hash: outputsHash,
67
+ result,
68
+ ts: new Date().toISOString(),
69
+ },
70
+ chain: {
71
+ chain_id: this.cfg.chainId,
72
+ seq: state.seq,
73
+ prev_hash: state.prev_hash,
74
+ },
75
+ signatures: { actor: null },
76
+ };
77
+ const body = canonicalize(envelope);
78
+ const r = await fetch(`${this.cfg.recorderUrl}/events`, {
79
+ method: "POST",
80
+ headers: { "content-type": "application/json" },
81
+ body,
82
+ });
83
+ const receipt = (await r.json());
84
+ if (r.status === 201 && receipt.ok && typeof receipt.seq === "number") {
85
+ this.state = Promise.resolve({
86
+ seq: receipt.seq + 1,
87
+ prev_hash: receipt.envelope_hash ?? state.prev_hash,
88
+ });
89
+ this.debug(`recorded seq=${receipt.seq} for ${envelope.event.tool} → ${envelope.event.result}`);
90
+ return;
91
+ }
92
+ // 409 with head info means our local state drifted. Re-sync and give up
93
+ // this one submission (the proxy will keep working; recorder stays
94
+ // consistent).
95
+ if (r.status === 409 &&
96
+ typeof receipt.current_head_seq === "number" &&
97
+ typeof receipt.current_head_hash === "string") {
98
+ this.state = Promise.resolve({
99
+ seq: receipt.current_head_seq + 1,
100
+ prev_hash: receipt.current_head_hash,
101
+ });
102
+ this.debug(`resynced after 409: new seq=${receipt.current_head_seq + 1} (missed this submission)`);
103
+ return;
104
+ }
105
+ this.debug(`submission failed (${r.status}): ${receipt.error ?? "unknown"}; event dropped`);
106
+ }
107
+ catch (err) {
108
+ this.debug(`submit threw: ${err.message}; event dropped`);
109
+ }
110
+ }
111
+ }
112
+ function deriveToolName(call) {
113
+ const p = call.params;
114
+ switch (call.method) {
115
+ case "tools/call":
116
+ return `mcp://tool/${p?.name ?? "unknown"}`;
117
+ case "resources/read":
118
+ return `mcp://resource/${p?.uri ?? "unknown"}`;
119
+ case "sampling/createMessage":
120
+ return `mcp://sampling/createMessage`;
121
+ default:
122
+ return `mcp://method/${call.method}`;
123
+ }
124
+ }
125
+ function deriveIntent(call) {
126
+ const p = call.params;
127
+ switch (call.method) {
128
+ case "tools/call":
129
+ return `invoke tool ${p?.name ?? "unknown"}`;
130
+ case "resources/read":
131
+ return `read resource ${p?.uri ?? "unknown"}`;
132
+ case "sampling/createMessage":
133
+ return `sample LLM completion`;
134
+ default:
135
+ return call.method;
136
+ }
137
+ }
138
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"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"}
package/dist/proxy.js ADDED
@@ -0,0 +1,97 @@
1
+ // Stdio-mode MCP proxy. Spawn the wrapped server, relay JSON-RPC traffic
2
+ // bidirectionally. Tap tools/call, resources/read, sampling/createMessage.
3
+ // Fail open — any error in the recorder path must NOT disrupt the agent's
4
+ // tool calls.
5
+ import { spawn } from "node:child_process";
6
+ import { createInterface } from "node:readline";
7
+ import { RecorderClient } from "./envelope.js";
8
+ export function runProxy(cfg, childCmd, childArgs) {
9
+ const recorder = new RecorderClient(cfg);
10
+ const pending = new Map();
11
+ const child = spawn(childCmd, childArgs, {
12
+ stdio: ["pipe", "pipe", "inherit"],
13
+ env: process.env,
14
+ });
15
+ child.on("exit", (code, signal) => {
16
+ if (cfg.logDebug)
17
+ console.error(`[arca] wrapped child exited code=${code} signal=${signal}; draining recorder queue`);
18
+ recorder
19
+ .drain()
20
+ .catch((err) => {
21
+ if (cfg.logDebug)
22
+ console.error(`[arca] drain error: ${err.message}`);
23
+ })
24
+ .finally(() => {
25
+ process.exit(code ?? 0);
26
+ });
27
+ });
28
+ child.on("error", (err) => {
29
+ console.error(`[arca] failed to spawn wrapped child: ${err.message}`);
30
+ process.exit(127);
31
+ });
32
+ // Parent stdin → child stdin. Also tap each line as an outgoing request.
33
+ const parentIn = createInterface({ input: process.stdin });
34
+ parentIn.on("line", (line) => {
35
+ if (!line.trim())
36
+ return;
37
+ try {
38
+ const msg = JSON.parse(line);
39
+ if (msg &&
40
+ typeof msg === "object" &&
41
+ "method" in msg &&
42
+ "id" in msg &&
43
+ cfg.captureMethods.has(msg.method)) {
44
+ pending.set(msg.id, {
45
+ id: msg.id,
46
+ method: msg.method,
47
+ params: msg.params ?? {},
48
+ startedAt: Date.now(),
49
+ });
50
+ }
51
+ }
52
+ catch {
53
+ // Not valid JSON — just pass through.
54
+ }
55
+ child.stdin.write(line + "\n");
56
+ });
57
+ parentIn.on("close", () => {
58
+ try {
59
+ child.stdin.end();
60
+ }
61
+ catch { }
62
+ });
63
+ // Child stdout → parent stdout. Tap responses that match a pending request.
64
+ const childOut = createInterface({ input: child.stdout });
65
+ childOut.on("line", (line) => {
66
+ if (!line.trim()) {
67
+ process.stdout.write(line + "\n");
68
+ return;
69
+ }
70
+ try {
71
+ const msg = JSON.parse(line);
72
+ if (msg &&
73
+ typeof msg === "object" &&
74
+ "id" in msg &&
75
+ (msg.id || msg.id === 0) &&
76
+ pending.has(msg.id)) {
77
+ const call = pending.get(msg.id);
78
+ pending.delete(msg.id);
79
+ recorder.submit(call, { result: msg.result, error: msg.error });
80
+ }
81
+ }
82
+ catch {
83
+ // Not valid JSON — pass through.
84
+ }
85
+ process.stdout.write(line + "\n");
86
+ });
87
+ // Forward signals so Ctrl-C etc. kills the child cleanly.
88
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
89
+ process.on(sig, () => {
90
+ try {
91
+ child.kill(sig);
92
+ }
93
+ catch { }
94
+ });
95
+ }
96
+ }
97
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "@sheafhq/mcp-recorder",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Transparent MCP (Model Context Protocol) proxy that captures every tool call an agent makes and emits signed, hash-chained, on-chain-anchored receipts via the Sheaf recorder. Zero-code install via claude_desktop_config.json.",
5
+ "type": "module",
6
+ "main": "./dist/cli.js",
7
+ "bin": {
8
+ "sheaf-mcp-recorder": "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
+ "claude",
23
+ "interceptor",
24
+ "proxy",
25
+ "receipt",
26
+ "hash-chain"
27
+ ],
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/sheafhq/sheaf.git",
32
+ "directory": "packages/mcp-recorder"
33
+ },
34
+ "homepage": "https://github.com/sheafhq/sheaf/tree/main/packages/mcp-recorder",
35
+ "bugs": {
36
+ "url": "https://github.com/sheafhq/sheaf/issues"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "^5.6.0",
43
+ "@types/node": "^22.0.0"
44
+ }
45
+ }