@vapi-ai/mcp-server 0.0.11 → 0.2.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.
- package/README.md +241 -104
- package/dist/knowledge/docs.d.ts +96 -0
- package/dist/knowledge/docs.d.ts.map +1 -0
- package/dist/knowledge/docs.js +16 -0
- package/dist/knowledge/skills.d.ts +28 -0
- package/dist/knowledge/skills.d.ts.map +1 -0
- package/dist/knowledge/skills.js +131 -0
- package/dist/schemas/assistant-openapi.d.ts +31 -0
- package/dist/schemas/assistant-openapi.d.ts.map +1 -0
- package/dist/schemas/assistant-openapi.js +87 -0
- package/dist/schemas/assistant-openapi.json +2341 -0
- package/dist/schemas/index.d.ts +161 -1181
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +99 -172
- package/dist/schemas/simulation-openapi.d.ts +9 -0
- package/dist/schemas/simulation-openapi.d.ts.map +1 -0
- package/dist/schemas/simulation-openapi.js +81 -0
- package/dist/schemas/simulation-openapi.json +27558 -0
- package/dist/schemas/simulation.d.ts +54 -0
- package/dist/schemas/simulation.d.ts.map +1 -0
- package/dist/schemas/simulation.js +76 -0
- package/dist/tools/annotations.d.ts +5 -0
- package/dist/tools/annotations.d.ts.map +1 -0
- package/dist/tools/annotations.js +14 -0
- package/dist/tools/api-schema.d.ts +4 -0
- package/dist/tools/api-schema.d.ts.map +1 -0
- package/dist/tools/api-schema.js +69 -0
- package/dist/tools/assistant.d.ts.map +1 -1
- package/dist/tools/assistant.js +33 -17
- package/dist/tools/call.d.ts.map +1 -1
- package/dist/tools/call.js +20 -11
- package/dist/tools/docs.d.ts +3 -0
- package/dist/tools/docs.d.ts.map +1 -0
- package/dist/tools/docs.js +27 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +8 -0
- package/dist/tools/input-schema.d.ts +5 -0
- package/dist/tools/input-schema.d.ts.map +1 -0
- package/dist/tools/input-schema.js +59 -0
- package/dist/tools/list.d.ts +34 -0
- package/dist/tools/list.d.ts.map +1 -0
- package/dist/tools/list.js +47 -0
- package/dist/tools/phone-number.d.ts.map +1 -1
- package/dist/tools/phone-number.js +17 -10
- package/dist/tools/simulation.d.ts +4 -0
- package/dist/tools/simulation.d.ts.map +1 -0
- package/dist/tools/simulation.js +146 -0
- package/dist/tools/skills.d.ts +3 -0
- package/dist/tools/skills.d.ts.map +1 -0
- package/dist/tools/skills.js +31 -0
- package/dist/tools/tool.d.ts.map +1 -1
- package/dist/tools/tool.js +31 -14
- package/dist/tools/validation.d.ts +24 -0
- package/dist/tools/validation.d.ts.map +1 -0
- package/dist/tools/validation.js +94 -0
- package/dist/transformers/index.d.ts +3 -3
- package/dist/transformers/index.d.ts.map +1 -1
- package/dist/transformers/index.js +52 -31
- package/package.json +9 -6
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { parse } from 'yaml';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const REPOSITORY = 'https://github.com/VapiAI/skills';
|
|
4
|
+
const API = 'https://api.github.com/repos/VapiAI/skills';
|
|
5
|
+
const RAW = 'https://raw.githubusercontent.com/VapiAI/skills';
|
|
6
|
+
const CACHE_MS = 5 * 60_000;
|
|
7
|
+
const TEXT_FILE = /\.(md|txt|json|ya?ml|py|[cm]?js|ts|sh|rst|toml)$/i;
|
|
8
|
+
const commitSchema = z.object({ sha: z.string().regex(/^[a-f0-9]{40}$/) });
|
|
9
|
+
const treeSchema = z.object({
|
|
10
|
+
truncated: z.literal(false),
|
|
11
|
+
tree: z.array(z.object({ path: z.string(), type: z.string(), mode: z.string() })),
|
|
12
|
+
});
|
|
13
|
+
// Each MCP server has its own bounded-lifetime snapshot. Rejected fetches are
|
|
14
|
+
// evicted so an upstream outage doesn't poison later calls.
|
|
15
|
+
export class PublicSkills {
|
|
16
|
+
fetcher;
|
|
17
|
+
cached;
|
|
18
|
+
constructor(fetcher = fetch) {
|
|
19
|
+
this.fetcher = fetcher;
|
|
20
|
+
}
|
|
21
|
+
async read(url, maxBytes = 512_000) {
|
|
22
|
+
const response = await this.fetcher(url, {
|
|
23
|
+
signal: AbortSignal.timeout(15_000),
|
|
24
|
+
redirect: 'error',
|
|
25
|
+
headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'vapi-mcp-server' },
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
await response.body?.cancel();
|
|
29
|
+
throw new Error(`Public skills request failed (HTTP ${response.status}). Retry later.`);
|
|
30
|
+
}
|
|
31
|
+
if (!response.body)
|
|
32
|
+
throw new Error('Public skills returned an empty response.');
|
|
33
|
+
const reader = response.body.getReader();
|
|
34
|
+
const chunks = [];
|
|
35
|
+
let size = 0;
|
|
36
|
+
try {
|
|
37
|
+
while (true) {
|
|
38
|
+
const { done, value } = await reader.read();
|
|
39
|
+
if (done)
|
|
40
|
+
break;
|
|
41
|
+
size += value.byteLength;
|
|
42
|
+
if (size > maxBytes)
|
|
43
|
+
throw new Error('Public skills response exceeds the supported size.');
|
|
44
|
+
chunks.push(value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
await reader.cancel().catch(() => undefined);
|
|
49
|
+
reader.releaseLock();
|
|
50
|
+
}
|
|
51
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
52
|
+
}
|
|
53
|
+
snapshot() {
|
|
54
|
+
if (this.cached && this.cached.expiresAt > Date.now())
|
|
55
|
+
return this.cached.promise;
|
|
56
|
+
const promise = (async () => {
|
|
57
|
+
// Pin file reads to one commit so SKILL.md and its references agree even
|
|
58
|
+
// when the public repository changes between requests.
|
|
59
|
+
const { sha } = commitSchema.parse(JSON.parse(await this.read(`${API}/commits/main`)));
|
|
60
|
+
const tree = treeSchema.parse(JSON.parse(await this.read(`${API}/git/trees/${sha}?recursive=1`, 2_000_000)));
|
|
61
|
+
const files = new Set(tree.tree.filter(entry => entry.type === 'blob' && ['100644', '100755'].includes(entry.mode)).map(entry => entry.path));
|
|
62
|
+
const names = [...files].filter(path => /^[a-z0-9]+(?:-[a-z0-9]+)*\/SKILL\.md$/.test(path)).map(path => path.split('/')[0]).sort();
|
|
63
|
+
if (!names.length)
|
|
64
|
+
throw new Error('No public Vapi skills were found. Retry later.');
|
|
65
|
+
return { revision: sha, files, names, contents: new Map() };
|
|
66
|
+
})();
|
|
67
|
+
this.cached = { expiresAt: Date.now() + CACHE_MS, promise };
|
|
68
|
+
void promise.catch(() => { if (this.cached?.promise === promise)
|
|
69
|
+
this.cached = undefined; });
|
|
70
|
+
return promise;
|
|
71
|
+
}
|
|
72
|
+
content(snapshot, path) {
|
|
73
|
+
const cached = snapshot.contents.get(path);
|
|
74
|
+
if (cached)
|
|
75
|
+
return cached;
|
|
76
|
+
const encodedPath = path.split('/').map(encodeURIComponent).join('/');
|
|
77
|
+
const promise = this.read(`${RAW}/${snapshot.revision}/${encodedPath}`);
|
|
78
|
+
if (snapshot.contents.size >= 100)
|
|
79
|
+
snapshot.contents.delete(snapshot.contents.keys().next().value);
|
|
80
|
+
snapshot.contents.set(path, promise);
|
|
81
|
+
void promise.catch(() => snapshot.contents.delete(path));
|
|
82
|
+
return promise;
|
|
83
|
+
}
|
|
84
|
+
async skill(snapshot, name) {
|
|
85
|
+
const content = await this.content(snapshot, `${name}/SKILL.md`);
|
|
86
|
+
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
|
|
87
|
+
if (!frontmatter)
|
|
88
|
+
throw new Error(`Invalid frontmatter for public skill ${name}.`);
|
|
89
|
+
const metadata = z.object({ name: z.literal(name), description: z.string().min(1) }).parse(parse(frontmatter[1]));
|
|
90
|
+
return { name, description: metadata.description, content };
|
|
91
|
+
}
|
|
92
|
+
async search(query, limit) {
|
|
93
|
+
const snapshot = await this.snapshot();
|
|
94
|
+
const skills = [];
|
|
95
|
+
// Limit concurrent public requests when warming a new snapshot.
|
|
96
|
+
for (let index = 0; index < snapshot.names.length; index += 4) {
|
|
97
|
+
skills.push(...await Promise.all(snapshot.names.slice(index, index + 4).map(name => this.skill(snapshot, name))));
|
|
98
|
+
}
|
|
99
|
+
const terms = [...new Set(query.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [])];
|
|
100
|
+
const matches = skills.map(skill => ({
|
|
101
|
+
skill,
|
|
102
|
+
score: terms.reduce((score, term) => score +
|
|
103
|
+
(skill.name.includes(term) ? 5 : 0) +
|
|
104
|
+
(skill.description.toLowerCase().includes(term) ? 3 : 0) +
|
|
105
|
+
(skill.content.toLowerCase().includes(term) ? 1 : 0), 0),
|
|
106
|
+
})).filter(({ score }) => !terms.length || score > 0)
|
|
107
|
+
.sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name));
|
|
108
|
+
return {
|
|
109
|
+
repository: REPOSITORY, revision: snapshot.revision, total: matches.length,
|
|
110
|
+
skills: matches.slice(0, limit).map(({ skill }) => ({
|
|
111
|
+
name: skill.name, description: skill.description,
|
|
112
|
+
sourceUrl: `${REPOSITORY}/blob/${snapshot.revision}/${skill.name}/SKILL.md`,
|
|
113
|
+
})),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async load(name, path) {
|
|
117
|
+
const snapshot = await this.snapshot();
|
|
118
|
+
if (!snapshot.names.includes(name))
|
|
119
|
+
throw new Error(`Unknown public skill. Use search_skills to find a skill name.`);
|
|
120
|
+
const files = [...snapshot.files].filter(file => file.startsWith(`${name}/`) && TEXT_FILE.test(file)).map(file => file.slice(name.length + 1)).sort();
|
|
121
|
+
// Exact tree membership, not URL/path normalization, bounds reads to this
|
|
122
|
+
// skill and excludes traversal, URLs, symlinks and binary assets.
|
|
123
|
+
if (!files.includes(path))
|
|
124
|
+
throw new Error('Unknown skill file. Load SKILL.md to see the available files.');
|
|
125
|
+
return {
|
|
126
|
+
name, path, revision: snapshot.revision,
|
|
127
|
+
sourceUrl: `${REPOSITORY}/blob/${snapshot.revision}/${name}/${path}`,
|
|
128
|
+
content: await this.content(snapshot, `${name}/${path}`), files,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
type JsonSchema = z.core.JSONSchema.JSONSchema;
|
|
3
|
+
export declare const requiredToolConfigurations: string[];
|
|
4
|
+
export declare const functionRequiresName: boolean;
|
|
5
|
+
export declare function inputContract(value: unknown, schemaMap?: boolean): unknown;
|
|
6
|
+
export declare function providerContract(field: 'voice' | 'transcriber' | 'llm'): JsonSchema;
|
|
7
|
+
export declare const providersRequiringExpandedInputs: {
|
|
8
|
+
[k: string]: string[];
|
|
9
|
+
};
|
|
10
|
+
type VoiceConfiguration = {
|
|
11
|
+
provider: string;
|
|
12
|
+
voiceId: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
};
|
|
15
|
+
type TranscriberConfiguration = {
|
|
16
|
+
provider: string;
|
|
17
|
+
model?: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const VoiceInputSchema: z.ZodType<VoiceConfiguration>;
|
|
20
|
+
export declare const TranscriberInputSchema: z.ZodType<TranscriberConfiguration>;
|
|
21
|
+
export declare const CreateAssistantNameSchema: z.ZodType<string>;
|
|
22
|
+
export declare const UpdateAssistantNameSchema: z.ZodType<string>;
|
|
23
|
+
export declare const LLMInputSchema: z.ZodType<{
|
|
24
|
+
provider: string;
|
|
25
|
+
model?: string;
|
|
26
|
+
}>;
|
|
27
|
+
export declare function fieldContract(name: string): JsonSchema;
|
|
28
|
+
export declare function apiField<T = unknown>(name: string, overrides?: Partial<JsonSchema>): z.ZodType<T>;
|
|
29
|
+
export declare function apiObjectField<T = Record<string, unknown>>(name: string): z.ZodType<T>;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=assistant-openapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistant-openapi.d.ts","sourceRoot":"","sources":["../../src/schemas/assistant-openapi.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,KAAK,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;AAkB/C,eAAO,MAAM,0BAA0B,UAAsC,CAAC;AAC9E,eAAO,MAAM,oBAAoB,SAAgC,CAAC;AAKlE,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,UAAQ,GAAG,OAAO,CAiCxE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,GAAG,KAAK,GAAG,UAAU,CAanF;AAID,eAAO,MAAM,gCAAgC;;CAI5C,CAAC;AAEF,KAAK,kBAAkB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAChF,KAAK,wBAAwB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAIrE,eAAO,MAAM,gBAAgB,EAAoF,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAC/I,eAAO,MAAM,sBAAsB,EAA0F,CAAC,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;AACjK,eAAO,MAAM,yBAAyB,EAA+E,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AACvI,eAAO,MAAM,yBAAyB,EAA+E,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AAEvI,eAAO,MAAM,cAAc,EAAkF,CAAC,CAAC,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE7J,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAKtD;AAED,wBAAgB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,OAAO,CAAC,UAAU,CAAM,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAErG;AAED,wBAAgB,cAAc,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAKtF"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const snapshot = JSON.parse(readFileSync(new URL('./assistant-openapi.json', import.meta.url), 'utf8'));
|
|
4
|
+
export const requiredToolConfigurations = snapshot.requiredToolConfigurations;
|
|
5
|
+
export const functionRequiresName = snapshot.functionRequiresName;
|
|
6
|
+
// The published spec uses overlapping oneOf [enum, string] branches for preset
|
|
7
|
+
// IDs and model names. The API accepts both. Change only this recognizable
|
|
8
|
+
// pattern, leaving discriminated/exclusive unions intact.
|
|
9
|
+
export function inputContract(value, schemaMap = false) {
|
|
10
|
+
if (Array.isArray(value))
|
|
11
|
+
return value.map(item => inputContract(item));
|
|
12
|
+
if (!value || typeof value !== 'object')
|
|
13
|
+
return value;
|
|
14
|
+
const schema = value;
|
|
15
|
+
// Property names such as oneOf/default are user-defined keys in schema maps,
|
|
16
|
+
// rather than JSON Schema keywords. Preserve them and normalize their values.
|
|
17
|
+
if (schemaMap)
|
|
18
|
+
return Object.fromEntries(Object.entries(schema).map(([key, nested]) => [key, inputContract(nested)]));
|
|
19
|
+
// An empty published preset enum denotes no preset values. Represent that
|
|
20
|
+
// branch as an always-failing schema so it remains valid JSON Schema.
|
|
21
|
+
if (Array.isArray(schema.enum) && schema.enum.length === 0)
|
|
22
|
+
return { not: {} };
|
|
23
|
+
const alternatives = Array.isArray(schema.oneOf) ? schema.oneOf : undefined;
|
|
24
|
+
const overlap = alternatives?.some(branch => branch.type === 'string' &&
|
|
25
|
+
Object.keys(branch).every(key => ['type', 'title', 'description'].includes(key))) &&
|
|
26
|
+
alternatives.some(branch => branch.type === 'string' && Array.isArray(branch.enum));
|
|
27
|
+
return Object.fromEntries(Object.entries(schema)
|
|
28
|
+
// OpenAPI defaults are annotations; preserve omitted input fields and let
|
|
29
|
+
// the API apply its defaults rather than materializing them in requests.
|
|
30
|
+
.filter(([key]) => key !== 'default')
|
|
31
|
+
.map(([key, nested]) => [
|
|
32
|
+
key === 'oneOf' && overlap ? 'anyOf' : key,
|
|
33
|
+
// Some published voice IDs repeat in enum arrays. Deduplication changes
|
|
34
|
+
// no accepted values and keeps the result valid JSON Schema for Ajv.
|
|
35
|
+
key === 'enum' && Array.isArray(nested) ? [...new Set(nested)]
|
|
36
|
+
// Published name patterns include JavaScript /.../ delimiters, while
|
|
37
|
+
// JSON Schema pattern expects the regex source without delimiters.
|
|
38
|
+
: key === 'pattern' && typeof nested === 'string' && nested.startsWith('/') && nested.endsWith('/') ? nested.slice(1, -1)
|
|
39
|
+
// BackoffPlan.type is published as object with a string enum. Keep the
|
|
40
|
+
// enum's actual value type; the incompatible type annotation is a spec defect.
|
|
41
|
+
: key === 'type' && nested === 'object' && Array.isArray(schema.enum) && schema.enum.every(v => typeof v === 'string') ? 'string'
|
|
42
|
+
: ['properties', 'definitions', '$defs', 'patternProperties', 'dependentSchemas'].includes(key) ? inputContract(nested, true)
|
|
43
|
+
: ['allOf', 'anyOf', 'oneOf', 'not', 'items', 'additionalProperties', 'contains', 'if', 'then', 'else'].includes(key) ? inputContract(nested)
|
|
44
|
+
: nested,
|
|
45
|
+
]));
|
|
46
|
+
}
|
|
47
|
+
export function providerContract(field) {
|
|
48
|
+
const supported = snapshot[field].filter(definition => definition.required.every(key => key in definition.properties));
|
|
49
|
+
return {
|
|
50
|
+
oneOf: supported.map(definition => ({
|
|
51
|
+
type: 'object',
|
|
52
|
+
required: definition.required,
|
|
53
|
+
properties: inputContract(definition.properties, true),
|
|
54
|
+
// API DTOs reject undeclared provider fields. In particular, a provider
|
|
55
|
+
// without a model property must reject model, not silently accept it.
|
|
56
|
+
additionalProperties: false,
|
|
57
|
+
})),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Providers requiring fields outside the existing compact MCP surface stay
|
|
61
|
+
// unsupported, rather than having those API-required fields silently dropped.
|
|
62
|
+
export const providersRequiringExpandedInputs = Object.fromEntries(['voice', 'transcriber', 'llm'].map(field => [field, snapshot[field]
|
|
63
|
+
.filter(definition => definition.required.some(key => !(key in definition.properties)))
|
|
64
|
+
.map(definition => definition.name)]));
|
|
65
|
+
// fromJSONSchema validates at runtime but intentionally returns an untyped
|
|
66
|
+
// ZodType. These types describe the projected fields, not a second validator.
|
|
67
|
+
export const VoiceInputSchema = z.fromJSONSchema(providerContract('voice'), { defaultTarget: 'openapi-3.0' });
|
|
68
|
+
export const TranscriberInputSchema = z.fromJSONSchema(providerContract('transcriber'), { defaultTarget: 'openapi-3.0' });
|
|
69
|
+
export const CreateAssistantNameSchema = z.fromJSONSchema(snapshot.name.create, { defaultTarget: 'openapi-3.0' });
|
|
70
|
+
export const UpdateAssistantNameSchema = z.fromJSONSchema(snapshot.name.update, { defaultTarget: 'openapi-3.0' });
|
|
71
|
+
export const LLMInputSchema = z.fromJSONSchema(providerContract('llm'), { defaultTarget: 'openapi-3.0' });
|
|
72
|
+
export function fieldContract(name) {
|
|
73
|
+
const field = snapshot.fields[name];
|
|
74
|
+
if (!field)
|
|
75
|
+
throw new Error(`Unknown API input field: ${name}`);
|
|
76
|
+
return JSON.parse(JSON.stringify(inputContract({ ...field, definitions: snapshot.definitions }), (key, value) => key === '$ref' && typeof value === 'string' ? value.replace('#/components/schemas/', '#/definitions/') : value));
|
|
77
|
+
}
|
|
78
|
+
export function apiField(name, overrides = {}) {
|
|
79
|
+
return z.fromJSONSchema({ ...fieldContract(name), ...overrides }, { defaultTarget: 'openapi-3.0' });
|
|
80
|
+
}
|
|
81
|
+
export function apiObjectField(name) {
|
|
82
|
+
const schema = apiField(name);
|
|
83
|
+
if (!(schema instanceof z.ZodObject))
|
|
84
|
+
throw new Error(`${name} is no longer an object schema`);
|
|
85
|
+
// Preserve the existing MCP policy of stripping unknown envelope fields.
|
|
86
|
+
return schema.strip();
|
|
87
|
+
}
|