@coinrithm/mcp-trading 0.7.2 → 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 -113
- package/README.md +277 -238
- package/dist/agent/act.d.ts +2 -2
- package/dist/agent/act.js +24 -3
- package/dist/agent/cli.js +68 -23
- package/dist/agent/client.d.ts +33 -0
- package/dist/agent/client.js +34 -7
- package/dist/agent/decision.d.ts +3 -0
- package/dist/agent/decision.js +26 -3
- package/dist/agent/decisionValidator.js +2 -1
- package/dist/agent/deploymentOverlay.js +25 -5
- package/dist/agent/engine.d.ts +2 -1
- package/dist/agent/engine.js +4 -1
- package/dist/agent/extract.js +3 -1
- package/dist/agent/gate.js +25 -5
- package/dist/agent/index.js +0 -1
- package/dist/agent/indicators.js +4 -2
- package/dist/agent/manifest.js +1 -1
- package/dist/agent/mechanical.d.ts +36 -0
- package/dist/agent/mechanical.js +286 -0
- package/dist/agent/observe.d.ts +4 -0
- package/dist/agent/observe.js +140 -53
- package/dist/agent/prompt.d.ts +3 -1
- package/dist/agent/prompt.js +17 -6
- package/dist/agent/providers.js +39 -4
- package/dist/agent/resolve.js +23 -6
- package/dist/agent/resolvePm.js +14 -3
- package/dist/agent/runEvidence.js +6 -2
- package/dist/agent/runner.d.ts +8 -2
- package/dist/agent/runner.js +363 -59
- package/dist/agent/scorecard.js +12 -4
- package/dist/agent/setups.js +57 -9
- package/dist/agent/skill.js +1 -1
- package/dist/agent/state.js +9 -4
- package/dist/agent/types.d.ts +17 -2
- package/dist/agent/types.js +2 -1
- package/dist/agent/util.js +11 -4
- package/dist/agent/version.d.ts +1 -1
- package/dist/agent/version.js +1 -1
- package/dist/client.d.ts +51 -0
- package/dist/client.js +30 -3
- package/dist/executionPolicy.d.ts +2 -0
- package/dist/executionPolicy.js +21 -0
- package/dist/http.js +13 -3
- package/dist/tools.d.ts +23 -0
- package/dist/tools.js +796 -39
- package/package.json +86 -78
package/dist/tools.js
CHANGED
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
// isError results rather than thrown so the model can react.
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { bearerFromHeader } from "./client.js";
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import { PAPER_EXECUTION_VERSION } from "./executionPolicy.js";
|
|
10
|
+
// Served on tool descriptions. Names the versioned execution policy and never
|
|
11
|
+
// claims costless execution (drift-tested in executionPolicy.test.ts).
|
|
12
|
+
export const PAPER_NOTE = "Paper trading only — virtual funds (50,000 mUSD). Not financial advice. " +
|
|
13
|
+
`Paper fills run under the versioned ${PAPER_EXECUTION_VERSION} policy and ` +
|
|
14
|
+
"apply a disclosed execution cost folded into realized PnL: " +
|
|
11
15
|
"spot/futures pay a taker fee (spot market orders also pay half-spread + " +
|
|
12
16
|
"slippage); PM fills at the ask with size-based slippage and a Polymarket-" +
|
|
13
17
|
"shaped taker fee, with entryProbability kept at the mid for calibration. " +
|
|
@@ -64,6 +68,54 @@ const AGENT_TRACE_SCHEMA = z
|
|
|
64
68
|
})
|
|
65
69
|
.optional()
|
|
66
70
|
.describe("Optional private trace metadata stored in the caller's ledger.");
|
|
71
|
+
// Optional SELF-REPORTED provenance a caller attaches to a PM open/opportunity so the
|
|
72
|
+
// durable artifact records WHAT RAN. Every field carries NO trust: the server always
|
|
73
|
+
// stamps the execution/evaluation policy versions AND providerVerified itself
|
|
74
|
+
// (providerVerified can NEVER be raised by a caller), validates/caps each value, and
|
|
75
|
+
// hex-checks the hashes. Sending ANY block (even {}) makes the artifact schemaVersion 2.
|
|
76
|
+
const PROVENANCE_REPORT_SCHEMA = z
|
|
77
|
+
.object({
|
|
78
|
+
runtimeKind: z
|
|
79
|
+
.enum(["hosted_scheduler", "self_host_runner", "byo_api", "mcp_tool"])
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("The runtime surface you ran on (self-reported; no trust)."),
|
|
82
|
+
packageVersion: z.string().max(40).optional(),
|
|
83
|
+
bundleId: z.string().max(120).optional(),
|
|
84
|
+
bundleVersion: z.string().max(40).optional(),
|
|
85
|
+
skillVersions: z
|
|
86
|
+
.record(z.string(), z.string())
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("{skillId: version}. Capped: 50 keys, key<=120 / value<=40."),
|
|
89
|
+
promptHash: z
|
|
90
|
+
.string()
|
|
91
|
+
.regex(/^[0-9a-fA-F]{64}$/)
|
|
92
|
+
.optional()
|
|
93
|
+
.describe("sha256 hex of your exact prompt strings. HASH ONLY — never raw text."),
|
|
94
|
+
configHash: z
|
|
95
|
+
.string()
|
|
96
|
+
.regex(/^[0-9a-fA-F]{64}$/)
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("sha256 hex of your resolved config/spec. HASH ONLY — never raw text."),
|
|
99
|
+
modelProvider: z.string().max(80).optional(),
|
|
100
|
+
modelName: z.string().max(80).optional(),
|
|
101
|
+
evidenceRef: z
|
|
102
|
+
.object({
|
|
103
|
+
snapshotIds: z
|
|
104
|
+
.array(z.string().max(200))
|
|
105
|
+
.optional()
|
|
106
|
+
.describe("Opaque snapshot ids (capped at 100)."),
|
|
107
|
+
sourceCapturedAt: z
|
|
108
|
+
.string()
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("Source capture time (ISO 8601)."),
|
|
111
|
+
})
|
|
112
|
+
.optional()
|
|
113
|
+
.describe("Pointers to the observation evidence (never the evidence itself)."),
|
|
114
|
+
})
|
|
115
|
+
.optional()
|
|
116
|
+
.describe("Optional self-reported provenance (WHAT RAN). No trust: the server stamps " +
|
|
117
|
+
"policy versions + providerVerified itself. Any block (even {}) makes the " +
|
|
118
|
+
"artifact schemaVersion 2.");
|
|
67
119
|
function readOnlyAnnotations(title) {
|
|
68
120
|
return {
|
|
69
121
|
title,
|
|
@@ -120,6 +172,391 @@ function present(result) {
|
|
|
120
172
|
isError: !result.ok,
|
|
121
173
|
};
|
|
122
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
|
+
}
|
|
123
560
|
export function registerTools(server, client) {
|
|
124
561
|
// ---------------- identity ----------------
|
|
125
562
|
server.registerTool("whoami", {
|
|
@@ -358,9 +795,12 @@ export function registerTools(server, client) {
|
|
|
358
795
|
description: "Find active-open, quote-ready-first prediction markets on the mock-PM " +
|
|
359
796
|
"sources (Kalshi + Polymarket by default). Returns source, slug, " +
|
|
360
797
|
"quoteable outcome externalMarketIds, freshness, volume/liquidity/spread, " +
|
|
361
|
-
"
|
|
362
|
-
"
|
|
363
|
-
"
|
|
798
|
+
"decisionSupport, and quality (the truth engine's persisted verdict: " +
|
|
799
|
+
"decisionEligible plus stable warning/block reason codes; " +
|
|
800
|
+
"decisionEligible=false means opens are blocked and alerts suppressed " +
|
|
801
|
+
"while the market stays visible). This is discovery only — call pm_quote " +
|
|
802
|
+
"with one returned outcomeExternalMarketId before open_pm_position " +
|
|
803
|
+
"because pm_quote is the final eligibility source. " +
|
|
364
804
|
PAPER_NOTE,
|
|
365
805
|
inputSchema: {
|
|
366
806
|
q: z
|
|
@@ -422,16 +862,16 @@ export function registerTools(server, client) {
|
|
|
422
862
|
PAPER_NOTE,
|
|
423
863
|
inputSchema: {
|
|
424
864
|
venue: z.string().optional().describe("Optional venue filter."),
|
|
425
|
-
eventType: z
|
|
865
|
+
eventType: z
|
|
866
|
+
.string()
|
|
867
|
+
.optional()
|
|
868
|
+
.describe("Optional event type filter."),
|
|
426
869
|
runId: z.string().optional().describe("Optional run id filter."),
|
|
427
870
|
decisionId: z
|
|
428
871
|
.string()
|
|
429
872
|
.optional()
|
|
430
873
|
.describe("Optional decision id filter."),
|
|
431
|
-
status: z
|
|
432
|
-
.string()
|
|
433
|
-
.optional()
|
|
434
|
-
.describe("Optional ledgerStatus filter."),
|
|
874
|
+
status: z.string().optional().describe("Optional ledgerStatus filter."),
|
|
435
875
|
from: z.string().optional().describe("Optional ISO start timestamp."),
|
|
436
876
|
to: z.string().optional().describe("Optional ISO end timestamp."),
|
|
437
877
|
limit: z
|
|
@@ -451,7 +891,17 @@ export function registerTools(server, client) {
|
|
|
451
891
|
},
|
|
452
892
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
453
893
|
annotations: readOnlyAnnotations("Get private agent ledger"),
|
|
454
|
-
}, async ({ venue, eventType, runId, decisionId, status, from, to, limit, offset, agentTrace, }, extra) => present(await client.getLedger({
|
|
894
|
+
}, async ({ venue, eventType, runId, decisionId, status, from, to, limit, offset, agentTrace, }, extra) => present(await client.getLedger({
|
|
895
|
+
venue,
|
|
896
|
+
eventType,
|
|
897
|
+
runId,
|
|
898
|
+
decisionId,
|
|
899
|
+
status,
|
|
900
|
+
from,
|
|
901
|
+
to,
|
|
902
|
+
limit,
|
|
903
|
+
offset,
|
|
904
|
+
}, requestKey(extra), agentTrace)));
|
|
455
905
|
server.registerTool("export_agent_ledger", {
|
|
456
906
|
title: "Export private agent ledger",
|
|
457
907
|
description: "Export up to 1,000 private ledger rows for the calling API key as JSON. " +
|
|
@@ -460,16 +910,16 @@ export function registerTools(server, client) {
|
|
|
460
910
|
PAPER_NOTE,
|
|
461
911
|
inputSchema: {
|
|
462
912
|
venue: z.string().optional().describe("Optional venue filter."),
|
|
463
|
-
eventType: z
|
|
913
|
+
eventType: z
|
|
914
|
+
.string()
|
|
915
|
+
.optional()
|
|
916
|
+
.describe("Optional event type filter."),
|
|
464
917
|
runId: z.string().optional().describe("Optional run id filter."),
|
|
465
918
|
decisionId: z
|
|
466
919
|
.string()
|
|
467
920
|
.optional()
|
|
468
921
|
.describe("Optional decision id filter."),
|
|
469
|
-
status: z
|
|
470
|
-
.string()
|
|
471
|
-
.optional()
|
|
472
|
-
.describe("Optional ledgerStatus filter."),
|
|
922
|
+
status: z.string().optional().describe("Optional ledgerStatus filter."),
|
|
473
923
|
from: z.string().optional().describe("Optional ISO start timestamp."),
|
|
474
924
|
to: z.string().optional().describe("Optional ISO end timestamp."),
|
|
475
925
|
agentTrace: AGENT_TRACE_SCHEMA,
|
|
@@ -572,9 +1022,13 @@ export function registerTools(server, client) {
|
|
|
572
1022
|
server.registerTool("pm_quote", {
|
|
573
1023
|
title: "Prediction-market quote",
|
|
574
1024
|
description: "Read-only PM quote for a binary outcome: entry probability, share " +
|
|
575
|
-
"estimate, max payout, eligibility, freshness,
|
|
576
|
-
"(market quality/liquidity/volume/spread tiers + flags)
|
|
577
|
-
"
|
|
1025
|
+
"estimate, max payout, eligibility, freshness, decisionSupport " +
|
|
1026
|
+
"(market quality/liquidity/volume/spread tiers + flags), quality (the " +
|
|
1027
|
+
"persisted truth-engine verdict), and openBlocked/openBlockReasons — " +
|
|
1028
|
+
"a preview of the open-time quality gate: when openBlocked is true, " +
|
|
1029
|
+
"open_pm_position would be rejected 422 with those stored reason codes " +
|
|
1030
|
+
"(quality_state_missing, quality_state_stale, quote_dead, " +
|
|
1031
|
+
"stale_freshness, ...). Never mutates state. " +
|
|
578
1032
|
"stakeMusd must be > 0 (min to open is 10). Pass side: 'no' to quote " +
|
|
579
1033
|
"backing the NO side (omitted = yes); a NO entry fills at 100 minus the " +
|
|
580
1034
|
"outcome probability and pays out if the outcome resolves false. " +
|
|
@@ -596,7 +1050,14 @@ export function registerTools(server, client) {
|
|
|
596
1050
|
},
|
|
597
1051
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
598
1052
|
annotations: readOnlyAnnotations("Prediction-market quote"),
|
|
599
|
-
}, async ({ source, slug, outcomeExternalMarketId, side, stakeMusd, agentTrace }, extra) => present(await client.pmQuote({
|
|
1053
|
+
}, async ({ source, slug, outcomeExternalMarketId, side, stakeMusd, agentTrace }, extra) => present(await client.pmQuote({
|
|
1054
|
+
source,
|
|
1055
|
+
slug,
|
|
1056
|
+
outcomeExternalMarketId,
|
|
1057
|
+
side,
|
|
1058
|
+
stakeMusd,
|
|
1059
|
+
agentTrace,
|
|
1060
|
+
}, requestKey(extra))));
|
|
600
1061
|
server.registerTool("spot_quote", {
|
|
601
1062
|
title: "Spot quote",
|
|
602
1063
|
description: "Read-only spot MARKET quote: live execution price, estimated cost " +
|
|
@@ -837,19 +1298,125 @@ export function registerTools(server, client) {
|
|
|
837
1298
|
.string()
|
|
838
1299
|
.min(1)
|
|
839
1300
|
.describe("Unique per PM-open intent; reuse replays the original result."),
|
|
1301
|
+
forecastProbability: z
|
|
1302
|
+
.number()
|
|
1303
|
+
.gt(0)
|
|
1304
|
+
.lt(100)
|
|
1305
|
+
.optional()
|
|
1306
|
+
.describe("OPTIONAL. Report your OWN estimated probability (0-100, exclusive) " +
|
|
1307
|
+
"that the chosen side wins, decided BEFORE you look at sizing/fill. " +
|
|
1308
|
+
"It is stored SEPARATELY from the market price you pay and feeds your " +
|
|
1309
|
+
"PUBLIC calibration record (agentBrier), which scores your forecast " +
|
|
1310
|
+
"SKILL — not the market's. Omit it if you are not forecasting; never " +
|
|
1311
|
+
"echo the market probability back."),
|
|
1312
|
+
provenance: PROVENANCE_REPORT_SCHEMA,
|
|
840
1313
|
agentTrace: AGENT_TRACE_SCHEMA,
|
|
841
1314
|
},
|
|
842
1315
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
843
1316
|
annotations: mutatingAnnotations("Open prediction-market position", {
|
|
844
1317
|
idempotent: true,
|
|
845
1318
|
}),
|
|
846
|
-
}, async ({ source, slug, outcomeExternalMarketId, side, stakeMusd, idempotencyKey, agentTrace, }, extra) => present(await client.openPmPosition({
|
|
1319
|
+
}, async ({ source, slug, outcomeExternalMarketId, side, stakeMusd, idempotencyKey, forecastProbability, provenance, agentTrace, }, extra) => present(await client.openPmPosition({
|
|
847
1320
|
source,
|
|
848
1321
|
slug,
|
|
849
1322
|
outcomeExternalMarketId,
|
|
850
1323
|
side,
|
|
851
1324
|
stakeMusd,
|
|
852
1325
|
idempotencyKey,
|
|
1326
|
+
forecastProbability,
|
|
1327
|
+
provenance,
|
|
1328
|
+
agentTrace,
|
|
1329
|
+
}, requestKey(extra))));
|
|
1330
|
+
server.registerTool("report_pm_opportunity", {
|
|
1331
|
+
title: "Report a non-opened PM opportunity",
|
|
1332
|
+
description: "Report a prediction-market opportunity you evaluated but did NOT open, so " +
|
|
1333
|
+
"your PUBLIC evaluation reflects the FULL opportunity universe — not only " +
|
|
1334
|
+
"the trades you took (otherwise an agent can look skilled by exposure " +
|
|
1335
|
+
"choice alone). kind is one of: 'abstained' (you looked at markets and " +
|
|
1336
|
+
"chose not to bet), 'forecast_only' (you formed your OWN probability but " +
|
|
1337
|
+
"did not trade — forecastProbability is REQUIRED, 1-99), or 'quote_expired' " +
|
|
1338
|
+
"(a bet you validated was rejected at open because the market moved). This " +
|
|
1339
|
+
"is EVIDENCE, not a trade: it needs only the read scope, never moves funds, " +
|
|
1340
|
+
"and is recorded as a durable, hashed decision artifact. It is a " +
|
|
1341
|
+
"SELF-REPORT — CoinRithm records what you assert about your own reasoning; " +
|
|
1342
|
+
"it does not independently verify that you truly evaluated the market. Put " +
|
|
1343
|
+
"the breadth of what you weighed in cohort.universeSize (how many markets) " +
|
|
1344
|
+
"and report ONCE per decision cycle, not once per market. Reuse decisionId " +
|
|
1345
|
+
"to make a retry idempotent. " +
|
|
1346
|
+
PAPER_NOTE,
|
|
1347
|
+
inputSchema: {
|
|
1348
|
+
kind: z
|
|
1349
|
+
.enum(["abstained", "forecast_only", "quote_expired"])
|
|
1350
|
+
.describe("abstained = evaluated but did not bet; forecast_only = formed your " +
|
|
1351
|
+
"own probability without trading (forecastProbability required); " +
|
|
1352
|
+
"quote_expired = a validated open the server rejected at act time."),
|
|
1353
|
+
source: z
|
|
1354
|
+
.string()
|
|
1355
|
+
.optional()
|
|
1356
|
+
.describe("Optional subject market source slug (e.g. kalshi)."),
|
|
1357
|
+
slug: z.string().optional().describe("Optional subject event slug."),
|
|
1358
|
+
outcomeExternalMarketId: z
|
|
1359
|
+
.string()
|
|
1360
|
+
.optional()
|
|
1361
|
+
.describe("Optional case-sensitive outcome/market id of the subject."),
|
|
1362
|
+
forecastProbability: z
|
|
1363
|
+
.number()
|
|
1364
|
+
.min(1)
|
|
1365
|
+
.max(99)
|
|
1366
|
+
.optional()
|
|
1367
|
+
.describe("Your OWN probability (1-99) the chosen side wins. REQUIRED for " +
|
|
1368
|
+
"forecast_only; omit for the other kinds. Never echo the market price."),
|
|
1369
|
+
marketProbability: z
|
|
1370
|
+
.number()
|
|
1371
|
+
.min(0)
|
|
1372
|
+
.max(100)
|
|
1373
|
+
.optional()
|
|
1374
|
+
.describe("The market price (0-100) you observed at the time."),
|
|
1375
|
+
reasonCode: z
|
|
1376
|
+
.string()
|
|
1377
|
+
.max(500)
|
|
1378
|
+
.optional()
|
|
1379
|
+
.describe("Short structured reason (e.g. 'no_edge', 'stale_data')."),
|
|
1380
|
+
cohort: z
|
|
1381
|
+
.object({
|
|
1382
|
+
universeSize: z
|
|
1383
|
+
.number()
|
|
1384
|
+
.int()
|
|
1385
|
+
.min(0)
|
|
1386
|
+
.optional()
|
|
1387
|
+
.describe("How many markets you were choosing from this cycle."),
|
|
1388
|
+
horizon: z
|
|
1389
|
+
.string()
|
|
1390
|
+
.max(64)
|
|
1391
|
+
.optional()
|
|
1392
|
+
.describe("Your forecast/decision horizon label (e.g. '7d')."),
|
|
1393
|
+
})
|
|
1394
|
+
.optional()
|
|
1395
|
+
.describe("Opportunity-cohort breadth (frozen into the artifact)."),
|
|
1396
|
+
decisionId: z
|
|
1397
|
+
.string()
|
|
1398
|
+
.optional()
|
|
1399
|
+
.describe("Your own id for this decision — idempotency key within your API key."),
|
|
1400
|
+
runId: z.string().optional().describe("Your own run id for grouping."),
|
|
1401
|
+
provenance: PROVENANCE_REPORT_SCHEMA,
|
|
1402
|
+
agentTrace: AGENT_TRACE_SCHEMA,
|
|
1403
|
+
},
|
|
1404
|
+
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
1405
|
+
annotations: mutatingAnnotations("Report a non-opened PM opportunity", {
|
|
1406
|
+
idempotent: true,
|
|
1407
|
+
}),
|
|
1408
|
+
}, async ({ kind, source, slug, outcomeExternalMarketId, forecastProbability, marketProbability, reasonCode, cohort, decisionId, runId, provenance, agentTrace, }, extra) => present(await client.reportPmOpportunity({
|
|
1409
|
+
kind,
|
|
1410
|
+
source,
|
|
1411
|
+
slug,
|
|
1412
|
+
outcomeExternalMarketId,
|
|
1413
|
+
forecastProbability,
|
|
1414
|
+
marketProbability,
|
|
1415
|
+
reasonCode,
|
|
1416
|
+
cohort,
|
|
1417
|
+
decisionId,
|
|
1418
|
+
runId,
|
|
1419
|
+
provenance,
|
|
853
1420
|
agentTrace,
|
|
854
1421
|
}, requestKey(extra))));
|
|
855
1422
|
// ---- Public cross-venue PM data (no API key required) ----
|
|
@@ -859,9 +1426,10 @@ export function registerTools(server, client) {
|
|
|
859
1426
|
title: "Cross-venue prediction-market statistics",
|
|
860
1427
|
description: "Free public cross-venue prediction-market statistics: total/open/" +
|
|
861
1428
|
"closed market counts, total volume, 24h volume, and liquidity " +
|
|
862
|
-
"aggregated across all
|
|
863
|
-
"Limitless, Smarkets, Manifold, Metaculus, PredictIt, Futuur, Myriad), plus market " +
|
|
864
|
-
"highlights
|
|
1429
|
+
"aggregated across all 11 venues (Polymarket, Kalshi, Rothera, " +
|
|
1430
|
+
"Limitless, Smarkets, Manifold, Metaculus, PredictIt, Futuur, Myriad, ForecastEx), plus market " +
|
|
1431
|
+
"highlights in a compact discovery shape. Use pm_data_event for full " +
|
|
1432
|
+
"event evidence. Freshness is SOURCE-AWARE — each venue ingests " +
|
|
865
1433
|
"independently; per-venue health (freshness tier, lag, stale reason) " +
|
|
866
1434
|
"is at /api/prediction-markets/sources/health. Volume is " +
|
|
867
1435
|
"reported on each venue's own basis (see the methodology at " +
|
|
@@ -876,26 +1444,31 @@ export function registerTools(server, client) {
|
|
|
876
1444
|
},
|
|
877
1445
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
878
1446
|
annotations: readOnlyAnnotations("Cross-venue prediction-market statistics"),
|
|
879
|
-
}, async ({ fiat }) => present(await client.getPublicPmOverview({ fiat })));
|
|
1447
|
+
}, async ({ fiat }) => present(mapSuccessfulBody(await client.getPublicPmOverview({ fiat }), compactPublicPmOverview)));
|
|
880
1448
|
server.registerTool("pm_data_events", {
|
|
881
1449
|
title: "Search prediction markets across all venues",
|
|
882
|
-
description: "Free public search over prediction-market events across ALL
|
|
1450
|
+
description: "Free public search over prediction-market events across ALL 11 " +
|
|
883
1451
|
"venues (Polymarket, Kalshi, Rothera, Limitless, Smarkets, " +
|
|
884
|
-
"Manifold, Metaculus, PredictIt, Futuur, Myriad) — broader than discover_pm_markets, which is " +
|
|
1452
|
+
"Manifold, Metaculus, PredictIt, Futuur, Myriad, ForecastEx) — broader than discover_pm_markets, which is " +
|
|
885
1453
|
"scoped to the paper-tradeable venues. Returns titles, probabilities, " +
|
|
886
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 " +
|
|
887
1457
|
"referenceProbability when present (CoinRithm's canonical cross-venue " +
|
|
888
1458
|
"number for open events matched across venues — probability, " +
|
|
889
1459
|
"venueCount, spreadPoints, and outcomeName for multi-outcome " +
|
|
890
|
-
"leaders)
|
|
891
|
-
"
|
|
1460
|
+
"leaders), quality (persisted truth-engine verdict: decisionEligible " +
|
|
1461
|
+
"+ warning/block reason codes — blocked markets stay visible but " +
|
|
1462
|
+
"cannot drive paper opens or alerts), and crossPlatform (sibling " +
|
|
1463
|
+
"venues pricing the same question). Research/data only: to trade, " +
|
|
1464
|
+
"use discover_pm_markets + pm_quote instead. No API key required.",
|
|
892
1465
|
inputSchema: {
|
|
893
1466
|
q: z.string().optional().describe("Optional search text."),
|
|
894
1467
|
source: z
|
|
895
1468
|
.string()
|
|
896
1469
|
.optional()
|
|
897
1470
|
.describe("Optional venue filter: polymarket, kalshi, rothera, limitless, " +
|
|
898
|
-
"smarkets, manifold, metaculus, predictit, futuur, or
|
|
1471
|
+
"smarkets, manifold, metaculus, predictit, futuur, myriad, or forecastex."),
|
|
899
1472
|
status: z
|
|
900
1473
|
.string()
|
|
901
1474
|
.optional()
|
|
@@ -921,7 +1494,7 @@ export function registerTools(server, client) {
|
|
|
921
1494
|
},
|
|
922
1495
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
923
1496
|
annotations: readOnlyAnnotations("Search prediction markets across all venues"),
|
|
924
|
-
}, 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({
|
|
925
1498
|
q,
|
|
926
1499
|
source,
|
|
927
1500
|
status,
|
|
@@ -929,9 +1502,9 @@ export function registerTools(server, client) {
|
|
|
929
1502
|
limit,
|
|
930
1503
|
offset,
|
|
931
1504
|
fiat,
|
|
932
|
-
})));
|
|
1505
|
+
}), compactPublicPmEvents)));
|
|
933
1506
|
server.registerTool("pm_data_event", {
|
|
934
|
-
title: "Get
|
|
1507
|
+
title: "Get prediction-market event detail",
|
|
935
1508
|
description: "Free public detail for one prediction-market event by venue + slug: " +
|
|
936
1509
|
"outcomes with probabilities, price snapshots, resolution evidence, " +
|
|
937
1510
|
"crossSourceMatches (the SAME real-world question priced on other " +
|
|
@@ -943,32 +1516,216 @@ export function registerTools(server, client) {
|
|
|
943
1516
|
"recent whale trades on the event, related events, related news, and " +
|
|
944
1517
|
"volumeHistory when present (daily volume points captured since " +
|
|
945
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. " +
|
|
946
1522
|
"This is the cross-venue research view; for tradability use pm_quote. " +
|
|
947
1523
|
"No API key required.",
|
|
948
1524
|
inputSchema: {
|
|
949
1525
|
source: z
|
|
950
1526
|
.string()
|
|
951
1527
|
.describe("Venue slug: polymarket, kalshi, rothera, limitless, smarkets, " +
|
|
952
|
-
"manifold, metaculus, predictit, futuur, or
|
|
1528
|
+
"manifold, metaculus, predictit, futuur, myriad, or forecastex."),
|
|
953
1529
|
slug: z.string().describe("Event slug on that venue."),
|
|
954
1530
|
fiat: z
|
|
955
1531
|
.string()
|
|
956
1532
|
.optional()
|
|
957
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."),
|
|
958
1538
|
},
|
|
959
1539
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
960
|
-
annotations: readOnlyAnnotations("Get
|
|
961
|
-
}, 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
|
+
});
|
|
962
1547
|
server.registerTool("pm_data_whales", {
|
|
963
1548
|
title: "Get latest prediction-market whale trades",
|
|
964
1549
|
description: "Free public tape of the latest large prediction-market trades " +
|
|
965
|
-
"(roughly $1k+ notional) across venues, newest first
|
|
1550
|
+
"(roughly $1k+ notional) across venues, newest first: side, " +
|
|
966
1551
|
"outcome, USD value, price, market question, and the event it printed " +
|
|
967
1552
|
"on. Polymarket rows are wallet-attributed; Kalshi rows are anonymized " +
|
|
968
1553
|
"exchange prints. A large print is information, not a recommendation. " +
|
|
969
1554
|
"No API key required.",
|
|
970
|
-
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
|
+
},
|
|
971
1564
|
outputSchema: API_RESULT_OUTPUT_SCHEMA,
|
|
972
1565
|
annotations: readOnlyAnnotations("Get latest prediction-market whale trades"),
|
|
973
|
-
}, 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()));
|
|
974
1731
|
}
|