easy-predict-mcp 1.0.0__py3-none-any.whl
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.
|
File without changes
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import base64
|
|
5
|
+
import secrets
|
|
6
|
+
import requests
|
|
7
|
+
from mcp.server.fastmcp import FastMCP
|
|
8
|
+
|
|
9
|
+
API_BASE = os.environ.get("EASY_PREDICT_URL", "https://easy-predict.com").rstrip("/")
|
|
10
|
+
PRIVATE_KEY = os.environ.get("WALLET_PRIVATE_KEY")
|
|
11
|
+
|
|
12
|
+
mcp = FastMCP(
|
|
13
|
+
"easy-predict",
|
|
14
|
+
instructions=(
|
|
15
|
+
"Use predict_timeseries to forecast the next value in a numeric series, "
|
|
16
|
+
"or detect_anomalies to find outliers. "
|
|
17
|
+
"Each call costs $0.01 USDC on Base, paid automatically from your local wallet via x402. "
|
|
18
|
+
"Your private key never leaves your machine."
|
|
19
|
+
),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _call_with_payment(url: str, body: dict) -> dict:
|
|
24
|
+
"""
|
|
25
|
+
POST to an x402-gated endpoint.
|
|
26
|
+
Signs the payment locally — the private key never leaves this process.
|
|
27
|
+
Only the signed payment header is sent to the remote server.
|
|
28
|
+
"""
|
|
29
|
+
if not PRIVATE_KEY:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
"WALLET_PRIVATE_KEY is not set. "
|
|
32
|
+
"Add it to your MCP client config under 'env': {\"WALLET_PRIVATE_KEY\": \"0x...\"}"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
from eth_account import Account
|
|
37
|
+
from eth_account.messages import encode_defunct
|
|
38
|
+
except ImportError:
|
|
39
|
+
raise RuntimeError(
|
|
40
|
+
"eth-account is not installed. "
|
|
41
|
+
"Reinstall the package: pip install easy-predict-mcp"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
resp = requests.post(url, json=body)
|
|
45
|
+
if resp.status_code == 200:
|
|
46
|
+
return resp.json()
|
|
47
|
+
if resp.status_code != 402:
|
|
48
|
+
resp.raise_for_status()
|
|
49
|
+
|
|
50
|
+
challenge = resp.json()
|
|
51
|
+
reqs = challenge["accepts"][0]
|
|
52
|
+
account = Account.from_key(PRIVATE_KEY)
|
|
53
|
+
|
|
54
|
+
payload = {
|
|
55
|
+
"x402Version": 2,
|
|
56
|
+
"scheme": reqs["scheme"],
|
|
57
|
+
"network": reqs["network"],
|
|
58
|
+
"asset": reqs["asset"],
|
|
59
|
+
"amount": reqs["amount"],
|
|
60
|
+
"payTo": reqs["payTo"],
|
|
61
|
+
"resource": url,
|
|
62
|
+
"from": account.address,
|
|
63
|
+
"nonce": secrets.token_hex(16),
|
|
64
|
+
"validUntil": int(time.time()) + reqs.get("maxTimeoutSeconds", 60),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Sign locally — only the resulting signature is sent over the network
|
|
68
|
+
canonical = json.dumps(payload, separators=(",", ":"), sort_keys=True)
|
|
69
|
+
sig = account.sign_message(encode_defunct(text=canonical))
|
|
70
|
+
signed = {"payload": payload, "signature": sig.signature.hex(), "from": account.address}
|
|
71
|
+
header = base64.b64encode(json.dumps(signed).encode()).decode()
|
|
72
|
+
|
|
73
|
+
resp = requests.post(url, json=body, headers={"PAYMENT-SIGNATURE": header})
|
|
74
|
+
if not resp.ok:
|
|
75
|
+
raise ValueError(f"API error after payment: {resp.status_code} — {resp.text[:200]}")
|
|
76
|
+
return resp.json()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@mcp.tool()
|
|
80
|
+
def predict_timeseries(series: list[float], context: str = "") -> str:
|
|
81
|
+
"""
|
|
82
|
+
Predict the next value in a numeric time series.
|
|
83
|
+
|
|
84
|
+
Calls easy-predict.com and pays $0.01 USDC on Base automatically via x402.
|
|
85
|
+
Your private key signs the payment locally and is never sent to any server.
|
|
86
|
+
Uses automatic model selection: linear, log1p-linear, last-delta, or mean.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
series: List of 3–1000 numbers, ordered oldest to newest.
|
|
90
|
+
context: What the series represents, e.g. "monthly revenue USD". Max 200 chars.
|
|
91
|
+
"""
|
|
92
|
+
body: dict = {"series": series}
|
|
93
|
+
if context:
|
|
94
|
+
body["context"] = context[:200]
|
|
95
|
+
return json.dumps(_call_with_payment(f"{API_BASE}/timeseries", body))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@mcp.tool()
|
|
99
|
+
def detect_anomalies(series: list[float], threshold: float = 2.0, context: str = "") -> str:
|
|
100
|
+
"""
|
|
101
|
+
Detect anomalies in a numeric time series using z-score analysis.
|
|
102
|
+
|
|
103
|
+
Calls easy-predict.com and pays $0.01 USDC on Base automatically via x402.
|
|
104
|
+
Your private key signs the payment locally and is never sent to any server.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
series: List of 3–1000 numbers to analyze.
|
|
108
|
+
threshold: Z-score cutoff (default 2.0, range 0–10). Lower = more sensitive.
|
|
109
|
+
context: What the series represents. Max 200 chars.
|
|
110
|
+
"""
|
|
111
|
+
body: dict = {"series": series, "threshold": threshold}
|
|
112
|
+
if context:
|
|
113
|
+
body["context"] = context[:200]
|
|
114
|
+
return json.dumps(_call_with_payment(f"{API_BASE}/anomaly-detection", body))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main() -> None:
|
|
118
|
+
mcp.run()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
main()
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: easy-predict-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP server for easy-predict.com — time series prediction and anomaly detection with automatic x402 micropayments on Base.
|
|
5
|
+
Project-URL: Homepage, https://easy-predict.com
|
|
6
|
+
Project-URL: Repository, https://github.com/WilliamsRizzi/easy-predict
|
|
7
|
+
Author: WilliamsRizzi
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: anomaly-detection,base,mcp,prediction,usdc,x402
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: eth-account>=0.13.0
|
|
12
|
+
Requires-Dist: mcp[cli]>=1.0.0
|
|
13
|
+
Requires-Dist: requests>=2.32.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# easy-predict
|
|
17
|
+
|
|
18
|
+
Agent-first prediction and anomaly detection API. Send a list of numbers, get the next predicted value or a list of anomalous points. Paid per call via [x402](https://x402.org) v2 micropayments — $0.01 USDC on Base. No API keys, no accounts.
|
|
19
|
+
|
|
20
|
+
**Live at [easy-predict.com](https://easy-predict.com)**
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Endpoints
|
|
25
|
+
|
|
26
|
+
| Method | Path | Cost | Description |
|
|
27
|
+
|--------|------|------|-------------|
|
|
28
|
+
| POST | `/timeseries` | $0.01 USDC | Predict the next value in a numeric series |
|
|
29
|
+
| POST | `/anomaly-detection` | $0.01 USDC | Detect anomalies via z-score |
|
|
30
|
+
| GET | `/.well-known/x402` | free | x402 v2 resource discovery |
|
|
31
|
+
| GET | `/openapi.json` | free | OpenAPI 3.1 spec |
|
|
32
|
+
| GET | `/llms.txt` | free | Human/agent-readable API docs |
|
|
33
|
+
|
|
34
|
+
### POST /timeseries
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
curl https://easy-predict.com/timeseries \
|
|
38
|
+
-H "Content-Type: application/json" \
|
|
39
|
+
-H "PAYMENT-SIGNATURE: <signed-x402-payload>" \
|
|
40
|
+
-d '{"series": [1.0, 2.3, 4.1, 6.8, 9.2], "context": "monthly revenue USD"}'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Response:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"prediction": 12.1,
|
|
48
|
+
"method": "linear",
|
|
49
|
+
"holdout_errors": {"linear": 0.05, "log1p-linear": 0.31, "last-delta": 0.05, "mean": 3.5},
|
|
50
|
+
"slope": 1.94,
|
|
51
|
+
"intercept": 0.12
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Model selection: holds out the last point, evaluates `linear`, `log1p-linear`, `last-delta`, and `mean` against it, retrains the winner on the full series.
|
|
56
|
+
|
|
57
|
+
### POST /anomaly-detection
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
curl https://easy-predict.com/anomaly-detection \
|
|
61
|
+
-H "Content-Type: application/json" \
|
|
62
|
+
-H "PAYMENT-SIGNATURE: <signed-x402-payload>" \
|
|
63
|
+
-d '{"series": [1.0, 2.3, 4.1, 6.8, 99.0], "threshold": 2.0}'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Response:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"anomalies": [{"index": 4, "value": 99.0, "z_score": 2.14}],
|
|
71
|
+
"method": "z-score",
|
|
72
|
+
"mean": 15.12,
|
|
73
|
+
"std": 39.18,
|
|
74
|
+
"threshold": 2.0
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Both endpoints accept an optional `"context"` string (max 200 chars) echoed back in the response.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## x402 payment flow
|
|
83
|
+
|
|
84
|
+
Omit the payment header to get a 402 with the full payment terms:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"x402Version": 2,
|
|
89
|
+
"error": "Payment Required",
|
|
90
|
+
"accepts": [{
|
|
91
|
+
"scheme": "exact",
|
|
92
|
+
"network": "eip155:8453",
|
|
93
|
+
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
94
|
+
"amount": "10000",
|
|
95
|
+
"payTo": "0xc99b83818c8865340AC55C45554f377f41c68DBC",
|
|
96
|
+
"maxTimeoutSeconds": 60
|
|
97
|
+
}]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
1. Parse `accepts[0]` from the 402 response
|
|
102
|
+
2. Sign a payment payload with your wallet
|
|
103
|
+
3. Base64-encode the signed JSON and retry with `PAYMENT-SIGNATURE: <encoded>`
|
|
104
|
+
4. The Cloudflare Worker verifies via `https://x402.org/facilitator` and settles on-chain
|
|
105
|
+
|
|
106
|
+
Payment: 10000 atomic units = $0.01 USDC (6 decimals) on Base mainnet.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## MCP server (Claude Desktop, Cursor, Windsurf)
|
|
111
|
+
|
|
112
|
+
The MCP server runs locally on your machine. Your wallet private key never leaves your device — it signs the x402 payment locally and only the signed header is sent to easy-predict.com.
|
|
113
|
+
|
|
114
|
+
Add to your MCP client config:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"mcpServers": {
|
|
119
|
+
"easy-predict": {
|
|
120
|
+
"command": "uvx",
|
|
121
|
+
"args": ["easy-predict-mcp"],
|
|
122
|
+
"env": {
|
|
123
|
+
"WALLET_PRIVATE_KEY": "0xYOUR_WALLET_PRIVATE_KEY"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Then ask Claude: *"Predict the next value for this series: 1.2, 2.4, 4.1, 6.8"* — it calls the tool, pays $0.01 USDC from your wallet automatically, and returns the forecast.
|
|
131
|
+
|
|
132
|
+
Listed on [Smithery](https://smithery.ai) — search `easy-predict` to install with one click.
|
|
133
|
+
|
|
134
|
+
Listed on [Smithery](https://smithery.ai) — search `easy-predict` to install with one click.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Agent integration (Python)
|
|
139
|
+
|
|
140
|
+
A working Claude agent that handles the full 402 → sign → retry loop autonomously:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pip install anthropic requests eth-account
|
|
144
|
+
ANTHROPIC_API_KEY=sk-... WALLET_PRIVATE_KEY=0x... python examples/demo_agent.py
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
[`examples/demo_agent.py`](examples/demo_agent.py) — uses Anthropic tool use. The same pattern works with LangChain, LlamaIndex, CrewAI, or AutoGen.
|
|
148
|
+
|
|
149
|
+
Demo mode (no wallet, against local server):
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
cd timeseries && python app.py &
|
|
153
|
+
ANTHROPIC_API_KEY=sk-... python examples/demo_agent.py
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Architecture
|
|
159
|
+
|
|
160
|
+
- **Cloudflare Worker** (`src/index.ts`) — edge runtime, rate limiting, full x402 verify+settle via the facilitator
|
|
161
|
+
- **Flask backend** (`timeseries/app.py`) — Python prediction logic, local dev server
|
|
162
|
+
- **Static assets** (`public/`) — splash page, `openapi.json`, `llms.txt`
|
|
163
|
+
|
|
164
|
+
The Worker handles all production traffic. The Flask app is used for local development and runs on port 8000.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Local development
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pip install -r requirements.txt
|
|
172
|
+
cd timeseries && python app.py # Flask on http://localhost:8000
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The local Flask server uses presence-only payment checking — any non-empty `PAYMENT-SIGNATURE` header is accepted. Useful for testing without a real wallet.
|
|
176
|
+
|
|
177
|
+
For the Cloudflare Worker:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npm install
|
|
181
|
+
npx wrangler dev # Worker on http://localhost:8787
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Repo structure
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
timeseries/app.py Flask backend (prediction + anomaly detection logic)
|
|
190
|
+
anomaly_detection/app.py Anomaly detection blueprint
|
|
191
|
+
src/index.ts Cloudflare Worker (edge runtime)
|
|
192
|
+
mcp_server/easy_predict_mcp/server.py MCP server — runs locally, signs payments on device
|
|
193
|
+
mcp_server/pyproject.toml PyPI package definition (easy-predict-mcp)
|
|
194
|
+
smithery.yaml Smithery registry config
|
|
195
|
+
public/
|
|
196
|
+
index.html Splash page
|
|
197
|
+
openapi.json OpenAPI 3.1 spec
|
|
198
|
+
llms.txt Human/agent-readable docs
|
|
199
|
+
examples/
|
|
200
|
+
demo_agent.py Claude agent integration demo with x402 payments
|
|
201
|
+
tests/ Test suite
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Configuration
|
|
207
|
+
|
|
208
|
+
Flask backend env vars (all optional):
|
|
209
|
+
|
|
210
|
+
| Variable | Default | Description |
|
|
211
|
+
|----------|---------|-------------|
|
|
212
|
+
| `PAY_TO_ADDRESS` | `0xc99b...` | Recipient wallet |
|
|
213
|
+
| `X402_ASSET` | USDC on Base | ERC-20 asset address |
|
|
214
|
+
| `X402_NETWORK` | `eip155:8453` | Chain ID |
|
|
215
|
+
| `X402_MAX_AMOUNT` | `10000` | Atomic units ($0.01) |
|
|
216
|
+
| `FACILITATOR_URL` | `https://x402.org/facilitator` | x402 facilitator |
|
|
217
|
+
| `PUBLIC_BASE_URL` | `https://easy-predict.com` | Base URL for resource URLs in 402 responses |
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
easy_predict_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
easy_predict_mcp/server.py,sha256=TuDsyKJka8OIC_keMYpfvuQw2m3-RJkDtDRVB3HgmP0,4229
|
|
3
|
+
easy_predict_mcp-1.0.0.dist-info/METADATA,sha256=AA6GCs2Z3Lu3b523aKycH7Uu3aoUJ8uADVFXfHDHYFo,6800
|
|
4
|
+
easy_predict_mcp-1.0.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
5
|
+
easy_predict_mcp-1.0.0.dist-info/entry_points.txt,sha256=sYGQI4OUlaqofi3Ddmd8N2WsBOakiGSLxe59mf2Ogg8,66
|
|
6
|
+
easy_predict_mcp-1.0.0.dist-info/RECORD,,
|