Skip to content

Commit b5d60a7

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 ad94d8b commit b5d60a7

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';
@@ -623,24 +623,38 @@ export function parseArguments(
623623
console.error(`Unknown arguments: ${unknownArgs.map(arg => `--${arg}`)}`);
624624
}
625625

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

642-
if (!isParsedArguments(resolvedConfig)) {
652+
const result = ConfigSchema.safeParse(resolvedConfig);
653+
if (!result.success) {
654+
throw new Error(result.error.issues[0].message);
655+
}
656+
if (!isParsedArguments(result.data)) {
643657
throw new Error('Failed to resolve configuration');
644658
}
645-
return resolvedConfig;
659+
return result.data;
646660
}

0 commit comments

Comments
 (0)