finmap-mcp 2.0.8 → 3.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 CHANGED
@@ -11,7 +11,7 @@ The finmap.org MCP server provides comprehensive historical data from the US, UK
11
11
  | American Stock Exchange | `amex` | United States | 2024-12-09 | Daily |
12
12
  | US Combined (AMEX + NASDAQ + NYSE) | `us-all` | United States | 2024-12-09 | Daily |
13
13
  | London Stock Exchange | `lse` | United Kingdom | 2025-02-07 | Hourly (weekdays) |
14
- | Hong Kong Stock Exchange | `hkex` | Hong Kong | 2025-09-26 | Every 30 minutes (weekdays) |
14
+ | Hong Kong Stock Exchange | `hkex` | Hong Kong | 2025-09-29 | Every 30 minutes (weekdays) |
15
15
  | Borsa Istanbul | `bist` | Turkey | 2015-11-30 | Every two months |
16
16
  | Moscow Exchange | `moex` | Russia | 2011-12-19 | Every 15 minutes (weekdays) |
17
17
 
@@ -59,13 +59,31 @@ npx finmap-mcp
59
59
  }
60
60
  ```
61
61
 
62
+
63
+ ## HTTP API Wrapper
64
+
65
+ The server also exposes a compact HTTP API for GPT Actions and direct integration.
66
+
67
+ - `GET /api/openapi.json`
68
+ - `GET /api/list-exchanges`
69
+ - `POST /api/list-sectors`
70
+ - `POST /api/list-sector-companies`
71
+ - `POST /api/search-companies`
72
+ - `POST /api/market-overview`
73
+ - `POST /api/sector-performance`
74
+ - `POST /api/rank-stocks`
75
+ - `POST /api/stock-snapshot`
76
+ - `POST /api/company-profile`
77
+
78
+ A ready-to-import GPT Actions schema is available in `gpt-actions.yaml`.
79
+
62
80
  ## Available Tools
63
81
 
64
- ### 1. `list_exchanges`
65
- - Title: List exchanges
66
- - Description: Return supported exchanges with IDs, names, country, currency, earliest available date, and update frequency.
82
+ ### 1. `list_supported_exchanges`
83
+ - Title: List supported stock exchanges
84
+ - Description: Return metadata for all supported stock exchanges in the Finmap dataset. Includes exchange ID, exchange name, country, currency, earliest available historical data date, and update frequency.
67
85
 
68
- **Example Promt:** `#finmap-mcp list available stock exchanges`
86
+ **Example Prompt:** `#finmap-mcp list available stock exchanges`
69
87
 
70
88
  **Example Response:**
71
89
  ```json
@@ -132,18 +150,18 @@ npx finmap-mcp
132
150
  "name": "Hong Kong Stock Exchange",
133
151
  "country": "Hong Kong",
134
152
  "currency": "HKD",
135
- "availableSince": "2025-09-26",
153
+ "availableSince": "2025-09-29",
136
154
  "updateFrequency": "Every 30 minutes (weekdays)"
137
155
  }
138
156
  ]
139
157
  }
140
158
  ```
141
159
 
142
- ### 2. `list_sectors`
143
- - Title: List sectors
144
- - Description: List available business sectors for an exchange on a specific date, including item counts.
160
+ ### 2. `list_exchange_sectors`
161
+ - Title: List sectors for a stock exchange
162
+ - Description: Return all business sectors available on a specific exchange and trading date. Each sector includes the number of companies in that sector.
145
163
 
146
- **Example Promt:** `#finmap-mcp List sectors for the Turkish stock exchange`
164
+ **Example Prompt:** `#finmap-mcp List sectors for the Turkish stock exchange`
147
165
 
148
166
  **Example Response:**
149
167
  ```json
@@ -236,11 +254,11 @@ npx finmap-mcp
236
254
  }
237
255
  ```
238
256
 
239
- ### 3. `list_tickers`
240
- - Title: List tickers by sector
241
- - Description: Return company tickers and names for an exchange on a specific date, grouped by sector.
257
+ ### 3. `list_sector_companies`
258
+ - Title: List companies by sector
259
+ - Description: Return company tickers and names grouped by sector for an exchange on a specific trading date. Optionally filter results by a single sector.
242
260
 
243
- **Example Promt:** `#finmap-mcp List companies in the Real Estate sector`
261
+ **Example Prompt:** `#finmap-mcp List companies in the Real Estate sector`
244
262
 
245
263
  **Example Response:**
246
264
  ```json
@@ -271,11 +289,11 @@ npx finmap-mcp
271
289
  }
272
290
  ```
273
291
 
274
- ### 4. `search_companies`
275
- - Title: Search companies
276
- - Description: Find companies by partial name or ticker on an exchange and return best matches.
292
+ ### 4. `search_exchange_companies`
293
+ - Title: Search companies by name or ticker
294
+ - Description: Search for companies on a specific exchange by partial ticker symbol or company name. Results are ranked by relevance using ticker and name similarity.
277
295
 
278
- **Example Promt:** `#finmap-mcp Search for companies named 'Sprouts'`
296
+ **Example Prompt:** `#finmap-mcp Search for companies named 'Sprouts'`
279
297
 
280
298
  **Example Response:**
281
299
  ```json
@@ -295,11 +313,11 @@ npx finmap-mcp
295
313
  }
296
314
  ```
297
315
 
298
- ### 5. `get_market_overview`
299
- - Title: Market overview
300
- - Description: Get total market cap, volume, value, and performance for an exchange on a specific date with a sector breakdown.
316
+ ### 5. `analyze_market_overview`
317
+ - Title: Analyze market overview
318
+ - Description: Return aggregated statistics for a stock exchange on a specific date. Includes total market capitalization, trading volume, total traded value, number of trades, and sector-level market breakdown.
301
319
 
302
- **Example Promt:** `#finmap-mcp market overview for Nasdaq`
320
+ **Example Prompt:** `#finmap-mcp market overview for Nasdaq`
303
321
 
304
322
  **Example Response:**
305
323
  ```json
@@ -358,11 +376,11 @@ npx finmap-mcp
358
376
  }
359
377
  ```
360
378
 
361
- ### 6. `get_sectors_overview`
362
- - Title: Sector performance
363
- - Description: Get aggregated performance metrics by sector for an exchange on a specific date.
379
+ ### 6. `analyze_sector_performance`
380
+ - Title: Analyze sector performance
381
+ - Description: Return aggregated metrics for each sector in a stock exchange. Includes sector market capitalization, price change percentage, trading volume, traded value, number of trades, and number of companies in the sector.
364
382
 
365
- **Example Promt:** `#finmap-mcp Get overview for the Utilities sector`
383
+ **Example Prompt:** `#finmap-mcp Get overview for the Utilities sector`
366
384
 
367
385
  **Example Response:**
368
386
  ```json
@@ -384,11 +402,11 @@ npx finmap-mcp
384
402
  }
385
403
  ```
386
404
 
387
- ### 7. `get_stock_data`
388
- - Title: Stock data by ticker
389
- - Description: Get detailed market data for a specific ticker on an exchange and date, including price, change, volume, value, market cap, and trades.
405
+ ### 7. `get_stock_snapshot`
406
+ - Title: Get stock market snapshot
407
+ - Description: Return detailed trading metrics for a single stock ticker on a specific exchange and trading date. Includes price open, last sale price, price change percentage, trading volume, traded value, number of trades, and market capitalization.
390
408
 
391
- **Example Promt:** `#finmap-mcp Dominion Energy, stock data`
409
+ **Example Prompt:** `#finmap-mcp Dominion Energy, stock data`
392
410
 
393
411
  **Example Response:**
394
412
  ```json
@@ -412,11 +430,11 @@ npx finmap-mcp
412
430
  }
413
431
  ```
414
432
 
415
- ### 8. `rank_stocks`
416
- - Title: Rank stocks
417
- - Description: Rank stocks on an exchange by a chosen metric (marketCap, priceChangePct, volume, value, numTrades) for a specific date with order and limit.
433
+ ### 8. `rank_exchange_companies`
434
+ - Title: Rank companies by market metric
435
+ - Description: Return companies ranked by a selected market metric on a specific exchange. Supported ranking metrics: market capitalization, price change percentage, trading volume, traded value, and number of trades. Results can be limited and optionally filtered by sector.
418
436
 
419
- **Example Promt:** `#finmap-mcp UK, rank stocks by market cap`
437
+ **Example Prompt:** `#finmap-mcp UK, rank stocks by market cap`
420
438
 
421
439
  **Example Response:**
422
440
  ```json
@@ -488,11 +506,11 @@ npx finmap-mcp
488
506
  }
489
507
  ```
490
508
 
491
- ### 9. `get_company_profile`
492
- - Title: Company profile (US)
493
- - Description: Get business description, industry, and background for a US-listed company by ticker.
509
+ ### 9. `get_company_profile_us`
510
+ - Title: Get US company profile
511
+ - Description: Return business description and background information for a US-listed company. Supported exchanges: NASDAQ, NYSE, and AMEX.
494
512
 
495
- **Example Promt:** `#finmap-mcp Sprouts Farmers Market, get company profile`
513
+ **Example Prompt:** `#finmap-mcp Sprouts Farmers Market, get company profile`
496
514
 
497
515
  **Example Response:**
498
516
  ```json
package/dist/core.js CHANGED
@@ -1,71 +1,4 @@
1
- import { z } from "zod";
2
- const BASE_URL = "https://finmap.org";
3
- const DATA_BASE_URL = "https://raw.githubusercontent.com/finmap-org";
4
- const INFO = {
5
- provider: "finmap.org",
6
- description: "Discover interactive stock charts and curated news at finmap.org",
7
- github: "https://github.com/finmap-org",
8
- donate: {
9
- patreon: "https://patreon.com/finmap",
10
- boosty: "https://boosty.to/finmap",
11
- },
12
- issues: "https://github.com/finmap-org/mcp-server/issues",
13
- feedback: "contact@finmap.org",
14
- };
15
- const STOCK_EXCHANGES = [
16
- "amex",
17
- "nasdaq",
18
- "nyse",
19
- "us-all",
20
- "lse",
21
- "moex",
22
- "bist",
23
- "hkex",
24
- ];
25
- const US_EXCHANGES = ["amex", "nasdaq", "nyse"];
26
- const SORT_FIELDS = [
27
- "priceChangePct",
28
- "marketCap",
29
- "value",
30
- "volume",
31
- "numTrades",
32
- ];
33
- const SORT_ORDERS = ["asc", "desc"];
34
- const INDICES = {
35
- EXCHANGE: 0,
36
- COUNTRY: 1,
37
- TYPE: 2,
38
- SECTOR: 3,
39
- INDUSTRY: 4,
40
- CURRENCY_ID: 5,
41
- TICKER: 6,
42
- NAME_ENG: 7,
43
- NAME_ENG_SHORT: 8,
44
- NAME_ORIGINAL: 9,
45
- NAME_ORIGINAL_SHORT: 10,
46
- PRICE_OPEN: 11,
47
- PRICE_LAST_SALE: 12,
48
- PRICE_CHANGE_PCT: 13,
49
- VOLUME: 14,
50
- VALUE: 15,
51
- NUM_TRADES: 16,
52
- MARKET_CAP: 17,
53
- LISTED_FROM: 18,
54
- LISTED_TILL: 19,
55
- WIKI_PAGE_ID_ENG: 20,
56
- WIKI_PAGE_ID_ORIGINAL: 21,
57
- ITEMS_PER_SECTOR: 22,
58
- };
59
- const EXCHANGE_TO_COUNTRY_MAP = {
60
- amex: "us",
61
- nasdaq: "us",
62
- nyse: "us",
63
- "us-all": "us",
64
- lse: "uk",
65
- moex: "russia",
66
- bist: "turkey",
67
- hkex: "hongkong",
68
- };
1
+ import { companyProfileSchema, getCompanyProfile, getMarketOverview, getSectorsOverview, getStockData, listExchanges, listSectors, listSectorsSchema, listTickers, listTickersSchema, marketOverviewSchema, rankStocks, rankStocksSchema, searchCompanies, searchCompaniesSchema, sectorsOverviewSchema, stockDataSchema, } from "./api.js";
69
2
  function createResponse(data) {
70
3
  return {
71
4
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
@@ -74,475 +7,110 @@ function createResponse(data) {
74
7
  function createErrorResponse(error) {
75
8
  return createResponse(`ERROR: ${error instanceof Error ? error.message : String(error)}`);
76
9
  }
77
- function createCharts(exchange, date) {
78
- return {
79
- histogram: `${BASE_URL}/?chart=histogram&data=marketcap&currency=USD&exchange=${exchange}`,
80
- treemap: `${BASE_URL}/?chart=treemap&data=marketcap&currency=USD&exchange=${exchange}${date ? `&date=${date.replaceAll("-", "/")}` : ""}`,
81
- };
82
- }
83
- function calculateMatchScore(ticker, name, searchTerm) {
84
- const tickerLower = ticker.toLowerCase();
85
- const nameLower = name.toLowerCase();
86
- if (tickerLower === searchTerm)
87
- return 100;
88
- if (tickerLower.startsWith(searchTerm))
89
- return 90;
90
- if (tickerLower.includes(searchTerm))
91
- return 80;
92
- if (nameLower.includes(searchTerm))
93
- return 70;
94
- return 0;
95
- }
96
- const EXCHANGE_INFO = {
97
- amex: {
98
- name: "American Stock Exchange",
99
- country: "United States",
100
- currency: "USD",
101
- availableSince: "2024-12-09",
102
- updateFrequency: "Daily",
103
- },
104
- nasdaq: {
105
- name: "NASDAQ Stock Market",
106
- country: "United States",
107
- currency: "USD",
108
- availableSince: "2024-12-09",
109
- updateFrequency: "Daily",
110
- },
111
- nyse: {
112
- name: "New York Stock Exchange",
113
- country: "United States",
114
- currency: "USD",
115
- availableSince: "2024-12-09",
116
- updateFrequency: "Daily",
117
- },
118
- "us-all": {
119
- name: "US Combined (AMEX + NASDAQ + NYSE)",
120
- country: "United States",
121
- currency: "USD",
122
- availableSince: "2024-12-09",
123
- updateFrequency: "Daily",
124
- },
125
- lse: {
126
- name: "London Stock Exchange",
127
- country: "United Kingdom",
128
- currency: "GBP",
129
- availableSince: "2025-02-07",
130
- updateFrequency: "Hourly (weekdays)",
131
- },
132
- moex: {
133
- name: "Moscow Exchange",
134
- country: "Russia",
135
- currency: "RUB",
136
- availableSince: "2011-12-19",
137
- updateFrequency: "Every 15 minutes (weekdays)",
138
- },
139
- bist: {
140
- name: "Borsa Istanbul",
141
- country: "Turkey",
142
- currency: "TRY",
143
- availableSince: "2015-11-30",
144
- updateFrequency: "Every two months",
145
- },
146
- hkex: {
147
- name: "Hong Kong Stock Exchange",
148
- country: "Hong Kong",
149
- currency: "HKD",
150
- availableSince: "2025-09-29",
151
- updateFrequency: "Every 30 minutes (weekdays)",
152
- },
153
- };
154
- const exchangeSchema = z
155
- .enum(STOCK_EXCHANGES)
156
- .describe("Stock exchange: amex, nasdaq, nyse, us-all, lse, moex, bist, hkex");
157
- const dateSchema = {
158
- year: z.number().int().min(2012).optional(),
159
- month: z.number().int().min(1).max(12).optional(),
160
- day: z.number().int().min(1).max(31).optional(),
161
- };
162
- function buildDateString(year, month, day) {
163
- const currentDate = new Date();
164
- const y = year ?? currentDate.getFullYear();
165
- const m = month ?? currentDate.getMonth() + 1;
166
- const d = day ?? currentDate.getDate();
167
- return `${y.toString()}/${m.toString().padStart(2, "0")}/${d.toString().padStart(2, "0")}`;
168
- }
169
- function validateAndFormatDate(dateString) {
170
- const date = dateString.replaceAll("/", "-");
171
- z.string().date().parse(date);
172
- const dayOfWeek = new Date(date).getDay();
173
- if (dayOfWeek === 0 || dayOfWeek === 6) {
174
- throw new Error("Data is only available for work days (Monday to Friday)");
175
- }
176
- return date;
177
- }
178
- function getDate(year, month, day) {
179
- const dateString = buildDateString(year, month, day);
180
- return validateAndFormatDate(dateString);
181
- }
182
- async function fetchMarketData(stockExchange, formattedDate) {
183
- const country = EXCHANGE_TO_COUNTRY_MAP[stockExchange];
184
- const date = formattedDate.replaceAll("-", "/");
185
- const url = `${DATA_BASE_URL}/data-${country}/refs/heads/main/marketdata/${date}/${stockExchange}.json`;
186
- const response = await fetch(url);
187
- if (response.status === 404) {
188
- throw new Error(`Not found, try another date. The date must be on or after ${EXCHANGE_INFO[stockExchange].availableSince} for ${stockExchange}`);
189
- }
190
- return response.json();
191
- }
192
- async function fetchSecurityInfo(exchange, ticker) {
193
- const firstLetter = ticker.charAt(0).toUpperCase();
194
- const url = `${DATA_BASE_URL}/data-us/refs/heads/main/securities/${exchange}/${firstLetter}/${ticker}.json`;
195
- const response = await fetch(url);
196
- if (response.status === 404) {
197
- throw new Error(`Security ${ticker} not found on ${exchange}`);
198
- }
199
- const data = (await response.json());
200
- return data;
201
- }
202
10
  export function registerFinmapTools(server) {
203
- server.registerTool("list_exchanges", {
204
- title: "List exchanges",
205
- description: "Return supported exchanges with IDs, names, country, currency, earliest available date, and update frequency.",
11
+ server.registerTool("list_supported_exchanges", {
12
+ title: "List supported stock exchanges",
13
+ description: "Return metadata for all supported stock exchanges in the Finmap dataset, including exchange ID, exchange name, country, currency, earliest available historical data date, and update frequency.",
206
14
  inputSchema: {},
207
15
  }, async () => {
208
16
  try {
209
- const exchanges = Object.entries(EXCHANGE_INFO).map(([id, info]) => ({
210
- id,
211
- ...info,
212
- }));
213
- return createResponse({ info: INFO, exchanges });
17
+ return createResponse(listExchanges());
214
18
  }
215
19
  catch (error) {
216
20
  return createErrorResponse(error);
217
21
  }
218
22
  });
219
- server.registerTool("list_sectors", {
220
- title: "List sectors",
221
- description: "List available business sectors for an exchange on a specific date, including item counts.",
222
- inputSchema: { stockExchange: exchangeSchema, ...dateSchema },
223
- }, async ({ stockExchange, year, month, day, }) => {
23
+ server.registerTool("list_exchange_sectors", {
24
+ title: "List sectors for a stock exchange",
25
+ description: "Return all business sectors available on a specific exchange and trading date. Each sector includes the number of companies in that sector.",
26
+ inputSchema: listSectorsSchema.shape,
27
+ }, async (input) => {
224
28
  try {
225
- const formattedDate = getDate(year, month, day);
226
- const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
227
- const sectorCounts = {};
228
- marketDataResponse.securities.data.forEach((item) => {
229
- if (item[INDICES.TYPE] !== "sector" && item[INDICES.SECTOR]) {
230
- sectorCounts[item[INDICES.SECTOR]] =
231
- (sectorCounts[item[INDICES.SECTOR]] || 0) + 1;
232
- }
233
- });
234
- const sectors = Object.entries(sectorCounts).map(([name, count]) => ({
235
- name,
236
- itemsPerSector: count,
237
- }));
238
- return createResponse({
239
- info: INFO,
240
- date: formattedDate,
241
- exchange: stockExchange.toUpperCase(),
242
- currency: EXCHANGE_INFO[stockExchange].currency,
243
- sectors,
244
- });
29
+ return createResponse(await listSectors(listSectorsSchema.parse(input)));
245
30
  }
246
31
  catch (error) {
247
32
  return createErrorResponse(error);
248
33
  }
249
34
  });
250
- server.registerTool("list_tickers", {
251
- title: "List tickers by sector",
252
- description: "Return company tickers and names for an exchange on a specific date, grouped by sector.",
253
- inputSchema: {
254
- stockExchange: exchangeSchema,
255
- ...dateSchema,
256
- sector: z.string().optional().describe("Filter by specific sector"),
257
- englishNames: z
258
- .boolean()
259
- .default(true)
260
- .describe("Use English names if available"),
261
- },
262
- }, async ({ stockExchange, year, month, day, sector, englishNames, }) => {
35
+ server.registerTool("list_sector_companies", {
36
+ title: "List companies by sector",
37
+ description: "Return company tickers and names grouped by sector for an exchange on a specific trading date. Optionally filter results by a single sector.",
38
+ inputSchema: listTickersSchema.shape,
39
+ }, async (input) => {
263
40
  try {
264
- const formattedDate = getDate(year, month, day);
265
- const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
266
- const sectorGroups = {};
267
- marketDataResponse.securities.data.forEach((item) => {
268
- if (item[INDICES.TYPE] !== "sector" &&
269
- item[INDICES.SECTOR] &&
270
- (!sector || item[INDICES.SECTOR] === sector)) {
271
- const sectorName = item[INDICES.SECTOR];
272
- const ticker = item[INDICES.TICKER];
273
- const name = englishNames
274
- ? item[INDICES.NAME_ENG]
275
- : item[INDICES.NAME_ORIGINAL_SHORT] || item[INDICES.NAME_ENG];
276
- if (ticker && name) {
277
- if (!sectorGroups[sectorName])
278
- sectorGroups[sectorName] = [];
279
- sectorGroups[sectorName].push({ ticker, name });
280
- }
281
- }
282
- });
283
- Object.values(sectorGroups).forEach((companies) => {
284
- companies.sort((a, b) => a.ticker.localeCompare(b.ticker));
285
- });
286
- return createResponse({
287
- info: INFO,
288
- date: formattedDate,
289
- exchange: stockExchange.toUpperCase(),
290
- currency: EXCHANGE_INFO[stockExchange].currency,
291
- sectors: sectorGroups,
292
- });
41
+ return createResponse(await listTickers(listTickersSchema.parse(input)));
293
42
  }
294
43
  catch (error) {
295
44
  return createErrorResponse(error);
296
45
  }
297
46
  });
298
- server.registerTool("search_companies", {
299
- title: "Search companies",
300
- description: "Find companies by partial name or ticker on an exchange and return best matches",
301
- inputSchema: {
302
- stockExchange: exchangeSchema,
303
- ...dateSchema,
304
- query: z
305
- .string()
306
- .describe("Search term (partial ticker or company name)"),
307
- limit: z
308
- .number()
309
- .int()
310
- .min(1)
311
- .max(50)
312
- .default(10)
313
- .describe("Maximum results"),
314
- },
315
- }, async ({ stockExchange, year, month, day, query, limit, }) => {
47
+ server.registerTool("search_exchange_companies", {
48
+ title: "Search companies by name or ticker",
49
+ description: "Search for companies on a specific exchange by partial ticker symbol or company name. Results are ranked by relevance using ticker and name similarity.",
50
+ inputSchema: searchCompaniesSchema.shape,
51
+ }, async (input) => {
316
52
  try {
317
- const formattedDate = getDate(year, month, day);
318
- const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
319
- const searchTerm = query.toLowerCase();
320
- const matches = marketDataResponse.securities.data
321
- .filter((item) => item[INDICES.TYPE] !== "sector" && item[INDICES.SECTOR])
322
- .map((item) => ({
323
- ticker: item[INDICES.TICKER],
324
- name: item[INDICES.NAME_ENG],
325
- sector: item[INDICES.SECTOR],
326
- score: calculateMatchScore(item[INDICES.TICKER], item[INDICES.NAME_ENG], searchTerm),
327
- }))
328
- .filter((match) => match.score > 0)
329
- .sort((a, b) => b.score - a.score)
330
- .slice(0, limit);
331
- return createResponse({
332
- info: INFO,
333
- date: formattedDate,
334
- exchange: stockExchange.toUpperCase(),
335
- currency: EXCHANGE_INFO[stockExchange].currency,
336
- query,
337
- matches,
338
- });
53
+ return createResponse(await searchCompanies(searchCompaniesSchema.parse(input)));
339
54
  }
340
55
  catch (error) {
341
56
  return createErrorResponse(error);
342
57
  }
343
58
  });
344
- server.registerTool("get_market_overview", {
345
- title: "Market overview",
346
- description: "Get total market cap, volume, value, and performance for an exchange on a specific date with a sector breakdown.",
347
- inputSchema: { stockExchange: exchangeSchema, ...dateSchema },
348
- }, async ({ stockExchange, year, month, day, }) => {
59
+ server.registerTool("analyze_market_overview", {
60
+ title: "Analyze market overview",
61
+ description: "Return aggregated statistics for a stock exchange on a specific date, including total market capitalization, trading volume, total traded value, number of trades, and sector-level market breakdown.",
62
+ inputSchema: marketOverviewSchema.shape,
63
+ }, async (input) => {
349
64
  try {
350
- const formattedDate = getDate(year, month, day);
351
- const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
352
- const sectorItems = marketDataResponse.securities.data.filter((item) => item[INDICES.TYPE] === "sector");
353
- let marketTotal = {};
354
- const sectors = [];
355
- sectorItems.forEach((item) => {
356
- const sectorData = {
357
- name: item[INDICES.TICKER],
358
- marketCap: item[INDICES.MARKET_CAP],
359
- marketCapChangePct: item[INDICES.PRICE_CHANGE_PCT],
360
- volume: item[INDICES.VOLUME],
361
- value: item[INDICES.VALUE],
362
- numTrades: item[INDICES.NUM_TRADES],
363
- itemsPerSector: item[INDICES.ITEMS_PER_SECTOR],
364
- };
365
- if (item[INDICES.SECTOR] === "") {
366
- marketTotal = sectorData;
367
- }
368
- else {
369
- sectors.push(sectorData);
370
- }
371
- });
372
- return createResponse({
373
- info: INFO,
374
- charts: createCharts(stockExchange, formattedDate),
375
- date: formattedDate,
376
- exchange: stockExchange.toUpperCase(),
377
- currency: EXCHANGE_INFO[stockExchange].currency,
378
- marketTotal,
379
- sectors,
380
- });
65
+ return createResponse(await getMarketOverview(marketOverviewSchema.parse(input)));
381
66
  }
382
67
  catch (error) {
383
68
  return createErrorResponse(error);
384
69
  }
385
70
  });
386
- server.registerTool("get_sectors_overview", {
387
- title: "Sector performance",
388
- description: "Get aggregated performance metrics by sector for an exchange on a specific date.",
389
- inputSchema: {
390
- stockExchange: exchangeSchema,
391
- ...dateSchema,
392
- sector: z
393
- .string()
394
- .optional()
395
- .describe("Get data for specific sector only"),
396
- },
397
- }, async ({ stockExchange, year, month, day, sector, }) => {
71
+ server.registerTool("analyze_sector_performance", {
72
+ title: "Analyze sector performance",
73
+ description: "Return aggregated metrics for each sector in a stock exchange, including sector market capitalization, price change percentage, trading volume, traded value, number of trades, and number of companies in the sector. Optionally filter for a single sector.",
74
+ inputSchema: sectorsOverviewSchema.shape,
75
+ }, async (input) => {
398
76
  try {
399
- const formattedDate = getDate(year, month, day);
400
- const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
401
- const sectors = marketDataResponse.securities.data
402
- .filter((item) => item[INDICES.TYPE] === "sector" && item[INDICES.SECTOR] !== "")
403
- .filter((item) => !sector || item[INDICES.TICKER] === sector)
404
- .map((item) => ({
405
- name: item[INDICES.TICKER],
406
- marketCap: item[INDICES.MARKET_CAP],
407
- marketCapChangePct: item[INDICES.PRICE_CHANGE_PCT],
408
- volume: item[INDICES.VOLUME],
409
- value: item[INDICES.VALUE],
410
- numTrades: item[INDICES.NUM_TRADES],
411
- itemsPerSector: item[INDICES.ITEMS_PER_SECTOR],
412
- }));
413
- return createResponse({
414
- info: INFO,
415
- charts: createCharts(stockExchange, formattedDate),
416
- date: formattedDate,
417
- exchange: stockExchange.toUpperCase(),
418
- currency: EXCHANGE_INFO[stockExchange].currency,
419
- sectors,
420
- });
77
+ return createResponse(await getSectorsOverview(sectorsOverviewSchema.parse(input)));
421
78
  }
422
79
  catch (error) {
423
80
  return createErrorResponse(error);
424
81
  }
425
82
  });
426
- server.registerTool("get_stock_data", {
427
- title: "Stock data by ticker",
428
- description: "Get detailed market data for a specific ticker on an exchange and date, including price, change, volume, value, market cap, and trades.",
429
- inputSchema: {
430
- stockExchange: exchangeSchema,
431
- ...dateSchema,
432
- ticker: z.string().describe("Stock ticker symbol (case-sensitive)"),
433
- },
434
- }, async ({ stockExchange, year, month, day, ticker, }) => {
83
+ server.registerTool("get_stock_snapshot", {
84
+ title: "Get stock market snapshot",
85
+ description: "Return detailed trading metrics for a single stock ticker on a specific exchange and trading date, including price open, last sale price, price change percentage, trading volume, traded value, number of trades, and market capitalization.",
86
+ inputSchema: stockDataSchema.shape,
87
+ }, async (input) => {
435
88
  try {
436
- const formattedDate = getDate(year, month, day);
437
- const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
438
- const stockData = marketDataResponse.securities.data.find((item) => item[INDICES.TYPE] !== "sector" && item[INDICES.TICKER] === ticker);
439
- if (!stockData) {
440
- throw new Error(`Ticker ${ticker} not found on ${stockExchange} for date ${formattedDate}`);
441
- }
442
- return createResponse({
443
- info: INFO,
444
- charts: createCharts(stockExchange, formattedDate),
445
- exchange: stockData[INDICES.EXCHANGE],
446
- country: stockData[INDICES.COUNTRY],
447
- currency: EXCHANGE_INFO[stockExchange].currency,
448
- sector: stockData[INDICES.SECTOR],
449
- ticker: stockData[INDICES.TICKER],
450
- nameEng: stockData[INDICES.NAME_ENG],
451
- nameOriginal: stockData[INDICES.NAME_ORIGINAL],
452
- priceOpen: stockData[INDICES.PRICE_OPEN],
453
- priceLastSale: stockData[INDICES.PRICE_LAST_SALE],
454
- priceChangePct: stockData[INDICES.PRICE_CHANGE_PCT],
455
- volume: stockData[INDICES.VOLUME],
456
- value: stockData[INDICES.VALUE],
457
- numTrades: stockData[INDICES.NUM_TRADES],
458
- marketCap: stockData[INDICES.MARKET_CAP],
459
- listedFrom: stockData[INDICES.LISTED_FROM],
460
- listedTill: stockData[INDICES.LISTED_TILL],
461
- });
89
+ return createResponse(await getStockData(stockDataSchema.parse(input)));
462
90
  }
463
91
  catch (error) {
464
92
  return createErrorResponse(error);
465
93
  }
466
94
  });
467
- server.registerTool("rank_stocks", {
468
- title: "Rank stocks",
469
- description: "Rank stocks on an exchange by a chosen metric (marketCap, priceChangePct, volume, value, numTrades) for a specific date with order and limit.",
470
- inputSchema: {
471
- stockExchange: exchangeSchema,
472
- ...dateSchema,
473
- sortBy: z
474
- .enum(SORT_FIELDS)
475
- .describe("Sort by: marketCap, priceChangePct, volume, value, numTrades"),
476
- order: z
477
- .enum(SORT_ORDERS)
478
- .default("desc")
479
- .describe("Sort order: asc or desc"),
480
- limit: z
481
- .number()
482
- .int()
483
- .min(1)
484
- .max(500)
485
- .default(10)
486
- .describe("Number of results"),
487
- sector: z.string().optional().describe("Filter by specific sector"),
488
- },
489
- }, async ({ stockExchange, year, month, day, sortBy, order, limit, sector, }) => {
95
+ server.registerTool("rank_exchange_companies", {
96
+ title: "Rank companies by market metric",
97
+ description: "Return companies ranked by a selected market metric on a specific exchange and date. Supported metrics: market capitalization, price change percentage, trading volume, traded value, and number of trades. Results can be limited and optionally filtered by sector.",
98
+ inputSchema: rankStocksSchema.shape,
99
+ }, async (input) => {
490
100
  try {
491
- const formattedDate = getDate(year, month, day);
492
- const marketDataResponse = await fetchMarketData(stockExchange, formattedDate);
493
- const stocks = marketDataResponse.securities.data
494
- .filter((item) => item[INDICES.TYPE] !== "sector" && item[INDICES.SECTOR] !== "")
495
- .filter((item) => !sector || item[INDICES.SECTOR] === sector)
496
- .map((item) => ({
497
- ticker: item[INDICES.TICKER],
498
- name: item[INDICES.NAME_ENG],
499
- sector: item[INDICES.SECTOR],
500
- priceLastSale: item[INDICES.PRICE_LAST_SALE],
501
- priceChangePct: item[INDICES.PRICE_CHANGE_PCT],
502
- marketCap: item[INDICES.MARKET_CAP],
503
- volume: item[INDICES.VOLUME],
504
- value: item[INDICES.VALUE],
505
- numTrades: item[INDICES.NUM_TRADES],
506
- }))
507
- .sort((a, b) => {
508
- const aVal = a[sortBy], bVal = b[sortBy];
509
- return order === "desc" ? bVal - aVal : aVal - bVal;
510
- })
511
- .slice(0, limit);
512
- return createResponse({
513
- info: INFO,
514
- charts: createCharts(stockExchange, formattedDate),
515
- date: formattedDate,
516
- exchange: stockExchange.toUpperCase(),
517
- currency: EXCHANGE_INFO[stockExchange].currency,
518
- sortBy,
519
- order,
520
- limit,
521
- count: stocks.length,
522
- stocks,
523
- });
101
+ return createResponse(await rankStocks(rankStocksSchema.parse(input)));
524
102
  }
525
103
  catch (error) {
526
104
  return createErrorResponse(error);
527
105
  }
528
106
  });
529
- server.registerTool("get_company_profile", {
530
- title: "Company profile (US)",
531
- description: "Get business description, industry, and background for a US-listed company by ticker.",
532
- inputSchema: {
533
- exchange: z
534
- .enum(US_EXCHANGES)
535
- .describe("US exchange: amex, nasdaq, nyse"),
536
- ticker: z.string().describe("Stock ticker symbol (case-sensitive)"),
537
- },
538
- }, async ({ exchange, ticker }) => {
107
+ server.registerTool("get_company_profile_us", {
108
+ title: "Get US company profile",
109
+ description: "Return business description and background information for a US-listed company by ticker. Supported exchanges: NASDAQ, NYSE, and AMEX.",
110
+ inputSchema: companyProfileSchema.shape,
111
+ }, async (input) => {
539
112
  try {
540
- const securityInfo = await fetchSecurityInfo(exchange, ticker);
541
- return createResponse({
542
- info: INFO,
543
- charts: createCharts(exchange),
544
- ...securityInfo,
545
- });
113
+ return createResponse(await getCompanyProfile(companyProfileSchema.parse(input)));
546
114
  }
547
115
  catch (error) {
548
116
  return createErrorResponse(error);
@@ -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: "2.0.8",
7
+ version: "3.0.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": "2.0.8",
3
+ "version": "3.0.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,15 +73,15 @@
73
73
  "package": "npm run build:stdio && npm pack --pack-destination dist"
74
74
  },
75
75
  "dependencies": {
76
- "agents": "^0.7.5",
76
+ "agents": "^0.7.6",
77
77
  "zod": "^4.3.6"
78
78
  },
79
79
  "devDependencies": {
80
- "@biomejs/biome": "^2.4.6",
81
- "@types/node": "^25.3.5",
80
+ "@biomejs/biome": "^2.4.7",
81
+ "@types/node": "^25.5.0",
82
82
  "rimraf": "^6.1.3",
83
83
  "tsx": "^4.21.0",
84
84
  "typescript": "5.9.3",
85
- "wrangler": "^4.71.0"
85
+ "wrangler": "^4.74.0"
86
86
  }
87
87
  }