clause-mcp 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.
Files changed (141) hide show
  1. clause_mcp-0.1.0/LICENSE +21 -0
  2. clause_mcp-0.1.0/PKG-INFO +199 -0
  3. clause_mcp-0.1.0/README.md +160 -0
  4. clause_mcp-0.1.0/clause/__init__.py +0 -0
  5. clause_mcp-0.1.0/clause/adapters/__init__.py +13 -0
  6. clause_mcp-0.1.0/clause/adapters/base.py +203 -0
  7. clause_mcp-0.1.0/clause/adapters/kalshi.py +118 -0
  8. clause_mcp-0.1.0/clause/adapters/limitless.py +229 -0
  9. clause_mcp-0.1.0/clause/adapters/polymarket.py +117 -0
  10. clause_mcp-0.1.0/clause/adapters/predictit.py +175 -0
  11. clause_mcp-0.1.0/clause/agents/__init__.py +7 -0
  12. clause_mcp-0.1.0/clause/agents/discovery.py +253 -0
  13. clause_mcp-0.1.0/clause/agents/markdown.py +247 -0
  14. clause_mcp-0.1.0/clause/agents/middleware.py +83 -0
  15. clause_mcp-0.1.0/clause/agents/routes.py +72 -0
  16. clause_mcp-0.1.0/clause/alerts/__init__.py +0 -0
  17. clause_mcp-0.1.0/clause/alerts/deliver.py +73 -0
  18. clause_mcp-0.1.0/clause/alerts/detect.py +198 -0
  19. clause_mcp-0.1.0/clause/alerts/rules.py +299 -0
  20. clause_mcp-0.1.0/clause/alerts/telegram.py +144 -0
  21. clause_mcp-0.1.0/clause/analytics/__init__.py +0 -0
  22. clause_mcp-0.1.0/clause/analytics/execution.py +268 -0
  23. clause_mcp-0.1.0/clause/analytics/zero_copy.py +202 -0
  24. clause_mcp-0.1.0/clause/api/__init__.py +0 -0
  25. clause_mcp-0.1.0/clause/api/auth.py +182 -0
  26. clause_mcp-0.1.0/clause/api/crossvenue_routes.py +196 -0
  27. clause_mcp-0.1.0/clause/api/execution_routes.py +1039 -0
  28. clause_mcp-0.1.0/clause/api/keys_middleware.py +83 -0
  29. clause_mcp-0.1.0/clause/api/main.py +422 -0
  30. clause_mcp-0.1.0/clause/archive/__init__.py +0 -0
  31. clause_mcp-0.1.0/clause/archive/recorder.py +320 -0
  32. clause_mcp-0.1.0/clause/archive/verify.py +186 -0
  33. clause_mcp-0.1.0/clause/chain/__init__.py +0 -0
  34. clause_mcp-0.1.0/clause/chain/adapters.py +92 -0
  35. clause_mcp-0.1.0/clause/chain/events.py +48 -0
  36. clause_mcp-0.1.0/clause/chain/rpc.py +185 -0
  37. clause_mcp-0.1.0/clause/content/__init__.py +1 -0
  38. clause_mcp-0.1.0/clause/content/findings.py +464 -0
  39. clause_mcp-0.1.0/clause/content/formats.py +190 -0
  40. clause_mcp-0.1.0/clause/content/longform.py +409 -0
  41. clause_mcp-0.1.0/clause/content/pieces/__init__.py +35 -0
  42. clause_mcp-0.1.0/clause/content/pieces/eighty_four_cent_outcome.py +186 -0
  43. clause_mcp-0.1.0/clause/content/publish.py +196 -0
  44. clause_mcp-0.1.0/clause/content/render.py +179 -0
  45. clause_mcp-0.1.0/clause/evidence/__init__.py +0 -0
  46. clause_mcp-0.1.0/clause/evidence/bundle.py +198 -0
  47. clause_mcp-0.1.0/clause/evidence/scorecard.py +185 -0
  48. clause_mcp-0.1.0/clause/execution/__init__.py +0 -0
  49. clause_mcp-0.1.0/clause/execution/capacity.py +399 -0
  50. clause_mcp-0.1.0/clause/execution/consolidated.py +194 -0
  51. clause_mcp-0.1.0/clause/execution/copy.py +516 -0
  52. clause_mcp-0.1.0/clause/execution/edge.py +205 -0
  53. clause_mcp-0.1.0/clause/execution/liquidity.py +317 -0
  54. clause_mcp-0.1.0/clause/execution/markout.py +208 -0
  55. clause_mcp-0.1.0/clause/execution/orders.py +297 -0
  56. clause_mcp-0.1.0/clause/execution/passive.py +263 -0
  57. clause_mcp-0.1.0/clause/execution/risk.py +246 -0
  58. clause_mcp-0.1.0/clause/execution/router.py +234 -0
  59. clause_mcp-0.1.0/clause/execution/schedule.py +292 -0
  60. clause_mcp-0.1.0/clause/execution/scorecard.py +203 -0
  61. clause_mcp-0.1.0/clause/execution/screen.py +168 -0
  62. clause_mcp-0.1.0/clause/execution/strategy.py +223 -0
  63. clause_mcp-0.1.0/clause/ingest/__init__.py +0 -0
  64. clause_mcp-0.1.0/clause/ingest/stream.py +228 -0
  65. clause_mcp-0.1.0/clause/matching/__init__.py +0 -0
  66. clause_mcp-0.1.0/clause/matching/canonical.py +123 -0
  67. clause_mcp-0.1.0/clause/matching/criteria_match.py +493 -0
  68. clause_mcp-0.1.0/clause/matching/crossvenue.py +242 -0
  69. clause_mcp-0.1.0/clause/matching/equivalence.py +267 -0
  70. clause_mcp-0.1.0/clause/mcp/__init__.py +0 -0
  71. clause_mcp-0.1.0/clause/mcp/server.py +651 -0
  72. clause_mcp-0.1.0/clause/net/__init__.py +0 -0
  73. clause_mcp-0.1.0/clause/net/budget.py +141 -0
  74. clause_mcp-0.1.0/clause/net/cache.py +99 -0
  75. clause_mcp-0.1.0/clause/net/ratelimit.py +88 -0
  76. clause_mcp-0.1.0/clause/polymarket/__init__.py +0 -0
  77. clause_mcp-0.1.0/clause/polymarket/client.py +308 -0
  78. clause_mcp-0.1.0/clause/resolution/__init__.py +0 -0
  79. clause_mcp-0.1.0/clause/resolution/lifecycle.py +271 -0
  80. clause_mcp-0.1.0/clause/resolution/schema.py +243 -0
  81. clause_mcp-0.1.0/clause/resolution/store.py +351 -0
  82. clause_mcp-0.1.0/clause/resolution/tracker.py +400 -0
  83. clause_mcp-0.1.0/clause/search/__init__.py +15 -0
  84. clause_mcp-0.1.0/clause/search/markets.py +428 -0
  85. clause_mcp-0.1.0/clause/service/__init__.py +0 -0
  86. clause_mcp-0.1.0/clause/service/execution_service.py +387 -0
  87. clause_mcp-0.1.0/clause/service/wallet_report.py +223 -0
  88. clause_mcp-0.1.0/clause/store/__init__.py +0 -0
  89. clause_mcp-0.1.0/clause/store/backend.py +210 -0
  90. clause_mcp-0.1.0/clause/store/db.py +408 -0
  91. clause_mcp-0.1.0/clause/store/joblock.py +205 -0
  92. clause_mcp-0.1.0/clause/store/prices.py +128 -0
  93. clause_mcp-0.1.0/clause/store/schema.sql +188 -0
  94. clause_mcp-0.1.0/clause/terms/__init__.py +0 -0
  95. clause_mcp-0.1.0/clause/terms/criteria.py +253 -0
  96. clause_mcp-0.1.0/clause/terms/extract.py +283 -0
  97. clause_mcp-0.1.0/clause/terms/normalise.py +80 -0
  98. clause_mcp-0.1.0/clause/tools/__init__.py +0 -0
  99. clause_mcp-0.1.0/clause/tools/explain.py +200 -0
  100. clause_mcp-0.1.0/clause_mcp.egg-info/PKG-INFO +199 -0
  101. clause_mcp-0.1.0/clause_mcp.egg-info/SOURCES.txt +139 -0
  102. clause_mcp-0.1.0/clause_mcp.egg-info/dependency_links.txt +1 -0
  103. clause_mcp-0.1.0/clause_mcp.egg-info/entry_points.txt +2 -0
  104. clause_mcp-0.1.0/clause_mcp.egg-info/requires.txt +23 -0
  105. clause_mcp-0.1.0/clause_mcp.egg-info/top_level.txt +1 -0
  106. clause_mcp-0.1.0/pyproject.toml +63 -0
  107. clause_mcp-0.1.0/setup.cfg +4 -0
  108. clause_mcp-0.1.0/tests/test_across_route.py +148 -0
  109. clause_mcp-0.1.0/tests/test_agent_discovery.py +207 -0
  110. clause_mcp-0.1.0/tests/test_alerts.py +351 -0
  111. clause_mcp-0.1.0/tests/test_archive_absent.py +259 -0
  112. clause_mcp-0.1.0/tests/test_archive_memory.py +110 -0
  113. clause_mcp-0.1.0/tests/test_archive_verify.py +156 -0
  114. clause_mcp-0.1.0/tests/test_backup.py +115 -0
  115. clause_mcp-0.1.0/tests/test_client.py +81 -0
  116. clause_mcp-0.1.0/tests/test_copy.py +288 -0
  117. clause_mcp-0.1.0/tests/test_crossvenue.py +203 -0
  118. clause_mcp-0.1.0/tests/test_deploy_units.py +62 -0
  119. clause_mcp-0.1.0/tests/test_evidence.py +167 -0
  120. clause_mcp-0.1.0/tests/test_explain_route.py +333 -0
  121. clause_mcp-0.1.0/tests/test_findings.py +158 -0
  122. clause_mcp-0.1.0/tests/test_follow_costs.py +152 -0
  123. clause_mcp-0.1.0/tests/test_formats.py +251 -0
  124. clause_mcp-0.1.0/tests/test_golden.py +1449 -0
  125. clause_mcp-0.1.0/tests/test_health.py +72 -0
  126. clause_mcp-0.1.0/tests/test_joblock.py +321 -0
  127. clause_mcp-0.1.0/tests/test_kalshi_book.py +112 -0
  128. clause_mcp-0.1.0/tests/test_limitless_authorisation.py +95 -0
  129. clause_mcp-0.1.0/tests/test_longform.py +226 -0
  130. clause_mcp-0.1.0/tests/test_markout.py +119 -0
  131. clause_mcp-0.1.0/tests/test_novig.py +150 -0
  132. clause_mcp-0.1.0/tests/test_pieces.py +64 -0
  133. clause_mcp-0.1.0/tests/test_predictit.py +123 -0
  134. clause_mcp-0.1.0/tests/test_publish.py +162 -0
  135. clause_mcp-0.1.0/tests/test_publishable.py +117 -0
  136. clause_mcp-0.1.0/tests/test_resolution.py +816 -0
  137. clause_mcp-0.1.0/tests/test_scale.py +173 -0
  138. clause_mcp-0.1.0/tests/test_scheduling.py +137 -0
  139. clause_mcp-0.1.0/tests/test_search.py +196 -0
  140. clause_mcp-0.1.0/tests/test_tools.py +85 -0
  141. clause_mcp-0.1.0/tests/test_wallet_analysis.py +200 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Clause
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,199 @@
1
+ Metadata-Version: 2.4
2
+ Name: clause-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for prediction markets: are these two contracts the same bet, and how much size will this market actually take?
5
+ Author: Clause
6
+ License-Expression: MIT
7
+ Keywords: mcp,model-context-protocol,prediction-markets,polymarket,kalshi,order-book,liquidity,market-microstructure
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: Financial and Insurance Industry
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Office/Business :: Financial
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: httpx>=0.28
21
+ Requires-Dist: mcp>=2.0
22
+ Provides-Extra: api
23
+ Requires-Dist: fastapi>=0.115; extra == "api"
24
+ Requires-Dist: uvicorn[standard]>=0.32; extra == "api"
25
+ Provides-Extra: chain
26
+ Requires-Dist: eth-abi>=5.0; extra == "chain"
27
+ Requires-Dist: eth-hash[pycryptodome]>=0.7; extra == "chain"
28
+ Requires-Dist: websockets>=15; extra == "chain"
29
+ Provides-Extra: postgres
30
+ Requires-Dist: psycopg[binary]>=3.2; extra == "postgres"
31
+ Provides-Extra: analysis
32
+ Requires-Dist: duckdb>=1.5; extra == "analysis"
33
+ Requires-Dist: rapidfuzz>=3.14; extra == "analysis"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=8.0; extra == "dev"
36
+ Requires-Dist: build>=1.2; extra == "dev"
37
+ Requires-Dist: twine>=5.0; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # clause-mcp
41
+
42
+ **You are about to trade a spread between two contracts that resolve
43
+ differently, and nothing you own will tell you.**
44
+
45
+ Polymarket lists *"U.S. enacts AI safety bill before 2027?"*. Kalshi lists what
46
+ reads as the same contract. Polymarket's resolves YES only on **restrictive
47
+ provisions**. Kalshi's resolves YES on a federal framework *"regardless of
48
+ whether those provisions mandate private-sector compliance or **promote
49
+ voluntary guidelines**."*
50
+
51
+ A voluntary standards act resolves one YES and the other NO. They are not the
52
+ same bet at any price. Every arbitrage screen in existence shows them as one
53
+ market at a spread, and a language model reading both rules texts will agree
54
+ with the screen — because the divergence is not a difference between the two
55
+ documents. It is **an explicit clause on one side matched by silence on the
56
+ other**, and silence is the one thing a comparator has to be told not to read
57
+ as agreement.
58
+
59
+ This is an MCP server. It gives an agent the checks it cannot perform on its
60
+ own.
61
+
62
+ ---
63
+
64
+ ## The second failure: the book that isn't there
65
+
66
+ A market on a major venue reported **$65,883** of order-book depth. Within a
67
+ sensible band of its own mid it held **$33**. It had traded **$122 in thirty
68
+ days**. Putting $200,000 into it is 49,276 days of volume — **135 years**.
69
+
70
+ A book is an offer. The tape is a fact. When they disagree, believe the tape.
71
+
72
+ ---
73
+
74
+ ## Install
75
+
76
+ ```bash
77
+ pip install clause-mcp
78
+ ```
79
+
80
+ ### Claude Code
81
+
82
+ ```bash
83
+ claude mcp add --scope user clause -- clause-mcp
84
+ ```
85
+
86
+ `--scope user` matters: the default registers the server for one project
87
+ directory only. **The tools appear in a new session**, not the one you ran the
88
+ add from.
89
+
90
+ ### Claude Desktop, or any stdio client
91
+
92
+ ```json
93
+ {
94
+ "mcpServers": {
95
+ "clause": { "command": "clause-mcp" }
96
+ }
97
+ }
98
+ ```
99
+
100
+ Streamable-HTTP instead of stdio: `clause-mcp --http --port 8848`.
101
+
102
+ ---
103
+
104
+ ## The eight tools
105
+
106
+ | Tool | Answers | Needs |
107
+ | --- | --- | --- |
108
+ | `check_equivalence` | are these two contracts the same bet? | live venues |
109
+ | `market_liquidity` | can this market absorb size — book *and* tape | live venues |
110
+ | `preflight` | will this specific order work, with numbers | live venues |
111
+ | `capacity` | how much fits inside 0.5¢ / 1¢ / 2¢ / 5¢ | live venues |
112
+ | `resting_order` | rest or cross, and how long to fill | live venues |
113
+ | `venue_conventions` | how each venue's API lies to you | nothing |
114
+ | `find_markets` | identifiers the other tools need | an archive |
115
+ | `identify_market` | which contract did the user mean, in plain words | an archive |
116
+
117
+ **Six of the eight work the moment you install this.** They read the venues
118
+ live and need no local data.
119
+
120
+ The two search tools need a Clause archive — a hash-chained daily record of
121
+ every open market's terms. This package does not ship one, and it will not
122
+ pretend otherwise: with no archive, both tools **refuse**, name the state, and
123
+ say in words that nothing was searched.
124
+
125
+ ```json
126
+ {
127
+ "ok": false,
128
+ "error": "archive_unavailable",
129
+ "this_is_not_a_negative_result":
130
+ "Do NOT report this as 'no such market' or 'no results'. Nothing was
131
+ searched. The named market may exist and be trading; this install
132
+ simply has no corpus to look in."
133
+ }
134
+ ```
135
+
136
+ That refusal is the point of the package rather than a limitation of it. The
137
+ alternative — falling back to a live search — is what the tool exists to
138
+ avoid: Polymarket's list API caps pages at 100 rows and rejects offsets past
139
+ ~2,100, so a live search silently misses the quiet markets, which are exactly
140
+ the ones whose liquidity most needs checking. Point `CLAUSE_DB` at an archive
141
+ if you have one.
142
+
143
+ ---
144
+
145
+ ## Why the tool descriptions read the way they do
146
+
147
+ They are written for the agent, not for a human browsing a catalogue. An agent
148
+ does not know it has a problem, so each description leads with the failure it
149
+ prevents rather than with what the tool returns. A tool called after the
150
+ mistake has been made is a tool that did nothing.
151
+
152
+ The verdicts are built the same way. `check_equivalence` returns two of them,
153
+ because two different things can make two contracts different bets. `verdict`
154
+ compares thresholds and settlement sources; it can reach `EQUIVALENT` when
155
+ both are genuinely comparable. `criteria_verdict` compares the *provisions* —
156
+ subject, jurisdiction, actor, action breadth, voluntary conduct, logical
157
+ structure, carve-outs — and it **never returns a bare `EQUIVALENT`**:
158
+ `LIKELY_EQUIVALENT` is its ceiling, with the clauses to read attached. That is
159
+ the one that catches the AI bills above, and roughly half the board resolves
160
+ on provisions rather than on numbers.
161
+
162
+ An `unknown` on a material field returns `REVIEW_REQUIRED`, never a pass.
163
+ `INSUFFICIENT_DATA` is a distinct verdict from `LIQUID` and must never render
164
+ as one.
165
+
166
+ ---
167
+
168
+ ## `venue_conventions`
169
+
170
+ Thirteen documented ways a venue API returns HTTP 200 while being wrong, each
171
+ one found by losing time to it and each one pinned by a test. A sample:
172
+
173
+ - Polymarket's `/book` returns **asks descending** — `asks[0]` is the worst price.
174
+ - A **YES bid at 0.10 is a NO ask at 0.90**. One order, two books. Never add the ladders.
175
+ - `data-api /trades` **ignores the `asset` parameter** and returns the full tape. A five-row sample "confirms" the filter works.
176
+ - Trades are **newest-first**; a capped pull is recent history, so dividing by 30 days understates a busy market ~15×.
177
+ - Kalshi's orderbook is under `orderbook_fp`, and **both sides are resting bids** — the NO ask is `1 − best YES bid`.
178
+ - Limitless caps `limit` at 25 and **400s above it**, which reads as "0 markets found".
179
+
180
+ The meta-lesson, and the reason the tools refuse the way they do: **a check
181
+ that cannot run reads as a check that passed.**
182
+
183
+ ---
184
+
185
+ ## Honest status
186
+
187
+ Version 0.1.0. The verdicts are conservative by construction and the refusals
188
+ are deliberate, but this is early software with no user base behind it yet.
189
+ It measures and records; **it does not recommend positions and it is not legal
190
+ advice**. Order transmission is off.
191
+
192
+ Requires Python 3.11+. Tested on 3.13.
193
+
194
+ **Name collision:** an unrelated PyPI package called `clause` (a Chinese NLP
195
+ SDK) also installs a top-level `clause` module. Installing both into one
196
+ environment will produce a broken hybrid. Run this server with `uvx`, `pipx`,
197
+ or its own virtualenv — which is how MCP servers are usually run anyway.
198
+
199
+ MIT licensed.
@@ -0,0 +1,160 @@
1
+ # clause-mcp
2
+
3
+ **You are about to trade a spread between two contracts that resolve
4
+ differently, and nothing you own will tell you.**
5
+
6
+ Polymarket lists *"U.S. enacts AI safety bill before 2027?"*. Kalshi lists what
7
+ reads as the same contract. Polymarket's resolves YES only on **restrictive
8
+ provisions**. Kalshi's resolves YES on a federal framework *"regardless of
9
+ whether those provisions mandate private-sector compliance or **promote
10
+ voluntary guidelines**."*
11
+
12
+ A voluntary standards act resolves one YES and the other NO. They are not the
13
+ same bet at any price. Every arbitrage screen in existence shows them as one
14
+ market at a spread, and a language model reading both rules texts will agree
15
+ with the screen — because the divergence is not a difference between the two
16
+ documents. It is **an explicit clause on one side matched by silence on the
17
+ other**, and silence is the one thing a comparator has to be told not to read
18
+ as agreement.
19
+
20
+ This is an MCP server. It gives an agent the checks it cannot perform on its
21
+ own.
22
+
23
+ ---
24
+
25
+ ## The second failure: the book that isn't there
26
+
27
+ A market on a major venue reported **$65,883** of order-book depth. Within a
28
+ sensible band of its own mid it held **$33**. It had traded **$122 in thirty
29
+ days**. Putting $200,000 into it is 49,276 days of volume — **135 years**.
30
+
31
+ A book is an offer. The tape is a fact. When they disagree, believe the tape.
32
+
33
+ ---
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install clause-mcp
39
+ ```
40
+
41
+ ### Claude Code
42
+
43
+ ```bash
44
+ claude mcp add --scope user clause -- clause-mcp
45
+ ```
46
+
47
+ `--scope user` matters: the default registers the server for one project
48
+ directory only. **The tools appear in a new session**, not the one you ran the
49
+ add from.
50
+
51
+ ### Claude Desktop, or any stdio client
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "clause": { "command": "clause-mcp" }
57
+ }
58
+ }
59
+ ```
60
+
61
+ Streamable-HTTP instead of stdio: `clause-mcp --http --port 8848`.
62
+
63
+ ---
64
+
65
+ ## The eight tools
66
+
67
+ | Tool | Answers | Needs |
68
+ | --- | --- | --- |
69
+ | `check_equivalence` | are these two contracts the same bet? | live venues |
70
+ | `market_liquidity` | can this market absorb size — book *and* tape | live venues |
71
+ | `preflight` | will this specific order work, with numbers | live venues |
72
+ | `capacity` | how much fits inside 0.5¢ / 1¢ / 2¢ / 5¢ | live venues |
73
+ | `resting_order` | rest or cross, and how long to fill | live venues |
74
+ | `venue_conventions` | how each venue's API lies to you | nothing |
75
+ | `find_markets` | identifiers the other tools need | an archive |
76
+ | `identify_market` | which contract did the user mean, in plain words | an archive |
77
+
78
+ **Six of the eight work the moment you install this.** They read the venues
79
+ live and need no local data.
80
+
81
+ The two search tools need a Clause archive — a hash-chained daily record of
82
+ every open market's terms. This package does not ship one, and it will not
83
+ pretend otherwise: with no archive, both tools **refuse**, name the state, and
84
+ say in words that nothing was searched.
85
+
86
+ ```json
87
+ {
88
+ "ok": false,
89
+ "error": "archive_unavailable",
90
+ "this_is_not_a_negative_result":
91
+ "Do NOT report this as 'no such market' or 'no results'. Nothing was
92
+ searched. The named market may exist and be trading; this install
93
+ simply has no corpus to look in."
94
+ }
95
+ ```
96
+
97
+ That refusal is the point of the package rather than a limitation of it. The
98
+ alternative — falling back to a live search — is what the tool exists to
99
+ avoid: Polymarket's list API caps pages at 100 rows and rejects offsets past
100
+ ~2,100, so a live search silently misses the quiet markets, which are exactly
101
+ the ones whose liquidity most needs checking. Point `CLAUSE_DB` at an archive
102
+ if you have one.
103
+
104
+ ---
105
+
106
+ ## Why the tool descriptions read the way they do
107
+
108
+ They are written for the agent, not for a human browsing a catalogue. An agent
109
+ does not know it has a problem, so each description leads with the failure it
110
+ prevents rather than with what the tool returns. A tool called after the
111
+ mistake has been made is a tool that did nothing.
112
+
113
+ The verdicts are built the same way. `check_equivalence` returns two of them,
114
+ because two different things can make two contracts different bets. `verdict`
115
+ compares thresholds and settlement sources; it can reach `EQUIVALENT` when
116
+ both are genuinely comparable. `criteria_verdict` compares the *provisions* —
117
+ subject, jurisdiction, actor, action breadth, voluntary conduct, logical
118
+ structure, carve-outs — and it **never returns a bare `EQUIVALENT`**:
119
+ `LIKELY_EQUIVALENT` is its ceiling, with the clauses to read attached. That is
120
+ the one that catches the AI bills above, and roughly half the board resolves
121
+ on provisions rather than on numbers.
122
+
123
+ An `unknown` on a material field returns `REVIEW_REQUIRED`, never a pass.
124
+ `INSUFFICIENT_DATA` is a distinct verdict from `LIQUID` and must never render
125
+ as one.
126
+
127
+ ---
128
+
129
+ ## `venue_conventions`
130
+
131
+ Thirteen documented ways a venue API returns HTTP 200 while being wrong, each
132
+ one found by losing time to it and each one pinned by a test. A sample:
133
+
134
+ - Polymarket's `/book` returns **asks descending** — `asks[0]` is the worst price.
135
+ - A **YES bid at 0.10 is a NO ask at 0.90**. One order, two books. Never add the ladders.
136
+ - `data-api /trades` **ignores the `asset` parameter** and returns the full tape. A five-row sample "confirms" the filter works.
137
+ - Trades are **newest-first**; a capped pull is recent history, so dividing by 30 days understates a busy market ~15×.
138
+ - Kalshi's orderbook is under `orderbook_fp`, and **both sides are resting bids** — the NO ask is `1 − best YES bid`.
139
+ - Limitless caps `limit` at 25 and **400s above it**, which reads as "0 markets found".
140
+
141
+ The meta-lesson, and the reason the tools refuse the way they do: **a check
142
+ that cannot run reads as a check that passed.**
143
+
144
+ ---
145
+
146
+ ## Honest status
147
+
148
+ Version 0.1.0. The verdicts are conservative by construction and the refusals
149
+ are deliberate, but this is early software with no user base behind it yet.
150
+ It measures and records; **it does not recommend positions and it is not legal
151
+ advice**. Order transmission is off.
152
+
153
+ Requires Python 3.11+. Tested on 3.13.
154
+
155
+ **Name collision:** an unrelated PyPI package called `clause` (a Chinese NLP
156
+ SDK) also installs a top-level `clause` module. Installing both into one
157
+ environment will produce a broken hybrid. Run this server with `uvx`, `pipx`,
158
+ or its own virtualenv — which is how MCP servers are usually run anyway.
159
+
160
+ MIT licensed.
File without changes
@@ -0,0 +1,13 @@
1
+ """Venue adapters.
2
+
3
+ Importing this package registers every adapter. The registry is populated by
4
+ `@REGISTRY.register` at class definition, so it only knows the venues whose
5
+ modules something has imported -- which made `REGISTRY.storable()` return
6
+ whatever the importer happened to touch. The cross-venue matcher asked it for
7
+ the list of venues it may write, got a partial answer, and would have refused
8
+ every pair while exiting 0.
9
+ """
10
+
11
+ from clause.adapters import kalshi, limitless, polymarket, predictit # noqa: F401
12
+
13
+ __all__ = ["kalshi", "limitless", "polymarket", "predictit"]
@@ -0,0 +1,203 @@
1
+ """Venue adapter framework — one interface, twenty venues.
2
+
3
+ The market went from two venues that mattered to twenty-plus in eighteen
4
+ months, and it is still moving. An adapter has to be a day of work or the
5
+ coverage that is supposed to be our moat becomes our bottleneck.
6
+
7
+ What every adapter must declare, and why each one is enforced rather than
8
+ documented:
9
+
10
+ `storage_permitted` Limitless restricts data compilations, and any venue
11
+ whose terms are unread defaults to False.
12
+ A flag that lives in a comment gets ignored; this one
13
+ is checked before any write. Kalshi was the original
14
+ reason this exists and is no longer an example of it:
15
+ written authorisation on 2026-09-07 lifted its
16
+ Developer Agreement s3.1 restriction. The mechanism
17
+ outlived the case that motivated it, which is the
18
+ point of putting it in the type rather than a note.
19
+ `book_supported` not every venue exposes depth, and capacity silently
20
+ returning nothing is worse than an honest refusal.
21
+ `terms_field` which field carries resolution criteria. The whole
22
+ equivalence layer depends on finding it.
23
+
24
+ **When a venue's terms of service cannot be verified, the default is
25
+ restrictive.** That is the posture already taken with Limitless, and it is the
26
+ one that keeps us able to sell to venues rather than being blocked by them.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from abc import ABC, abstractmethod
32
+ from dataclasses import dataclass, field
33
+ from typing import Iterator, Literal
34
+
35
+ from clause.execution.capacity import Book
36
+
37
+ VenueStatus = Literal["live", "beta", "planned", "blocked"]
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class VenueMarket:
42
+ """A market as any venue describes it, normalised."""
43
+ venue: str
44
+ venue_market_id: str
45
+ title: str
46
+ body: str # resolution criteria as published
47
+ outcome_token_ids: tuple[str, ...] = ()
48
+ condition_id: str | None = None
49
+ slug: str | None = None
50
+ close_time: int | None = None
51
+ status: str = "open"
52
+ raw: dict = field(default_factory=dict, compare=False, repr=False)
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class VenueCapabilities:
57
+ """What an adapter can actually do. Absent capabilities are refused, not faked."""
58
+ markets: bool = True
59
+ terms: bool = True
60
+ book: bool = False
61
+ trades: bool = False
62
+ streaming: bool = False
63
+ order_construction: bool = False
64
+
65
+
66
+ class VenueAdapter(ABC):
67
+ """Implement four methods and declare your permissions."""
68
+
69
+ venue: str = "unnamed"
70
+ display_name: str = "Unnamed"
71
+ status: VenueStatus = "planned"
72
+
73
+ # Hard gate, checked before persistence. Default deny.
74
+ storage_permitted: bool = False
75
+ tos_url: str | None = None
76
+ tos_verified_on: str | None = None
77
+ tos_note: str = "terms not verified — treated as restrictive"
78
+
79
+ capabilities: VenueCapabilities = VenueCapabilities()
80
+
81
+ @abstractmethod
82
+ def list_markets(self, max_markets: int = 1000) -> Iterator[VenueMarket]:
83
+ ...
84
+
85
+ def fetch_book(self, token_id: str) -> Book:
86
+ raise NotImplementedError(
87
+ f"{self.venue} does not expose order-book depth")
88
+
89
+ def close(self) -> None:
90
+ return None
91
+
92
+ # ---------- enforcement ----------
93
+
94
+ def assert_can_store(self) -> None:
95
+ """Raise rather than write when the terms do not permit it.
96
+
97
+ Called at the persistence boundary. A permission expressed only as a
98
+ comment is a permission that gets ignored during a refactor.
99
+ """
100
+ if not self.storage_permitted:
101
+ raise PermissionError(
102
+ f"{self.venue}: storage not permitted "
103
+ f"({self.tos_note}). Derived values only — raw content from "
104
+ f"this venue must not be persisted.")
105
+
106
+ def describe(self) -> dict:
107
+ return {
108
+ "venue": self.venue,
109
+ "display_name": self.display_name,
110
+ "status": self.status,
111
+ "storage_permitted": self.storage_permitted,
112
+ "tos": {"url": self.tos_url, "verified_on": self.tos_verified_on,
113
+ "note": self.tos_note},
114
+ "capabilities": {
115
+ "markets": self.capabilities.markets,
116
+ "terms": self.capabilities.terms,
117
+ "book": self.capabilities.book,
118
+ "trades": self.capabilities.trades,
119
+ "streaming": self.capabilities.streaming,
120
+ "order_construction": self.capabilities.order_construction,
121
+ },
122
+ }
123
+
124
+
125
+ class Registry:
126
+ """Every known venue, including the ones we deliberately do not ingest.
127
+
128
+ Blocked venues stay listed with their reason. A venue that silently
129
+ disappears from a registry looks like one nobody thought about; a venue
130
+ listed as blocked, with the clause that blocked it, is a decision.
131
+ """
132
+
133
+ def __init__(self) -> None:
134
+ self._adapters: dict[str, type[VenueAdapter]] = {}
135
+ self._blocked: dict[str, str] = {}
136
+
137
+ def register(self, adapter_cls: type[VenueAdapter]) -> type[VenueAdapter]:
138
+ self._adapters[adapter_cls.venue] = adapter_cls
139
+ return adapter_cls
140
+
141
+ def block(self, venue: str, reason: str) -> None:
142
+ self._blocked[venue] = reason
143
+
144
+ def get(self, venue: str) -> type[VenueAdapter]:
145
+ if venue in self._blocked:
146
+ raise KeyError(f"{venue} is blocked: {self._blocked[venue]}")
147
+ if venue not in self._adapters:
148
+ raise KeyError(f"no adapter registered for {venue!r}")
149
+ return self._adapters[venue]
150
+
151
+ def storable(self) -> list[str]:
152
+ return [v for v, a in self._adapters.items() if a.storage_permitted]
153
+
154
+ def with_books(self) -> list[str]:
155
+ return [v for v, a in self._adapters.items() if a.capabilities.book]
156
+
157
+ def describe_all(self) -> dict:
158
+ return {
159
+ "registered": [a().describe() for a in self._adapters.values()],
160
+ "blocked": [{"venue": v, "reason": r}
161
+ for v, r in self._blocked.items()],
162
+ "counts": {
163
+ "registered": len(self._adapters),
164
+ "storable": len(self.storable()),
165
+ "with_books": len(self.with_books()),
166
+ "blocked": len(self._blocked),
167
+ },
168
+ }
169
+
170
+
171
+ REGISTRY = Registry()
172
+
173
+ # Recorded so the reasoning is not lost. Probed 2026-09-02.
174
+ REGISTRY.block(
175
+ "myriad",
176
+ "api.myriad.markets answers 'Service Healthy' but exposes no documented "
177
+ "market path; the Polkamarkets backend rejected all eleven pagination "
178
+ "conventions tried. Blocked pending real documentation.")
179
+ REGISTRY.block(
180
+ "coinbase",
181
+ "not a venue — Coinbase 'Predict' routes through Kalshi's matching engine.")
182
+ REGISTRY.block(
183
+ "kraken", "announced, not live as of 2026-09-02.")
184
+ REGISTRY.block(
185
+ "sporttrade",
186
+ "not trading: exited US sports betting 25 May 2026; CFTC DCM/DCO "
187
+ "application filed Feb 2026 and still pending as of 2026-09-09.")
188
+
189
+ # Verified 2026-09-09 by reading the clause, not a summary of it. This is
190
+ # the only venue found whose terms prohibit this product by description.
191
+ REGISTRY.block(
192
+ "sx_bet",
193
+ "terms forbid content being \"merged with other data\" or any "
194
+ "activity intended to \"collect, store, reorganise or manipulate\" it, "
195
+ "and separately prohibit scraping their odds. Cross-venue merging is "
196
+ "precisely this product, so there is no permitted reading. "
197
+ "help.sx.bet/en/articles/3613372-terms-and-conditions")
198
+
199
+ REGISTRY.block(
200
+ "metaculus",
201
+ "licence is limited, personal, non-exclusive and non-commercial, and "
202
+ "automated collection is barred except through their own API; they also "
203
+ "403 us outright. Verified 2026-09-09.")