probnode-mcp 0.1.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.
probnode_mcp/__init__.py
ADDED
probnode_mcp/server.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""ProbNode as MCP tools, for an AI agent working with Polymarket history.
|
|
2
|
+
|
|
3
|
+
{"mcpServers": {"probnode": {"command": "probnode-mcp",
|
|
4
|
+
"env": {"PROBNODE_API_KEY": "pn_..."}}}}
|
|
5
|
+
|
|
6
|
+
Every tool is one ProbNode API call through `probnode.Client`, so an agent sees exactly what a
|
|
7
|
+
customer's script sees: the same entitlements, the same quotas, the same refusals in words. Tools
|
|
8
|
+
return small JSON; bulk data is handed over as signed URLs (`sign_files`) or a capped range query
|
|
9
|
+
(`timeseries`), never streamed through the model's context.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from mcp.server.fastmcp import FastMCP
|
|
17
|
+
from probnode import Client, ProbNodeError
|
|
18
|
+
|
|
19
|
+
#: Rows a `timeseries` call returns to the model at most. The API caps a window at 200,000 rows;
|
|
20
|
+
#: that is a file, not context. More than this, the tool says so and suggests files instead.
|
|
21
|
+
MAX_ROWS_TO_MODEL = 500
|
|
22
|
+
|
|
23
|
+
mcp = FastMCP("probnode")
|
|
24
|
+
_client: Client | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def client() -> Client:
|
|
28
|
+
global _client
|
|
29
|
+
if _client is None:
|
|
30
|
+
_client = Client()
|
|
31
|
+
return _client
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _refusal(exc: ProbNodeError) -> dict[str, Any]:
|
|
35
|
+
return {"error": exc.detail, "status": exc.status}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@mcp.tool()
|
|
39
|
+
def datasets() -> Any:
|
|
40
|
+
"""Datasets ProbNode holds (books, trades, gaps, markets, quotes, ohlcv_1m), their formats,
|
|
41
|
+
whether a paid plan is needed (tier "tape") and the first and last UTC hour held."""
|
|
42
|
+
try:
|
|
43
|
+
return client().datasets()
|
|
44
|
+
except ProbNodeError as exc:
|
|
45
|
+
return _refusal(exc)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@mcp.tool()
|
|
49
|
+
def search_markets(query: str, limit: int = 20) -> Any:
|
|
50
|
+
"""Find Polymarket markets by words in their slug or title. Returns slugs, series, start and
|
|
51
|
+
end times, resolution and outcome token ids."""
|
|
52
|
+
try:
|
|
53
|
+
return client().markets(q=query, limit=min(max(limit, 1), 100))
|
|
54
|
+
except ProbNodeError as exc:
|
|
55
|
+
return _refusal(exc)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@mcp.tool()
|
|
59
|
+
def coverage(token_id: str, start: str | None = None, end: str | None = None) -> Any:
|
|
60
|
+
"""How completely one outcome token was recorded, hour by hour: recorded ms, gaps and uptime.
|
|
61
|
+
Times are ISO UTC. Hours marked implied were recorded whole (no gap touched them)."""
|
|
62
|
+
try:
|
|
63
|
+
return client().coverage(token_id, start, end)
|
|
64
|
+
except ProbNodeError as exc:
|
|
65
|
+
return _refusal(exc)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@mcp.tool()
|
|
69
|
+
def list_files(
|
|
70
|
+
dataset: str,
|
|
71
|
+
format: str,
|
|
72
|
+
start: str,
|
|
73
|
+
end: str,
|
|
74
|
+
granularity: str = "hour",
|
|
75
|
+
market: list[str] | None = None,
|
|
76
|
+
) -> Any:
|
|
77
|
+
"""Files for a dataset and format over [start, end) (ISO UTC, on the hour, at most 31 days):
|
|
78
|
+
object path, rows, bytes and sha256 each, plus totals. granularity "hour" (all markets per
|
|
79
|
+
file) or "market_day" (one market, one UTC day). Needs an API key."""
|
|
80
|
+
try:
|
|
81
|
+
files = client().files(
|
|
82
|
+
dataset, format, start, end, granularity=granularity, market=market or ()
|
|
83
|
+
)
|
|
84
|
+
except ProbNodeError as exc:
|
|
85
|
+
return _refusal(exc)
|
|
86
|
+
return {
|
|
87
|
+
"total_files": len(files),
|
|
88
|
+
"total_bytes": sum(f.bytes for f in files),
|
|
89
|
+
"total_rows": sum(f.rows for f in files),
|
|
90
|
+
"files": [
|
|
91
|
+
{
|
|
92
|
+
"object_path": f.object_path,
|
|
93
|
+
"rows": f.rows,
|
|
94
|
+
"bytes": f.bytes,
|
|
95
|
+
"sha256": f.sha256,
|
|
96
|
+
"market": f.market,
|
|
97
|
+
}
|
|
98
|
+
for f in files[:200]
|
|
99
|
+
],
|
|
100
|
+
"truncated": len(files) > 200,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@mcp.tool()
|
|
105
|
+
def sign_files(object_paths: list[str]) -> Any:
|
|
106
|
+
"""Short-lived download URLs (about 15 minutes) for object paths from list_files. Each counts
|
|
107
|
+
against the account's daily signed-URL limit; tape data needs a paid plan."""
|
|
108
|
+
try:
|
|
109
|
+
return client().sign(object_paths)
|
|
110
|
+
except ProbNodeError as exc:
|
|
111
|
+
return _refusal(exc)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@mcp.tool()
|
|
115
|
+
def timeseries(dataset: str, market: str, start: str, end: str) -> Any:
|
|
116
|
+
"""Rows of one dataset (e.g. quotes, trades, ohlcv_1m) for one market over a short window,
|
|
117
|
+
as JSON objects. For more than a few hundred rows use list_files + sign_files instead."""
|
|
118
|
+
try:
|
|
119
|
+
rows = client().timeseries(dataset, market, start, end, format="ndjson")
|
|
120
|
+
except ProbNodeError as exc:
|
|
121
|
+
return _refusal(exc)
|
|
122
|
+
if len(rows) > MAX_ROWS_TO_MODEL:
|
|
123
|
+
return {
|
|
124
|
+
"rows": rows[:MAX_ROWS_TO_MODEL],
|
|
125
|
+
"returned": MAX_ROWS_TO_MODEL,
|
|
126
|
+
"total": len(rows),
|
|
127
|
+
"note": "more rows than fit here; narrow the window or download files",
|
|
128
|
+
}
|
|
129
|
+
return {"rows": rows, "returned": len(rows), "total": len(rows)}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@mcp.tool()
|
|
133
|
+
def samples() -> Any:
|
|
134
|
+
"""Free sample files - one market-day per dataset and format - with URLs that need no key."""
|
|
135
|
+
try:
|
|
136
|
+
return client().samples()
|
|
137
|
+
except ProbNodeError as exc:
|
|
138
|
+
return _refusal(exc)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def main() -> None:
|
|
142
|
+
mcp.run()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
if __name__ == "__main__":
|
|
146
|
+
main()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: probnode-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for ProbNode: Polymarket order books, trades and gaps for AI agents
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: mcp<2,>=1.2
|
|
8
|
+
Requires-Dist: probnode-sdk>=0.1.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
11
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# probnode-mcp
|
|
15
|
+
|
|
16
|
+
An [MCP](https://modelcontextprotocol.io) server that gives an AI agent ProbNode's Polymarket
|
|
17
|
+
history: find markets, check recording coverage, list files with sizes and sha256, sign download
|
|
18
|
+
links, and pull small windows of rows.
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pip install ./packages/probnode ./packages/probnode-mcp # from a checkout; on PyPI soon
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{"mcpServers": {"probnode": {"command": "probnode-mcp", "env": {"PROBNODE_API_KEY": "pmd_..."}}}}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Tools: `datasets`, `search_markets`, `coverage`, `list_files`, `sign_files`, `timeseries`,
|
|
29
|
+
`samples`. Every call goes through the same API, plan and limits as the `probnode` SDK; refusals
|
|
30
|
+
come back as `{"error": ..., "status": ...}`. Bulk data travels as signed URLs, not through the
|
|
31
|
+
model's context - `timeseries` returns at most 500 rows.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
probnode_mcp/__init__.py,sha256=D-nj4UIMAHgV6wXhm9hIo8YPmwbQvJlzNzzMkCm8ZRk,74
|
|
2
|
+
probnode_mcp/server.py,sha256=FxowPq_NyM2TC4lSJBh85CbOIhdH_WcVx2hquNQggG0,4854
|
|
3
|
+
probnode_mcp-0.1.0.dist-info/METADATA,sha256=fxvLebKsx47JyhHd4IaAjP8cee3aBkMKYg8noM0QAt0,1188
|
|
4
|
+
probnode_mcp-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
5
|
+
probnode_mcp-0.1.0.dist-info/entry_points.txt,sha256=LJNUXpTSoQT3X7_ttyp1Ph4BnrVWPlwnqf1zdVBSe2w,58
|
|
6
|
+
probnode_mcp-0.1.0.dist-info/RECORD,,
|