commitlayer-mcp-recorder 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Asterfold Labs, Inc.
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.
@@ -0,0 +1,212 @@
1
+ Metadata-Version: 2.4
2
+ Name: commitlayer-mcp-recorder
3
+ Version: 0.1.0
4
+ Summary: CommitLayer MCP Recorder: a stdio proxy that records an agent's MCP tool calls as CommitLayer actions
5
+ Author-email: "Asterfold Labs, Inc." <hello@commitlayer.ai>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://commitlayer.ai
8
+ Project-URL: Documentation, https://commitlayer.ai/docs
9
+ Project-URL: Changelog, https://commitlayer.ai/changelog
10
+ Project-URL: Source, https://github.com/kobikwelu/stateprf-svcs/tree/main/recorder/python
11
+ Keywords: ai-agents,verification,audit,mcp,observability
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: yaml
24
+ Requires-Dist: pyyaml>=6.0; extra == "yaml"
25
+ Dynamic: license-file
26
+
27
+ # CommitLayer MCP Recorder (Python)
28
+
29
+ A small stdio proxy that sits between an MCP client (Claude Desktop, Claude Code, Cursor, an agent framework)
30
+ and any downstream MCP server (a Stripe or Zendesk MCP server, your own). It forwards every JSON-RPC message
31
+ unchanged in both directions, and when the client calls a mapped tool it records the intent as a CommitLayer
32
+ action **before** forwarding the call. Standard library only; Python 3.10+.
33
+
34
+ > The agent may declare its intent. It may read the verdict. It must never control the independent facts, the governing contract, or the final evidence.
35
+
36
+ The recorder only does the first of those three. It never blocks, reorders or alters the agent's traffic, and
37
+ the downstream tool's result is never sent to CommitLayer: a tool answering "refund issued" is a claim, not
38
+ evidence. Verification reads the systems of record.
39
+
40
+ ```bash
41
+ pip install ./recorder/python # from this repository; `pip install pyyaml` for .yaml mappings
42
+ export COMMITLAYER_API_KEY=spk_... # a workspace API key; without it the proxy is a passthrough
43
+ commitlayer-mcp-recorder --map mapping.json -- npx -y @stripe/mcp --tools=all
44
+ ```
45
+
46
+ ## Wiring it into a client
47
+
48
+ Wherever the client is told how to start the server, put the recorder in front and move the real command
49
+ after `--`.
50
+
51
+ Claude Desktop (`claude_desktop_config.json`):
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "stripe": {
57
+ "command": "commitlayer-mcp-recorder",
58
+ "args": ["--map", "/etc/commitlayer/stripe.json", "--agent-id", "support-bot", "--", "npx", "-y", "@stripe/mcp", "--tools=all"],
59
+ "env": {"COMMITLAYER_API_KEY": "spk_..."}
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ Claude Code:
66
+
67
+ ```bash
68
+ claude mcp add stripe -e COMMITLAYER_API_KEY=spk_... -- commitlayer-mcp-recorder --map stripe.json -- npx -y @stripe/mcp --tools=all
69
+ ```
70
+
71
+ Cursor and other clients use the same `command` / `args` / `env` shape. The downstream server does not know
72
+ the recorder is there; it sees the same bytes it would have seen.
73
+
74
+ ## CLI
75
+
76
+ ```
77
+ commitlayer-mcp-recorder --map mapping.json [--agent-id ID] [--workflow-id ID] [--api-url URL]
78
+ [--timeout 5] [--no-fail-open] [--warn-unmapped] -- <downstream command and args>
79
+ ```
80
+
81
+ | Flag | Meaning |
82
+ | --- | --- |
83
+ | `--map` | Mapping file, JSON or YAML (YAML needs PyYAML). Default `$COMMITLAYER_RECORDER_MAP`. |
84
+ | `--agent-id`, `--workflow-id` | Override `agent_id` / `workflow_id` from the mapping file. |
85
+ | `--api-url` | Default `$COMMITLAYER_API_URL`, then `https://api.commitlayer.ai`. |
86
+ | `--timeout` | Seconds to wait for the API per call (default 5). |
87
+ | `--no-fail-open` | When recording fails, refuse the call instead of forwarding it (see below). |
88
+ | `--warn-unmapped` | Log each tool that is called but not mapped, once per tool name. |
89
+
90
+ Environment: `COMMITLAYER_API_KEY` (required to record; without it the recorder warns once at startup and
91
+ runs as a pure passthrough), `COMMITLAYER_API_URL`, `COMMITLAYER_RECORDER_MAP`. The key is never logged. All
92
+ diagnostics go to stderr, one line each, prefixed `commitlayer-recorder:`. The downstream server's own stderr
93
+ is relayed as is.
94
+
95
+ The proxy exits when the client closes stdin or the downstream server exits, with the server's exit code.
96
+
97
+ ## Mapping file
98
+
99
+ ```json
100
+ {
101
+ "agent_id": "support-bot",
102
+ "workflow_id": "refunds",
103
+ "tools": {
104
+ "refund_payment": {
105
+ "contract_id": "refund_workflow",
106
+ "action_type": "refund_order",
107
+ "subject": "arguments.order_id",
108
+ "subject_kind": "order",
109
+ "params": {"amount": "arguments.amount", "currency": "arguments.currency"},
110
+ "action_id": "refund:{arguments.order_id}",
111
+ "compensates": "arguments.original_action_id"
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ - Keys under `tools` are MCP tool names. An exact match wins; a key with a server prefix
118
+ (`stripe.refund_payment`) also matches the bare name (`refund_payment`).
119
+ - `contract_id` and `subject` are required per tool. Unknown keys are allowed and ignored.
120
+ - `subject`, each `params` value and `compensates` are dotted paths into the `tools/call` params object:
121
+ `arguments.order_id`, `name`, `_meta.progressToken`, `arguments.items.0`.
122
+ - `action_id` is a template with `{path}` placeholders. Without it the id is `mcp:{tool}:{request id}`, so a
123
+ retried request updates one record instead of creating two.
124
+ - `contract_version` (integer), `action_type` and `subject_kind` are passed through.
125
+ - A `params` path that is absent from the call is skipped; an absent `compensates` path is omitted. An absent
126
+ `subject` (or `action_id` placeholder) means the call is forwarded **but not recorded**, with a warning.
127
+
128
+ The mapping is validated on load; an invalid file stops the proxy with exit code 2 and a message naming the
129
+ tool and key.
130
+
131
+ ### Example: the Coinbase MCP server
132
+
133
+ [`examples/coinbase.json`](examples/coinbase.json) maps `orders_create` (the order tool of the Coinbase MCP
134
+ server behind Coinbase for Agents) to the `coinbase_agent_trade` library contract: the subject is the
135
+ product id the agent passes, `amount` is its quote size, `side` and `client_order_id` ride along, and the
136
+ action id is derived from the client order id so a retried call updates one record. The paths follow the
137
+ create-order request; run the recorder once with `--warn-unmapped` against the server's own `tools/list`
138
+ and adjust them if the argument names differ. The Coinbase server is remote (HTTP), and the recorder is a
139
+ stdio proxy: put it in front of whatever stdio bridge the client uses to reach the remote server, so the
140
+ bridge is the "downstream command". Verification then reads the order and its fills from Coinbase through
141
+ the Coinbase connector; the tool's own answer is never evidence.
142
+
143
+ ## What is recorded
144
+
145
+ For each client → server `tools/call` whose `params.name` is mapped, the recorder posts one action to
146
+ `POST {api_url}/v1/actions` (`x-api-key`, `content-type: application/json`) and then forwards the original
147
+ line unchanged:
148
+
149
+ ```json
150
+ {
151
+ "action_id": "refund:order_10231",
152
+ "contract_id": "refund_workflow",
153
+ "subject_id": "order_10231",
154
+ "subject_kind": "order",
155
+ "action_type": "refund_order",
156
+ "agent_id": "support-bot",
157
+ "workflow_id": "refunds",
158
+ "requested_at": "2026-10-15T12:00:00Z",
159
+ "params": {"amount": 180.0, "currency": "USD", "mcp_tool": "refund_payment", "mcp_request_id": "7"},
160
+ "channel": "mcp"
161
+ }
162
+ ```
163
+
164
+ `params` always carries `mcp_tool` and `mcp_request_id`. `requested_at` is taken when the call is seen, so
165
+ it precedes every effect. Unmapped tools, notifications, `initialize`, `tools/list` and everything else pass
166
+ straight through.
167
+
168
+ When the downstream answer for a recorded request comes back, the recorder logs one line:
169
+
170
+ ```
171
+ commitlayer-recorder: recorded refund:order_10231 → tool result isError=false
172
+ ```
173
+
174
+ That is all it does with the result. **The tool's own "success" is never evidence.** It is not sent to the
175
+ API and it does not change the verdict; CommitLayer settles the action from the facts the systems of record
176
+ report, under the governing contract. Read the verdict with the SDK or `GET /v1/actions/{action_id}`.
177
+
178
+ ## Fail-open, and the alternative
179
+
180
+ By default recording is fail-open: if the API answers 4xx/5xx, times out or cannot be reached, a warning is
181
+ written to stderr and the call is forwarded anyway. A verifier that is down must not stop the business
182
+ action; the gap shows up as an action that was never recorded rather than one that never happened.
183
+
184
+ With `--no-fail-open` a call whose recording fails is **not** forwarded. The client receives a JSON-RPC
185
+ error for that request id (`code -32000`, message `CommitLayer recording failed: …`) and the downstream
186
+ server never sees it. Other traffic is unaffected.
187
+
188
+ ## Library use
189
+
190
+ ```python
191
+ from commitlayer_mcp_recorder import Recorder, build_action, load_mapping
192
+
193
+ mapping = load_mapping("mapping.json")
194
+ recorder = Recorder(mapping, api_key="spk_...", agent_id="support-bot")
195
+ decision = recorder.before_forward(message) # a parsed client message; .forward, .action_id, .error
196
+ recorder.after_response(message) # a parsed server message; logs the isError flag
197
+ ```
198
+
199
+ `build_action(tool_name, call_params, tool_mapping, request_id=...)` returns the body shown above.
200
+
201
+ ## Limits
202
+
203
+ - Stdio transport only, newline-delimited JSON-RPC. Lines that are not JSON are forwarded untouched.
204
+ - JSON-RPC batch arrays are forwarded untouched and not inspected.
205
+ - A `tools/call` without a JSON-RPC id is forwarded without recording (there is nothing to match the answer
206
+ to).
207
+
208
+ ## Tests
209
+
210
+ ```bash
211
+ python -m pytest recorder/python/tests -q
212
+ ```
@@ -0,0 +1,186 @@
1
+ # CommitLayer MCP Recorder (Python)
2
+
3
+ A small stdio proxy that sits between an MCP client (Claude Desktop, Claude Code, Cursor, an agent framework)
4
+ and any downstream MCP server (a Stripe or Zendesk MCP server, your own). It forwards every JSON-RPC message
5
+ unchanged in both directions, and when the client calls a mapped tool it records the intent as a CommitLayer
6
+ action **before** forwarding the call. Standard library only; Python 3.10+.
7
+
8
+ > The agent may declare its intent. It may read the verdict. It must never control the independent facts, the governing contract, or the final evidence.
9
+
10
+ The recorder only does the first of those three. It never blocks, reorders or alters the agent's traffic, and
11
+ the downstream tool's result is never sent to CommitLayer: a tool answering "refund issued" is a claim, not
12
+ evidence. Verification reads the systems of record.
13
+
14
+ ```bash
15
+ pip install ./recorder/python # from this repository; `pip install pyyaml` for .yaml mappings
16
+ export COMMITLAYER_API_KEY=spk_... # a workspace API key; without it the proxy is a passthrough
17
+ commitlayer-mcp-recorder --map mapping.json -- npx -y @stripe/mcp --tools=all
18
+ ```
19
+
20
+ ## Wiring it into a client
21
+
22
+ Wherever the client is told how to start the server, put the recorder in front and move the real command
23
+ after `--`.
24
+
25
+ Claude Desktop (`claude_desktop_config.json`):
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "stripe": {
31
+ "command": "commitlayer-mcp-recorder",
32
+ "args": ["--map", "/etc/commitlayer/stripe.json", "--agent-id", "support-bot", "--", "npx", "-y", "@stripe/mcp", "--tools=all"],
33
+ "env": {"COMMITLAYER_API_KEY": "spk_..."}
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ Claude Code:
40
+
41
+ ```bash
42
+ claude mcp add stripe -e COMMITLAYER_API_KEY=spk_... -- commitlayer-mcp-recorder --map stripe.json -- npx -y @stripe/mcp --tools=all
43
+ ```
44
+
45
+ Cursor and other clients use the same `command` / `args` / `env` shape. The downstream server does not know
46
+ the recorder is there; it sees the same bytes it would have seen.
47
+
48
+ ## CLI
49
+
50
+ ```
51
+ commitlayer-mcp-recorder --map mapping.json [--agent-id ID] [--workflow-id ID] [--api-url URL]
52
+ [--timeout 5] [--no-fail-open] [--warn-unmapped] -- <downstream command and args>
53
+ ```
54
+
55
+ | Flag | Meaning |
56
+ | --- | --- |
57
+ | `--map` | Mapping file, JSON or YAML (YAML needs PyYAML). Default `$COMMITLAYER_RECORDER_MAP`. |
58
+ | `--agent-id`, `--workflow-id` | Override `agent_id` / `workflow_id` from the mapping file. |
59
+ | `--api-url` | Default `$COMMITLAYER_API_URL`, then `https://api.commitlayer.ai`. |
60
+ | `--timeout` | Seconds to wait for the API per call (default 5). |
61
+ | `--no-fail-open` | When recording fails, refuse the call instead of forwarding it (see below). |
62
+ | `--warn-unmapped` | Log each tool that is called but not mapped, once per tool name. |
63
+
64
+ Environment: `COMMITLAYER_API_KEY` (required to record; without it the recorder warns once at startup and
65
+ runs as a pure passthrough), `COMMITLAYER_API_URL`, `COMMITLAYER_RECORDER_MAP`. The key is never logged. All
66
+ diagnostics go to stderr, one line each, prefixed `commitlayer-recorder:`. The downstream server's own stderr
67
+ is relayed as is.
68
+
69
+ The proxy exits when the client closes stdin or the downstream server exits, with the server's exit code.
70
+
71
+ ## Mapping file
72
+
73
+ ```json
74
+ {
75
+ "agent_id": "support-bot",
76
+ "workflow_id": "refunds",
77
+ "tools": {
78
+ "refund_payment": {
79
+ "contract_id": "refund_workflow",
80
+ "action_type": "refund_order",
81
+ "subject": "arguments.order_id",
82
+ "subject_kind": "order",
83
+ "params": {"amount": "arguments.amount", "currency": "arguments.currency"},
84
+ "action_id": "refund:{arguments.order_id}",
85
+ "compensates": "arguments.original_action_id"
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ - Keys under `tools` are MCP tool names. An exact match wins; a key with a server prefix
92
+ (`stripe.refund_payment`) also matches the bare name (`refund_payment`).
93
+ - `contract_id` and `subject` are required per tool. Unknown keys are allowed and ignored.
94
+ - `subject`, each `params` value and `compensates` are dotted paths into the `tools/call` params object:
95
+ `arguments.order_id`, `name`, `_meta.progressToken`, `arguments.items.0`.
96
+ - `action_id` is a template with `{path}` placeholders. Without it the id is `mcp:{tool}:{request id}`, so a
97
+ retried request updates one record instead of creating two.
98
+ - `contract_version` (integer), `action_type` and `subject_kind` are passed through.
99
+ - A `params` path that is absent from the call is skipped; an absent `compensates` path is omitted. An absent
100
+ `subject` (or `action_id` placeholder) means the call is forwarded **but not recorded**, with a warning.
101
+
102
+ The mapping is validated on load; an invalid file stops the proxy with exit code 2 and a message naming the
103
+ tool and key.
104
+
105
+ ### Example: the Coinbase MCP server
106
+
107
+ [`examples/coinbase.json`](examples/coinbase.json) maps `orders_create` (the order tool of the Coinbase MCP
108
+ server behind Coinbase for Agents) to the `coinbase_agent_trade` library contract: the subject is the
109
+ product id the agent passes, `amount` is its quote size, `side` and `client_order_id` ride along, and the
110
+ action id is derived from the client order id so a retried call updates one record. The paths follow the
111
+ create-order request; run the recorder once with `--warn-unmapped` against the server's own `tools/list`
112
+ and adjust them if the argument names differ. The Coinbase server is remote (HTTP), and the recorder is a
113
+ stdio proxy: put it in front of whatever stdio bridge the client uses to reach the remote server, so the
114
+ bridge is the "downstream command". Verification then reads the order and its fills from Coinbase through
115
+ the Coinbase connector; the tool's own answer is never evidence.
116
+
117
+ ## What is recorded
118
+
119
+ For each client → server `tools/call` whose `params.name` is mapped, the recorder posts one action to
120
+ `POST {api_url}/v1/actions` (`x-api-key`, `content-type: application/json`) and then forwards the original
121
+ line unchanged:
122
+
123
+ ```json
124
+ {
125
+ "action_id": "refund:order_10231",
126
+ "contract_id": "refund_workflow",
127
+ "subject_id": "order_10231",
128
+ "subject_kind": "order",
129
+ "action_type": "refund_order",
130
+ "agent_id": "support-bot",
131
+ "workflow_id": "refunds",
132
+ "requested_at": "2026-10-15T12:00:00Z",
133
+ "params": {"amount": 180.0, "currency": "USD", "mcp_tool": "refund_payment", "mcp_request_id": "7"},
134
+ "channel": "mcp"
135
+ }
136
+ ```
137
+
138
+ `params` always carries `mcp_tool` and `mcp_request_id`. `requested_at` is taken when the call is seen, so
139
+ it precedes every effect. Unmapped tools, notifications, `initialize`, `tools/list` and everything else pass
140
+ straight through.
141
+
142
+ When the downstream answer for a recorded request comes back, the recorder logs one line:
143
+
144
+ ```
145
+ commitlayer-recorder: recorded refund:order_10231 → tool result isError=false
146
+ ```
147
+
148
+ That is all it does with the result. **The tool's own "success" is never evidence.** It is not sent to the
149
+ API and it does not change the verdict; CommitLayer settles the action from the facts the systems of record
150
+ report, under the governing contract. Read the verdict with the SDK or `GET /v1/actions/{action_id}`.
151
+
152
+ ## Fail-open, and the alternative
153
+
154
+ By default recording is fail-open: if the API answers 4xx/5xx, times out or cannot be reached, a warning is
155
+ written to stderr and the call is forwarded anyway. A verifier that is down must not stop the business
156
+ action; the gap shows up as an action that was never recorded rather than one that never happened.
157
+
158
+ With `--no-fail-open` a call whose recording fails is **not** forwarded. The client receives a JSON-RPC
159
+ error for that request id (`code -32000`, message `CommitLayer recording failed: …`) and the downstream
160
+ server never sees it. Other traffic is unaffected.
161
+
162
+ ## Library use
163
+
164
+ ```python
165
+ from commitlayer_mcp_recorder import Recorder, build_action, load_mapping
166
+
167
+ mapping = load_mapping("mapping.json")
168
+ recorder = Recorder(mapping, api_key="spk_...", agent_id="support-bot")
169
+ decision = recorder.before_forward(message) # a parsed client message; .forward, .action_id, .error
170
+ recorder.after_response(message) # a parsed server message; logs the isError flag
171
+ ```
172
+
173
+ `build_action(tool_name, call_params, tool_mapping, request_id=...)` returns the body shown above.
174
+
175
+ ## Limits
176
+
177
+ - Stdio transport only, newline-delimited JSON-RPC. Lines that are not JSON are forwarded untouched.
178
+ - JSON-RPC batch arrays are forwarded untouched and not inspected.
179
+ - A `tools/call` without a JSON-RPC id is forwarded without recording (there is nothing to match the answer
180
+ to).
181
+
182
+ ## Tests
183
+
184
+ ```bash
185
+ python -m pytest recorder/python/tests -q
186
+ ```