worm-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.
- worm_mcp-0.1.0/.gitignore +16 -0
- worm_mcp-0.1.0/LICENSE +21 -0
- worm_mcp-0.1.0/PKG-INFO +245 -0
- worm_mcp-0.1.0/README.md +212 -0
- worm_mcp-0.1.0/pyproject.toml +63 -0
- worm_mcp-0.1.0/src/worm_mcp/__init__.py +5 -0
- worm_mcp-0.1.0/src/worm_mcp/__main__.py +14 -0
- worm_mcp-0.1.0/src/worm_mcp/_version.py +1 -0
- worm_mcp-0.1.0/src/worm_mcp/constants.py +15 -0
- worm_mcp-0.1.0/src/worm_mcp/docs.py +58 -0
- worm_mcp-0.1.0/src/worm_mcp/py.typed +0 -0
- worm_mcp-0.1.0/src/worm_mcp/server.py +885 -0
- worm_mcp-0.1.0/src/worm_mcp/tools.py +523 -0
worm_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Worm
|
|
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.
|
worm_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: worm-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for Worm prediction markets on Solana
|
|
5
|
+
Project-URL: Homepage, https://worm.wtf
|
|
6
|
+
Project-URL: Documentation, https://docs.worm.wtf
|
|
7
|
+
Project-URL: Repository, https://github.com/wormwtf/worm-mcp
|
|
8
|
+
Project-URL: Issues, https://github.com/wormwtf/worm-mcp/issues
|
|
9
|
+
Author-email: Worm <support@worm.wtf>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: mcp,prediction-markets,solana,trading,worm
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: httpx>=0.25
|
|
27
|
+
Requires-Dist: mcp>=1.2.0
|
|
28
|
+
Requires-Dist: worm-sdk[signing]>=0.9.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
<div align="center">
|
|
35
|
+
<img src="https://www.worm.wtf/images/logo.png" alt="Worm" width="110px" />
|
|
36
|
+
<h1>Worm MCP</h1>
|
|
37
|
+
<p>Browse, trade, and manage prediction markets from any AI agent.</p>
|
|
38
|
+
<p>
|
|
39
|
+
๐ <a href="https://worm.wtf">Worm</a> ยท
|
|
40
|
+
๐ <a href="https://docs.worm.wtf">Docs</a> ยท
|
|
41
|
+
๐ฆ <a href="https://github.com/wormwtf/worm-sdk">worm-sdk</a> ยท
|
|
42
|
+
๐ <a href="https://github.com/wormwtf/worm-mcp/issues">Issues</a> ยท
|
|
43
|
+
โ๏ธ <a href="LICENSE">MIT</a>
|
|
44
|
+
</p>
|
|
45
|
+
<p>
|
|
46
|
+
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python 3.10+" /></a>
|
|
47
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
|
|
48
|
+
</p>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
worm-mcp runs over stdio; your MCP client launches it on demand. Set `WALLET_PRIVATE_KEY` to enable authenticated and trading tools, or omit it to use only the public, read-only tools.
|
|
54
|
+
|
|
55
|
+
Pick the flow that fits you:
|
|
56
|
+
|
|
57
|
+
- **[For users](#for-users)**: you just want to use the tool in your MCP client. Your client launches it with `uvx`; no manual install.
|
|
58
|
+
- **[For developers](#for-developers)**: you want to modify the code. Clone the repo and run it from a local editable install.
|
|
59
|
+
|
|
60
|
+
### For users
|
|
61
|
+
|
|
62
|
+
Your client launches worm-mcp on demand; `uvx` fetches and runs it for you.
|
|
63
|
+
|
|
64
|
+
**Claude Code**: register it once from the CLI; `--scope user` makes it available in every project:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
claude mcp add --scope user worm \
|
|
68
|
+
-e WALLET_PRIVATE_KEY=<base58-solana-private-key> \
|
|
69
|
+
-- uvx worm-mcp
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Confirm with `claude mcp list` (shows `worm โฆ โ Connected`).
|
|
73
|
+
|
|
74
|
+
**Cursor**: add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project), then enable it under **Settings โ MCP**:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"mcpServers": {
|
|
79
|
+
"worm": {
|
|
80
|
+
"command": "uvx",
|
|
81
|
+
"args": ["worm-mcp"],
|
|
82
|
+
"env": { "WALLET_PRIVATE_KEY": "<base58-solana-private-key>" }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Claude Desktop**: **Settings โ Developer โ Edit Config** opens `claude_desktop_config.json`; add the same `mcpServers` block and restart the app.
|
|
89
|
+
|
|
90
|
+
**Smithery**: [Smithery](https://smithery.ai) installs it into a supported client with one command (it runs locally over stdio, so your key stays on your machine):
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx -y @smithery/cli install worm-mcp --client claude
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Other clients** (Windsurf, Cline, Zed, VS Code, โฆ): same launch: command `uvx`, args `["worm-mcp"]`, plus the `WALLET_PRIVATE_KEY` env. Only the config file location differs.
|
|
97
|
+
|
|
98
|
+
> No `uv`? `pipx run worm-mcp` or `pip install worm-mcp` work too.
|
|
99
|
+
|
|
100
|
+
### For developers
|
|
101
|
+
|
|
102
|
+
Run worm-mcp from a local clone, for hacking on the code or running an unpublished build. Clone and set up an editable environment:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
git clone https://github.com/wormwtf/worm-mcp.git
|
|
106
|
+
cd worm-mcp
|
|
107
|
+
uv venv
|
|
108
|
+
uv pip install -e ".[dev]"
|
|
109
|
+
uv run ruff check src
|
|
110
|
+
WALLET_PRIVATE_KEY=... worm-mcp # run the stdio server directly
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Point your client at the editable checkout so code changes take effect on the next client restart. Either add `--with-editable` to the uvx launch:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
claude mcp add --scope user worm \
|
|
117
|
+
-e WALLET_PRIVATE_KEY=<base58-solana-private-key> \
|
|
118
|
+
-- uvx --from /path/to/worm-mcp --with-editable /path/to/worm-mcp worm-mcp
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
โฆor point the client's `command` straight at the venv binary the editable install creates (`args` can be omitted):
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"mcpServers": {
|
|
126
|
+
"worm": {
|
|
127
|
+
"command": "/path/to/worm-mcp/.venv/bin/worm-mcp",
|
|
128
|
+
"env": { "WALLET_PRIVATE_KEY": "<base58-solana-private-key>" }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Verify
|
|
135
|
+
|
|
136
|
+
Ask your client *"List trending markets on Worm"* (public) or *"What's my Worm account summary?"* (confirms auth): a sensible answer means it's wired up.
|
|
137
|
+
|
|
138
|
+
## Requirements
|
|
139
|
+
|
|
140
|
+
- An MCP client (Claude Code, Cursor, Claude Desktop, โฆ)
|
|
141
|
+
- [`uv`](https://docs.astral.sh/uv/) (or `pipx` / `pip`)
|
|
142
|
+
- A funded Solana wallet (only for trading and other authenticated tools)
|
|
143
|
+
|
|
144
|
+
## Environment variables
|
|
145
|
+
|
|
146
|
+
| Variable | Required | Purpose |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `WALLET_PRIVATE_KEY` | for writes | Base58 Solana private key (the export from Phantom/Solflare; a 64-byte hex keypair also works). Signs transactions locally and bootstraps HMAC credentials on first run, cached in `~/.worm/config.json`. |
|
|
149
|
+
| `WORM_API_KEY` / `WORM_API_SECRET` | no | Use existing HMAC credentials instead of bootstrapping |
|
|
150
|
+
| `WORM_API_BASE` | no | API base URL (default `https://api.worm.wtf`) |
|
|
151
|
+
|
|
152
|
+
## Tools
|
|
153
|
+
|
|
154
|
+
### Public (no credentials)
|
|
155
|
+
|
|
156
|
+
| Tool | Description |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `search` | Search markets and events by text and filters |
|
|
159
|
+
| `list_markets` | Browse markets by category, state, or sort |
|
|
160
|
+
| `get_market` | Full market detail (outcomes, fees, config, state) |
|
|
161
|
+
| `get_market_stats` | Volume, market cap, and trade count |
|
|
162
|
+
| `get_market_price` | Mid-price snapshot for an outcome |
|
|
163
|
+
| `get_orderbook` | Spot orderbook bids and asks |
|
|
164
|
+
| `get_market_candles` | OHLCV candles (5m / 30m) |
|
|
165
|
+
| `get_market_trades` | Recent public trades |
|
|
166
|
+
| `get_market_margin_activity` | Public margin activity feed |
|
|
167
|
+
| `list_events` | Browse events (groups of related markets) |
|
|
168
|
+
| `get_event` | Full event detail with all markets |
|
|
169
|
+
| `list_sports` | Sports and leagues catalog |
|
|
170
|
+
| `estimate_margin_position` | Preview a leveraged position |
|
|
171
|
+
| `read_worm_docs` | Fetch Worm documentation pages |
|
|
172
|
+
|
|
173
|
+
### Authenticated read (HMAC)
|
|
174
|
+
|
|
175
|
+
| Tool | Description |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `get_account_summary` | Your profile |
|
|
178
|
+
| `get_account_assets` | Portfolio share balances |
|
|
179
|
+
| `get_account_pnl` | PnL breakdown |
|
|
180
|
+
| `list_orders` / `get_order` | Your spot orders |
|
|
181
|
+
| `list_trades` | Your trade fills |
|
|
182
|
+
| `list_margin_positions` / `get_margin_position` | Your margin positions |
|
|
183
|
+
| `list_margin_requests` / `get_margin_request` | Your margin position requests |
|
|
184
|
+
| `list_margin_settlements` | Margin settlements |
|
|
185
|
+
| `list_redeems` / `get_redeem` | Your redeem records |
|
|
186
|
+
| `list_api_keys` | Your API keys |
|
|
187
|
+
|
|
188
|
+
### Authenticated write (HMAC)
|
|
189
|
+
|
|
190
|
+
| Tool | Description |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `cancel_margin_request` | Cancel a pending margin request |
|
|
193
|
+
| `close_margin_position` | Close a margin position |
|
|
194
|
+
| `set_tp_sl` / `remove_tp_sl` | Set / clear take-profit and stop-loss |
|
|
195
|
+
| `claim_margin_settlement` | Claim a resolved settlement |
|
|
196
|
+
| `revoke_api_key` | Revoke an API key |
|
|
197
|
+
|
|
198
|
+
### Authenticated write: local signing (needs `WALLET_PRIVATE_KEY`)
|
|
199
|
+
|
|
200
|
+
| Tool | Description |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `place_order` / `cancel_order` | Place / cancel a spot order |
|
|
203
|
+
| `open_margin_position` | Open a leveraged position |
|
|
204
|
+
| `redeem` | Redeem winnings from a resolved market |
|
|
205
|
+
|
|
206
|
+
## Notes for agents
|
|
207
|
+
|
|
208
|
+
- **Spot vs margin**: `place_order` is for non-margin (orderbook) markets; use `open_margin_position` when `margin_enabled=true`. Margin markets quote against an AMM, so `get_orderbook` may be empty; size with `estimate_margin_position`.
|
|
209
|
+
- **Margin lifecycle**: `open_margin_position` returns a *position-request* pubkey; the actual position appears via `list_margin_positions` (match `position_request_pubkey`) once funding completes. TP/SL is unsupported on Hyperliquid-backed markets.
|
|
210
|
+
- **Leverage**: always call `estimate_margin_position` and confirm with the user before opening; leverage carries liquidation risk.
|
|
211
|
+
- **Docs on demand**: call `read_worm_docs` with no arguments for the index, then pass a path for exact API schemas and examples.
|
|
212
|
+
|
|
213
|
+
## Features
|
|
214
|
+
|
|
215
|
+
- 38 tools across markets, events, search, sports, account, orders, trades, margin, redeems, key management, and docs
|
|
216
|
+
- Local Solana keypair signing, so your private key never leaves the MCP process
|
|
217
|
+
- Auto-bootstrapped HMAC credentials: set one env var and the rest is automatic
|
|
218
|
+
- Public market data works with no credentials at all
|
|
219
|
+
- Built on the official [`worm-sdk`](https://pypi.org/project/worm-sdk/), with full type hints
|
|
220
|
+
|
|
221
|
+
## Architecture
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
โโโโโโโโโโโโโโโโ stdio โโโโโโโโโโโโโโ HTTPS + HMAC โโโโโโโโโโโโโโโโ
|
|
225
|
+
โ MCP client โ โโโโโโโโถ โ worm-mcp โ โโโโโโโโโโโโโโถ โ Worm API โ
|
|
226
|
+
โ (Claude etc.)โ โ (Python) โ โ api.worm.wtf โ
|
|
227
|
+
โโโโโโโโโโโโโโโโ โโโโโโโฌโโโโโโโ โโโโโโโโโโโโโโโโ
|
|
228
|
+
โ ed25519 signing (orders, margin, redeems)
|
|
229
|
+
โผ
|
|
230
|
+
worm-sdk + solders (local keypair)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
See [rate limits](https://docs.worm.wtf/api-reference/rate-limits) in the docs. Markets are viewable at `https://worm.wtf/market/{condition_id}`.
|
|
234
|
+
|
|
235
|
+
## Security
|
|
236
|
+
|
|
237
|
+
- Your private key is used only to sign transactions locally; it is never sent to the Worm API or any third party.
|
|
238
|
+
- Bootstrapped HMAC credentials are cached at `~/.worm/config.json` with `0600` permissions, keyed by API base URL and wallet, so multiple wallets or environments (e.g. `WORM_API_BASE` overrides) each keep their own credentials instead of clobbering one another. Writes are atomic and locked, so concurrent clients can't corrupt or lose cached credentials.
|
|
239
|
+
- Use a dedicated wallet funded with only what you intend to trade, and keep the key out of version control and shared configs.
|
|
240
|
+
|
|
241
|
+
## Troubleshooting
|
|
242
|
+
|
|
243
|
+
- **`uvx: command not found`**: install [`uv`](https://docs.astral.sh/uv/), or use `pipx run worm-mcp` / `pip install worm-mcp`.
|
|
244
|
+
- **Tools don't appear**: fully restart the client; it spawns the server once at startup. In Claude Code, check `claude mcp list`.
|
|
245
|
+
- **Authentication errors**: confirm `WALLET_PRIVATE_KEY` is a valid base58 Solana key; delete `~/.worm/config.json` to force a fresh credential bootstrap.
|
worm_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://www.worm.wtf/images/logo.png" alt="Worm" width="110px" />
|
|
3
|
+
<h1>Worm MCP</h1>
|
|
4
|
+
<p>Browse, trade, and manage prediction markets from any AI agent.</p>
|
|
5
|
+
<p>
|
|
6
|
+
๐ <a href="https://worm.wtf">Worm</a> ยท
|
|
7
|
+
๐ <a href="https://docs.worm.wtf">Docs</a> ยท
|
|
8
|
+
๐ฆ <a href="https://github.com/wormwtf/worm-sdk">worm-sdk</a> ยท
|
|
9
|
+
๐ <a href="https://github.com/wormwtf/worm-mcp/issues">Issues</a> ยท
|
|
10
|
+
โ๏ธ <a href="LICENSE">MIT</a>
|
|
11
|
+
</p>
|
|
12
|
+
<p>
|
|
13
|
+
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python 3.10+" /></a>
|
|
14
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
|
|
15
|
+
</p>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
worm-mcp runs over stdio; your MCP client launches it on demand. Set `WALLET_PRIVATE_KEY` to enable authenticated and trading tools, or omit it to use only the public, read-only tools.
|
|
21
|
+
|
|
22
|
+
Pick the flow that fits you:
|
|
23
|
+
|
|
24
|
+
- **[For users](#for-users)**: you just want to use the tool in your MCP client. Your client launches it with `uvx`; no manual install.
|
|
25
|
+
- **[For developers](#for-developers)**: you want to modify the code. Clone the repo and run it from a local editable install.
|
|
26
|
+
|
|
27
|
+
### For users
|
|
28
|
+
|
|
29
|
+
Your client launches worm-mcp on demand; `uvx` fetches and runs it for you.
|
|
30
|
+
|
|
31
|
+
**Claude Code**: register it once from the CLI; `--scope user` makes it available in every project:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
claude mcp add --scope user worm \
|
|
35
|
+
-e WALLET_PRIVATE_KEY=<base58-solana-private-key> \
|
|
36
|
+
-- uvx worm-mcp
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Confirm with `claude mcp list` (shows `worm โฆ โ Connected`).
|
|
40
|
+
|
|
41
|
+
**Cursor**: add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project), then enable it under **Settings โ MCP**:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"worm": {
|
|
47
|
+
"command": "uvx",
|
|
48
|
+
"args": ["worm-mcp"],
|
|
49
|
+
"env": { "WALLET_PRIVATE_KEY": "<base58-solana-private-key>" }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Claude Desktop**: **Settings โ Developer โ Edit Config** opens `claude_desktop_config.json`; add the same `mcpServers` block and restart the app.
|
|
56
|
+
|
|
57
|
+
**Smithery**: [Smithery](https://smithery.ai) installs it into a supported client with one command (it runs locally over stdio, so your key stays on your machine):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx -y @smithery/cli install worm-mcp --client claude
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Other clients** (Windsurf, Cline, Zed, VS Code, โฆ): same launch: command `uvx`, args `["worm-mcp"]`, plus the `WALLET_PRIVATE_KEY` env. Only the config file location differs.
|
|
64
|
+
|
|
65
|
+
> No `uv`? `pipx run worm-mcp` or `pip install worm-mcp` work too.
|
|
66
|
+
|
|
67
|
+
### For developers
|
|
68
|
+
|
|
69
|
+
Run worm-mcp from a local clone, for hacking on the code or running an unpublished build. Clone and set up an editable environment:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git clone https://github.com/wormwtf/worm-mcp.git
|
|
73
|
+
cd worm-mcp
|
|
74
|
+
uv venv
|
|
75
|
+
uv pip install -e ".[dev]"
|
|
76
|
+
uv run ruff check src
|
|
77
|
+
WALLET_PRIVATE_KEY=... worm-mcp # run the stdio server directly
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Point your client at the editable checkout so code changes take effect on the next client restart. Either add `--with-editable` to the uvx launch:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
claude mcp add --scope user worm \
|
|
84
|
+
-e WALLET_PRIVATE_KEY=<base58-solana-private-key> \
|
|
85
|
+
-- uvx --from /path/to/worm-mcp --with-editable /path/to/worm-mcp worm-mcp
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
โฆor point the client's `command` straight at the venv binary the editable install creates (`args` can be omitted):
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"worm": {
|
|
94
|
+
"command": "/path/to/worm-mcp/.venv/bin/worm-mcp",
|
|
95
|
+
"env": { "WALLET_PRIVATE_KEY": "<base58-solana-private-key>" }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Verify
|
|
102
|
+
|
|
103
|
+
Ask your client *"List trending markets on Worm"* (public) or *"What's my Worm account summary?"* (confirms auth): a sensible answer means it's wired up.
|
|
104
|
+
|
|
105
|
+
## Requirements
|
|
106
|
+
|
|
107
|
+
- An MCP client (Claude Code, Cursor, Claude Desktop, โฆ)
|
|
108
|
+
- [`uv`](https://docs.astral.sh/uv/) (or `pipx` / `pip`)
|
|
109
|
+
- A funded Solana wallet (only for trading and other authenticated tools)
|
|
110
|
+
|
|
111
|
+
## Environment variables
|
|
112
|
+
|
|
113
|
+
| Variable | Required | Purpose |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `WALLET_PRIVATE_KEY` | for writes | Base58 Solana private key (the export from Phantom/Solflare; a 64-byte hex keypair also works). Signs transactions locally and bootstraps HMAC credentials on first run, cached in `~/.worm/config.json`. |
|
|
116
|
+
| `WORM_API_KEY` / `WORM_API_SECRET` | no | Use existing HMAC credentials instead of bootstrapping |
|
|
117
|
+
| `WORM_API_BASE` | no | API base URL (default `https://api.worm.wtf`) |
|
|
118
|
+
|
|
119
|
+
## Tools
|
|
120
|
+
|
|
121
|
+
### Public (no credentials)
|
|
122
|
+
|
|
123
|
+
| Tool | Description |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `search` | Search markets and events by text and filters |
|
|
126
|
+
| `list_markets` | Browse markets by category, state, or sort |
|
|
127
|
+
| `get_market` | Full market detail (outcomes, fees, config, state) |
|
|
128
|
+
| `get_market_stats` | Volume, market cap, and trade count |
|
|
129
|
+
| `get_market_price` | Mid-price snapshot for an outcome |
|
|
130
|
+
| `get_orderbook` | Spot orderbook bids and asks |
|
|
131
|
+
| `get_market_candles` | OHLCV candles (5m / 30m) |
|
|
132
|
+
| `get_market_trades` | Recent public trades |
|
|
133
|
+
| `get_market_margin_activity` | Public margin activity feed |
|
|
134
|
+
| `list_events` | Browse events (groups of related markets) |
|
|
135
|
+
| `get_event` | Full event detail with all markets |
|
|
136
|
+
| `list_sports` | Sports and leagues catalog |
|
|
137
|
+
| `estimate_margin_position` | Preview a leveraged position |
|
|
138
|
+
| `read_worm_docs` | Fetch Worm documentation pages |
|
|
139
|
+
|
|
140
|
+
### Authenticated read (HMAC)
|
|
141
|
+
|
|
142
|
+
| Tool | Description |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `get_account_summary` | Your profile |
|
|
145
|
+
| `get_account_assets` | Portfolio share balances |
|
|
146
|
+
| `get_account_pnl` | PnL breakdown |
|
|
147
|
+
| `list_orders` / `get_order` | Your spot orders |
|
|
148
|
+
| `list_trades` | Your trade fills |
|
|
149
|
+
| `list_margin_positions` / `get_margin_position` | Your margin positions |
|
|
150
|
+
| `list_margin_requests` / `get_margin_request` | Your margin position requests |
|
|
151
|
+
| `list_margin_settlements` | Margin settlements |
|
|
152
|
+
| `list_redeems` / `get_redeem` | Your redeem records |
|
|
153
|
+
| `list_api_keys` | Your API keys |
|
|
154
|
+
|
|
155
|
+
### Authenticated write (HMAC)
|
|
156
|
+
|
|
157
|
+
| Tool | Description |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `cancel_margin_request` | Cancel a pending margin request |
|
|
160
|
+
| `close_margin_position` | Close a margin position |
|
|
161
|
+
| `set_tp_sl` / `remove_tp_sl` | Set / clear take-profit and stop-loss |
|
|
162
|
+
| `claim_margin_settlement` | Claim a resolved settlement |
|
|
163
|
+
| `revoke_api_key` | Revoke an API key |
|
|
164
|
+
|
|
165
|
+
### Authenticated write: local signing (needs `WALLET_PRIVATE_KEY`)
|
|
166
|
+
|
|
167
|
+
| Tool | Description |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `place_order` / `cancel_order` | Place / cancel a spot order |
|
|
170
|
+
| `open_margin_position` | Open a leveraged position |
|
|
171
|
+
| `redeem` | Redeem winnings from a resolved market |
|
|
172
|
+
|
|
173
|
+
## Notes for agents
|
|
174
|
+
|
|
175
|
+
- **Spot vs margin**: `place_order` is for non-margin (orderbook) markets; use `open_margin_position` when `margin_enabled=true`. Margin markets quote against an AMM, so `get_orderbook` may be empty; size with `estimate_margin_position`.
|
|
176
|
+
- **Margin lifecycle**: `open_margin_position` returns a *position-request* pubkey; the actual position appears via `list_margin_positions` (match `position_request_pubkey`) once funding completes. TP/SL is unsupported on Hyperliquid-backed markets.
|
|
177
|
+
- **Leverage**: always call `estimate_margin_position` and confirm with the user before opening; leverage carries liquidation risk.
|
|
178
|
+
- **Docs on demand**: call `read_worm_docs` with no arguments for the index, then pass a path for exact API schemas and examples.
|
|
179
|
+
|
|
180
|
+
## Features
|
|
181
|
+
|
|
182
|
+
- 38 tools across markets, events, search, sports, account, orders, trades, margin, redeems, key management, and docs
|
|
183
|
+
- Local Solana keypair signing, so your private key never leaves the MCP process
|
|
184
|
+
- Auto-bootstrapped HMAC credentials: set one env var and the rest is automatic
|
|
185
|
+
- Public market data works with no credentials at all
|
|
186
|
+
- Built on the official [`worm-sdk`](https://pypi.org/project/worm-sdk/), with full type hints
|
|
187
|
+
|
|
188
|
+
## Architecture
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
โโโโโโโโโโโโโโโโ stdio โโโโโโโโโโโโโโ HTTPS + HMAC โโโโโโโโโโโโโโโโ
|
|
192
|
+
โ MCP client โ โโโโโโโโถ โ worm-mcp โ โโโโโโโโโโโโโโถ โ Worm API โ
|
|
193
|
+
โ (Claude etc.)โ โ (Python) โ โ api.worm.wtf โ
|
|
194
|
+
โโโโโโโโโโโโโโโโ โโโโโโโฌโโโโโโโ โโโโโโโโโโโโโโโโ
|
|
195
|
+
โ ed25519 signing (orders, margin, redeems)
|
|
196
|
+
โผ
|
|
197
|
+
worm-sdk + solders (local keypair)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
See [rate limits](https://docs.worm.wtf/api-reference/rate-limits) in the docs. Markets are viewable at `https://worm.wtf/market/{condition_id}`.
|
|
201
|
+
|
|
202
|
+
## Security
|
|
203
|
+
|
|
204
|
+
- Your private key is used only to sign transactions locally; it is never sent to the Worm API or any third party.
|
|
205
|
+
- Bootstrapped HMAC credentials are cached at `~/.worm/config.json` with `0600` permissions, keyed by API base URL and wallet, so multiple wallets or environments (e.g. `WORM_API_BASE` overrides) each keep their own credentials instead of clobbering one another. Writes are atomic and locked, so concurrent clients can't corrupt or lose cached credentials.
|
|
206
|
+
- Use a dedicated wallet funded with only what you intend to trade, and keep the key out of version control and shared configs.
|
|
207
|
+
|
|
208
|
+
## Troubleshooting
|
|
209
|
+
|
|
210
|
+
- **`uvx: command not found`**: install [`uv`](https://docs.astral.sh/uv/), or use `pipx run worm-mcp` / `pip install worm-mcp`.
|
|
211
|
+
- **Tools don't appear**: fully restart the client; it spawns the server once at startup. In Claude Code, check `claude mcp list`.
|
|
212
|
+
- **Authentication errors**: confirm `WALLET_PRIVATE_KEY` is a valid base58 Solana key; delete `~/.worm/config.json` to force a fresh credential bootstrap.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "worm-mcp"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "MCP server for Worm prediction markets on Solana"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "Worm", email = "support@worm.wtf" }]
|
|
13
|
+
keywords = ["mcp", "worm", "prediction-markets", "solana", "trading"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Office/Business :: Financial",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"mcp>=1.2.0",
|
|
30
|
+
"worm-sdk[signing]>=0.9.0",
|
|
31
|
+
"httpx>=0.25",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://worm.wtf"
|
|
36
|
+
Documentation = "https://docs.worm.wtf"
|
|
37
|
+
Repository = "https://github.com/wormwtf/worm-mcp"
|
|
38
|
+
Issues = "https://github.com/wormwtf/worm-mcp/issues"
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
dev = [
|
|
42
|
+
"ruff>=0.1",
|
|
43
|
+
"mypy>=1.0",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.scripts]
|
|
47
|
+
worm-mcp = "worm_mcp.__main__:main"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.version]
|
|
50
|
+
path = "src/worm_mcp/_version.py"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel]
|
|
53
|
+
packages = ["src/worm_mcp"]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.sdist]
|
|
56
|
+
include = ["src/worm_mcp", "README.md", "LICENSE", "pyproject.toml"]
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
line-length = 120
|
|
60
|
+
target-version = "py310"
|
|
61
|
+
|
|
62
|
+
[tool.mypy]
|
|
63
|
+
python_version = "3.10"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Entry point launched by Claude Code/Cursor/Claude Desktop as subprocess."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .server import build_server
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
server = build_server()
|
|
10
|
+
server.run()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Configuration constants for the Worm MCP server."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
# Bootstrapped HMAC credentials are cached here, keyed by (base_url, wallet).
|
|
6
|
+
# This path is documented in the README and MCP server instructions โ keep it.
|
|
7
|
+
CONFIG_FILE = Path.home() / ".worm" / "config.json"
|
|
8
|
+
|
|
9
|
+
DOCS_HOST = "docs.worm.wtf"
|
|
10
|
+
DOCS_BASE = f"https://{DOCS_HOST}"
|
|
11
|
+
DOCS_INDEX_PATH = "llms.txt"
|
|
12
|
+
DOCS_FULL_PATH = "llms-full.txt"
|
|
13
|
+
|
|
14
|
+
# Cap on docs content returned by a single read_worm_docs call.
|
|
15
|
+
DOCS_MAX_CHARS = 100_000
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""read_worm_docs tool โ fetches Worm documentation from docs.worm.wtf via httpx."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from worm_mcp.constants import DOCS_BASE, DOCS_FULL_PATH, DOCS_HOST, DOCS_INDEX_PATH, DOCS_MAX_CHARS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _resolve_url(path: str | None, full: bool) -> str:
|
|
13
|
+
"""Map (path, full) to a docs.worm.wtf URL, rejecting any other host."""
|
|
14
|
+
if full:
|
|
15
|
+
if path:
|
|
16
|
+
raise RuntimeError("Pass either a path or full=True, not both.")
|
|
17
|
+
return f"{DOCS_BASE}/{DOCS_FULL_PATH}"
|
|
18
|
+
|
|
19
|
+
# urlparse drops any query/fragment so they can't leak into the .md suffix below.
|
|
20
|
+
parsed = urlparse((path or "").strip())
|
|
21
|
+
if parsed.scheme and parsed.hostname != DOCS_HOST:
|
|
22
|
+
raise RuntimeError(f"read_worm_docs only fetches {DOCS_HOST}; got host {parsed.hostname!r}.")
|
|
23
|
+
|
|
24
|
+
rel = parsed.path.lstrip("/")
|
|
25
|
+
if not rel: # empty path, bare "/", or a bare docs URL โ index
|
|
26
|
+
return f"{DOCS_BASE}/{DOCS_INDEX_PATH}"
|
|
27
|
+
if ".." in rel.split("/"):
|
|
28
|
+
raise RuntimeError(f"Invalid docs path {path!r}.")
|
|
29
|
+
if not rel.endswith((".md", ".txt")):
|
|
30
|
+
rel = f"{rel}.md"
|
|
31
|
+
return f"{DOCS_BASE}/{rel}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def read_worm_docs(path: str | None = None, full: bool = False) -> dict:
|
|
35
|
+
"""Fetch the docs index (no args), a doc page by path, or the full docs (full=True)."""
|
|
36
|
+
url = _resolve_url(path, full)
|
|
37
|
+
try:
|
|
38
|
+
resp = httpx.get(url, timeout=30.0, follow_redirects=True)
|
|
39
|
+
except httpx.HTTPError as e:
|
|
40
|
+
raise RuntimeError(f"Failed to fetch {url}: {e}") from e
|
|
41
|
+
|
|
42
|
+
# docs.worm.wtf uses same-host 307s (e.g. section -> intro page); follow them, but
|
|
43
|
+
# refuse the content if ANY hop (intermediate or final) left the allowlist.
|
|
44
|
+
off = next((r.url.host for r in [*resp.history, resp] if r.url.host != DOCS_HOST), None)
|
|
45
|
+
if off is not None:
|
|
46
|
+
raise RuntimeError(f"Refusing content from off-allowlist host {off!r} after redirect.")
|
|
47
|
+
|
|
48
|
+
if resp.status_code != 200:
|
|
49
|
+
raise RuntimeError(
|
|
50
|
+
f"{url} returned HTTP {resp.status_code}. "
|
|
51
|
+
"Call read_worm_docs() with no arguments to get the index of valid paths."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
content = resp.text
|
|
55
|
+
truncated = len(content) > DOCS_MAX_CHARS
|
|
56
|
+
if truncated:
|
|
57
|
+
content = content[:DOCS_MAX_CHARS] + "\n\n[truncated โ fetch a more specific path]"
|
|
58
|
+
return {"url": str(resp.url), "truncated": truncated, "content": content}
|
|
File without changes
|