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(