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%
1import { describe, expect, it } from 'vitest';2import { Client } from '@modelcontextprotocol/sdk/client/index.js';3import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';4import { createServer } from '../src/server.js';5import { RATE_HEADERS, bars, clientWith } from './helpers.js';67async function connected(routes: Parameters<typeof clientWith>[0]) {8 const { client: http, calls } = clientWith(routes);9 const { server } = createServer({ client: http });10 const [a, b] = InMemoryTransport.createLinkedPair();11 await server.connect(a);12 const mcp = new Client({ name: 'test', version: '0.0.0' });13 await mcp.connect(b);14 return { mcp, server, calls };15}1617describe('MCP server (in-memory transport)', () => {18 it('lists 14 tools, 2 resources and 3 prompts with JSON schemas', async () => {19 const { mcp, server } = await connected([]);20 const tools = await mcp.listTools();21 expect(tools.tools.map((t) => t.name)).toHaveLength(14);22 const getBars = tools.tools.find((t) => t.name === 'get_bars')!;23 expect(getBars.inputSchema.type).toBe('object');24 expect(Object.keys((getBars.inputSchema as any).properties)).toContain('timeframe');25 expect((getBars.inputSchema as any).required).toEqual(['asset', 'symbol']);26 const resources = await mcp.listResources();27 expect(resources.resources.map((r) => r.uri).sort()).toEqual(['hfmarketdata://limits', 'hfmarketdata://status']);28 const prompts = await mcp.listPrompts();29 expect(prompts.prompts.map((p) => p.name).sort()).toEqual(['compare-contracts', 'fundamentals-snapshot', 'term-structure-analysis']);30 await server.close();31 });3233 it('calls a tool end-to-end and returns text content', async () => {34 const { mcp, server, calls } = await connected([{ match: '/v1/bars/stock/AAPL', body: { count: 2, data: bars(2) }, headers: RATE_HEADERS }]);35 const res = await mcp.callTool({ name: 'get_bars', arguments: { asset: 'stock', symbol: 'AAPL', timeframe: '1d', limit: 2 } });36 expect(res.isError).toBeFalsy();37 const text = (res.content as any)[0].text as string;38 expect(text).toContain('"row_count":2');39 expect(calls[0]).toContain('timeframe=1day');40 await server.close();41 });4243 it('returns isError for API failures and for bad arguments', async () => {44 const { mcp, server } = await connected([{ match: '/v1/futures/ZZ/chain', status: 404, body: { error: { code: 'ROOT_NOT_FOUND', message: 'nope' } } }]);45 const bad = await mcp.callTool({ name: 'get_futures_chain', arguments: { root: 'ZZ' } });46 expect(bad.isError).toBe(true);47 expect((bad.content as any)[0].text).toContain('ROOT_NOT_FOUND');48 const invalid = await mcp.callTool({ name: 'get_futures_chain', arguments: { root: 'ZZ', depth: 999 } });49 expect(invalid.isError).toBe(true);50 await server.close();51 });5253 it('reads both resources', async () => {54 const { mcp, server } = await connected([{ match: '/v1/status', body: { datasets: { stock: { '1day': { adj_splitdiv: 7664 } } } } }]);55 const status = await mcp.readResource({ uri: 'hfmarketdata://status' });56 expect((status.contents[0] as any).text).toContain('7664');57 const limits = await mcp.readResource({ uri: 'hfmarketdata://limits' });58 const text = (limits.contents[0] as any).text as string;59 expect(text).toContain('keyless');60 expect(text).toContain('"requests": 120');61 expect(text).toContain('unavailable (404');62 await server.close();63 });6465 it('renders prompts with arguments', async () => {66 const { mcp, server } = await connected([]);67 const p = await mcp.getPrompt({ name: 'compare-contracts', arguments: { symbol_a: 'esz25', symbol_b: 'esh26', days: '45' } });68 const text = (p.messages[0].content as any).text as string;69 expect(text).toContain('ESZ25');70 expect(text).toContain('45 days');71 const ts = await mcp.getPrompt({ name: 'term-structure-analysis', arguments: { root: 'cl' } });72 expect((ts.messages[0].content as any).text).toContain('get_term_structure(root="CL")');73 const fs = await mcp.getPrompt({ name: 'fundamentals-snapshot', arguments: { ticker: 'aapl' } });74 expect((fs.messages[0].content as any).text).toContain('get_ratios(ticker="AAPL")');75 await server.close();76 });77});78