polymarket-dvm-mcp 1.0.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.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Polymarket DVM MCP Server
2
+
3
+ A Model Context Protocol (MCP) server that interfaces with the Polymarket Data Verification Mechanism (DVM) via Nostr. This server allows AI agents to search for prediction markets and obtain detailed AI summaries.
4
+
5
+ ## Features
6
+
7
+ - **Search Markets**: Search for prediction markets on Polymarket using keywords.
8
+ - **AI Summary**: Get deep market analysis and AI-generated summaries for specific markets using their numerical IDs.
9
+ - **Payment Handling**: Seamlessly handles Nostr DVM feedback (Kind 7000), providing lightning invoices when payment is required for searches or summaries.
10
+ - **NDK Integration**: Uses the Nostr Dev Kit (NDK) for robust Nostr communication.
11
+
12
+ ## Prerequisites
13
+
14
+ - Node.js (v18 or higher)
15
+ - npm or yarn
16
+
17
+ ## Installation
18
+
19
+ 1. Clone the repository and navigate to the project directory.
20
+ 2. Install dependencies:
21
+ ```bash
22
+ npm install
23
+ ```
24
+ 3. Build the project:
25
+ ```bash
26
+ npm run build
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ The server can be configured via environment variables:
32
+
33
+ - `NOSTR_NSEC`: (Optional) Your Nostr private key in `nsec` format. If not provided, a random identity will be generated for each session.
34
+
35
+ ## Usage
36
+
37
+ ### Running the Server
38
+
39
+ Start the server using `ts-node`:
40
+ ```bash
41
+ npm start
42
+ ```
43
+
44
+ Or run the compiled JavaScript (after building):
45
+ ```bash
46
+ npm run serve
47
+ ```
48
+
49
+ ### MCP Tools
50
+
51
+ The server exposes the following tools to MCP clients (like Claude Desktop):
52
+
53
+ 1. **`search_polymarket`**
54
+ - **Description**: Search for prediction markets. Returns markets with their questions and IDs.
55
+ - **Arguments**: `query` (string)
56
+ - **Note**: May return a lightning invoice during high utilization.
57
+
58
+ 2. **`get_market`**
59
+ - **Description**: Get a detailed AI summary for a specific market.
60
+ - **Arguments**: `marketId` (string, the numerical ID from search results)
61
+ - **Note**: Usually requires payment via lightning invoice.
62
+
63
+ ## Deployment with Docker
64
+
65
+ You can run the MCP server using Docker:
66
+
67
+ 1. Build the image:
68
+ ```bash
69
+ docker build -t polymarket-dvm-mcp .
70
+ ```
71
+ 2. Run the container:
72
+ ```bash
73
+ docker run -i -e NOSTR_NSEC=your_nsec_here polymarket-dvm-mcp
74
+ ```
75
+
76
+ ## Security Note
77
+
78
+ Your `nsec` is sensitive information. Never commit it to version control. Use environment variables to pass it safely to the application.
package/dist/index.js ADDED
@@ -0,0 +1,146 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { NostrClient } from "./ndk.js";
5
+ import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
6
+ const server = new Server({
7
+ name: "polymarket-dvm-mcp",
8
+ version: "0.1.0",
9
+ }, {
10
+ capabilities: {
11
+ tools: {},
12
+ },
13
+ });
14
+ // Helper for structured logging to stderr (to avoid interfering with MCP stdio)
15
+ const log = (level, message, data) => {
16
+ const timestamp = new Date().toISOString();
17
+ console.error(JSON.stringify({ timestamp, level, message, data }));
18
+ };
19
+ const nsec = process.env.NOSTR_NSEC;
20
+ let signer;
21
+ if (nsec) {
22
+ try {
23
+ if (!nsec.startsWith('nsec1')) {
24
+ throw new Error("Invalid nsec format. Must start with 'nsec1'");
25
+ }
26
+ signer = new NDKPrivateKeySigner(nsec);
27
+ log("INFO", "Nostr identity loaded from NOSTR_NSEC");
28
+ }
29
+ catch (e) {
30
+ log("ERROR", `Failed to initialize Nostr signer: ${e.message}`);
31
+ process.exit(1);
32
+ }
33
+ }
34
+ else {
35
+ log("INFO", "No NOSTR_NSEC provided, generating random identity");
36
+ signer = NDKPrivateKeySigner.generate();
37
+ }
38
+ const nostrClient = new NostrClient(signer);
39
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
40
+ return {
41
+ tools: [
42
+ {
43
+ name: "search_polymarket",
44
+ description: "Search for prediction markets on Polymarket. Returns a list of markets with their questions, descriptions, and numerical 'id' values. Use these numerical IDs to call 'get_market' for detailed analysis. Note: May return a JSON object with a lightning invoice if payment is required due to high utilization.",
45
+ inputSchema: {
46
+ type: "object",
47
+ properties: {
48
+ query: {
49
+ type: "string",
50
+ description: "The search term or question to search for (e.g., 'Will Bitcoin hit $100k?').",
51
+ },
52
+ },
53
+ required: ["query"],
54
+ },
55
+ },
56
+ {
57
+ name: "get_market",
58
+ description: "Get a detailed AI summary and deep market analysis for a specific Polymarket event. Requires a numerical market 'id' obtained from 'search_polymarket'. If the DVM requires payment for the AI summary, this tool will return a lightning invoice to be paid before the summary is provided.",
59
+ inputSchema: {
60
+ type: "object",
61
+ properties: {
62
+ marketId: {
63
+ type: "string",
64
+ description: "The numerical market ID from search results (e.g., '956590').",
65
+ },
66
+ },
67
+ required: ["marketId"],
68
+ },
69
+ },
70
+ ],
71
+ };
72
+ });
73
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
74
+ const { name, arguments: args } = request.params;
75
+ try {
76
+ log("DEBUG", `Connecting to Nostr relays for tool: ${name}`);
77
+ await nostrClient.connect();
78
+ if (name === "search_polymarket") {
79
+ const query = String(args?.query);
80
+ log("INFO", "Performing Polymarket search", { query });
81
+ const result = await nostrClient.requestSearch(query);
82
+ if ("status" in result && result.status === "payment-required") {
83
+ return {
84
+ content: [
85
+ {
86
+ type: "text",
87
+ text: `Payment required to perform this search due to high utilization.\n\nAmount: ${result.amount}\nInvoice: ${result.invoice}\n\nPlease pay the invoice and call this tool again once paid.`,
88
+ },
89
+ ],
90
+ };
91
+ }
92
+ return {
93
+ content: [
94
+ {
95
+ type: "text",
96
+ text: JSON.stringify(result, null, 2),
97
+ },
98
+ ],
99
+ };
100
+ }
101
+ if (name === "get_market") {
102
+ const marketId = String(args?.marketId);
103
+ log("INFO", "Fetching market summary", { marketId });
104
+ const result = await nostrClient.requestSummary(marketId);
105
+ if ("status" in result && result.status === "payment-required") {
106
+ return {
107
+ content: [
108
+ {
109
+ type: "text",
110
+ text: `Payment required to access this summary.\n\nAmount: ${result.amount}\nInvoice: ${result.invoice}\n\nPlease pay the invoice and call this tool again once paid.`,
111
+ },
112
+ ],
113
+ };
114
+ }
115
+ return {
116
+ content: [
117
+ {
118
+ type: "text",
119
+ text: JSON.stringify(result, null, 2),
120
+ },
121
+ ],
122
+ };
123
+ }
124
+ throw new Error(`Unknown tool: ${name}`);
125
+ }
126
+ catch (error) {
127
+ return {
128
+ content: [
129
+ {
130
+ type: "text",
131
+ text: `Error: ${error.message}`,
132
+ },
133
+ ],
134
+ isError: true,
135
+ };
136
+ }
137
+ });
138
+ async function main() {
139
+ const transport = new StdioServerTransport();
140
+ await server.connect(transport);
141
+ console.error("Polymarket DVM MCP server running on stdio");
142
+ }
143
+ main().catch((error) => {
144
+ console.error("Server error:", error);
145
+ process.exit(1);
146
+ });
package/dist/ndk.js ADDED
@@ -0,0 +1,94 @@
1
+ import NDK, { NDKEvent, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
2
+ import WebSocket from 'ws';
3
+ // Required for NDK in Node.js
4
+ // @ts-ignore
5
+ global.WebSocket = WebSocket;
6
+ export const DVM_PUBKEY = '813c654f1b7a4996c8f4769079288a0eace4380dd55e71949116100759b9dddc';
7
+ export const RELAYS = ['wss://relay.damus.io', 'wss://nos.lol', 'wss://relay.primal.net'];
8
+ export const KIND_SEARCH_REQUEST = 5005;
9
+ export const KIND_SEARCH_RESULT = 6005;
10
+ export const KIND_SUMMARY_REQUEST = 5001;
11
+ export const KIND_SUMMARY_RESULT = 6001;
12
+ export const KIND_FEEDBACK = 7000;
13
+ export class NostrClient {
14
+ ndk;
15
+ constructor(signer) {
16
+ this.ndk = new NDK({
17
+ explicitRelayUrls: RELAYS,
18
+ signer: signer || NDKPrivateKeySigner.generate(),
19
+ });
20
+ }
21
+ async connect() {
22
+ await this.ndk.connect();
23
+ console.error('Connected to relays');
24
+ }
25
+ async requestSearch(query) {
26
+ const event = new NDKEvent(this.ndk);
27
+ event.kind = KIND_SEARCH_REQUEST;
28
+ event.content = query;
29
+ event.tags = [
30
+ ['p', DVM_PUBKEY],
31
+ ['i', query],
32
+ ];
33
+ await event.publish();
34
+ return await this.waitForDVM(event, [KIND_SEARCH_RESULT, KIND_FEEDBACK]);
35
+ }
36
+ async requestSummary(marketId) {
37
+ const event = new NDKEvent(this.ndk);
38
+ event.kind = KIND_SUMMARY_REQUEST;
39
+ event.content = marketId;
40
+ event.tags = [
41
+ ['p', DVM_PUBKEY],
42
+ ['i', marketId],
43
+ ];
44
+ await event.publish();
45
+ return this.waitForDVM(event, [KIND_SUMMARY_RESULT, KIND_FEEDBACK]);
46
+ }
47
+ async waitForDVM(requestEvent, kinds, timeoutMs = 60000) {
48
+ return new Promise((resolve, reject) => {
49
+ let resolved = false;
50
+ const filter = {
51
+ kinds: kinds,
52
+ '#e': [requestEvent.id],
53
+ authors: [DVM_PUBKEY],
54
+ };
55
+ const timeout = setTimeout(() => {
56
+ if (!resolved) {
57
+ resolved = true;
58
+ sub.stop();
59
+ reject(new Error(`Timeout waiting for DVM response (Event ID: ${requestEvent.id})`));
60
+ }
61
+ }, timeoutMs);
62
+ const sub = this.ndk.subscribe(filter, { closeOnEose: false });
63
+ sub.on('event', (event) => {
64
+ if (resolved)
65
+ return;
66
+ resolved = true;
67
+ clearTimeout(timeout);
68
+ sub.stop();
69
+ try {
70
+ if (event.kind === KIND_FEEDBACK) {
71
+ const statusTag = event.tags.find(t => t[0] === 'status');
72
+ const amountTag = event.tags.find(t => t[0] === 'amount');
73
+ resolve({
74
+ status: statusTag ? statusTag[1] : 'unknown',
75
+ amount: amountTag ? amountTag[1] : undefined,
76
+ invoice: amountTag ? amountTag[2] : undefined,
77
+ content: event.content
78
+ });
79
+ }
80
+ else {
81
+ resolve(JSON.parse(event.content));
82
+ }
83
+ }
84
+ catch (e) {
85
+ reject(new Error(`Failed to parse DVM response: ${e}`));
86
+ }
87
+ });
88
+ });
89
+ }
90
+ async close() {
91
+ // NDK doesn't have a direct close, but we can stop all subs if needed.
92
+ // Relays are handled by NDK internally.
93
+ }
94
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "polymarket-dvm-mcp",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "start": "ts-node src/index.ts",
13
+ "build": "tsc",
14
+ "serve": "node dist/index.js"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "dependencies": {
20
+ "@modelcontextprotocol/sdk": "^1.26.0",
21
+ "@nostr-dev-kit/ndk": "^3.0.0",
22
+ "@types/node": "^25.2.3",
23
+ "ts-node": "^10.9.2",
24
+ "typescript": "^5.9.3",
25
+ "ws": "^8.19.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/ws": "^8.18.1"
29
+ }
30
+ }