@solworks/poll-mcp 0.1.19 → 0.1.22
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/dist/index.js
CHANGED
|
@@ -3,9 +3,12 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { PollClient } from '@solworks/poll-api-client';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
6
7
|
import { TOOL_DEFINITIONS } from './tools/index.js';
|
|
7
8
|
import { RESOURCE_DEFINITIONS } from './resources/index.js';
|
|
8
9
|
import { PROMPT_DEFINITIONS } from './prompts/index.js';
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version: PKG_VERSION } = require('../package.json');
|
|
9
12
|
/**
|
|
10
13
|
* Convert a JSON Schema property type to a Zod schema.
|
|
11
14
|
*/
|
|
@@ -23,7 +26,7 @@ function jsonPropToZod(prop) {
|
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
export function createServer(config, clientOverride) {
|
|
26
|
-
const server = new McpServer({ name: 'poll-fun', version:
|
|
29
|
+
const server = new McpServer({ name: 'poll-fun', version: PKG_VERSION }, { capabilities: { tools: {}, resources: { subscribe: true, listChanged: true }, prompts: {} } });
|
|
27
30
|
const client = clientOverride ??
|
|
28
31
|
new PollClient({
|
|
29
32
|
apiUrl: config?.apiUrl ?? 'https://api.poll.fun',
|
package/dist/tools/index.js
CHANGED
|
@@ -405,7 +405,7 @@ export const TOOL_DEFINITIONS = {
|
|
|
405
405
|
if (!options || !Array.isArray(options) || options.length < 2)
|
|
406
406
|
return errorResult('Missing or invalid parameter: options (must be an array with at least 2 items)');
|
|
407
407
|
try {
|
|
408
|
-
const
|
|
408
|
+
const result = await client.createBet({
|
|
409
409
|
question,
|
|
410
410
|
expectedUserCount: 2,
|
|
411
411
|
minimumVoteCount: 2,
|
|
@@ -414,7 +414,7 @@ export const TOOL_DEFINITIONS = {
|
|
|
414
414
|
minimumWagerAmount: args.amount,
|
|
415
415
|
allWagersDueBy: args.expiresAt,
|
|
416
416
|
});
|
|
417
|
-
return textResult(`Bet created successfully!\n Question: ${
|
|
417
|
+
return textResult(`Bet created successfully!\n Question: ${question}\n Options: ${options.join(', ')}\n Address: ${result.relatedAddress}`);
|
|
418
418
|
}
|
|
419
419
|
catch (err) {
|
|
420
420
|
return errorResult(err.message);
|
|
@@ -437,6 +437,10 @@ export const TOOL_DEFINITIONS = {
|
|
|
437
437
|
if (!betAddress)
|
|
438
438
|
return errorResult('Missing required parameter: betAddress');
|
|
439
439
|
try {
|
|
440
|
+
const bet = await client.getBet(betAddress);
|
|
441
|
+
if (!bet.isPublic) {
|
|
442
|
+
return errorResult('This is a private bet. You can only join private bets via the share link in the app.');
|
|
443
|
+
}
|
|
440
444
|
await client.joinBet(betAddress);
|
|
441
445
|
return textResult(`Successfully joined bet: ${betAddress}`);
|
|
442
446
|
}
|
|
@@ -486,19 +490,23 @@ export const TOOL_DEFINITIONS = {
|
|
|
486
490
|
type: 'object',
|
|
487
491
|
properties: {
|
|
488
492
|
betAddress: { type: 'string', description: 'Address of the bet' },
|
|
489
|
-
wagerAddress: { type: 'string', description: 'Address of the wager to cancel' },
|
|
490
493
|
},
|
|
491
|
-
required: ['betAddress'
|
|
494
|
+
required: ['betAddress'],
|
|
492
495
|
},
|
|
493
496
|
requiredScope: 'bet:write',
|
|
494
497
|
async handler(args, client) {
|
|
495
498
|
const betAddress = args.betAddress;
|
|
496
|
-
const wagerAddress = args.wagerAddress;
|
|
497
499
|
if (!betAddress)
|
|
498
500
|
return errorResult('Missing required parameter: betAddress');
|
|
499
|
-
if (!wagerAddress)
|
|
500
|
-
return errorResult('Missing required parameter: wagerAddress');
|
|
501
501
|
try {
|
|
502
|
+
const wagers = await client.getMyWagers();
|
|
503
|
+
const activeWager = wagers.find((w) => {
|
|
504
|
+
const addr = w.betAddress ?? w.programBetBetAddress;
|
|
505
|
+
return addr === betAddress && w.amount > 0;
|
|
506
|
+
});
|
|
507
|
+
if (!activeWager) {
|
|
508
|
+
return errorResult(`You don't have an active wager on bet: ${betAddress}`);
|
|
509
|
+
}
|
|
502
510
|
await client.cancelWager({ marketPubkey: betAddress });
|
|
503
511
|
return textResult(`Wager cancelled successfully for bet: ${betAddress}`);
|
|
504
512
|
}
|
|
@@ -523,6 +531,11 @@ export const TOOL_DEFINITIONS = {
|
|
|
523
531
|
if (!betAddress)
|
|
524
532
|
return errorResult('Missing required parameter: betAddress');
|
|
525
533
|
try {
|
|
534
|
+
const bet = await client.getBet(betAddress);
|
|
535
|
+
const status = (bet.status ?? '').toLowerCase();
|
|
536
|
+
if (status === 'resolving' || status === 'resolved' || status === 'distributed') {
|
|
537
|
+
return errorResult(`Vote has already been initiated for this bet (status: ${bet.status})`);
|
|
538
|
+
}
|
|
526
539
|
await client.initiateVote(betAddress);
|
|
527
540
|
return textResult(`Vote initiated for bet: ${betAddress}`);
|
|
528
541
|
}
|
|
@@ -551,6 +564,14 @@ export const TOOL_DEFINITIONS = {
|
|
|
551
564
|
if (optionIndex === undefined || optionIndex === null)
|
|
552
565
|
return errorResult('Missing required parameter: optionIndex');
|
|
553
566
|
try {
|
|
567
|
+
const [bet, account] = await Promise.all([
|
|
568
|
+
client.getBet(betAddress),
|
|
569
|
+
client.getAccount(),
|
|
570
|
+
]);
|
|
571
|
+
const alreadyVoted = Array.isArray(bet.votes) && bet.votes.some((v) => v.user === account.uuid || v.userUuid === account.uuid);
|
|
572
|
+
if (alreadyVoted) {
|
|
573
|
+
return errorResult('You have already voted on this bet');
|
|
574
|
+
}
|
|
554
575
|
const outcome = optionIndex === 0 ? 'for' : 'against';
|
|
555
576
|
await client.vote({ marketPubkey: betAddress, outcome: outcome });
|
|
556
577
|
return textResult(`Vote cast successfully on bet ${betAddress} for option ${optionIndex} (${outcome})`);
|
|
@@ -576,6 +597,24 @@ export const TOOL_DEFINITIONS = {
|
|
|
576
597
|
if (!betAddress)
|
|
577
598
|
return errorResult('Missing required parameter: betAddress');
|
|
578
599
|
try {
|
|
600
|
+
const bet = await client.getBet(betAddress);
|
|
601
|
+
const status = (bet.status ?? '').toLowerCase();
|
|
602
|
+
if (status === 'distributed') {
|
|
603
|
+
return errorResult('This bet has already been settled and distributed');
|
|
604
|
+
}
|
|
605
|
+
if (status !== 'resolving' && status !== 'resolved') {
|
|
606
|
+
return errorResult(`Bet cannot be settled in its current status: ${bet.status}. Vote must be initiated first.`);
|
|
607
|
+
}
|
|
608
|
+
const wagerUsers = new Set((bet.wagers ?? []).filter((w) => w.amount > 0).map((w) => w.user));
|
|
609
|
+
const votedUsers = new Set((bet.votes ?? []).map((v) => v.user));
|
|
610
|
+
const notVoted = [...wagerUsers].filter((u) => !votedUsers.has(u));
|
|
611
|
+
if (notVoted.length > 0) {
|
|
612
|
+
const names = notVoted.map((u) => {
|
|
613
|
+
const wager = bet.wagers.find((w) => w.user === u);
|
|
614
|
+
return wager?.userDisplayName ?? u;
|
|
615
|
+
});
|
|
616
|
+
return errorResult(`Cannot settle: ${notVoted.length} participant(s) have not voted yet: ${names.join(', ')}`);
|
|
617
|
+
}
|
|
579
618
|
await client.settleBet(betAddress);
|
|
580
619
|
return textResult(`Bet settled successfully: ${betAddress}`);
|
|
581
620
|
}
|
|
@@ -610,6 +649,36 @@ export const TOOL_DEFINITIONS = {
|
|
|
610
649
|
},
|
|
611
650
|
},
|
|
612
651
|
// ── Social write tools (scope: social:write) ──────────────────────
|
|
652
|
+
get_friend_requests: {
|
|
653
|
+
name: 'get_friend_requests',
|
|
654
|
+
description: 'Get your pending friend requests (received and sent)',
|
|
655
|
+
inputSchema: { type: 'object', properties: {} },
|
|
656
|
+
requiredScope: 'read',
|
|
657
|
+
async handler(_args, client) {
|
|
658
|
+
try {
|
|
659
|
+
const [received, sent] = await Promise.all([
|
|
660
|
+
client.getReceivedFriendRequests(),
|
|
661
|
+
client.getSentFriendRequests(),
|
|
662
|
+
]);
|
|
663
|
+
const receivedLines = received.length > 0
|
|
664
|
+
? received.map((r, i) => ` ${i + 1}. From: ${r.sender?.displayName ?? 'Unknown'} (${r.sender?.uuid ?? '?'}) — Request ID: ${r.id} — ${r.createdAt}`)
|
|
665
|
+
: [' None'];
|
|
666
|
+
const sentLines = sent.length > 0
|
|
667
|
+
? sent.map((r, i) => ` ${i + 1}. To: ${r.receiver?.displayName ?? 'Unknown'} (${r.receiver?.uuid ?? '?'}) — Request ID: ${r.id} — ${r.createdAt}`)
|
|
668
|
+
: [' None'];
|
|
669
|
+
return textResult([
|
|
670
|
+
`Received Friend Requests (${received.length}):`,
|
|
671
|
+
...receivedLines,
|
|
672
|
+
'',
|
|
673
|
+
`Sent Friend Requests (${sent.length}):`,
|
|
674
|
+
...sentLines,
|
|
675
|
+
].join('\n'));
|
|
676
|
+
}
|
|
677
|
+
catch (err) {
|
|
678
|
+
return errorResult(err.message);
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
},
|
|
613
682
|
send_friend_request: {
|
|
614
683
|
name: 'send_friend_request',
|
|
615
684
|
description: 'Send a friend request to another user',
|
package/package.json
CHANGED
|
@@ -19,13 +19,14 @@ function mockClient(overrides: Partial<PollClient> = {}): PollClient {
|
|
|
19
19
|
getXpBalance: vi.fn().mockResolvedValue({ xp: 1000, error: null }),
|
|
20
20
|
getUsdcBalance: vi.fn().mockResolvedValue({ usdc: 50, error: null }),
|
|
21
21
|
getTrendingBets: vi.fn().mockResolvedValue([
|
|
22
|
-
{ question: 'Trending bet 1',
|
|
22
|
+
{ question: 'Trending bet 1', totalOiFor: 50, totalOiAgainst: 50, betAddress: 'addr1' },
|
|
23
23
|
]),
|
|
24
24
|
getLeaderboard: vi.fn().mockResolvedValue([
|
|
25
25
|
{ userId: 1, uuid: 'u1', displayName: 'Top', rank: 1, points: 999 },
|
|
26
26
|
]),
|
|
27
27
|
getMyBets: vi.fn().mockResolvedValue([]),
|
|
28
28
|
getMyWagers: vi.fn().mockResolvedValue([]),
|
|
29
|
+
trackEvent: vi.fn().mockResolvedValue(undefined),
|
|
29
30
|
...overrides,
|
|
30
31
|
} as unknown as PollClient;
|
|
31
32
|
}
|
|
@@ -81,6 +82,7 @@ describe('MCP Server integration', () => {
|
|
|
81
82
|
'update_display_name',
|
|
82
83
|
'send_friend_request',
|
|
83
84
|
'respond_friend_request',
|
|
85
|
+
'get_friend_requests',
|
|
84
86
|
'favourite_bet',
|
|
85
87
|
'unfavourite_bet',
|
|
86
88
|
];
|
|
@@ -88,7 +90,7 @@ describe('MCP Server integration', () => {
|
|
|
88
90
|
for (const name of expectedTools) {
|
|
89
91
|
expect(toolNames).toContain(name);
|
|
90
92
|
}
|
|
91
|
-
expect(tools.length).toBe(
|
|
93
|
+
expect(tools.length).toBe(28);
|
|
92
94
|
});
|
|
93
95
|
|
|
94
96
|
it('does NOT expose excluded tools', async () => {
|
|
@@ -10,8 +10,8 @@ describe('Bet tool handlers', () => {
|
|
|
10
10
|
it('get_trending_bets returns formatted list', async () => {
|
|
11
11
|
const client = mockClient({
|
|
12
12
|
getTrendingBets: vi.fn().mockResolvedValue([
|
|
13
|
-
{ question: 'Will BTC hit 100k?',
|
|
14
|
-
{ question: 'ETH above 5k?',
|
|
13
|
+
{ question: 'Will BTC hit 100k?', totalOiFor: 3000, totalOiAgainst: 2000, betAddress: 'addr1' },
|
|
14
|
+
{ question: 'ETH above 5k?', totalOiFor: 2000, totalOiAgainst: 1000, betAddress: 'addr2' },
|
|
15
15
|
]),
|
|
16
16
|
});
|
|
17
17
|
const result = await TOOL_DEFINITIONS.get_trending_bets.handler({}, client);
|
|
@@ -28,7 +28,8 @@ describe('Bet tool handlers', () => {
|
|
|
28
28
|
question: 'Will it rain tomorrow?',
|
|
29
29
|
status: 'OPEN',
|
|
30
30
|
options: ['Yes', 'No'],
|
|
31
|
-
|
|
31
|
+
totalOiFor: 600,
|
|
32
|
+
totalOiAgainst: 400,
|
|
32
33
|
createdAt: '2026-01-01T00:00:00Z',
|
|
33
34
|
betAddress: 'addr123',
|
|
34
35
|
}),
|
|
@@ -80,7 +81,8 @@ describe('Bet tool handlers', () => {
|
|
|
80
81
|
|
|
81
82
|
it('place_wager with valid params', async () => {
|
|
82
83
|
const placeWagerFn = vi.fn().mockResolvedValue({});
|
|
83
|
-
const
|
|
84
|
+
const validateWagerSideFn = vi.fn().mockResolvedValue(undefined);
|
|
85
|
+
const client = mockClient({ placeWager: placeWagerFn, validateWagerSide: validateWagerSideFn });
|
|
84
86
|
const result = await TOOL_DEFINITIONS.place_wager.handler(
|
|
85
87
|
{ betAddress: 'addr1', optionIndex: 0, amount: 50 },
|
|
86
88
|
client
|
|
@@ -88,9 +90,9 @@ describe('Bet tool handlers', () => {
|
|
|
88
90
|
expect(result.isError).toBeUndefined();
|
|
89
91
|
expect(result.content[0].text).toContain('Wager placed successfully');
|
|
90
92
|
expect(placeWagerFn).toHaveBeenCalledWith({
|
|
91
|
-
|
|
92
|
-
optionIndex: 0,
|
|
93
|
+
marketPubkey: 'addr1',
|
|
93
94
|
amount: 50,
|
|
95
|
+
side: 'for',
|
|
94
96
|
});
|
|
95
97
|
});
|
|
96
98
|
|