Skip to content

Add thv vmcp init subcommand #4885

Description

@yrobla

Description

Add the init subcommand to the existing cmd/thv/app/vmcp.go file (created in #4883), wiring the --group, --config, and --output flags and delegating entirely to the Init(ctx, InitConfig) function established in #4882 (pkg/vmcp/cli/init.go). Regenerate CLI documentation via task docs. This completes Phase 2 of RFC THV-0059 and gives users the thv vmcp init scaffolding command that generates a starter config file from running group workloads.

Context

#4882 delivered pkg/vmcp/cli/init.go with the Init(ctx, InitConfig) function and all business logic for workload discovery and YAML config rendering. #4883 created cmd/thv/app/vmcp.go with the top-level vmcp Cobra command plus serve and validate subcommands. This item is the thin CLI wiring layer that adds init as the third subcommand, strictly following the thin wrapper principle: cmd/thv/app/vmcp.go parses flags, constructs InitConfig, and calls cli.Init — no business logic belongs here.

The init subcommand is a prerequisite for the #4888 E2E test flow: thv vmcp initthv vmcp validate --configthv vmcp serve --config. It is also the last Phase 2 item; all Phase 3 and Phase 4 work can proceed once this merges.

Dependencies: Depends on #4882 (pkg/vmcp/cli/init.go with Init function), #4883 (cmd/thv/app/vmcp.go with vmcp command structure)
Blocks: #4888

Acceptance Criteria

  • cmd/thv/app/vmcp.go contains a newVMCPInitCommand() constructor function that returns a *cobra.Command with Use: "init"
  • The init subcommand is registered via cmd.AddCommand(newVMCPInitCommand()) inside newVMCPCommand()
  • The init subcommand accepts --group/-g (string, required) — the ToolHive group name whose workloads are enumerated
  • The init subcommand accepts --config/-c (string, optional, default "") — output file path for the generated YAML config; when empty, output goes to stdout
  • The init subcommand accepts --output/-o (string, optional, default "") — alias/alternative for --config or the output path (align with the flag shape specified in pkg/vmcp/cli/init.go InitConfig.OutputPath)
  • The RunE of init constructs a vmcpcli.InitConfig from parsed flag values, injects a real workloads.Discoverer (constructed from the CLI context, consistent with how serve and validate construct their dependencies), and calls vmcpcli.Init(cmd.Context(), cfg)
  • --group is marked required via cmd.MarkFlagRequired("group")
  • thv vmcp --help lists init as an available subcommand with a short description
  • thv vmcp init --help displays the three flags with their types, defaults, and descriptions
  • thv vmcp init --group default (with workloads running) writes a valid YAML config to stdout
  • thv vmcp init --group default --config /tmp/vmcp.yaml writes the config to the specified file and exits 0
  • thv vmcp init with no --group flag exits non-zero with Cobra's "required flag not set" error
  • task docs is run and the generated CLI documentation in docs/ reflects the new init subcommand
  • go build ./cmd/thv/... succeeds with no errors
  • All existing tests pass (no regressions)
  • Code reviewed and approved

Technical Approach

Recommended Implementation

Add newVMCPInitCommand() to cmd/thv/app/vmcp.go following the same constructor pattern used for newVMCPServeCommand() and newVMCPValidateCommand() in #4883. The function body is short:

  1. Declare local flag variables: groupName string, outputPath string.
  2. Define a *cobra.Command with Use: "init" and a descriptive Short/Long.
  3. In RunE, construct a vmcpcli.InitConfig with GroupName, OutputPath, and Discoverer. The Discoverer must be a real workloads.Discoverer instance, not nil — look at how newVMCPServeCommand()'s RunE constructs its runtime dependencies from cmd.Context() as the pattern to follow.
  4. Call vmcpcli.Init(cmd.Context(), cfg) and return the error.
  5. Attach --group/-g, --config/-c, and/or --output/-o flags using cmd.Flags().StringVarP.
  6. Mark --group as required.
  7. Inside newVMCPCommand(), add cmd.AddCommand(newVMCPInitCommand()) after the existing serve and validate registrations.
  8. Run task docs.

The exact flag name(s) for the output path should match what InitConfig.OutputPath and the RFC specify. If the RFC uses --config for the output path (since the generated file becomes the input to thv vmcp serve --config), use --config/-c. If a separate --output/-o is preferred for clarity, use that and avoid flag name collisions with sibling subcommands (flags are scoped per subcommand in Cobra, so init --config and serve --config do not conflict). Consult #4882's InitConfig definition for the canonical field name.

Patterns & Frameworks

  • Thin wrapper principle (hard rule): newVMCPInitCommand() contains only flag definitions, InitConfig construction, and vmcpcli.Init(...) delegation — no YAML rendering, no discoverer construction logic beyond what is needed to instantiate the interface
  • SPDX headers: already present on cmd/thv/app/vmcp.go from Add thv vmcp serve and thv vmcp validate subcommands #4883; do not add a second header
  • Constructor pattern: newVMCPInitCommand() returns *cobra.Command, consistent with newVMCPServeCommand() and newVMCPValidateCommand() (and all other commands in cmd/thv/app/)
  • Flag style: cmd.Flags().StringVarP(&groupName, "group", "g", "", "...") — local flag, not persistent; mark required via _ = cmd.MarkFlagRequired("group")
  • Error return: return vmcpcli.Init(cmd.Context(), cfg) directly; wrap with fmt.Errorf("...: %w", err) only if adding context not already present in the error
  • task docs requirement: CLI documentation must be regenerated after any command change; CI enforces this

Code Pointers

  • cmd/thv/app/vmcp.go — File to modify: add newVMCPInitCommand() and register it in newVMCPCommand(); created by Add thv vmcp serve and thv vmcp validate subcommands #4883 with serve and validate already present
  • pkg/vmcp/cli/init.go — Created by Add init.go to pkg/vmcp/cli/ for config scaffolding #4882; defines Init(ctx context.Context, cfg InitConfig) error and InitConfig — these are the exact types to import and call
  • cmd/thv/app/mcp.go — Reference for Cobra command constructor pattern, flag attachment, and RunE shape
  • cmd/thv/app/mcp_serve.go — Reference for RunE that constructs a runtime dependency (e.g., a manager/discoverer) from cmd.Context() and delegates to a pkg/ function
  • cmd/thv/app/commands.go — No changes needed for this item (registration in NewRootCmd() and IsInformationalCommand() was already done in Add thv vmcp serve and thv vmcp validate subcommands #4883); review to confirm vmcp is present
  • .claude/rules/cli-commands.md — Thin wrapper principle, new command checklist, E2E-first testing policy for CLI

Component Interfaces

// Addition to cmd/thv/app/vmcp.go
// (SPDX header and package declaration already exist from #4883)

// newVMCPInitCommand returns the "vmcp init" subcommand.
func newVMCPInitCommand() *cobra.Command {
    var (
        groupName  string
        outputPath string
    )
    cmd := &cobra.Command{
        Use:   "init",
        Short: "Generate a starter vMCP configuration file",
        Long: `Discover running workloads in a ToolHive group and generate a starter
vMCP YAML configuration file pre-populated with one backend entry per
accessible workload.

The generated file can be reviewed and customized, then passed to
'thv vmcp validate --config' to check it and 'thv vmcp serve --config'
to start the aggregated server.

If --config is not provided, the generated YAML is written to stdout.`,
        RunE: func(cmd *cobra.Command, _ []string) error {
            // Construct the real Discoverer from context
            // (exact constructor call depends on #4883 patterns)
            discoverer, err := workloads.NewDiscoverer(cmd.Context())
            if err != nil {
                return fmt.Errorf("failed to create workload discoverer: %w", err)
            }
            return vmcpcli.Init(cmd.Context(), vmcpcli.InitConfig{
                GroupName:  groupName,
                OutputPath: outputPath,
                Discoverer: discoverer,
            })
        },
    }
    cmd.Flags().StringVarP(&groupName, "group", "g", "", "ToolHive group name to discover workloads from (required)")
    cmd.Flags().StringVarP(&outputPath, "config", "c", "", "Output file path for the generated config (default: stdout)")
    _ = cmd.MarkFlagRequired("group")
    return cmd
}
// Inside newVMCPCommand() in cmd/thv/app/vmcp.go — add after validate:
cmd.AddCommand(newVMCPServeCommand())
cmd.AddCommand(newVMCPValidateCommand())
cmd.AddCommand(newVMCPInitCommand())  // add this line

Note: The exact Discoverer constructor call in RunE must match what #4883's serve and validate subcommands use. If those commands construct a *groups.CLIManager or call a helper function, replicate that pattern exactly. The interface definition above is illustrative; adjust to the actual runtime dependency pattern established in #4883.

Testing Strategy

Per organizational standards (.claude/rules/testing.md), CLI commands in cmd/thv/app/ are covered by E2E tests only — no unit tests unless testing output formatting or flag validation helpers. All business logic lives in pkg/vmcp/cli/init.go (#4882). Full E2E coverage for thv vmcp init is scoped to #4888.

Unit Tests (not applicable for this item — thin wrapper with no business logic)

Integration Tests

  • go build ./cmd/thv/... passes — confirms the new subcommand compiles and the import from pkg/vmcp/cli/ resolves correctly
  • thv vmcp --help output includes init in the subcommand list
  • thv vmcp init --help shows --group, --config flags with their descriptions and defaults
  • thv vmcp init with no flags exits non-zero with Cobra's "required flag(s) not set: group" error

Edge Cases

  • Running thv vmcp init with no subcommand arguments (no --group) is rejected by Cobra before RunE is called — the MarkFlagRequired ensures this
  • Passing --group "" (empty string) should be caught by Init in pkg/vmcp/cli/init.go with a descriptive error (not a concern for the CLI layer, but verify it surfaces cleanly)
  • The --config flag on init does not conflict with --config on serve or validate — Cobra scopes flags per subcommand

Out of Scope

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    cliChanges that impact CLI functionalityenhancementNew feature or requestvmcpVirtual MCP Server related issues
    No fields configured for Task 📋.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions