@solworks/poll-mcp 0.1.22 → 0.1.27

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
@@ -26,7 +26,7 @@ function jsonPropToZod(prop) {
26
26
  }
27
27
  }
28
28
  export function createServer(config, clientOverride) {
29
- const server = new McpServer({ name: 'poll-fun', version: PKG_VERSION }, { capabilities: { tools: {}, resources: { subscribe: true, listChanged: true }, prompts: {} } });
29
+ const server = new McpServer({ name: 'poll-fun', version: PKG_VERSION }, { capabilities: { tools: {}, resources: { listChanged: true }, prompts: {} } });
30
30
  const client = clientOverride ??
31
31
  new PollClient({
32
32
  apiUrl: config?.apiUrl ?? 'https://api.poll.fun',
@@ -568,6 +568,10 @@ export const TOOL_DEFINITIONS = {
568
568
  client.getBet(betAddress),
569
569
  client.getAccount(),
570
570
  ]);
571
+ const status = (bet.status ?? '').toLowerCase();
572
+ if (status !== 'resolving') {
573
+ return errorResult(`Cannot vote: voting has not been initiated for this bet (status: ${bet.status}). Use initiate_vote first.`);
574
+ }
571
575
  const alreadyVoted = Array.isArray(bet.votes) && bet.votes.some((v) => v.user === account.uuid || v.userUuid === account.uuid);
572
576
  if (alreadyVoted) {
573
577
  return errorResult('You have already voted on this bet');
@@ -747,6 +751,10 @@ export const TOOL_DEFINITIONS = {
747
751
  if (!betAddress)
748
752
  return errorResult('Missing required parameter: betAddress');
749
753
  try {
754
+ const alreadyFavourited = await client.isBetFavourited(betAddress);
755
+ if (alreadyFavourited) {
756
+ return textResult(`Bet is already in your favourites: ${betAddress}`);
757
+ }
750
758
  await client.favouriteBet(betAddress);
751
759
  return textResult(`Bet added to favourites: ${betAddress}`);
752
760
  }
@@ -771,6 +779,10 @@ export const TOOL_DEFINITIONS = {
771
779
  if (!betAddress)
772
780
  return errorResult('Missing required parameter: betAddress');
773
781
  try {
782
+ const isFavourited = await client.isBetFavourited(betAddress);
783
+ if (!isFavourited) {
784
+ return textResult(`Bet is not in your favourites: ${betAddress}`);
785
+ }
774
786
  await client.unfavouriteBet(betAddress);
775
787
  return textResult(`Bet removed from favourites: ${betAddress}`);
776
788
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solworks/poll-mcp",
3
- "version": "0.1.22",
3
+ "version": "0.1.27",
4
4
  "description": "MCP server for Poll.fun. See documentation at https://dev.poll.fun",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@modelcontextprotocol/sdk": "^1.12.1",
21
- "@solworks/poll-api-client": "latest",
21
+ "@solworks/poll-api-client": "^0.1.18",
22
22
  "zod": "^3.24.0"
23
23
  },
24
24
  "devDependencies": {
@@ -140,6 +140,17 @@ describe('MCP Server integration', () => {
140
140
  expect(prompts.length).toBe(3);
141
141
  });
142
142
 
143
+ it('can read a resource and re-read returns fresh data', async () => {
144
+ const result1 = await mcpClient.readResource({ uri: 'poll://user/balances' });
145
+ expect(result1.contents[0].text).toContain('XP');
146
+
147
+ const result2 = await mcpClient.readResource({ uri: 'poll://user/balances' });
148
+ expect(result2.contents[0].text).toContain('XP');
149
+
150
+ // Verify handler was called twice (no server-side caching)
151
+ expect(client.getXpBalance).toHaveBeenCalledTimes(2);
152
+ });
153
+
143
154
  it('handles API errors gracefully', async () => {
144
155
  const errorClient = mockClient({
145
156
  getAccount: vi.fn().mockRejectedValue(new Error('API Error 401: Unauthorized')),
@@ -106,6 +106,41 @@ describe('Bet tool handlers', () => {
106
106
  expect(result.content[0].text).toContain('Missing required parameter: betAddress');
107
107
  });
108
108
 
109
+ it('vote returns error when voting not initiated', async () => {
110
+ const client = mockClient({
111
+ getBet: vi.fn().mockResolvedValue({
112
+ status: 'Pending',
113
+ votes: [],
114
+ }),
115
+ getAccount: vi.fn().mockResolvedValue({ uuid: 'user-1' }),
116
+ });
117
+ const result = await TOOL_DEFINITIONS.vote.handler(
118
+ { betAddress: 'addr1', optionIndex: 0 },
119
+ client
120
+ );
121
+ expect(result.isError).toBe(true);
122
+ expect(result.content[0].text).toContain('voting has not been initiated');
123
+ });
124
+
125
+ it('vote succeeds when bet is in Resolving status', async () => {
126
+ const voteFn = vi.fn().mockResolvedValue({});
127
+ const client = mockClient({
128
+ getBet: vi.fn().mockResolvedValue({
129
+ status: 'Resolving',
130
+ votes: [],
131
+ }),
132
+ getAccount: vi.fn().mockResolvedValue({ uuid: 'user-1' }),
133
+ vote: voteFn,
134
+ });
135
+ const result = await TOOL_DEFINITIONS.vote.handler(
136
+ { betAddress: 'addr1', optionIndex: 0 },
137
+ client
138
+ );
139
+ expect(result.isError).toBeUndefined();
140
+ expect(result.content[0].text).toContain('Vote cast successfully');
141
+ expect(voteFn).toHaveBeenCalledWith({ marketPubkey: 'addr1', outcome: 'for' });
142
+ });
143
+
109
144
  it('create_bet handles API error', async () => {
110
145
  const client = mockClient({
111
146
  createBet: vi.fn().mockRejectedValue(new Error('API Error 400: Invalid question')),
@@ -23,7 +23,10 @@ describe('Social tool handlers', () => {
23
23
 
24
24
  it('favourite_bet calls client', async () => {
25
25
  const favouriteFn = vi.fn().mockResolvedValue({});
26
- const client = mockClient({ favouriteBet: favouriteFn });
26
+ const client = mockClient({
27
+ isBetFavourited: vi.fn().mockResolvedValue(false),
28
+ favouriteBet: favouriteFn,
29
+ });
27
30
  const result = await TOOL_DEFINITIONS.favourite_bet.handler(
28
31
  { betAddress: 'addr-fav' },
29
32
  client
@@ -33,6 +36,51 @@ describe('Social tool handlers', () => {
33
36
  expect(favouriteFn).toHaveBeenCalledWith('addr-fav');
34
37
  });
35
38
 
39
+ it('favourite_bet returns already-favourited message', async () => {
40
+ const favouriteFn = vi.fn();
41
+ const client = mockClient({
42
+ isBetFavourited: vi.fn().mockResolvedValue(true),
43
+ favouriteBet: favouriteFn,
44
+ });
45
+ const result = await TOOL_DEFINITIONS.favourite_bet.handler(
46
+ { betAddress: 'addr-fav' },
47
+ client
48
+ );
49
+ expect(result.isError).toBeUndefined();
50
+ expect(result.content[0].text).toContain('Bet is already in your favourites: addr-fav');
51
+ expect(favouriteFn).not.toHaveBeenCalled();
52
+ });
53
+
54
+ it('unfavourite_bet removes favourite', async () => {
55
+ const unfavouriteFn = vi.fn().mockResolvedValue({});
56
+ const client = mockClient({
57
+ isBetFavourited: vi.fn().mockResolvedValue(true),
58
+ unfavouriteBet: unfavouriteFn,
59
+ });
60
+ const result = await TOOL_DEFINITIONS.unfavourite_bet.handler(
61
+ { betAddress: 'addr-fav' },
62
+ client
63
+ );
64
+ expect(result.isError).toBeUndefined();
65
+ expect(result.content[0].text).toContain('Bet removed from favourites: addr-fav');
66
+ expect(unfavouriteFn).toHaveBeenCalledWith('addr-fav');
67
+ });
68
+
69
+ it('unfavourite_bet returns not-favourited message', async () => {
70
+ const unfavouriteFn = vi.fn();
71
+ const client = mockClient({
72
+ isBetFavourited: vi.fn().mockResolvedValue(false),
73
+ unfavouriteBet: unfavouriteFn,
74
+ });
75
+ const result = await TOOL_DEFINITIONS.unfavourite_bet.handler(
76
+ { betAddress: 'addr-fav' },
77
+ client
78
+ );
79
+ expect(result.isError).toBeUndefined();
80
+ expect(result.content[0].text).toContain('Bet is not in your favourites: addr-fav');
81
+ expect(unfavouriteFn).not.toHaveBeenCalled();
82
+ });
83
+
36
84
  it('send_friend_request calls client', async () => {
37
85
  const sendFn = vi.fn().mockResolvedValue({});
38
86
  const client = mockClient({ sendFriendRequest: sendFn });