Skip to content

Commit d82340e

Browse files
refactor: validate resolved config with a zod schema
Move the mutually exclusive argument check into a zod superRefine so schema validation is the single final gatekeeper for the resolved configuration. No behavior change.
1 parent 49a9628 commit d82340e

1 file changed

Lines changed: 32 additions & 18 deletions

File tree

‎src/config/mcp-options.ts‎

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import type {YargsOptions} from '../third_party/index.js';
8-
import {yargs, hideBin} from '../third_party/index.js';
8+
import {yargs, hideBin, zod as z} from '../third_party/index.js';
99
import os from 'node:os';
1010
import {readFileSync} from 'node:fs';
1111
import path from 'node:path';
@@ -614,24 +614,38 @@ export function parseArguments(
614614
console.error(`Unknown arguments: ${unknownArgs.map(arg => `--${arg}`)}`);
615615
}
616616

617-
// Step 5: Validation. Conflicts are only checked against explicit user input
618-
// so that dynamic defaults never conflict.
619-
const activeArgs = new Set<string>();
620-
for (const [key, val] of Object.entries(explicitConfig)) {
621-
if (val !== undefined && val !== false) {
622-
activeArgs.add(key);
623-
}
624-
}
625-
for (const group of CONFLICTING_ARGS) {
626-
const activeInGroup = group.filter(arg => activeArgs.has(arg));
627-
if (activeInGroup.length > 1) {
628-
const [arg1, arg2] = activeInGroup;
629-
throw new Error(`Arguments ${arg1} and ${arg2} are mutually exclusive`);
630-
}
631-
}
617+
// Step 5: Final Schema Validation & Type Safety. Conflicts are only checked
618+
// against explicit user input so that dynamic defaults never conflict.
619+
const ConfigSchema = z
620+
.object({})
621+
.passthrough()
622+
.superRefine((_config, ctx) => {
623+
const activeArgs = new Set<string>();
624+
for (const [key, val] of Object.entries(explicitConfig)) {
625+
if (val !== undefined && val !== false) {
626+
activeArgs.add(key);
627+
}
628+
}
629+
630+
for (const group of CONFLICTING_ARGS) {
631+
const activeInGroup = group.filter(arg => activeArgs.has(arg));
632+
if (activeInGroup.length > 1) {
633+
const [arg1, arg2] = activeInGroup;
634+
ctx.addIssue({
635+
code: z.ZodIssueCode.custom,
636+
message: `Arguments ${arg1} and ${arg2} are mutually exclusive`,
637+
path: [arg1, arg2],
638+
});
639+
}
640+
}
641+
});
632642

633-
if (!isParsedArguments(resolvedConfig)) {
643+
const result = ConfigSchema.safeParse(resolvedConfig);
644+
if (!result.success) {
645+
throw new Error(result.error.issues[0].message);
646+
}
647+
if (!isParsedArguments(result.data)) {
634648
throw new Error('Failed to resolve configuration');
635649
}
636-
return resolvedConfig;
650+
return result.data;
637651
}

0 commit comments

Comments
 (0)