finmap-mcp 1.0.7 → 1.1.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 +1 -1
- package/dist/core.js +336 -300
- package/dist/stdio-server.js +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
package/dist/core.js
CHANGED
|
@@ -12,6 +12,24 @@ const INFO = {
|
|
|
12
12
|
issues: "https://github.com/finmap-org/mcp-server/issues",
|
|
13
13
|
feedback: "contact@finmap.org",
|
|
14
14
|
};
|
|
15
|
+
const STOCK_EXCHANGES = [
|
|
16
|
+
"amex",
|
|
17
|
+
"nasdaq",
|
|
18
|
+
"nyse",
|
|
19
|
+
"us-all",
|
|
20
|
+
"lse",
|
|
21
|
+
"moex",
|
|
22
|
+
"bist",
|
|
23
|
+
];
|
|
24
|
+
const US_EXCHANGES = ["amex", "nasdaq", "nyse"];
|
|
25
|
+
const SORT_FIELDS = [
|
|
26
|
+
"priceChangePct",
|
|
27
|
+
"marketCap",
|
|
28
|
+
"value",
|
|
29
|
+
"volume",
|
|
30
|
+
"numTrades",
|
|
31
|
+
];
|
|
32
|
+
const SORT_ORDERS = ["asc", "desc"];
|
|
15
33
|
const INDICES = {
|
|
16
34
|
EXCHANGE: 0,
|
|
17
35
|
COUNTRY: 1,
|
|
@@ -46,61 +64,88 @@ const EXCHANGE_TO_COUNTRY_MAP = {
|
|
|
46
64
|
moex: "russia",
|
|
47
65
|
bist: "turkey",
|
|
48
66
|
};
|
|
67
|
+
function createResponse(data) {
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function createErrorResponse(error) {
|
|
73
|
+
return createResponse(`ERROR: ${error instanceof Error ? error.message : String(error)}`);
|
|
74
|
+
}
|
|
75
|
+
function createCharts(exchange, date) {
|
|
76
|
+
return {
|
|
77
|
+
histogram: `${BASE_URL}/?chartType=histogram&dataType=marketcap&exchange=${exchange}`,
|
|
78
|
+
treemap: `${BASE_URL}/?chartType=treemap&dataType=marketcap&exchange=${exchange}${date ? `&date=${date}` : ""}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function calculateMatchScore(ticker, name, searchTerm) {
|
|
82
|
+
const tickerLower = ticker.toLowerCase();
|
|
83
|
+
const nameLower = name.toLowerCase();
|
|
84
|
+
if (tickerLower === searchTerm)
|
|
85
|
+
return 100;
|
|
86
|
+
if (tickerLower.startsWith(searchTerm))
|
|
87
|
+
return 90;
|
|
88
|
+
if (tickerLower.includes(searchTerm))
|
|
89
|
+
return 80;
|
|
90
|
+
if (nameLower.includes(searchTerm))
|
|
91
|
+
return 70;
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
49
94
|
const EXCHANGE_INFO = {
|
|
50
95
|
amex: {
|
|
51
96
|
name: "American Stock Exchange",
|
|
52
97
|
country: "United States",
|
|
98
|
+
currency: "USD",
|
|
53
99
|
availableSince: "2024-12-09",
|
|
54
100
|
updateFrequency: "Hourly (weekdays)",
|
|
55
101
|
},
|
|
56
102
|
nasdaq: {
|
|
57
103
|
name: "NASDAQ Stock Market",
|
|
58
104
|
country: "United States",
|
|
105
|
+
currency: "USD",
|
|
59
106
|
availableSince: "2024-12-09",
|
|
60
107
|
updateFrequency: "Hourly (weekdays)",
|
|
61
108
|
},
|
|
62
109
|
nyse: {
|
|
63
110
|
name: "New York Stock Exchange",
|
|
64
111
|
country: "United States",
|
|
112
|
+
currency: "USD",
|
|
65
113
|
availableSince: "2024-12-09",
|
|
66
114
|
updateFrequency: "Hourly (weekdays)",
|
|
67
115
|
},
|
|
68
116
|
"us-all": {
|
|
69
117
|
name: "US Combined (AMEX + NASDAQ + NYSE)",
|
|
70
118
|
country: "United States",
|
|
119
|
+
currency: "USD",
|
|
71
120
|
availableSince: "2024-12-09",
|
|
72
121
|
updateFrequency: "Hourly (weekdays)",
|
|
73
122
|
},
|
|
74
123
|
lse: {
|
|
75
124
|
name: "London Stock Exchange",
|
|
76
125
|
country: "United Kingdom",
|
|
126
|
+
currency: "GBP",
|
|
77
127
|
availableSince: "2025-02-07",
|
|
78
128
|
updateFrequency: "Hourly (weekdays)",
|
|
79
129
|
},
|
|
80
130
|
moex: {
|
|
81
131
|
name: "Moscow Exchange",
|
|
82
132
|
country: "Russia",
|
|
133
|
+
currency: "RUB",
|
|
83
134
|
availableSince: "2011-12-19",
|
|
84
135
|
updateFrequency: "Every 15 minutes (weekdays)",
|
|
85
136
|
},
|
|
86
137
|
bist: {
|
|
87
138
|
name: "Borsa Istanbul",
|
|
88
139
|
country: "Turkey",
|
|
140
|
+
currency: "TRY",
|
|
89
141
|
availableSince: "2015-11-30",
|
|
90
|
-
updateFrequency: "
|
|
142
|
+
updateFrequency: "Every two months",
|
|
91
143
|
},
|
|
92
144
|
};
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
amex - American Stock Exchange;
|
|
98
|
-
nasdaq - Nasdaq;
|
|
99
|
-
nyse - New York Stock Exchange;
|
|
100
|
-
us-all - AmEx, Nasdaq and NYSE combined;
|
|
101
|
-
lse - London Stock Exchange;
|
|
102
|
-
moex - Moscow Exchange;
|
|
103
|
-
bist - Borsa Istanbul (Turkish Stock Exchange)`),
|
|
145
|
+
const exchangeSchema = z
|
|
146
|
+
.enum(STOCK_EXCHANGES)
|
|
147
|
+
.describe("Stock exchange: amex, nasdaq, nyse, us-all, lse, moex, bist");
|
|
148
|
+
const dateSchema = {
|
|
104
149
|
year: z.number().int().min(2012).optional(),
|
|
105
150
|
month: z.number().int().min(1).max(12).optional(),
|
|
106
151
|
day: z.number().int().min(1).max(31).optional(),
|
|
@@ -113,13 +158,17 @@ function buildDateString(year, month, day) {
|
|
|
113
158
|
return `${y.toString()}/${m.toString().padStart(2, "0")}/${d.toString().padStart(2, "0")}`;
|
|
114
159
|
}
|
|
115
160
|
function validateAndFormatDate(dateString) {
|
|
116
|
-
const
|
|
117
|
-
z.string().date().parse(
|
|
118
|
-
const dayOfWeek = new Date(
|
|
161
|
+
const date = dateString.replaceAll("/", "-");
|
|
162
|
+
z.string().date().parse(date);
|
|
163
|
+
const dayOfWeek = new Date(date).getDay();
|
|
119
164
|
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
|
120
165
|
throw new Error("Data is only available for work days (Monday to Friday)");
|
|
121
166
|
}
|
|
122
|
-
return
|
|
167
|
+
return date;
|
|
168
|
+
}
|
|
169
|
+
function getDate(year, month, day) {
|
|
170
|
+
const dateString = buildDateString(year, month, day);
|
|
171
|
+
return validateAndFormatDate(dateString);
|
|
123
172
|
}
|
|
124
173
|
async function fetchMarketData(stockExchange, formattedDate) {
|
|
125
174
|
const country = EXCHANGE_TO_COUNTRY_MAP[stockExchange];
|
|
@@ -142,365 +191,352 @@ async function fetchSecurityInfo(exchange, ticker) {
|
|
|
142
191
|
return data;
|
|
143
192
|
}
|
|
144
193
|
export function registerFinmapTools(server) {
|
|
145
|
-
server.
|
|
146
|
-
title: "
|
|
147
|
-
description: "
|
|
148
|
-
|
|
194
|
+
server.registerTool("list_exchanges", {
|
|
195
|
+
title: "List exchanges",
|
|
196
|
+
description: "Return supported exchanges with IDs, names, country, currency, earliest available date, and update frequency.",
|
|
197
|
+
inputSchema: {},
|
|
149
198
|
}, async () => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
199
|
+
try {
|
|
200
|
+
const exchanges = Object.entries(EXCHANGE_INFO).map(([id, info]) => ({
|
|
201
|
+
id,
|
|
202
|
+
...info,
|
|
203
|
+
}));
|
|
204
|
+
return createResponse({ info: INFO, exchanges });
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
return createErrorResponse(error);
|
|
208
|
+
}
|
|
158
209
|
});
|
|
159
|
-
server.registerTool("
|
|
160
|
-
title: "
|
|
161
|
-
description: "
|
|
162
|
-
inputSchema:
|
|
210
|
+
server.registerTool("list_sectors", {
|
|
211
|
+
title: "List sectors",
|
|
212
|
+
description: "List available business sectors for an exchange on a specific date, including item counts.",
|
|
213
|
+
inputSchema: { stockExchange: exchangeSchema, ...dateSchema },
|
|
163
214
|
}, async ({ stockExchange, year, month, day, }) => {
|
|
164
215
|
try {
|
|
165
|
-
const
|
|
166
|
-
const
|
|
216
|
+
const formattedDate = getDate(year, month, day);
|
|
217
|
+
const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
|
|
218
|
+
const sectorCounts = {};
|
|
219
|
+
marketDataResponse.securities.data.forEach((item) => {
|
|
220
|
+
if (item[INDICES.TYPE] !== "sector" && item[INDICES.SECTOR]) {
|
|
221
|
+
sectorCounts[item[INDICES.SECTOR]] =
|
|
222
|
+
(sectorCounts[item[INDICES.SECTOR]] || 0) + 1;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
const sectors = Object.entries(sectorCounts).map(([name, count]) => ({
|
|
226
|
+
name,
|
|
227
|
+
count,
|
|
228
|
+
}));
|
|
229
|
+
return createResponse({
|
|
230
|
+
info: INFO,
|
|
231
|
+
date: formattedDate,
|
|
232
|
+
exchange: stockExchange.toUpperCase(),
|
|
233
|
+
currency: EXCHANGE_INFO[stockExchange].currency,
|
|
234
|
+
sectors,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
return createErrorResponse(error);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
server.registerTool("list_tickers", {
|
|
242
|
+
title: "List tickers by sector",
|
|
243
|
+
description: "Return company tickers and names for an exchange on a specific date, grouped by sector.",
|
|
244
|
+
inputSchema: {
|
|
245
|
+
stockExchange: exchangeSchema,
|
|
246
|
+
...dateSchema,
|
|
247
|
+
sector: z.string().optional().describe("Filter by specific sector"),
|
|
248
|
+
englishNames: z
|
|
249
|
+
.boolean()
|
|
250
|
+
.default(true)
|
|
251
|
+
.describe("Use English names if available"),
|
|
252
|
+
},
|
|
253
|
+
}, async ({ stockExchange, year, month, day, sector, englishNames, }) => {
|
|
254
|
+
try {
|
|
255
|
+
const formattedDate = getDate(year, month, day);
|
|
256
|
+
const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
|
|
257
|
+
const sectorGroups = {};
|
|
258
|
+
marketDataResponse.securities.data.forEach((item) => {
|
|
259
|
+
if (item[INDICES.TYPE] !== "sector" &&
|
|
260
|
+
item[INDICES.SECTOR] &&
|
|
261
|
+
(!sector || item[INDICES.SECTOR] === sector)) {
|
|
262
|
+
const sectorName = item[INDICES.SECTOR];
|
|
263
|
+
const ticker = item[INDICES.TICKER];
|
|
264
|
+
const name = englishNames
|
|
265
|
+
? item[INDICES.NAME_ENG]
|
|
266
|
+
: item[INDICES.NAME_ORIGINAL_SHORT] || item[INDICES.NAME_ENG];
|
|
267
|
+
if (ticker && name) {
|
|
268
|
+
if (!sectorGroups[sectorName])
|
|
269
|
+
sectorGroups[sectorName] = [];
|
|
270
|
+
sectorGroups[sectorName].push({ ticker, name });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
Object.values(sectorGroups).forEach((companies) => {
|
|
275
|
+
companies.sort((a, b) => a.ticker.localeCompare(b.ticker));
|
|
276
|
+
});
|
|
277
|
+
return createResponse({
|
|
278
|
+
info: INFO,
|
|
279
|
+
date: formattedDate,
|
|
280
|
+
exchange: stockExchange.toUpperCase(),
|
|
281
|
+
currency: EXCHANGE_INFO[stockExchange].currency,
|
|
282
|
+
sectors: sectorGroups,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
return createErrorResponse(error);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
server.registerTool("search_companies", {
|
|
290
|
+
title: "Search companies",
|
|
291
|
+
description: "Find companies by partial name or ticker on an exchange and return best matches",
|
|
292
|
+
inputSchema: {
|
|
293
|
+
stockExchange: exchangeSchema,
|
|
294
|
+
...dateSchema,
|
|
295
|
+
query: z
|
|
296
|
+
.string()
|
|
297
|
+
.describe("Search term (partial ticker or company name)"),
|
|
298
|
+
limit: z
|
|
299
|
+
.number()
|
|
300
|
+
.int()
|
|
301
|
+
.min(1)
|
|
302
|
+
.max(50)
|
|
303
|
+
.default(10)
|
|
304
|
+
.describe("Maximum results"),
|
|
305
|
+
},
|
|
306
|
+
}, async ({ stockExchange, year, month, day, query, limit, }) => {
|
|
307
|
+
try {
|
|
308
|
+
const formattedDate = getDate(year, month, day);
|
|
167
309
|
const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
|
|
168
|
-
const
|
|
169
|
-
const
|
|
310
|
+
const searchTerm = query.toLowerCase();
|
|
311
|
+
const matches = marketDataResponse.securities.data
|
|
312
|
+
.filter((item) => item[INDICES.TYPE] !== "sector" && item[INDICES.SECTOR])
|
|
313
|
+
.map((item) => ({
|
|
314
|
+
ticker: item[INDICES.TICKER],
|
|
315
|
+
name: item[INDICES.NAME_ENG],
|
|
316
|
+
sector: item[INDICES.SECTOR],
|
|
317
|
+
score: calculateMatchScore(item[INDICES.TICKER], item[INDICES.NAME_ENG], searchTerm),
|
|
318
|
+
}))
|
|
319
|
+
.filter((match) => match.score > 0)
|
|
320
|
+
.sort((a, b) => b.score - a.score)
|
|
321
|
+
.slice(0, limit);
|
|
322
|
+
return createResponse({
|
|
170
323
|
info: INFO,
|
|
171
|
-
charts: {
|
|
172
|
-
histogram: `${BASE_URL}/?chartType=histogram&dataType=marketcap&exchange=${stockExchange}`,
|
|
173
|
-
treemap: `${BASE_URL}/?chartType=treemap&dataType=marketcap&exchange=${stockExchange}&date=${formattedDate}`,
|
|
174
|
-
},
|
|
175
324
|
date: formattedDate,
|
|
176
325
|
exchange: stockExchange.toUpperCase(),
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
326
|
+
currency: EXCHANGE_INFO[stockExchange].currency,
|
|
327
|
+
query,
|
|
328
|
+
matches,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
return createErrorResponse(error);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
server.registerTool("get_market_overview", {
|
|
336
|
+
title: "Market overview",
|
|
337
|
+
description: "Get total market cap, volume, value, and performance for an exchange on a specific date with a sector breakdown.",
|
|
338
|
+
inputSchema: { stockExchange: exchangeSchema, ...dateSchema },
|
|
339
|
+
}, async ({ stockExchange, year, month, day, }) => {
|
|
340
|
+
try {
|
|
341
|
+
const formattedDate = getDate(year, month, day);
|
|
342
|
+
const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
|
|
343
|
+
const sectorItems = marketDataResponse.securities.data.filter((item) => item[INDICES.TYPE] === "sector");
|
|
344
|
+
let marketTotal = {};
|
|
345
|
+
const sectors = [];
|
|
346
|
+
sectorItems.forEach((item) => {
|
|
189
347
|
const sectorData = {
|
|
190
|
-
name:
|
|
191
|
-
marketCap:
|
|
192
|
-
marketCapChangePct:
|
|
193
|
-
volume:
|
|
194
|
-
value:
|
|
195
|
-
numTrades:
|
|
196
|
-
itemsPerSector:
|
|
348
|
+
name: item[INDICES.TICKER],
|
|
349
|
+
marketCap: item[INDICES.MARKET_CAP],
|
|
350
|
+
marketCapChangePct: item[INDICES.PRICE_CHANGE_PCT],
|
|
351
|
+
volume: item[INDICES.VOLUME],
|
|
352
|
+
value: item[INDICES.VALUE],
|
|
353
|
+
numTrades: item[INDICES.NUM_TRADES],
|
|
354
|
+
itemsPerSector: item[INDICES.ITEMS_PER_SECTOR],
|
|
197
355
|
};
|
|
198
|
-
if (
|
|
199
|
-
|
|
356
|
+
if (item[INDICES.SECTOR] === "") {
|
|
357
|
+
marketTotal = sectorData;
|
|
200
358
|
}
|
|
201
359
|
else {
|
|
202
|
-
|
|
360
|
+
sectors.push(sectorData);
|
|
203
361
|
}
|
|
204
362
|
});
|
|
205
|
-
return {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
363
|
+
return createResponse({
|
|
364
|
+
info: INFO,
|
|
365
|
+
charts: createCharts(stockExchange, formattedDate),
|
|
366
|
+
date: formattedDate,
|
|
367
|
+
exchange: stockExchange.toUpperCase(),
|
|
368
|
+
currency: EXCHANGE_INFO[stockExchange].currency,
|
|
369
|
+
marketTotal,
|
|
370
|
+
sectors,
|
|
371
|
+
});
|
|
213
372
|
}
|
|
214
373
|
catch (error) {
|
|
215
|
-
return
|
|
216
|
-
content: [
|
|
217
|
-
{
|
|
218
|
-
type: "text",
|
|
219
|
-
text: `ERROR: ${error instanceof Error ? error.message : String(error)}`,
|
|
220
|
-
},
|
|
221
|
-
],
|
|
222
|
-
};
|
|
374
|
+
return createErrorResponse(error);
|
|
223
375
|
}
|
|
224
376
|
});
|
|
225
|
-
server.registerTool("
|
|
226
|
-
title: "
|
|
227
|
-
description: "Get
|
|
377
|
+
server.registerTool("get_sectors_overview", {
|
|
378
|
+
title: "Sector performance",
|
|
379
|
+
description: "Get aggregated performance metrics by sector for an exchange on a specific date.",
|
|
228
380
|
inputSchema: {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
.boolean()
|
|
232
|
-
.default(true)
|
|
233
|
-
.optional()
|
|
234
|
-
.describe(`If false, returns the original company names instead of the English ones`),
|
|
381
|
+
stockExchange: exchangeSchema,
|
|
382
|
+
...dateSchema,
|
|
235
383
|
sector: z
|
|
236
384
|
.string()
|
|
237
385
|
.optional()
|
|
238
|
-
.describe(
|
|
386
|
+
.describe("Get data for specific sector only"),
|
|
239
387
|
},
|
|
240
|
-
}, async ({ stockExchange, year, month, day,
|
|
388
|
+
}, async ({ stockExchange, year, month, day, sector, }) => {
|
|
241
389
|
try {
|
|
242
|
-
const
|
|
243
|
-
const formattedDate = validateAndFormatDate(dateString);
|
|
390
|
+
const formattedDate = getDate(year, month, day);
|
|
244
391
|
const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
|
|
245
|
-
const
|
|
392
|
+
const sectors = marketDataResponse.securities.data
|
|
393
|
+
.filter((item) => item[INDICES.TYPE] === "sector" && item[INDICES.SECTOR] !== "")
|
|
394
|
+
.filter((item) => !sector || item[INDICES.TICKER] === sector)
|
|
395
|
+
.map((item) => ({
|
|
396
|
+
name: item[INDICES.TICKER],
|
|
397
|
+
marketCap: item[INDICES.MARKET_CAP],
|
|
398
|
+
marketCapChangePct: item[INDICES.PRICE_CHANGE_PCT],
|
|
399
|
+
volume: item[INDICES.VOLUME],
|
|
400
|
+
value: item[INDICES.VALUE],
|
|
401
|
+
numTrades: item[INDICES.NUM_TRADES],
|
|
402
|
+
itemsPerSector: item[INDICES.ITEMS_PER_SECTOR],
|
|
403
|
+
}));
|
|
404
|
+
return createResponse({
|
|
405
|
+
info: INFO,
|
|
406
|
+
charts: createCharts(stockExchange, formattedDate),
|
|
246
407
|
date: formattedDate,
|
|
247
408
|
exchange: stockExchange.toUpperCase(),
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
sectors: {},
|
|
252
|
-
};
|
|
253
|
-
const result = {
|
|
254
|
-
info: INFO,
|
|
255
|
-
charts: {
|
|
256
|
-
histogram: `${BASE_URL}/?chartType=histogram&dataType=marketcap&exchange=${stockExchange}`,
|
|
257
|
-
treemap: `${BASE_URL}/?chartType=treemap&dataType=marketcap&exchange=${stockExchange}&date=${formattedDate}`,
|
|
258
|
-
},
|
|
259
|
-
...tickersResult,
|
|
260
|
-
};
|
|
261
|
-
const filteredSecurities = marketDataResponse.securities.data.filter((securityItem) => securityItem[INDICES.TYPE] !== "sector" &&
|
|
262
|
-
securityItem[INDICES.SECTOR] !== "" &&
|
|
263
|
-
(!sector || securityItem[INDICES.SECTOR] === sector));
|
|
264
|
-
const groupedBySector = filteredSecurities.reduce((accumulator, securityItem) => {
|
|
265
|
-
const sectorName = securityItem[INDICES.SECTOR];
|
|
266
|
-
if (!accumulator[sectorName])
|
|
267
|
-
accumulator[sectorName] = [];
|
|
268
|
-
accumulator[sectorName].push(securityItem);
|
|
269
|
-
return accumulator;
|
|
270
|
-
}, {});
|
|
271
|
-
for (const [sectorName, sectorSecurities] of Object.entries(groupedBySector)) {
|
|
272
|
-
const validTickerPairs = sectorSecurities
|
|
273
|
-
.map((securityItem) => {
|
|
274
|
-
const ticker = securityItem[INDICES.TICKER];
|
|
275
|
-
const companyName = englishNames
|
|
276
|
-
? securityItem[INDICES.NAME_ENG]
|
|
277
|
-
: securityItem[INDICES.NAME_ORIGINAL_SHORT] ||
|
|
278
|
-
securityItem[INDICES.NAME_ENG];
|
|
279
|
-
return ticker && companyName
|
|
280
|
-
? [ticker, companyName]
|
|
281
|
-
: null;
|
|
282
|
-
})
|
|
283
|
-
.filter((entry) => entry !== null)
|
|
284
|
-
.sort(([a], [b]) => a.localeCompare(b));
|
|
285
|
-
const tickersMap = Object.fromEntries(validTickerPairs);
|
|
286
|
-
if (Object.keys(tickersMap).length > 0) {
|
|
287
|
-
result.sectors[sectorName] = {
|
|
288
|
-
count: Object.keys(tickersMap).length,
|
|
289
|
-
tickers: tickersMap,
|
|
290
|
-
};
|
|
291
|
-
result.itemsPerSector += Object.keys(tickersMap).length;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return {
|
|
295
|
-
content: [
|
|
296
|
-
{
|
|
297
|
-
type: "text",
|
|
298
|
-
text: JSON.stringify(result, null, 2),
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
};
|
|
409
|
+
currency: EXCHANGE_INFO[stockExchange].currency,
|
|
410
|
+
sectors,
|
|
411
|
+
});
|
|
302
412
|
}
|
|
303
413
|
catch (error) {
|
|
304
|
-
return
|
|
305
|
-
content: [
|
|
306
|
-
{
|
|
307
|
-
type: "text",
|
|
308
|
-
text: `ERROR: ${error instanceof Error ? error.message : String(error)}`,
|
|
309
|
-
},
|
|
310
|
-
],
|
|
311
|
-
};
|
|
414
|
+
return createErrorResponse(error);
|
|
312
415
|
}
|
|
313
416
|
});
|
|
314
|
-
server.registerTool("
|
|
315
|
-
title: "
|
|
316
|
-
description: "Get market data for a specific ticker on
|
|
417
|
+
server.registerTool("get_stock_data", {
|
|
418
|
+
title: "Stock data by ticker",
|
|
419
|
+
description: "Get detailed market data for a specific ticker on an exchange and date, including price, change, volume, value, market cap, and trades.",
|
|
317
420
|
inputSchema: {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
.describe("The ticker symbol to get market data for. Case-sensitive"),
|
|
421
|
+
stockExchange: exchangeSchema,
|
|
422
|
+
...dateSchema,
|
|
423
|
+
ticker: z.string().describe("Stock ticker symbol (case-sensitive)"),
|
|
322
424
|
},
|
|
323
425
|
}, async ({ stockExchange, year, month, day, ticker, }) => {
|
|
324
426
|
try {
|
|
325
|
-
const
|
|
326
|
-
const formattedDate = validateAndFormatDate(dateString);
|
|
427
|
+
const formattedDate = getDate(year, month, day);
|
|
327
428
|
const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
if (!marketData) {
|
|
429
|
+
const stockData = marketDataResponse.securities.data.find((item) => item[INDICES.TYPE] !== "sector" && item[INDICES.TICKER] === ticker);
|
|
430
|
+
if (!stockData) {
|
|
331
431
|
throw new Error(`Ticker ${ticker} not found on ${stockExchange} for date ${formattedDate}`);
|
|
332
432
|
}
|
|
333
|
-
|
|
433
|
+
return createResponse({
|
|
334
434
|
info: INFO,
|
|
335
|
-
charts:
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
volume: marketData[INDICES.VOLUME],
|
|
354
|
-
value: marketData[INDICES.VALUE],
|
|
355
|
-
numTrades: marketData[INDICES.NUM_TRADES],
|
|
356
|
-
marketCap: marketData[INDICES.MARKET_CAP],
|
|
357
|
-
listedFrom: marketData[INDICES.LISTED_FROM],
|
|
358
|
-
listedTill: marketData[INDICES.LISTED_TILL],
|
|
359
|
-
wikiPageIdEng: marketData[INDICES.WIKI_PAGE_ID_ENG],
|
|
360
|
-
wikiPageIdOriginal: marketData[INDICES.WIKI_PAGE_ID_ORIGINAL],
|
|
361
|
-
itemsPerSector: marketData[INDICES.ITEMS_PER_SECTOR],
|
|
362
|
-
};
|
|
363
|
-
return {
|
|
364
|
-
content: [
|
|
365
|
-
{
|
|
366
|
-
type: "text",
|
|
367
|
-
text: JSON.stringify(result, null, 2),
|
|
368
|
-
},
|
|
369
|
-
],
|
|
370
|
-
};
|
|
435
|
+
charts: createCharts(stockExchange, formattedDate),
|
|
436
|
+
exchange: stockData[INDICES.EXCHANGE],
|
|
437
|
+
country: stockData[INDICES.COUNTRY],
|
|
438
|
+
currency: EXCHANGE_INFO[stockExchange].currency,
|
|
439
|
+
sector: stockData[INDICES.SECTOR],
|
|
440
|
+
ticker: stockData[INDICES.TICKER],
|
|
441
|
+
nameEng: stockData[INDICES.NAME_ENG],
|
|
442
|
+
nameOriginal: stockData[INDICES.NAME_ORIGINAL],
|
|
443
|
+
priceOpen: stockData[INDICES.PRICE_OPEN],
|
|
444
|
+
priceLastSale: stockData[INDICES.PRICE_LAST_SALE],
|
|
445
|
+
priceChangePct: stockData[INDICES.PRICE_CHANGE_PCT],
|
|
446
|
+
volume: stockData[INDICES.VOLUME],
|
|
447
|
+
value: stockData[INDICES.VALUE],
|
|
448
|
+
numTrades: stockData[INDICES.NUM_TRADES],
|
|
449
|
+
marketCap: stockData[INDICES.MARKET_CAP],
|
|
450
|
+
listedFrom: stockData[INDICES.LISTED_FROM],
|
|
451
|
+
listedTill: stockData[INDICES.LISTED_TILL],
|
|
452
|
+
});
|
|
371
453
|
}
|
|
372
454
|
catch (error) {
|
|
373
|
-
return
|
|
374
|
-
content: [
|
|
375
|
-
{
|
|
376
|
-
type: "text",
|
|
377
|
-
text: `ERROR: ${error instanceof Error ? error.message : String(error)}`,
|
|
378
|
-
},
|
|
379
|
-
],
|
|
380
|
-
};
|
|
455
|
+
return createErrorResponse(error);
|
|
381
456
|
}
|
|
382
457
|
});
|
|
383
|
-
server.registerTool("
|
|
384
|
-
title: "
|
|
385
|
-
description: "
|
|
458
|
+
server.registerTool("rank_stocks", {
|
|
459
|
+
title: "Rank stocks",
|
|
460
|
+
description: "Rank stocks on an exchange by a chosen metric (marketCap, priceChangePct, volume, value, numTrades) for a specific date with order and limit.",
|
|
386
461
|
inputSchema: {
|
|
387
|
-
|
|
462
|
+
stockExchange: exchangeSchema,
|
|
463
|
+
...dateSchema,
|
|
388
464
|
sortBy: z
|
|
389
|
-
.enum(
|
|
390
|
-
.describe(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
itemsPerSector: Number of items in the sector`),
|
|
396
|
-
order: z.enum(["asc", "desc"]).default("desc").describe("Sort order"),
|
|
465
|
+
.enum(SORT_FIELDS)
|
|
466
|
+
.describe("Sort by: marketCap, priceChangePct, volume, value, numTrades"),
|
|
467
|
+
order: z
|
|
468
|
+
.enum(SORT_ORDERS)
|
|
469
|
+
.default("desc")
|
|
470
|
+
.describe("Sort order: asc or desc"),
|
|
397
471
|
limit: z
|
|
398
472
|
.number()
|
|
399
473
|
.int()
|
|
400
474
|
.min(1)
|
|
401
475
|
.max(500)
|
|
402
476
|
.default(10)
|
|
403
|
-
.describe("Number of
|
|
477
|
+
.describe("Number of results"),
|
|
478
|
+
sector: z.string().optional().describe("Filter by specific sector"),
|
|
404
479
|
},
|
|
405
|
-
}, async ({ stockExchange, year, month, day, sortBy, order, limit, }) => {
|
|
480
|
+
}, async ({ stockExchange, year, month, day, sortBy, order, limit, sector, }) => {
|
|
406
481
|
try {
|
|
407
|
-
const
|
|
408
|
-
const formattedDate = validateAndFormatDate(dateString);
|
|
482
|
+
const formattedDate = getDate(year, month, day);
|
|
409
483
|
const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
|
|
410
|
-
const
|
|
411
|
-
.filter((
|
|
412
|
-
|
|
413
|
-
.map((
|
|
414
|
-
ticker:
|
|
415
|
-
name:
|
|
416
|
-
sector:
|
|
417
|
-
priceLastSale:
|
|
418
|
-
priceChangePct:
|
|
419
|
-
marketCap:
|
|
420
|
-
volume:
|
|
421
|
-
value:
|
|
422
|
-
numTrades:
|
|
484
|
+
const stocks = marketDataResponse.securities.data
|
|
485
|
+
.filter((item) => item[INDICES.TYPE] !== "sector" && item[INDICES.SECTOR] !== "")
|
|
486
|
+
.filter((item) => !sector || item[INDICES.SECTOR] === sector)
|
|
487
|
+
.map((item) => ({
|
|
488
|
+
ticker: item[INDICES.TICKER],
|
|
489
|
+
name: item[INDICES.NAME_ENG],
|
|
490
|
+
sector: item[INDICES.SECTOR],
|
|
491
|
+
priceLastSale: item[INDICES.PRICE_LAST_SALE],
|
|
492
|
+
priceChangePct: item[INDICES.PRICE_CHANGE_PCT],
|
|
493
|
+
marketCap: item[INDICES.MARKET_CAP],
|
|
494
|
+
volume: item[INDICES.VOLUME],
|
|
495
|
+
value: item[INDICES.VALUE],
|
|
496
|
+
numTrades: item[INDICES.NUM_TRADES],
|
|
423
497
|
}))
|
|
424
498
|
.sort((a, b) => {
|
|
425
|
-
const aVal = a[sortBy];
|
|
426
|
-
const bVal = b[sortBy];
|
|
499
|
+
const aVal = a[sortBy], bVal = b[sortBy];
|
|
427
500
|
return order === "desc" ? bVal - aVal : aVal - bVal;
|
|
428
501
|
})
|
|
429
|
-
.slice(0, limit
|
|
430
|
-
|
|
502
|
+
.slice(0, limit);
|
|
503
|
+
return createResponse({
|
|
431
504
|
info: INFO,
|
|
432
|
-
charts:
|
|
433
|
-
histogram: `${BASE_URL}/?chartType=histogram&dataType=marketcap&exchange=${stockExchange}`,
|
|
434
|
-
treemap: `${BASE_URL}/?chartType=treemap&dataType=marketcap&exchange=${stockExchange}&date=${formattedDate}`,
|
|
435
|
-
},
|
|
505
|
+
charts: createCharts(stockExchange, formattedDate),
|
|
436
506
|
date: formattedDate,
|
|
437
507
|
exchange: stockExchange.toUpperCase(),
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
content: [
|
|
446
|
-
{
|
|
447
|
-
type: "text",
|
|
448
|
-
text: JSON.stringify(result, null, 2),
|
|
449
|
-
},
|
|
450
|
-
],
|
|
451
|
-
};
|
|
508
|
+
currency: EXCHANGE_INFO[stockExchange].currency,
|
|
509
|
+
sortBy,
|
|
510
|
+
order,
|
|
511
|
+
limit,
|
|
512
|
+
count: stocks.length,
|
|
513
|
+
stocks,
|
|
514
|
+
});
|
|
452
515
|
}
|
|
453
516
|
catch (error) {
|
|
454
|
-
return
|
|
455
|
-
content: [
|
|
456
|
-
{
|
|
457
|
-
type: "text",
|
|
458
|
-
text: `ERROR: ${error instanceof Error ? error.message : String(error)}`,
|
|
459
|
-
},
|
|
460
|
-
],
|
|
461
|
-
};
|
|
517
|
+
return createErrorResponse(error);
|
|
462
518
|
}
|
|
463
519
|
});
|
|
464
|
-
server.registerTool("
|
|
465
|
-
title: "
|
|
466
|
-
description: "Get
|
|
520
|
+
server.registerTool("get_company_profile", {
|
|
521
|
+
title: "Company profile (US)",
|
|
522
|
+
description: "Get business description, industry, and background for a US-listed company by ticker.",
|
|
467
523
|
inputSchema: {
|
|
468
524
|
exchange: z
|
|
469
|
-
.enum(
|
|
470
|
-
.describe("US exchange
|
|
471
|
-
ticker: z
|
|
472
|
-
.string()
|
|
473
|
-
.describe("The ticker symbol to get information for. Case-sensitive"),
|
|
525
|
+
.enum(US_EXCHANGES)
|
|
526
|
+
.describe("US exchange: amex, nasdaq, nyse"),
|
|
527
|
+
ticker: z.string().describe("Stock ticker symbol (case-sensitive)"),
|
|
474
528
|
},
|
|
475
529
|
}, async ({ exchange, ticker }) => {
|
|
476
530
|
try {
|
|
477
531
|
const securityInfo = await fetchSecurityInfo(exchange, ticker);
|
|
478
|
-
|
|
532
|
+
return createResponse({
|
|
479
533
|
info: INFO,
|
|
480
|
-
charts:
|
|
481
|
-
histogram: `${BASE_URL}/?chartType=histogram&dataType=marketcap&exchange=${exchange}`,
|
|
482
|
-
treemap: `${BASE_URL}/?chartType=treemap&dataType=marketcap&exchange=${exchange}`,
|
|
483
|
-
},
|
|
534
|
+
charts: createCharts(exchange),
|
|
484
535
|
...securityInfo,
|
|
485
|
-
};
|
|
486
|
-
return {
|
|
487
|
-
content: [
|
|
488
|
-
{
|
|
489
|
-
type: "text",
|
|
490
|
-
text: JSON.stringify(result, null, 2),
|
|
491
|
-
},
|
|
492
|
-
],
|
|
493
|
-
};
|
|
536
|
+
});
|
|
494
537
|
}
|
|
495
538
|
catch (error) {
|
|
496
|
-
return
|
|
497
|
-
content: [
|
|
498
|
-
{
|
|
499
|
-
type: "text",
|
|
500
|
-
text: `ERROR: ${error instanceof Error ? error.message : String(error)}`,
|
|
501
|
-
},
|
|
502
|
-
],
|
|
503
|
-
};
|
|
539
|
+
return createErrorResponse(error);
|
|
504
540
|
}
|
|
505
541
|
});
|
|
506
542
|
}
|
package/dist/stdio-server.js
CHANGED
|
@@ -4,7 +4,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
const server = new McpServer({
|
|
6
6
|
name: "finmap-mcp",
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.1.0",
|
|
8
8
|
});
|
|
9
9
|
registerFinmapTools(server);
|
|
10
10
|
const transport = new StdioServerTransport();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "finmap-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP server providing financial market data from finmap.org",
|
|
5
5
|
"main": "./dist/stdio-server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -73,16 +73,16 @@
|
|
|
73
73
|
"package": "npm run build:stdio && npm pack --pack-destination dist"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"@modelcontextprotocol/sdk": "1.17.
|
|
76
|
+
"@modelcontextprotocol/sdk": "1.17.4",
|
|
77
77
|
"agents": "^0.0.113",
|
|
78
78
|
"zod": "^3.25.76"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@biomejs/biome": "^2.2.
|
|
81
|
+
"@biomejs/biome": "^2.2.2",
|
|
82
82
|
"@types/node": "^24.3.0",
|
|
83
83
|
"rimraf": "^6.0.1",
|
|
84
|
-
"tsx": "^4.20.
|
|
84
|
+
"tsx": "^4.20.5",
|
|
85
85
|
"typescript": "5.9.2",
|
|
86
|
-
"wrangler": "^4.
|
|
86
|
+
"wrangler": "^4.33.1"
|
|
87
87
|
}
|
|
88
88
|
}
|