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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ easy-predict-mcp = easy_predict_mcp.server:main