manifold-mcp-server
Version:
MCP server for interacting with Manifold Markets prediction markets
425 lines • 16.7 kB
JavaScript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode, } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
const API_BASE = 'https://api.manifold.markets';
const server = new Server({
name: 'manifold-markets',
version: '0.1.0',
}, {
capabilities: {
tools: {},
},
});
// Tool schemas
// Tool schemas
const SearchMarketsSchema = z.object({
term: z.string().optional(),
limit: z.number().min(1).max(100).optional(),
filter: z.enum(['all', 'open', 'closed', 'resolved']).optional(),
sort: z.enum(['newest', 'score', 'liquidity']).optional(),
});
const PlaceBetSchema = z.object({
marketId: z.string(),
amount: z.number().positive(),
outcome: z.enum(['YES', 'NO']),
limitProb: z.number().min(0.01).max(0.99).optional(),
});
const GetMarketSchema = z.object({
marketId: z.string(),
});
const GetUserSchema = z.object({
username: z.string(),
});
const GetUserPositionsSchema = z.object({
userId: z.string(),
});
const SellSharesSchema = z.object({
marketId: z.string(),
outcome: z.enum(['YES', 'NO']).optional(),
shares: z.number().optional(),
});
const AddLiquiditySchema = z.object({
marketId: z.string(),
amount: z.number().positive(),
});
const CancelBetSchema = z.object({
betId: z.string(),
});
const SendManaSchema = z.object({
toIds: z.array(z.string()),
amount: z.number().min(10),
message: z.string().optional(),
});
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'search_markets',
description: 'Search for prediction markets with optional filters',
inputSchema: {
type: 'object',
properties: {
term: { type: 'string', description: 'Search query' },
limit: { type: 'number', description: 'Max number of results (1-100)' },
filter: { type: 'string', enum: ['all', 'open', 'closed', 'resolved'] },
sort: { type: 'string', enum: ['newest', 'score', 'liquidity'] },
},
},
},
{
name: 'get_market',
description: 'Get detailed information about a specific market',
inputSchema: {
type: 'object',
properties: {
marketId: { type: 'string', description: 'Market ID' },
},
required: ['marketId'],
},
},
{
name: 'get_user',
description: 'Get user information by username',
inputSchema: {
type: 'object',
properties: {
username: { type: 'string', description: 'Username' },
},
required: ['username'],
},
},
{
name: 'place_bet',
description: 'Place a bet on a market',
inputSchema: {
type: 'object',
properties: {
marketId: { type: 'string', description: 'Market ID' },
amount: { type: 'number', description: 'Amount to bet in mana' },
outcome: { type: 'string', enum: ['YES', 'NO'] },
limitProb: {
type: 'number',
description: 'Optional limit order probability (0.01-0.99)',
},
},
required: ['marketId', 'amount', 'outcome'],
},
},
{
name: 'cancel_bet',
description: 'Cancel a limit order bet',
inputSchema: {
type: 'object',
properties: {
betId: { type: 'string', description: 'Bet ID to cancel' },
},
required: ['betId'],
},
},
{
name: 'sell_shares',
description: 'Sell shares in a market',
inputSchema: {
type: 'object',
properties: {
marketId: { type: 'string', description: 'Market ID' },
outcome: { type: 'string', enum: ['YES', 'NO'], description: 'Which type of shares to sell (defaults to what you have)' },
shares: { type: 'number', description: 'How many shares to sell (defaults to all)' },
},
required: ['marketId'],
},
},
{
name: 'add_liquidity',
description: 'Add mana to market liquidity pool',
inputSchema: {
type: 'object',
properties: {
marketId: { type: 'string', description: 'Market ID' },
amount: { type: 'number', description: 'Amount of mana to add' },
},
required: ['marketId', 'amount'],
},
},
{
name: 'get_positions',
description: 'Get user positions across markets',
inputSchema: {
type: 'object',
properties: {
userId: { type: 'string', description: 'User ID' },
},
required: ['userId'],
},
},
{
name: 'send_mana',
description: 'Send mana to other users',
inputSchema: {
type: 'object',
properties: {
toIds: {
type: 'array',
items: { type: 'string' },
description: 'Array of user IDs to send mana to'
},
amount: { type: 'number', description: 'Amount of mana to send (min 10)' },
message: { type: 'string', description: 'Optional message to include' },
},
required: ['toIds', 'amount'],
},
},
],
}));
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'search_markets': {
const params = SearchMarketsSchema.parse(args);
const searchParams = new URLSearchParams();
if (params.term)
searchParams.set('term', params.term);
if (params.limit)
searchParams.set('limit', params.limit.toString());
if (params.filter)
searchParams.set('filter', params.filter);
if (params.sort)
searchParams.set('sort', params.sort);
const response = await fetch(`${API_BASE}/v0/search-markets?${searchParams}`, { headers: { Accept: 'application/json' } });
if (!response.ok) {
throw new McpError(ErrorCode.InternalError, `Manifold API error: ${response.statusText}`);
}
const markets = await response.json();
return {
content: [
{
type: 'text',
text: JSON.stringify(markets, null, 2),
},
],
};
}
case 'get_market': {
const { marketId } = GetMarketSchema.parse(args);
const response = await fetch(`${API_BASE}/v0/market/${marketId}`, {
headers: { Accept: 'application/json' },
});
if (!response.ok) {
throw new McpError(ErrorCode.InternalError, `Manifold API error: ${response.statusText}`);
}
const market = await response.json();
return {
content: [
{
type: 'text',
text: JSON.stringify(market, null, 2),
},
],
};
}
case 'get_user': {
const { username } = GetUserSchema.parse(args);
const response = await fetch(`${API_BASE}/v0/user/${username}`, {
headers: { Accept: 'application/json' },
});
if (!response.ok) {
throw new McpError(ErrorCode.InternalError, `Manifold API error: ${response.statusText}`);
}
const user = await response.json();
return {
content: [
{
type: 'text',
text: JSON.stringify(user, null, 2),
},
],
};
}
case 'place_bet': {
const params = PlaceBetSchema.parse(args);
const apiKey = process.env.MANIFOLD_API_KEY;
if (!apiKey) {
throw new McpError(ErrorCode.InternalError, 'MANIFOLD_API_KEY environment variable is required');
}
const response = await fetch(`${API_BASE}/v0/bet`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Key ${apiKey}`,
},
body: JSON.stringify({
contractId: params.marketId,
amount: params.amount,
outcome: params.outcome,
limitProb: params.limitProb,
}),
});
if (!response.ok) {
throw new McpError(ErrorCode.InternalError, `Manifold API error: ${response.statusText}`);
}
const result = await response.json();
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'cancel_bet': {
const { betId } = CancelBetSchema.parse(args);
const apiKey = process.env.MANIFOLD_API_KEY;
if (!apiKey) {
throw new McpError(ErrorCode.InternalError, 'MANIFOLD_API_KEY environment variable is required');
}
const response = await fetch(`${API_BASE}/v0/bet/cancel/${betId}`, {
method: 'POST',
headers: {
Authorization: `Key ${apiKey}`,
},
});
if (!response.ok) {
throw new McpError(ErrorCode.InternalError, `Manifold API error: ${response.statusText}`);
}
return {
content: [
{
type: 'text',
text: 'Bet cancelled successfully',
},
],
};
}
case 'sell_shares': {
const params = SellSharesSchema.parse(args);
const apiKey = process.env.MANIFOLD_API_KEY;
if (!apiKey) {
throw new McpError(ErrorCode.InternalError, 'MANIFOLD_API_KEY environment variable is required');
}
const response = await fetch(`${API_BASE}/v0/market/${params.marketId}/sell`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Key ${apiKey}`,
},
body: JSON.stringify({
outcome: params.outcome,
shares: params.shares,
}),
});
if (!response.ok) {
throw new McpError(ErrorCode.InternalError, `Manifold API error: ${response.statusText}`);
}
const result = await response.json();
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
case 'add_liquidity': {
const params = AddLiquiditySchema.parse(args);
const apiKey = process.env.MANIFOLD_API_KEY;
if (!apiKey) {
throw new McpError(ErrorCode.InternalError, 'MANIFOLD_API_KEY environment variable is required');
}
const response = await fetch(`${API_BASE}/v0/market/${params.marketId}/add-liquidity`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Key ${apiKey}`,
},
body: JSON.stringify({
amount: params.amount,
}),
});
if (!response.ok) {
throw new McpError(ErrorCode.InternalError, `Manifold API error: ${response.statusText}`);
}
return {
content: [
{
type: 'text',
text: 'Liquidity added successfully',
},
],
};
}
case 'get_positions': {
const { userId } = GetUserPositionsSchema.parse(args);
const response = await fetch(`${API_BASE}/v0/bets?userId=${userId}`, { headers: { Accept: 'application/json' } });
if (!response.ok) {
throw new McpError(ErrorCode.InternalError, `Manifold API error: ${response.statusText}`);
}
const positions = await response.json();
return {
content: [
{
type: 'text',
text: JSON.stringify(positions, null, 2),
},
],
};
}
case 'send_mana': {
const params = SendManaSchema.parse(args);
const apiKey = process.env.MANIFOLD_API_KEY;
if (!apiKey) {
throw new McpError(ErrorCode.InternalError, 'MANIFOLD_API_KEY environment variable is required');
}
const response = await fetch(`${API_BASE}/v0/managram`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Key ${apiKey}`,
},
body: JSON.stringify({
toIds: params.toIds,
amount: params.amount,
message: params.message,
}),
});
if (!response.ok) {
throw new McpError(ErrorCode.InternalError, `Manifold API error: ${response.statusText}`);
}
return {
content: [
{
type: 'text',
text: 'Mana sent successfully',
},
],
};
}
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
}
catch (error) {
if (error instanceof z.ZodError) {
throw new McpError(ErrorCode.InvalidParams, `Invalid parameters: ${error.errors
.map((e) => `${e.path.join('.')}: ${e.message}`)
.join(', ')}`);
}
throw error;
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Manifold Markets MCP Server running on stdio');
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});
//# sourceMappingURL=index.js.map