Jaspers Hub

jaspers / fmp

pluginOfficialReviewed★ 0↓ 4

FMP: News & Technicals: Connects Financial Modeling Prep's MCP server to show company news and technical indicators, with Form 13F as a source only.

3.1 KB
1import { defineConnection, definePlugin, defineSource, defineView } from '@jaspers-ai/sdk'
2import { z } from 'zod'
3import { KINDS, newsSummary, technicalsSummary, TIMEFRAMES } from './fmp'
4import { NEWS_INSTRUCTIONS, TECHNICALS_INSTRUCTIONS } from './instructions'
5import { NewsView } from './News'
6import { TechnicalsView } from './Technicals'
7
8// Financial Modeling Prep over its own connection, fmp/mcp, FMP's MCP server cut to three of its
9// tools: one source per tool the connection offers, and a view for two of them, each about one
10// company. Form 13F has a source and no view: FMP keeps it behind its Ultimate plan, and a
11// view that has only ever been refused is not worth its space. The sources carry no description of
12// their own: an MCP source without one borrows the server's, which lists the group's endpoints and
13// what each requires, beside the server's own schema, whose `endpoint` names them. The orchestrator
14// gets fmp__form-13f, fmp__news, and fmp__technical-indicators, and places the views instead of
15// calling them for what a view shows.
16
17const form13f = defineSource({ mcp: 'mcp', tool: 'form13F' })
18const news = defineSource({ mcp: 'mcp', tool: 'news' })
19const technicalIndicators = defineSource({ mcp: 'mcp', tool: 'technicalIndicators' })
20
21const Symbol = z.string().default('')
22const Amount = z.number().nullable()
23
24const NewsState = z.object({ symbol: Symbol, limit: z.number().int().min(1).max(50).optional() })
25const NewsOutput = z.object({
26 symbol: z.string(),
27 count: z.number(),
28 items: z.array(z.object({ date: z.string(), site: z.string(), title: z.string(), url: z.string() })),
29})
30
31const TechnicalsState = z.object({
32 symbol: Symbol,
33 timeframe: z.enum(TIMEFRAMES).optional(),
34 days: z.number().int().min(1).max(365).optional(),
35 indicators: z.array(z.object({ kind: z.enum(KINDS), period: z.number().int().min(1).max(500) })).max(6).optional(),
36})
37const TechnicalsOutput = z.object({
38 symbol: z.string(),
39 timeframe: z.enum(TIMEFRAMES),
40 date: z.string().nullable(),
41 close: Amount,
42 values: z.record(z.string(), Amount),
43 rows: z.number(),
44})
45
46export default definePlugin({
47 id: 'fmp',
48 secrets: { apikey: { label: 'FMP API key' } },
49 connections: {
50 // FMP's MCP server, streamable HTTP and stateful, cut to three of its tools. FMP reads the key
51 // from the URL and nowhere else (a header is refused), so it rides there. FMP accepts any key at
52 // connect, so a wrong one shows as ready and fails at the first call, saying so.
53 mcp: defineConnection({
54 url: 'https://financialmodelingprep.com/mcp?apikey=${secret:apikey}',
55 tools: ['form13F', 'news', 'technicalIndicators'],
56 }),
57 },
58 sources: { 'form-13f': form13f, news, 'technical-indicators': technicalIndicators },
59 views: {
60 news: defineView(NewsView, {
61 title: 'News',
62 state: NewsState,
63 output: NewsOutput,
64 instructions: NEWS_INSTRUCTIONS,
65 renders: [news],
66 summarize: newsSummary,
67 }),
68 technicals: defineView(TechnicalsView, {
69 title: 'Technicals',
70 state: TechnicalsState,
71 output: TechnicalsOutput,
72 instructions: TECHNICALS_INSTRUCTIONS,
73 renders: [technicalIndicators],
74 summarize: technicalsSummary,
75 }),
76 },
77})