openfigi-mcp 0.2.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,195 @@
1
+ # openfigi-mcp
2
+
3
+ MCP (Model Context Protocol) server for the OpenFIGI API. Map financial identifiers to FIGIs directly from Claude and other MCP-compatible AI assistants.
4
+
5
+ ## Features
6
+
7
+ - **Search by identifier type**: ISIN, CUSIP, SEDOL, Ticker, Bloomberg ID
8
+ - **Auto-detection**: Automatically detect identifier types from text/CSV data
9
+ - **Ticker + Exchange**: Parse formats like `AAPL US`, `ABLI SS`, `VOD LN`
10
+ - **Batch operations**: Process up to 100 identifiers in a single request
11
+ - **Not Found reporting**: Clear indicators when identifiers don't exist
12
+ - **Reference resources**: Access identifier types, exchange codes, security types
13
+
14
+ ## Installation
15
+
16
+ ### Using npx (easiest)
17
+
18
+ No installation required - just configure Claude Desktop:
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "openfigi": {
24
+ "command": "npx",
25
+ "args": ["openfigi-mcp"],
26
+ "env": {
27
+ "OPENFIGI_API_KEY": "your-api-key-here"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### Global Installation
35
+
36
+ ```bash
37
+ npm install -g openfigi-mcp
38
+ ```
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "openfigi": {
44
+ "command": "openfigi-mcp",
45
+ "env": {
46
+ "OPENFIGI_API_KEY": "your-api-key-here"
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Config file location:
54
+ - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
55
+ - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
56
+
57
+ ## Available Tools
58
+
59
+ ### Search Tools
60
+
61
+ | Tool | Description | Example |
62
+ |------|-------------|---------|
63
+ | `search_by_isin` | Search by ISIN | `US0378331005` (Apple) |
64
+ | `search_by_cusip` | Search by CUSIP | `037833100` (Apple) |
65
+ | `search_by_sedol` | Search by SEDOL | `2046251` (Apple) |
66
+ | `search_by_ticker` | Search by ticker | `AAPL` with exchCode `US` |
67
+ | `search_by_bloomberg_id` | Search by Bloomberg ID | `BBG000B9XRY4` |
68
+
69
+ ### Auto-Detection Tools
70
+
71
+ | Tool | Description |
72
+ |------|-------------|
73
+ | `search_auto_detect` | Auto-detect identifier type and search a single identifier |
74
+ | `parse_identifiers` | Parse CSV/text and detect identifier types (no API call) |
75
+ | `batch_search_auto_detect` | Parse and search multiple identifiers in one request |
76
+
77
+ ### Utility Tools
78
+
79
+ | Tool | Description |
80
+ |------|-------------|
81
+ | `batch_mapping` | Map up to 100 identifiers with explicit types |
82
+ | `validate_identifier` | Validate identifier format without searching |
83
+ | `get_rate_limit_status` | Check current API rate limit status |
84
+
85
+ ## Auto-Detection Examples
86
+
87
+ ### Tickers with Exchange Codes
88
+
89
+ The tools recognize the Bloomberg Terminal format `TICKER EXCHANGE`:
90
+
91
+ ```
92
+ Ticker
93
+ ABLI SS
94
+ ABSO SS
95
+ AAPL US
96
+ VOD LN
97
+ 7203 JP
98
+ ```
99
+
100
+ Common exchange codes:
101
+
102
+ | Code | Exchange |
103
+ |------|----------|
104
+ | `US` | United States |
105
+ | `SS` | Stockholm (Nasdaq Stockholm) |
106
+ | `LN` | London |
107
+ | `GY` | Germany (Xetra) |
108
+ | `JP` | Japan |
109
+ | `HK` | Hong Kong |
110
+ | `FP` | France (Euronext Paris) |
111
+
112
+ ### Mixed Identifiers
113
+
114
+ The tools auto-detect different identifier types:
115
+
116
+ ```
117
+ US0378331005 → ISIN [high confidence]
118
+ BBG000B9XRY4 → Bloomberg ID [high confidence]
119
+ AAPL US → Ticker with exchange [high confidence]
120
+ 037833100 → CUSIP [medium confidence]
121
+ 2046251 → SEDOL [medium confidence]
122
+ AAPL → Ticker [low confidence]
123
+ ```
124
+
125
+ ### Not Found Handling
126
+
127
+ When identifiers don't exist in OpenFIGI, the output clearly indicates:
128
+
129
+ **Single search:**
130
+ ```
131
+ Detected type: TICKER (Exchange: SS) [high confidence]
132
+
133
+ ⚠️ NOT FOUND: "BLABLABLA" on exchange SS returned no results.
134
+
135
+ No results found
136
+ ```
137
+
138
+ **Batch search:**
139
+ ```
140
+ Detected 3 identifiers:
141
+ - Ticker: 3
142
+
143
+ [1] TICKER: AAPL [US]
144
+ Result 1:
145
+ FIGI: BBG000B9XRY4
146
+ Name: APPLE INC
147
+ ...
148
+
149
+ ---
150
+
151
+ [2] TICKER: INVALID [SS] ⚠️ NOT FOUND
152
+ No results found
153
+
154
+ ---
155
+
156
+ [3] TICKER: MSFT [US]
157
+ Result 1:
158
+ FIGI: BBG000BPH459
159
+ ...
160
+
161
+ ---
162
+ Results: 2 found, 1 not found
163
+
164
+ ⚠️ Not found:
165
+ - INVALID [SS]
166
+ ```
167
+
168
+ ## Available Resources
169
+
170
+ Access reference data via MCP resources:
171
+
172
+ | Resource URI | Description |
173
+ |--------------|-------------|
174
+ | `openfigi://identifier-types` | List of supported identifier types (ID_ISIN, ID_CUSIP, etc.) |
175
+ | `openfigi://exchange-codes` | Common exchange codes (US, SS, LN, etc.) |
176
+ | `openfigi://security-types` | Security type values (Common Stock, ETF, etc.) |
177
+ | `openfigi://market-sectors` | Market sector values (Equity, Corp, etc.) |
178
+
179
+ ## Environment Variables
180
+
181
+ | Variable | Description | Required |
182
+ |----------|-------------|----------|
183
+ | `OPENFIGI_API_KEY` | Your OpenFIGI API key | No (but recommended) |
184
+
185
+ ## API Key
186
+
187
+ Get your free API key at [openfigi.com/api](https://www.openfigi.com/api).
188
+
189
+ | | Without API Key | With API Key |
190
+ |---|-----------------|--------------|
191
+ | Rate Limit | 25 req/min | 250 req/min |
192
+
193
+ ## License
194
+
195
+ MIT
@@ -0,0 +1 @@
1
+ export { };
package/dist/index.js ADDED
@@ -0,0 +1,915 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
+ import { createClient, isValidBloombergId, isValidCUSIP, isValidISIN, isValidSEDOL } from "openfigi-sdk";
5
+
6
+ //#region src/tools/index.ts
7
+ const filterProperties = {
8
+ exchCode: {
9
+ type: "string",
10
+ description: "Exchange code filter (e.g., \"US\", \"NASDAQ\")"
11
+ },
12
+ micCode: {
13
+ type: "string",
14
+ description: "MIC code filter (e.g., \"XNAS\", \"XNYS\")"
15
+ },
16
+ currency: {
17
+ type: "string",
18
+ description: "Currency filter (e.g., \"USD\", \"EUR\")"
19
+ },
20
+ marketSecDes: {
21
+ type: "string",
22
+ description: "Market sector filter (e.g., \"Equity\", \"Corp\")"
23
+ },
24
+ securityType: {
25
+ type: "string",
26
+ description: "Security type filter (e.g., \"Common Stock\")"
27
+ },
28
+ includeUnlistedEquities: {
29
+ type: "boolean",
30
+ description: "Include unlisted equities"
31
+ }
32
+ };
33
+ const getClient = () => {
34
+ const apiKey = process.env.OPENFIGI_API_KEY;
35
+ return createClient({ apiKey });
36
+ };
37
+ const hasApiKey = () => Boolean(process.env.OPENFIGI_API_KEY);
38
+ const getBatchSize = () => hasApiKey() ? 100 : 10;
39
+ const chunkArray = (array, size) => {
40
+ const chunks = [];
41
+ for (let i = 0; i < array.length; i += size) chunks.push(array.slice(i, i + size));
42
+ return chunks;
43
+ };
44
+ const extractFilters = (args) => ({
45
+ exchCode: args.exchCode,
46
+ micCode: args.micCode,
47
+ currency: args.currency,
48
+ marketSecDes: args.marketSecDes,
49
+ securityType: args.securityType,
50
+ includeUnlistedEquities: args.includeUnlistedEquities
51
+ });
52
+ const detectIdentifierType = (identifier) => {
53
+ const trimmed = identifier.trim();
54
+ if (isValidISIN(trimmed)) return {
55
+ value: trimmed,
56
+ type: "ISIN",
57
+ confidence: "high"
58
+ };
59
+ if (isValidBloombergId(trimmed)) return {
60
+ value: trimmed,
61
+ type: "BLOOMBERG_ID",
62
+ confidence: "high"
63
+ };
64
+ if (isValidCUSIP(trimmed)) return {
65
+ value: trimmed,
66
+ type: "CUSIP",
67
+ confidence: "medium"
68
+ };
69
+ if (isValidSEDOL(trimmed)) return {
70
+ value: trimmed,
71
+ type: "SEDOL",
72
+ confidence: "medium"
73
+ };
74
+ const tickerWithExchange = trimmed.match(/^([A-Z0-9]+)\s+([A-Z]{2})$/i);
75
+ if (tickerWithExchange) return {
76
+ value: tickerWithExchange[1].toUpperCase(),
77
+ type: "TICKER",
78
+ exchCode: tickerWithExchange[2].toUpperCase(),
79
+ confidence: "high"
80
+ };
81
+ if (/^[A-Z]{1,5}$/.test(trimmed)) return {
82
+ value: trimmed,
83
+ type: "TICKER",
84
+ confidence: "low"
85
+ };
86
+ if (/^[A-Z0-9.]{1,10}$/i.test(trimmed)) return {
87
+ value: trimmed.toUpperCase(),
88
+ type: "TICKER",
89
+ confidence: "low"
90
+ };
91
+ return {
92
+ value: trimmed,
93
+ type: "UNKNOWN",
94
+ confidence: "low"
95
+ };
96
+ };
97
+ const parseAndDetectIdentifiers = (text) => {
98
+ const lines = text.split(/\r?\n/).filter((line) => line.trim());
99
+ const identifiers = [];
100
+ const typeCounts = {
101
+ ISIN: 0,
102
+ CUSIP: 0,
103
+ SEDOL: 0,
104
+ BLOOMBERG_ID: 0,
105
+ TICKER: 0,
106
+ UNKNOWN: 0
107
+ };
108
+ const firstLine = lines[0]?.trim().toLowerCase();
109
+ const hasHeader = firstLine?.includes("ticker") || firstLine?.includes("isin") || firstLine?.includes("cusip") || firstLine?.includes("sedol") || firstLine?.includes("symbol") || firstLine?.includes("identifier");
110
+ const dataLines = hasHeader ? lines.slice(1) : lines;
111
+ for (const line of dataLines) {
112
+ const columns = line.split(/[,\t]/);
113
+ const value = columns[0]?.trim();
114
+ if (value && value.length > 0) {
115
+ const detected = detectIdentifierType(value);
116
+ identifiers.push(detected);
117
+ typeCounts[detected.type]++;
118
+ }
119
+ }
120
+ const summary = [
121
+ `Detected ${identifiers.length} identifiers:`,
122
+ typeCounts.ISIN > 0 ? ` - ISIN: ${typeCounts.ISIN}` : null,
123
+ typeCounts.CUSIP > 0 ? ` - CUSIP: ${typeCounts.CUSIP}` : null,
124
+ typeCounts.SEDOL > 0 ? ` - SEDOL: ${typeCounts.SEDOL}` : null,
125
+ typeCounts.BLOOMBERG_ID > 0 ? ` - Bloomberg ID: ${typeCounts.BLOOMBERG_ID}` : null,
126
+ typeCounts.TICKER > 0 ? ` - Ticker: ${typeCounts.TICKER}` : null,
127
+ typeCounts.UNKNOWN > 0 ? ` - Unknown: ${typeCounts.UNKNOWN}` : null
128
+ ].filter(Boolean).join("\n");
129
+ return {
130
+ identifiers,
131
+ summary
132
+ };
133
+ };
134
+ const formatResponse = (response) => {
135
+ if (response.error) return `Error: ${response.error}`;
136
+ if (response.warning) return `Warning: ${response.warning}`;
137
+ if (!response.data || response.data.length === 0) return "No results found";
138
+ return response.data.map((result, index) => {
139
+ const lines = [`Result ${index + 1}:`];
140
+ lines.push(` FIGI: ${result.figi}`);
141
+ if (result.name) lines.push(` Name: ${result.name}`);
142
+ if (result.ticker) lines.push(` Ticker: ${result.ticker}`);
143
+ if (result.exchCode) lines.push(` Exchange: ${result.exchCode}`);
144
+ if (result.marketSector) lines.push(` Market Sector: ${result.marketSector}`);
145
+ if (result.securityType) lines.push(` Security Type: ${result.securityType}`);
146
+ if (result.compositeFIGI) lines.push(` Composite FIGI: ${result.compositeFIGI}`);
147
+ if (result.shareClassFIGI) lines.push(` Share Class FIGI: ${result.shareClassFIGI}`);
148
+ return lines.join("\n");
149
+ }).join("\n\n");
150
+ };
151
+ const toolDefinitions = [
152
+ {
153
+ name: "search_by_isin",
154
+ description: "Search for financial instruments by ISIN (International Securities Identification Number). Returns FIGI identifiers and instrument details.",
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {
158
+ isin: {
159
+ type: "string",
160
+ description: "ISIN to search for (e.g., \"US0378331005\" for Apple)"
161
+ },
162
+ ...filterProperties
163
+ },
164
+ required: ["isin"]
165
+ }
166
+ },
167
+ {
168
+ name: "search_by_cusip",
169
+ description: "Search for financial instruments by CUSIP (Committee on Uniform Securities Identification Procedures). Returns FIGI identifiers and instrument details.",
170
+ inputSchema: {
171
+ type: "object",
172
+ properties: {
173
+ cusip: {
174
+ type: "string",
175
+ description: "CUSIP to search for (e.g., \"037833100\" for Apple)"
176
+ },
177
+ ...filterProperties
178
+ },
179
+ required: ["cusip"]
180
+ }
181
+ },
182
+ {
183
+ name: "search_by_sedol",
184
+ description: "Search for financial instruments by SEDOL (Stock Exchange Daily Official List). Returns FIGI identifiers and instrument details.",
185
+ inputSchema: {
186
+ type: "object",
187
+ properties: {
188
+ sedol: {
189
+ type: "string",
190
+ description: "SEDOL to search for (e.g., \"2046251\" for Apple)"
191
+ },
192
+ ...filterProperties
193
+ },
194
+ required: ["sedol"]
195
+ }
196
+ },
197
+ {
198
+ name: "search_by_ticker",
199
+ description: "Search for financial instruments by ticker symbol. Returns FIGI identifiers and instrument details.",
200
+ inputSchema: {
201
+ type: "object",
202
+ properties: {
203
+ ticker: {
204
+ type: "string",
205
+ description: "Ticker symbol to search for (e.g., \"AAPL\")"
206
+ },
207
+ exchCode: {
208
+ type: "string",
209
+ description: "Exchange code to narrow search (e.g., \"US\", \"NASDAQ\")"
210
+ },
211
+ micCode: filterProperties.micCode,
212
+ currency: filterProperties.currency,
213
+ marketSecDes: filterProperties.marketSecDes,
214
+ securityType: filterProperties.securityType,
215
+ includeUnlistedEquities: filterProperties.includeUnlistedEquities
216
+ },
217
+ required: ["ticker"]
218
+ }
219
+ },
220
+ {
221
+ name: "search_by_bloomberg_id",
222
+ description: "Search for financial instruments by Bloomberg Global ID. Returns FIGI identifiers and instrument details.",
223
+ inputSchema: {
224
+ type: "object",
225
+ properties: {
226
+ bloombergId: {
227
+ type: "string",
228
+ description: "Bloomberg Global ID to search for (e.g., \"BBG000B9XRY4\")"
229
+ },
230
+ ...filterProperties
231
+ },
232
+ required: ["bloombergId"]
233
+ }
234
+ },
235
+ {
236
+ name: "batch_mapping",
237
+ description: "Map multiple financial identifiers to FIGIs in a single request. Supports up to 100 identifiers per request.",
238
+ inputSchema: {
239
+ type: "object",
240
+ properties: { identifiers: {
241
+ type: "array",
242
+ items: {
243
+ type: "object",
244
+ properties: {
245
+ idType: {
246
+ type: "string",
247
+ description: "Type of identifier (e.g., \"ID_ISIN\", \"ID_CUSIP\", \"ID_SEDOL\", \"ID_EXCH_SYMBOL\", \"ID_BB_GLOBAL\")"
248
+ },
249
+ idValue: {
250
+ type: "string",
251
+ description: "The identifier value"
252
+ },
253
+ exchCode: {
254
+ type: "string",
255
+ description: "Optional exchange code filter"
256
+ }
257
+ },
258
+ required: ["idType", "idValue"]
259
+ },
260
+ description: "Array of identifiers to map (max 100)"
261
+ } },
262
+ required: ["identifiers"]
263
+ }
264
+ },
265
+ {
266
+ name: "validate_identifier",
267
+ description: "Validate the format of a financial identifier (ISIN, CUSIP, SEDOL, or Bloomberg ID).",
268
+ inputSchema: {
269
+ type: "object",
270
+ properties: {
271
+ identifier: {
272
+ type: "string",
273
+ description: "The identifier to validate"
274
+ },
275
+ type: {
276
+ type: "string",
277
+ enum: [
278
+ "ISIN",
279
+ "CUSIP",
280
+ "SEDOL",
281
+ "BLOOMBERG_ID"
282
+ ],
283
+ description: "Type of identifier to validate"
284
+ }
285
+ },
286
+ required: ["identifier", "type"]
287
+ }
288
+ },
289
+ {
290
+ name: "get_rate_limit_status",
291
+ description: "Get the current OpenFIGI API rate limit status.",
292
+ inputSchema: {
293
+ type: "object",
294
+ properties: {}
295
+ }
296
+ },
297
+ {
298
+ name: "parse_identifiers",
299
+ description: "Parse and auto-detect financial identifiers from CSV/text data. Supports tickers with exchange codes (e.g., \"AAPL US\", \"ABLI SS\"), ISINs, CUSIPs, SEDOLs, and Bloomberg IDs. Automatically detects the identifier type and extracts exchange codes when present.",
300
+ inputSchema: {
301
+ type: "object",
302
+ properties: { text: {
303
+ type: "string",
304
+ description: "CSV or newline-separated text containing identifiers. Can include headers. Example: \"Ticker\\nABLI SS\\nACE SS\""
305
+ } },
306
+ required: ["text"]
307
+ }
308
+ },
309
+ {
310
+ name: "search_auto_detect",
311
+ description: "Automatically detect the identifier type and search for a single financial instrument. Supports ISINs, CUSIPs, SEDOLs, Bloomberg IDs, and tickers with optional exchange codes (e.g., \"AAPL US\").",
312
+ inputSchema: {
313
+ type: "object",
314
+ properties: { identifier: {
315
+ type: "string",
316
+ description: "The identifier to search for. Type will be auto-detected. Examples: \"US0378331005\" (ISIN), \"037833100\" (CUSIP), \"AAPL US\" (ticker with exchange)"
317
+ } },
318
+ required: ["identifier"]
319
+ }
320
+ },
321
+ {
322
+ name: "batch_search_auto_detect",
323
+ description: "Parse identifiers from text/CSV and search for all of them in a single batch operation. Automatically detects identifier types. Great for processing lists of tickers or identifiers from spreadsheets.",
324
+ inputSchema: {
325
+ type: "object",
326
+ properties: { text: {
327
+ type: "string",
328
+ description: "CSV or newline-separated text containing identifiers. Example: \"Ticker\\nABLI SS\\nACE SS\\nACTI SS\""
329
+ } },
330
+ required: ["text"]
331
+ }
332
+ }
333
+ ];
334
+ async function handleTool(name, args) {
335
+ const client = getClient();
336
+ try {
337
+ switch (name) {
338
+ case "search_by_isin": {
339
+ const isin = args.isin;
340
+ const filters = extractFilters(args);
341
+ const response = await client.searchByISIN(isin, filters);
342
+ return { content: [{
343
+ type: "text",
344
+ text: formatResponse(response)
345
+ }] };
346
+ }
347
+ case "search_by_cusip": {
348
+ const cusip = args.cusip;
349
+ const filters = extractFilters(args);
350
+ const response = await client.searchByCUSIP(cusip, filters);
351
+ return { content: [{
352
+ type: "text",
353
+ text: formatResponse(response)
354
+ }] };
355
+ }
356
+ case "search_by_sedol": {
357
+ const sedol = args.sedol;
358
+ const filters = extractFilters(args);
359
+ const response = await client.searchBySEDOL(sedol, filters);
360
+ return { content: [{
361
+ type: "text",
362
+ text: formatResponse(response)
363
+ }] };
364
+ }
365
+ case "search_by_ticker": {
366
+ const ticker = args.ticker;
367
+ const exchCode = args.exchCode;
368
+ const filters = extractFilters(args);
369
+ const response = await client.searchByTicker(ticker, exchCode, filters);
370
+ return { content: [{
371
+ type: "text",
372
+ text: formatResponse(response)
373
+ }] };
374
+ }
375
+ case "search_by_bloomberg_id": {
376
+ const bloombergId = args.bloombergId;
377
+ const filters = extractFilters(args);
378
+ const response = await client.searchByBloombergId(bloombergId, filters);
379
+ return { content: [{
380
+ type: "text",
381
+ text: formatResponse(response)
382
+ }] };
383
+ }
384
+ case "batch_mapping": {
385
+ const { identifiers } = args;
386
+ const requests = identifiers.map((id) => ({
387
+ idType: id.idType,
388
+ idValue: id.idValue,
389
+ exchCode: id.exchCode
390
+ }));
391
+ const batchSize = getBatchSize();
392
+ const batches = chunkArray(requests, batchSize);
393
+ const allResponses = [];
394
+ for (const batch of batches) {
395
+ const batchResponses = await client.mapping(batch);
396
+ allResponses.push(...batchResponses);
397
+ }
398
+ const responses = allResponses;
399
+ const formatted = responses.map((response, index) => {
400
+ const id = identifiers[index];
401
+ return `[${index + 1}] ${id.idType}: ${id.idValue}\n${formatResponse(response)}`;
402
+ }).join("\n\n---\n\n");
403
+ return { content: [{
404
+ type: "text",
405
+ text: formatted
406
+ }] };
407
+ }
408
+ case "validate_identifier": {
409
+ const { identifier, type } = args;
410
+ let isValid = false;
411
+ let format = "";
412
+ switch (type) {
413
+ case "ISIN":
414
+ isValid = isValidISIN(identifier);
415
+ format = "2 letter country code + 9 alphanumeric characters + 1 check digit";
416
+ break;
417
+ case "CUSIP":
418
+ isValid = isValidCUSIP(identifier);
419
+ format = "9 alphanumeric characters";
420
+ break;
421
+ case "SEDOL":
422
+ isValid = isValidSEDOL(identifier);
423
+ format = "7 alphanumeric characters";
424
+ break;
425
+ case "BLOOMBERG_ID":
426
+ isValid = isValidBloombergId(identifier);
427
+ format = "BBG + 9 alphanumeric characters";
428
+ break;
429
+ }
430
+ const result = isValid ? `Valid ${type}: "${identifier}"` : `Invalid ${type}: "${identifier}"\nExpected format: ${format}`;
431
+ return { content: [{
432
+ type: "text",
433
+ text: result
434
+ }] };
435
+ }
436
+ case "get_rate_limit_status": {
437
+ const rateLimit = client.getRateLimitInfo();
438
+ if (!rateLimit) return { content: [{
439
+ type: "text",
440
+ text: "No rate limit information available. Make a request first to get rate limit data."
441
+ }] };
442
+ const text = [
443
+ "OpenFIGI API Rate Limit Status:",
444
+ ` Limit: ${rateLimit.limit} requests`,
445
+ ` Remaining: ${rateLimit.remaining} requests`,
446
+ ` Resets: ${rateLimit.reset.toISOString()}`
447
+ ].join("\n");
448
+ return { content: [{
449
+ type: "text",
450
+ text
451
+ }] };
452
+ }
453
+ case "parse_identifiers": {
454
+ const text = args.text;
455
+ const { identifiers, summary } = parseAndDetectIdentifiers(text);
456
+ const details = identifiers.map((id, index) => {
457
+ const exchPart = id.exchCode ? ` (Exchange: ${id.exchCode})` : "";
458
+ return `${index + 1}. "${id.value}" → ${id.type}${exchPart} [${id.confidence} confidence]`;
459
+ }).join("\n");
460
+ return { content: [{
461
+ type: "text",
462
+ text: `${summary}\n\nDetails:\n${details}`
463
+ }] };
464
+ }
465
+ case "search_auto_detect": {
466
+ const identifier = args.identifier;
467
+ const detected = detectIdentifierType(identifier);
468
+ if (detected.type === "UNKNOWN") return { content: [{
469
+ type: "text",
470
+ text: `Could not determine identifier type for "${identifier}". Please use a specific search tool or provide more context.`
471
+ }] };
472
+ let response;
473
+ switch (detected.type) {
474
+ case "ISIN":
475
+ response = await client.searchByISIN(detected.value);
476
+ break;
477
+ case "CUSIP":
478
+ response = await client.searchByCUSIP(detected.value);
479
+ break;
480
+ case "SEDOL":
481
+ response = await client.searchBySEDOL(detected.value);
482
+ break;
483
+ case "BLOOMBERG_ID":
484
+ response = await client.searchByBloombergId(detected.value);
485
+ break;
486
+ case "TICKER":
487
+ response = await client.searchByTicker(detected.value, detected.exchCode);
488
+ break;
489
+ }
490
+ const header = `Detected type: ${detected.type}${detected.exchCode ? ` (Exchange: ${detected.exchCode})` : ""} [${detected.confidence} confidence]\n\n`;
491
+ const hasResults = response.data && response.data.length > 0;
492
+ const notFoundWarning = hasResults ? "" : `⚠️ NOT FOUND: "${detected.value}"${detected.exchCode ? ` on exchange ${detected.exchCode}` : ""} returned no results.\n\n`;
493
+ return { content: [{
494
+ type: "text",
495
+ text: header + notFoundWarning + formatResponse(response)
496
+ }] };
497
+ }
498
+ case "batch_search_auto_detect": {
499
+ const text = args.text;
500
+ const { identifiers, summary } = parseAndDetectIdentifiers(text);
501
+ const validIdentifiers = identifiers.filter((id) => id.type !== "UNKNOWN");
502
+ if (validIdentifiers.length === 0) return { content: [{
503
+ type: "text",
504
+ text: `${summary}\n\nNo valid identifiers found to search.`
505
+ }] };
506
+ const idTypeMap = {
507
+ ISIN: "ID_ISIN",
508
+ CUSIP: "ID_CUSIP",
509
+ SEDOL: "ID_SEDOL",
510
+ BLOOMBERG_ID: "ID_BB_GLOBAL",
511
+ TICKER: "ID_EXCH_SYMBOL",
512
+ UNKNOWN: "ID_EXCH_SYMBOL"
513
+ };
514
+ const requests = validIdentifiers.map((id) => ({
515
+ idType: idTypeMap[id.type],
516
+ idValue: id.value,
517
+ exchCode: id.exchCode,
518
+ ...id.type === "TICKER" && { securityType2: "Common Stock" }
519
+ }));
520
+ const batchSize = getBatchSize();
521
+ const batches = chunkArray(requests, batchSize);
522
+ const allResponses = [];
523
+ for (const batch of batches) {
524
+ const batchResponses = await client.mapping(batch);
525
+ allResponses.push(...batchResponses);
526
+ }
527
+ const responses = allResponses;
528
+ const found = [];
529
+ const notFound = [];
530
+ const formatted = responses.map((response, index) => {
531
+ const id = validIdentifiers[index];
532
+ const exchPart = id.exchCode ? ` [${id.exchCode}]` : "";
533
+ const idLabel = `${id.value}${exchPart}`;
534
+ const hasResults = response.data && response.data.length > 0;
535
+ if (hasResults) found.push(idLabel);
536
+ else notFound.push(idLabel);
537
+ const status = hasResults ? "" : " ⚠️ NOT FOUND";
538
+ return `[${index + 1}] ${id.type}: ${idLabel}${status}\n${formatResponse(response)}`;
539
+ }).join("\n\n---\n\n");
540
+ const resultSummary = [`\nResults: ${found.length} found, ${notFound.length} not found`, notFound.length > 0 ? `\n⚠️ Not found:\n${notFound.map((id) => ` - ${id}`).join("\n")}` : ""].join("");
541
+ return { content: [{
542
+ type: "text",
543
+ text: `${summary}\n\n${formatted}\n\n---${resultSummary}`
544
+ }] };
545
+ }
546
+ default: return { content: [{
547
+ type: "text",
548
+ text: `Unknown tool: ${name}`
549
+ }] };
550
+ }
551
+ } catch (error) {
552
+ const message = error instanceof Error ? error.message : String(error);
553
+ return { content: [{
554
+ type: "text",
555
+ text: `Error: ${message}`
556
+ }] };
557
+ }
558
+ }
559
+
560
+ //#endregion
561
+ //#region src/resources/index.ts
562
+ const resourceDefinitions = [
563
+ {
564
+ uri: "openfigi://search-guide",
565
+ name: "Search Guide",
566
+ description: "IMPORTANT: Read this first! Guide on how to effectively search for securities using OpenFIGI",
567
+ mimeType: "text/plain"
568
+ },
569
+ {
570
+ uri: "openfigi://identifier-types",
571
+ name: "Identifier Types",
572
+ description: "List of supported financial identifier types for OpenFIGI API",
573
+ mimeType: "text/plain"
574
+ },
575
+ {
576
+ uri: "openfigi://exchange-codes",
577
+ name: "Exchange Codes",
578
+ description: "Common exchange codes used in OpenFIGI API",
579
+ mimeType: "text/plain"
580
+ },
581
+ {
582
+ uri: "openfigi://security-types",
583
+ name: "Security Types",
584
+ description: "Common security types used in OpenFIGI API",
585
+ mimeType: "text/plain"
586
+ },
587
+ {
588
+ uri: "openfigi://market-sectors",
589
+ name: "Market Sectors",
590
+ description: "Market sector values used in OpenFIGI API",
591
+ mimeType: "text/plain"
592
+ }
593
+ ];
594
+ const searchGuideContent = `OpenFIGI Search Guide
595
+ =====================
596
+
597
+ ## Best Practices for Searching Securities
598
+
599
+ ### 1. PREFER ISIN OVER TICKER
600
+ ISINs are globally unique and always return accurate results:
601
+ - ISIN format: 2-letter country code + 9 alphanumeric + 1 check digit
602
+ - Example: US0378331005 (Apple), SE0000108656 (Ericsson)
603
+ - Use search_by_isin for the most reliable results
604
+
605
+ ### 2. TICKER SEARCH REQUIREMENTS
606
+ When searching by ticker (search_by_ticker or search_auto_detect):
607
+ - MUST include exchange code for accurate results
608
+ - Uses securityType2="Common Stock" by default
609
+ - Swedish tickers need share class: "ERIC B" not "ERIC"
610
+ - US tickers: "AAPL" with exchCode "US"
611
+
612
+ ### 3. EXCHANGE CODE MAPPING
613
+ Common Bloomberg-style suffixes and their OpenFIGI exchCode:
614
+ - "SS" → exchCode: "SS" (Stockholm/Nasdaq Nordic)
615
+ - "US" → exchCode: "US" (United States)
616
+ - "LN" → exchCode: "LONDON" or "LSE"
617
+ - "GY" or "GR" → exchCode: "XETRA" or "FRANKFURT"
618
+ - "FP" → exchCode: "EURONEXT-PARIS"
619
+ - "NA" → exchCode: "EURONEXT-AMSTER"
620
+ - "FH" → exchCode: "FH" (Helsinki)
621
+ - "DC" → exchCode: "NOMX COPENHAGEN"
622
+ - "NO" → exchCode: "OSLO"
623
+ - "JP" → exchCode: "TOKYO"
624
+ - "HK" → exchCode: "HONG KONG"
625
+
626
+ ### 4. NORDIC STOCK TICKER CONVENTIONS
627
+ Nordic stocks often have share class suffixes:
628
+ - A shares: "VOLV A SS" (Volvo A shares)
629
+ - B shares: "ERIC B SS" (Ericsson B shares)
630
+ - Common pattern: TICKER + space + CLASS + space + EXCHANGE
631
+ - If search fails, try adding "A" or "B" suffix
632
+
633
+ ### 5. HANDLING "NOT FOUND" RESULTS
634
+ If a ticker search returns no results:
635
+ 1. Check if share class is needed (add A/B suffix)
636
+ 2. Try without exchange code for broader search
637
+ 3. Search by ISIN if available (most reliable)
638
+ 4. Check if company uses different ticker in OpenFIGI
639
+
640
+ ### 6. BATCH SEARCH TIPS
641
+ For batch_search_auto_detect:
642
+ - Supports ISINs, tickers with exchange codes, CUSIPs, SEDOLs
643
+ - Format: "TICKER EXCHANGE" on each line (e.g., "AAPL US")
644
+ - Automatically splits into smaller batches
645
+ - Mix of identifier types is supported
646
+
647
+ ### 7. DATA SOURCE FORMATS
648
+ Different data sources format tickers differently:
649
+
650
+ Bloomberg: "AAPL US Equity" → use "AAPL" with exchCode "US"
651
+ FactSet: "AAPL-US" → use "AAPL" with exchCode "US"
652
+ Reuters: "AAPL.O" → use "AAPL" with exchCode "NASDAQ"
653
+
654
+ ### 8. WHEN TO USE WHICH TOOL
655
+ - search_by_isin: Best for accuracy, use when ISIN is available
656
+ - search_by_ticker: When you have ticker + exchange
657
+ - search_auto_detect: Single identifier, type unknown
658
+ - batch_search_auto_detect: Multiple identifiers from spreadsheet/CSV
659
+ - search_by_cusip: US securities with CUSIP
660
+ - search_by_sedol: UK/Irish securities with SEDOL
661
+
662
+ ### 9. COMMON PITFALLS
663
+ - Ticker without exchange: May return too many or zero results
664
+ - Wrong exchange code: "NASDAQ" vs "US" can give different results
665
+ - Missing share class: Nordic stocks often need A/B suffix
666
+ - Case sensitivity: Tickers are converted to uppercase automatically
667
+ `;
668
+ const identifierTypesContent = `OpenFIGI Identifier Types
669
+ ========================
670
+
671
+ The OpenFIGI API supports the following identifier types (idType):
672
+
673
+ Primary Identifiers:
674
+ - ID_ISIN: International Securities Identification Number (12 characters, e.g., US0378331005)
675
+ - ID_CUSIP: Committee on Uniform Securities Identification Procedures (9 characters, e.g., 037833100)
676
+ - ID_SEDOL: Stock Exchange Daily Official List (7 characters, e.g., 2046251)
677
+ - ID_EXCH_SYMBOL: Exchange ticker symbol (e.g., AAPL)
678
+
679
+ Bloomberg Identifiers:
680
+ - ID_BB_GLOBAL: Bloomberg Global Identifier (e.g., BBG000B9XRY4)
681
+ - ID_BB: Bloomberg ID
682
+ - ID_BB_UNIQUE: Bloomberg Unique ID
683
+ - ID_BB_SEC_NUM: Bloomberg Security Number
684
+ - ID_BB_SEC_NUM_DES: Bloomberg Security Number Description
685
+ - ID_BB_GLOBAL_SHARE_CLASS_LEVEL: Bloomberg Share Class Level FIGI
686
+ - COMPOSITE_ID_BB_GLOBAL: Bloomberg Composite Global ID
687
+
688
+ Regional Identifiers:
689
+ - ID_CINS: CUSIP International Numbering System
690
+ - ID_COMMON: Common Code (used in Euroclear/Clearstream)
691
+ - ID_WERTPAPIER: German securities identifier (WKN)
692
+ - ID_BELGIUM: Belgian securities identifier
693
+ - ID_DENMARK: Danish securities identifier (Fondskode)
694
+ - ID_FRANCE: French securities identifier (SICOVAM)
695
+ - ID_ITALY: Italian securities identifier (Codice ABI)
696
+ - ID_JAPAN: Japanese securities identifier
697
+ - ID_LUXEMBOURG: Luxembourg securities identifier
698
+ - ID_NETHERLANDS: Dutch securities identifier
699
+ - ID_POLAND: Polish securities identifier
700
+ - ID_PORTUGAL: Portuguese securities identifier
701
+ - ID_SWEDEN: Swedish securities identifier
702
+
703
+ Other:
704
+ - ID_FULL_EXCHANGE_SYMBOL: Full exchange symbol including exchange identifier
705
+ - ID_SHORT_CODE: Short code identifier
706
+
707
+ Usage Example:
708
+ {
709
+ "idType": "ID_ISIN",
710
+ "idValue": "US0378331005"
711
+ }
712
+ `;
713
+ const exchangeCodesContent = `OpenFIGI Exchange Codes
714
+ ======================
715
+
716
+ Common Exchange Codes (exchCode):
717
+
718
+ United States:
719
+ - US: United States
720
+ - NASDAQ: NASDAQ Stock Exchange
721
+ - NYSE: New York Stock Exchange
722
+ - NYSE AMERICAN: NYSE American (formerly AMEX)
723
+ - NYSE ARCA: NYSE Arca
724
+ - CBOE: Chicago Board Options Exchange
725
+ - CME: Chicago Mercantile Exchange
726
+
727
+ Europe:
728
+ - LONDON: London Stock Exchange
729
+ - LSE: London Stock Exchange
730
+ - EURONEXT-PARIS: Euronext Paris
731
+ - EURONEXT-AMSTER: Euronext Amsterdam
732
+ - XETRA: Deutsche Börse XETRA
733
+ - FRANKFURT: Frankfurt Stock Exchange
734
+ - SIX: SIX Swiss Exchange
735
+ - MILAN: Milan Stock Exchange
736
+
737
+ Asia-Pacific:
738
+ - TOKYO: Tokyo Stock Exchange
739
+ - HONG KONG: Hong Kong Stock Exchange
740
+ - SHANGHAI: Shanghai Stock Exchange
741
+ - SHENZHEN: Shenzhen Stock Exchange
742
+ - SGX: Singapore Exchange
743
+ - ASX: Australian Securities Exchange
744
+ - KOREA: Korea Exchange
745
+ - KOSDAQ: KOSDAQ
746
+
747
+ Other Major Markets:
748
+ - TORONTO: Toronto Stock Exchange
749
+ - TSX VENTURE: TSX Venture Exchange
750
+ - MEXICO: Mexican Stock Exchange
751
+ - SAO PAULO: B3 (Brasil Bolsa Balcão)
752
+ - JOHANNESBURG: Johannesburg Stock Exchange
753
+
754
+ Special Values:
755
+ - NOT LISTED: For unlisted securities
756
+ - OTC US: US Over-the-Counter
757
+ - OTC BB: OTC Bulletin Board
758
+ - PINK SHEETS: Pink Sheets
759
+
760
+ Usage Example:
761
+ {
762
+ "idType": "ID_EXCH_SYMBOL",
763
+ "idValue": "AAPL",
764
+ "exchCode": "US"
765
+ }
766
+ `;
767
+ const securityTypesContent = `OpenFIGI Security Types
768
+ ======================
769
+
770
+ Common Security Types (securityType):
771
+
772
+ Equity:
773
+ - Common Stock: Common/ordinary shares
774
+ - Preferred: Preferred shares
775
+ - ADR: American Depositary Receipt
776
+ - GDR: Global Depositary Receipt
777
+ - REIT: Real Estate Investment Trust
778
+ - MLP: Master Limited Partnership
779
+ - Right: Rights issue
780
+ - Warrant: Warrant
781
+ - Unit: Unit (combination of securities)
782
+
783
+ Fixed Income:
784
+ - Bond: General bond
785
+ - Conv Bond: Convertible bond
786
+ - COMMERCIAL PAPER: Commercial paper
787
+ - TREASURY BILL: Treasury bill
788
+ - MED TERM NOTE: Medium term note
789
+
790
+ Derivatives:
791
+ - Equity Option: Stock option
792
+ - Index Option: Index option
793
+ - Currency future.: Currency future
794
+ - Currency option.: Currency option
795
+
796
+ Funds:
797
+ - Mutual Fund: Mutual fund
798
+ - Open-End Fund: Open-end fund
799
+ - Closed-End Fund: Closed-end fund
800
+ - ETP: Exchange Traded Product
801
+
802
+ Other:
803
+ - Index: Market index
804
+ - Crypto: Cryptocurrency
805
+ - SWAP: Swap contract
806
+ - FRA: Forward rate agreement
807
+
808
+ Usage Example:
809
+ {
810
+ "idType": "ID_ISIN",
811
+ "idValue": "US0378331005",
812
+ "securityType": "Common Stock"
813
+ }
814
+ `;
815
+ const marketSectorsContent = `OpenFIGI Market Sectors
816
+ ======================
817
+
818
+ Market Sector Values (marketSecDes):
819
+
820
+ - Equity: Stocks, shares, and equity derivatives
821
+ - Corp: Corporate bonds and debt instruments
822
+ - Govt: Government bonds and securities
823
+ - Muni: Municipal bonds
824
+ - Mtge: Mortgage-backed securities
825
+ - M-Mkt: Money market instruments
826
+ - Comdty: Commodities
827
+ - Curncy: Currencies and FX instruments
828
+ - Index: Market indices
829
+ - Pfd: Preferred securities
830
+
831
+ Usage Example:
832
+ {
833
+ "idType": "ID_EXCH_SYMBOL",
834
+ "idValue": "AAPL",
835
+ "marketSecDes": "Equity"
836
+ }
837
+
838
+ Note: Market sector helps narrow down search results when multiple
839
+ securities share the same identifier across different asset classes.
840
+ `;
841
+ function handleResource(uri) {
842
+ switch (uri) {
843
+ case "openfigi://search-guide": return { contents: [{
844
+ uri,
845
+ mimeType: "text/plain",
846
+ text: searchGuideContent
847
+ }] };
848
+ case "openfigi://identifier-types": return { contents: [{
849
+ uri,
850
+ mimeType: "text/plain",
851
+ text: identifierTypesContent
852
+ }] };
853
+ case "openfigi://exchange-codes": return { contents: [{
854
+ uri,
855
+ mimeType: "text/plain",
856
+ text: exchangeCodesContent
857
+ }] };
858
+ case "openfigi://security-types": return { contents: [{
859
+ uri,
860
+ mimeType: "text/plain",
861
+ text: securityTypesContent
862
+ }] };
863
+ case "openfigi://market-sectors": return { contents: [{
864
+ uri,
865
+ mimeType: "text/plain",
866
+ text: marketSectorsContent
867
+ }] };
868
+ default: throw new Error(`Unknown resource: ${uri}`);
869
+ }
870
+ }
871
+
872
+ //#endregion
873
+ //#region src/index.ts
874
+ const server = new Server({
875
+ name: "openfigi-mcp",
876
+ version: "0.1.0"
877
+ }, { capabilities: {
878
+ tools: {},
879
+ resources: {}
880
+ } });
881
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
882
+ return { tools: toolDefinitions.map((tool) => ({
883
+ name: tool.name,
884
+ description: tool.description,
885
+ inputSchema: tool.inputSchema
886
+ })) };
887
+ });
888
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
889
+ const { name, arguments: args } = request.params;
890
+ return handleTool(name, args ?? {});
891
+ });
892
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
893
+ return { resources: resourceDefinitions.map((resource) => ({
894
+ uri: resource.uri,
895
+ name: resource.name,
896
+ description: resource.description,
897
+ mimeType: resource.mimeType
898
+ })) };
899
+ });
900
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
901
+ const { uri } = request.params;
902
+ return handleResource(uri);
903
+ });
904
+ async function main() {
905
+ const transport = new StdioServerTransport();
906
+ await server.connect(transport);
907
+ console.error("OpenFIGI MCP server running on stdio");
908
+ }
909
+ main().catch((error) => {
910
+ console.error("Fatal error:", error);
911
+ process.exit(1);
912
+ });
913
+
914
+ //#endregion
915
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["array: T[]","size: number","chunks: T[][]","args: Record<string, unknown>","identifier: string","text: string","identifiers: DetectedIdentifier[]","typeCounts: Record<IdentifierType, number>","response: MappingResponse","result: FigiResult","index: number","name: string","requests: MappingRequest[]","allResponses: MappingResponse[]","idTypeMap: Record<IdentifierType, MappingRequest['idType']>","found: string[]","notFound: string[]","uri: string"],"sources":["../src/tools/index.ts","../src/resources/index.ts","../src/index.ts"],"sourcesContent":["import {\n createClient,\n isValidISIN,\n isValidCUSIP,\n isValidSEDOL,\n isValidBloombergId,\n type MappingRequest,\n type MappingResponse,\n type FigiResult,\n} from 'openfigi-sdk'\n\ninterface SearchFilters {\n exchCode?: string\n micCode?: string\n currency?: string\n marketSecDes?: string\n securityType?: string\n includeUnlistedEquities?: boolean\n}\n\n// Common filter properties for JSON schema\nconst filterProperties = {\n exchCode: {\n type: 'string',\n description: 'Exchange code filter (e.g., \"US\", \"NASDAQ\")',\n },\n micCode: {\n type: 'string',\n description: 'MIC code filter (e.g., \"XNAS\", \"XNYS\")',\n },\n currency: {\n type: 'string',\n description: 'Currency filter (e.g., \"USD\", \"EUR\")',\n },\n marketSecDes: {\n type: 'string',\n description: 'Market sector filter (e.g., \"Equity\", \"Corp\")',\n },\n securityType: {\n type: 'string',\n description: 'Security type filter (e.g., \"Common Stock\")',\n },\n includeUnlistedEquities: {\n type: 'boolean',\n description: 'Include unlisted equities',\n },\n} as const\n\n// Create client with API key from environment\nconst getClient = () => {\n const apiKey = process.env.OPENFIGI_API_KEY\n return createClient({ apiKey })\n}\n\n// Check if API key is configured\nconst hasApiKey = () => Boolean(process.env.OPENFIGI_API_KEY)\n\n// Batch size limits based on API key presence\nconst getBatchSize = () => (hasApiKey() ? 100 : 10)\n\n// Helper to chunk array into smaller batches\nconst chunkArray = <T>(array: T[], size: number): T[][] => {\n const chunks: T[][] = []\n for (let i = 0; i < array.length; i += size) {\n chunks.push(array.slice(i, i + size))\n }\n return chunks\n}\n\n// Extract filter properties from args\nconst extractFilters = (args: Record<string, unknown>): SearchFilters => ({\n exchCode: args.exchCode as string | undefined,\n micCode: args.micCode as string | undefined,\n currency: args.currency as string | undefined,\n marketSecDes: args.marketSecDes as string | undefined,\n securityType: args.securityType as string | undefined,\n includeUnlistedEquities: args.includeUnlistedEquities as boolean | undefined,\n})\n\n// Detect identifier type from a string\ntype IdentifierType = 'ISIN' | 'CUSIP' | 'SEDOL' | 'BLOOMBERG_ID' | 'TICKER' | 'UNKNOWN'\n\ninterface DetectedIdentifier {\n value: string\n type: IdentifierType\n exchCode?: string\n confidence: 'high' | 'medium' | 'low'\n}\n\nconst detectIdentifierType = (identifier: string): DetectedIdentifier => {\n const trimmed = identifier.trim()\n\n // Check for ISIN (12 chars, 2 letter country code + 9 alphanumeric + 1 check digit)\n if (isValidISIN(trimmed)) {\n return { value: trimmed, type: 'ISIN', confidence: 'high' }\n }\n\n // Check for Bloomberg ID (starts with BBG)\n if (isValidBloombergId(trimmed)) {\n return { value: trimmed, type: 'BLOOMBERG_ID', confidence: 'high' }\n }\n\n // Check for CUSIP (9 alphanumeric)\n if (isValidCUSIP(trimmed)) {\n return { value: trimmed, type: 'CUSIP', confidence: 'medium' }\n }\n\n // Check for SEDOL (7 alphanumeric)\n if (isValidSEDOL(trimmed)) {\n return { value: trimmed, type: 'SEDOL', confidence: 'medium' }\n }\n\n // Check for ticker with exchange suffix (e.g., \"AAPL US\", \"ABLI SS\")\n const tickerWithExchange = trimmed.match(/^([A-Z0-9]+)\\s+([A-Z]{2})$/i)\n if (tickerWithExchange) {\n return {\n value: tickerWithExchange[1].toUpperCase(),\n type: 'TICKER',\n exchCode: tickerWithExchange[2].toUpperCase(),\n confidence: 'high',\n }\n }\n\n // Check for simple ticker (all caps, 1-5 chars)\n if (/^[A-Z]{1,5}$/.test(trimmed)) {\n return { value: trimmed, type: 'TICKER', confidence: 'low' }\n }\n\n // Check for ticker with numbers (e.g., \"BRK.A\", \"TSLA3\")\n if (/^[A-Z0-9.]{1,10}$/i.test(trimmed)) {\n return { value: trimmed.toUpperCase(), type: 'TICKER', confidence: 'low' }\n }\n\n return { value: trimmed, type: 'UNKNOWN', confidence: 'low' }\n}\n\n// Parse CSV/text data and detect identifiers\nconst parseAndDetectIdentifiers = (\n text: string\n): { identifiers: DetectedIdentifier[]; summary: string } => {\n const lines = text.split(/\\r?\\n/).filter((line) => line.trim())\n const identifiers: DetectedIdentifier[] = []\n const typeCounts: Record<IdentifierType, number> = {\n ISIN: 0,\n CUSIP: 0,\n SEDOL: 0,\n BLOOMBERG_ID: 0,\n TICKER: 0,\n UNKNOWN: 0,\n }\n\n // Check if first line looks like a header\n const firstLine = lines[0]?.trim().toLowerCase()\n const hasHeader =\n firstLine?.includes('ticker') ||\n firstLine?.includes('isin') ||\n firstLine?.includes('cusip') ||\n firstLine?.includes('sedol') ||\n firstLine?.includes('symbol') ||\n firstLine?.includes('identifier')\n\n const dataLines = hasHeader ? lines.slice(1) : lines\n\n for (const line of dataLines) {\n // Handle CSV/tab-separated: take first column\n const columns = line.split(/[,\\t]/)\n const value = columns[0]?.trim()\n\n if (value && value.length > 0) {\n const detected = detectIdentifierType(value)\n identifiers.push(detected)\n typeCounts[detected.type]++\n }\n }\n\n const summary = [\n `Detected ${identifiers.length} identifiers:`,\n typeCounts.ISIN > 0 ? ` - ISIN: ${typeCounts.ISIN}` : null,\n typeCounts.CUSIP > 0 ? ` - CUSIP: ${typeCounts.CUSIP}` : null,\n typeCounts.SEDOL > 0 ? ` - SEDOL: ${typeCounts.SEDOL}` : null,\n typeCounts.BLOOMBERG_ID > 0\n ? ` - Bloomberg ID: ${typeCounts.BLOOMBERG_ID}`\n : null,\n typeCounts.TICKER > 0 ? ` - Ticker: ${typeCounts.TICKER}` : null,\n typeCounts.UNKNOWN > 0 ? ` - Unknown: ${typeCounts.UNKNOWN}` : null,\n ]\n .filter(Boolean)\n .join('\\n')\n\n return { identifiers, summary }\n}\n\n// Format a mapping response for display\nconst formatResponse = (response: MappingResponse): string => {\n if (response.error) {\n return `Error: ${response.error}`\n }\n if (response.warning) {\n return `Warning: ${response.warning}`\n }\n if (!response.data || response.data.length === 0) {\n return 'No results found'\n }\n\n return response.data\n .map((result: FigiResult, index: number) => {\n const lines = [`Result ${index + 1}:`]\n lines.push(` FIGI: ${result.figi}`)\n if (result.name) lines.push(` Name: ${result.name}`)\n if (result.ticker) lines.push(` Ticker: ${result.ticker}`)\n if (result.exchCode) lines.push(` Exchange: ${result.exchCode}`)\n if (result.marketSector) lines.push(` Market Sector: ${result.marketSector}`)\n if (result.securityType) lines.push(` Security Type: ${result.securityType}`)\n if (result.compositeFIGI) lines.push(` Composite FIGI: ${result.compositeFIGI}`)\n if (result.shareClassFIGI) lines.push(` Share Class FIGI: ${result.shareClassFIGI}`)\n return lines.join('\\n')\n })\n .join('\\n\\n')\n}\n\n// Tool definitions\nexport const toolDefinitions = [\n {\n name: 'search_by_isin',\n description:\n 'Search for financial instruments by ISIN (International Securities Identification Number). Returns FIGI identifiers and instrument details.',\n inputSchema: {\n type: 'object',\n properties: {\n isin: {\n type: 'string',\n description: 'ISIN to search for (e.g., \"US0378331005\" for Apple)',\n },\n ...filterProperties,\n },\n required: ['isin'],\n },\n },\n {\n name: 'search_by_cusip',\n description:\n 'Search for financial instruments by CUSIP (Committee on Uniform Securities Identification Procedures). Returns FIGI identifiers and instrument details.',\n inputSchema: {\n type: 'object',\n properties: {\n cusip: {\n type: 'string',\n description: 'CUSIP to search for (e.g., \"037833100\" for Apple)',\n },\n ...filterProperties,\n },\n required: ['cusip'],\n },\n },\n {\n name: 'search_by_sedol',\n description:\n 'Search for financial instruments by SEDOL (Stock Exchange Daily Official List). Returns FIGI identifiers and instrument details.',\n inputSchema: {\n type: 'object',\n properties: {\n sedol: {\n type: 'string',\n description: 'SEDOL to search for (e.g., \"2046251\" for Apple)',\n },\n ...filterProperties,\n },\n required: ['sedol'],\n },\n },\n {\n name: 'search_by_ticker',\n description:\n 'Search for financial instruments by ticker symbol. Returns FIGI identifiers and instrument details.',\n inputSchema: {\n type: 'object',\n properties: {\n ticker: {\n type: 'string',\n description: 'Ticker symbol to search for (e.g., \"AAPL\")',\n },\n exchCode: {\n type: 'string',\n description: 'Exchange code to narrow search (e.g., \"US\", \"NASDAQ\")',\n },\n micCode: filterProperties.micCode,\n currency: filterProperties.currency,\n marketSecDes: filterProperties.marketSecDes,\n securityType: filterProperties.securityType,\n includeUnlistedEquities: filterProperties.includeUnlistedEquities,\n },\n required: ['ticker'],\n },\n },\n {\n name: 'search_by_bloomberg_id',\n description:\n 'Search for financial instruments by Bloomberg Global ID. Returns FIGI identifiers and instrument details.',\n inputSchema: {\n type: 'object',\n properties: {\n bloombergId: {\n type: 'string',\n description: 'Bloomberg Global ID to search for (e.g., \"BBG000B9XRY4\")',\n },\n ...filterProperties,\n },\n required: ['bloombergId'],\n },\n },\n {\n name: 'batch_mapping',\n description:\n 'Map multiple financial identifiers to FIGIs in a single request. Supports up to 100 identifiers per request.',\n inputSchema: {\n type: 'object',\n properties: {\n identifiers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n idType: {\n type: 'string',\n description:\n 'Type of identifier (e.g., \"ID_ISIN\", \"ID_CUSIP\", \"ID_SEDOL\", \"ID_EXCH_SYMBOL\", \"ID_BB_GLOBAL\")',\n },\n idValue: {\n type: 'string',\n description: 'The identifier value',\n },\n exchCode: {\n type: 'string',\n description: 'Optional exchange code filter',\n },\n },\n required: ['idType', 'idValue'],\n },\n description: 'Array of identifiers to map (max 100)',\n },\n },\n required: ['identifiers'],\n },\n },\n {\n name: 'validate_identifier',\n description:\n 'Validate the format of a financial identifier (ISIN, CUSIP, SEDOL, or Bloomberg ID).',\n inputSchema: {\n type: 'object',\n properties: {\n identifier: {\n type: 'string',\n description: 'The identifier to validate',\n },\n type: {\n type: 'string',\n enum: ['ISIN', 'CUSIP', 'SEDOL', 'BLOOMBERG_ID'],\n description: 'Type of identifier to validate',\n },\n },\n required: ['identifier', 'type'],\n },\n },\n {\n name: 'get_rate_limit_status',\n description: 'Get the current OpenFIGI API rate limit status.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n {\n name: 'parse_identifiers',\n description:\n 'Parse and auto-detect financial identifiers from CSV/text data. Supports tickers with exchange codes (e.g., \"AAPL US\", \"ABLI SS\"), ISINs, CUSIPs, SEDOLs, and Bloomberg IDs. Automatically detects the identifier type and extracts exchange codes when present.',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description:\n 'CSV or newline-separated text containing identifiers. Can include headers. Example: \"Ticker\\\\nABLI SS\\\\nACE SS\"',\n },\n },\n required: ['text'],\n },\n },\n {\n name: 'search_auto_detect',\n description:\n 'Automatically detect the identifier type and search for a single financial instrument. Supports ISINs, CUSIPs, SEDOLs, Bloomberg IDs, and tickers with optional exchange codes (e.g., \"AAPL US\").',\n inputSchema: {\n type: 'object',\n properties: {\n identifier: {\n type: 'string',\n description:\n 'The identifier to search for. Type will be auto-detected. Examples: \"US0378331005\" (ISIN), \"037833100\" (CUSIP), \"AAPL US\" (ticker with exchange)',\n },\n },\n required: ['identifier'],\n },\n },\n {\n name: 'batch_search_auto_detect',\n description:\n 'Parse identifiers from text/CSV and search for all of them in a single batch operation. Automatically detects identifier types. Great for processing lists of tickers or identifiers from spreadsheets.',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description:\n 'CSV or newline-separated text containing identifiers. Example: \"Ticker\\\\nABLI SS\\\\nACE SS\\\\nACTI SS\"',\n },\n },\n required: ['text'],\n },\n },\n] as const\n\n// Tool handlers\nexport async function handleTool(\n name: string,\n args: Record<string, unknown>\n): Promise<{ content: Array<{ type: 'text'; text: string }> }> {\n const client = getClient()\n\n try {\n switch (name) {\n case 'search_by_isin': {\n const isin = args.isin as string\n const filters = extractFilters(args)\n const response = await client.searchByISIN(isin, filters)\n return { content: [{ type: 'text', text: formatResponse(response) }] }\n }\n\n case 'search_by_cusip': {\n const cusip = args.cusip as string\n const filters = extractFilters(args)\n const response = await client.searchByCUSIP(cusip, filters)\n return { content: [{ type: 'text', text: formatResponse(response) }] }\n }\n\n case 'search_by_sedol': {\n const sedol = args.sedol as string\n const filters = extractFilters(args)\n const response = await client.searchBySEDOL(sedol, filters)\n return { content: [{ type: 'text', text: formatResponse(response) }] }\n }\n\n case 'search_by_ticker': {\n const ticker = args.ticker as string\n const exchCode = args.exchCode as string | undefined\n const filters = extractFilters(args)\n const response = await client.searchByTicker(ticker, exchCode, filters)\n return { content: [{ type: 'text', text: formatResponse(response) }] }\n }\n\n case 'search_by_bloomberg_id': {\n const bloombergId = args.bloombergId as string\n const filters = extractFilters(args)\n const response = await client.searchByBloombergId(bloombergId, filters)\n return { content: [{ type: 'text', text: formatResponse(response) }] }\n }\n\n case 'batch_mapping': {\n const { identifiers } = args as {\n identifiers: Array<{\n idType: string\n idValue: string\n exchCode?: string\n }>\n }\n\n const requests: MappingRequest[] = identifiers.map((id) => ({\n idType: id.idType as MappingRequest['idType'],\n idValue: id.idValue,\n exchCode: id.exchCode as MappingRequest['exchCode'],\n }))\n\n // Split into batches based on API key presence\n const batchSize = getBatchSize()\n const batches = chunkArray(requests, batchSize)\n\n // Process all batches and combine results\n const allResponses: MappingResponse[] = []\n for (const batch of batches) {\n const batchResponses = await client.mapping(batch)\n allResponses.push(...batchResponses)\n }\n const responses = allResponses\n const formatted = responses\n .map((response: MappingResponse, index: number) => {\n const id = identifiers[index]\n return `[${index + 1}] ${id.idType}: ${id.idValue}\\n${formatResponse(response)}`\n })\n .join('\\n\\n---\\n\\n')\n\n return { content: [{ type: 'text', text: formatted }] }\n }\n\n case 'validate_identifier': {\n const { identifier, type } = args as {\n identifier: string\n type: 'ISIN' | 'CUSIP' | 'SEDOL' | 'BLOOMBERG_ID'\n }\n\n let isValid = false\n let format = ''\n\n switch (type) {\n case 'ISIN':\n isValid = isValidISIN(identifier)\n format = '2 letter country code + 9 alphanumeric characters + 1 check digit'\n break\n case 'CUSIP':\n isValid = isValidCUSIP(identifier)\n format = '9 alphanumeric characters'\n break\n case 'SEDOL':\n isValid = isValidSEDOL(identifier)\n format = '7 alphanumeric characters'\n break\n case 'BLOOMBERG_ID':\n isValid = isValidBloombergId(identifier)\n format = 'BBG + 9 alphanumeric characters'\n break\n }\n\n const result = isValid\n ? `Valid ${type}: \"${identifier}\"`\n : `Invalid ${type}: \"${identifier}\"\\nExpected format: ${format}`\n\n return { content: [{ type: 'text', text: result }] }\n }\n\n case 'get_rate_limit_status': {\n const rateLimit = client.getRateLimitInfo()\n\n if (!rateLimit) {\n return {\n content: [\n {\n type: 'text',\n text: 'No rate limit information available. Make a request first to get rate limit data.',\n },\n ],\n }\n }\n\n const text = [\n 'OpenFIGI API Rate Limit Status:',\n ` Limit: ${rateLimit.limit} requests`,\n ` Remaining: ${rateLimit.remaining} requests`,\n ` Resets: ${rateLimit.reset.toISOString()}`,\n ].join('\\n')\n\n return { content: [{ type: 'text', text }] }\n }\n\n case 'parse_identifiers': {\n const text = args.text as string\n const { identifiers, summary } = parseAndDetectIdentifiers(text)\n\n const details = identifiers\n .map((id, index) => {\n const exchPart = id.exchCode ? ` (Exchange: ${id.exchCode})` : ''\n return `${index + 1}. \"${id.value}\" → ${id.type}${exchPart} [${id.confidence} confidence]`\n })\n .join('\\n')\n\n return {\n content: [\n {\n type: 'text',\n text: `${summary}\\n\\nDetails:\\n${details}`,\n },\n ],\n }\n }\n\n case 'search_auto_detect': {\n const identifier = args.identifier as string\n const detected = detectIdentifierType(identifier)\n\n if (detected.type === 'UNKNOWN') {\n return {\n content: [\n {\n type: 'text',\n text: `Could not determine identifier type for \"${identifier}\". Please use a specific search tool or provide more context.`,\n },\n ],\n }\n }\n\n let response: MappingResponse\n\n switch (detected.type) {\n case 'ISIN':\n response = await client.searchByISIN(detected.value)\n break\n case 'CUSIP':\n response = await client.searchByCUSIP(detected.value)\n break\n case 'SEDOL':\n response = await client.searchBySEDOL(detected.value)\n break\n case 'BLOOMBERG_ID':\n response = await client.searchByBloombergId(detected.value)\n break\n case 'TICKER':\n response = await client.searchByTicker(detected.value, detected.exchCode)\n break\n }\n\n const header = `Detected type: ${detected.type}${detected.exchCode ? ` (Exchange: ${detected.exchCode})` : ''} [${detected.confidence} confidence]\\n\\n`\n const hasResults = response.data && response.data.length > 0\n const notFoundWarning = hasResults ? '' : `⚠️ NOT FOUND: \"${detected.value}\"${detected.exchCode ? ` on exchange ${detected.exchCode}` : ''} returned no results.\\n\\n`\n return { content: [{ type: 'text', text: header + notFoundWarning + formatResponse(response) }] }\n }\n\n case 'batch_search_auto_detect': {\n const text = args.text as string\n const { identifiers, summary } = parseAndDetectIdentifiers(text)\n\n // Filter out unknown identifiers\n const validIdentifiers = identifiers.filter((id) => id.type !== 'UNKNOWN')\n\n if (validIdentifiers.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: `${summary}\\n\\nNo valid identifiers found to search.`,\n },\n ],\n }\n }\n\n // Convert to mapping requests\n const idTypeMap: Record<IdentifierType, MappingRequest['idType']> = {\n ISIN: 'ID_ISIN',\n CUSIP: 'ID_CUSIP',\n SEDOL: 'ID_SEDOL',\n BLOOMBERG_ID: 'ID_BB_GLOBAL',\n TICKER: 'ID_EXCH_SYMBOL',\n UNKNOWN: 'ID_EXCH_SYMBOL', // Won't be used\n }\n\n const requests: MappingRequest[] = validIdentifiers.map((id) => ({\n idType: idTypeMap[id.type],\n idValue: id.value,\n exchCode: id.exchCode as MappingRequest['exchCode'],\n // securityType2 is required for ID_EXCH_SYMBOL (ticker) searches\n ...(id.type === 'TICKER' && { securityType2: 'Common Stock' as const }),\n }))\n\n // Split into batches based on API key presence\n const batchSize = getBatchSize()\n const batches = chunkArray(requests, batchSize)\n\n // Process all batches and combine results\n const allResponses: MappingResponse[] = []\n for (const batch of batches) {\n const batchResponses = await client.mapping(batch)\n allResponses.push(...batchResponses)\n }\n const responses = allResponses\n // Track found and not found\n const found: string[] = []\n const notFound: string[] = []\n\n const formatted = responses\n .map((response: MappingResponse, index: number) => {\n const id = validIdentifiers[index]\n const exchPart = id.exchCode ? ` [${id.exchCode}]` : ''\n const idLabel = `${id.value}${exchPart}`\n\n // Check if no results\n const hasResults = response.data && response.data.length > 0\n if (hasResults) {\n found.push(idLabel)\n } else {\n notFound.push(idLabel)\n }\n\n const status = hasResults ? '' : ' ⚠️ NOT FOUND'\n return `[${index + 1}] ${id.type}: ${idLabel}${status}\\n${formatResponse(response)}`\n })\n .join('\\n\\n---\\n\\n')\n\n // Add summary of found/not found\n const resultSummary = [\n `\\nResults: ${found.length} found, ${notFound.length} not found`,\n notFound.length > 0 ? `\\n⚠️ Not found:\\n${notFound.map((id) => ` - ${id}`).join('\\n')}` : '',\n ].join('')\n\n return { content: [{ type: 'text', text: `${summary}\\n\\n${formatted}\\n\\n---${resultSummary}` }] }\n }\n\n default:\n return {\n content: [{ type: 'text', text: `Unknown tool: ${name}` }],\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return { content: [{ type: 'text', text: `Error: ${message}` }] }\n }\n}\n","// Resource definitions for OpenFIGI MCP server\n// These provide reference data for identifier types, exchange codes, security types, etc.\n\nexport const resourceDefinitions = [\n {\n uri: 'openfigi://search-guide',\n name: 'Search Guide',\n description: 'IMPORTANT: Read this first! Guide on how to effectively search for securities using OpenFIGI',\n mimeType: 'text/plain',\n },\n {\n uri: 'openfigi://identifier-types',\n name: 'Identifier Types',\n description: 'List of supported financial identifier types for OpenFIGI API',\n mimeType: 'text/plain',\n },\n {\n uri: 'openfigi://exchange-codes',\n name: 'Exchange Codes',\n description: 'Common exchange codes used in OpenFIGI API',\n mimeType: 'text/plain',\n },\n {\n uri: 'openfigi://security-types',\n name: 'Security Types',\n description: 'Common security types used in OpenFIGI API',\n mimeType: 'text/plain',\n },\n {\n uri: 'openfigi://market-sectors',\n name: 'Market Sectors',\n description: 'Market sector values used in OpenFIGI API',\n mimeType: 'text/plain',\n },\n] as const\n\n// Search guide content - helps AI understand how to search effectively\nconst searchGuideContent = `OpenFIGI Search Guide\n=====================\n\n## Best Practices for Searching Securities\n\n### 1. PREFER ISIN OVER TICKER\nISINs are globally unique and always return accurate results:\n- ISIN format: 2-letter country code + 9 alphanumeric + 1 check digit\n- Example: US0378331005 (Apple), SE0000108656 (Ericsson)\n- Use search_by_isin for the most reliable results\n\n### 2. TICKER SEARCH REQUIREMENTS\nWhen searching by ticker (search_by_ticker or search_auto_detect):\n- MUST include exchange code for accurate results\n- Uses securityType2=\"Common Stock\" by default\n- Swedish tickers need share class: \"ERIC B\" not \"ERIC\"\n- US tickers: \"AAPL\" with exchCode \"US\"\n\n### 3. EXCHANGE CODE MAPPING\nCommon Bloomberg-style suffixes and their OpenFIGI exchCode:\n- \"SS\" → exchCode: \"SS\" (Stockholm/Nasdaq Nordic)\n- \"US\" → exchCode: \"US\" (United States)\n- \"LN\" → exchCode: \"LONDON\" or \"LSE\"\n- \"GY\" or \"GR\" → exchCode: \"XETRA\" or \"FRANKFURT\"\n- \"FP\" → exchCode: \"EURONEXT-PARIS\"\n- \"NA\" → exchCode: \"EURONEXT-AMSTER\"\n- \"FH\" → exchCode: \"FH\" (Helsinki)\n- \"DC\" → exchCode: \"NOMX COPENHAGEN\"\n- \"NO\" → exchCode: \"OSLO\"\n- \"JP\" → exchCode: \"TOKYO\"\n- \"HK\" → exchCode: \"HONG KONG\"\n\n### 4. NORDIC STOCK TICKER CONVENTIONS\nNordic stocks often have share class suffixes:\n- A shares: \"VOLV A SS\" (Volvo A shares)\n- B shares: \"ERIC B SS\" (Ericsson B shares)\n- Common pattern: TICKER + space + CLASS + space + EXCHANGE\n- If search fails, try adding \"A\" or \"B\" suffix\n\n### 5. HANDLING \"NOT FOUND\" RESULTS\nIf a ticker search returns no results:\n1. Check if share class is needed (add A/B suffix)\n2. Try without exchange code for broader search\n3. Search by ISIN if available (most reliable)\n4. Check if company uses different ticker in OpenFIGI\n\n### 6. BATCH SEARCH TIPS\nFor batch_search_auto_detect:\n- Supports ISINs, tickers with exchange codes, CUSIPs, SEDOLs\n- Format: \"TICKER EXCHANGE\" on each line (e.g., \"AAPL US\")\n- Automatically splits into smaller batches\n- Mix of identifier types is supported\n\n### 7. DATA SOURCE FORMATS\nDifferent data sources format tickers differently:\n\nBloomberg: \"AAPL US Equity\" → use \"AAPL\" with exchCode \"US\"\nFactSet: \"AAPL-US\" → use \"AAPL\" with exchCode \"US\"\nReuters: \"AAPL.O\" → use \"AAPL\" with exchCode \"NASDAQ\"\n\n### 8. WHEN TO USE WHICH TOOL\n- search_by_isin: Best for accuracy, use when ISIN is available\n- search_by_ticker: When you have ticker + exchange\n- search_auto_detect: Single identifier, type unknown\n- batch_search_auto_detect: Multiple identifiers from spreadsheet/CSV\n- search_by_cusip: US securities with CUSIP\n- search_by_sedol: UK/Irish securities with SEDOL\n\n### 9. COMMON PITFALLS\n- Ticker without exchange: May return too many or zero results\n- Wrong exchange code: \"NASDAQ\" vs \"US\" can give different results\n- Missing share class: Nordic stocks often need A/B suffix\n- Case sensitivity: Tickers are converted to uppercase automatically\n`\n\n// Identifier types content\nconst identifierTypesContent = `OpenFIGI Identifier Types\n========================\n\nThe OpenFIGI API supports the following identifier types (idType):\n\nPrimary Identifiers:\n- ID_ISIN: International Securities Identification Number (12 characters, e.g., US0378331005)\n- ID_CUSIP: Committee on Uniform Securities Identification Procedures (9 characters, e.g., 037833100)\n- ID_SEDOL: Stock Exchange Daily Official List (7 characters, e.g., 2046251)\n- ID_EXCH_SYMBOL: Exchange ticker symbol (e.g., AAPL)\n\nBloomberg Identifiers:\n- ID_BB_GLOBAL: Bloomberg Global Identifier (e.g., BBG000B9XRY4)\n- ID_BB: Bloomberg ID\n- ID_BB_UNIQUE: Bloomberg Unique ID\n- ID_BB_SEC_NUM: Bloomberg Security Number\n- ID_BB_SEC_NUM_DES: Bloomberg Security Number Description\n- ID_BB_GLOBAL_SHARE_CLASS_LEVEL: Bloomberg Share Class Level FIGI\n- COMPOSITE_ID_BB_GLOBAL: Bloomberg Composite Global ID\n\nRegional Identifiers:\n- ID_CINS: CUSIP International Numbering System\n- ID_COMMON: Common Code (used in Euroclear/Clearstream)\n- ID_WERTPAPIER: German securities identifier (WKN)\n- ID_BELGIUM: Belgian securities identifier\n- ID_DENMARK: Danish securities identifier (Fondskode)\n- ID_FRANCE: French securities identifier (SICOVAM)\n- ID_ITALY: Italian securities identifier (Codice ABI)\n- ID_JAPAN: Japanese securities identifier\n- ID_LUXEMBOURG: Luxembourg securities identifier\n- ID_NETHERLANDS: Dutch securities identifier\n- ID_POLAND: Polish securities identifier\n- ID_PORTUGAL: Portuguese securities identifier\n- ID_SWEDEN: Swedish securities identifier\n\nOther:\n- ID_FULL_EXCHANGE_SYMBOL: Full exchange symbol including exchange identifier\n- ID_SHORT_CODE: Short code identifier\n\nUsage Example:\n{\n \"idType\": \"ID_ISIN\",\n \"idValue\": \"US0378331005\"\n}\n`\n\n// Exchange codes content (subset of most common)\nconst exchangeCodesContent = `OpenFIGI Exchange Codes\n======================\n\nCommon Exchange Codes (exchCode):\n\nUnited States:\n- US: United States\n- NASDAQ: NASDAQ Stock Exchange\n- NYSE: New York Stock Exchange\n- NYSE AMERICAN: NYSE American (formerly AMEX)\n- NYSE ARCA: NYSE Arca\n- CBOE: Chicago Board Options Exchange\n- CME: Chicago Mercantile Exchange\n\nEurope:\n- LONDON: London Stock Exchange\n- LSE: London Stock Exchange\n- EURONEXT-PARIS: Euronext Paris\n- EURONEXT-AMSTER: Euronext Amsterdam\n- XETRA: Deutsche Börse XETRA\n- FRANKFURT: Frankfurt Stock Exchange\n- SIX: SIX Swiss Exchange\n- MILAN: Milan Stock Exchange\n\nAsia-Pacific:\n- TOKYO: Tokyo Stock Exchange\n- HONG KONG: Hong Kong Stock Exchange\n- SHANGHAI: Shanghai Stock Exchange\n- SHENZHEN: Shenzhen Stock Exchange\n- SGX: Singapore Exchange\n- ASX: Australian Securities Exchange\n- KOREA: Korea Exchange\n- KOSDAQ: KOSDAQ\n\nOther Major Markets:\n- TORONTO: Toronto Stock Exchange\n- TSX VENTURE: TSX Venture Exchange\n- MEXICO: Mexican Stock Exchange\n- SAO PAULO: B3 (Brasil Bolsa Balcão)\n- JOHANNESBURG: Johannesburg Stock Exchange\n\nSpecial Values:\n- NOT LISTED: For unlisted securities\n- OTC US: US Over-the-Counter\n- OTC BB: OTC Bulletin Board\n- PINK SHEETS: Pink Sheets\n\nUsage Example:\n{\n \"idType\": \"ID_EXCH_SYMBOL\",\n \"idValue\": \"AAPL\",\n \"exchCode\": \"US\"\n}\n`\n\n// Security types content (subset of most common)\nconst securityTypesContent = `OpenFIGI Security Types\n======================\n\nCommon Security Types (securityType):\n\nEquity:\n- Common Stock: Common/ordinary shares\n- Preferred: Preferred shares\n- ADR: American Depositary Receipt\n- GDR: Global Depositary Receipt\n- REIT: Real Estate Investment Trust\n- MLP: Master Limited Partnership\n- Right: Rights issue\n- Warrant: Warrant\n- Unit: Unit (combination of securities)\n\nFixed Income:\n- Bond: General bond\n- Conv Bond: Convertible bond\n- COMMERCIAL PAPER: Commercial paper\n- TREASURY BILL: Treasury bill\n- MED TERM NOTE: Medium term note\n\nDerivatives:\n- Equity Option: Stock option\n- Index Option: Index option\n- Currency future.: Currency future\n- Currency option.: Currency option\n\nFunds:\n- Mutual Fund: Mutual fund\n- Open-End Fund: Open-end fund\n- Closed-End Fund: Closed-end fund\n- ETP: Exchange Traded Product\n\nOther:\n- Index: Market index\n- Crypto: Cryptocurrency\n- SWAP: Swap contract\n- FRA: Forward rate agreement\n\nUsage Example:\n{\n \"idType\": \"ID_ISIN\",\n \"idValue\": \"US0378331005\",\n \"securityType\": \"Common Stock\"\n}\n`\n\n// Market sectors content\nconst marketSectorsContent = `OpenFIGI Market Sectors\n======================\n\nMarket Sector Values (marketSecDes):\n\n- Equity: Stocks, shares, and equity derivatives\n- Corp: Corporate bonds and debt instruments\n- Govt: Government bonds and securities\n- Muni: Municipal bonds\n- Mtge: Mortgage-backed securities\n- M-Mkt: Money market instruments\n- Comdty: Commodities\n- Curncy: Currencies and FX instruments\n- Index: Market indices\n- Pfd: Preferred securities\n\nUsage Example:\n{\n \"idType\": \"ID_EXCH_SYMBOL\",\n \"idValue\": \"AAPL\",\n \"marketSecDes\": \"Equity\"\n}\n\nNote: Market sector helps narrow down search results when multiple\nsecurities share the same identifier across different asset classes.\n`\n\nexport function handleResource(uri: string): {\n contents: Array<{\n uri: string\n mimeType: string\n text: string\n }>\n} {\n switch (uri) {\n case 'openfigi://search-guide':\n return {\n contents: [\n {\n uri,\n mimeType: 'text/plain',\n text: searchGuideContent,\n },\n ],\n }\n\n case 'openfigi://identifier-types':\n return {\n contents: [\n {\n uri,\n mimeType: 'text/plain',\n text: identifierTypesContent,\n },\n ],\n }\n\n case 'openfigi://exchange-codes':\n return {\n contents: [\n {\n uri,\n mimeType: 'text/plain',\n text: exchangeCodesContent,\n },\n ],\n }\n\n case 'openfigi://security-types':\n return {\n contents: [\n {\n uri,\n mimeType: 'text/plain',\n text: securityTypesContent,\n },\n ],\n }\n\n case 'openfigi://market-sectors':\n return {\n contents: [\n {\n uri,\n mimeType: 'text/plain',\n text: marketSectorsContent,\n },\n ],\n }\n\n default:\n throw new Error(`Unknown resource: ${uri}`)\n }\n}\n","import { Server } from '@modelcontextprotocol/sdk/server/index.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js'\nimport { toolDefinitions, handleTool } from './tools/index.js'\nimport { resourceDefinitions, handleResource } from './resources/index.js'\n\n// Create the MCP server\nconst server = new Server(\n {\n name: 'openfigi-mcp',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n resources: {},\n },\n }\n)\n\n// Handle tool listing\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: toolDefinitions.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n })),\n }\n})\n\n// Handle tool execution\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params\n return handleTool(name, args ?? {})\n})\n\n// Handle resource listing\nserver.setRequestHandler(ListResourcesRequestSchema, async () => {\n return {\n resources: resourceDefinitions.map((resource) => ({\n uri: resource.uri,\n name: resource.name,\n description: resource.description,\n mimeType: resource.mimeType,\n })),\n }\n})\n\n// Handle resource reading\nserver.setRequestHandler(ReadResourceRequestSchema, async (request) => {\n const { uri } = request.params\n return handleResource(uri)\n})\n\n// Start the server\nasync function main() {\n const transport = new StdioServerTransport()\n await server.connect(transport)\n console.error('OpenFIGI MCP server running on stdio')\n}\n\nmain().catch((error) => {\n console.error('Fatal error:', error)\n process.exit(1)\n})\n"],"mappings":"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iBAAiB;IAC1B,UAAU,iBAAiB;IAC3B,cAAc,iBAAiB;IAC/B,cAAc,iBAAiB;IAC/B,yBAAyB,iBAAiB;GAC3C;GACD,UAAU,CAAC,QAAS;EACrB;CACF;CACD;EACE,MAAM;EACN,aACE;EACF,aAAa;GACX,MAAM;GACN,YAAY;IACV,aAAa;KACX,MAAM;KACN,aAAa;IACd;IACD,GAAG;GACJ;GACD,UAAU,CAAC,aAAc;EAC1B;CACF;CACD;EACE,MAAM;EACN,aACE;EACF,aAAa;GACX,MAAM;GACN,YAAY,EACV,aAAa;IACX,MAAM;IACN,OAAO;KACL,MAAM;KACN,YAAY;MACV,QAAQ;OACN,MAAM;OACN,aACE;MACH;MACD,SAAS;OACP,MAAM;OACN,aAAa;MACd;MACD,UAAU;OACR,MAAM;OACN,aAAa;MACd;KACF;KACD,UAAU,CAAC,UAAU,SAAU;IAChC;IACD,aAAa;GACd,EACF;GACD,UAAU,CAAC,aAAc;EAC1B;CACF;CACD;EACE,MAAM;EACN,aACE;EACF,aAAa;GACX,MAAM;GACN,YAAY;IACV,YAAY;KACV,MAAM;KACN,aAAa;IACd;IACD,MAAM;KACJ,MAAM;KACN,MAAM;MAAC;MAAQ;MAAS;MAAS;KAAe;KAChD,aAAa;IACd;GACF;GACD,UAAU,CAAC,cAAc,MAAO;EACjC;CACF;CACD;EACE,MAAM;EACN,aAAa;EACb,aAAa;GACX,MAAM;GACN,YAAY,CAAE;EACf;CACF;CACD;EACE,MAAM;EACN,aACE;EACF,aAAa;GACX,MAAM;GACN,YAAY,EACV,MAAM;IACJ,MAAM;IACN,aACE;GACH,EACF;GACD,UAAU,CAAC,MAAO;EACnB;CACF;CACD;EACE,MAAM;EACN,aACE;EACF,aAAa;GACX,MAAM;GACN,YAAY,EACV,YAAY;IACV,MAAM;IACN,aACE;GACH,EACF;GACD,UAAU,CAAC,YAAa;EACzB;CACF;CACD;EACE,MAAM;EACN,aACE;EACF,aAAa;GACX,MAAM;GACN,YAAY,EACV,MAAM;IACJ,MAAM;IACN,aACE;GACH,EACF;GACD,UAAU,CAAC,MAAO;EACnB;CACF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"}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "openfigi-mcp",
3
+ "version": "0.2.0",
4
+ "description": "MCP server for OpenFIGI API - Map financial identifiers to FIGIs via Model Context Protocol",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "openfigi-mcp": "./dist/index.js"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public",
13
+ "registry": "https://registry.npmjs.org/"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "import": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "scripts": {
31
+ "dev": "tsdown --watch",
32
+ "build": "tsdown",
33
+ "test": "vitest",
34
+ "test:coverage": "vitest run --coverage",
35
+ "lint": "bun --bun oxlint --fix .",
36
+ "typecheck": "tsc --noEmit"
37
+ },
38
+ "keywords": [
39
+ "openfigi",
40
+ "mcp",
41
+ "model-context-protocol",
42
+ "figi",
43
+ "finance",
44
+ "api",
45
+ "typescript",
46
+ "financial-identifiers",
47
+ "bloomberg",
48
+ "claude"
49
+ ],
50
+ "author": "Viktor Larsson <viktorsarstrom@gmail.com>",
51
+ "license": "MIT",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/viktorlarsson/openfigi.git",
55
+ "directory": "packages/mcp"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/viktorlarsson/openfigi/issues"
59
+ },
60
+ "homepage": "https://github.com/viktorlarsson/openfigi#readme",
61
+ "funding": "https://github.com/sponsors/viktorlarsson",
62
+ "engines": {
63
+ "node": ">=18.0.0"
64
+ },
65
+ "devDependencies": {
66
+ "@types/node": "^25.0.9",
67
+ "@vitest/coverage-v8": "^4.0.17",
68
+ "oxlint": "^1.0.0",
69
+ "tsdown": "^0.9.0",
70
+ "vitest": "^4.0.17"
71
+ },
72
+ "peerDependencies": {
73
+ "typescript": "^5.9.3"
74
+ },
75
+ "dependencies": {
76
+ "@modelcontextprotocol/sdk": "^1.12.1",
77
+ "openfigi-sdk": "workspace:*"
78
+ }
79
+ }