UNPKG

octagon-mcp

Version:

MCP server for Octagon API. Provides specialized AI agents for investment research of public and private markets.

160 lines (159 loc) 8.4 kB
import { z } from "zod"; import { debugLog, summarizeDebugIdentifier, summarizeDebugPrompt, } from "../debug.js"; import { clearOctagonConversation, getSessionState, normalizeToolContext, resolveSessionAnchor, storeOctagonConversation, } from "../toolSessionState.js"; import { createMissingApiKeyResult, createOctagonAgentResponse, createTextErrorResult, } from "#tools/shared"; const AGENT_NAME = "octagon-agent"; const AGENT_DESCRIPTION = "Orchestrates all agents for comprehensive market intelligence analysis. Capabilities: Combines insights from SEC filings, earnings calls, financial metrics, stock data, institutional holdings, private company research, funding analysis, M&A transactions, investor intelligence, debt analysis, and news and press releases to provide holistic market intelligence. Best for: Complex research requiring multiple data sources and comprehensive analysis across public and private markets."; const octagonAgentInputShape = { prompt: z .string() .describe("Your natural language query or request for the agent"), conversation: z .string() .trim() .min(1) .optional() .describe("Existing Octagon conversation ID to continue a prior octagon-agent thread. Omit on the first turn."), newConversation: z .boolean() .optional() .describe("If true, start a fresh Octagon conversation for the active session. Top-layer agents should pass this on the first turn of a new user chat."), }; export const octagonAgentInputSchema = z.object(octagonAgentInputShape).strict(); export const octagonAgentOutputSchema = z.object({ model: z.literal(AGENT_NAME), text: z.string(), conversation: z.string().optional(), responseId: z.string().optional(), followUp: z .object({ required: z.boolean(), instructions: z.string().optional(), inputTemplate: z.string().optional(), exampleRequest: z.string().optional(), missingIdentifier: z.string().optional(), reason: z.string().optional(), source: z.string().optional(), }) .optional(), rawMetadata: z.record(z.string()).optional(), }); const CONFLICTING_CONTINUATION_ERROR = "Error: octagon-agent request cannot include both conversation and newConversation. Use conversation to continue an existing thread, or newConversation to start a fresh one."; const MISSING_SESSION_ERROR = "Error: octagon-agent requires session continuity. Provide MCP transport session identity, rely on the stdio session, or explicitly continue with conversation."; export async function executeOctagonAgentTool(client, { prompt, conversation, newConversation }, extra) { try { const normalizedConversation = typeof conversation === "string" ? conversation.trim() || undefined : undefined; const sessionContext = normalizeToolContext(extra); const sessionAnchor = resolveSessionAnchor(sessionContext); const storedSessionState = getSessionState(sessionContext); const storedConversation = storedSessionState?.activeConversationId; const effectiveReset = Boolean(newConversation); const resetReason = newConversation ? "new_conversation_request" : null; const promptSummary = summarizeDebugPrompt(prompt); if (normalizedConversation && newConversation) { debugLog("octagon-agent conflicting continuation arguments", { ...promptSummary, transportKind: sessionContext.transportKind, sessionId: summarizeDebugIdentifier(sessionContext.sessionId), providedConversation: summarizeDebugIdentifier(normalizedConversation), newConversation: true, }); return createTextErrorResult(CONFLICTING_CONTINUATION_ERROR); } if (!sessionAnchor && !normalizedConversation) { debugLog("octagon-agent missing required session continuity", { ...promptSummary, transportKind: sessionContext.transportKind, sessionId: summarizeDebugIdentifier(sessionContext.sessionId), anchorType: "none", providedConversation: null, storedConversation: null, resolvedConversation: null, hasConversation: false, newConversation: Boolean(newConversation), sessionResetApplied: false, resetReason, conversationSource: "new", sessionRequired: true, reuseBlockedReason: sessionContext.transportKind === "stdio" ? "missing_stdio_session" : "missing_required_session_anchor", }); return createTextErrorResult(MISSING_SESSION_ERROR); } const resolvedConversation = normalizedConversation ?? (effectiveReset ? undefined : storedConversation); const anchorType = normalizedConversation ? "provided_conversation" : sessionAnchor?.type ?? "none"; const conversationSource = normalizedConversation ? "provided" : resolvedConversation && sessionAnchor?.type === "transport_session" ? "stored_transport_session" : resolvedConversation && sessionAnchor?.type === "stdio_session" ? "stored_stdio_session" : "new"; const sessionSource = sessionAnchor?.type ?? "none"; const sessionStateId = storedSessionState?.sessionId ?? sessionAnchor?.sessionId; debugLog("octagon-agent inbound MCP request", { ...promptSummary, transportKind: sessionContext.transportKind, sessionId: summarizeDebugIdentifier(sessionContext.sessionId), sessionSource, sessionStateId: summarizeDebugIdentifier(sessionStateId), anchorType, providedConversation: summarizeDebugIdentifier(normalizedConversation), storedConversation: summarizeDebugIdentifier(storedConversation), resolvedConversation: summarizeDebugIdentifier(resolvedConversation), hasConversation: Boolean(resolvedConversation), newConversation: Boolean(newConversation), sessionResetApplied: effectiveReset, resetReason, conversationSource, sessionRequired: !normalizedConversation, reuseBlockedReason: !sessionAnchor && !normalizedConversation ? sessionContext.transportKind === "stdio" ? "missing_stdio_session" : "missing_required_session_anchor" : null, }); const result = await createOctagonAgentResponse(client, { prompt, conversation: resolvedConversation, }); if (effectiveReset) { clearOctagonConversation(sessionContext, resetReason ?? "explicit_reset"); } if (result.conversation) { storeOctagonConversation(sessionContext, { conversation: result.conversation, responseId: result.responseId, }); } const toolResult = { content: [{ type: "text", text: result.text }], structuredContent: result, }; debugLog("octagon-agent outbound MCP tool result", { model: result.model, conversation: summarizeDebugIdentifier(result.conversation), responseId: summarizeDebugIdentifier(result.responseId), textLength: result.text.length, hasFollowUp: Boolean(result.followUp), metadataKeys: result.rawMetadata ? Object.keys(result.rawMetadata) : [], }); return toolResult; } catch (error) { console.error("Error calling Octagon agent:", error); return createTextErrorResult("Error: Failed to process Octagon agent query."); } } export function registerTool(server, client) { const toolServer = server; toolServer.tool(AGENT_NAME, AGENT_DESCRIPTION, octagonAgentInputShape, async ({ prompt, conversation, newConversation }, extra) => client ? executeOctagonAgentTool(client, { prompt, conversation, newConversation }, extra) : Promise.resolve(createMissingApiKeyResult())); }