From e755e9be306f88908fb34d4aed4cd1d002af6924 Mon Sep 17 00:00:00 2001 From: Vapi Tasker Date: Mon, 23 Mar 2026 05:01:15 +0000 Subject: [PATCH] fix: expose phone number provider field and add outbound call guidance Customers using create_call with a Vapi-provisioned phone number get a cryptic transport error because Vapi numbers are inbound-only. This change surfaces the provider field so MCP clients can identify which numbers support outbound dialing, and updates tool descriptions to clearly communicate the Twilio/Vonage requirement. Changes: - Add provider field to PhoneNumberOutputSchema and transformer - Update create_call tool description with outbound requirements - Update CallInputSchema.phoneNumberId with provider guidance - Add 6 unit tests covering all three fixes Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 4 +- .../outbound-call-validation.test.ts | 90 +++++++++++++++++++ src/schemas/index.ts | 9 +- src/tools/call.ts | 2 +- src/transformers/index.ts | 1 + 5 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 src/__tests__/outbound-call-validation.test.ts diff --git a/package-lock.json b/package-lock.json index 8b8d379..5134c4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@vapi-ai/mcp-server", - "version": "0.0.9", + "version": "0.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@vapi-ai/mcp-server", - "version": "0.0.9", + "version": "0.0.10", "dependencies": { "@modelcontextprotocol/sdk": "^1.12.3", "@vapi-ai/server-sdk": "^0.5.2", diff --git a/src/__tests__/outbound-call-validation.test.ts b/src/__tests__/outbound-call-validation.test.ts new file mode 100644 index 0000000..199ab95 --- /dev/null +++ b/src/__tests__/outbound-call-validation.test.ts @@ -0,0 +1,90 @@ +import { + transformPhoneNumberOutput, + transformCallInput, +} from '../transformers/index.js'; +import { CallInputSchema, PhoneNumberOutputSchema } from '../schemas/index.js'; + +describe('Outbound call validation and phone number provider exposure', () => { + describe('PhoneNumberOutputSchema includes provider field', () => { + test('schema should have a provider field', () => { + const shape = PhoneNumberOutputSchema.shape; + expect(shape).toHaveProperty('provider'); + }); + }); + + describe('transformPhoneNumberOutput exposes provider', () => { + test('should include provider field for a vapi phone number', () => { + const vapiPhoneNumber = { + id: 'pn-123', + name: 'My Vapi Number', + createdAt: '2025-01-01T00:00:00Z', + updatedAt: '2025-01-01T00:00:00Z', + number: '+15551234567', + status: 'active', + provider: 'vapi', + }; + + const result = transformPhoneNumberOutput(vapiPhoneNumber); + expect(result).toHaveProperty('provider'); + expect(result.provider).toBe('vapi'); + }); + + test('should include provider field for a twilio phone number', () => { + const twilioPhoneNumber = { + id: 'pn-456', + name: 'My Twilio Number', + createdAt: '2025-01-01T00:00:00Z', + updatedAt: '2025-01-01T00:00:00Z', + number: '+15559876543', + status: 'active', + provider: 'twilio', + }; + + const result = transformPhoneNumberOutput(twilioPhoneNumber); + expect(result).toHaveProperty('provider'); + expect(result.provider).toBe('twilio'); + }); + + test('should include provider field for a vonage phone number', () => { + const vonagePhoneNumber = { + id: 'pn-789', + name: 'My Vonage Number', + createdAt: '2025-01-01T00:00:00Z', + updatedAt: '2025-01-01T00:00:00Z', + number: '+15555555555', + status: 'active', + provider: 'vonage', + }; + + const result = transformPhoneNumberOutput(vonagePhoneNumber); + expect(result).toHaveProperty('provider'); + expect(result.provider).toBe('vonage'); + }); + + test('should default provider to "unknown" when not present on source', () => { + const phoneNumberNoProvider = { + id: 'pn-000', + name: 'Legacy Number', + createdAt: '2025-01-01T00:00:00Z', + updatedAt: '2025-01-01T00:00:00Z', + number: '+15550000000', + status: 'active', + }; + + const result = transformPhoneNumberOutput(phoneNumberNoProvider); + expect(result).toHaveProperty('provider'); + expect(result.provider).toBe('unknown'); + }); + }); + + describe('CallInputSchema.phoneNumberId has outbound guidance in description', () => { + test('phoneNumberId description should mention Twilio or Vonage for outbound', () => { + const phoneNumberIdField = CallInputSchema.shape.phoneNumberId; + const description = phoneNumberIdField.description; + expect(description).toBeDefined(); + expect(description!.toLowerCase()).toContain('twilio'); + expect(description!.toLowerCase()).toContain('vonage'); + expect(description!.toLowerCase()).toContain('outbound'); + }); + }); +}); diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 2638121..10c5108 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -261,7 +261,9 @@ export const CallInputSchema = z.object({ phoneNumberId: z .string() .optional() - .describe('ID of the phone number to use for the call'), + .describe( + 'ID of the phone number to use for the call. For outbound calls, this must be a Twilio or Vonage imported number. Vapi-provisioned numbers are inbound-only and cannot dial outbound. Use list_phone_numbers to check the provider field.' + ), customer: z .object({ number: z.string().describe('Customer phone number'), @@ -314,6 +316,11 @@ export const PhoneNumberOutputSchema = BaseResponseSchema.extend({ name: z.string().optional(), phoneNumber: z.string(), status: z.string(), + provider: z + .string() + .describe( + 'Phone number provider (e.g. "vapi", "twilio", "vonage"). Vapi numbers are inbound-only. Twilio and Vonage numbers support outbound dialing.' + ), capabilities: z .object({ sms: z.boolean().optional(), diff --git a/src/tools/call.ts b/src/tools/call.ts index 408e347..d4aa887 100644 --- a/src/tools/call.ts +++ b/src/tools/call.ts @@ -24,7 +24,7 @@ export const registerCallTools = ( server.tool( 'create_call', - 'Creates a outbound call', + 'Creates an outbound call. Important: outbound calls require a Twilio or Vonage imported phone number. Vapi-provisioned numbers are inbound-only and will fail with a transport error if used for outbound dialing.', CallInputSchema.shape, createToolHandler(async (data) => { const createCallDto = transformCallInput(data); diff --git a/src/transformers/index.ts b/src/transformers/index.ts index 53fa800..83d9350 100644 --- a/src/transformers/index.ts +++ b/src/transformers/index.ts @@ -240,6 +240,7 @@ export function transformPhoneNumberOutput( updatedAt: phoneNumber.updatedAt, phoneNumber: phoneNumber.number, status: phoneNumber.status, + provider: phoneNumber.provider || 'unknown', }; }