sayso-mcp-server 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.
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,770 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+
7
+ // src/config.ts
8
+ function loadConfig() {
9
+ const privateKey = process.env.SAYSO_PRIVATE_KEY;
10
+ if (!privateKey) {
11
+ throw new Error(
12
+ `SAYSO_PRIVATE_KEY is required. Generate one with:
13
+ node -e "const w = require('ethers').Wallet.createRandom(); console.log('SAYSO_PRIVATE_KEY=' + w.privateKey)"`
14
+ );
15
+ }
16
+ return {
17
+ apiBase: process.env.SAYSO_API_BASE ?? "https://mcap-api-564778804231.us-east4.run.app",
18
+ providerId: process.env.SAYSO_PROVIDER_ID ?? "da32ce28-4eb4-491e-80ce-134b50b5379c",
19
+ tokenId: process.env.SAYSO_TOKEN_ID ?? "ac9361a7-9ad6-4169-9cd6-ea67fb555eba",
20
+ privateKey
21
+ };
22
+ }
23
+
24
+ // src/auth.ts
25
+ import { ethers } from "ethers";
26
+ var AuthManager = class {
27
+ wallet;
28
+ state = null;
29
+ config;
30
+ constructor(config) {
31
+ this.config = config;
32
+ this.wallet = new ethers.Wallet(config.privateKey);
33
+ }
34
+ get walletAddress() {
35
+ return this.wallet.address;
36
+ }
37
+ get isAuthenticated() {
38
+ return this.state !== null && Date.now() < this.state.expiresAt;
39
+ }
40
+ get userId() {
41
+ return this.state?.userId ?? null;
42
+ }
43
+ /** Get a valid access token, refreshing or re-authenticating as needed */
44
+ async getToken() {
45
+ if (this.state && Date.now() < this.state.expiresAt - 6e4) {
46
+ return this.state.accessToken;
47
+ }
48
+ if (this.state?.refreshToken) {
49
+ try {
50
+ await this.refresh();
51
+ return this.state.accessToken;
52
+ } catch {
53
+ }
54
+ }
55
+ await this.login();
56
+ return this.state.accessToken;
57
+ }
58
+ /** Full login flow: nonce -> sign -> login -> profile */
59
+ async login() {
60
+ const { apiBase, providerId } = this.config;
61
+ const nonceRes = await fetch(`${apiBase}/v1/auth/nonce`, {
62
+ method: "POST",
63
+ headers: { "Content-Type": "application/json" },
64
+ body: JSON.stringify({
65
+ wallet_address: this.wallet.address,
66
+ provider_id: providerId
67
+ })
68
+ });
69
+ if (!nonceRes.ok) throw new Error(`Nonce request failed: ${nonceRes.status}`);
70
+ const { nonce } = await nonceRes.json();
71
+ const signature = await this.wallet.signMessage(
72
+ `Sign this message to authenticate with nonce: ${nonce}`
73
+ );
74
+ const loginRes = await fetch(`${apiBase}/v1/auth/login`, {
75
+ method: "POST",
76
+ headers: { "Content-Type": "application/json" },
77
+ body: JSON.stringify({
78
+ provider_id: providerId,
79
+ wallet_address: this.wallet.address,
80
+ signature,
81
+ nonce,
82
+ user_type: "bot"
83
+ })
84
+ });
85
+ if (!loginRes.ok) throw new Error(`Login failed: ${loginRes.status}`);
86
+ const loginData = await loginRes.json();
87
+ const profileRes = await fetch(`${apiBase}/v1/auth/profile`, {
88
+ headers: { Authorization: `Bearer ${loginData.access_token}` }
89
+ });
90
+ if (!profileRes.ok)
91
+ throw new Error(`Profile fetch failed: ${profileRes.status}`);
92
+ const profile = await profileRes.json();
93
+ this.state = {
94
+ accessToken: loginData.access_token,
95
+ refreshToken: loginData.refresh_token,
96
+ expiresAt: Date.now() + loginData.expires_in * 1e3,
97
+ userId: profile.user_id,
98
+ walletAddress: profile.wallet_address
99
+ };
100
+ return this.state;
101
+ }
102
+ /** Refresh the access token */
103
+ async refresh() {
104
+ const { apiBase, providerId } = this.config;
105
+ const res = await fetch(`${apiBase}/v1/auth/refresh`, {
106
+ method: "POST",
107
+ headers: { "Content-Type": "application/json" },
108
+ body: JSON.stringify({
109
+ refresh_token: this.state.refreshToken,
110
+ provider_id: providerId
111
+ })
112
+ });
113
+ if (!res.ok) throw new Error(`Token refresh failed: ${res.status}`);
114
+ const data = await res.json();
115
+ this.state = {
116
+ ...this.state,
117
+ accessToken: data.access_token,
118
+ refreshToken: data.refresh_token,
119
+ expiresAt: Date.now() + data.expires_in * 1e3
120
+ };
121
+ }
122
+ };
123
+
124
+ // src/api.ts
125
+ var SaySoAPI = class {
126
+ constructor(config, auth) {
127
+ this.config = config;
128
+ this.auth = auth;
129
+ }
130
+ // ── Helpers ──────────────────────────────────────────────
131
+ get base() {
132
+ return this.config.apiBase;
133
+ }
134
+ async authedHeaders() {
135
+ const token = await this.auth.getToken();
136
+ return {
137
+ "Content-Type": "application/json",
138
+ Authorization: `Bearer ${token}`
139
+ };
140
+ }
141
+ async request(path, init = {}) {
142
+ const res = await fetch(`${this.base}${path}`, init);
143
+ if (!res.ok) {
144
+ const text2 = await res.text();
145
+ let msg;
146
+ try {
147
+ const err = JSON.parse(text2);
148
+ msg = err.message ?? err.error ?? text2;
149
+ } catch {
150
+ msg = text2;
151
+ }
152
+ throw new Error(`API ${res.status}: ${msg}`);
153
+ }
154
+ const ct = res.headers.get("content-type");
155
+ if (ct?.includes("application/json")) return res.json();
156
+ return {};
157
+ }
158
+ async get(path, authed = false) {
159
+ const headers = authed ? await this.authedHeaders() : { "Content-Type": "application/json" };
160
+ return this.request(path, { headers });
161
+ }
162
+ async post(path, body, authed = false) {
163
+ const headers = authed ? await this.authedHeaders() : { "Content-Type": "application/json" };
164
+ return this.request(path, {
165
+ method: "POST",
166
+ headers,
167
+ body: JSON.stringify(body)
168
+ });
169
+ }
170
+ async put(path, body) {
171
+ return this.request(path, {
172
+ method: "PUT",
173
+ headers: await this.authedHeaders(),
174
+ body: JSON.stringify(body)
175
+ });
176
+ }
177
+ async del(path) {
178
+ return this.request(path, {
179
+ method: "DELETE",
180
+ headers: await this.authedHeaders()
181
+ });
182
+ }
183
+ // ── Markets ──────────────────────────────────────────────
184
+ async searchMarkets(filters = {}, options) {
185
+ return this.post(
186
+ "/v1/markets/search",
187
+ {
188
+ filters: { provider_id: this.config.providerId, ...filters },
189
+ sort: options?.sort ?? { columns: ["created_at"], order: "DESC" },
190
+ limit: options?.limit ?? 20,
191
+ ...options?.cursor ? { cursor: options.cursor } : {}
192
+ }
193
+ );
194
+ }
195
+ async getMarket(marketId) {
196
+ return this.get(`/v1/markets/${marketId}`);
197
+ }
198
+ async getPriceHistory(marketId, outcomeId, range = "7d", interval = "1hour") {
199
+ return this.post("/v1/markets/price-history/search", {
200
+ filters: {
201
+ market_id: marketId,
202
+ range_type: range,
203
+ interval_type: interval,
204
+ ...outcomeId ? { outcome_id: outcomeId } : {}
205
+ }
206
+ });
207
+ }
208
+ // ── Trading ──────────────────────────────────────────────
209
+ async previewBet(outcomeId, amount) {
210
+ return this.post("/v1/markets/bet-preview", {
211
+ outcome_id: outcomeId,
212
+ amount
213
+ });
214
+ }
215
+ async placeBet(marketId, outcomeId, amount, maxSlippageBps = 500) {
216
+ return this.post(
217
+ `/v1/markets/${marketId}/bet`,
218
+ {
219
+ outcome_id: outcomeId,
220
+ amount,
221
+ max_slippage_bps: maxSlippageBps
222
+ },
223
+ true
224
+ );
225
+ }
226
+ async previewSell(outcomeId, sharesAmount) {
227
+ return this.post("/v1/markets/sell-preview", {
228
+ outcome_id: outcomeId,
229
+ shares_amount: sharesAmount
230
+ });
231
+ }
232
+ async sellShares(marketId, outcomeId, sharesAmount, maxSlippageBps = 500) {
233
+ return this.post(
234
+ `/v1/markets/${marketId}/sell`,
235
+ {
236
+ outcome_id: outcomeId,
237
+ shares_amount: sharesAmount,
238
+ max_slippage_bps: maxSlippageBps
239
+ },
240
+ true
241
+ );
242
+ }
243
+ // ── Positions ────────────────────────────────────────────
244
+ async searchHoldings(filters = {}) {
245
+ return this.post(
246
+ "/v1/markets/holdings/search",
247
+ { filters },
248
+ true
249
+ );
250
+ }
251
+ async claimWinnings(positionId) {
252
+ return this.post(
253
+ `/v1/markets/positions/${positionId}/claim`,
254
+ {},
255
+ true
256
+ );
257
+ }
258
+ // ── Balance & Credits ────────────────────────────────────
259
+ async getBalance() {
260
+ const res = await this.post(
261
+ "/v1/users/balance",
262
+ { token_ids: [this.config.tokenId] },
263
+ true
264
+ );
265
+ return res.balances ?? [];
266
+ }
267
+ async checkCreditEligibility() {
268
+ return this.get(
269
+ `/v1/credit-tokens/eligibility/${this.config.tokenId}`,
270
+ true
271
+ );
272
+ }
273
+ async claimCredits() {
274
+ return this.post(
275
+ "/v1/credit-tokens/issue",
276
+ { token_id: this.config.tokenId },
277
+ true
278
+ );
279
+ }
280
+ // ── Comments & Reactions ─────────────────────────────────
281
+ async searchComments(marketId, options) {
282
+ return this.post(
283
+ `/v1/markets/${marketId}/comments/search`,
284
+ {
285
+ filters: options?.parentId ? { parent_id: options.parentId } : {},
286
+ sort: { columns: ["created_at"], order: "DESC" },
287
+ limit: options?.limit ?? 20,
288
+ ...options?.cursor ? { cursor: options.cursor } : {}
289
+ }
290
+ );
291
+ }
292
+ async postComment(marketId, body, parentId) {
293
+ return this.post(
294
+ `/v1/markets/${marketId}/comments`,
295
+ { body, ...parentId ? { parent_id: parentId } : {} },
296
+ true
297
+ );
298
+ }
299
+ async likeMarket(marketId) {
300
+ return this.post(
301
+ `/v1/markets/${marketId}/like`,
302
+ {},
303
+ true
304
+ );
305
+ }
306
+ async unlikeMarket(marketId) {
307
+ return this.del(`/v1/markets/${marketId}/like`);
308
+ }
309
+ async likeComment(commentId) {
310
+ return this.post(
311
+ `/v1/markets/comments/${commentId}/like`,
312
+ {},
313
+ true
314
+ );
315
+ }
316
+ async unlikeComment(commentId) {
317
+ return this.del(
318
+ `/v1/markets/comments/${commentId}/like`
319
+ );
320
+ }
321
+ // ── Leaderboard ──────────────────────────────────────────
322
+ async searchLeaderboard(options) {
323
+ return this.post("/v1/markets/leaderboard/search", {
324
+ filters: {
325
+ provider_id: this.config.providerId,
326
+ ...options?.rankBy ? { rank_by: options.rankBy } : {},
327
+ ...options?.userTypes?.length ? { user_types: options.userTypes } : {}
328
+ },
329
+ limit: options?.limit ?? 20
330
+ });
331
+ }
332
+ // ── Social ───────────────────────────────────────────────
333
+ async followUser(userId) {
334
+ return this.post(
335
+ `/v1/users/${userId}/follow`,
336
+ {},
337
+ true
338
+ );
339
+ }
340
+ async unfollowUser(userId) {
341
+ return this.del(`/v1/users/${userId}/follow`);
342
+ }
343
+ async searchFeed(options) {
344
+ return this.post(
345
+ "/v1/feed/search",
346
+ {
347
+ filters: { provider_id: this.config.providerId },
348
+ sort: { columns: ["created_at"], order: "DESC" },
349
+ limit: options?.limit ?? 20,
350
+ ...options?.cursor ? { cursor: options.cursor } : {}
351
+ },
352
+ true
353
+ );
354
+ }
355
+ // ── Profile ──────────────────────────────────────────────
356
+ async getMyProfile() {
357
+ return this.get("/v1/auth/profile", true);
358
+ }
359
+ async getCreatorProfile(userId) {
360
+ return this.get(`/v1/creators/${userId}`);
361
+ }
362
+ async updateProfile(data) {
363
+ return this.put("/v1/users/profile", data);
364
+ }
365
+ // ── Transactions ─────────────────────────────────────────
366
+ async searchTransactions(options) {
367
+ return this.post(
368
+ "/v1/users/transactions/search",
369
+ {
370
+ filters: { provider_id: this.config.providerId },
371
+ limit: options?.limit ?? 20,
372
+ ...options?.cursor ? { cursor: options.cursor } : {}
373
+ },
374
+ true
375
+ );
376
+ }
377
+ };
378
+
379
+ // src/tools.ts
380
+ import { z } from "zod";
381
+
382
+ // src/utils.ts
383
+ var WEI_PER_TOKEN = 10n ** 18n;
384
+ function tokensToWei(tokens) {
385
+ const parts = tokens.split(".");
386
+ const whole = parts[0] ?? "0";
387
+ const frac = (parts[1] ?? "").padEnd(18, "0").slice(0, 18);
388
+ const wei = BigInt(whole) * WEI_PER_TOKEN + BigInt(frac);
389
+ return wei.toString();
390
+ }
391
+ function weiToTokens(wei) {
392
+ const n = BigInt(wei);
393
+ const whole = n / WEI_PER_TOKEN;
394
+ const frac = n % WEI_PER_TOKEN;
395
+ if (frac === 0n) return whole.toString();
396
+ const fracStr = frac.toString().padStart(18, "0").replace(/0+$/, "");
397
+ return `${whole}.${fracStr}`;
398
+ }
399
+ function weiToPercent(wei) {
400
+ const n = Number(BigInt(wei)) / 1e18;
401
+ return `${(n * 100).toFixed(1)}%`;
402
+ }
403
+ function formatMarket(m) {
404
+ const outcomes = m.outcomes;
405
+ const outcomeLines = outcomes?.map(
406
+ (o) => ` - ${o.title} (${o.id}): ${weiToPercent(String(o.current_price ?? "0"))} probability`
407
+ ).join("\n");
408
+ return [
409
+ `**${m.question}**`,
410
+ `ID: ${m.id}`,
411
+ `Status: ${m.status}`,
412
+ m.description ? `Description: ${m.description}` : null,
413
+ m.total_volume ? `Volume: ${weiToTokens(String(m.total_volume))} tokens (${m.bet_count} bets)` : null,
414
+ m.ends_at ? `Ends: ${m.ends_at}` : null,
415
+ outcomeLines ? `Outcomes:
416
+ ${outcomeLines}` : null
417
+ ].filter(Boolean).join("\n");
418
+ }
419
+
420
+ // src/tools.ts
421
+ function text(t) {
422
+ return { content: [{ type: "text", text: t }] };
423
+ }
424
+ function registerTools(server, api, auth) {
425
+ server.tool(
426
+ "auth_login",
427
+ "Authenticate with SaySo. Run this first. Generates nonce, signs with your wallet, and logs in. Optionally set your display name.",
428
+ { display_name: z.string().optional().describe("Set your display name on SaySo") },
429
+ async ({ display_name }) => {
430
+ const state = await auth.login();
431
+ if (display_name) {
432
+ await api.updateProfile({ display_name });
433
+ }
434
+ return text(
435
+ `Authenticated as ${state.walletAddress}
436
+ User ID: ${state.userId}
437
+ ` + (display_name ? `Display name set to: ${display_name}
438
+ ` : "") + `Token valid for 7 days.`
439
+ );
440
+ }
441
+ );
442
+ server.tool(
443
+ "browse_markets",
444
+ "Search prediction markets. Returns a list of markets with probabilities.",
445
+ {
446
+ status: z.enum(["open", "closed", "resolved", "cancelled", "suspended", "pending_resolution"]).optional().describe("Filter by status (default: open)"),
447
+ tags: z.array(z.string()).optional().describe("Filter by tags"),
448
+ category: z.string().optional().describe("Filter by category"),
449
+ sort_by: z.enum(["created_at", "ends_at", "starts_at"]).optional().describe("Sort field (default: created_at)"),
450
+ limit: z.number().min(1).max(100).optional().describe("Results per page (default: 20)"),
451
+ cursor: z.string().optional().describe("Pagination cursor from previous response")
452
+ },
453
+ async ({ status, tags, category, sort_by, limit, cursor }) => {
454
+ const filters = {};
455
+ if (status) filters.status = [status];
456
+ if (tags) filters.tags = tags;
457
+ if (category) filters.category = category;
458
+ const result = await api.searchMarkets(filters, {
459
+ sort: { columns: [sort_by ?? "created_at"], order: "DESC" },
460
+ limit,
461
+ cursor
462
+ });
463
+ const markets = result.data.map((m) => formatMarket(m));
464
+ const output = markets.length ? markets.join("\n\n---\n\n") : "No markets found.";
465
+ return text(
466
+ output + (result.cursor ? `
467
+
468
+ _Next page cursor: ${result.cursor}_` : "")
469
+ );
470
+ }
471
+ );
472
+ server.tool(
473
+ "get_market",
474
+ "Get full details for a specific prediction market, including outcomes and probabilities.",
475
+ { market_id: z.string().describe("The market ID") },
476
+ async ({ market_id }) => {
477
+ const market = await api.getMarket(market_id);
478
+ return text(formatMarket(market));
479
+ }
480
+ );
481
+ server.tool(
482
+ "place_bet",
483
+ "Place a bet on a market outcome. Shows a preview first, then executes. Amounts are in tokens (e.g. '1.5'), not wei.",
484
+ {
485
+ market_id: z.string().describe("The market ID"),
486
+ outcome_id: z.string().describe("The outcome ID to bet on"),
487
+ amount: z.string().describe("Amount in tokens (e.g. '1.5' = 1.5 tokens)"),
488
+ max_slippage_percent: z.number().optional().describe("Max slippage in percent (default: 5%)"),
489
+ preview_only: z.boolean().optional().describe("If true, only show preview without executing")
490
+ },
491
+ async ({ market_id, outcome_id, amount, max_slippage_percent, preview_only }) => {
492
+ const weiAmount = tokensToWei(amount);
493
+ const slippageBps = Math.round((max_slippage_percent ?? 5) * 100);
494
+ const preview = await api.previewBet(outcome_id, weiAmount);
495
+ const impact = preview.price_impact;
496
+ const returns = preview.potential_returns;
497
+ const previewText = `Bet preview:
498
+ Amount: ${amount} tokens
499
+ Estimated shares: ${weiToTokens(String(preview.estimated_shares))}
500
+ Price per share: ${weiToPercent(String(preview.price_per_share))}
501
+ Price impact: ${impact?.price_change_percent ?? "N/A"}%
502
+ Potential return: ${weiToTokens(String(returns?.if_wins ?? "0"))} tokens (profit: ${weiToTokens(String(returns?.profit ?? "0"))})`;
503
+ if (preview_only) return text(previewText);
504
+ const result = await api.placeBet(market_id, outcome_id, weiAmount, slippageBps);
505
+ return text(
506
+ previewText + `
507
+
508
+ Bet placed!
509
+ Bet ID: ${result.bet_id}
510
+ Shares purchased: ${weiToTokens(String(result.shares_purchased))}
511
+ Total cost: ${weiToTokens(String(result.total_cost))} tokens
512
+ New balance: ${weiToTokens(String(result.new_balance))} tokens`
513
+ );
514
+ }
515
+ );
516
+ server.tool(
517
+ "sell_position",
518
+ "Sell shares in a market position. Shows preview first, then executes.",
519
+ {
520
+ market_id: z.string().describe("The market ID"),
521
+ outcome_id: z.string().describe("The outcome ID"),
522
+ shares: z.string().describe("Number of shares to sell (in tokens, e.g. '1.5')"),
523
+ preview_only: z.boolean().optional().describe("If true, only show preview")
524
+ },
525
+ async ({ market_id, outcome_id, shares, preview_only }) => {
526
+ const weiShares = tokensToWei(shares);
527
+ const preview = await api.previewSell(outcome_id, weiShares);
528
+ const previewText = `Sell preview:
529
+ Shares to sell: ${shares}
530
+ Estimated return: ${weiToTokens(String(preview.estimated_return ?? preview.payout))} tokens
531
+ Price per share: ${weiToPercent(String(preview.price_per_share ?? "0"))}`;
532
+ if (preview_only) return text(previewText);
533
+ const result = await api.sellShares(market_id, outcome_id, weiShares);
534
+ return text(
535
+ previewText + `
536
+
537
+ Shares sold!
538
+ Tokens received: ${weiToTokens(String(result.payout ?? result.tokens_received))} tokens
539
+ New balance: ${weiToTokens(String(result.new_balance ?? "0"))} tokens`
540
+ );
541
+ }
542
+ );
543
+ server.tool(
544
+ "my_positions",
545
+ "View your current holdings across all markets, with P&L.",
546
+ {},
547
+ async () => {
548
+ const result = await api.searchHoldings();
549
+ if (!result.data.length) return text("No positions found.");
550
+ const lines = result.data.map((p) => {
551
+ const pos = p;
552
+ const pnl = pos.unrealized_pnl ? weiToTokens(String(pos.unrealized_pnl)) : "N/A";
553
+ const claimable = pos.claimable_amount && String(pos.claimable_amount) !== "0";
554
+ return `**${pos.market_question}** \u2014 ${pos.outcome_title}
555
+ Shares: ${weiToTokens(String(pos.shares_balance))}
556
+ Cost: ${weiToTokens(String(pos.total_buy_cost))} tokens
557
+ Current price: ${weiToPercent(String(pos.current_price ?? "0"))}
558
+ Unrealised P&L: ${pnl} tokens` + (claimable ? `
559
+ CLAIMABLE: ${weiToTokens(String(pos.claimable_amount))} tokens (position ID: ${pos.id})` : "");
560
+ });
561
+ return text(lines.join("\n\n---\n\n"));
562
+ }
563
+ );
564
+ server.tool(
565
+ "claim_winnings",
566
+ "Claim winnings from a resolved market position.",
567
+ { position_id: z.string().describe("The position ID to claim from") },
568
+ async ({ position_id }) => {
569
+ const result = await api.claimWinnings(position_id);
570
+ return text(
571
+ `Winnings claimed!
572
+ Amount: ${weiToTokens(String(result.amount ?? result.claimed_amount ?? "0"))} tokens`
573
+ );
574
+ }
575
+ );
576
+ server.tool(
577
+ "check_balance",
578
+ "Check your token balance and daily credit eligibility.",
579
+ {},
580
+ async () => {
581
+ const [balances, eligibility] = await Promise.all([
582
+ api.getBalance(),
583
+ api.checkCreditEligibility()
584
+ ]);
585
+ const balance = Array.isArray(balances) && balances[0] ? weiToTokens(String(balances[0].balance ?? "0")) : "0";
586
+ return text(
587
+ `Balance: ${balance} tokens
588
+ Credits eligible: ${eligibility.eligible ? "Yes" : "No"}` + (!eligibility.eligible && eligibility.next_issuance_at ? ` (next: ${eligibility.next_issuance_at})` : "")
589
+ );
590
+ }
591
+ );
592
+ server.tool(
593
+ "claim_credits",
594
+ "Claim your daily free credits.",
595
+ {},
596
+ async () => {
597
+ const eligibility = await api.checkCreditEligibility();
598
+ if (!eligibility.eligible) {
599
+ return text(
600
+ `Not eligible yet. Next claim: ${eligibility.next_issuance_at ?? "unknown"}`
601
+ );
602
+ }
603
+ await api.claimCredits();
604
+ const balances = await api.getBalance();
605
+ const balance = Array.isArray(balances) && balances[0] ? weiToTokens(String(balances[0].balance ?? "0")) : "0";
606
+ return text(`Credits claimed! New balance: ${balance} tokens`);
607
+ }
608
+ );
609
+ server.tool(
610
+ "post_comment",
611
+ "Post a comment on a market. Can also reply to an existing comment.",
612
+ {
613
+ market_id: z.string().describe("The market ID"),
614
+ body: z.string().max(2e3).describe("Your comment text (max 2000 chars)"),
615
+ parent_id: z.string().optional().describe("Reply to this comment ID (for threading)")
616
+ },
617
+ async ({ market_id, body, parent_id }) => {
618
+ const comment = await api.postComment(market_id, body, parent_id);
619
+ return text(
620
+ `Comment posted!
621
+ ID: ${comment.id}
622
+ ` + (parent_id ? ` Reply to: ${parent_id}
623
+ ` : "") + ` Body: ${body}`
624
+ );
625
+ }
626
+ );
627
+ server.tool(
628
+ "browse_comments",
629
+ "Read comments on a market. Can fetch replies to a specific comment.",
630
+ {
631
+ market_id: z.string().describe("The market ID"),
632
+ parent_id: z.string().optional().describe("Fetch replies to this comment"),
633
+ limit: z.number().optional().describe("Results per page (default: 20)"),
634
+ cursor: z.string().optional().describe("Pagination cursor")
635
+ },
636
+ async ({ market_id, parent_id, limit, cursor }) => {
637
+ const result = await api.searchComments(market_id, {
638
+ parentId: parent_id,
639
+ limit,
640
+ cursor
641
+ });
642
+ if (!result.data.length) return text("No comments found.");
643
+ const lines = result.data.map((c) => {
644
+ const comment = c;
645
+ return `**${comment.display_name}** (${comment.created_at})
646
+ ${comment.body}
647
+ Likes: ${comment.like_count}` + (Number(comment.reply_count) > 0 ? ` | Replies: ${comment.reply_count}` : "") + `
648
+ ID: ${comment.id}`;
649
+ });
650
+ return text(
651
+ lines.join("\n\n---\n\n") + (result.cursor ? `
652
+
653
+ _Next page cursor: ${result.cursor}_` : "")
654
+ );
655
+ }
656
+ );
657
+ server.tool(
658
+ "like",
659
+ "Like or unlike a market or comment.",
660
+ {
661
+ target_type: z.enum(["market", "comment"]).describe("What to like"),
662
+ target_id: z.string().describe("The market or comment ID"),
663
+ unlike: z.boolean().optional().describe("Set to true to unlike")
664
+ },
665
+ async ({ target_type, target_id, unlike }) => {
666
+ if (target_type === "market") {
667
+ const result = unlike ? await api.unlikeMarket(target_id) : await api.likeMarket(target_id);
668
+ return text(`${unlike ? "Unliked" : "Liked"} market. Likes: ${result.like_count}`);
669
+ } else {
670
+ const result = unlike ? await api.unlikeComment(target_id) : await api.likeComment(target_id);
671
+ return text(`${unlike ? "Unliked" : "Liked"} comment. Likes: ${result.like_count}`);
672
+ }
673
+ }
674
+ );
675
+ server.tool(
676
+ "leaderboard",
677
+ "View the prediction market leaderboard.",
678
+ {
679
+ rank_by: z.enum(["volume", "pnl", "markets_traded"]).optional().describe("Ranking metric (default: volume)"),
680
+ user_types: z.array(z.string()).optional().describe('Filter by user type, e.g. ["human"] or ["bot"] (default: all)'),
681
+ limit: z.number().optional().describe("Results to show (default: 20)")
682
+ },
683
+ async ({ rank_by, user_types, limit }) => {
684
+ const result = await api.searchLeaderboard({ rankBy: rank_by, userTypes: user_types, limit });
685
+ if (!result.data.length) return text("Leaderboard is empty.");
686
+ const lines = result.data.map((e) => {
687
+ const entry = e;
688
+ return `#${entry.rank} **${entry.display_name ?? entry.user_id}**
689
+ Volume: ${weiToTokens(String(entry.total_volume ?? "0"))} | P&L: ${weiToTokens(String(entry.total_pnl ?? "0"))} | Markets: ${entry.markets_traded ?? 0} | Wins: ${entry.win_count ?? 0}`;
690
+ });
691
+ return text(lines.join("\n"));
692
+ }
693
+ );
694
+ server.tool(
695
+ "follow_user",
696
+ "Follow or unfollow a user.",
697
+ {
698
+ user_id: z.string().describe("The user ID to follow/unfollow"),
699
+ unfollow: z.boolean().optional().describe("Set to true to unfollow")
700
+ },
701
+ async ({ user_id, unfollow }) => {
702
+ const result = unfollow ? await api.unfollowUser(user_id) : await api.followUser(user_id);
703
+ return text(
704
+ `${unfollow ? "Unfollowed" : "Followed"} user. Followers: ${result.followers_count}`
705
+ );
706
+ }
707
+ );
708
+ server.tool(
709
+ "my_feed",
710
+ "Browse your personalised feed \u2014 markets from creators you follow.",
711
+ {
712
+ limit: z.number().optional().describe("Results per page (default: 20)"),
713
+ cursor: z.string().optional().describe("Pagination cursor")
714
+ },
715
+ async ({ limit, cursor }) => {
716
+ const result = await api.searchFeed({ limit, cursor });
717
+ if (!result.data.length)
718
+ return text("Feed is empty. Follow some creators to populate it.");
719
+ const markets = result.data.map((m) => formatMarket(m));
720
+ return text(
721
+ markets.join("\n\n---\n\n") + (result.cursor ? `
722
+
723
+ _Next page cursor: ${result.cursor}_` : "")
724
+ );
725
+ }
726
+ );
727
+ server.tool(
728
+ "get_profile",
729
+ "View your own profile or another user's profile.",
730
+ {
731
+ user_id: z.string().optional().describe("User ID to look up. Omit to view your own profile.")
732
+ },
733
+ async ({ user_id }) => {
734
+ const profile = user_id ? await api.getCreatorProfile(user_id) : await api.getMyProfile();
735
+ const stats = profile.stats;
736
+ const lines = [
737
+ `**${profile.display_name ?? "Anonymous"}**`,
738
+ `ID: ${profile.id ?? profile.user_id}`,
739
+ profile.wallet_address ? `Wallet: ${profile.wallet_address}` : null,
740
+ profile.role ? `Role: ${profile.role}` : null,
741
+ profile.bio ? `Bio: ${profile.bio}` : null,
742
+ stats ? `Markets created: ${stats.markets_created ?? 0} | Resolved: ${stats.markets_resolved ?? 0}` : null,
743
+ stats?.total_volume ? `Total volume: ${weiToTokens(String(stats.total_volume))} tokens` : null,
744
+ stats?.followers_count != null ? `Followers: ${stats.followers_count} | Following: ${stats.following_count ?? 0}` : null,
745
+ profile.created_at ? `Joined: ${profile.created_at}` : null
746
+ ];
747
+ return text(lines.filter(Boolean).join("\n"));
748
+ }
749
+ );
750
+ }
751
+
752
+ // src/index.ts
753
+ async function main() {
754
+ const config = loadConfig();
755
+ const auth = new AuthManager(config);
756
+ const api = new SaySoAPI(config, auth);
757
+ const server = new McpServer({
758
+ name: "sayso-predictions",
759
+ version: "0.1.0"
760
+ });
761
+ registerTools(server, api, auth);
762
+ const transport = new StdioServerTransport();
763
+ await server.connect(transport);
764
+ }
765
+ main().catch((err) => {
766
+ process.stderr.write(`SaySo MCP server failed to start: ${err}
767
+ `);
768
+ process.exit(1);
769
+ });
770
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/auth.ts","../src/api.ts","../src/tools.ts","../src/utils.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { loadConfig } from \"./config.js\";\nimport { AuthManager } from \"./auth.js\";\nimport { SaySoAPI } from \"./api.js\";\nimport { registerTools } from \"./tools.js\";\n\nasync function main() {\n const config = loadConfig();\n const auth = new AuthManager(config);\n const api = new SaySoAPI(config, auth);\n\n const server = new McpServer({\n name: \"sayso-predictions\",\n version: \"0.1.0\",\n });\n\n registerTools(server, api, auth);\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n process.stderr.write(`SaySo MCP server failed to start: ${err}\\n`);\n process.exit(1);\n});\n","export interface SaySoConfig {\n apiBase: string;\n providerId: string;\n tokenId: string;\n privateKey: string;\n}\n\nexport function loadConfig(): SaySoConfig {\n const privateKey = process.env.SAYSO_PRIVATE_KEY;\n if (!privateKey) {\n throw new Error(\n \"SAYSO_PRIVATE_KEY is required. Generate one with:\\n\" +\n 'node -e \"const w = require(\\'ethers\\').Wallet.createRandom(); ' +\n \"console.log('SAYSO_PRIVATE_KEY=' + w.privateKey)\\\"\",\n );\n }\n\n return {\n apiBase:\n process.env.SAYSO_API_BASE ??\n \"https://mcap-api-564778804231.us-east4.run.app\",\n providerId:\n process.env.SAYSO_PROVIDER_ID ??\n \"da32ce28-4eb4-491e-80ce-134b50b5379c\",\n tokenId:\n process.env.SAYSO_TOKEN_ID ?? \"ac9361a7-9ad6-4169-9cd6-ea67fb555eba\",\n privateKey,\n };\n}\n","import { ethers } from \"ethers\";\nimport type { SaySoConfig } from \"./config.js\";\n\ninterface AuthState {\n accessToken: string;\n refreshToken: string;\n expiresAt: number;\n userId: string;\n walletAddress: string;\n}\n\nexport class AuthManager {\n private wallet: ethers.Wallet;\n private state: AuthState | null = null;\n private config: SaySoConfig;\n\n constructor(config: SaySoConfig) {\n this.config = config;\n this.wallet = new ethers.Wallet(config.privateKey);\n }\n\n get walletAddress(): string {\n return this.wallet.address;\n }\n\n get isAuthenticated(): boolean {\n return this.state !== null && Date.now() < this.state.expiresAt;\n }\n\n get userId(): string | null {\n return this.state?.userId ?? null;\n }\n\n /** Get a valid access token, refreshing or re-authenticating as needed */\n async getToken(): Promise<string> {\n if (this.state && Date.now() < this.state.expiresAt - 60_000) {\n return this.state.accessToken;\n }\n\n if (this.state?.refreshToken) {\n try {\n await this.refresh();\n return this.state!.accessToken;\n } catch {\n // Refresh failed, fall through to full login\n }\n }\n\n await this.login();\n return this.state!.accessToken;\n }\n\n /** Full login flow: nonce -> sign -> login -> profile */\n async login(): Promise<AuthState> {\n const { apiBase, providerId } = this.config;\n\n // Step 1: Get nonce\n const nonceRes = await fetch(`${apiBase}/v1/auth/nonce`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n wallet_address: this.wallet.address,\n provider_id: providerId,\n }),\n });\n if (!nonceRes.ok) throw new Error(`Nonce request failed: ${nonceRes.status}`);\n const { nonce } = (await nonceRes.json()) as { nonce: string };\n\n // Step 2: Sign and login\n const signature = await this.wallet.signMessage(\n `Sign this message to authenticate with nonce: ${nonce}`,\n );\n\n const loginRes = await fetch(`${apiBase}/v1/auth/login`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n provider_id: providerId,\n wallet_address: this.wallet.address,\n signature,\n nonce,\n user_type: \"bot\",\n }),\n });\n if (!loginRes.ok) throw new Error(`Login failed: ${loginRes.status}`);\n\n const loginData = (await loginRes.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n\n // Step 3: Get profile\n const profileRes = await fetch(`${apiBase}/v1/auth/profile`, {\n headers: { Authorization: `Bearer ${loginData.access_token}` },\n });\n if (!profileRes.ok)\n throw new Error(`Profile fetch failed: ${profileRes.status}`);\n const profile = (await profileRes.json()) as {\n user_id: string;\n wallet_address: string;\n };\n\n this.state = {\n accessToken: loginData.access_token,\n refreshToken: loginData.refresh_token,\n expiresAt: Date.now() + loginData.expires_in * 1000,\n userId: profile.user_id,\n walletAddress: profile.wallet_address,\n };\n\n return this.state;\n }\n\n /** Refresh the access token */\n private async refresh(): Promise<void> {\n const { apiBase, providerId } = this.config;\n\n const res = await fetch(`${apiBase}/v1/auth/refresh`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n refresh_token: this.state!.refreshToken,\n provider_id: providerId,\n }),\n });\n\n if (!res.ok) throw new Error(`Token refresh failed: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n };\n\n this.state = {\n ...this.state!,\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n }\n}\n","import type { AuthManager } from \"./auth.js\";\nimport type { SaySoConfig } from \"./config.js\";\n\n/** Lightweight API client — all endpoints the MCP tools need. */\nexport class SaySoAPI {\n constructor(\n private config: SaySoConfig,\n private auth: AuthManager,\n ) {}\n\n // ── Helpers ──────────────────────────────────────────────\n\n private get base() {\n return this.config.apiBase;\n }\n\n private async authedHeaders(): Promise<Record<string, string>> {\n const token = await this.auth.getToken();\n return {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n };\n }\n\n private async request<T>(\n path: string,\n init: RequestInit = {},\n ): Promise<T> {\n const res = await fetch(`${this.base}${path}`, init);\n if (!res.ok) {\n const text = await res.text();\n let msg: string;\n try {\n const err = JSON.parse(text);\n msg = err.message ?? err.error ?? text;\n } catch {\n msg = text;\n }\n throw new Error(`API ${res.status}: ${msg}`);\n }\n const ct = res.headers.get(\"content-type\");\n if (ct?.includes(\"application/json\")) return res.json() as Promise<T>;\n return {} as T;\n }\n\n private async get<T>(path: string, authed = false): Promise<T> {\n const headers: Record<string, string> = authed\n ? await this.authedHeaders()\n : { \"Content-Type\": \"application/json\" };\n return this.request<T>(path, { headers });\n }\n\n private async post<T>(\n path: string,\n body: unknown,\n authed = false,\n ): Promise<T> {\n const headers: Record<string, string> = authed\n ? await this.authedHeaders()\n : { \"Content-Type\": \"application/json\" };\n return this.request<T>(path, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n }\n\n private async put<T>(path: string, body: unknown): Promise<T> {\n return this.request<T>(path, {\n method: \"PUT\",\n headers: await this.authedHeaders(),\n body: JSON.stringify(body),\n });\n }\n\n private async del<T>(path: string): Promise<T> {\n return this.request<T>(path, {\n method: \"DELETE\",\n headers: await this.authedHeaders(),\n });\n }\n\n // ── Markets ──────────────────────────────────────────────\n\n async searchMarkets(filters: Record<string, unknown> = {}, options?: {\n sort?: { columns: string[]; order: string };\n limit?: number;\n cursor?: string;\n }) {\n return this.post<{ data: unknown[]; cursor: string | null }>(\n \"/v1/markets/search\",\n {\n filters: { provider_id: this.config.providerId, ...filters },\n sort: options?.sort ?? { columns: [\"created_at\"], order: \"DESC\" },\n limit: options?.limit ?? 20,\n ...(options?.cursor ? { cursor: options.cursor } : {}),\n },\n );\n }\n\n async getMarket(marketId: string) {\n return this.get<Record<string, unknown>>(`/v1/markets/${marketId}`);\n }\n\n async getPriceHistory(marketId: string, outcomeId?: string, range = \"7d\", interval = \"1hour\") {\n return this.post<{ data: unknown[] }>(\"/v1/markets/price-history/search\", {\n filters: {\n market_id: marketId,\n range_type: range,\n interval_type: interval,\n ...(outcomeId ? { outcome_id: outcomeId } : {}),\n },\n });\n }\n\n // ── Trading ──────────────────────────────────────────────\n\n async previewBet(outcomeId: string, amount: string) {\n return this.post<Record<string, unknown>>(\"/v1/markets/bet-preview\", {\n outcome_id: outcomeId,\n amount,\n });\n }\n\n async placeBet(\n marketId: string,\n outcomeId: string,\n amount: string,\n maxSlippageBps = 500,\n ) {\n return this.post<Record<string, unknown>>(\n `/v1/markets/${marketId}/bet`,\n {\n outcome_id: outcomeId,\n amount,\n max_slippage_bps: maxSlippageBps,\n },\n true,\n );\n }\n\n async previewSell(outcomeId: string, sharesAmount: string) {\n return this.post<Record<string, unknown>>(\"/v1/markets/sell-preview\", {\n outcome_id: outcomeId,\n shares_amount: sharesAmount,\n });\n }\n\n async sellShares(\n marketId: string,\n outcomeId: string,\n sharesAmount: string,\n maxSlippageBps = 500,\n ) {\n return this.post<Record<string, unknown>>(\n `/v1/markets/${marketId}/sell`,\n {\n outcome_id: outcomeId,\n shares_amount: sharesAmount,\n max_slippage_bps: maxSlippageBps,\n },\n true,\n );\n }\n\n // ── Positions ────────────────────────────────────────────\n\n async searchHoldings(filters: Record<string, unknown> = {}) {\n return this.post<{ data: unknown[] }>(\n \"/v1/markets/holdings/search\",\n { filters },\n true,\n );\n }\n\n async claimWinnings(positionId: string) {\n return this.post<Record<string, unknown>>(\n `/v1/markets/positions/${positionId}/claim`,\n {},\n true,\n );\n }\n\n // ── Balance & Credits ────────────────────────────────────\n\n async getBalance() {\n const res = await this.post<{ balances?: unknown[] }>(\n \"/v1/users/balance\",\n { token_ids: [this.config.tokenId] },\n true,\n );\n return res.balances ?? [];\n }\n\n async checkCreditEligibility() {\n return this.get<{ eligible: boolean; next_issuance_at: string }>(\n `/v1/credit-tokens/eligibility/${this.config.tokenId}`,\n true,\n );\n }\n\n async claimCredits() {\n return this.post<Record<string, unknown>>(\n \"/v1/credit-tokens/issue\",\n { token_id: this.config.tokenId },\n true,\n );\n }\n\n // ── Comments & Reactions ─────────────────────────────────\n\n async searchComments(\n marketId: string,\n options?: { parentId?: string; limit?: number; cursor?: string },\n ) {\n return this.post<{ data: unknown[]; cursor: string | null }>(\n `/v1/markets/${marketId}/comments/search`,\n {\n filters: options?.parentId ? { parent_id: options.parentId } : {},\n sort: { columns: [\"created_at\"], order: \"DESC\" },\n limit: options?.limit ?? 20,\n ...(options?.cursor ? { cursor: options.cursor } : {}),\n },\n );\n }\n\n async postComment(marketId: string, body: string, parentId?: string) {\n return this.post<Record<string, unknown>>(\n `/v1/markets/${marketId}/comments`,\n { body, ...(parentId ? { parent_id: parentId } : {}) },\n true,\n );\n }\n\n async likeMarket(marketId: string) {\n return this.post<Record<string, unknown>>(\n `/v1/markets/${marketId}/like`,\n {},\n true,\n );\n }\n\n async unlikeMarket(marketId: string) {\n return this.del<Record<string, unknown>>(`/v1/markets/${marketId}/like`);\n }\n\n async likeComment(commentId: string) {\n return this.post<Record<string, unknown>>(\n `/v1/markets/comments/${commentId}/like`,\n {},\n true,\n );\n }\n\n async unlikeComment(commentId: string) {\n return this.del<Record<string, unknown>>(\n `/v1/markets/comments/${commentId}/like`,\n );\n }\n\n // ── Leaderboard ──────────────────────────────────────────\n\n async searchLeaderboard(options?: {\n rankBy?: string;\n seasonId?: string;\n userTypes?: string[];\n limit?: number;\n }) {\n return this.post<{ data: unknown[] }>(\"/v1/markets/leaderboard/search\", {\n filters: {\n provider_id: this.config.providerId,\n ...(options?.rankBy ? { rank_by: options.rankBy } : {}),\n ...(options?.userTypes?.length ? { user_types: options.userTypes } : {}),\n },\n limit: options?.limit ?? 20,\n });\n }\n\n // ── Social ───────────────────────────────────────────────\n\n async followUser(userId: string) {\n return this.post<Record<string, unknown>>(\n `/v1/users/${userId}/follow`,\n {},\n true,\n );\n }\n\n async unfollowUser(userId: string) {\n return this.del<Record<string, unknown>>(`/v1/users/${userId}/follow`);\n }\n\n async searchFeed(options?: { limit?: number; cursor?: string }) {\n return this.post<{ data: unknown[]; cursor: string | null }>(\n \"/v1/feed/search\",\n {\n filters: { provider_id: this.config.providerId },\n sort: { columns: [\"created_at\"], order: \"DESC\" },\n limit: options?.limit ?? 20,\n ...(options?.cursor ? { cursor: options.cursor } : {}),\n },\n true,\n );\n }\n\n // ── Profile ──────────────────────────────────────────────\n\n async getMyProfile() {\n return this.get<Record<string, unknown>>(\"/v1/auth/profile\", true);\n }\n\n async getCreatorProfile(userId: string) {\n return this.get<Record<string, unknown>>(`/v1/creators/${userId}`);\n }\n\n async updateProfile(data: { display_name?: string; avatar_url?: string }) {\n return this.put<Record<string, unknown>>(\"/v1/users/profile\", data);\n }\n\n // ── Transactions ─────────────────────────────────────────\n\n async searchTransactions(options?: { limit?: number; cursor?: string }) {\n return this.post<{ data: unknown[]; cursor: string | null }>(\n \"/v1/users/transactions/search\",\n {\n filters: { provider_id: this.config.providerId },\n limit: options?.limit ?? 20,\n ...(options?.cursor ? { cursor: options.cursor } : {}),\n },\n true,\n );\n }\n}\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport type { SaySoAPI } from \"./api.js\";\nimport type { AuthManager } from \"./auth.js\";\nimport { tokensToWei, weiToTokens, weiToPercent, formatMarket } from \"./utils.js\";\n\nfunction text(t: string) {\n return { content: [{ type: \"text\" as const, text: t }] };\n}\n\nexport function registerTools(\n server: McpServer,\n api: SaySoAPI,\n auth: AuthManager,\n) {\n // ── auth_login ─────────────────────────────────────────\n\n server.tool(\n \"auth_login\",\n \"Authenticate with SaySo. Run this first. Generates nonce, signs with your wallet, and logs in. Optionally set your display name.\",\n { display_name: z.string().optional().describe(\"Set your display name on SaySo\") },\n async ({ display_name }) => {\n const state = await auth.login();\n\n if (display_name) {\n await api.updateProfile({ display_name });\n }\n\n return text(\n `Authenticated as ${state.walletAddress}\\n` +\n `User ID: ${state.userId}\\n` +\n (display_name ? `Display name set to: ${display_name}\\n` : \"\") +\n `Token valid for 7 days.`,\n );\n },\n );\n\n // ── browse_markets ─────────────────────────────────────\n\n server.tool(\n \"browse_markets\",\n \"Search prediction markets. Returns a list of markets with probabilities.\",\n {\n status: z\n .enum([\"open\", \"closed\", \"resolved\", \"cancelled\", \"suspended\", \"pending_resolution\"])\n .optional()\n .describe(\"Filter by status (default: open)\"),\n tags: z.array(z.string()).optional().describe(\"Filter by tags\"),\n category: z.string().optional().describe(\"Filter by category\"),\n sort_by: z\n .enum([\"created_at\", \"ends_at\", \"starts_at\"])\n .optional()\n .describe(\"Sort field (default: created_at)\"),\n limit: z.number().min(1).max(100).optional().describe(\"Results per page (default: 20)\"),\n cursor: z.string().optional().describe(\"Pagination cursor from previous response\"),\n },\n async ({ status, tags, category, sort_by, limit, cursor }) => {\n const filters: Record<string, unknown> = {};\n if (status) filters.status = [status];\n if (tags) filters.tags = tags;\n if (category) filters.category = category;\n\n const result = await api.searchMarkets(filters, {\n sort: { columns: [sort_by ?? \"created_at\"], order: \"DESC\" },\n limit,\n cursor,\n });\n\n const markets = result.data.map((m) => formatMarket(m as Record<string, unknown>));\n const output = markets.length\n ? markets.join(\"\\n\\n---\\n\\n\")\n : \"No markets found.\";\n\n return text(\n output + (result.cursor ? `\\n\\n_Next page cursor: ${result.cursor}_` : \"\"),\n );\n },\n );\n\n // ── get_market ─────────────────────────────────────────\n\n server.tool(\n \"get_market\",\n \"Get full details for a specific prediction market, including outcomes and probabilities.\",\n { market_id: z.string().describe(\"The market ID\") },\n async ({ market_id }) => {\n const market = await api.getMarket(market_id);\n return text(formatMarket(market));\n },\n );\n\n // ── place_bet ──────────────────────────────────────────\n\n server.tool(\n \"place_bet\",\n \"Place a bet on a market outcome. Shows a preview first, then executes. Amounts are in tokens (e.g. '1.5'), not wei.\",\n {\n market_id: z.string().describe(\"The market ID\"),\n outcome_id: z.string().describe(\"The outcome ID to bet on\"),\n amount: z.string().describe(\"Amount in tokens (e.g. '1.5' = 1.5 tokens)\"),\n max_slippage_percent: z\n .number()\n .optional()\n .describe(\"Max slippage in percent (default: 5%)\"),\n preview_only: z\n .boolean()\n .optional()\n .describe(\"If true, only show preview without executing\"),\n },\n async ({ market_id, outcome_id, amount, max_slippage_percent, preview_only }) => {\n const weiAmount = tokensToWei(amount);\n const slippageBps = Math.round((max_slippage_percent ?? 5) * 100);\n\n // Preview\n const preview = await api.previewBet(outcome_id, weiAmount);\n const impact = preview.price_impact as Record<string, unknown> | undefined;\n const returns = preview.potential_returns as Record<string, unknown> | undefined;\n const previewText =\n `Bet preview:\\n` +\n ` Amount: ${amount} tokens\\n` +\n ` Estimated shares: ${weiToTokens(String(preview.estimated_shares))}\\n` +\n ` Price per share: ${weiToPercent(String(preview.price_per_share))}\\n` +\n ` Price impact: ${impact?.price_change_percent ?? \"N/A\"}%\\n` +\n ` Potential return: ${weiToTokens(String(returns?.if_wins ?? \"0\"))} tokens (profit: ${weiToTokens(String(returns?.profit ?? \"0\"))})`;\n\n if (preview_only) return text(previewText);\n\n // Execute\n const result = await api.placeBet(market_id, outcome_id, weiAmount, slippageBps);\n return text(\n previewText +\n `\\n\\nBet placed!\\n` +\n ` Bet ID: ${result.bet_id}\\n` +\n ` Shares purchased: ${weiToTokens(String(result.shares_purchased))}\\n` +\n ` Total cost: ${weiToTokens(String(result.total_cost))} tokens\\n` +\n ` New balance: ${weiToTokens(String(result.new_balance))} tokens`,\n );\n },\n );\n\n // ── sell_position ──────────────────────────────────────\n\n server.tool(\n \"sell_position\",\n \"Sell shares in a market position. Shows preview first, then executes.\",\n {\n market_id: z.string().describe(\"The market ID\"),\n outcome_id: z.string().describe(\"The outcome ID\"),\n shares: z.string().describe(\"Number of shares to sell (in tokens, e.g. '1.5')\"),\n preview_only: z.boolean().optional().describe(\"If true, only show preview\"),\n },\n async ({ market_id, outcome_id, shares, preview_only }) => {\n const weiShares = tokensToWei(shares);\n\n const preview = await api.previewSell(outcome_id, weiShares);\n const previewText =\n `Sell preview:\\n` +\n ` Shares to sell: ${shares}\\n` +\n ` Estimated return: ${weiToTokens(String(preview.estimated_return ?? preview.payout))} tokens\\n` +\n ` Price per share: ${weiToPercent(String(preview.price_per_share ?? \"0\"))}`;\n\n if (preview_only) return text(previewText);\n\n const result = await api.sellShares(market_id, outcome_id, weiShares);\n return text(\n previewText +\n `\\n\\nShares sold!\\n` +\n ` Tokens received: ${weiToTokens(String(result.payout ?? result.tokens_received))} tokens\\n` +\n ` New balance: ${weiToTokens(String(result.new_balance ?? \"0\"))} tokens`,\n );\n },\n );\n\n // ── my_positions ───────────────────────────────────────\n\n server.tool(\n \"my_positions\",\n \"View your current holdings across all markets, with P&L.\",\n {},\n async () => {\n const result = await api.searchHoldings();\n if (!result.data.length) return text(\"No positions found.\");\n\n const lines = result.data.map((p: unknown) => {\n const pos = p as Record<string, unknown>;\n const pnl = pos.unrealized_pnl\n ? weiToTokens(String(pos.unrealized_pnl))\n : \"N/A\";\n const claimable = pos.claimable_amount && String(pos.claimable_amount) !== \"0\";\n return (\n `**${pos.market_question}** — ${pos.outcome_title}\\n` +\n ` Shares: ${weiToTokens(String(pos.shares_balance))}\\n` +\n ` Cost: ${weiToTokens(String(pos.total_buy_cost))} tokens\\n` +\n ` Current price: ${weiToPercent(String(pos.current_price ?? \"0\"))}\\n` +\n ` Unrealised P&L: ${pnl} tokens` +\n (claimable\n ? `\\n CLAIMABLE: ${weiToTokens(String(pos.claimable_amount))} tokens (position ID: ${pos.id})`\n : \"\")\n );\n });\n\n return text(lines.join(\"\\n\\n---\\n\\n\"));\n },\n );\n\n // ── claim_winnings ─────────────────────────────────────\n\n server.tool(\n \"claim_winnings\",\n \"Claim winnings from a resolved market position.\",\n { position_id: z.string().describe(\"The position ID to claim from\") },\n async ({ position_id }) => {\n const result = await api.claimWinnings(position_id);\n return text(\n `Winnings claimed!\\n` +\n ` Amount: ${weiToTokens(String(result.amount ?? result.claimed_amount ?? \"0\"))} tokens`,\n );\n },\n );\n\n // ── check_balance ──────────────────────────────────────\n\n server.tool(\n \"check_balance\",\n \"Check your token balance and daily credit eligibility.\",\n {},\n async () => {\n const [balances, eligibility] = await Promise.all([\n api.getBalance(),\n api.checkCreditEligibility(),\n ]);\n\n const balance = Array.isArray(balances) && balances[0]\n ? weiToTokens(String((balances[0] as Record<string, unknown>).balance ?? \"0\"))\n : \"0\";\n\n return text(\n `Balance: ${balance} tokens\\n` +\n `Credits eligible: ${eligibility.eligible ? \"Yes\" : \"No\"}` +\n (!eligibility.eligible && eligibility.next_issuance_at\n ? ` (next: ${eligibility.next_issuance_at})`\n : \"\"),\n );\n },\n );\n\n // ── claim_credits ──────────────────────────────────────\n\n server.tool(\n \"claim_credits\",\n \"Claim your daily free credits.\",\n {},\n async () => {\n const eligibility = await api.checkCreditEligibility();\n if (!eligibility.eligible) {\n return text(\n `Not eligible yet. Next claim: ${eligibility.next_issuance_at ?? \"unknown\"}`,\n );\n }\n\n await api.claimCredits();\n\n // Check balance after claiming to show actual credited amount\n const balances = await api.getBalance();\n const balance = Array.isArray(balances) && balances[0]\n ? weiToTokens(String((balances[0] as Record<string, unknown>).balance ?? \"0\"))\n : \"0\";\n\n return text(`Credits claimed! New balance: ${balance} tokens`);\n },\n );\n\n // ── post_comment ───────────────────────────────────────\n\n server.tool(\n \"post_comment\",\n \"Post a comment on a market. Can also reply to an existing comment.\",\n {\n market_id: z.string().describe(\"The market ID\"),\n body: z.string().max(2000).describe(\"Your comment text (max 2000 chars)\"),\n parent_id: z.string().optional().describe(\"Reply to this comment ID (for threading)\"),\n },\n async ({ market_id, body, parent_id }) => {\n const comment = await api.postComment(market_id, body, parent_id);\n return text(\n `Comment posted!\\n` +\n ` ID: ${comment.id}\\n` +\n (parent_id ? ` Reply to: ${parent_id}\\n` : \"\") +\n ` Body: ${body}`,\n );\n },\n );\n\n // ── browse_comments ────────────────────────────────────\n\n server.tool(\n \"browse_comments\",\n \"Read comments on a market. Can fetch replies to a specific comment.\",\n {\n market_id: z.string().describe(\"The market ID\"),\n parent_id: z.string().optional().describe(\"Fetch replies to this comment\"),\n limit: z.number().optional().describe(\"Results per page (default: 20)\"),\n cursor: z.string().optional().describe(\"Pagination cursor\"),\n },\n async ({ market_id, parent_id, limit, cursor }) => {\n const result = await api.searchComments(market_id, {\n parentId: parent_id,\n limit,\n cursor,\n });\n\n if (!result.data.length) return text(\"No comments found.\");\n\n const lines = result.data.map((c: unknown) => {\n const comment = c as Record<string, unknown>;\n return (\n `**${comment.display_name}** (${comment.created_at})\\n` +\n `${comment.body}\\n` +\n `Likes: ${comment.like_count}` +\n (Number(comment.reply_count) > 0\n ? ` | Replies: ${comment.reply_count}`\n : \"\") +\n `\\nID: ${comment.id}`\n );\n });\n\n return text(\n lines.join(\"\\n\\n---\\n\\n\") +\n (result.cursor ? `\\n\\n_Next page cursor: ${result.cursor}_` : \"\"),\n );\n },\n );\n\n // ── like ───────────────────────────────────────────────\n\n server.tool(\n \"like\",\n \"Like or unlike a market or comment.\",\n {\n target_type: z.enum([\"market\", \"comment\"]).describe(\"What to like\"),\n target_id: z.string().describe(\"The market or comment ID\"),\n unlike: z.boolean().optional().describe(\"Set to true to unlike\"),\n },\n async ({ target_type, target_id, unlike }) => {\n if (target_type === \"market\") {\n const result = unlike\n ? await api.unlikeMarket(target_id)\n : await api.likeMarket(target_id);\n return text(`${unlike ? \"Unliked\" : \"Liked\"} market. Likes: ${result.like_count}`);\n } else {\n const result = unlike\n ? await api.unlikeComment(target_id)\n : await api.likeComment(target_id);\n return text(`${unlike ? \"Unliked\" : \"Liked\"} comment. Likes: ${result.like_count}`);\n }\n },\n );\n\n // ── leaderboard ────────────────────────────────────────\n\n server.tool(\n \"leaderboard\",\n \"View the prediction market leaderboard.\",\n {\n rank_by: z\n .enum([\"volume\", \"pnl\", \"markets_traded\"])\n .optional()\n .describe(\"Ranking metric (default: volume)\"),\n user_types: z\n .array(z.string())\n .optional()\n .describe('Filter by user type, e.g. [\"human\"] or [\"bot\"] (default: all)'),\n limit: z.number().optional().describe(\"Results to show (default: 20)\"),\n },\n async ({ rank_by, user_types, limit }) => {\n const result = await api.searchLeaderboard({ rankBy: rank_by, userTypes: user_types, limit });\n\n if (!result.data.length) return text(\"Leaderboard is empty.\");\n\n const lines = result.data.map((e: unknown) => {\n const entry = e as Record<string, unknown>;\n return (\n `#${entry.rank} **${entry.display_name ?? entry.user_id}**\\n` +\n ` Volume: ${weiToTokens(String(entry.total_volume ?? \"0\"))} | ` +\n `P&L: ${weiToTokens(String(entry.total_pnl ?? \"0\"))} | ` +\n `Markets: ${entry.markets_traded ?? 0} | ` +\n `Wins: ${entry.win_count ?? 0}`\n );\n });\n\n return text(lines.join(\"\\n\"));\n },\n );\n\n // ── follow_user ────────────────────────────────────────\n\n server.tool(\n \"follow_user\",\n \"Follow or unfollow a user.\",\n {\n user_id: z.string().describe(\"The user ID to follow/unfollow\"),\n unfollow: z.boolean().optional().describe(\"Set to true to unfollow\"),\n },\n async ({ user_id, unfollow }) => {\n const result = unfollow\n ? await api.unfollowUser(user_id)\n : await api.followUser(user_id);\n return text(\n `${unfollow ? \"Unfollowed\" : \"Followed\"} user. Followers: ${(result as Record<string, unknown>).followers_count}`,\n );\n },\n );\n\n // ── my_feed ────────────────────────────────────────────\n\n server.tool(\n \"my_feed\",\n \"Browse your personalised feed — markets from creators you follow.\",\n {\n limit: z.number().optional().describe(\"Results per page (default: 20)\"),\n cursor: z.string().optional().describe(\"Pagination cursor\"),\n },\n async ({ limit, cursor }) => {\n const result = await api.searchFeed({ limit, cursor });\n\n if (!result.data.length)\n return text(\"Feed is empty. Follow some creators to populate it.\");\n\n const markets = result.data.map((m) => formatMarket(m as Record<string, unknown>));\n return text(\n markets.join(\"\\n\\n---\\n\\n\") +\n (result.cursor ? `\\n\\n_Next page cursor: ${result.cursor}_` : \"\"),\n );\n },\n );\n\n // ── get_profile ────────────────────────────────────────\n\n server.tool(\n \"get_profile\",\n \"View your own profile or another user's profile.\",\n {\n user_id: z.string().optional().describe(\"User ID to look up. Omit to view your own profile.\"),\n },\n async ({ user_id }) => {\n const profile = user_id\n ? await api.getCreatorProfile(user_id)\n : await api.getMyProfile();\n\n const stats = profile.stats as Record<string, unknown> | undefined;\n const lines = [\n `**${profile.display_name ?? \"Anonymous\"}**`,\n `ID: ${profile.id ?? profile.user_id}`,\n profile.wallet_address ? `Wallet: ${profile.wallet_address}` : null,\n profile.role ? `Role: ${profile.role}` : null,\n profile.bio ? `Bio: ${profile.bio}` : null,\n stats ? `Markets created: ${stats.markets_created ?? 0} | Resolved: ${stats.markets_resolved ?? 0}` : null,\n stats?.total_volume ? `Total volume: ${weiToTokens(String(stats.total_volume))} tokens` : null,\n stats?.followers_count != null ? `Followers: ${stats.followers_count} | Following: ${stats.following_count ?? 0}` : null,\n profile.created_at ? `Joined: ${profile.created_at}` : null,\n ];\n\n return text(lines.filter(Boolean).join(\"\\n\"));\n },\n );\n}\n","const WEI_PER_TOKEN = 10n ** 18n;\n\n/** Convert a human-readable token amount (e.g. \"1.5\") to wei string */\nexport function tokensToWei(tokens: string): string {\n const parts = tokens.split(\".\");\n const whole = parts[0] ?? \"0\";\n const frac = (parts[1] ?? \"\").padEnd(18, \"0\").slice(0, 18);\n const wei = BigInt(whole) * WEI_PER_TOKEN + BigInt(frac);\n return wei.toString();\n}\n\n/** Convert a wei string to human-readable token amount */\nexport function weiToTokens(wei: string): string {\n const n = BigInt(wei);\n const whole = n / WEI_PER_TOKEN;\n const frac = n % WEI_PER_TOKEN;\n if (frac === 0n) return whole.toString();\n const fracStr = frac.toString().padStart(18, \"0\").replace(/0+$/, \"\");\n return `${whole}.${fracStr}`;\n}\n\n/** Convert a wei price (probability) to percentage string */\nexport function weiToPercent(wei: string): string {\n const n = Number(BigInt(wei)) / 1e18;\n return `${(n * 100).toFixed(1)}%`;\n}\n\n/** Format a market for human-readable output */\nexport function formatMarket(m: Record<string, unknown>): string {\n const outcomes = m.outcomes as Array<Record<string, unknown>> | undefined;\n const outcomeLines = outcomes\n ?.map(\n (o) =>\n ` - ${o.title} (${o.id}): ${weiToPercent(String(o.current_price ?? \"0\"))} probability`,\n )\n .join(\"\\n\");\n\n return [\n `**${m.question}**`,\n `ID: ${m.id}`,\n `Status: ${m.status}`,\n m.description ? `Description: ${m.description}` : null,\n m.total_volume\n ? `Volume: ${weiToTokens(String(m.total_volume))} tokens (${m.bet_count} bets)`\n : null,\n m.ends_at ? `Ends: ${m.ends_at}` : null,\n outcomeLines ? `Outcomes:\\n${outcomeLines}` : null,\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n"],"mappings":"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","names":["text"]}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "sayso-mcp-server",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "sayso-mcp": "./dist/index.js"
7
+ },
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "files": ["dist"],
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "dev": "tsup --watch",
14
+ "start": "node dist/index.js",
15
+ "clean": "rm -rf dist .turbo node_modules"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.11.0",
19
+ "ethers": "^6.13.0",
20
+ "zod": "^3.23.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^20.10.6",
24
+ "typescript": "^5.3.3",
25
+ "tsup": "^8.0.1"
26
+ }
27
+ }