@c0pilot/mcp-polymarket 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,159 @@
1
+ # mcp-polymarket
2
+
3
+ MCP (Model Context Protocol) server and client library for [Polymarket](https://polymarket.com) prediction markets.
4
+
5
+ [![npm version](https://badge.fury.io/js/mcp-polymarket.svg)](https://www.npmjs.com/package/mcp-polymarket)
6
+
7
+ ## Features
8
+
9
+ - **MCP Server**: Run as a standalone MCP server for AI agents
10
+ - **Client Library**: Import and use in your own projects
11
+ - Browse and search prediction markets
12
+ - View order books and market prices
13
+ - Check wallet balance and positions
14
+ - Place and cancel orders
15
+ - Full integration with Polymarket's CLOB API
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install mcp-polymarket
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### As MCP Server
26
+
27
+ #### With Claude Desktop
28
+
29
+ Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`):
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "polymarket": {
35
+ "command": "npx",
36
+ "args": ["mcp-polymarket"],
37
+ "env": {
38
+ "POLYMARKET_PRIVATE_KEY": "0x...",
39
+ "POLYMARKET_FUNDER": "0x..."
40
+ }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ #### Standalone
47
+
48
+ ```bash
49
+ export POLYMARKET_PRIVATE_KEY="0x..."
50
+ export POLYMARKET_FUNDER="0x..."
51
+ npx mcp-polymarket
52
+ ```
53
+
54
+ ### As Library
55
+
56
+ ```typescript
57
+ import { ClobClientWrapper } from 'mcp-polymarket/client';
58
+ import { createConfig } from 'mcp-polymarket/config';
59
+
60
+ // Create config
61
+ const config = createConfig({
62
+ privateKey: '0x...',
63
+ funder: '0x...', // optional
64
+ readonly: false, // optional
65
+ });
66
+
67
+ // Initialize client
68
+ const client = new ClobClientWrapper(config);
69
+ await client.initialize();
70
+
71
+ // Use the client
72
+ const clobClient = client.getClient();
73
+ const orderbook = await clobClient.getOrderBook(tokenId);
74
+ ```
75
+
76
+ ## Configuration
77
+
78
+ ### Environment Variables
79
+
80
+ | Variable | Required | Default | Description |
81
+ |----------|----------|---------|-------------|
82
+ | `POLYMARKET_PRIVATE_KEY` | Yes | - | Wallet private key for signing |
83
+ | `POLYMARKET_FUNDER` | No | derived | Proxy wallet address |
84
+ | `POLYMARKET_API_KEY` | No | derived | API key (auto-derived if not set) |
85
+ | `POLYMARKET_API_SECRET` | No | derived | API secret (auto-derived if not set) |
86
+ | `POLYMARKET_PASSPHRASE` | No | derived | API passphrase (auto-derived if not set) |
87
+ | `POLYMARKET_CHAIN_ID` | No | 137 | Polygon mainnet |
88
+ | `POLYMARKET_READONLY` | No | false | Disable trading tools |
89
+
90
+ ### Finding Your Funder Address
91
+
92
+ Your "funder" is your Polymarket proxy wallet - the address shown on polymarket.com when logged in. If you deposited through Polymarket's UI, funds are in this proxy wallet.
93
+
94
+ ## Available MCP Tools
95
+
96
+ ### Read-Only
97
+
98
+ | Tool | Description |
99
+ |------|-------------|
100
+ | `polymarket_get_markets` | List active prediction markets |
101
+ | `polymarket_get_market` | Get details for a specific market |
102
+ | `polymarket_get_orderbook` | View order book for a token |
103
+ | `polymarket_get_balance` | Check wallet USDC balance |
104
+ | `polymarket_get_positions` | View open orders and positions |
105
+ | `polymarket_get_trades` | Get recent trade history |
106
+
107
+ ### Trading
108
+
109
+ | Tool | Description |
110
+ |------|-------------|
111
+ | `polymarket_place_order` | Place a limit order (BUY/SELL) |
112
+ | `polymarket_cancel_order` | Cancel an open order |
113
+
114
+ ## API Exports
115
+
116
+ ```typescript
117
+ // Main MCP server entry
118
+ import mcp from 'mcp-polymarket';
119
+
120
+ // Client wrapper for Polymarket CLOB
121
+ import { ClobClientWrapper } from 'mcp-polymarket/client';
122
+
123
+ // Configuration utilities
124
+ import { createConfig, loadConfig, Config } from 'mcp-polymarket/config';
125
+
126
+ // Type definitions
127
+ import { MarketInfo, OrderbookInfo, Position } from 'mcp-polymarket/types';
128
+ ```
129
+
130
+ ## Security
131
+
132
+ - Private keys are never logged
133
+ - Use `POLYMARKET_READONLY=true` for safe exploration
134
+ - API credentials auto-derived from private key
135
+ - Input validation on all parameters
136
+
137
+ ## Development
138
+
139
+ ```bash
140
+ # Install dependencies
141
+ npm install
142
+
143
+ # Build
144
+ npm run build
145
+
146
+ # Run tests
147
+ npm test
148
+
149
+ # Run E2E tests (requires env vars)
150
+ npm run test:e2e
151
+ ```
152
+
153
+ ## Related
154
+
155
+ - [@openclaw/polymarket](https://github.com/unsanction/openclaw-polymarket) - OpenClaw plugin using this library
156
+
157
+ ## License
158
+
159
+ MIT
@@ -0,0 +1,15 @@
1
+ import { ClobClient } from "@polymarket/clob-client";
2
+ import { Config } from "./config.js";
3
+ export declare class ClobClientWrapper {
4
+ private client;
5
+ private config;
6
+ constructor(config?: Config);
7
+ initialize(): Promise<void>;
8
+ getClient(): ClobClient;
9
+ isReadonly(): boolean;
10
+ ensureWriteAccess(): void;
11
+ getGammaApiUrl(): string;
12
+ getClobApiUrl(): string;
13
+ getFunder(): string;
14
+ }
15
+ export declare function getClientWrapper(): Promise<ClobClientWrapper>;
@@ -0,0 +1,79 @@
1
+ import { ClobClient } from "@polymarket/clob-client";
2
+ import { Wallet } from "ethers";
3
+ import { getConfig } from "./config.js";
4
+ const CLOB_API_URL = "https://clob.polymarket.com";
5
+ const GAMMA_API_URL = "https://gamma-api.polymarket.com";
6
+ export class ClobClientWrapper {
7
+ client = null;
8
+ config;
9
+ constructor(config) {
10
+ this.config = config || getConfig();
11
+ }
12
+ async initialize() {
13
+ // Skip if already initialized
14
+ if (this.client) {
15
+ return;
16
+ }
17
+ // Cast to any to handle ethers version mismatch between our ethers and @polymarket/clob-client's ethers
18
+ const wallet = new Wallet(this.config.privateKey);
19
+ const funder = this.config.funder;
20
+ // Signature type depends on wallet setup:
21
+ // 0 = Direct EOA (when funder == signer)
22
+ // 1 = Magic/Privy (email wallet)
23
+ // 2 = Browser wallet proxy/GnosisSafe (when funder != signer)
24
+ const signatureType = funder.toLowerCase() === wallet.address.toLowerCase() ? 0 : 2;
25
+ if (this.config.apiKey && this.config.apiSecret && this.config.passphrase) {
26
+ // Use provided API credentials
27
+ this.client = new ClobClient(CLOB_API_URL, this.config.chainId, wallet, {
28
+ key: this.config.apiKey,
29
+ secret: this.config.apiSecret,
30
+ passphrase: this.config.passphrase,
31
+ }, signatureType, funder);
32
+ // Initialized with provided API credentials
33
+ }
34
+ else {
35
+ // Create client with funder to properly derive API credentials
36
+ const tempClient = new ClobClient(CLOB_API_URL, this.config.chainId, wallet, undefined, signatureType, funder);
37
+ try {
38
+ // Try to derive or create API credentials
39
+ const creds = await tempClient.createOrDeriveApiKey();
40
+ this.client = new ClobClient(CLOB_API_URL, this.config.chainId, wallet, creds, signatureType, funder);
41
+ }
42
+ catch {
43
+ // Failed to derive API credentials, using unauthenticated client
44
+ this.client = tempClient;
45
+ }
46
+ }
47
+ }
48
+ getClient() {
49
+ if (!this.client) {
50
+ throw new Error("Client not initialized. Call initialize() first.");
51
+ }
52
+ return this.client;
53
+ }
54
+ isReadonly() {
55
+ return this.config.readonly;
56
+ }
57
+ ensureWriteAccess() {
58
+ if (this.config.readonly) {
59
+ throw new Error("Trading is disabled in readonly mode. Set POLYMARKET_READONLY=false to enable trading.");
60
+ }
61
+ }
62
+ getGammaApiUrl() {
63
+ return GAMMA_API_URL;
64
+ }
65
+ getClobApiUrl() {
66
+ return CLOB_API_URL;
67
+ }
68
+ getFunder() {
69
+ return this.config.funder;
70
+ }
71
+ }
72
+ let clientWrapper = null;
73
+ export async function getClientWrapper() {
74
+ if (!clientWrapper) {
75
+ clientWrapper = new ClobClientWrapper();
76
+ await clientWrapper.initialize();
77
+ }
78
+ return clientWrapper;
79
+ }
@@ -0,0 +1,24 @@
1
+ export interface Config {
2
+ privateKey: string;
3
+ apiKey?: string;
4
+ apiSecret?: string;
5
+ passphrase?: string;
6
+ funder: string;
7
+ chainId: number;
8
+ readonly: boolean;
9
+ }
10
+ export declare function deriveAddressFromPrivateKey(privateKey: string): string;
11
+ /**
12
+ * Create a Config from an object (useful for plugins that pass config directly)
13
+ */
14
+ export declare function createConfig(options: {
15
+ privateKey: string;
16
+ funder?: string;
17
+ apiKey?: string;
18
+ apiSecret?: string;
19
+ passphrase?: string;
20
+ chainId?: number;
21
+ readonly?: boolean;
22
+ }): Config;
23
+ export declare function loadConfig(): Config;
24
+ export declare function getConfig(): Config;
@@ -0,0 +1,55 @@
1
+ import { Wallet } from "ethers";
2
+ function getEnvVar(name, required = false) {
3
+ const value = process.env[name];
4
+ if (required && !value) {
5
+ throw new Error(`Missing required environment variable: ${name}`);
6
+ }
7
+ return value;
8
+ }
9
+ export function deriveAddressFromPrivateKey(privateKey) {
10
+ const wallet = new Wallet(privateKey);
11
+ return wallet.address;
12
+ }
13
+ /**
14
+ * Create a Config from an object (useful for plugins that pass config directly)
15
+ */
16
+ export function createConfig(options) {
17
+ const privateKey = options.privateKey;
18
+ if (!privateKey) {
19
+ throw new Error("privateKey is required");
20
+ }
21
+ return {
22
+ privateKey,
23
+ funder: options.funder || deriveAddressFromPrivateKey(privateKey),
24
+ apiKey: options.apiKey,
25
+ apiSecret: options.apiSecret,
26
+ passphrase: options.passphrase,
27
+ chainId: options.chainId || 137,
28
+ readonly: options.readonly || false,
29
+ };
30
+ }
31
+ export function loadConfig() {
32
+ const privateKey = getEnvVar("POLYMARKET_PRIVATE_KEY", true);
33
+ const funder = getEnvVar("POLYMARKET_FUNDER") || deriveAddressFromPrivateKey(privateKey);
34
+ const chainId = parseInt(getEnvVar("POLYMARKET_CHAIN_ID") || "137", 10);
35
+ const readonly = getEnvVar("POLYMARKET_READONLY")?.toLowerCase() === "true";
36
+ const apiKey = getEnvVar("POLYMARKET_API_KEY");
37
+ const apiSecret = getEnvVar("POLYMARKET_API_SECRET");
38
+ const passphrase = getEnvVar("POLYMARKET_PASSPHRASE");
39
+ return {
40
+ privateKey,
41
+ apiKey,
42
+ apiSecret,
43
+ passphrase,
44
+ funder,
45
+ chainId,
46
+ readonly,
47
+ };
48
+ }
49
+ let cachedConfig = null;
50
+ export function getConfig() {
51
+ if (!cachedConfig) {
52
+ cachedConfig = loadConfig();
53
+ }
54
+ return cachedConfig;
55
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/build/index.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { getClientWrapper } from "./client.js";
5
+ import { registerAllTools } from "./tools/index.js";
6
+ async function main() {
7
+ console.error("Starting Polymarket MCP Server...");
8
+ // Initialize the CLOB client
9
+ const clientWrapper = await getClientWrapper();
10
+ console.error("CLOB client initialized");
11
+ // Create MCP server
12
+ const server = new McpServer({
13
+ name: "polymarket",
14
+ version: "1.0.0",
15
+ });
16
+ // Register all tools
17
+ registerAllTools(server, clientWrapper);
18
+ // Connect via stdio transport
19
+ const transport = new StdioServerTransport();
20
+ await server.connect(transport);
21
+ console.error("Polymarket MCP Server running on stdio");
22
+ }
23
+ main().catch((error) => {
24
+ console.error("Fatal error:", error);
25
+ process.exit(1);
26
+ });
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ClobClientWrapper } from "../client.js";
3
+ export declare function registerAccountTools(server: McpServer, clientWrapper: ClobClientWrapper): void;
@@ -0,0 +1,110 @@
1
+ import { z } from "zod";
2
+ import { AssetType } from "@polymarket/clob-client";
3
+ const GetBalanceSchema = z.object({});
4
+ const GetPositionsSchema = z.object({});
5
+ export function registerAccountTools(server, clientWrapper) {
6
+ server.tool("polymarket_get_balance", "Get the USDC balance and allowance for the configured wallet on Polymarket.", GetBalanceSchema.shape, async () => {
7
+ try {
8
+ const client = clientWrapper.getClient();
9
+ const funder = clientWrapper.getFunder();
10
+ // Get balance allowance for USDC collateral
11
+ const balanceData = await client.getBalanceAllowance({
12
+ asset_type: AssetType.COLLATERAL,
13
+ });
14
+ const result = {
15
+ balance: balanceData.balance || "0",
16
+ allowance: balanceData.allowance || "0",
17
+ };
18
+ return {
19
+ content: [
20
+ {
21
+ type: "text",
22
+ text: JSON.stringify({
23
+ address: funder,
24
+ ...result,
25
+ }, null, 2),
26
+ },
27
+ ],
28
+ };
29
+ }
30
+ catch (error) {
31
+ const message = error instanceof Error ? error.message : String(error);
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: `Error fetching balance: ${message}`,
37
+ },
38
+ ],
39
+ isError: true,
40
+ };
41
+ }
42
+ });
43
+ server.tool("polymarket_get_positions", "Get all open orders and positions for the configured wallet, including P&L calculations.", GetPositionsSchema.shape, async () => {
44
+ try {
45
+ const client = clientWrapper.getClient();
46
+ // Get open orders to derive positions
47
+ const openOrders = (await client.getOpenOrders());
48
+ if (!openOrders || openOrders.length === 0) {
49
+ return {
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: JSON.stringify({ positions: [], open_orders: [] }, null, 2),
54
+ },
55
+ ],
56
+ };
57
+ }
58
+ // Group orders by token to calculate positions
59
+ const positionMap = new Map();
60
+ for (const order of openOrders) {
61
+ const tokenId = order.asset_id;
62
+ const existing = positionMap.get(tokenId);
63
+ if (!existing) {
64
+ positionMap.set(tokenId, {
65
+ token_id: tokenId,
66
+ market: order.market || "Unknown",
67
+ outcome: order.outcome || "Unknown",
68
+ size: order.original_size,
69
+ avg_price: order.price,
70
+ current_price: order.price,
71
+ pnl: "0",
72
+ });
73
+ }
74
+ }
75
+ const positions = Array.from(positionMap.values());
76
+ // Also return raw open orders for transparency
77
+ const formattedOrders = openOrders.map((o) => ({
78
+ id: o.id,
79
+ token_id: o.asset_id,
80
+ side: o.side,
81
+ price: o.price,
82
+ size: o.original_size,
83
+ filled: o.size_matched,
84
+ }));
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text",
89
+ text: JSON.stringify({
90
+ positions,
91
+ open_orders: formattedOrders,
92
+ }, null, 2),
93
+ },
94
+ ],
95
+ };
96
+ }
97
+ catch (error) {
98
+ const message = error instanceof Error ? error.message : String(error);
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text",
103
+ text: `Error fetching positions: ${message}`,
104
+ },
105
+ ],
106
+ isError: true,
107
+ };
108
+ }
109
+ });
110
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ClobClientWrapper } from "../client.js";
3
+ export declare function registerAllTools(server: McpServer, clientWrapper: ClobClientWrapper): void;
@@ -0,0 +1,15 @@
1
+ import { registerMarketTools } from "./markets.js";
2
+ import { registerOrderbookTools } from "./orderbook.js";
3
+ import { registerAccountTools } from "./account.js";
4
+ import { registerTradingTools } from "./trading.js";
5
+ export function registerAllTools(server, clientWrapper) {
6
+ const isReadonly = clientWrapper.isReadonly();
7
+ console.error(`Registering tools (readonly: ${isReadonly})`);
8
+ // Register read-only tools
9
+ registerMarketTools(server, clientWrapper);
10
+ registerOrderbookTools(server, clientWrapper);
11
+ registerAccountTools(server, clientWrapper);
12
+ // Register trading tools (write tools are conditionally included)
13
+ registerTradingTools(server, clientWrapper, !isReadonly);
14
+ console.error("All tools registered successfully");
15
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ClobClientWrapper } from "../client.js";
3
+ export declare function registerMarketTools(server: McpServer, clientWrapper: ClobClientWrapper): void;
@@ -0,0 +1,128 @@
1
+ import { z } from "zod";
2
+ const GetMarketsSchema = z.object({
3
+ limit: z.number().min(1).max(100).optional().default(10),
4
+ offset: z.number().min(0).optional().default(0),
5
+ search: z.string().optional(),
6
+ });
7
+ const GetMarketSchema = z.object({
8
+ condition_id: z.string().min(1),
9
+ });
10
+ async function fetchGammaMarkets(clientWrapper, limit, offset, search) {
11
+ const baseUrl = clientWrapper.getGammaApiUrl();
12
+ const params = new URLSearchParams({
13
+ limit: limit.toString(),
14
+ offset: offset.toString(),
15
+ active: "true",
16
+ });
17
+ if (search) {
18
+ params.set("slug_contains", search.toLowerCase());
19
+ }
20
+ const url = `${baseUrl}/markets?${params.toString()}`;
21
+ const response = await fetch(url);
22
+ if (!response.ok) {
23
+ throw new Error(`Failed to fetch markets: ${response.statusText}`);
24
+ }
25
+ const data = await response.json();
26
+ return Array.isArray(data) ? data : [];
27
+ }
28
+ async function fetchGammaMarket(clientWrapper, conditionId) {
29
+ const baseUrl = clientWrapper.getGammaApiUrl();
30
+ const url = `${baseUrl}/markets/${conditionId}`;
31
+ const response = await fetch(url);
32
+ if (!response.ok) {
33
+ if (response.status === 404) {
34
+ return null;
35
+ }
36
+ throw new Error(`Failed to fetch market: ${response.statusText}`);
37
+ }
38
+ return await response.json();
39
+ }
40
+ function formatMarket(market) {
41
+ const tokens = [];
42
+ const outcomes = market.outcomes || ["Yes", "No"];
43
+ const prices = market.outcomePrices || [];
44
+ const tokenIds = market.clobTokenIds || [];
45
+ for (let i = 0; i < outcomes.length; i++) {
46
+ tokens.push({
47
+ token_id: tokenIds[i] || "",
48
+ outcome: outcomes[i],
49
+ price: prices[i] ? parseFloat(prices[i]) : 0,
50
+ });
51
+ }
52
+ return {
53
+ condition_id: market.conditionId,
54
+ question: market.question,
55
+ tokens,
56
+ volume: market.volume || "0",
57
+ end_date: market.endDate || "",
58
+ active: market.active,
59
+ closed: market.closed,
60
+ };
61
+ }
62
+ export function registerMarketTools(server, clientWrapper) {
63
+ server.tool("polymarket_get_markets", "List available prediction markets on Polymarket. Returns market question, current prices for Yes/No outcomes, and trading volume.", GetMarketsSchema.shape, async (args) => {
64
+ try {
65
+ const { limit, offset, search } = GetMarketsSchema.parse(args);
66
+ const markets = await fetchGammaMarkets(clientWrapper, limit, offset, search);
67
+ const formatted = markets.map(formatMarket);
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: JSON.stringify(formatted, null, 2),
73
+ },
74
+ ],
75
+ };
76
+ }
77
+ catch (error) {
78
+ const message = error instanceof Error ? error.message : String(error);
79
+ return {
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: `Error fetching markets: ${message}`,
84
+ },
85
+ ],
86
+ isError: true,
87
+ };
88
+ }
89
+ });
90
+ server.tool("polymarket_get_market", "Get detailed information about a specific prediction market including token IDs, current prices, and market status.", GetMarketSchema.shape, async (args) => {
91
+ try {
92
+ const { condition_id } = GetMarketSchema.parse(args);
93
+ const market = await fetchGammaMarket(clientWrapper, condition_id);
94
+ if (!market) {
95
+ return {
96
+ content: [
97
+ {
98
+ type: "text",
99
+ text: `Market not found: ${condition_id}`,
100
+ },
101
+ ],
102
+ isError: true,
103
+ };
104
+ }
105
+ const formatted = formatMarket(market);
106
+ return {
107
+ content: [
108
+ {
109
+ type: "text",
110
+ text: JSON.stringify(formatted, null, 2),
111
+ },
112
+ ],
113
+ };
114
+ }
115
+ catch (error) {
116
+ const message = error instanceof Error ? error.message : String(error);
117
+ return {
118
+ content: [
119
+ {
120
+ type: "text",
121
+ text: `Error fetching market: ${message}`,
122
+ },
123
+ ],
124
+ isError: true,
125
+ };
126
+ }
127
+ });
128
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ClobClientWrapper } from "../client.js";
3
+ export declare function registerOrderbookTools(server: McpServer, clientWrapper: ClobClientWrapper): void;
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ const GetOrderbookSchema = z.object({
3
+ token_id: z.string().min(1),
4
+ });
5
+ function formatOrderbook(tokenId, rawOrderbook) {
6
+ const formatEntries = (entries) => {
7
+ if (!entries)
8
+ return [];
9
+ return entries.map((e) => ({
10
+ price: e.price,
11
+ size: e.size,
12
+ }));
13
+ };
14
+ return {
15
+ token_id: tokenId,
16
+ bids: formatEntries(rawOrderbook.bids),
17
+ asks: formatEntries(rawOrderbook.asks),
18
+ };
19
+ }
20
+ export function registerOrderbookTools(server, clientWrapper) {
21
+ server.tool("polymarket_get_orderbook", "Get the order book for a specific token showing current bids and asks with prices and sizes.", GetOrderbookSchema.shape, async (args) => {
22
+ try {
23
+ const { token_id } = GetOrderbookSchema.parse(args);
24
+ const client = clientWrapper.getClient();
25
+ const orderbook = await client.getOrderBook(token_id);
26
+ const formatted = formatOrderbook(token_id, orderbook);
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: JSON.stringify(formatted, null, 2),
32
+ },
33
+ ],
34
+ };
35
+ }
36
+ catch (error) {
37
+ const message = error instanceof Error ? error.message : String(error);
38
+ return {
39
+ content: [
40
+ {
41
+ type: "text",
42
+ text: `Error fetching orderbook: ${message}`,
43
+ },
44
+ ],
45
+ isError: true,
46
+ };
47
+ }
48
+ });
49
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ClobClientWrapper } from "../client.js";
3
+ export declare function registerTradingTools(server: McpServer, clientWrapper: ClobClientWrapper, includeWriteTools: boolean): void;
@@ -0,0 +1,173 @@
1
+ import { z } from "zod";
2
+ import { Side as ClobSide } from "@polymarket/clob-client";
3
+ const PlaceOrderSchema = z.object({
4
+ token_id: z.string().min(1),
5
+ side: z.enum(["BUY", "SELL"]),
6
+ size: z.string().refine((val) => {
7
+ const num = parseFloat(val);
8
+ return !isNaN(num) && num > 0;
9
+ }, "Size must be a positive number"),
10
+ price: z.string().refine((val) => {
11
+ const num = parseFloat(val);
12
+ return !isNaN(num) && num > 0 && num < 1;
13
+ }, "Price must be between 0 and 1 (exclusive)"),
14
+ });
15
+ const CancelOrderSchema = z.object({
16
+ order_id: z.string().min(1),
17
+ });
18
+ const GetTradesSchema = z.object({
19
+ limit: z.number().min(1).max(100).optional().default(20),
20
+ });
21
+ async function getMarketInfoForToken(clientWrapper, tokenId) {
22
+ const client = clientWrapper.getClient();
23
+ try {
24
+ // The CLOB client should have a method to get market info
25
+ const marketInfo = (await client.getMarket(tokenId));
26
+ return {
27
+ tickSize: marketInfo.minimum_tick_size || 0.01,
28
+ negRisk: marketInfo.neg_risk || false,
29
+ };
30
+ }
31
+ catch {
32
+ // Default values if we can't fetch market info
33
+ return {
34
+ tickSize: 0.01,
35
+ negRisk: false,
36
+ };
37
+ }
38
+ }
39
+ function mapSide(side) {
40
+ return side === "BUY" ? ClobSide.BUY : ClobSide.SELL;
41
+ }
42
+ export function registerTradingTools(server, clientWrapper, includeWriteTools) {
43
+ // Get trades is always available (read-only)
44
+ server.tool("polymarket_get_trades", "Get recent executed trades for the configured wallet.", GetTradesSchema.shape, async (args) => {
45
+ try {
46
+ const { limit } = GetTradesSchema.parse(args);
47
+ const client = clientWrapper.getClient();
48
+ const allTrades = (await client.getTrades({}));
49
+ const trades = (allTrades || []).slice(0, limit);
50
+ const formatted = trades.map((t) => ({
51
+ id: t.id,
52
+ token_id: t.asset_id,
53
+ side: t.side,
54
+ price: t.price,
55
+ size: t.size,
56
+ timestamp: t.timestamp || t.match_time || "",
57
+ status: t.status,
58
+ }));
59
+ return {
60
+ content: [
61
+ {
62
+ type: "text",
63
+ text: JSON.stringify(formatted, null, 2),
64
+ },
65
+ ],
66
+ };
67
+ }
68
+ catch (error) {
69
+ const message = error instanceof Error ? error.message : String(error);
70
+ return {
71
+ content: [
72
+ {
73
+ type: "text",
74
+ text: `Error fetching trades: ${message}`,
75
+ },
76
+ ],
77
+ isError: true,
78
+ };
79
+ }
80
+ });
81
+ // Only register write tools if not in readonly mode
82
+ if (!includeWriteTools) {
83
+ console.error("Readonly mode: trading tools (place_order, cancel_order) disabled");
84
+ return;
85
+ }
86
+ server.tool("polymarket_place_order", "Place a limit order on Polymarket. CAUTION: This executes a real trade with real funds. Price must be between 0 and 1, size in shares.", PlaceOrderSchema.shape, async (args) => {
87
+ try {
88
+ clientWrapper.ensureWriteAccess();
89
+ const { token_id, side, size, price } = PlaceOrderSchema.parse(args);
90
+ const client = clientWrapper.getClient();
91
+ // Get market info for tick size and neg risk
92
+ const { tickSize, negRisk } = await getMarketInfoForToken(clientWrapper, token_id);
93
+ // Create the order
94
+ const orderArgs = {
95
+ tokenID: token_id,
96
+ side: mapSide(side),
97
+ size: parseFloat(size),
98
+ price: parseFloat(price),
99
+ feeRateBps: 0,
100
+ };
101
+ const signedOrder = await client.createOrder(orderArgs);
102
+ // Post the order
103
+ const response = (await client.postOrder(signedOrder));
104
+ const result = {
105
+ order_id: response.orderID || "",
106
+ status: response.status || "unknown",
107
+ message: response.errorMsg,
108
+ };
109
+ return {
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: JSON.stringify({
114
+ ...result,
115
+ order_details: {
116
+ token_id,
117
+ side,
118
+ size,
119
+ price,
120
+ tick_size: tickSize,
121
+ neg_risk: negRisk,
122
+ },
123
+ }, null, 2),
124
+ },
125
+ ],
126
+ };
127
+ }
128
+ catch (error) {
129
+ const message = error instanceof Error ? error.message : String(error);
130
+ return {
131
+ content: [
132
+ {
133
+ type: "text",
134
+ text: `Error placing order: ${message}`,
135
+ },
136
+ ],
137
+ isError: true,
138
+ };
139
+ }
140
+ });
141
+ server.tool("polymarket_cancel_order", "Cancel an existing order on Polymarket.", CancelOrderSchema.shape, async (args) => {
142
+ try {
143
+ clientWrapper.ensureWriteAccess();
144
+ const { order_id } = CancelOrderSchema.parse(args);
145
+ const client = clientWrapper.getClient();
146
+ const response = await client.cancelOrder({ orderID: order_id });
147
+ return {
148
+ content: [
149
+ {
150
+ type: "text",
151
+ text: JSON.stringify({
152
+ order_id,
153
+ status: "cancelled",
154
+ response,
155
+ }, null, 2),
156
+ },
157
+ ],
158
+ };
159
+ }
160
+ catch (error) {
161
+ const message = error instanceof Error ? error.message : String(error);
162
+ return {
163
+ content: [
164
+ {
165
+ type: "text",
166
+ text: `Error cancelling order: ${message}`,
167
+ },
168
+ ],
169
+ isError: true,
170
+ };
171
+ }
172
+ });
173
+ }
@@ -0,0 +1,51 @@
1
+ export interface TokenInfo {
2
+ token_id: string;
3
+ outcome: string;
4
+ price: number;
5
+ }
6
+ export interface MarketInfo {
7
+ condition_id: string;
8
+ question: string;
9
+ tokens: TokenInfo[];
10
+ volume: string;
11
+ end_date: string;
12
+ active: boolean;
13
+ closed: boolean;
14
+ }
15
+ export interface OrderbookEntry {
16
+ price: string;
17
+ size: string;
18
+ }
19
+ export interface OrderbookInfo {
20
+ bids: OrderbookEntry[];
21
+ asks: OrderbookEntry[];
22
+ token_id: string;
23
+ }
24
+ export interface Position {
25
+ token_id: string;
26
+ market: string;
27
+ outcome: string;
28
+ size: string;
29
+ avg_price: string;
30
+ current_price: string;
31
+ pnl: string;
32
+ }
33
+ export interface TradeInfo {
34
+ id: string;
35
+ token_id: string;
36
+ side: string;
37
+ price: string;
38
+ size: string;
39
+ timestamp: string;
40
+ status: string;
41
+ }
42
+ export interface BalanceInfo {
43
+ balance: string;
44
+ allowance: string;
45
+ }
46
+ export interface OrderResult {
47
+ order_id: string;
48
+ status: string;
49
+ message?: string;
50
+ }
51
+ export type Side = "BUY" | "SELL";
package/build/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@c0pilot/mcp-polymarket",
3
+ "version": "1.0.0",
4
+ "description": "MCP server and client library for Polymarket prediction markets - trade, browse markets, manage positions",
5
+ "type": "module",
6
+ "main": "./build/index.js",
7
+ "types": "./build/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./build/index.d.ts",
11
+ "default": "./build/index.js"
12
+ },
13
+ "./client": {
14
+ "types": "./build/client.d.ts",
15
+ "default": "./build/client.js"
16
+ },
17
+ "./config": {
18
+ "types": "./build/config.d.ts",
19
+ "default": "./build/config.js"
20
+ },
21
+ "./types": {
22
+ "types": "./build/types.d.ts",
23
+ "default": "./build/types.js"
24
+ }
25
+ },
26
+ "bin": {
27
+ "mcp-polymarket": "./build/index.js"
28
+ },
29
+ "files": [
30
+ "build",
31
+ "README.md"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsc && chmod 755 build/index.js",
35
+ "start": "node build/index.js",
36
+ "test": "vitest run",
37
+ "test:e2e": "vitest run e2e",
38
+ "clean": "rm -rf build",
39
+ "prepublishOnly": "npm run build"
40
+ },
41
+ "keywords": [
42
+ "mcp",
43
+ "polymarket",
44
+ "prediction-markets",
45
+ "trading",
46
+ "crypto",
47
+ "clob",
48
+ "model-context-protocol"
49
+ ],
50
+ "author": "unsanction",
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "https://github.com/unsanction/mcp-polymarket"
55
+ },
56
+ "homepage": "https://github.com/unsanction/mcp-polymarket#readme",
57
+ "bugs": {
58
+ "url": "https://github.com/unsanction/mcp-polymarket/issues"
59
+ },
60
+ "engines": {
61
+ "node": ">=18.0.0"
62
+ },
63
+ "dependencies": {
64
+ "@modelcontextprotocol/sdk": "^1.0.0",
65
+ "@polymarket/clob-client": "^5.2.1",
66
+ "ethers": "^6.16.0",
67
+ "zod": "^3.22.0"
68
+ },
69
+ "devDependencies": {
70
+ "@types/node": "^20.0.0",
71
+ "dotenv": "^17.2.3",
72
+ "typescript": "^5.0.0",
73
+ "vitest": "^4.0.18"
74
+ }
75
+ }