From fb49dd2cd43b0c415fa58780ce7109442bc6defb Mon Sep 17 00:00:00 2001 From: nickrobin Date: Thu, 7 May 2026 16:21:31 -0700 Subject: [PATCH] feat: add get_call_details tool The existing get_call tool returns a summary projection (id, status, endedReason, assistantId, phoneNumberId, customer.number, scheduledAt) which makes it impossible to fetch transcripts, recordings, messages, costs, or analysis without dropping out to the REST API. This adds a sibling get_call_details tool that returns the full call payload. It accepts an optional 'include' array so callers can scope the response to specific fields (transcript, recordingUrl, messages, costs, cost, analysis, summary, artifact) and keep LLM context windows in check on long calls. - New schemas: GetCallDetailsInputSchema, CallDetailsOutputSchema, CALL_DETAIL_FIELDS - New transformer: transformCallDetailsOutput - New tool registration in src/tools/call.ts - E2E test updated to assert tool is registered --- src/__tests__/mcp-server-e2e.test.ts | 1 + src/schemas/index.ts | 32 ++++++++++++++++++++++++++++ src/tools/call.ts | 17 ++++++++++++++- src/transformers/index.ts | 24 +++++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/__tests__/mcp-server-e2e.test.ts b/src/__tests__/mcp-server-e2e.test.ts index 8bff458..c4db482 100644 --- a/src/__tests__/mcp-server-e2e.test.ts +++ b/src/__tests__/mcp-server-e2e.test.ts @@ -59,6 +59,7 @@ describe('MCP Server E2E Test', () => { expect(toolNames).toContain('list_calls'); expect(toolNames).toContain('create_call'); expect(toolNames).toContain('get_call'); + expect(toolNames).toContain('get_call_details'); }); describe('Assistant Tools', () => { diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 2638121..e79415d 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -304,6 +304,38 @@ export const GetCallInputSchema = z.object({ callId: z.string().describe('ID of the call to get'), }); +export const CALL_DETAIL_FIELDS = [ + 'transcript', + 'recordingUrl', + 'messages', + 'costs', + 'cost', + 'analysis', + 'summary', + 'artifact', +] as const; + +export const GetCallDetailsInputSchema = z.object({ + callId: z.string().describe('ID of the call to get full details for'), + include: z + .array(z.enum(CALL_DETAIL_FIELDS)) + .optional() + .describe( + 'Subset of detail fields to return. Omit to return the full call object. Useful for keeping LLM context small on long calls.' + ), +}); + +export const CallDetailsOutputSchema = CallOutputSchema.extend({ + transcript: z.string().optional(), + recordingUrl: z.string().optional(), + summary: z.string().optional(), + messages: z.array(z.any()).optional(), + costs: z.array(z.any()).optional(), + cost: z.number().optional(), + analysis: z.any().optional(), + artifact: z.any().optional(), +}); + // ===== Phone Number Schemas ===== export const GetPhoneNumberInputSchema = z.object({ diff --git a/src/tools/call.ts b/src/tools/call.ts index 408e347..693e3bd 100644 --- a/src/tools/call.ts +++ b/src/tools/call.ts @@ -1,10 +1,15 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { VapiClient, Vapi } from '@vapi-ai/server-sdk'; -import { CallInputSchema, GetCallInputSchema } from '../schemas/index.js'; +import { + CallInputSchema, + GetCallInputSchema, + GetCallDetailsInputSchema, +} from '../schemas/index.js'; import { transformCallInput, transformCallOutput, + transformCallDetailsOutput, } from '../transformers/index.js'; import { createToolHandler } from './utils.js'; @@ -42,4 +47,14 @@ export const registerCallTools = ( return transformCallOutput(call); }) ); + + server.tool( + 'get_call_details', + 'Gets full details of a specific call (transcript, recording URL, messages, costs, analysis, summary, artifact). Use after get_call when summary fields are not enough. Pass `include` to scope the response and avoid blowing past LLM context windows on long calls.', + GetCallDetailsInputSchema.shape, + createToolHandler(async (data) => { + const call = await vapiClient.calls.get(data.callId); + return transformCallDetailsOutput(call, data.include); + }) + ); }; diff --git a/src/transformers/index.ts b/src/transformers/index.ts index 53fa800..53442c8 100644 --- a/src/transformers/index.ts +++ b/src/transformers/index.ts @@ -10,6 +10,7 @@ import { UpdateAssistantInputSchema, CreateToolInputSchema, UpdateToolInputSchema, + CALL_DETAIL_FIELDS, } from '../schemas/index.js'; // ===== Assistant Transformers ===== @@ -228,6 +229,29 @@ export function transformCallOutput( }; } +export function transformCallDetailsOutput( + call: Vapi.Call, + include?: ReadonlyArray<(typeof CALL_DETAIL_FIELDS)[number]> +): Record { + const summary = transformCallOutput(call); + const callRecord = call as unknown as Record; + const artifact = (callRecord.artifact ?? {}) as Record; + + const fields = include ?? CALL_DETAIL_FIELDS; + const details: Record = {}; + for (const field of fields) { + const value = callRecord[field] ?? artifact[field]; + if (value !== undefined) { + details[field] = value; + } + } + + if (include) { + return { id: call.id, ...details }; + } + return { ...summary, ...details }; +} + // ===== Phone Number Transformers ===== export function transformPhoneNumberOutput(