Skip to content

Commit ed91662

Browse files
committed
added get, list vapi tools and update assistant
1 parent f539ccd commit ed91662

5 files changed

Lines changed: 233 additions & 4 deletions

File tree

src/schemas/index.ts

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ export const CreateAssistantInputSchema = z.object({
141141
])
142142
.default(DEFAULT_LLM)
143143
.describe('LLM configuration'),
144+
toolIds: z
145+
.array(z.string())
146+
.optional()
147+
.describe('IDs of tools to use with this assistant'),
144148
transcriber: z
145149
.object({
146150
provider: z.string().describe('Provider to use for transcription'),
@@ -160,7 +164,7 @@ export const CreateAssistantInputSchema = z.object({
160164
.string()
161165
.optional()
162166
.default('Hello, how can I help you today?')
163-
.describe('First message to send'),
167+
.describe('First message to say to the user'),
164168
firstMessageMode: z
165169
.enum([
166170
'assistant-speaks-first',
@@ -169,7 +173,7 @@ export const CreateAssistantInputSchema = z.object({
169173
])
170174
.default('assistant-speaks-first')
171175
.optional()
172-
.describe('First message mode'),
176+
.describe('This determines who speaks first, either assistant or user'),
173177
});
174178

175179
export const AssistantOutputSchema = BaseResponseSchema.extend({
@@ -187,12 +191,66 @@ export const AssistantOutputSchema = BaseResponseSchema.extend({
187191
provider: z.string(),
188192
model: z.string(),
189193
}),
194+
toolIds: z.array(z.string()).optional(),
190195
});
191196

192197
export const GetAssistantInputSchema = z.object({
193198
assistantId: z.string().describe('ID of the assistant to get'),
194199
});
195200

201+
export const UpdateAssistantInputSchema = z.object({
202+
assistantId: z.string().describe('ID of the assistant to update'),
203+
name: z.string().optional().describe('New name for the assistant'),
204+
instructions: z
205+
.string()
206+
.optional()
207+
.describe('New instructions for the assistant'),
208+
llm: z
209+
.union([
210+
LLMSchema,
211+
z.string().transform((str) => {
212+
try {
213+
return JSON.parse(str);
214+
} catch (e) {
215+
throw new Error(`Invalid LLM JSON string: ${str}`);
216+
}
217+
}),
218+
])
219+
.optional()
220+
.describe('New LLM configuration'),
221+
toolIds: z
222+
.array(z.string())
223+
.optional()
224+
.describe('New IDs of tools to use with this assistant'),
225+
transcriber: z
226+
.object({
227+
provider: z.string().describe('Provider to use for transcription'),
228+
model: z.string().describe('Transcription model to use'),
229+
})
230+
.optional()
231+
.describe('New transcription configuration'),
232+
voice: z
233+
.object({
234+
provider: VoiceProviderSchema.describe('Provider to use for voice'),
235+
voiceId: z.string().describe('Voice ID to use'),
236+
model: z.string().optional().describe('Voice model to use'),
237+
})
238+
.optional()
239+
.describe('New voice configuration'),
240+
firstMessage: z
241+
.string()
242+
.optional()
243+
.describe('First message to say to the user'),
244+
firstMessageMode: z
245+
.enum([
246+
'assistant-speaks-first',
247+
'assistant-waits-for-user',
248+
'assistant-speaks-first-with-model-generated-message',
249+
])
250+
.optional()
251+
.describe('This determines who speaks first, either assistant or user'),
252+
});
253+
196254
// ===== Call Schemas =====
197255

198256
export const CallInputSchema = z.object({
@@ -213,7 +271,9 @@ export const CallInputSchema = z.object({
213271
scheduledAt: z
214272
.string()
215273
.optional()
216-
.describe('ISO datetime string for when the call should be scheduled (e.g. "2025-03-25T22:39:27.771Z")'),
274+
.describe(
275+
'ISO datetime string for when the call should be scheduled (e.g. "2025-03-25T22:39:27.771Z")'
276+
),
217277
});
218278

219279
export const CallOutputSchema = BaseResponseSchema.extend({
@@ -235,7 +295,6 @@ export const GetCallInputSchema = z.object({
235295

236296
// ===== Phone Number Schemas =====
237297

238-
239298
export const GetPhoneNumberInputSchema = z.object({
240299
phoneNumberId: z.string().describe('ID of the phone number to get'),
241300
});
@@ -251,3 +310,17 @@ export const PhoneNumberOutputSchema = BaseResponseSchema.extend({
251310
})
252311
.optional(),
253312
});
313+
314+
// ===== Tool Schemas =====
315+
316+
export const GetToolInputSchema = z.object({
317+
toolId: z.string().describe('ID of the tool to get'),
318+
});
319+
320+
export const ToolOutputSchema = BaseResponseSchema.extend({
321+
type: z
322+
.string()
323+
.describe('Type of the tool (dtmf, function, mcp, query, etc.)'),
324+
name: z.string().describe('Name of the tool'),
325+
description: z.string().describe('Description of the tool'),
326+
});

src/tools/assistant.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import { VapiClient, Vapi } from '@vapi-ai/server-sdk';
33
import {
44
CreateAssistantInputSchema,
55
GetAssistantInputSchema,
6+
UpdateAssistantInputSchema,
67
} from '../schemas/index.js';
78
import {
89
transformAssistantInput,
910
transformAssistantOutput,
11+
transformUpdateAssistantInput,
1012
} from '../transformers/index.js';
1113
import { createToolHandler } from './utils.js';
1214

@@ -57,4 +59,34 @@ export const registerAssistantTools = (
5759
}
5860
})
5961
);
62+
63+
server.tool(
64+
'update_assistant',
65+
'Updates an existing Vapi assistant',
66+
UpdateAssistantInputSchema.shape,
67+
createToolHandler(async (data) => {
68+
const assistantId = data.assistantId;
69+
try {
70+
// First check if the assistant exists
71+
const existingAssistant = await vapiClient.assistants.get(assistantId);
72+
if (!existingAssistant) {
73+
throw new Error(`Assistant with ID ${assistantId} not found`);
74+
}
75+
76+
// Transform the update data
77+
const updateAssistantDto = transformUpdateAssistantInput(data);
78+
79+
// Update the assistant
80+
const updatedAssistant = await vapiClient.assistants.update(
81+
assistantId,
82+
updateAssistantDto
83+
);
84+
85+
return transformAssistantOutput(updatedAssistant);
86+
} catch (error: any) {
87+
console.error(`Error updating assistant: ${error.message}`);
88+
throw error;
89+
}
90+
})
91+
);
6092
};

src/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import { VapiClient } from '@vapi-ai/server-sdk';
44
import { registerAssistantTools } from './assistant.js';
55
import { registerCallTools } from './call.js';
66
import { registerPhoneNumberTools } from './phone-number.js';
7+
import { registerToolTools } from './tool.js';
78

89
export const registerAllTools = (server: McpServer, vapiClient: VapiClient) => {
910
registerAssistantTools(server, vapiClient);
1011
registerCallTools(server, vapiClient);
1112
registerPhoneNumberTools(server, vapiClient);
13+
registerToolTools(server, vapiClient);
1214
};

src/tools/tool.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2+
import { VapiClient, Vapi } from '@vapi-ai/server-sdk';
3+
4+
import { GetToolInputSchema } from '../schemas/index.js';
5+
import { transformToolOutput } from '../transformers/index.js';
6+
import { createToolHandler } from './utils.js';
7+
8+
export const registerToolTools = (
9+
server: McpServer,
10+
vapiClient: VapiClient
11+
) => {
12+
server.tool(
13+
'list_tools',
14+
'Lists all Vapi tools',
15+
{},
16+
createToolHandler(async () => {
17+
const tools = await vapiClient.tools.list({ limit: 10 });
18+
return tools.map(transformToolOutput);
19+
})
20+
);
21+
22+
server.tool(
23+
'get_tool',
24+
'Gets details of a specific tool',
25+
GetToolInputSchema.shape,
26+
createToolHandler(async (data) => {
27+
const tool = await vapiClient.tools.get(data.toolId);
28+
return transformToolOutput(tool);
29+
})
30+
);
31+
};

src/transformers/index.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
AssistantOutputSchema,
77
CallOutputSchema,
88
PhoneNumberOutputSchema,
9+
ToolOutputSchema,
10+
UpdateAssistantInputSchema,
911
} from '../schemas/index.js';
1012

1113
// ===== Assistant Transformers =====
@@ -22,6 +24,10 @@ export function transformAssistantInput(
2224
model: input.llm.model,
2325
};
2426

27+
if (input.toolIds && input.toolIds.length > 0) {
28+
assistantDto.model.toolIds = input.toolIds;
29+
}
30+
2531
if (input.instructions) {
2632
assistantDto.model.messages = [
2733
{
@@ -53,6 +59,75 @@ export function transformAssistantInput(
5359
return assistantDto as Vapi.CreateAssistantDto;
5460
}
5561

62+
export function transformUpdateAssistantInput(
63+
input: z.infer<typeof UpdateAssistantInputSchema>
64+
): Vapi.UpdateAssistantDto {
65+
const updateDto: any = {};
66+
67+
if (input.name) {
68+
updateDto.name = input.name;
69+
}
70+
71+
if (input.llm) {
72+
updateDto.model = {
73+
provider: input.llm.provider as any,
74+
model: input.llm.model,
75+
};
76+
77+
if (input.toolIds && input.toolIds.length > 0) {
78+
updateDto.model.toolIds = input.toolIds;
79+
}
80+
81+
if (input.instructions) {
82+
updateDto.model.messages = [
83+
{
84+
role: 'system',
85+
content: input.instructions,
86+
},
87+
];
88+
}
89+
} else {
90+
if (input.toolIds && input.toolIds.length > 0) {
91+
updateDto.model = { toolIds: input.toolIds };
92+
}
93+
94+
if (input.instructions) {
95+
if (!updateDto.model) updateDto.model = {};
96+
updateDto.model.messages = [
97+
{
98+
role: 'system',
99+
content: input.instructions,
100+
},
101+
];
102+
}
103+
}
104+
105+
if (input.transcriber) {
106+
updateDto.transcriber = {
107+
provider: input.transcriber.provider,
108+
...(input.transcriber.model ? { model: input.transcriber.model } : {}),
109+
};
110+
}
111+
112+
if (input.voice) {
113+
updateDto.voice = {
114+
provider: input.voice.provider as any,
115+
voiceId: input.voice.voiceId,
116+
...(input.voice.model ? { model: input.voice.model } : {}),
117+
};
118+
}
119+
120+
if (input.firstMessage) {
121+
updateDto.firstMessage = input.firstMessage;
122+
}
123+
124+
if (input.firstMessageMode) {
125+
updateDto.firstMessageMode = input.firstMessageMode;
126+
}
127+
128+
return updateDto as Vapi.UpdateAssistantDto;
129+
}
130+
56131
export function transformAssistantOutput(
57132
assistant: Vapi.Assistant
58133
): z.infer<typeof AssistantOutputSchema> {
@@ -74,6 +149,7 @@ export function transformAssistantOutput(
74149
provider: assistant.transcriber?.provider || 'deepgram',
75150
model: getAssistantTranscriberModel(assistant.transcriber) || 'nova-3',
76151
},
152+
toolIds: assistant.model?.toolIds || [],
77153
};
78154
}
79155

@@ -159,3 +235,18 @@ export function transformPhoneNumberOutput(
159235
status: phoneNumber.status,
160236
};
161237
}
238+
239+
// ===== Tool Transformers =====
240+
241+
export function transformToolOutput(
242+
tool: any
243+
): z.infer<typeof ToolOutputSchema> {
244+
return {
245+
id: tool.id,
246+
createdAt: tool.createdAt,
247+
updatedAt: tool.updatedAt,
248+
type: tool.type || '',
249+
name: tool.function?.name || '',
250+
description: tool.function?.description || '',
251+
};
252+
}

0 commit comments

Comments
 (0)