mcp-attest 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 Balanov.PRO
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,239 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-attest
3
+ Version: 0.1.0
4
+ Summary: Conformance and attestation harness for MCP agent access control: prove that a policy actually enforces, detect shadow MCP paths, and emit tamper-evident receipts.
5
+ Author: Balanov.PRO
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Balanov/mcp-attest
8
+ Project-URL: Source, https://github.com/Balanov/mcp-attest
9
+ Project-URL: Issues, https://github.com/Balanov/mcp-attest/issues
10
+ Keywords: mcp,model-context-protocol,security,policy,audit,attestation,agents
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Information Technology
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Security
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ # mcp-attest
24
+
25
+ **Prove that your MCP policy actually enforces.**
26
+
27
+ `mcp-attest` is a conformance and attestation harness for agent access control.
28
+ It takes the policy you *wrote* and the trace of what *actually happened*, and
29
+ reports every point where they disagree — including the failure modes that
30
+ gateways accept silently: a rule that was registered but never enforced, a
31
+ tool that is hidden from `tools/list` but still callable, a client that reaches
32
+ a server outside the gateway, and a tool definition that changed after it was
33
+ approved.
34
+
35
+ ```bash
36
+ pipx install mcp-attest
37
+ attest init
38
+ attest check --policy policy.json --trace trace.jsonl
39
+ ```
40
+
41
+ ```
42
+ !! [CRITICAL] C6 Policy denies 'shell__exec' but the gateway allowed it
43
+ Local evaluation of the policy says deny (tool is on the deny list); the
44
+ trace records allow ('argument-conditioned rule matched'). This is the
45
+ 'policy accepted but not enforced' failure mode — a rule that is
46
+ registered but never applied.
47
+ fix: Replay the call against the gateway with the same arguments and fix
48
+ the rule binding, then add this call as a regression case in CI.
49
+
50
+ !! [CRITICAL] C9 Tool 'fs__read_file' changed after it was listed
51
+ The description hash of an already-approved tool changed between two
52
+ listings. This is the shape of a rug pull: the tool was harmless at
53
+ approval time.
54
+
55
+ summary: critical=7, high=6
56
+ ```
57
+
58
+ Exit code is `1` when a blocking finding exists, so it works as a CI gate
59
+ without a wrapper script. The session above is `examples/session.jsonl` — run
60
+ `attest check --policy examples/policy.json --trace examples/session.jsonl` to
61
+ reproduce it.
62
+
63
+ ---
64
+
65
+ ## Why this exists
66
+
67
+ Gateways, policy engines and allowlists for Model Context Protocol are now
68
+ everywhere — Docker MCP Gateway, agentgateway, Bifrost, Runlayer, Lasso, Obot,
69
+ and GitHub's own `gh-aw-mcpg`. The policy language is not the problem.
70
+
71
+ The problem is that **nobody verifies the policy is enforced.** In a detailed
72
+ evaluation of agentgateway published in October 2026, an argument-conditioned
73
+ policy was "reported as accepted and still did not enforce what it said", and
74
+ the conclusion was blunt: "turning a policy on is not the end of the job — send
75
+ real calls and check."
76
+
77
+ The same evaluation found policies that were accepted and had no effect at
78
+ all. The State of MCP Security 2026 survey describes the structural gap:
79
+ "Gateways only protect the servers they route. An agent calling an MCP server
80
+ outside the gateway is invisible," and "Most gateways also do not do deep
81
+ content inspection of responses. The gateway enforces who and where, not what."
82
+
83
+ Meanwhile the threat volume is not theoretical: more than 40 CVEs against MCP
84
+ implementations were disclosed in the first four months of 2026, an estimated
85
+ 200,000 servers are exposed, 38% of scanned MCP servers had no authentication
86
+ on critical endpoints, and in April 2026 researchers hijacked Claude Code, Gemini
87
+ CLI and GitHub Copilot through malicious pull-request titles — with bug bounties
88
+ paid by the vendors.
89
+
90
+ And the buyers of this category are being told the same thing. The US DoD's
91
+ Center for Security and Information Technology wrote that "without these
92
+ traceable audit logs, incident response and accountability become significantly
93
+ more difficult, especially in multi-tenant environments."
94
+
95
+ So the missing primitive is small, sharp and useful: **a harness that takes a
96
+ policy and a trace and tells you, with evidence, whether your control plane
97
+ does what you wrote down.**
98
+
99
+ `mcp-attest` is that harness. It is not a gateway, it does not sit on the hot
100
+ path, and it does not need to be trusted with your traffic — it reads what
101
+ already happened.
102
+
103
+ ## What it checks
104
+
105
+ | ID | Rule | The failure it catches |
106
+ |----|------|------------------------|
107
+ | C1 | default-deny | An unset allowlist silently meaning "allow everything" |
108
+ | C2 | list/call parity | A tool hidden from `tools/list` but callable by name |
109
+ | C3 | narrowing only | A per-request control widening a client's grant |
110
+ | C4 | credential separation | The client's token replayed upstream verbatim |
111
+ | C5 | fail closed | A call allowed because the policy check errored |
112
+ | C6 | enforcement parity | The policy says deny, the gateway allowed it |
113
+ | C7 | shadow MCP | A server the agent can reach outside the gateway |
114
+ | C8 | receipt chain | Allowed calls with no verifiable receipt, or a broken chain |
115
+ | C9 | description integrity | A tool definition changed after approval (rug pull) |
116
+
117
+ C6 is the one that matters most, and it is the one no existing tool does: it
118
+ re-evaluates your policy locally against every recorded call and flags any call
119
+ whose observed decision disagrees with the policy. That catches the
120
+ "accepted but not enforced" class directly.
121
+
122
+ ## Install
123
+
124
+ ```bash
125
+ pipx install mcp-attest # isolated CLI
126
+ uvx mcp-attest check --help # one-shot
127
+ pip install mcp-attest # into a virtualenv
128
+ ```
129
+
130
+ Python 3.10+, zero runtime dependencies.
131
+
132
+ ## Use it in CI
133
+
134
+ ```yaml
135
+ - uses: Balanov/mcp-attest@v0
136
+ with:
137
+ policy: .attest/policy.json
138
+ trace: .attest/trace.jsonl
139
+ ```
140
+
141
+ The action fails the job on any critical or high finding, so "our agent policy
142
+ is enforced" becomes something the pipeline proves instead of something the
143
+ security questionnaire asks about.
144
+
145
+ ## Policy format
146
+
147
+ ```json
148
+ {
149
+ "version": 1,
150
+ "default_decision": "deny",
151
+ "clients": {
152
+ "ci-bot": {
153
+ "tools": {
154
+ "allow": ["fs__read_file", "fs__list_directory"],
155
+ "deny": ["fs__write_file", "fs__delete_everything"],
156
+ "conditional": [
157
+ {
158
+ "tool": "fs__write_file",
159
+ "allow_when": { "path": { "starts_with": "./out/" } },
160
+ "action": "allow",
161
+ "reason": "writes are allowed only inside ./out/"
162
+ }
163
+ ]
164
+ },
165
+ "egress_hosts": ["api.internal.example.com"]
166
+ }
167
+ },
168
+ "gateway": { "mode": "routed", "fail_closed": true, "passthrough_credentials": false },
169
+ "receipts": { "required": true, "key_env": "MCP_ATTEST_KEY" }
170
+ }
171
+ ```
172
+
173
+ Condition operators: `eq`, `not_eq`, `starts_with`, `ends_with`, `contains`,
174
+ `in`, `not_in`, `gt`, `gte`, `lt`, `lte`, `glob`, `regex`. A bare string ending
175
+ in `/` is a prefix; a bare string containing `*` is a glob.
176
+
177
+ ## Trace format
178
+
179
+ Newline-delimited JSON, one event per line. Any proxy, agent harness or CI
180
+ step can produce it.
181
+
182
+ ```jsonl
183
+ {"type":"tools_list","actor":"ci-bot","tools":[{"name":"fs__read_file","description_sha256":"a1b2…"}]}
184
+ {"type":"tool_call","actor":"ci-bot","tool":"fs__read_file","args":{"path":"/srv/README.md"},"decision":"allow","upstream":true,"reason":"tool is on the allow list"}
185
+ {"type":"client_servers","client":"claude-code","servers":["fs","git"]}
186
+ {"type":"gateway_route","server":"fs","tools":["read_file","list_directory"]}
187
+ ```
188
+
189
+ Full specification: [SPEC.md](SPEC.md).
190
+
191
+ ## Receipts
192
+
193
+ `attest receipts --trace trace.jsonl --out receipts.jsonl` produces a
194
+ hash-chained receipt per allowed call. Each receipt commits to the previous
195
+ one, so removing or reordering a receipt breaks the chain. With
196
+ `MCP_ATTEST_KEY` set, every receipt is additionally signed with HMAC-SHA256,
197
+ so an auditor can detect edits without access to the gateway.
198
+
199
+ ```bash
200
+ export MCP_ATTEST_KEY=…
201
+ attest receipts --trace trace.jsonl --out receipts.jsonl
202
+ # chain: ok (12 receipts, key=yes)
203
+ ```
204
+
205
+ This is deliberately not a PKI. It answers the question an incident responder
206
+ actually asks: *did this session really contain these calls, in this order, and
207
+ has anything been altered since?*
208
+
209
+ ## Commands
210
+
211
+ | Command | Purpose |
212
+ |---------|---------|
213
+ | `attest init` | Write a starter policy |
214
+ | `attest check` | Check a trace against a policy (`--json`, `--strict`) |
215
+ | `attest receipts` | Build and verify a hash-chained receipt trail |
216
+ | `attest shadow` | List paths that bypass the control point |
217
+ | `attest rules` | List the enforced rules |
218
+
219
+ ## Design principles
220
+
221
+ - **Read-only.** It never sits in the request path and never holds a credential
222
+ for the upstream. It reads evidence.
223
+ - **Evidence over opinion.** Every finding carries the exact line, tool,
224
+ arguments and decision that produced it.
225
+ - **Offline.** No network calls, no telemetry, no account. Traces contain
226
+ payloads and stay where they are.
227
+ - **Exit codes, not vibes.** `0` clean, `1` blocking finding, `2` bad input.
228
+
229
+ ## Development
230
+
231
+ ```bash
232
+ python -m venv .venv && . .venv/Scripts/activate
233
+ pip install -e ".[dev]"
234
+ python -m unittest discover -s tests -v
235
+ ```
236
+
237
+ ## License
238
+
239
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,217 @@
1
+ # mcp-attest
2
+
3
+ **Prove that your MCP policy actually enforces.**
4
+
5
+ `mcp-attest` is a conformance and attestation harness for agent access control.
6
+ It takes the policy you *wrote* and the trace of what *actually happened*, and
7
+ reports every point where they disagree — including the failure modes that
8
+ gateways accept silently: a rule that was registered but never enforced, a
9
+ tool that is hidden from `tools/list` but still callable, a client that reaches
10
+ a server outside the gateway, and a tool definition that changed after it was
11
+ approved.
12
+
13
+ ```bash
14
+ pipx install mcp-attest
15
+ attest init
16
+ attest check --policy policy.json --trace trace.jsonl
17
+ ```
18
+
19
+ ```
20
+ !! [CRITICAL] C6 Policy denies 'shell__exec' but the gateway allowed it
21
+ Local evaluation of the policy says deny (tool is on the deny list); the
22
+ trace records allow ('argument-conditioned rule matched'). This is the
23
+ 'policy accepted but not enforced' failure mode — a rule that is
24
+ registered but never applied.
25
+ fix: Replay the call against the gateway with the same arguments and fix
26
+ the rule binding, then add this call as a regression case in CI.
27
+
28
+ !! [CRITICAL] C9 Tool 'fs__read_file' changed after it was listed
29
+ The description hash of an already-approved tool changed between two
30
+ listings. This is the shape of a rug pull: the tool was harmless at
31
+ approval time.
32
+
33
+ summary: critical=7, high=6
34
+ ```
35
+
36
+ Exit code is `1` when a blocking finding exists, so it works as a CI gate
37
+ without a wrapper script. The session above is `examples/session.jsonl` — run
38
+ `attest check --policy examples/policy.json --trace examples/session.jsonl` to
39
+ reproduce it.
40
+
41
+ ---
42
+
43
+ ## Why this exists
44
+
45
+ Gateways, policy engines and allowlists for Model Context Protocol are now
46
+ everywhere — Docker MCP Gateway, agentgateway, Bifrost, Runlayer, Lasso, Obot,
47
+ and GitHub's own `gh-aw-mcpg`. The policy language is not the problem.
48
+
49
+ The problem is that **nobody verifies the policy is enforced.** In a detailed
50
+ evaluation of agentgateway published in October 2026, an argument-conditioned
51
+ policy was "reported as accepted and still did not enforce what it said", and
52
+ the conclusion was blunt: "turning a policy on is not the end of the job — send
53
+ real calls and check."
54
+
55
+ The same evaluation found policies that were accepted and had no effect at
56
+ all. The State of MCP Security 2026 survey describes the structural gap:
57
+ "Gateways only protect the servers they route. An agent calling an MCP server
58
+ outside the gateway is invisible," and "Most gateways also do not do deep
59
+ content inspection of responses. The gateway enforces who and where, not what."
60
+
61
+ Meanwhile the threat volume is not theoretical: more than 40 CVEs against MCP
62
+ implementations were disclosed in the first four months of 2026, an estimated
63
+ 200,000 servers are exposed, 38% of scanned MCP servers had no authentication
64
+ on critical endpoints, and in April 2026 researchers hijacked Claude Code, Gemini
65
+ CLI and GitHub Copilot through malicious pull-request titles — with bug bounties
66
+ paid by the vendors.
67
+
68
+ And the buyers of this category are being told the same thing. The US DoD's
69
+ Center for Security and Information Technology wrote that "without these
70
+ traceable audit logs, incident response and accountability become significantly
71
+ more difficult, especially in multi-tenant environments."
72
+
73
+ So the missing primitive is small, sharp and useful: **a harness that takes a
74
+ policy and a trace and tells you, with evidence, whether your control plane
75
+ does what you wrote down.**
76
+
77
+ `mcp-attest` is that harness. It is not a gateway, it does not sit on the hot
78
+ path, and it does not need to be trusted with your traffic — it reads what
79
+ already happened.
80
+
81
+ ## What it checks
82
+
83
+ | ID | Rule | The failure it catches |
84
+ |----|------|------------------------|
85
+ | C1 | default-deny | An unset allowlist silently meaning "allow everything" |
86
+ | C2 | list/call parity | A tool hidden from `tools/list` but callable by name |
87
+ | C3 | narrowing only | A per-request control widening a client's grant |
88
+ | C4 | credential separation | The client's token replayed upstream verbatim |
89
+ | C5 | fail closed | A call allowed because the policy check errored |
90
+ | C6 | enforcement parity | The policy says deny, the gateway allowed it |
91
+ | C7 | shadow MCP | A server the agent can reach outside the gateway |
92
+ | C8 | receipt chain | Allowed calls with no verifiable receipt, or a broken chain |
93
+ | C9 | description integrity | A tool definition changed after approval (rug pull) |
94
+
95
+ C6 is the one that matters most, and it is the one no existing tool does: it
96
+ re-evaluates your policy locally against every recorded call and flags any call
97
+ whose observed decision disagrees with the policy. That catches the
98
+ "accepted but not enforced" class directly.
99
+
100
+ ## Install
101
+
102
+ ```bash
103
+ pipx install mcp-attest # isolated CLI
104
+ uvx mcp-attest check --help # one-shot
105
+ pip install mcp-attest # into a virtualenv
106
+ ```
107
+
108
+ Python 3.10+, zero runtime dependencies.
109
+
110
+ ## Use it in CI
111
+
112
+ ```yaml
113
+ - uses: Balanov/mcp-attest@v0
114
+ with:
115
+ policy: .attest/policy.json
116
+ trace: .attest/trace.jsonl
117
+ ```
118
+
119
+ The action fails the job on any critical or high finding, so "our agent policy
120
+ is enforced" becomes something the pipeline proves instead of something the
121
+ security questionnaire asks about.
122
+
123
+ ## Policy format
124
+
125
+ ```json
126
+ {
127
+ "version": 1,
128
+ "default_decision": "deny",
129
+ "clients": {
130
+ "ci-bot": {
131
+ "tools": {
132
+ "allow": ["fs__read_file", "fs__list_directory"],
133
+ "deny": ["fs__write_file", "fs__delete_everything"],
134
+ "conditional": [
135
+ {
136
+ "tool": "fs__write_file",
137
+ "allow_when": { "path": { "starts_with": "./out/" } },
138
+ "action": "allow",
139
+ "reason": "writes are allowed only inside ./out/"
140
+ }
141
+ ]
142
+ },
143
+ "egress_hosts": ["api.internal.example.com"]
144
+ }
145
+ },
146
+ "gateway": { "mode": "routed", "fail_closed": true, "passthrough_credentials": false },
147
+ "receipts": { "required": true, "key_env": "MCP_ATTEST_KEY" }
148
+ }
149
+ ```
150
+
151
+ Condition operators: `eq`, `not_eq`, `starts_with`, `ends_with`, `contains`,
152
+ `in`, `not_in`, `gt`, `gte`, `lt`, `lte`, `glob`, `regex`. A bare string ending
153
+ in `/` is a prefix; a bare string containing `*` is a glob.
154
+
155
+ ## Trace format
156
+
157
+ Newline-delimited JSON, one event per line. Any proxy, agent harness or CI
158
+ step can produce it.
159
+
160
+ ```jsonl
161
+ {"type":"tools_list","actor":"ci-bot","tools":[{"name":"fs__read_file","description_sha256":"a1b2…"}]}
162
+ {"type":"tool_call","actor":"ci-bot","tool":"fs__read_file","args":{"path":"/srv/README.md"},"decision":"allow","upstream":true,"reason":"tool is on the allow list"}
163
+ {"type":"client_servers","client":"claude-code","servers":["fs","git"]}
164
+ {"type":"gateway_route","server":"fs","tools":["read_file","list_directory"]}
165
+ ```
166
+
167
+ Full specification: [SPEC.md](SPEC.md).
168
+
169
+ ## Receipts
170
+
171
+ `attest receipts --trace trace.jsonl --out receipts.jsonl` produces a
172
+ hash-chained receipt per allowed call. Each receipt commits to the previous
173
+ one, so removing or reordering a receipt breaks the chain. With
174
+ `MCP_ATTEST_KEY` set, every receipt is additionally signed with HMAC-SHA256,
175
+ so an auditor can detect edits without access to the gateway.
176
+
177
+ ```bash
178
+ export MCP_ATTEST_KEY=…
179
+ attest receipts --trace trace.jsonl --out receipts.jsonl
180
+ # chain: ok (12 receipts, key=yes)
181
+ ```
182
+
183
+ This is deliberately not a PKI. It answers the question an incident responder
184
+ actually asks: *did this session really contain these calls, in this order, and
185
+ has anything been altered since?*
186
+
187
+ ## Commands
188
+
189
+ | Command | Purpose |
190
+ |---------|---------|
191
+ | `attest init` | Write a starter policy |
192
+ | `attest check` | Check a trace against a policy (`--json`, `--strict`) |
193
+ | `attest receipts` | Build and verify a hash-chained receipt trail |
194
+ | `attest shadow` | List paths that bypass the control point |
195
+ | `attest rules` | List the enforced rules |
196
+
197
+ ## Design principles
198
+
199
+ - **Read-only.** It never sits in the request path and never holds a credential
200
+ for the upstream. It reads evidence.
201
+ - **Evidence over opinion.** Every finding carries the exact line, tool,
202
+ arguments and decision that produced it.
203
+ - **Offline.** No network calls, no telemetry, no account. Traces contain
204
+ payloads and stay where they are.
205
+ - **Exit codes, not vibes.** `0` clean, `1` blocking finding, `2` bad input.
206
+
207
+ ## Development
208
+
209
+ ```bash
210
+ python -m venv .venv && . .venv/Scripts/activate
211
+ pip install -e ".[dev]"
212
+ python -m unittest discover -s tests -v
213
+ ```
214
+
215
+ ## License
216
+
217
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mcp-attest"
7
+ version = "0.1.0"
8
+ description = "Conformance and attestation harness for MCP agent access control: prove that a policy actually enforces, detect shadow MCP paths, and emit tamper-evident receipts."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Balanov.PRO" }]
13
+ keywords = ["mcp", "model-context-protocol", "security", "policy", "audit", "attestation", "agents"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Intended Audience :: Information Technology",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Security",
21
+ "Topic :: Software Development :: Testing",
22
+ ]
23
+ dependencies = []
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/Balanov/mcp-attest"
27
+ Source = "https://github.com/Balanov/mcp-attest"
28
+ Issues = "https://github.com/Balanov/mcp-attest/issues"
29
+
30
+ [project.scripts]
31
+ attest = "mcp_attest.cli:main"
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ """mcp-attest — conformance and attestation harness for MCP agent access control."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .checks import RULES, Finding, run_checks, summarize
6
+ from .policy import Policy, load_policy, policy_from_dict
7
+ from .receipts import build_receipts, verify_receipts
8
+ from .shadow import find_shadow_paths
9
+ from .trace import Trace, load_trace
10
+
11
+ __all__ = [
12
+ "RULES",
13
+ "Finding",
14
+ "Policy",
15
+ "Trace",
16
+ "build_receipts",
17
+ "find_shadow_paths",
18
+ "load_policy",
19
+ "load_trace",
20
+ "policy_from_dict",
21
+ "run_checks",
22
+ "summarize",
23
+ "verify_receipts",
24
+ ]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())