import { defineConnection, definePlugin, defineSource, defineView } from '@jaspers-ai/sdk' import { z } from 'zod' import { summarize } from './calls' import { CallsView } from './CallsView' import { CALLS_INSTRUCTIONS } from './instructions' // Earnings calls from EarningsCall.biz over its own connection, earningscall/server: one source per // tool the connection offers, and one view that shows a call's transcript or a day's calendar. The // sources carry no description of their own, so the orchestrator reads the server's. It gets // earningscall__calls, earningscall__transcript, and earningscall__calendar, and places the view // instead of calling them for what the view shows. const MINUTE = 60_000 const calls = defineSource({ mcp: 'server', tool: 'list_calls', ttlMs: 15 * MINUTE }) // A published transcript does not change. const transcript = defineSource({ mcp: 'server', tool: 'get_transcript', ttlMs: 60 * MINUTE }) const calendar = defineSource({ mcp: 'server', tool: 'get_calendar', ttlMs: 15 * MINUTE }) const Call = z.object({ year: z.number(), quarter: z.number(), date: z.string().nullable(), published: z.boolean() }) const CallsState = z.object({ symbol: z.string().optional(), exchange: z.string().optional(), year: z.number().int().nullable().optional(), quarter: z.number().int().min(1).max(4).nullable().optional(), date: z .string() .regex(/^\d{4}-\d{2}-\d{2}$/) .nullable() .optional(), find: z.string().optional(), }) const CallsOutput = z.object({ mode: z.enum(['call', 'calendar']), error: z.string().nullable(), // A call. symbol: z.string().optional(), exchange: z.string().nullable().optional(), company: z.string().nullable().optional(), year: z.number().nullable().optional(), quarter: z.number().nullable().optional(), date: z.string().nullable().optional(), calls: z.array(Call).optional(), paragraphs: z.number().optional(), qaParagraph: z.number().nullable().optional(), find: z.string().optional(), matches: z.number().optional(), snippets: z.array(z.object({ paragraph: z.number(), text: z.string() })).optional(), // A calendar day. count: z.number().optional(), events: z .array( z.object({ exchange: z.string(), symbol: z.string(), company: z.string(), year: z.number(), quarter: z.number(), date: z.string().nullable(), ready: z.boolean(), }), ) .optional(), }) export default definePlugin({ id: 'earningscall', secrets: { apikey: { label: 'EarningsCall API key' } }, connections: { // EarningsCall.biz has a REST API and no MCP server, so this runs the small one in server/, on // the Node the app runs on, with the key in its environment; the server keeps it out of what it // says. Its transcripts are the plain kind every plan has. server: defineConnection({ command: ['node', '--disable-warning=MODULE_TYPELESS_PACKAGE_JSON', 'server/main.ts'], env: { EARNINGSCALL_API_KEY: '${secret:apikey}' }, tools: ['list_calls', 'get_transcript', 'get_calendar'], }), }, sources: { calls, transcript, calendar }, views: { calls: defineView(CallsView, { title: 'Earnings calls', state: CallsState, output: CallsOutput, instructions: CALLS_INSTRUCTIONS, renders: [calls, transcript, calendar], summarize, }), }, })