SPB Git forge

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)

127commits 1branches 0releases
24.7 MBsize
maindefault branch
11 days agolast push
JavaScript 53.7% Python 38.3% CSS 4.6% TypeScript 3.1%
4.2 KB · 78 lines typescript
Raw Blame History
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