spb/hfmarketdata
Public
Open high-frequency market data platform — FirstRate full-history downloader, DuckDB/Parquet lake, open REST API and React docs platform (www.hfmarketdata.io)
JavaScript 53.7%
Python 38.3%
CSS 4.6%
TypeScript 3.1%
1/**2 * Assemble the MCP server: 14 tools, 2 resources, 3 prompts, on top of an `HfmdClient`.3 * Exported so that it can be embedded (tests use an in-memory transport).4 */5import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';6import type { CallToolResult, GetPromptResult } from '@modelcontextprotocol/sdk/types.js';7import { HfmdClient, type ClientOptions } from './client.js';8import { PROMPTS } from './prompts.js';9import { RESOURCES } from './resources.js';10import { TOOLS, runTool, type ToolContext } from './tools.js';1112export const SERVER_NAME = 'hfmarketdata';13export const SERVER_VERSION = '1.0.0';1415export interface ServerOptions extends ClientOptions {16 client?: HfmdClient;17 toolContext?: ToolContext;18}1920export function createServer(opts: ServerOptions = {}): { server: McpServer; client: HfmdClient } {21 const client = opts.client ?? new HfmdClient(opts);22 const server = new McpServer(23 { name: SERVER_NAME, version: SERVER_VERSION },24 {25 instructions: [26 'HF Market Data: open high-frequency market data (stocks, ETFs, futures, crypto, indices, FX at 1-minute to daily since ~2007/2010; options chains with Greeks since 2010; SEC fundamentals point-in-time).',27 'Use search_symbols to resolve symbols, get_bars for prices, the futures tools for contracts/curves/continuous series, and the fundamentals tools for statements, ratios, screens and filings.',28 'Outputs are compact tables ({columns, rows}); results above 200 rows are summarised — narrow the range or lower `limit` to see everything.',29 client.keyless30 ? 'Running keyless (low hourly limits: 30 requests/hour per IP). Set HFMD_API_KEY for 120 requests/minute — the API key is free (account at https://www.hfmarketdata.io/signup); higher limits are granted on request by e-mail to contact@spboucher.ai, also free.'31 : 'A free API key is configured (120 requests/minute; higher limits on request by e-mail to contact@spboucher.ai, also free).',32 'Intraday timestamps are naive US/Eastern; daily bars are dates. Missing values are null — never invent data.',33 ].join(' '),34 },35 );3637 for (const t of TOOLS) {38 server.registerTool(39 t.name,40 {41 title: t.title,42 description: t.description,43 inputSchema: t.schema,44 annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: t.name !== 'subscribe_filings', openWorldHint: true },45 },46 async (args: unknown): Promise<CallToolResult> => {47 const { text, isError } = await runTool(t.name, client, args, opts.toolContext);48 return { content: [{ type: 'text', text }], isError: isError || undefined };49 },50 );51 }5253 for (const r of RESOURCES) {54 server.registerResource(r.name, r.uri, { title: r.title, description: r.description, mimeType: r.mimeType }, async (uri) => {55 let text: string;56 try {57 text = await r.read(client);58 } catch (e) {59 text = JSON.stringify({ error: e instanceof Error ? e.message : String(e) });60 }61 return { contents: [{ uri: uri.href, mimeType: r.mimeType, text }] };62 });63 }6465 for (const p of PROMPTS) {66 server.registerPrompt(p.name, { title: p.title, description: p.description, argsSchema: p.args }, (args: Record<string, unknown>): GetPromptResult => ({67 messages: [{ role: 'user' as const, content: { type: 'text' as const, text: p.build(args as never) } }],68 }));69 }7071 return { server, client };72}7374export { HfmdClient, HfmdError } from './client.js';75export { TOOLS, TOOL_NAMES, runTool } from './tools.js';76export { RESOURCES } from './resources.js';77export { PROMPTS } from './prompts.js';78