@vapi-ai/mcp-server 0.0.11 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +152 -6
  2. package/dist/knowledge/docs.d.ts +96 -0
  3. package/dist/knowledge/docs.d.ts.map +1 -0
  4. package/dist/knowledge/docs.js +16 -0
  5. package/dist/knowledge/skills.d.ts +28 -0
  6. package/dist/knowledge/skills.d.ts.map +1 -0
  7. package/dist/knowledge/skills.js +131 -0
  8. package/dist/schemas/assistant-openapi.d.ts +31 -0
  9. package/dist/schemas/assistant-openapi.d.ts.map +1 -0
  10. package/dist/schemas/assistant-openapi.js +87 -0
  11. package/dist/schemas/assistant-openapi.json +2341 -0
  12. package/dist/schemas/index.d.ts +158 -1181
  13. package/dist/schemas/index.d.ts.map +1 -1
  14. package/dist/schemas/index.js +95 -172
  15. package/dist/schemas/simulation-openapi.d.ts +9 -0
  16. package/dist/schemas/simulation-openapi.d.ts.map +1 -0
  17. package/dist/schemas/simulation-openapi.js +81 -0
  18. package/dist/schemas/simulation-openapi.json +27558 -0
  19. package/dist/schemas/simulation.d.ts +54 -0
  20. package/dist/schemas/simulation.d.ts.map +1 -0
  21. package/dist/schemas/simulation.js +76 -0
  22. package/dist/tools/api-schema.d.ts +4 -0
  23. package/dist/tools/api-schema.d.ts.map +1 -0
  24. package/dist/tools/api-schema.js +67 -0
  25. package/dist/tools/assistant.d.ts.map +1 -1
  26. package/dist/tools/assistant.js +25 -10
  27. package/dist/tools/call.d.ts.map +1 -1
  28. package/dist/tools/call.js +13 -6
  29. package/dist/tools/docs.d.ts +3 -0
  30. package/dist/tools/docs.d.ts.map +1 -0
  31. package/dist/tools/docs.js +27 -0
  32. package/dist/tools/index.d.ts.map +1 -1
  33. package/dist/tools/index.js +8 -0
  34. package/dist/tools/input-schema.d.ts +5 -0
  35. package/dist/tools/input-schema.d.ts.map +1 -0
  36. package/dist/tools/input-schema.js +59 -0
  37. package/dist/tools/phone-number.d.ts.map +1 -1
  38. package/dist/tools/phone-number.js +11 -5
  39. package/dist/tools/simulation.d.ts +4 -0
  40. package/dist/tools/simulation.d.ts.map +1 -0
  41. package/dist/tools/simulation.js +133 -0
  42. package/dist/tools/skills.d.ts +3 -0
  43. package/dist/tools/skills.d.ts.map +1 -0
  44. package/dist/tools/skills.js +31 -0
  45. package/dist/tools/tool.d.ts.map +1 -1
  46. package/dist/tools/tool.js +23 -9
  47. package/dist/tools/validation.d.ts +22 -0
  48. package/dist/tools/validation.d.ts.map +1 -0
  49. package/dist/tools/validation.js +94 -0
  50. package/dist/transformers/index.d.ts +3 -3
  51. package/dist/transformers/index.d.ts.map +1 -1
  52. package/dist/transformers/index.js +52 -31
  53. package/package.json +9 -6
@@ -0,0 +1,133 @@
1
+ import { CreateSimulationInputSchema, CreateSimulationPersonalityInputSchema, CreateSimulationScenarioInputSchema, CreateSimulationSuiteInputSchema, GetSimulationRunInputSchema, GetSimulationRunItemInputSchema, ListSimulationResourcesInputSchema, ListSimulationRunsInputSchema, ListSimulationScenariosInputSchema, RunSimulationsInputSchema, } from '../schemas/index.js';
2
+ import { registerValidatedTool } from './validation.js';
3
+ import { createToolHandler } from './utils.js';
4
+ const DEFAULT_LIMIT = 50;
5
+ // Run items carry call snapshots and transcripts in these fields, which can
6
+ // be large. get_simulation_run leaves them out; get_simulation_run_item
7
+ // returns them.
8
+ const LARGE_RUN_ITEM_FIELDS = [
9
+ 'metadata',
10
+ 'configurations',
11
+ 'improvementSuggestions',
12
+ ];
13
+ function summarizeRunItem(item) {
14
+ const summary = { ...item };
15
+ for (const field of LARGE_RUN_ITEM_FIELDS) {
16
+ delete summary[field];
17
+ }
18
+ return summary;
19
+ }
20
+ export const registerSimulationTools = (server, vapiClient) => {
21
+ registerValidatedTool(server, 'list_simulation_personalities', {
22
+ description: 'Lists simulation personalities: the simulated callers (testers) used in Vapi simulations',
23
+ inputSchema: ListSimulationResourcesInputSchema,
24
+ }, createToolHandler(async (data) => vapiClient.simulationPersonalities.personalityControllerFindAll({
25
+ limit: data.limit ?? DEFAULT_LIMIT,
26
+ })));
27
+ registerValidatedTool(server, 'create_simulation_personality', {
28
+ description: 'Creates a simulation personality: the simulated caller that talks to the assistant under test',
29
+ inputSchema: CreateSimulationPersonalityInputSchema,
30
+ }, createToolHandler(async (data) => vapiClient.simulationPersonalities.personalityControllerCreate(data)));
31
+ registerValidatedTool(server, 'list_simulation_scenarios', {
32
+ description: 'Lists simulation scenarios: the scripts testers follow and the checks that decide pass or fail',
33
+ inputSchema: ListSimulationScenariosInputSchema,
34
+ }, createToolHandler(async (data) => vapiClient.simulationScenarios.scenarioControllerFindAll({
35
+ limit: data.limit ?? DEFAULT_LIMIT,
36
+ ...(data.name && { name: data.name }),
37
+ })));
38
+ registerValidatedTool(server, 'create_simulation_scenario', {
39
+ description: 'Creates a simulation scenario: instructions for the tester plus evaluations that decide whether the assistant passed',
40
+ inputSchema: CreateSimulationScenarioInputSchema,
41
+ }, createToolHandler(async ({ additionalFields, ...data }) => vapiClient.simulationScenarios.scenarioControllerCreate({
42
+ ...data,
43
+ ...additionalFields,
44
+ })));
45
+ registerValidatedTool(server, 'list_simulations', {
46
+ description: 'Lists simulations. Each simulation pairs one scenario with one personality.',
47
+ inputSchema: ListSimulationResourcesInputSchema,
48
+ }, createToolHandler(async (data) => vapiClient.simulations.simulationControllerFindAll({
49
+ limit: data.limit ?? DEFAULT_LIMIT,
50
+ })));
51
+ registerValidatedTool(server, 'create_simulation', {
52
+ description: 'Creates a simulation that pairs a scenario with a personality. Run it with run_simulations.',
53
+ inputSchema: CreateSimulationInputSchema,
54
+ }, createToolHandler(async (data) => vapiClient.simulations.simulationControllerCreate(data)));
55
+ registerValidatedTool(server, 'list_simulation_suites', {
56
+ description: 'Lists simulation suites: named groups of simulations',
57
+ inputSchema: ListSimulationResourcesInputSchema,
58
+ }, createToolHandler(async (data) => vapiClient.simulationSuites.simulationSuiteControllerFindAll({
59
+ limit: data.limit ?? DEFAULT_LIMIT,
60
+ })));
61
+ registerValidatedTool(server, 'create_simulation_suite', {
62
+ description: 'Creates a simulation suite that groups simulations so they can be run together',
63
+ inputSchema: CreateSimulationSuiteInputSchema,
64
+ }, createToolHandler(async ({ additionalFields, ...data }) => vapiClient.simulationSuites.simulationSuiteControllerCreate({
65
+ ...data,
66
+ ...additionalFields,
67
+ })));
68
+ registerValidatedTool(server, 'run_simulations', {
69
+ description: 'Starts a simulation run of simulations and/or suites against an assistant or squad. ' +
70
+ 'Returns the run with its ID and a dashboard URL to share with the user. Check progress with get_simulation_run.',
71
+ inputSchema: RunSimulationsInputSchema,
72
+ }, createToolHandler(async (data) => {
73
+ const simulations = [
74
+ ...(data.simulationIds ?? []).map((simulationId) => ({
75
+ type: 'simulation',
76
+ simulationId,
77
+ })),
78
+ ...(data.suiteIds ?? []).map((simulationSuiteId) => ({
79
+ type: 'simulationSuite',
80
+ simulationSuiteId,
81
+ })),
82
+ ];
83
+ return vapiClient.simulationRuns.simulationRunControllerCreate({
84
+ simulations,
85
+ target: data.assistantId
86
+ ? { type: 'assistant', assistantId: data.assistantId }
87
+ : { type: 'squad', squadId: data.squadId },
88
+ ...(data.iterations && { iterations: data.iterations }),
89
+ ...(data.transport && { transport: { provider: data.transport } }),
90
+ }, data.idempotencyKey
91
+ ? { headers: { 'Idempotency-Key': data.idempotencyKey } }
92
+ : undefined);
93
+ }));
94
+ registerValidatedTool(server, 'list_simulation_runs', {
95
+ description: 'Lists simulation runs, newest first',
96
+ inputSchema: ListSimulationRunsInputSchema,
97
+ }, createToolHandler(async (data) => vapiClient.simulationRuns.simulationRunControllerFindAll({
98
+ limit: data.limit ?? DEFAULT_LIMIT,
99
+ ...(data.status && { status: data.status }),
100
+ ...(data.targetId && { targetId: data.targetId }),
101
+ })));
102
+ registerValidatedTool(server, 'get_simulation_run', {
103
+ description: 'Gets a simulation run with its status, item counts, and the first 100 item summaries. ' +
104
+ 'itemsTruncated indicates omitted items; itemsMetadata preserves API pagination metadata. ' +
105
+ 'Use get_simulation_run_item for an item’s transcript and call details.',
106
+ inputSchema: GetSimulationRunInputSchema,
107
+ }, createToolHandler(async (data) => {
108
+ const [run, response] = await Promise.all([
109
+ vapiClient.simulationRuns.simulationRunControllerFindOne({
110
+ id: data.runId,
111
+ }),
112
+ vapiClient.simulationRuns.simulationRunControllerFindItems({
113
+ id: data.runId,
114
+ limit: 100,
115
+ }),
116
+ ]);
117
+ const page = response;
118
+ const items = Array.isArray(page) ? page : page.results;
119
+ return {
120
+ ...run,
121
+ items: items.map(summarizeRunItem),
122
+ itemsTruncated: !Array.isArray(page) && page.metadata.totalItems > items.length,
123
+ ...(!Array.isArray(page) && { itemsMetadata: page.metadata }),
124
+ };
125
+ }));
126
+ registerValidatedTool(server, 'get_simulation_run_item', {
127
+ description: 'Gets one simulation run item in full, including results, call snapshots, transcript, and improvement suggestions',
128
+ inputSchema: GetSimulationRunItemInputSchema,
129
+ }, createToolHandler(async (data) => vapiClient.simulationRuns.simulationRunControllerFindItem({
130
+ id: data.runId,
131
+ itemId: data.itemId,
132
+ })));
133
+ };
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerSkillTools(server: McpServer): void;
3
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/tools/skills.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAiBzE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,QAkBnD"}
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ import { PublicSkills } from '../knowledge/skills.js';
3
+ async function knowledgeResult(action) {
4
+ try {
5
+ return await action();
6
+ }
7
+ catch (error) {
8
+ return { isError: true, content: [{ type: 'text', text: `Knowledge request failed: ${error instanceof Error ? error.message : 'Unknown upstream error'}` }] };
9
+ }
10
+ }
11
+ function jsonResult(data) {
12
+ return { content: [{ type: 'text', text: JSON.stringify(data) }], structuredContent: data };
13
+ }
14
+ export function registerSkillTools(server) {
15
+ const skills = new PublicSkills();
16
+ const annotations = { readOnlyHint: true, destructiveHint: false, openWorldHint: true };
17
+ server.registerTool('search_skills', {
18
+ description: 'Find Vapi agent skills in the public VapiAI/skills repository by name, description or content. Omit query to browse. Use load_skill to read a result.',
19
+ inputSchema: {
20
+ query: z.string().trim().max(500).default('').describe('Keywords describing the Vapi task, or empty to browse all skills.'),
21
+ limit: z.number().int().min(1).max(30).default(10),
22
+ }, annotations,
23
+ }, ({ query, limit }) => knowledgeResult(async () => jsonResult(await skills.search(query, limit))));
24
+ server.registerTool('load_skill', {
25
+ description: 'Read a public Vapi skill or one of its supporting files. Start with SKILL.md, then load relevant files from the returned file list as needed. Returns instructions for this task; does not install skills or execute scripts.',
26
+ inputSchema: {
27
+ name: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(100).describe('Exact skill name from search_skills.'),
28
+ path: z.string().min(1).max(300).default('SKILL.md').describe('File path relative to the skill directory, such as SKILL.md or references/hooks.md.'),
29
+ }, annotations,
30
+ }, ({ name, path }) => knowledgeResult(async () => jsonResult(await skills.load(name, path))));
31
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/tools/tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAQ,MAAM,qBAAqB,CAAC;AAMvD,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,SAAS,EACjB,YAAY,UAAU,SA6CvB,CAAC"}
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../src/tools/tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAQ,MAAM,qBAAqB,CAAC;AAMvD,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,SAAS,EACjB,YAAY,UAAU,SA2DvB,CAAC"}
@@ -1,26 +1,40 @@
1
- import { GetToolInputSchema, CreateToolInputSchema, UpdateToolInputSchema } from '../schemas/index.js';
1
+ import { registerValidatedTool } from './validation.js';
2
+ import { EmptyInputSchema, GetToolInputSchema, CreateToolInputSchema, UpdateToolInputSchema } from '../schemas/index.js';
2
3
  import { transformToolInput, transformUpdateToolInput, transformToolOutput } from '../transformers/index.js';
3
4
  import { createToolHandler } from './utils.js';
4
5
  export const registerToolTools = (server, vapiClient) => {
5
- server.registerTool('list_tools', {
6
+ registerValidatedTool(server, 'list_tools', {
6
7
  description: 'Lists all Vapi tools',
7
- inputSchema: {},
8
+ inputSchema: EmptyInputSchema,
8
9
  }, createToolHandler(async () => {
9
10
  const tools = await vapiClient.tools.list({ limit: 10 });
10
11
  return tools.map(transformToolOutput);
11
12
  }));
12
- server.tool('get_tool', 'Gets details of a specific tool', GetToolInputSchema.shape, createToolHandler(async (data) => {
13
- const tool = await vapiClient.tools.get(data.toolId);
13
+ registerValidatedTool(server, 'get_tool', {
14
+ description: 'Gets details of a specific tool',
15
+ inputSchema: GetToolInputSchema,
16
+ }, createToolHandler(async (data) => {
17
+ const tool = await vapiClient.tools.get({ id: data.toolId });
14
18
  return transformToolOutput(tool);
15
19
  }));
16
- server.tool('create_tool', 'Creates a new Vapi tool', CreateToolInputSchema.shape, createToolHandler(async (data) => {
20
+ registerValidatedTool(server, 'create_tool', {
21
+ description: 'Creates a new Vapi tool',
22
+ inputSchema: CreateToolInputSchema,
23
+ }, createToolHandler(async (data) => {
17
24
  const createToolDto = transformToolInput(data);
18
25
  const tool = await vapiClient.tools.create(createToolDto);
19
26
  return transformToolOutput(tool);
20
27
  }));
21
- server.tool('update_tool', 'Updates an existing Vapi tool', UpdateToolInputSchema.shape, createToolHandler(async (data) => {
22
- const updateToolDto = transformUpdateToolInput(data);
23
- const tool = await vapiClient.tools.update(data.toolId, updateToolDto);
28
+ registerValidatedTool(server, 'update_tool', {
29
+ description: 'Updates an existing Vapi tool',
30
+ inputSchema: UpdateToolInputSchema,
31
+ }, createToolHandler(async (data) => {
32
+ const existingTool = await vapiClient.tools.get({ id: data.toolId });
33
+ const updateToolDto = transformUpdateToolInput(data, existingTool);
34
+ const tool = await vapiClient.tools.update({
35
+ id: data.toolId,
36
+ body: updateToolDto,
37
+ });
24
38
  return transformToolOutput(tool);
25
39
  }));
26
40
  };
@@ -0,0 +1,22 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import type { ToolResponse } from './utils.js';
4
+ type Issue = z.core.$ZodIssue;
5
+ type FieldIssue = {
6
+ path: string;
7
+ code: string;
8
+ message: string;
9
+ allowedValues?: unknown[];
10
+ allowedValuesTruncated?: boolean;
11
+ expected?: string;
12
+ minimum?: number;
13
+ maximum?: number;
14
+ format?: string;
15
+ };
16
+ export declare function validationIssues(issues: Issue[], input: unknown, prefix?: PropertyKey[]): FieldIssue[];
17
+ export declare function registerValidatedTool<Schema extends z.ZodType>(server: McpServer, name: string, config: {
18
+ description: string;
19
+ inputSchema: Schema;
20
+ }, handler: (params: z.output<Schema>) => Promise<ToolResponse>): import("@modelcontextprotocol/sdk/server/mcp.js").RegisteredTool;
21
+ export {};
22
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/tools/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,KAAK,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC;AAC9B,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;IACvE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACxE,CAAC;AAcF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,GAAE,WAAW,EAAO,GAAG,UAAU,EAAE,CA8C1G;AAWD,wBAAgB,qBAAqB,CAAC,MAAM,SAAS,CAAC,CAAC,OAAO,EAC5D,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,EACpD,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,oEAsB7D"}
@@ -0,0 +1,94 @@
1
+ import { z } from 'zod';
2
+ import { summarizeInputSchema } from './input-schema.js';
3
+ const MAX_ISSUES = 12;
4
+ const MAX_OPTIONS = 8;
5
+ const at = (input, path) => path.reduce((value, key) => value !== null && typeof value === 'object' ? value[key] : undefined, input);
6
+ const fieldPath = (path) => path.map((key, index) => typeof key === 'number' ? `[${key}]` : `${index ? '.' : ''}${String(key)}`).join('') || '$';
7
+ const matchesShape = (issues, kind) => !issues.some(issue => issue.path.length === 0 && (issue.code === 'invalid_type' && issue.expected !== kind ||
8
+ issue.code === 'invalid_union' && issue.errors.every(branch => !matchesShape(branch, kind))));
9
+ // Select the supplied provider's branch before formatting a union failure.
10
+ // Otherwise a single forbidden field can emit dozens of unrelated providers.
11
+ export function validationIssues(issues, input, prefix = []) {
12
+ return issues.flatMap((issue) => {
13
+ const path = [...prefix, ...issue.path];
14
+ if (issue.code === 'invalid_union') {
15
+ const value = at(input, path);
16
+ const kind = Array.isArray(value) ? 'array' : value === null ? 'null' : typeof value;
17
+ const compatible = issue.errors.filter(branch => matchesShape(branch, kind));
18
+ if (compatible.length === 1)
19
+ return validationIssues(compatible[0], input, path);
20
+ let record = value && typeof value === 'object' ? value : {};
21
+ // LLM configuration also accepts a JSON string. Decode only to select
22
+ // the correct branch; submitted values never enter the error response.
23
+ if (typeof value === 'string') {
24
+ try {
25
+ const decoded = JSON.parse(value);
26
+ if (decoded && typeof decoded === 'object')
27
+ record = decoded;
28
+ }
29
+ catch { /* The JSON parser's existing issue supplies the correction. */ }
30
+ }
31
+ const missingDiscriminator = compatible.length ? ['provider', 'type'].find(key => compatible.every(branch => branch.some(child => child.path.length === 1 && child.path[0] === key && child.code === 'invalid_value'))) : undefined;
32
+ const discriminator = missingDiscriminator ?? (typeof record.provider === 'string' ? 'provider' : typeof record.type === 'string' ? 'type' : undefined);
33
+ if (discriminator) {
34
+ const matches = compatible.filter(branch => !branch.some(child => child.path.length === 1 && child.path[0] === discriminator && child.code === 'invalid_value'));
35
+ if (matches.length === 1)
36
+ return validationIssues(matches[0], input, path);
37
+ if (matches.length === 0) {
38
+ const options = compatible.flatMap(branch => branch.flatMap(child => child.code === 'invalid_value' && child.path[0] === discriminator ? child.values : []));
39
+ return [optionIssue([...path, discriminator], options)];
40
+ }
41
+ }
42
+ if (compatible.length && compatible.every(branch => branch.every(child => child.path.length === 0 && child.code === 'invalid_value'))) {
43
+ return [optionIssue(path, compatible.flatMap(branch => branch.flatMap(child => child.code === 'invalid_value' ? child.values : [])))];
44
+ }
45
+ const expected = [...new Set(issue.errors.flatMap(branch => branch.flatMap(child => child.code === 'invalid_type' && child.path.length === 0 ? [child.expected] : [])))];
46
+ return [{ path: fieldPath(path), code: 'invalid_union', message: expected.length ? `Expected ${expected.join(' or ')}.` : 'Does not match the supported configuration; check the API schema for this field.' }];
47
+ }
48
+ if (issue.code === 'unrecognized_keys')
49
+ return issue.keys.map(key => ({
50
+ path: fieldPath([...path, key]), code: 'unrecognized_key',
51
+ message: 'This field is not supported for the selected provider or configuration; remove it.',
52
+ }));
53
+ if (issue.code === 'invalid_value')
54
+ return [optionIssue(path, issue.values)];
55
+ const detail = { path: fieldPath(path), code: issue.code, message: issue.message.slice(0, 300) };
56
+ if (issue.code === 'invalid_type')
57
+ detail.expected = issue.expected;
58
+ if (issue.code === 'too_big' && typeof issue.maximum === 'number')
59
+ detail.maximum = issue.maximum;
60
+ if (issue.code === 'too_small' && typeof issue.minimum === 'number')
61
+ detail.minimum = issue.minimum;
62
+ if (issue.code === 'invalid_format')
63
+ detail.format = issue.format;
64
+ return [detail];
65
+ });
66
+ }
67
+ function optionIssue(path, values) {
68
+ const unique = [...new Set(values)];
69
+ const allowedValues = unique.slice(0, MAX_OPTIONS);
70
+ return {
71
+ path: fieldPath(path), code: 'invalid_value', allowedValues, allowedValuesTruncated: unique.length > MAX_OPTIONS,
72
+ message: `Expected one of: ${allowedValues.map(value => JSON.stringify(value)).join(', ')}${unique.length > MAX_OPTIONS ? ` (and ${unique.length - MAX_OPTIONS} more; see the API schema for this field)` : ''}.`,
73
+ };
74
+ }
75
+ export function registerValidatedTool(server, name, config, handler) {
76
+ const schema = config.inputSchema;
77
+ // Advertise a compact input summary, but run the full parser exactly once. This
78
+ // boundary lets us format validation failures before the SDK stringifies a
79
+ // raw ZodError. No request reaches the API handler before parsing succeeds.
80
+ const transportSchema = z.object({}).passthrough().meta(summarizeInputSchema(z.toJSONSchema(schema, { io: 'input', target: 'draft-07' })));
81
+ return server.registerTool(name, { description: config.description, inputSchema: transportSchema }, async (raw) => {
82
+ const parsed = await schema.safeParseAsync(raw);
83
+ if (!parsed.success) {
84
+ const all = validationIssues(parsed.error.issues, raw);
85
+ const issues = all.slice(0, MAX_ISSUES);
86
+ return {
87
+ isError: true,
88
+ content: [{ type: 'text', text: `Input validation error for ${name}:\n${issues.map(issue => `- ${issue.path}: ${issue.message}`).join('\n')}${all.length > MAX_ISSUES ? `\n${all.length - MAX_ISSUES} additional issues omitted.` : ''}\nCorrect the listed fields and retry.` }],
89
+ structuredContent: { validationError: { code: 'INVALID_ARGUMENTS', tool: name, issues, truncated: all.length > MAX_ISSUES } },
90
+ };
91
+ }
92
+ return handler(parsed.data);
93
+ });
94
+ }
@@ -2,12 +2,12 @@ import { Vapi } from '@vapi-ai/server-sdk';
2
2
  import { z } from 'zod';
3
3
  import { CreateAssistantInputSchema, CallInputSchema, AssistantOutputSchema, CallOutputSchema, PhoneNumberOutputSchema, ToolOutputSchema, UpdateAssistantInputSchema, CreateToolInputSchema, UpdateToolInputSchema } from '../schemas/index.js';
4
4
  export declare function transformAssistantInput(input: z.infer<typeof CreateAssistantInputSchema>): Vapi.CreateAssistantDto;
5
- export declare function transformUpdateAssistantInput(input: z.infer<typeof UpdateAssistantInputSchema>): Vapi.UpdateAssistantDto;
5
+ export declare function transformUpdateAssistantInput(input: z.infer<typeof UpdateAssistantInputSchema>, existing?: Vapi.Assistant): Vapi.UpdateAssistantDto;
6
6
  export declare function transformAssistantOutput(assistant: Vapi.Assistant): z.infer<typeof AssistantOutputSchema>;
7
7
  export declare function transformCallInput(input: z.infer<typeof CallInputSchema>): Vapi.CreateCallDto;
8
8
  export declare function transformCallOutput(call: Vapi.Call): z.infer<typeof CallOutputSchema>;
9
9
  export declare function transformPhoneNumberOutput(phoneNumber: any): z.infer<typeof PhoneNumberOutputSchema>;
10
10
  export declare function transformToolInput(input: z.infer<typeof CreateToolInputSchema>): any;
11
- export declare function transformUpdateToolInput(input: z.infer<typeof UpdateToolInputSchema>): any;
12
- export declare function transformToolOutput(tool: Vapi.ToolsGetResponse): z.infer<typeof ToolOutputSchema>;
11
+ export declare function transformUpdateToolInput(input: z.infer<typeof UpdateToolInputSchema>, existing?: Vapi.GetToolsResponse): any;
12
+ export declare function transformToolOutput(tool: Vapi.GetToolsResponse): z.infer<typeof ToolOutputSchema>;
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transformers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,uBAAuB,EACvB,gBAAgB,EAChB,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,qBAAqB,CAAC;AAI7B,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,GAChD,IAAI,CAAC,kBAAkB,CA2CzB;AAED,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,GAChD,IAAI,CAAC,kBAAkB,CAiEzB;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,IAAI,CAAC,SAAS,GACxB,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAqBvC;AA2BD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,GACrC,IAAI,CAAC,aAAa,CAwBpB;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,IAAI,GACd,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAgBlC;AAID,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,GAAG,GACf,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CASzC;AAID,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,GAC3C,GAAG,CA8DL;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,GAC3C,GAAG,CAiDL;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,gBAAgB,GAC1B,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAclC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transformers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,uBAAuB,EACvB,gBAAgB,EAChB,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,qBAAqB,CAAC;AAI7B,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,GAChD,IAAI,CAAC,kBAAkB,CA2CzB;AAED,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,EACjD,QAAQ,CAAC,EAAE,IAAI,CAAC,SAAS,GACxB,IAAI,CAAC,kBAAkB,CAyEzB;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,IAAI,CAAC,SAAS,GACxB,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAuBvC;AA2BD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,GACrC,IAAI,CAAC,aAAa,CAwBpB;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,IAAI,GACd,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAgBlC;AAID,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,GAAG,GACf,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CASzC;AAID,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,GAC3C,GAAG,CAkEL;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,EAC5C,QAAQ,CAAC,EAAE,IAAI,CAAC,gBAAgB,GAC/B,GAAG,CA+DL;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,CAAC,gBAAgB,GAC1B,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAiBlC"}
@@ -35,42 +35,44 @@ export function transformAssistantInput(input) {
35
35
  }
36
36
  return assistantDto;
37
37
  }
38
- export function transformUpdateAssistantInput(input) {
38
+ export function transformUpdateAssistantInput(input, existing) {
39
39
  const updateDto = {};
40
40
  if (input.name) {
41
41
  updateDto.name = input.name;
42
42
  }
43
- if (input.llm) {
44
- updateDto.model = {
45
- provider: input.llm.provider,
46
- model: input.llm.model,
47
- };
48
- if (input.toolIds && input.toolIds.length > 0) {
49
- updateDto.model.toolIds = input.toolIds;
50
- }
51
- if (input.instructions) {
52
- updateDto.model.messages = [
53
- {
54
- role: 'system',
55
- content: input.instructions,
56
- },
57
- ];
43
+ const hasToolIds = !!input.toolIds && input.toolIds.length > 0;
44
+ if (input.llm || hasToolIds || input.instructions) {
45
+ // PATCH replaces `model` as a whole, so start from the assistant's current
46
+ // model. Sending only the changed fields drops `provider`/`model` and the
47
+ // API rejects the update.
48
+ const existingModel = existing?.model ?? {};
49
+ const providerChanged = !!input.llm && input.llm.provider !== existingModel.provider;
50
+ // Provider-specific settings may not be valid for a new provider, so only
51
+ // the prompt and tools carry over when the provider changes.
52
+ const model = providerChanged
53
+ ? {
54
+ ...(existingModel.messages && { messages: existingModel.messages }),
55
+ ...(existingModel.toolIds && { toolIds: existingModel.toolIds }),
56
+ }
57
+ : { ...existingModel };
58
+ if (input.llm) {
59
+ model.provider = input.llm.provider;
60
+ model.model = input.llm.model;
58
61
  }
59
- }
60
- else {
61
- if (input.toolIds && input.toolIds.length > 0) {
62
- updateDto.model = { toolIds: input.toolIds };
62
+ if (hasToolIds) {
63
+ model.toolIds = input.toolIds;
63
64
  }
64
65
  if (input.instructions) {
65
- if (!updateDto.model)
66
- updateDto.model = {};
67
- updateDto.model.messages = [
66
+ const otherMessages = (model.messages ?? []).filter((message) => message.role !== 'system');
67
+ model.messages = [
68
68
  {
69
69
  role: 'system',
70
70
  content: input.instructions,
71
71
  },
72
+ ...otherMessages,
72
73
  ];
73
74
  }
75
+ updateDto.model = model;
74
76
  }
75
77
  if (input.transcriber) {
76
78
  updateDto.transcriber = {
@@ -94,6 +96,8 @@ export function transformUpdateAssistantInput(input) {
94
96
  return updateDto;
95
97
  }
96
98
  export function transformAssistantOutput(assistant) {
99
+ const voiceModel = getAssistantVoiceModel(assistant.voice);
100
+ const transcriberModel = getAssistantTranscriberModel(assistant.transcriber);
97
101
  return {
98
102
  id: assistant.id,
99
103
  createdAt: assistant.createdAt,
@@ -106,11 +110,11 @@ export function transformAssistantOutput(assistant) {
106
110
  voice: {
107
111
  provider: assistant.voice?.provider || '11labs',
108
112
  voiceId: getAssistantVoiceId(assistant.voice),
109
- model: getAssistantVoiceModel(assistant.voice) || 'eleven_turbo_v2_5',
113
+ ...(voiceModel ? { model: voiceModel } : {}),
110
114
  },
111
115
  transcriber: {
112
116
  provider: assistant.transcriber?.provider || 'deepgram',
113
- model: getAssistantTranscriberModel(assistant.transcriber) || 'nova-3',
117
+ ...(transcriberModel ? { model: transcriberModel } : {}),
114
118
  },
115
119
  toolIds: assistant.model?.toolIds || [],
116
120
  };
@@ -245,13 +249,22 @@ export function transformToolInput(input) {
245
249
  default:
246
250
  throw new Error(`Unsupported tool type: ${input.type}`);
247
251
  }
252
+ if (input.additionalFields) {
253
+ Object.assign(toolDto, input.additionalFields);
254
+ }
248
255
  return toolDto;
249
256
  }
250
- export function transformUpdateToolInput(input) {
257
+ export function transformUpdateToolInput(input, existing) {
251
258
  let updateDto = {};
259
+ // PATCH replaces `function` and `server` as a whole, so start from the
260
+ // tool's current values. Sending only the changed fields drops
261
+ // `function.name` and the API rejects the update.
262
+ const existingFunction = existing && 'function' in existing ? existing.function : undefined;
263
+ const existingServer = existing && 'server' in existing ? existing.server : undefined;
252
264
  // Add function definition if name and description are provided
253
265
  if (input.name || input.description) {
254
266
  updateDto.function = {
267
+ ...existingFunction,
255
268
  ...(input.name && { name: input.name }),
256
269
  ...(input.description && { description: input.description }),
257
270
  };
@@ -272,10 +285,12 @@ export function transformUpdateToolInput(input) {
272
285
  }
273
286
  else {
274
287
  updateDto.function = {
288
+ ...existingFunction,
275
289
  parameters: input.function.parameters,
276
290
  };
277
291
  }
278
292
  updateDto.server = {
293
+ ...existingServer,
279
294
  url: input.function.server.url,
280
295
  ...(input.function.server.headers && { headers: input.function.server.headers }),
281
296
  };
@@ -295,20 +310,26 @@ export function transformUpdateToolInput(input) {
295
310
  if (input.apiRequest.timeoutSeconds)
296
311
  updateDto.timeoutSeconds = input.apiRequest.timeoutSeconds;
297
312
  }
313
+ if (input.additionalFields) {
314
+ Object.assign(updateDto, input.additionalFields);
315
+ }
298
316
  return updateDto;
299
317
  }
300
318
  export function transformToolOutput(tool) {
319
+ // Not every tool type in SDK 3.x has `function` or `server`.
320
+ const toolFunction = 'function' in tool ? tool.function : undefined;
321
+ const toolServer = 'server' in tool ? tool.server : undefined;
301
322
  return {
302
323
  id: tool.id,
303
324
  createdAt: tool.createdAt,
304
325
  updatedAt: tool.updatedAt,
305
326
  type: tool.type || '',
306
- name: tool.function?.name || '',
307
- description: tool.function?.description || '',
308
- parameters: tool.function?.parameters || {},
327
+ name: toolFunction?.name || '',
328
+ description: toolFunction?.description || '',
329
+ parameters: toolFunction?.parameters || {},
309
330
  server: {
310
- url: tool.server?.url || '',
311
- headers: tool.server?.headers || {},
331
+ url: toolServer?.url || '',
332
+ headers: toolServer?.headers || {},
312
333
  }
313
334
  };
314
335
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vapi-ai/mcp-server",
3
3
  "description": "Build AI voice assistants and phone agents with Vapi through the Model Context Protocol",
4
- "version": "0.0.11",
4
+ "version": "0.1.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",
@@ -37,7 +37,7 @@
37
37
  "scripts": {
38
38
  "prepare": "npm run build",
39
39
  "clean": "shx rm -rf dist",
40
- "build": "npm run clean && tsc -p tsconfig.build.json && shx chmod +x dist/*.js",
40
+ "build": "npm run clean && tsc -p tsconfig.build.json && shx cp src/schemas/assistant-openapi.json src/schemas/simulation-openapi.json dist/schemas/ && shx chmod +x dist/*.js",
41
41
  "typecheck": "tsc --noEmit -p tsconfig.json",
42
42
  "start": "node dist/index.js",
43
43
  "dev": "tsx watch src/index.ts",
@@ -49,6 +49,7 @@
49
49
  "test": "VAPI_TOKEN=test-mock-token NODE_OPTIONS=--experimental-vm-modules jest --runInBand --testPathIgnorePatterns='.*-live\\.test\\.ts$'",
50
50
  "test:unit": "VAPI_TOKEN=test-mock-token NODE_OPTIONS=--experimental-vm-modules jest --runInBand src/__tests__/mcp-tool-execution.test.ts src/__tests__/mcp-tools-list.test.ts",
51
51
  "test:docs": "NODE_OPTIONS=--experimental-vm-modules jest --runInBand src/__tests__/documentation-tools.test.ts",
52
+ "schemas:snapshot": "node scripts/snapshot-assistant-validation.mjs",
52
53
  "pretest:stdio": "npm run build",
53
54
  "test:stdio": "VAPI_TOKEN=test-mock-token NODE_OPTIONS=--experimental-vm-modules jest --runInBand src/__tests__/mcp-stdio-compat.test.ts",
54
55
  "pretest:package": "npm run build",
@@ -62,16 +63,18 @@
62
63
  "dist/**/*.d.ts.map"
63
64
  ],
64
65
  "dependencies": {
65
- "@modelcontextprotocol/sdk": "1.26.0",
66
- "@vapi-ai/server-sdk": "^0.5.2",
66
+ "@modelcontextprotocol/sdk": "1.32.2",
67
+ "@vapi-ai/server-sdk": "^3.0.0",
67
68
  "dotenv": "^16.4.7",
68
- "zod": "^3.25.76",
69
- "zod-to-json-schema": "^3.24.5"
69
+ "yaml": "^2.9.1",
70
+ "zod": "4.6.7"
70
71
  },
71
72
  "devDependencies": {
72
73
  "@modelcontextprotocol/inspector": "1.0.1",
73
74
  "@types/jest": "^29.5.14",
74
75
  "@types/node": "^22.14.0",
76
+ "ajv": "8.20.0",
77
+ "ajv-formats": "3.0.1",
75
78
  "jest": "^29.7.0",
76
79
  "shx": "^0.4.0",
77
80
  "ts-jest": "^29.3.1",