@coinrithm/mcp-trading 0.7.3 → 0.7.4
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/CHANGELOG.md +171 -158
- package/README.md +277 -240
- package/dist/agent/cli.js +9 -5
- package/dist/agent/observe.d.ts +4 -0
- package/dist/agent/observe.js +23 -4
- package/dist/client.d.ts +18 -0
- package/dist/client.js +18 -0
- package/dist/http.js +3 -1
- package/dist/tools.d.ts +22 -0
- package/dist/tools.js +582 -10
- package/package.json +86 -86
package/dist/tools.js
CHANGED
|
@@ -172,6 +172,391 @@ function present(result) {
|
|
|
172
172
|
isError: !result.ok,
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
|
+
function isJsonRecord(value) {
|
|
176
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
177
|
+
}
|
|
178
|
+
function pick(record, keys) {
|
|
179
|
+
return Object.fromEntries(keys
|
|
180
|
+
.filter((key) => record[key] !== undefined)
|
|
181
|
+
.map((key) => [key, record[key]]));
|
|
182
|
+
}
|
|
183
|
+
function probability(value) {
|
|
184
|
+
if (typeof value !== "number" && typeof value !== "string")
|
|
185
|
+
return null;
|
|
186
|
+
if (typeof value === "string" && value.trim() === "")
|
|
187
|
+
return null;
|
|
188
|
+
const parsed = Number(value);
|
|
189
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
190
|
+
}
|
|
191
|
+
const EVENT_SUMMARY_FIELDS = [
|
|
192
|
+
"id",
|
|
193
|
+
"slug",
|
|
194
|
+
"title",
|
|
195
|
+
"status",
|
|
196
|
+
"startDate",
|
|
197
|
+
"endDate",
|
|
198
|
+
"resolvedAt",
|
|
199
|
+
"freshness",
|
|
200
|
+
"volume",
|
|
201
|
+
"volume24h",
|
|
202
|
+
"liquidity",
|
|
203
|
+
"priceChange24h",
|
|
204
|
+
"priceChange7d",
|
|
205
|
+
"bestBid",
|
|
206
|
+
"bestAsk",
|
|
207
|
+
"spread",
|
|
208
|
+
"marketsCount",
|
|
209
|
+
"referenceProbability",
|
|
210
|
+
"crossPlatform",
|
|
211
|
+
"decisionSupport",
|
|
212
|
+
"quality",
|
|
213
|
+
];
|
|
214
|
+
const OUTCOME_SUMMARY_FIELDS = [
|
|
215
|
+
"externalMarketId",
|
|
216
|
+
"name",
|
|
217
|
+
"probability",
|
|
218
|
+
"priceChange24h",
|
|
219
|
+
];
|
|
220
|
+
const WHALE_TRADE_FIELDS = [
|
|
221
|
+
"source",
|
|
222
|
+
"sourceName",
|
|
223
|
+
"eventSlug",
|
|
224
|
+
"eventTitle",
|
|
225
|
+
"wallet",
|
|
226
|
+
"traderName",
|
|
227
|
+
"side",
|
|
228
|
+
"outcome",
|
|
229
|
+
"marketQuestion",
|
|
230
|
+
"usdValue",
|
|
231
|
+
"price",
|
|
232
|
+
"sourceMarketRef",
|
|
233
|
+
"nativeValue",
|
|
234
|
+
"nativeCurrency",
|
|
235
|
+
"valueBasis",
|
|
236
|
+
"evidenceType",
|
|
237
|
+
"evidenceRef",
|
|
238
|
+
"evidenceUrl",
|
|
239
|
+
"availability",
|
|
240
|
+
"observedAt",
|
|
241
|
+
"latencySeconds",
|
|
242
|
+
"tradedAt",
|
|
243
|
+
];
|
|
244
|
+
function boundedText(value, maxLength) {
|
|
245
|
+
if (typeof value !== "string" || value.length <= maxLength)
|
|
246
|
+
return value;
|
|
247
|
+
return `${value.slice(0, maxLength - 1)}…`;
|
|
248
|
+
}
|
|
249
|
+
function compactWhaleTrade(value) {
|
|
250
|
+
return isJsonRecord(value) ? pick(value, WHALE_TRADE_FIELDS) : value;
|
|
251
|
+
}
|
|
252
|
+
function eventSummary(value) {
|
|
253
|
+
if (!isJsonRecord(value))
|
|
254
|
+
return value;
|
|
255
|
+
const source = isJsonRecord(value.source)
|
|
256
|
+
? pick(value.source, ["id", "name", "kind", "supportsTrading"])
|
|
257
|
+
: value.source;
|
|
258
|
+
const outcomes = Array.isArray(value.outcomes)
|
|
259
|
+
? value.outcomes
|
|
260
|
+
.map((outcome, index) => ({ outcome, index }))
|
|
261
|
+
.sort((left, right) => {
|
|
262
|
+
const a = isJsonRecord(left.outcome)
|
|
263
|
+
? probability(left.outcome.probability)
|
|
264
|
+
: null;
|
|
265
|
+
const b = isJsonRecord(right.outcome)
|
|
266
|
+
? probability(right.outcome.probability)
|
|
267
|
+
: null;
|
|
268
|
+
const aValid = a !== null;
|
|
269
|
+
const bValid = b !== null;
|
|
270
|
+
if (aValid !== bValid)
|
|
271
|
+
return aValid ? -1 : 1;
|
|
272
|
+
return aValid && bValid && a !== b
|
|
273
|
+
? b - a
|
|
274
|
+
: left.index - right.index;
|
|
275
|
+
})
|
|
276
|
+
.slice(0, 5)
|
|
277
|
+
.map(({ outcome }) => isJsonRecord(outcome)
|
|
278
|
+
? pick(outcome, OUTCOME_SUMMARY_FIELDS)
|
|
279
|
+
: outcome)
|
|
280
|
+
: [];
|
|
281
|
+
return {
|
|
282
|
+
...pick(value, EVENT_SUMMARY_FIELDS),
|
|
283
|
+
source,
|
|
284
|
+
outcomeCount: Array.isArray(value.outcomes) ? value.outcomes.length : 0,
|
|
285
|
+
outcomes,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Keep keyless discovery calls small enough for an agent context window.
|
|
290
|
+
* Full event evidence remains available from pm_data_event.
|
|
291
|
+
*/
|
|
292
|
+
export function compactPublicPmOverview(data) {
|
|
293
|
+
if (!isJsonRecord(data))
|
|
294
|
+
return data;
|
|
295
|
+
const highlights = isJsonRecord(data.highlights)
|
|
296
|
+
? Object.fromEntries(Object.entries(data.highlights).map(([key, value]) => [
|
|
297
|
+
key,
|
|
298
|
+
Array.isArray(value) ? value.map(eventSummary) : value,
|
|
299
|
+
]))
|
|
300
|
+
: data.highlights;
|
|
301
|
+
return {
|
|
302
|
+
...pick(data, [
|
|
303
|
+
"stats",
|
|
304
|
+
"categories",
|
|
305
|
+
"bySource",
|
|
306
|
+
"byCategory",
|
|
307
|
+
"updatedAt",
|
|
308
|
+
]),
|
|
309
|
+
highlights,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
export function compactPublicPmEvents(data) {
|
|
313
|
+
if (!isJsonRecord(data))
|
|
314
|
+
return data;
|
|
315
|
+
return {
|
|
316
|
+
...data,
|
|
317
|
+
data: Array.isArray(data.data) ? data.data.map(eventSummary) : data.data,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function compactSnapshot(value) {
|
|
321
|
+
if (!isJsonRecord(value))
|
|
322
|
+
return value;
|
|
323
|
+
return {
|
|
324
|
+
...pick(value, [
|
|
325
|
+
"id",
|
|
326
|
+
"capturedAt",
|
|
327
|
+
"observedAt",
|
|
328
|
+
"timestamp",
|
|
329
|
+
"asOf",
|
|
330
|
+
"probability",
|
|
331
|
+
"volume",
|
|
332
|
+
"volume24h",
|
|
333
|
+
"liquidity",
|
|
334
|
+
"bestBid",
|
|
335
|
+
"bestAsk",
|
|
336
|
+
"spread",
|
|
337
|
+
"outcome",
|
|
338
|
+
"externalMarketId",
|
|
339
|
+
]),
|
|
340
|
+
...(Array.isArray(value.outcomes)
|
|
341
|
+
? {
|
|
342
|
+
outcomeCount: value.outcomes.length,
|
|
343
|
+
outcomes: value.outcomes
|
|
344
|
+
.slice(0, 10)
|
|
345
|
+
.map((outcome) => isJsonRecord(outcome)
|
|
346
|
+
? pick(outcome, OUTCOME_SUMMARY_FIELDS)
|
|
347
|
+
: outcome),
|
|
348
|
+
}
|
|
349
|
+
: {}),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function compactComparison(value) {
|
|
353
|
+
if (!isJsonRecord(value))
|
|
354
|
+
return value;
|
|
355
|
+
const outcomes = Array.isArray(value.outcomes) ? value.outcomes : [];
|
|
356
|
+
const shared = outcomes.filter((outcome) => isJsonRecord(outcome) &&
|
|
357
|
+
(outcome.isShared === true ||
|
|
358
|
+
(outcome.presentInA === true && outcome.presentInB === true)));
|
|
359
|
+
const candidates = shared.length > 0 ? shared : outcomes;
|
|
360
|
+
const ranked = candidates
|
|
361
|
+
.map((outcome, index) => ({ outcome, index }))
|
|
362
|
+
.sort((left, right) => {
|
|
363
|
+
const a = isJsonRecord(left.outcome)
|
|
364
|
+
? Math.abs(probability(left.outcome.deltaPoints) ?? -1)
|
|
365
|
+
: -1;
|
|
366
|
+
const b = isJsonRecord(right.outcome)
|
|
367
|
+
? Math.abs(probability(right.outcome.deltaPoints) ?? -1)
|
|
368
|
+
: -1;
|
|
369
|
+
return a !== b ? b - a : left.index - right.index;
|
|
370
|
+
})
|
|
371
|
+
.slice(0, 5)
|
|
372
|
+
.map(({ outcome }) => isJsonRecord(outcome)
|
|
373
|
+
? pick(outcome, [
|
|
374
|
+
"key",
|
|
375
|
+
"label",
|
|
376
|
+
"eventAProbability",
|
|
377
|
+
"eventBProbability",
|
|
378
|
+
"deltaPoints",
|
|
379
|
+
"presentInA",
|
|
380
|
+
"presentInB",
|
|
381
|
+
"isShared",
|
|
382
|
+
])
|
|
383
|
+
: outcome);
|
|
384
|
+
return {
|
|
385
|
+
summary: value.summary,
|
|
386
|
+
outcomeCount: outcomes.length,
|
|
387
|
+
outcomes: ranked,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function compactCrossSourceMatch(value) {
|
|
391
|
+
if (!isJsonRecord(value))
|
|
392
|
+
return value;
|
|
393
|
+
return {
|
|
394
|
+
...pick(value, [
|
|
395
|
+
"matchId",
|
|
396
|
+
"confidence",
|
|
397
|
+
"matchMethod",
|
|
398
|
+
"recommendedSourceId",
|
|
399
|
+
"divergence",
|
|
400
|
+
]),
|
|
401
|
+
comparison: compactComparison(value.comparison),
|
|
402
|
+
event: eventSummary(value.event),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Default event detail for agents: enough provenance and comparison evidence
|
|
407
|
+
* to reason safely without recursively spending an entire context window.
|
|
408
|
+
* Callers can explicitly request detail=full for the untouched API record.
|
|
409
|
+
*/
|
|
410
|
+
export function compactPublicPmEvent(data) {
|
|
411
|
+
if (!isJsonRecord(data))
|
|
412
|
+
return data;
|
|
413
|
+
const rawEvent = isJsonRecord(data.event) ? data.event : null;
|
|
414
|
+
const summary = rawEvent ? eventSummary(rawEvent) : data.event;
|
|
415
|
+
const event = isJsonRecord(summary)
|
|
416
|
+
? {
|
|
417
|
+
...summary,
|
|
418
|
+
...(rawEvent
|
|
419
|
+
? {
|
|
420
|
+
image: rawEvent.image,
|
|
421
|
+
externalUrl: rawEvent.externalUrl,
|
|
422
|
+
description: boundedText(rawEvent.description, 1_200),
|
|
423
|
+
resolutionCriteria: boundedText(rawEvent.resolutionCriteria, 2_000),
|
|
424
|
+
forecasting: rawEvent.forecasting,
|
|
425
|
+
topics: Array.isArray(rawEvent.topics)
|
|
426
|
+
? rawEvent.topics.slice(0, 20)
|
|
427
|
+
: rawEvent.topics,
|
|
428
|
+
directRelatedCoins: Array.isArray(rawEvent.directRelatedCoins)
|
|
429
|
+
? rawEvent.directRelatedCoins.slice(0, 20)
|
|
430
|
+
: rawEvent.directRelatedCoins,
|
|
431
|
+
indirectRelatedCoins: Array.isArray(rawEvent.indirectRelatedCoins)
|
|
432
|
+
? rawEvent.indirectRelatedCoins.slice(0, 20)
|
|
433
|
+
: rawEvent.indirectRelatedCoins,
|
|
434
|
+
}
|
|
435
|
+
: {}),
|
|
436
|
+
}
|
|
437
|
+
: summary;
|
|
438
|
+
const relatedEvents = Array.isArray(data.relatedEvents)
|
|
439
|
+
? data.relatedEvents.slice(0, 5).map(eventSummary)
|
|
440
|
+
: data.relatedEvents;
|
|
441
|
+
const crossSourceMatches = Array.isArray(data.crossSourceMatches)
|
|
442
|
+
? data.crossSourceMatches
|
|
443
|
+
.map((match, index) => ({ match, index }))
|
|
444
|
+
.sort((left, right) => {
|
|
445
|
+
const a = isJsonRecord(left.match)
|
|
446
|
+
? probability(left.match.confidence)
|
|
447
|
+
: null;
|
|
448
|
+
const b = isJsonRecord(right.match)
|
|
449
|
+
? probability(right.match.confidence)
|
|
450
|
+
: null;
|
|
451
|
+
return a !== b && a !== null && b !== null
|
|
452
|
+
? b - a
|
|
453
|
+
: left.index - right.index;
|
|
454
|
+
})
|
|
455
|
+
.slice(0, 5)
|
|
456
|
+
.map(({ match }) => compactCrossSourceMatch(match))
|
|
457
|
+
: data.crossSourceMatches;
|
|
458
|
+
return {
|
|
459
|
+
...pick(data, ["resolution", "agentsTradingMarket"]),
|
|
460
|
+
event,
|
|
461
|
+
snapshotCount: Array.isArray(data.snapshots) ? data.snapshots.length : 0,
|
|
462
|
+
snapshots: Array.isArray(data.snapshots)
|
|
463
|
+
? data.snapshots.slice(-20).map(compactSnapshot)
|
|
464
|
+
: data.snapshots,
|
|
465
|
+
relatedEventCount: Array.isArray(data.relatedEvents)
|
|
466
|
+
? data.relatedEvents.length
|
|
467
|
+
: 0,
|
|
468
|
+
relatedEvents,
|
|
469
|
+
crossSourceMatchCount: Array.isArray(data.crossSourceMatches)
|
|
470
|
+
? data.crossSourceMatches.length
|
|
471
|
+
: 0,
|
|
472
|
+
crossSourceMatches,
|
|
473
|
+
volumeHistory: Array.isArray(data.volumeHistory)
|
|
474
|
+
? data.volumeHistory.slice(-90)
|
|
475
|
+
: data.volumeHistory,
|
|
476
|
+
relatedNews: Array.isArray(data.relatedNews)
|
|
477
|
+
? data.relatedNews.slice(0, 5)
|
|
478
|
+
: data.relatedNews,
|
|
479
|
+
topicRelatedCoins: Array.isArray(data.topicRelatedCoins)
|
|
480
|
+
? data.topicRelatedCoins.slice(0, 20)
|
|
481
|
+
: data.topicRelatedCoins,
|
|
482
|
+
recentWhaleTrades: Array.isArray(data.recentWhaleTrades)
|
|
483
|
+
? data.recentWhaleTrades.slice(0, 5).map(compactWhaleTrade)
|
|
484
|
+
: data.recentWhaleTrades,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
export function compactPublicPmWhales(data, limit) {
|
|
488
|
+
if (!isJsonRecord(data))
|
|
489
|
+
return data;
|
|
490
|
+
const trades = Array.isArray(data.trades)
|
|
491
|
+
? data.trades.slice(0, limit).map(compactWhaleTrade)
|
|
492
|
+
: data.trades;
|
|
493
|
+
return { ...data, trades };
|
|
494
|
+
}
|
|
495
|
+
const MATCH_PAIR_FIELDS = [
|
|
496
|
+
"matchId",
|
|
497
|
+
"confidence",
|
|
498
|
+
"matchMethod",
|
|
499
|
+
"recommendedSourceId",
|
|
500
|
+
"divergence",
|
|
501
|
+
];
|
|
502
|
+
function compactMatchPair(value) {
|
|
503
|
+
if (!isJsonRecord(value))
|
|
504
|
+
return value;
|
|
505
|
+
return {
|
|
506
|
+
...pick(value, MATCH_PAIR_FIELDS),
|
|
507
|
+
comparison: compactComparison(value.comparison),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function compactMatchCluster(value) {
|
|
511
|
+
if (!isJsonRecord(value))
|
|
512
|
+
return value;
|
|
513
|
+
const events = Array.isArray(value.events)
|
|
514
|
+
? value.events.map(eventSummary)
|
|
515
|
+
: value.events;
|
|
516
|
+
const comparisons = Array.isArray(value.comparisons)
|
|
517
|
+
? value.comparisons.map((comparison) => isJsonRecord(comparison)
|
|
518
|
+
? {
|
|
519
|
+
eventId: comparison.eventId,
|
|
520
|
+
pair: compactMatchPair(comparison.pair),
|
|
521
|
+
}
|
|
522
|
+
: comparison)
|
|
523
|
+
: value.comparisons;
|
|
524
|
+
return {
|
|
525
|
+
...pick(value, [
|
|
526
|
+
"clusterId",
|
|
527
|
+
"primaryEventId",
|
|
528
|
+
"title",
|
|
529
|
+
"referenceProbability",
|
|
530
|
+
"maxOverallGap",
|
|
531
|
+
"maxOutcomeGap",
|
|
532
|
+
"maxConfidence",
|
|
533
|
+
]),
|
|
534
|
+
eventCount: Array.isArray(value.events) ? value.events.length : 0,
|
|
535
|
+
events,
|
|
536
|
+
comparisons,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Keep cross-venue disagreement clusters small enough for an agent context
|
|
541
|
+
* window: each event is reduced to eventSummary (drops descriptions, images,
|
|
542
|
+
* sparklines) and each pairwise comparison keeps only its top-5 highest-delta
|
|
543
|
+
* shared outcomes (compactComparison) — the same bounding pm_data_event
|
|
544
|
+
* applies to crossSourceMatches. Verified live: a 5-cluster page drops from
|
|
545
|
+
* ~466KB to ~48KB.
|
|
546
|
+
*/
|
|
547
|
+
export function compactPublicPmDisagreements(data) {
|
|
548
|
+
if (!isJsonRecord(data))
|
|
549
|
+
return data;
|
|
550
|
+
return {
|
|
551
|
+
...pick(data, ["total", "hasMore", "pagination", "meta"]),
|
|
552
|
+
data: Array.isArray(data.data)
|
|
553
|
+
? data.data.map(compactMatchCluster)
|
|
554
|
+
: data.data,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function mapSuccessfulBody(result, transform) {
|
|
558
|
+
return result.ok ? { ...result, data: transform(result.data) } : result;
|
|
559
|
+
}
|
|
175
560
|
export function registerTools(server, client) {
|
|
176
561
|
// ---------------- identity ----------------
|
|
177
562
|
server.registerTool("whoami", {
|
|
@@ -1043,7 +1428,8 @@ export function registerTools(server, client) {
|
|
|
1043
1428
|
"closed market counts, total volume, 24h volume, and liquidity " +
|
|
1044
1429
|
"aggregated across all 11 venues (Polymarket, Kalshi, Rothera, " +
|
|
1045
1430
|
"Limitless, Smarkets, Manifold, Metaculus, PredictIt, Futuur, Myriad, ForecastEx), plus market " +
|
|
1046
|
-
"highlights
|
|
1431
|
+
"highlights in a compact discovery shape. Use pm_data_event for full " +
|
|
1432
|
+
"event evidence. Freshness is SOURCE-AWARE — each venue ingests " +
|
|
1047
1433
|
"independently; per-venue health (freshness tier, lag, stale reason) " +
|
|
1048
1434
|
"is at /api/prediction-markets/sources/health. Volume is " +
|
|
1049
1435
|
"reported on each venue's own basis (see the methodology at " +
|
|
@@ -1058,7 +1444,7 @@ export function registerTools(server, client) {
|
|
|
1058
1444
|
},
|
|
1059
1445
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
1060
1446
|
annotations: readOnlyAnnotations("Cross-venue prediction-market statistics"),
|
|
1061
|
-
}, async ({ fiat }) => present(await client.getPublicPmOverview({ fiat })));
|
|
1447
|
+
}, async ({ fiat }) => present(mapSuccessfulBody(await client.getPublicPmOverview({ fiat }), compactPublicPmOverview)));
|
|
1062
1448
|
server.registerTool("pm_data_events", {
|
|
1063
1449
|
title: "Search prediction markets across all venues",
|
|
1064
1450
|
description: "Free public search over prediction-market events across ALL 11 " +
|
|
@@ -1066,6 +1452,8 @@ export function registerTools(server, client) {
|
|
|
1066
1452
|
"Manifold, Metaculus, PredictIt, Futuur, Myriad, ForecastEx) — broader than discover_pm_markets, which is " +
|
|
1067
1453
|
"scoped to the paper-tradeable venues. Returns titles, probabilities, " +
|
|
1068
1454
|
"volume/liquidity, status, and source per event, plus " +
|
|
1455
|
+
"the five highest-probability outcomes and the full outcome count. " +
|
|
1456
|
+
"Use pm_data_event for all outcomes and full evidence. Also returns " +
|
|
1069
1457
|
"referenceProbability when present (CoinRithm's canonical cross-venue " +
|
|
1070
1458
|
"number for open events matched across venues — probability, " +
|
|
1071
1459
|
"venueCount, spreadPoints, and outcomeName for multi-outcome " +
|
|
@@ -1106,7 +1494,7 @@ export function registerTools(server, client) {
|
|
|
1106
1494
|
},
|
|
1107
1495
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
1108
1496
|
annotations: readOnlyAnnotations("Search prediction markets across all venues"),
|
|
1109
|
-
}, async ({ q, source, status, sort, limit, offset, fiat }) => present(await client.listPublicPmEvents({
|
|
1497
|
+
}, async ({ q, source, status, sort, limit, offset, fiat }) => present(mapSuccessfulBody(await client.listPublicPmEvents({
|
|
1110
1498
|
q,
|
|
1111
1499
|
source,
|
|
1112
1500
|
status,
|
|
@@ -1114,9 +1502,9 @@ export function registerTools(server, client) {
|
|
|
1114
1502
|
limit,
|
|
1115
1503
|
offset,
|
|
1116
1504
|
fiat,
|
|
1117
|
-
})));
|
|
1505
|
+
}), compactPublicPmEvents)));
|
|
1118
1506
|
server.registerTool("pm_data_event", {
|
|
1119
|
-
title: "Get
|
|
1507
|
+
title: "Get prediction-market event detail",
|
|
1120
1508
|
description: "Free public detail for one prediction-market event by venue + slug: " +
|
|
1121
1509
|
"outcomes with probabilities, price snapshots, resolution evidence, " +
|
|
1122
1510
|
"crossSourceMatches (the SAME real-world question priced on other " +
|
|
@@ -1128,6 +1516,9 @@ export function registerTools(server, client) {
|
|
|
1128
1516
|
"recent whale trades on the event, related events, related news, and " +
|
|
1129
1517
|
"volumeHistory when present (daily volume points captured since " +
|
|
1130
1518
|
"2026-07-02 — read the event's volume trend directly from it). " +
|
|
1519
|
+
"The default summary bounds outcomes, related events, matches and tape " +
|
|
1520
|
+
"for agent context windows while preserving counts and core evidence. " +
|
|
1521
|
+
"Set detail=full only when the untouched provider-rich record is needed. " +
|
|
1131
1522
|
"This is the cross-venue research view; for tradability use pm_quote. " +
|
|
1132
1523
|
"No API key required.",
|
|
1133
1524
|
inputSchema: {
|
|
@@ -1140,20 +1531,201 @@ export function registerTools(server, client) {
|
|
|
1140
1531
|
.string()
|
|
1141
1532
|
.optional()
|
|
1142
1533
|
.describe("Fiat currency code for monetary figures (default usd)."),
|
|
1534
|
+
detail: z
|
|
1535
|
+
.enum(["summary", "full"])
|
|
1536
|
+
.optional()
|
|
1537
|
+
.describe("Response detail: bounded summary (default) or untouched full record."),
|
|
1143
1538
|
},
|
|
1144
1539
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
1145
|
-
annotations: readOnlyAnnotations("Get
|
|
1146
|
-
}, async ({ source, slug, fiat }) =>
|
|
1540
|
+
annotations: readOnlyAnnotations("Get prediction-market event detail"),
|
|
1541
|
+
}, async ({ source, slug, fiat, detail }) => {
|
|
1542
|
+
const result = await client.getPublicPmEvent(source, slug, { fiat });
|
|
1543
|
+
return present(detail === "full"
|
|
1544
|
+
? result
|
|
1545
|
+
: mapSuccessfulBody(result, compactPublicPmEvent));
|
|
1546
|
+
});
|
|
1147
1547
|
server.registerTool("pm_data_whales", {
|
|
1148
1548
|
title: "Get latest prediction-market whale trades",
|
|
1149
1549
|
description: "Free public tape of the latest large prediction-market trades " +
|
|
1150
|
-
"(roughly $1k+ notional) across venues, newest first
|
|
1550
|
+
"(roughly $1k+ notional) across venues, newest first: side, " +
|
|
1151
1551
|
"outcome, USD value, price, market question, and the event it printed " +
|
|
1152
1552
|
"on. Polymarket rows are wallet-attributed; Kalshi rows are anonymized " +
|
|
1153
1553
|
"exchange prints. A large print is information, not a recommendation. " +
|
|
1154
1554
|
"No API key required.",
|
|
1155
|
-
inputSchema: {
|
|
1555
|
+
inputSchema: {
|
|
1556
|
+
limit: z
|
|
1557
|
+
.number()
|
|
1558
|
+
.int()
|
|
1559
|
+
.min(1)
|
|
1560
|
+
.max(50)
|
|
1561
|
+
.optional()
|
|
1562
|
+
.describe("Max rows (1-50, default 10)."),
|
|
1563
|
+
},
|
|
1156
1564
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
1157
1565
|
annotations: readOnlyAnnotations("Get latest prediction-market whale trades"),
|
|
1158
|
-
}, async () => present(await client.getPublicPmWhales()));
|
|
1566
|
+
}, async ({ limit }) => present(mapSuccessfulBody(await client.getPublicPmWhales(), (data) => compactPublicPmWhales(data, limit ?? 10))));
|
|
1567
|
+
server.registerTool("pm_data_disagreements", {
|
|
1568
|
+
title: "Cross-venue disagreement clusters",
|
|
1569
|
+
description: "Free public cross-venue disagreement clusters: prediction-market " +
|
|
1570
|
+
"events CoinRithm has matched as the SAME real-world question across " +
|
|
1571
|
+
"2+ venues (approved cross-source matches), graph-clustered so one row " +
|
|
1572
|
+
"covers every venue tracking that question. Each pairwise comparison " +
|
|
1573
|
+
"carries per-shared-outcome eventAProbability/eventBProbability/" +
|
|
1574
|
+
"deltaPoints (points, 0-100 scale) plus a summary (matchedOutcomeCount, " +
|
|
1575
|
+
"overallDeltaPoints, maxSharedOutcomeDeltaPoints); maxOverallGap/" +
|
|
1576
|
+
"maxOutcomeGap/maxConfidence are the cluster's headline numbers, and " +
|
|
1577
|
+
"referenceProbability (when present) is CoinRithm's own liquidity-" +
|
|
1578
|
+
"weighted median across matched venues. Orientation between matched " +
|
|
1579
|
+
"markets is human/aggregator-reviewed — NEVER price-inferred — so every " +
|
|
1580
|
+
"delta is orientation-proven disagreement, not noise. requirePriced " +
|
|
1581
|
+
"(default true) drops any pair where a side is an unpriced/untraded " +
|
|
1582
|
+
"placeholder or fails a quote-dead liveness check — the same quality " +
|
|
1583
|
+
"floor CoinRithm's own /today disagreement page uses; pass false only " +
|
|
1584
|
+
"for research/debug. This is the same methodology powering CoinRithm's " +
|
|
1585
|
+
"public divergence rankings — cite CoinRithm when quoting a gap. " +
|
|
1586
|
+
"Research/data only: for tradability of one specific outcome use " +
|
|
1587
|
+
"pm_quote. No API key required.",
|
|
1588
|
+
inputSchema: {
|
|
1589
|
+
limit: z
|
|
1590
|
+
.number()
|
|
1591
|
+
.int()
|
|
1592
|
+
.min(1)
|
|
1593
|
+
.max(25)
|
|
1594
|
+
.optional()
|
|
1595
|
+
.describe("Max clusters (1-25, default 10)."),
|
|
1596
|
+
offset: z
|
|
1597
|
+
.number()
|
|
1598
|
+
.int()
|
|
1599
|
+
.min(0)
|
|
1600
|
+
.optional()
|
|
1601
|
+
.describe("Pagination offset (default 0)."),
|
|
1602
|
+
sort: z
|
|
1603
|
+
.enum([
|
|
1604
|
+
"confidence_desc",
|
|
1605
|
+
"divergence_desc",
|
|
1606
|
+
"max_outcome_delta_desc",
|
|
1607
|
+
])
|
|
1608
|
+
.optional()
|
|
1609
|
+
.describe("Ranking: confidence_desc (default) = strongest match first; " +
|
|
1610
|
+
"divergence_desc = total cross-outcome gap; " +
|
|
1611
|
+
"max_outcome_delta_desc = single largest shared-outcome gap " +
|
|
1612
|
+
"(avoids multi-leg basket noise)."),
|
|
1613
|
+
minDivergence: z
|
|
1614
|
+
.number()
|
|
1615
|
+
.min(0)
|
|
1616
|
+
.optional()
|
|
1617
|
+
.describe("Floor (points, 0-100) on whichever metric the active sort ranks by."),
|
|
1618
|
+
sourceKind: z
|
|
1619
|
+
.enum(["market"])
|
|
1620
|
+
.optional()
|
|
1621
|
+
.describe("Pass 'market' to restrict both sides of every pair to real-money " +
|
|
1622
|
+
"market venues (excludes forecast/play-money venues like " +
|
|
1623
|
+
"Metaculus/Manifold)."),
|
|
1624
|
+
status: z
|
|
1625
|
+
.enum(["open"])
|
|
1626
|
+
.optional()
|
|
1627
|
+
.describe("Pass 'open' to require BOTH matched events be currently open."),
|
|
1628
|
+
maxSnapshotAgeMinutes: z
|
|
1629
|
+
.number()
|
|
1630
|
+
.min(0)
|
|
1631
|
+
.optional()
|
|
1632
|
+
.describe("Require both matched events' probability come from a price " +
|
|
1633
|
+
"snapshot captured within this many minutes."),
|
|
1634
|
+
requirePriced: z
|
|
1635
|
+
.boolean()
|
|
1636
|
+
.optional()
|
|
1637
|
+
.describe("Default true: drops any pair where a side is an unpriced/untraded " +
|
|
1638
|
+
"placeholder or fails a quote-dead liveness check. Set false only " +
|
|
1639
|
+
"for research/debug."),
|
|
1640
|
+
fiat: z
|
|
1641
|
+
.string()
|
|
1642
|
+
.optional()
|
|
1643
|
+
.describe("Fiat currency code for monetary figures (default usd)."),
|
|
1644
|
+
},
|
|
1645
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
1646
|
+
annotations: readOnlyAnnotations("Cross-venue disagreement clusters"),
|
|
1647
|
+
}, async ({ limit, offset, sort, minDivergence, sourceKind, status, maxSnapshotAgeMinutes, requirePriced, fiat, }) => present(mapSuccessfulBody(await client.getPublicPmMatches({
|
|
1648
|
+
limit,
|
|
1649
|
+
offset,
|
|
1650
|
+
sort,
|
|
1651
|
+
minDivergence,
|
|
1652
|
+
sourceKind,
|
|
1653
|
+
status,
|
|
1654
|
+
maxSnapshotAgeMinutes,
|
|
1655
|
+
requirePriced,
|
|
1656
|
+
fiat,
|
|
1657
|
+
}), compactPublicPmDisagreements)));
|
|
1658
|
+
server.registerTool("pm_data_calibration", {
|
|
1659
|
+
title: "Per-venue forecast-accuracy calibration",
|
|
1660
|
+
description: "Free public per-venue forecast-accuracy scorecard: for each venue, " +
|
|
1661
|
+
"calibrationError (Expected Calibration Error, 0-1, lower is better — " +
|
|
1662
|
+
"the fair cross-venue headline), sampleSize, meanWinnerConfidence, and " +
|
|
1663
|
+
"a 10-bucket reliability curve (predictedMean vs realizedRate per " +
|
|
1664
|
+
"probability bucket) computed from that venue's OWN probability ~24h " +
|
|
1665
|
+
"before resolution against the outcome that actually happened, over " +
|
|
1666
|
+
"resolved markets with >=24h of pre-resolution history. Venues below " +
|
|
1667
|
+
"minSample (currently 30 scored events) appear in `pending` instead of " +
|
|
1668
|
+
"a curve — too few resolutions to publish a reliable number yet. Use " +
|
|
1669
|
+
"this to answer 'which venue forecasts best' with evidence, not vibes; " +
|
|
1670
|
+
"cite CoinRithm's methodology field when quoting a number. No API key " +
|
|
1671
|
+
"required.",
|
|
1672
|
+
inputSchema: {},
|
|
1673
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
1674
|
+
annotations: readOnlyAnnotations("Per-venue forecast-accuracy calibration"),
|
|
1675
|
+
}, async () => present(await client.getPublicPmCalibration()));
|
|
1676
|
+
server.registerTool("pm_data_canonical", {
|
|
1677
|
+
title: "Canonical cross-venue event identity",
|
|
1678
|
+
description: "Free public canonical-event identity: CoinRithm's stable cross-venue " +
|
|
1679
|
+
"identity for one real-world question, independent of any single " +
|
|
1680
|
+
"venue's slug. Omit `key` to page the directory of active canonicals " +
|
|
1681
|
+
"(uuid, slug, title, memberCount). Pass `key` (a canonical's uuid OR " +
|
|
1682
|
+
"slug) for one canonical's full record: its venue members (each with " +
|
|
1683
|
+
"orientation — same/inverted/unknown, NEVER price-inferred — plus " +
|
|
1684
|
+
"confidence and provenance basis) and an append-only judgment lineage " +
|
|
1685
|
+
"(created/member_added/member_removed/merged, newest first). A MERGED " +
|
|
1686
|
+
"canonical still resolves (status='merged' + a mergedInto pointer) so " +
|
|
1687
|
+
"a stable key never 404s. Use this to track one question across " +
|
|
1688
|
+
"venues by a durable identity instead of re-matching venue slugs " +
|
|
1689
|
+
"yourself. No API key required.",
|
|
1690
|
+
inputSchema: {
|
|
1691
|
+
key: z
|
|
1692
|
+
.string()
|
|
1693
|
+
.min(1)
|
|
1694
|
+
.optional()
|
|
1695
|
+
.describe("UUID or slug of one canonical event. Omit to list active canonicals."),
|
|
1696
|
+
limit: z
|
|
1697
|
+
.number()
|
|
1698
|
+
.int()
|
|
1699
|
+
.min(1)
|
|
1700
|
+
.max(200)
|
|
1701
|
+
.optional()
|
|
1702
|
+
.describe("List mode only: max rows (1-200, default 50)."),
|
|
1703
|
+
cursor: z
|
|
1704
|
+
.number()
|
|
1705
|
+
.int()
|
|
1706
|
+
.positive()
|
|
1707
|
+
.optional()
|
|
1708
|
+
.describe("List mode only: pagination cursor — pass the previous " +
|
|
1709
|
+
"response's pagination.nextCursor."),
|
|
1710
|
+
},
|
|
1711
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
1712
|
+
annotations: readOnlyAnnotations("Canonical cross-venue event identity"),
|
|
1713
|
+
}, async ({ key, limit, cursor }) => present(key
|
|
1714
|
+
? await client.getPublicPmCanonicalDetail(key)
|
|
1715
|
+
: await client.getPublicPmCanonicalList({ limit, cursor })));
|
|
1716
|
+
server.registerTool("pm_data_volume_history", {
|
|
1717
|
+
title: "Global prediction-market volume trend",
|
|
1718
|
+
description: "Free public global daily prediction-market volume trend: one point " +
|
|
1719
|
+
"per UTC calendar day (day-over-day delta of each event's cumulative " +
|
|
1720
|
+
"volume, summed across REAL-MONEY venues only — play-money/forecast " +
|
|
1721
|
+
"venues like Manifold and Metaculus are excluded), with a per-venue " +
|
|
1722
|
+
"breakdown (bySource) each day. Captured forward since 2026-07-02, " +
|
|
1723
|
+
"bounded to a rolling ~90-day window; a day or venue with no known " +
|
|
1724
|
+
"value is a gap (null), never a zero bar — do not read a gap as zero " +
|
|
1725
|
+
"activity. Use this to see whether cross-venue prediction-market " +
|
|
1726
|
+
"activity is growing or shrinking over time. No API key required.",
|
|
1727
|
+
inputSchema: {},
|
|
1728
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
1729
|
+
annotations: readOnlyAnnotations("Global prediction-market volume trend"),
|
|
1730
|
+
}, async () => present(await client.getPublicPmVolumeHistory()));
|
|
1159
1731
|
}
|