@pipeworx/mcp-manifold 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/package.json +20 -0
- package/server.json +18 -0
- package/src/index.ts +248 -0
- package/tsconfig.json +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pipeworx
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# mcp-manifold
|
|
2
|
+
|
|
3
|
+
Manifold Markets MCP.
|
|
4
|
+
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 865+ live data sources.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"manifold": {
|
|
20
|
+
"url": "https://gateway.pipeworx.io/manifold/mcp"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or connect to the full Pipeworx gateway for access to all 865+ data sources:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"pipeworx": {
|
|
32
|
+
"url": "https://gateway.pipeworx.io/mcp"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Using with ask_pipeworx
|
|
39
|
+
|
|
40
|
+
Instead of calling tools directly, you can ask questions in plain English:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
ask_pipeworx({ question: "your question about Manifold data" })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The gateway picks the right tool and fills the arguments automatically.
|
|
47
|
+
|
|
48
|
+
## More
|
|
49
|
+
|
|
50
|
+
- [All tools and guides](https://github.com/pipeworx-io/examples)
|
|
51
|
+
- [pipeworx.io](https://pipeworx.io)
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipeworx/mcp-manifold",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Manifold Markets MCP.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "manifold"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-manifold"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.pipeworx-io/manifold",
|
|
4
|
+
"title": "Manifold",
|
|
5
|
+
"description": "Manifold Markets MCP.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/manifold",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-manifold",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/manifold/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
interface McpToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface McpToolExport {
|
|
12
|
+
tools: McpToolDefinition[];
|
|
13
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
meter?: { credits: number };
|
|
15
|
+
cost?: Record<string, unknown>;
|
|
16
|
+
provider?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Manifold Markets MCP.
|
|
21
|
+
*
|
|
22
|
+
* Keyless play-money prediction market with tens of thousands of user-created
|
|
23
|
+
* markets on any topic (politics, tech, sports, crypto, AI, culture). Search
|
|
24
|
+
* markets by topic, get full market detail with multiple-choice answer
|
|
25
|
+
* probabilities, and surface the most-active open markets as an
|
|
26
|
+
* attention/demand signal. Complements the polymarket and kalshi packs as a
|
|
27
|
+
* third major prediction-market venue. Keyless.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
const BASE = 'https://api.manifold.markets/v0';
|
|
32
|
+
const UA = 'pipeworx/1.0 (+https://pipeworx.io)';
|
|
33
|
+
|
|
34
|
+
const tools: McpToolExport['tools'] = [
|
|
35
|
+
{
|
|
36
|
+
name: 'search_markets',
|
|
37
|
+
description:
|
|
38
|
+
'Search Manifold Markets prediction markets by topic or question text. Returns markets with current probability (for binary), trading volume, liquidity, bettor count, and resolution status. Manifold is a play-money venue with tens of thousands of user-created markets on any topic. Keyless.',
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
term: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'Topic or question text to search for, e.g. "2026 election", "AGI", "Bitcoin 100k".',
|
|
45
|
+
},
|
|
46
|
+
filter: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Market state filter: all | open | closed | resolved. Default "open".',
|
|
49
|
+
},
|
|
50
|
+
sort: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'Sort order: score | newest | liquidity | most-popular. Default "score".',
|
|
53
|
+
},
|
|
54
|
+
contract_type: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'Market type filter: ALL | BINARY | MULTIPLE_CHOICE. Default "ALL".',
|
|
57
|
+
},
|
|
58
|
+
limit: { type: 'number', description: 'Max markets to return (default 10, max 25).' },
|
|
59
|
+
},
|
|
60
|
+
required: ['term'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'get_market',
|
|
65
|
+
description:
|
|
66
|
+
'Get full detail for a single Manifold market by its URL slug or id. Provide exactly one of slug/id. For multiple-choice markets, returns each answer with its probability (top 12). Includes plain-text description, creator, volume, liquidity, and resolution. Keyless.',
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
slug: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description:
|
|
73
|
+
'Market URL slug — the part after manifold.markets/{user}/, e.g. "who-will-win-the-2026-california-gu".',
|
|
74
|
+
},
|
|
75
|
+
id: { type: 'string', description: 'Market id, e.g. "KmbNYfuOrUnI1w1GSHdb".' },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'top_markets',
|
|
81
|
+
description:
|
|
82
|
+
'Most-active open Manifold markets right now — a live attention/demand signal for what the prediction-market crowd is trading. Optionally narrow to a topic. Returns compact market records sorted by popularity. Keyless.',
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
limit: { type: 'number', description: 'Max markets to return (default 15, max 25).' },
|
|
87
|
+
term: { type: 'string', description: 'Optional topic to narrow to, e.g. "politics", "AI".' },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
94
|
+
try {
|
|
95
|
+
switch (name) {
|
|
96
|
+
case 'search_markets':
|
|
97
|
+
return searchMarkets(args);
|
|
98
|
+
case 'get_market':
|
|
99
|
+
return getMarket(args);
|
|
100
|
+
case 'top_markets':
|
|
101
|
+
return topMarkets(args);
|
|
102
|
+
default:
|
|
103
|
+
return { error: `Unknown tool: ${name}` };
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --- helpers ---------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/** epoch-ms → ISO string, null-safe. */
|
|
113
|
+
function isoMs(ms: unknown): string | null {
|
|
114
|
+
if (typeof ms !== 'number' || !isFinite(ms)) return null;
|
|
115
|
+
return new Date(ms).toISOString();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** probability (0-1) → percent rounded to 1dp, null-safe. */
|
|
119
|
+
function pct(p: unknown): number | null {
|
|
120
|
+
if (typeof p !== 'number' || !isFinite(p)) return null;
|
|
121
|
+
return Math.round(p * 1000) / 10;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function round0(n: unknown): number | null {
|
|
125
|
+
if (typeof n !== 'number' || !isFinite(n)) return null;
|
|
126
|
+
return Math.round(n);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Compact market mapping shared across all three tools. */
|
|
130
|
+
function mapMarket(raw: Record<string, unknown>): Record<string, unknown> {
|
|
131
|
+
const isBinary = raw.outcomeType === 'BINARY';
|
|
132
|
+
return {
|
|
133
|
+
id: raw.id,
|
|
134
|
+
question: raw.question,
|
|
135
|
+
url: raw.url,
|
|
136
|
+
type: raw.outcomeType,
|
|
137
|
+
probability_pct: isBinary ? pct(raw.probability) : null,
|
|
138
|
+
volume: round0(raw.volume),
|
|
139
|
+
total_liquidity: round0(raw.totalLiquidity),
|
|
140
|
+
unique_bettors: raw.uniqueBettorCount ?? null,
|
|
141
|
+
close_time: isoMs(raw.closeTime),
|
|
142
|
+
is_resolved: raw.isResolved ?? null,
|
|
143
|
+
resolution: raw.resolution ?? null,
|
|
144
|
+
creator: raw.creatorName ?? null,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Best-effort plain text from a Manifold description (string or rich-text doc). */
|
|
149
|
+
function descriptionText(raw: Record<string, unknown>): string | null {
|
|
150
|
+
// The slug/market endpoints often include a precomputed plain-text field.
|
|
151
|
+
if (typeof raw.textDescription === 'string' && raw.textDescription.trim()) {
|
|
152
|
+
return raw.textDescription.trim().slice(0, 500);
|
|
153
|
+
}
|
|
154
|
+
const d = raw.description;
|
|
155
|
+
if (typeof d === 'string') return d.trim().slice(0, 500) || null;
|
|
156
|
+
if (d && typeof d === 'object') {
|
|
157
|
+
const parts: string[] = [];
|
|
158
|
+
const walk = (node: unknown): void => {
|
|
159
|
+
if (!node || typeof node !== 'object') return;
|
|
160
|
+
const n = node as Record<string, unknown>;
|
|
161
|
+
if (typeof n.text === 'string') parts.push(n.text);
|
|
162
|
+
if (Array.isArray(n.content)) n.content.forEach(walk);
|
|
163
|
+
};
|
|
164
|
+
walk(d);
|
|
165
|
+
const text = parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
166
|
+
return text ? text.slice(0, 500) : null;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function fetchJson(url: string): Promise<unknown> {
|
|
172
|
+
return fetch(url, { headers: { Accept: 'application/json', 'User-Agent': UA } });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// --- tools -----------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
async function searchMarkets(args: Record<string, unknown>): Promise<unknown> {
|
|
178
|
+
const term = typeof args.term === 'string' ? args.term.trim() : '';
|
|
179
|
+
if (!term) return { error: 'provide a search term', term: args.term ?? null };
|
|
180
|
+
|
|
181
|
+
const filter = (typeof args.filter === 'string' && args.filter.trim()) || 'open';
|
|
182
|
+
const sort = (typeof args.sort === 'string' && args.sort.trim()) || 'score';
|
|
183
|
+
const contractType = (typeof args.contract_type === 'string' && args.contract_type.trim()) || 'ALL';
|
|
184
|
+
const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 25);
|
|
185
|
+
|
|
186
|
+
const qs = new URLSearchParams({
|
|
187
|
+
term,
|
|
188
|
+
filter,
|
|
189
|
+
sort,
|
|
190
|
+
contractType,
|
|
191
|
+
limit: String(limit),
|
|
192
|
+
});
|
|
193
|
+
const res = (await fetchJson(`${BASE}/search-markets?${qs}`)) as Response;
|
|
194
|
+
if (!res.ok) return { error: `manifold: ${res.status} ${(await res.text()).slice(0, 200)}` };
|
|
195
|
+
|
|
196
|
+
const arr = (await res.json()) as Array<Record<string, unknown>>;
|
|
197
|
+
const list = Array.isArray(arr) ? arr : [];
|
|
198
|
+
return { count: list.length, markets: list.map(mapMarket) };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function getMarket(args: Record<string, unknown>): Promise<unknown> {
|
|
202
|
+
const slug = typeof args.slug === 'string' ? args.slug.trim() : '';
|
|
203
|
+
const id = typeof args.id === 'string' ? args.id.trim() : '';
|
|
204
|
+
if (!slug && !id) return { error: 'provide exactly one of slug or id' };
|
|
205
|
+
if (slug && id) return { error: 'provide exactly one of slug or id, not both' };
|
|
206
|
+
|
|
207
|
+
const path = slug ? `slug/${encodeURIComponent(slug)}` : `market/${encodeURIComponent(id)}`;
|
|
208
|
+
const res = (await fetchJson(`${BASE}/${path}`)) as Response;
|
|
209
|
+
if (res.status === 404) return { error: 'market not found', slug: slug || null, id: id || null };
|
|
210
|
+
if (!res.ok) return { error: `manifold: ${res.status} ${(await res.text()).slice(0, 200)}` };
|
|
211
|
+
|
|
212
|
+
const m = (await res.json()) as Record<string, unknown>;
|
|
213
|
+
const out: Record<string, unknown> = {
|
|
214
|
+
...mapMarket(m),
|
|
215
|
+
created_time: isoMs(m.createdTime),
|
|
216
|
+
total_bettors: m.uniqueBettorCount ?? null,
|
|
217
|
+
description_text: descriptionText(m),
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
if (m.outcomeType === 'MULTIPLE_CHOICE' && Array.isArray(m.answers)) {
|
|
221
|
+
out.answers = (m.answers as Array<Record<string, unknown>>)
|
|
222
|
+
.map((a) => ({ text: a.text, probability_pct: pct(a.probability) }))
|
|
223
|
+
.sort((a, b) => (b.probability_pct ?? -1) - (a.probability_pct ?? -1))
|
|
224
|
+
.slice(0, 12);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function topMarkets(args: Record<string, unknown>): Promise<unknown> {
|
|
231
|
+
const limit = Math.min(Math.max(Number(args.limit) || 15, 1), 25);
|
|
232
|
+
const term = typeof args.term === 'string' ? args.term.trim() : '';
|
|
233
|
+
|
|
234
|
+
const qs = new URLSearchParams({
|
|
235
|
+
term,
|
|
236
|
+
sort: 'most-popular',
|
|
237
|
+
filter: 'open',
|
|
238
|
+
limit: String(limit),
|
|
239
|
+
});
|
|
240
|
+
const res = (await fetchJson(`${BASE}/search-markets?${qs}`)) as Response;
|
|
241
|
+
if (!res.ok) return { error: `manifold: ${res.status} ${(await res.text()).slice(0, 200)}` };
|
|
242
|
+
|
|
243
|
+
const arr = (await res.json()) as Array<Record<string, unknown>>;
|
|
244
|
+
const list = Array.isArray(arr) ? arr : [];
|
|
245
|
+
return { count: list.length, markets: list.map(mapMarket) };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export default { tools, callTool, meter: { credits: 1 } } satisfies McpToolExport;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|