diff --git a/mcp-server/src/schemas/index.ts b/mcp-server/src/schemas/index.ts new file mode 100644 index 00000000..3ff508d8 --- /dev/null +++ b/mcp-server/src/schemas/index.ts @@ -0,0 +1,90 @@ +/** + * Schemas for MCP server tool inputs. + * + * These define the shape and descriptions of parameters accepted + * by the MCP tools for the Vapi API. + */ + +export interface SchemaProperty { + type: string; + description: string; + enum?: string[]; +} + +export interface ToolSchema { + type: "object"; + properties: Record; + required?: string[]; +} + +/** + * Schema for the create_call tool input. + * + * The `phoneNumberId` description explicitly notes that outbound calls + * require a Twilio or Vonage imported number, and that Vapi-provisioned + * numbers are inbound-only. + */ +export const CallInputSchema: ToolSchema = { + type: "object", + properties: { + phoneNumberId: { + type: "string", + description: + "The ID of the phone number to use for the outbound call. " + + "Must be a Twilio or Vonage imported number for outbound calls. " + + "Vapi-provisioned numbers are inbound-only and cannot be used for outbound dialing. " + + 'Use the "list_phone_numbers" tool to find numbers with provider "twilio" or "vonage".', + }, + assistantId: { + type: "string", + description: + "The ID of the assistant to use for the call. " + "Provide either assistantId, squadId, or workflowId.", + }, + workflowId: { + type: "string", + description: + "The ID of the workflow to use for the call. " + "Provide either assistantId, squadId, or workflowId.", + }, + squadId: { + type: "string", + description: + "The ID of the squad to use for the call. " + "Provide either assistantId, squadId, or workflowId.", + }, + customerId: { + type: "string", + description: "The ID of an existing customer to call.", + }, + customerNumber: { + type: "string", + description: "The phone number of the customer to call (E.164 format, e.g. +14155551234).", + }, + }, + required: ["phoneNumberId"], +}; + +/** + * Schema for the list_phone_numbers tool input. + */ +export const ListPhoneNumbersSchema: ToolSchema = { + type: "object", + properties: { + limit: { + type: "string", + description: "Maximum number of phone numbers to return.", + }, + }, +}; + +/** + * Schema for the get_phone_number tool input. + */ +export const GetPhoneNumberSchema: ToolSchema = { + type: "object", + properties: { + id: { + type: "string", + description: "The unique identifier of the phone number to retrieve.", + }, + }, + required: ["id"], +}; diff --git a/mcp-server/src/tools/call.ts b/mcp-server/src/tools/call.ts new file mode 100644 index 00000000..fd1ae820 --- /dev/null +++ b/mcp-server/src/tools/call.ts @@ -0,0 +1,68 @@ +/** + * MCP tool definitions for Vapi call operations. + * + * These tool definitions follow the MCP (Model Context Protocol) tool + * specification and are used to expose Vapi call functionality to + * AI agents and other MCP clients. + */ + +import type { ToolSchema } from "../schemas/index.js"; +import { CallInputSchema, GetPhoneNumberSchema, ListPhoneNumbersSchema } from "../schemas/index.js"; + +export interface ToolDefinition { + name: string; + description: string; + inputSchema: ToolSchema; +} + +/** + * Tool definition for creating outbound calls. + * + * IMPORTANT: Outbound calls require a Twilio or Vonage imported phone number. + * Vapi-provisioned numbers are inbound-only and cannot dial outbound. + * Use the "list_phone_numbers" tool to find numbers with provider + * "twilio" or "vonage" that support outbound calling. + */ +export const createCallTool: ToolDefinition = { + name: "create_call", + description: + "Creates an outbound phone call. " + + "IMPORTANT: Outbound calls require a Twilio or Vonage imported phone number. " + + "Vapi-provisioned numbers (provider: 'vapi') are inbound-only and cannot be used for outbound calls. " + + 'Use the "list_phone_numbers" tool first to find a phone number with provider "twilio" or "vonage". ' + + "You must provide a phoneNumberId, at least one of assistantId/squadId/workflowId, " + + "and a customer to call (via customerId or customerNumber).", + inputSchema: CallInputSchema, +}; + +/** + * Tool definition for listing phone numbers. + * + * Returns phone numbers with their provider field exposed so users + * can identify which numbers support outbound calling. + */ +export const listPhoneNumbersTool: ToolDefinition = { + name: "list_phone_numbers", + description: + "Lists all phone numbers in your Vapi account. " + + "Each number includes a 'provider' field indicating the phone number type: " + + "'twilio', 'vonage', 'telnyx', or 'byo-phone-number' for imported numbers (support outbound calls), " + + "or 'vapi' for Vapi-provisioned numbers (inbound-only, cannot make outbound calls). " + + "Use this tool to find outbound-capable numbers before creating a call.", + inputSchema: ListPhoneNumbersSchema, +}; + +/** + * Tool definition for getting a specific phone number. + * + * Returns the phone number details including the provider field. + */ +export const getPhoneNumberTool: ToolDefinition = { + name: "get_phone_number", + description: + "Gets details of a specific phone number by ID. " + + "Returns the phone number configuration including the 'provider' field: " + + "'twilio', 'vonage', 'telnyx', or 'byo-phone-number' for imported numbers (support outbound calls), " + + "or 'vapi' for Vapi-provisioned numbers (inbound-only).", + inputSchema: GetPhoneNumberSchema, +}; diff --git a/mcp-server/src/transformers/index.ts b/mcp-server/src/transformers/index.ts new file mode 100644 index 00000000..9de77220 --- /dev/null +++ b/mcp-server/src/transformers/index.ts @@ -0,0 +1,172 @@ +/** + * Transformers for MCP server inputs and outputs. + * + * These functions transform data between the MCP tool interface + * and the Vapi API format. + */ + +/** Phone number providers that support outbound calling. */ +const OUTBOUND_CAPABLE_PROVIDERS = ["twilio", "vonage", "telnyx", "byo-phone-number"] as const; + +/** Phone number providers that are inbound-only. */ +const INBOUND_ONLY_PROVIDERS = ["vapi"] as const; + +export type OutboundCapableProvider = (typeof OUTBOUND_CAPABLE_PROVIDERS)[number]; +export type InboundOnlyProvider = (typeof INBOUND_ONLY_PROVIDERS)[number]; +export type PhoneNumberProvider = OutboundCapableProvider | InboundOnlyProvider; + +export interface PhoneNumberOutput { + id: string; + orgId: string; + provider: PhoneNumberProvider; + number?: string; + name?: string; + createdAt: string; + updatedAt: string; + assistantId?: string; + workflowId?: string; + squadId?: string; +} + +export interface PhoneNumberApiResponse { + id: string; + orgId: string; + provider: string; + number?: string; + name?: string; + createdAt: string; + updatedAt: string; + assistantId?: string; + workflowId?: string; + squadId?: string; + [key: string]: unknown; +} + +/** + * Transforms a phone number API response into the MCP output format. + * + * Exposes the `provider` field so MCP users can identify which numbers + * support outbound calling (twilio, vonage, telnyx, byo-phone-number) + * versus inbound-only (vapi). + */ +export function transformPhoneNumberOutput(apiResponse: PhoneNumberApiResponse): PhoneNumberOutput { + return { + id: apiResponse.id, + orgId: apiResponse.orgId, + provider: apiResponse.provider as PhoneNumberProvider, + number: apiResponse.number, + name: apiResponse.name, + createdAt: apiResponse.createdAt, + updatedAt: apiResponse.updatedAt, + assistantId: apiResponse.assistantId, + workflowId: apiResponse.workflowId, + squadId: apiResponse.squadId, + }; +} + +export interface CallInput { + phoneNumberId?: string; + assistantId?: string; + workflowId?: string; + squadId?: string; + customerId?: string; + customer?: { + number: string; + [key: string]: unknown; + }; + [key: string]: unknown; +} + +export interface CallApiPayload { + phoneNumberId?: string; + assistantId?: string; + workflowId?: string; + squadId?: string; + customerId?: string; + customer?: { + number: string; + [key: string]: unknown; + }; + [key: string]: unknown; +} + +export class OutboundCallValidationError extends Error { + constructor(message: string) { + super(message); + this.name = "OutboundCallValidationError"; + } +} + +/** + * Validates whether a phone number can be used for outbound calling. + * + * Vapi-provisioned numbers are inbound-only and cannot dial outbound. + * This pre-validates the phone number type before making the API call + * to provide a clear, actionable error message instead of the cryptic + * API error "Vapi Numbers Can't Dial Outbound Yet". + * + * @param phoneNumberId - The ID of the phone number to validate. + * @param fetchPhoneNumber - A function that fetches the phone number details by ID. + * @throws {OutboundCallValidationError} If the phone number is a Vapi-provisioned number. + */ +export async function validatePhoneNumberForOutbound( + phoneNumberId: string, + fetchPhoneNumber: (id: string) => Promise, +): Promise { + const phoneNumber = await fetchPhoneNumber(phoneNumberId); + + if (phoneNumber.provider === "vapi") { + throw new OutboundCallValidationError( + `Phone number "${phoneNumberId}" is a Vapi-provisioned number (provider: "vapi") and cannot be used for outbound calls. ` + + "Vapi-provisioned numbers are inbound-only. " + + "To make outbound calls, use a Twilio or Vonage imported number instead. " + + 'You can check your available numbers with the "list_phone_numbers" tool and look for numbers with provider "twilio" or "vonage".', + ); + } +} + +/** + * Transforms call input from MCP format to the API payload format. + * + * If a `phoneNumberId` is provided, this function validates that the + * phone number is capable of outbound calling before returning the + * API payload. Vapi-provisioned numbers will be rejected with a + * clear error message. + * + * @param input - The MCP call input. + * @param fetchPhoneNumber - A function that fetches phone number details by ID. + * Required when `phoneNumberId` is provided in the input. + * @returns The API payload for creating an outbound call. + * @throws {OutboundCallValidationError} If a Vapi-provisioned number is used. + */ +export async function transformCallInput( + input: CallInput, + fetchPhoneNumber?: (id: string) => Promise, +): Promise { + if (input.phoneNumberId && fetchPhoneNumber) { + await validatePhoneNumberForOutbound(input.phoneNumberId, fetchPhoneNumber); + } + + const payload: CallApiPayload = {}; + + if (input.phoneNumberId !== undefined) { + payload.phoneNumberId = input.phoneNumberId; + } + if (input.assistantId !== undefined) { + payload.assistantId = input.assistantId; + } + if (input.workflowId !== undefined) { + payload.workflowId = input.workflowId; + } + if (input.squadId !== undefined) { + payload.squadId = input.squadId; + } + if (input.customerId !== undefined) { + payload.customerId = input.customerId; + } + if (input.customer !== undefined) { + payload.customer = input.customer; + } + + return payload; +} diff --git a/tests/mcp-server/schemas.test.ts b/tests/mcp-server/schemas.test.ts new file mode 100644 index 00000000..6489b7d0 --- /dev/null +++ b/tests/mcp-server/schemas.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from "vitest"; +import { CallInputSchema, GetPhoneNumberSchema, ListPhoneNumbersSchema } from "../../mcp-server/src/schemas/index.js"; + +describe("CallInputSchema", () => { + it("should have phoneNumberId as a required field", () => { + expect(CallInputSchema.required).toContain("phoneNumberId"); + }); + + it("should document that phoneNumberId requires Twilio or Vonage for outbound", () => { + const description = CallInputSchema.properties.phoneNumberId.description; + expect(description).toContain("Twilio"); + expect(description).toContain("Vonage"); + }); + + it("should document that Vapi numbers are inbound-only", () => { + const description = CallInputSchema.properties.phoneNumberId.description; + expect(description).toContain("Vapi-provisioned"); + expect(description).toContain("inbound-only"); + }); + + it("should reference list_phone_numbers tool for finding outbound-capable numbers", () => { + const description = CallInputSchema.properties.phoneNumberId.description; + expect(description).toContain("list_phone_numbers"); + }); + + it("should include assistantId, workflowId, and squadId properties", () => { + expect(CallInputSchema.properties).toHaveProperty("assistantId"); + expect(CallInputSchema.properties).toHaveProperty("workflowId"); + expect(CallInputSchema.properties).toHaveProperty("squadId"); + }); + + it("should include customer-related properties", () => { + expect(CallInputSchema.properties).toHaveProperty("customerId"); + expect(CallInputSchema.properties).toHaveProperty("customerNumber"); + }); +}); + +describe("ListPhoneNumbersSchema", () => { + it("should be a valid object schema", () => { + expect(ListPhoneNumbersSchema.type).toBe("object"); + }); + + it("should have an optional limit property", () => { + expect(ListPhoneNumbersSchema.properties).toHaveProperty("limit"); + expect(ListPhoneNumbersSchema.required).toBeUndefined(); + }); +}); + +describe("GetPhoneNumberSchema", () => { + it("should require the id field", () => { + expect(GetPhoneNumberSchema.required).toContain("id"); + }); + + it("should have an id property with string type", () => { + expect(GetPhoneNumberSchema.properties.id.type).toBe("string"); + }); +}); diff --git a/tests/mcp-server/tools.test.ts b/tests/mcp-server/tools.test.ts new file mode 100644 index 00000000..31650931 --- /dev/null +++ b/tests/mcp-server/tools.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from "vitest"; +import { createCallTool, getPhoneNumberTool, listPhoneNumbersTool } from "../../mcp-server/src/tools/call.js"; + +describe("createCallTool", () => { + it("should have the name 'create_call'", () => { + expect(createCallTool.name).toBe("create_call"); + }); + + it("should describe that outbound calls require Twilio or Vonage numbers", () => { + expect(createCallTool.description).toContain("Twilio"); + expect(createCallTool.description).toContain("Vonage"); + }); + + it("should warn that Vapi numbers cannot be used for outbound", () => { + expect(createCallTool.description).toContain("Vapi-provisioned"); + expect(createCallTool.description).toContain("inbound-only"); + }); + + it("should reference list_phone_numbers tool in the description", () => { + expect(createCallTool.description).toContain("list_phone_numbers"); + }); + + it("should have a valid input schema", () => { + expect(createCallTool.inputSchema.type).toBe("object"); + expect(createCallTool.inputSchema.properties).toHaveProperty("phoneNumberId"); + }); +}); + +describe("listPhoneNumbersTool", () => { + it("should have the name 'list_phone_numbers'", () => { + expect(listPhoneNumbersTool.name).toBe("list_phone_numbers"); + }); + + it("should describe the provider field in the output", () => { + expect(listPhoneNumbersTool.description).toContain("provider"); + }); + + it("should explain which providers support outbound calls", () => { + expect(listPhoneNumbersTool.description).toContain("twilio"); + expect(listPhoneNumbersTool.description).toContain("vonage"); + }); + + it("should explain that Vapi numbers are inbound-only", () => { + expect(listPhoneNumbersTool.description).toContain("vapi"); + expect(listPhoneNumbersTool.description).toContain("inbound-only"); + }); +}); + +describe("getPhoneNumberTool", () => { + it("should have the name 'get_phone_number'", () => { + expect(getPhoneNumberTool.name).toBe("get_phone_number"); + }); + + it("should describe the provider field in the output", () => { + expect(getPhoneNumberTool.description).toContain("provider"); + }); + + it("should require an id input", () => { + expect(getPhoneNumberTool.inputSchema.required).toContain("id"); + }); +}); diff --git a/tests/mcp-server/transformers.test.ts b/tests/mcp-server/transformers.test.ts new file mode 100644 index 00000000..9a5670b2 --- /dev/null +++ b/tests/mcp-server/transformers.test.ts @@ -0,0 +1,311 @@ +import { describe, expect, it } from "vitest"; +import type { PhoneNumberApiResponse } from "../../mcp-server/src/transformers/index.js"; +import { + OutboundCallValidationError, + transformCallInput, + transformPhoneNumberOutput, + validatePhoneNumberForOutbound, +} from "../../mcp-server/src/transformers/index.js"; + +describe("transformPhoneNumberOutput", () => { + it("should expose the provider field for a Vapi phone number", () => { + const apiResponse: PhoneNumberApiResponse = { + id: "pn_123", + orgId: "org_456", + provider: "vapi", + number: "+14155551234", + name: "My Vapi Number", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + assistantId: "asst_789", + }; + + const result = transformPhoneNumberOutput(apiResponse); + + expect(result.provider).toBe("vapi"); + expect(result.id).toBe("pn_123"); + expect(result.number).toBe("+14155551234"); + expect(result.name).toBe("My Vapi Number"); + }); + + it("should expose the provider field for a Twilio phone number", () => { + const apiResponse: PhoneNumberApiResponse = { + id: "pn_tw_123", + orgId: "org_456", + provider: "twilio", + number: "+14155559876", + name: "My Twilio Number", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + twilioAccountSid: "AC123", + }; + + const result = transformPhoneNumberOutput(apiResponse); + + expect(result.provider).toBe("twilio"); + expect(result.id).toBe("pn_tw_123"); + }); + + it("should expose the provider field for a Vonage phone number", () => { + const apiResponse: PhoneNumberApiResponse = { + id: "pn_vn_123", + orgId: "org_456", + provider: "vonage", + number: "+14155550000", + name: "My Vonage Number", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + credentialId: "cred_123", + }; + + const result = transformPhoneNumberOutput(apiResponse); + + expect(result.provider).toBe("vonage"); + }); + + it("should strip extra API fields not in the output type", () => { + const apiResponse: PhoneNumberApiResponse = { + id: "pn_123", + orgId: "org_456", + provider: "twilio", + number: "+14155551234", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + twilioAccountSid: "AC123", + twilioAuthToken: "secret_token", + }; + + const result = transformPhoneNumberOutput(apiResponse); + + expect(result).not.toHaveProperty("twilioAccountSid"); + expect(result).not.toHaveProperty("twilioAuthToken"); + }); + + it("should handle optional fields being undefined", () => { + const apiResponse: PhoneNumberApiResponse = { + id: "pn_123", + orgId: "org_456", + provider: "vapi", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }; + + const result = transformPhoneNumberOutput(apiResponse); + + expect(result.number).toBeUndefined(); + expect(result.name).toBeUndefined(); + expect(result.assistantId).toBeUndefined(); + expect(result.workflowId).toBeUndefined(); + expect(result.squadId).toBeUndefined(); + }); +}); + +describe("validatePhoneNumberForOutbound", () => { + it("should throw OutboundCallValidationError for Vapi-provisioned numbers", async () => { + const fetchPhoneNumber = async (_id: string): Promise => ({ + id: "pn_vapi_123", + orgId: "org_456", + provider: "vapi", + number: "+14155551234", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }); + + await expect(validatePhoneNumberForOutbound("pn_vapi_123", fetchPhoneNumber)).rejects.toThrow( + OutboundCallValidationError, + ); + }); + + it("should include actionable guidance in the error message for Vapi numbers", async () => { + const fetchPhoneNumber = async (_id: string): Promise => ({ + id: "pn_vapi_123", + orgId: "org_456", + provider: "vapi", + number: "+14155551234", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }); + + await expect(validatePhoneNumberForOutbound("pn_vapi_123", fetchPhoneNumber)).rejects.toThrow( + /Twilio or Vonage imported number/, + ); + }); + + it("should not throw for Twilio numbers", async () => { + const fetchPhoneNumber = async (_id: string): Promise => ({ + id: "pn_tw_123", + orgId: "org_456", + provider: "twilio", + number: "+14155559876", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }); + + await expect(validatePhoneNumberForOutbound("pn_tw_123", fetchPhoneNumber)).resolves.toBeUndefined(); + }); + + it("should not throw for Vonage numbers", async () => { + const fetchPhoneNumber = async (_id: string): Promise => ({ + id: "pn_vn_123", + orgId: "org_456", + provider: "vonage", + number: "+14155550000", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }); + + await expect(validatePhoneNumberForOutbound("pn_vn_123", fetchPhoneNumber)).resolves.toBeUndefined(); + }); + + it("should not throw for Telnyx numbers", async () => { + const fetchPhoneNumber = async (_id: string): Promise => ({ + id: "pn_tl_123", + orgId: "org_456", + provider: "telnyx", + number: "+14155550001", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }); + + await expect(validatePhoneNumberForOutbound("pn_tl_123", fetchPhoneNumber)).resolves.toBeUndefined(); + }); + + it("should not throw for BYO phone numbers", async () => { + const fetchPhoneNumber = async (_id: string): Promise => ({ + id: "pn_byo_123", + orgId: "org_456", + provider: "byo-phone-number", + number: "+14155550002", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }); + + await expect(validatePhoneNumberForOutbound("pn_byo_123", fetchPhoneNumber)).resolves.toBeUndefined(); + }); + + it("should include the phone number ID in the error message", async () => { + const fetchPhoneNumber = async (_id: string): Promise => ({ + id: "pn_specific_id", + orgId: "org_456", + provider: "vapi", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }); + + await expect(validatePhoneNumberForOutbound("pn_specific_id", fetchPhoneNumber)).rejects.toThrow( + /pn_specific_id/, + ); + }); +}); + +describe("transformCallInput", () => { + it("should pass through fields when no phoneNumberId is provided", async () => { + const input = { + assistantId: "asst_123", + customerId: "cust_456", + }; + + const result = await transformCallInput(input); + + expect(result.assistantId).toBe("asst_123"); + expect(result.customerId).toBe("cust_456"); + expect(result.phoneNumberId).toBeUndefined(); + }); + + it("should validate phone number and pass through for Twilio numbers", async () => { + const fetchPhoneNumber = async (_id: string): Promise => ({ + id: "pn_tw_123", + orgId: "org_456", + provider: "twilio", + number: "+14155559876", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }); + + const input = { + phoneNumberId: "pn_tw_123", + assistantId: "asst_123", + customerId: "cust_456", + }; + + const result = await transformCallInput(input, fetchPhoneNumber); + + expect(result.phoneNumberId).toBe("pn_tw_123"); + expect(result.assistantId).toBe("asst_123"); + expect(result.customerId).toBe("cust_456"); + }); + + it("should reject Vapi-provisioned numbers with a clear error", async () => { + const fetchPhoneNumber = async (_id: string): Promise => ({ + id: "pn_vapi_123", + orgId: "org_456", + provider: "vapi", + number: "+14155551234", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }); + + const input = { + phoneNumberId: "pn_vapi_123", + assistantId: "asst_123", + customerId: "cust_456", + }; + + await expect(transformCallInput(input, fetchPhoneNumber)).rejects.toThrow(OutboundCallValidationError); + }); + + it("should skip validation when fetchPhoneNumber is not provided", async () => { + const input = { + phoneNumberId: "pn_vapi_123", + assistantId: "asst_123", + }; + + // Should not throw even though phoneNumberId is provided, + // because no fetchPhoneNumber function was given + const result = await transformCallInput(input); + + expect(result.phoneNumberId).toBe("pn_vapi_123"); + }); + + it("should pass through customer object correctly", async () => { + const input = { + assistantId: "asst_123", + customer: { + number: "+14155551234", + name: "John Doe", + }, + }; + + const result = await transformCallInput(input); + + expect(result.customer).toEqual({ + number: "+14155551234", + name: "John Doe", + }); + }); + + it("should only include defined fields in the output payload", async () => { + const input = { + phoneNumberId: "pn_tw_123", + assistantId: "asst_123", + }; + + const fetchPhoneNumber = async (_id: string): Promise => ({ + id: "pn_tw_123", + orgId: "org_456", + provider: "twilio", + number: "+14155559876", + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + }); + + const result = await transformCallInput(input, fetchPhoneNumber); + + expect(result).toHaveProperty("phoneNumberId"); + expect(result).toHaveProperty("assistantId"); + expect(result).not.toHaveProperty("customerId"); + expect(result).not.toHaveProperty("customer"); + expect(result).not.toHaveProperty("squadId"); + expect(result).not.toHaveProperty("workflowId"); + }); +});