@houtini/fmp-mcp 1.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/HANDOVER.md +627 -0
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/build/index.d.ts +9 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +302 -0
- package/build/index.js.map +1 -0
- package/package.json +40 -0
- package/src/index.ts +342 -0
- package/test-server.js +34 -0
- package/tsconfig.json +19 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Financial Modeling Prep MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Stdio-based Model Context Protocol server for Financial Modeling Prep API.
|
|
7
|
+
* Provides real-time financial data, stock quotes, company fundamentals, and market insights.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
+
import {
|
|
13
|
+
CallToolRequestSchema,
|
|
14
|
+
ListToolsRequestSchema,
|
|
15
|
+
Tool,
|
|
16
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
17
|
+
|
|
18
|
+
const FMP_API_KEY = process.env.FMP_API_KEY;
|
|
19
|
+
const FMP_BASE_URL = 'https://financialmodelingprep.com/stable';
|
|
20
|
+
|
|
21
|
+
if (!FMP_API_KEY) {
|
|
22
|
+
console.error('Error: FMP_API_KEY environment variable is required');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Helper function to make FMP API requests
|
|
28
|
+
*/
|
|
29
|
+
async function fetchFMP(endpoint: string): Promise<any> {
|
|
30
|
+
const url = `${FMP_BASE_URL}${endpoint}${endpoint.includes('?') ? '&' : '?'}apikey=${FMP_API_KEY}`;
|
|
31
|
+
|
|
32
|
+
const response = await fetch(url);
|
|
33
|
+
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`FMP API error: ${response.status} ${response.statusText}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return response.json();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Define available tools
|
|
43
|
+
*/
|
|
44
|
+
const TOOLS: Tool[] = [
|
|
45
|
+
{
|
|
46
|
+
name: 'get_quote',
|
|
47
|
+
description: 'Get real-time stock quote for a symbol (e.g., AAPL, TSLA, MSFT)',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
symbol: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Stock ticker symbol (e.g., AAPL)',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ['symbol'],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'search_symbol',
|
|
61
|
+
description: 'Search for stock symbols by company name or ticker',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
query: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'Search query (company name or ticker)',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
required: ['query'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'get_company_profile',
|
|
75
|
+
description: 'Get detailed company profile information including description, industry, sector, CEO, and more',
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
symbol: {
|
|
80
|
+
type: 'string',
|
|
81
|
+
description: 'Stock ticker symbol',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: ['symbol'],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'get_income_statement',
|
|
89
|
+
description: 'Get company income statement (annual or quarterly)',
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
symbol: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
description: 'Stock ticker symbol',
|
|
96
|
+
},
|
|
97
|
+
period: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
description: 'Period type (annual or quarter)',
|
|
100
|
+
enum: ['annual', 'quarter'],
|
|
101
|
+
},
|
|
102
|
+
limit: {
|
|
103
|
+
type: 'number',
|
|
104
|
+
description: 'Number of periods to return (default: 5)',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
required: ['symbol'],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'get_balance_sheet',
|
|
112
|
+
description: 'Get company balance sheet statement (annual or quarterly)',
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
symbol: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
description: 'Stock ticker symbol',
|
|
119
|
+
},
|
|
120
|
+
period: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: 'Period type (annual or quarter)',
|
|
123
|
+
enum: ['annual', 'quarter'],
|
|
124
|
+
},
|
|
125
|
+
limit: {
|
|
126
|
+
type: 'number',
|
|
127
|
+
description: 'Number of periods to return (default: 5)',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
required: ['symbol'],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'get_cash_flow',
|
|
135
|
+
description: 'Get company cash flow statement (annual or quarterly)',
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: {
|
|
139
|
+
symbol: {
|
|
140
|
+
type: 'string',
|
|
141
|
+
description: 'Stock ticker symbol',
|
|
142
|
+
},
|
|
143
|
+
period: {
|
|
144
|
+
type: 'string',
|
|
145
|
+
description: 'Period type (annual or quarter)',
|
|
146
|
+
enum: ['annual', 'quarter'],
|
|
147
|
+
},
|
|
148
|
+
limit: {
|
|
149
|
+
type: 'number',
|
|
150
|
+
description: 'Number of periods to return (default: 5)',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
required: ['symbol'],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'get_stock_news',
|
|
158
|
+
description: 'Get latest news articles for a stock symbol',
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
properties: {
|
|
162
|
+
symbol: {
|
|
163
|
+
type: 'string',
|
|
164
|
+
description: 'Stock ticker symbol',
|
|
165
|
+
},
|
|
166
|
+
limit: {
|
|
167
|
+
type: 'number',
|
|
168
|
+
description: 'Number of articles to return (default: 10)',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
required: ['symbol'],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Create and configure the MCP server
|
|
178
|
+
*/
|
|
179
|
+
const server = new Server(
|
|
180
|
+
{
|
|
181
|
+
name: 'fmp-mcp-server',
|
|
182
|
+
version: '1.0.0',
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
capabilities: {
|
|
186
|
+
tools: {},
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Handler for listing available tools
|
|
193
|
+
*/
|
|
194
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
195
|
+
return {
|
|
196
|
+
tools: TOOLS,
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Handler for tool execution
|
|
202
|
+
*/
|
|
203
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
204
|
+
const { name, arguments: args } = request.params;
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
switch (name) {
|
|
208
|
+
case 'get_quote': {
|
|
209
|
+
const { symbol } = args as { symbol: string };
|
|
210
|
+
const data = await fetchFMP(`/quote?symbol=${symbol.toUpperCase()}`);
|
|
211
|
+
return {
|
|
212
|
+
content: [
|
|
213
|
+
{
|
|
214
|
+
type: 'text',
|
|
215
|
+
text: JSON.stringify(data, null, 2),
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
case 'search_symbol': {
|
|
222
|
+
const { query } = args as { query: string };
|
|
223
|
+
const data = await fetchFMP(`/search-symbol?query=${encodeURIComponent(query)}&limit=10`);
|
|
224
|
+
return {
|
|
225
|
+
content: [
|
|
226
|
+
{
|
|
227
|
+
type: 'text',
|
|
228
|
+
text: JSON.stringify(data, null, 2),
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
case 'get_company_profile': {
|
|
235
|
+
const { symbol } = args as { symbol: string };
|
|
236
|
+
const data = await fetchFMP(`/profile?symbol=${symbol.toUpperCase()}`);
|
|
237
|
+
return {
|
|
238
|
+
content: [
|
|
239
|
+
{
|
|
240
|
+
type: 'text',
|
|
241
|
+
text: JSON.stringify(data, null, 2),
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case 'get_income_statement': {
|
|
248
|
+
const { symbol, period = 'annual', limit = 5 } = args as {
|
|
249
|
+
symbol: string;
|
|
250
|
+
period?: string;
|
|
251
|
+
limit?: number;
|
|
252
|
+
};
|
|
253
|
+
const data = await fetchFMP(`/income-statement?symbol=${symbol.toUpperCase()}&period=${period}&limit=${limit}`);
|
|
254
|
+
return {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: 'text',
|
|
258
|
+
text: JSON.stringify(data, null, 2),
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
case 'get_balance_sheet': {
|
|
265
|
+
const { symbol, period = 'annual', limit = 5 } = args as {
|
|
266
|
+
symbol: string;
|
|
267
|
+
period?: string;
|
|
268
|
+
limit?: number;
|
|
269
|
+
};
|
|
270
|
+
const data = await fetchFMP(`/balance-sheet-statement?symbol=${symbol.toUpperCase()}&period=${period}&limit=${limit}`);
|
|
271
|
+
return {
|
|
272
|
+
content: [
|
|
273
|
+
{
|
|
274
|
+
type: 'text',
|
|
275
|
+
text: JSON.stringify(data, null, 2),
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
case 'get_cash_flow': {
|
|
282
|
+
const { symbol, period = 'annual', limit = 5 } = args as {
|
|
283
|
+
symbol: string;
|
|
284
|
+
period?: string;
|
|
285
|
+
limit?: number;
|
|
286
|
+
};
|
|
287
|
+
const data = await fetchFMP(`/cash-flow-statement?symbol=${symbol.toUpperCase()}&period=${period}&limit=${limit}`);
|
|
288
|
+
return {
|
|
289
|
+
content: [
|
|
290
|
+
{
|
|
291
|
+
type: 'text',
|
|
292
|
+
text: JSON.stringify(data, null, 2),
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
case 'get_stock_news': {
|
|
299
|
+
const { symbol, limit = 10 } = args as { symbol: string; limit?: number };
|
|
300
|
+
const data = await fetchFMP(`/stock_news?tickers=${symbol.toUpperCase()}&limit=${limit}`);
|
|
301
|
+
return {
|
|
302
|
+
content: [
|
|
303
|
+
{
|
|
304
|
+
type: 'text',
|
|
305
|
+
text: JSON.stringify(data, null, 2),
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
default:
|
|
312
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
316
|
+
return {
|
|
317
|
+
content: [
|
|
318
|
+
{
|
|
319
|
+
type: 'text',
|
|
320
|
+
text: `Error: ${errorMessage}`,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
isError: true,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Start the server
|
|
330
|
+
*/
|
|
331
|
+
async function main() {
|
|
332
|
+
const transport = new StdioServerTransport();
|
|
333
|
+
await server.connect(transport);
|
|
334
|
+
|
|
335
|
+
// Log to stderr so it doesn't interfere with stdio protocol
|
|
336
|
+
console.error('FMP MCP Server running on stdio');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
main().catch((error) => {
|
|
340
|
+
console.error('Fatal error:', error);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
});
|
package/test-server.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple test script to verify the FMP MCP server works
|
|
5
|
+
* Run with: node test-server.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
|
|
10
|
+
const server = spawn('node', ['./build/index.js'], {
|
|
11
|
+
env: {
|
|
12
|
+
...process.env,
|
|
13
|
+
FMP_API_KEY: '4bqSxSXvO3TBUszm6VqRMKszhq5M0SmF'
|
|
14
|
+
},
|
|
15
|
+
stdio: ['pipe', 'pipe', 'inherit']
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
server.stdout.on('data', (data) => {
|
|
19
|
+
console.log('STDOUT:', data.toString());
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
server.on('error', (error) => {
|
|
23
|
+
console.error('Failed to start server:', error);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
server.on('exit', (code) => {
|
|
27
|
+
console.log('Server exited with code:', code);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Give it 2 seconds to start
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
console.log('Server should be running now. Check stderr for "FMP MCP Server running on stdio"');
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}, 2000);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "./build",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "build"]
|
|
19
|
+
}
|