You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 init → thv vmcp validate --config → thv 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.goInitConfig.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:
Declare local flag variables: groupName string, outputPath string.
Define a *cobra.Command with Use: "init" and a descriptive Short/Long.
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.
Call vmcpcli.Init(cmd.Context(), cfg) and return the error.
Attach --group/-g, --config/-c, and/or --output/-o flags using cmd.Flags().StringVarP.
Mark --group as required.
Inside newVMCPCommand(), add cmd.AddCommand(newVMCPInitCommand()) after the existing serve and validate registrations.
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
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
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
.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.funcnewVMCPInitCommand() *cobra.Command {
var (
groupNamestringoutputPathstring
)
cmd:=&cobra.Command{
Use: "init",
Short: "Generate a starter vMCP configuration file",
Long: `Discover running workloads in a ToolHive group and generate a startervMCP YAML configuration file pre-populated with one backend entry peraccessible 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())
iferr!=nil {
returnfmt.Errorf("failed to create workload discoverer: %w", err)
}
returnvmcpcli.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")
returncmd
}
// 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)
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
Description
Add the
initsubcommand to the existingcmd/thv/app/vmcp.gofile (created in #4883), wiring the--group,--config, and--outputflags and delegating entirely to theInit(ctx, InitConfig)function established in #4882 (pkg/vmcp/cli/init.go). Regenerate CLI documentation viatask docs. This completes Phase 2 of RFC THV-0059 and gives users thethv vmcp initscaffolding command that generates a starter config file from running group workloads.Context
#4882 delivered
pkg/vmcp/cli/init.gowith theInit(ctx, InitConfig)function and all business logic for workload discovery and YAML config rendering. #4883 createdcmd/thv/app/vmcp.gowith the top-levelvmcpCobra command plusserveandvalidatesubcommands. This item is the thin CLI wiring layer that addsinitas the third subcommand, strictly following the thin wrapper principle:cmd/thv/app/vmcp.goparses flags, constructsInitConfig, and callscli.Init— no business logic belongs here.The init subcommand is a prerequisite for the #4888 E2E test flow:
thv vmcp init→thv vmcp validate --config→thv 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.gowithInitfunction), #4883 (cmd/thv/app/vmcp.gowithvmcpcommand structure)Blocks: #4888
Acceptance Criteria
cmd/thv/app/vmcp.gocontains anewVMCPInitCommand()constructor function that returns a*cobra.CommandwithUse: "init"initsubcommand is registered viacmd.AddCommand(newVMCPInitCommand())insidenewVMCPCommand()initsubcommand accepts--group/-g(string, required) — the ToolHive group name whose workloads are enumeratedinitsubcommand accepts--config/-c(string, optional, default"") — output file path for the generated YAML config; when empty, output goes to stdoutinitsubcommand accepts--output/-o(string, optional, default"") — alias/alternative for--configor the output path (align with the flag shape specified inpkg/vmcp/cli/init.goInitConfig.OutputPath)RunEofinitconstructs avmcpcli.InitConfigfrom parsed flag values, injects a realworkloads.Discoverer(constructed from the CLI context, consistent with howserveandvalidateconstruct their dependencies), and callsvmcpcli.Init(cmd.Context(), cfg)--groupis marked required viacmd.MarkFlagRequired("group")thv vmcp --helplistsinitas an available subcommand with a short descriptionthv vmcp init --helpdisplays the three flags with their types, defaults, and descriptionsthv vmcp init --group default(with workloads running) writes a valid YAML config to stdoutthv vmcp init --group default --config /tmp/vmcp.yamlwrites the config to the specified file and exits 0thv vmcp initwith no--groupflag exits non-zero with Cobra's "required flag not set" errortask docsis run and the generated CLI documentation indocs/reflects the newinitsubcommandgo build ./cmd/thv/...succeeds with no errorsTechnical Approach
Recommended Implementation
Add
newVMCPInitCommand()tocmd/thv/app/vmcp.gofollowing the same constructor pattern used fornewVMCPServeCommand()andnewVMCPValidateCommand()in #4883. The function body is short:groupName string,outputPath string.*cobra.CommandwithUse: "init"and a descriptiveShort/Long.RunE, construct avmcpcli.InitConfigwithGroupName,OutputPath, andDiscoverer. TheDiscoverermust be a realworkloads.Discovererinstance, not nil — look at hownewVMCPServeCommand()'sRunEconstructs its runtime dependencies fromcmd.Context()as the pattern to follow.vmcpcli.Init(cmd.Context(), cfg)and return the error.--group/-g,--config/-c, and/or--output/-oflags usingcmd.Flags().StringVarP.--groupas required.newVMCPCommand(), addcmd.AddCommand(newVMCPInitCommand())after the existingserveandvalidateregistrations.task docs.The exact flag name(s) for the output path should match what
InitConfig.OutputPathand the RFC specify. If the RFC uses--configfor the output path (since the generated file becomes the input tothv vmcp serve --config), use--config/-c. If a separate--output/-ois preferred for clarity, use that and avoid flag name collisions with sibling subcommands (flags are scoped per subcommand in Cobra, soinit --configandserve --configdo not conflict). Consult #4882'sInitConfigdefinition for the canonical field name.Patterns & Frameworks
newVMCPInitCommand()contains only flag definitions,InitConfigconstruction, andvmcpcli.Init(...)delegation — no YAML rendering, no discoverer construction logic beyond what is needed to instantiate the interfacecmd/thv/app/vmcp.gofrom Addthv vmcp serveandthv vmcp validatesubcommands #4883; do not add a second headernewVMCPInitCommand()returns*cobra.Command, consistent withnewVMCPServeCommand()andnewVMCPValidateCommand()(and all other commands incmd/thv/app/)cmd.Flags().StringVarP(&groupName, "group", "g", "", "...")— local flag, not persistent; mark required via_ = cmd.MarkFlagRequired("group")vmcpcli.Init(cmd.Context(), cfg)directly; wrap withfmt.Errorf("...: %w", err)only if adding context not already present in the errortask docsrequirement: CLI documentation must be regenerated after any command change; CI enforces thisCode Pointers
cmd/thv/app/vmcp.go— File to modify: addnewVMCPInitCommand()and register it innewVMCPCommand(); created by Addthv vmcp serveandthv vmcp validatesubcommands #4883 withserveandvalidatealready presentpkg/vmcp/cli/init.go— Created by Addinit.gotopkg/vmcp/cli/for config scaffolding #4882; definesInit(ctx context.Context, cfg InitConfig) errorandInitConfig— these are the exact types to import and callcmd/thv/app/mcp.go— Reference for Cobra command constructor pattern, flag attachment, andRunEshapecmd/thv/app/mcp_serve.go— Reference forRunEthat constructs a runtime dependency (e.g., a manager/discoverer) fromcmd.Context()and delegates to apkg/functioncmd/thv/app/commands.go— No changes needed for this item (registration inNewRootCmd()andIsInformationalCommand()was already done in Addthv vmcp serveandthv vmcp validatesubcommands #4883); review to confirmvmcpis present.claude/rules/cli-commands.md— Thin wrapper principle, new command checklist, E2E-first testing policy for CLIComponent Interfaces
Note: The exact
Discovererconstructor call inRunEmust match what #4883'sserveandvalidatesubcommands use. If those commands construct a*groups.CLIManageror 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 incmd/thv/app/are covered by E2E tests only — no unit tests unless testing output formatting or flag validation helpers. All business logic lives inpkg/vmcp/cli/init.go(#4882). Full E2E coverage forthv vmcp initis scoped to #4888.Unit Tests (not applicable for this item — thin wrapper with no business logic)
pkg/vmcp/cli/init.go(Addinit.gotopkg/vmcp/cli/for config scaffolding #4882)Integration Tests
go build ./cmd/thv/...passes — confirms the new subcommand compiles and the import frompkg/vmcp/cli/resolves correctlythv vmcp --helpoutput includesinitin the subcommand listthv vmcp init --helpshows--group,--configflags with their descriptions and defaultsthv vmcp initwith no flags exits non-zero with Cobra's "required flag(s) not set: group" errorEdge Cases
thv vmcp initwith no subcommand arguments (no--group) is rejected by Cobra beforeRunEis called — theMarkFlagRequiredensures this--group ""(empty string) should be caught byInitinpkg/vmcp/cli/init.gowith a descriptive error (not a concern for the CLI layer, but verify it surfaces cleanly)--configflag oninitdoes not conflict with--configonserveorvalidate— Cobra scopes flags per subcommandOut of Scope
pkg/vmcp/cli/init.go(Addinit.gotopkg/vmcp/cli/for config scaffolding #4882)vmcpinNewRootCmd()andIsInformationalCommand()— already done in Addthv vmcp serveandthv vmcp validatesubcommands #4883thv vmcp serve --group— that is Implement zero-config quick mode forthv vmcp serve#4886--optimizer,--optimizer-embedding, etc.) — that is Wire optimizer flags intothv vmcp serve#4887thv vmcp init→validate→servelifecycle — those are E2E tests: quick mode and config-file mode #4888EmbeddingServiceManager— that is Implement EmbeddingServiceManager in pkg/vmcp/cli/ #4884pkg/vmcp/packages beyond what is importedReferences
initsubcommand flag names and behaviorcmd/thv/app/mcp.goandcmd/thv/app/mcp_serve.go— Structural models for Cobra command constructorspkg/vmcp/cli/init.go(Addinit.gotopkg/vmcp/cli/for config scaffolding #4882) — TheInitfunction andInitConfigstruct this item wires upcmd/thv/app/vmcp.go(Addthv vmcp serveandthv vmcp validatesubcommands #4883) — File to modify; contains thevmcpcommand andserve/validatesubcommands.claude/rules/cli-commands.md— Thin wrapper principle and new command checklist.claude/rules/go-style.md— SPDX header requirement, error handling conventions