#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import axios, { AxiosInstance } from 'axios';
import { z } from 'zod';
// Get API key from environment
const API_KEY = process.env.POLYGON_API_KEY;
if (!API_KEY) {
throw new Error('POLYGON_API_KEY environment variable is required');
}
// Create Polygon API client
const polygonApi: AxiosInstance = axios.create({
baseURL: 'https://api.polygon.io',
params: {
apiKey: API_KEY
}
});
// Create MCP server
const server = new Server(
{
name: "polygon-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
// Tool handlers
const toolHandlers = {
get_ticker_details: async (args: { ticker: string }) => {
try {
const response = await polygonApi.get(`/v3/reference/tickers/${args.ticker}`);
return {
content: [{
type: "text",
text: JSON.stringify(response.data, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: "text",
text: `Error getting ticker details: ${error.response?.data?.message || error.message}`
}],
isError: true
};
}
},
get_latest_quote: async (args: { ticker: string }) => {
try {
const response = await polygonApi.get(`/v2/last/nbbo/${args.ticker}`);
return {
content: [{
type: "text",
text: JSON.stringify(response.data, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: "text",
text: `Error getting latest quote: ${error.response?.data?.message || error.message}`
}],
isError: true
};
}
},
get_aggregates: async (args: {
ticker: string;
timespan: string;
from: string;
to: string;
adjusted?: boolean;
sort?: string;
limit?: number;
}) => {
try {
const params: any = {
adjusted: args.adjusted !== false,
sort: args.sort || 'asc',
limit: args.limit || 120
};
const response = await polygonApi.get(
`/v2/aggs/ticker/${args.ticker}/range/1/${args.timespan}/${args.from}/${args.to}`,
{ params }
);
return {
content: [{
type: "text",
text: JSON.stringify(response.data, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: "text",
text: `Error getting aggregates: ${error.response?.data?.message || error.message}`
}],
isError: true
};
}
},
get_daily_open_close: async (args: { ticker: string; date: string }) => {
try {
const response = await polygonApi.get(`/v1/open-close/${args.ticker}/${args.date}`);
return {
content: [{
type: "text",
text: JSON.stringify(response.data, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: "text",
text: `Error getting daily open/close: ${error.response?.data?.message || error.message}`
}],
isError: true
};
}
},
get_market_status: async () => {
try {
const response = await polygonApi.get('/v1/marketstatus/now');
return {
content: [{
type: "text",
text: JSON.stringify(response.data, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: "text",
text: `Error getting market status: ${error.response?.data?.message || error.message}`
}],
isError: true
};
}
},
get_ticker_news: async (args: { ticker: string; limit?: number }) => {
try {
const params: any = {
limit: args.limit || 10
};
const response = await polygonApi.get(`/v2/reference/news`, {
params: {
...params,
ticker: args.ticker
}
});
return {
content: [{
type: "text",
text: JSON.stringify(response.data, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: "text",
text: `Error getting ticker news: ${error.response?.data?.message || error.message}`
}],
isError: true
};
}
},
list_tickers: async (args: { search?: string; type?: string; market?: string; limit?: number }) => {
try {
const params: any = {
limit: args.limit || 100,
active: true
};
if (args.search) params.search = args.search;
if (args.type) params.type = args.type;
if (args.market) params.market = args.market;
const response = await polygonApi.get('/v3/reference/tickers', { params });
return {
content: [{
type: "text",
text: JSON.stringify(response.data, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: "text",
text: `Error listing tickers: ${error.response?.data?.message || error.message}`
}],
isError: true
};
}
},
get_snapshot: async (args: { tickers: string }) => {
try {
const response = await polygonApi.get('/v2/snapshot/locale/us/markets/stocks/tickers', {
params: {
tickers: args.tickers
}
});
return {
content: [{
type: "text",
text: JSON.stringify(response.data, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: "text",
text: `Error getting snapshot: ${error.response?.data?.message || error.message}`
}],
isError: true
};
}
}
};
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get_ticker_details",
description: "Get details about a ticker symbol",
inputSchema: {
type: "object",
properties: {
ticker: {
type: "string",
description: "Ticker symbol (e.g., AAPL)"
}
},
required: ["ticker"]
}
},
{
name: "get_latest_quote",
description: "Get real-time quote for a ticker",
inputSchema: {
type: "object",
properties: {
ticker: {
type: "string",
description: "Ticker symbol (e.g., AAPL)"
}
},
required: ["ticker"]
}
},
{
name: "get_aggregates",
description: "Get aggregate bars for a ticker",
inputSchema: {
type: "object",
properties: {
ticker: {
type: "string",
description: "Ticker symbol (e.g., AAPL)"
},
timespan: {
type: "string",
enum: ["minute", "hour", "day", "week", "month", "quarter", "year"],
description: "Size of the time window"
},
from: {
type: "string",
description: "Start date (YYYY-MM-DD format)"
},
to: {
type: "string",
description: "End date (YYYY-MM-DD format)"
},
adjusted: {
type: "boolean",
description: "Whether to adjust for splits (default: true)"
},
sort: {
type: "string",
enum: ["asc", "desc"],
description: "Sort order (default: asc)"
},
limit: {
type: "number",
description: "Number of results (default: 120, max: 50000)"
}
},
required: ["ticker", "timespan", "from", "to"]
}
},
{
name: "get_daily_open_close",
description: "Get daily open/close prices",
inputSchema: {
type: "object",
properties: {
ticker: {
type: "string",
description: "Ticker symbol (e.g., AAPL)"
},
date: {
type: "string",
description: "Date (YYYY-MM-DD format)"
}
},
required: ["ticker", "date"]
}
},
{
name: "get_market_status",
description: "Check if markets are open",
inputSchema: {
type: "object",
properties: {}
}
},
{
name: "get_ticker_news",
description: "Get news articles for a ticker",
inputSchema: {
type: "object",
properties: {
ticker: {
type: "string",
description: "Ticker symbol (e.g., AAPL)"
},
limit: {
type: "number",
description: "Number of articles to return (default: 10)"
}
},
required: ["ticker"]
}
},
{
name: "list_tickers",
description: "Search/list available tickers",
inputSchema: {
type: "object",
properties: {
search: {
type: "string",
description: "Search query"
},
type: {
type: "string",
enum: ["CS", "ADRC", "ADRP", "ADRR", "UNIT", "RIGHT", "PFD", "FUND", "SP", "WARRANT", "INDEX", "ETF", "ETN", "OS", "GDR", "OTHER", "NYRS", "AGEN", "EQLK", "BOND", "ADRW", "BASKET"],
description: "Security type"
},
market: {
type: "string",
enum: ["stocks", "crypto", "fx", "otc", "indices"],
description: "Market type"
},
limit: {
type: "number",
description: "Number of results (default: 100)"
}
}
}
},
{
name: "get_snapshot",
description: "Get snapshot of ticker(s)",
inputSchema: {
type: "object",
properties: {
tickers: {
type: "string",
description: "Comma-separated list of tickers (e.g., AAPL,MSFT,GOOGL)"
}
},
required: ["tickers"]
}
}
]
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name in toolHandlers) {
return await (toolHandlers as any)[name](args || {});
}
throw new Error(`Unknown tool: ${name}`);
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Polygon MCP server running on stdio');
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});