Skip to content

feat: add Zod schema validation for CLI arguments #15

Open

Description

@gsong

Summary

Add Zod-based schema validation for CLI arguments to provide comprehensive input validation, better error messages, and runtime type safety.

Current State (Implemented)

The CLI argument parsing has been significantly improved with:

✅ Custom arg parsing to prevent Claude Code flag collisions (src/index.ts:85-102)

  • Separates ccmcp flags from passthrough args
  • Handles combined short flags (e.g., -in → -i -n)
  • Prevents passthrough of conflicting flags to Claude Code

✅ Basic validation (src/index.ts:75-83)

  • validateConfigDir() checks for empty strings and null bytes
  • Throws errors with clear messages

✅ Standard Node.js parseArgs

  • Uses built-in parseArgs with strict mode
  • Proper type definitions via CliArgs interface

What Remains To Be Done

The main gap is comprehensive validation using Zod schemas:

1. Schema-Based Validation

Add Zod schemas for stronger runtime validation:

import { z } from 'zod';

const cliArgsSchema = z.object({
  help: z.boolean().optional(),
  version: z.boolean().optional(),
  'config-dir': z.string()
    .min(1, "Config directory cannot be empty")
    .refine(path => !path.includes('\0'), "Config directory contains invalid characters")
    .refine(isValidPath, "Invalid path format")
    .optional(),
  'ignore-cache': z.boolean().optional(),
  'clear-cache': z.boolean().optional(),
  'no-save': z.boolean().optional(),
  cleanup: z.boolean().optional(),
  'dry-run': z.boolean().optional(),
  yes: z.boolean().optional(),
});

type CliArgs = z.infer<typeof cliArgsSchema>;

2. Enhanced Validation Rules

  • Filesystem-aware path validation (check existence, permissions)
  • More comprehensive path format validation
  • Validation for conflicting flag combinations (e.g., --yes with --dry-run)

3. Better Error Messages

  • Field-specific errors with suggestions
  • Show examples of valid inputs when validation fails
  • Point users to --help for more information

4. Consolidated Validation Logic

  • Move validation from scattered locations into the schema
  • Reuse validation patterns across the application
  • Type-safe validation with compile-time and runtime checks

Benefits

  • Type Safety: Compile-time and runtime type checking with inferred types
  • Better UX: Clear, actionable error messages when validation fails
  • Maintainability: Centralized validation logic that's easy to extend
  • Consistency: Same validation approach as MCP config validation (already uses Zod)

Implementation Notes

  • Project already uses Zod for MCP config validation (src/schemas/mcp-config.ts)
  • Can follow same patterns for CLI arg validation
  • Keep existing custom parsing logic for flag collision prevention
  • Add Zod validation as an additional layer after parseArgs

Acceptance Criteria

  • Zod schema defined for all CLI arguments
  • Enhanced path validation beyond null bytes
  • Improved error messages with suggestions
  • Type-safe CLI args with schema inference
  • Tests for validation scenarios
  • No breaking changes to existing CLI behavior

Activity

  1. gsong commented on Oct 13, 2025

    @gsong
    OwnerAuthor

    Alternative: Incremental Refactoring Approach

    While the Commander.js + Zod solution proposed in this issue would be comprehensive, there's a simpler incremental approach that could improve the current codebase without adding dependencies:

    Current State

    The parseCliArgs() function in src/index.ts:85-152 is 67 lines with manual flag handling logic that's somewhat complex.

    Simpler Alternative

    Extract flag definitions and simplify parsing logic:

    // Extract to constant
    const CCMCP_FLAGS = {
      help: ['-h', '--help'],
      version: ['-v', '--version'],
      configDir: ['-c', '--config-dir'],
      ignoreCache: ['--ignore-cache'],
      clearCache: ['--clear-cache'],
      cleanup: ['--cleanup'],
      dryRun: ['--dry-run'],
      yes: ['--yes'],
      verbose: ['--verbose'],
    } as const;
    
    // Simplify the parsing logic
    const allFlags = new Set(Object.values(CCMCP_FLAGS).flat());

    Benefits

    • No new dependencies
    • Easier to maintain flag definitions
    • Simpler to add new flags
    • Reduces complexity from 67 lines to ~40 lines

    Trade-offs

    • Doesn't provide the advanced validation and help generation of a full CLI library
    • Still requires manual validation logic
    • Doesn't address all the UX improvements proposed in this issue

    This could serve as an interim improvement while deciding whether the full Commander.js + Zod migration is worth the complexity.

  2. changed the title [-]feat: implement robust CLI options handling with proper validation[/-] [+]feat: add Zod schema validation for CLI arguments[/+] on Oct 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions