Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion internal/cmd/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ PowerShell:
PS> awmg completion powershell > awmg.ps1
# and source this file from your PowerShell profile.
`,
GroupID: "utils",
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
Expand All @@ -67,7 +68,11 @@ PowerShell:
},
}

// Override the parent's PersistentPreRunE to skip validation for completion command
// Override PersistentPreRunE to skip the root command's validation hook.
// Note: cobra does NOT automatically chain PersistentPreRunE hooks — unlike
// middleware-style frameworks, a child's PersistentPreRunE completely replaces
// the parent's. We explicitly set a no-op here so that completion generation
// runs without requiring a config file.
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
return nil
}
Expand Down
5 changes: 5 additions & 0 deletions internal/cmd/completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ func newTestRootWithCompletion() (*cobra.Command, *cobra.Command) {
root := &cobra.Command{
Use: "awmg",
}
// Add the real root's "utils" group so GroupID assignments on attached
// subcommands remain valid when root.Execute() is called in tests.
root.AddGroup(&cobra.Group{ID: "utils", Title: "Utilities:"})
completion := newCompletionCmd()
root.AddCommand(completion)
return root, completion
Expand Down Expand Up @@ -283,6 +286,8 @@ func TestNewCompletionCmd_OverridesParentPersistentPreRunE(t *testing.T) {
return assert.AnError
},
}
// Add the group so the completion command's GroupID is valid.
root.AddGroup(&cobra.Group{ID: "utils", Title: "Utilities:"})
completion := newCompletionCmd()
root.AddCommand(completion)

Expand Down
47 changes: 18 additions & 29 deletions internal/cmd/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,51 +103,40 @@ func TestRegisterFlagCompletions(t *testing.T) {
t.Run("config flag completion returns toml extension filter", func(t *testing.T) {
cmd := setupCmd(t)

completionFn, ok := cmd.GetFlagCompletionFunc("config")
require.True(t, ok, "config flag should have a completion function registered")
require.NotNil(t, completionFn, "config completion function should not be nil")

completions, directive := completionFn(cmd, nil, "")
assert.Equal(t, cobra.ShellCompDirectiveFilterFileExt, directive,
"config flag should use FilterFileExt directive for .toml files")
assert.Equal(t, []string{"toml"}, completions,
flag := cmd.Flags().Lookup("config")
require.NotNil(t, flag, "config flag should be registered")
exts, ok := flag.Annotations[cobra.BashCompFilenameExt]
require.True(t, ok, "config flag should have filename extension annotation")
assert.Equal(t, []string{"toml"}, exts,
"config flag should complete with .toml extension")
})

t.Run("log-dir flag completion returns directory filter", func(t *testing.T) {
cmd := setupCmd(t)

completionFn, ok := cmd.GetFlagCompletionFunc("log-dir")
require.True(t, ok, "log-dir flag should have a completion function registered")

completions, directive := completionFn(cmd, nil, "")
assert.Equal(t, cobra.ShellCompDirectiveFilterDirs, directive,
"log-dir flag should use FilterDirs directive")
assert.Nil(t, completions, "log-dir flag completion should return nil completions")
flag := cmd.Flags().Lookup("log-dir")
require.NotNil(t, flag, "log-dir flag should be registered")
_, ok := flag.Annotations[cobra.BashCompSubdirsInDir]
assert.True(t, ok, "log-dir flag should have directory annotation")
})

t.Run("payload-dir flag completion returns directory filter", func(t *testing.T) {
cmd := setupCmd(t)

completionFn, ok := cmd.GetFlagCompletionFunc("payload-dir")
require.True(t, ok, "payload-dir flag should have a completion function registered")

completions, directive := completionFn(cmd, nil, "")
assert.Equal(t, cobra.ShellCompDirectiveFilterDirs, directive,
"payload-dir flag should use FilterDirs directive")
assert.Nil(t, completions, "payload-dir flag completion should return nil completions")
flag := cmd.Flags().Lookup("payload-dir")
require.NotNil(t, flag, "payload-dir flag should be registered")
_, ok := flag.Annotations[cobra.BashCompSubdirsInDir]
assert.True(t, ok, "payload-dir flag should have directory annotation")
})

t.Run("env flag completion returns .env extension filter", func(t *testing.T) {
cmd := setupCmd(t)

completionFn, ok := cmd.GetFlagCompletionFunc("env")
require.True(t, ok, "env flag should have a completion function registered")

completions, directive := completionFn(cmd, nil, "")
assert.Equal(t, cobra.ShellCompDirectiveFilterFileExt, directive,
"env flag should use FilterFileExt directive for .env files")
assert.Equal(t, []string{"env"}, completions,
flag := cmd.Flags().Lookup("env")
require.NotNil(t, flag, "env flag should be registered")
exts, ok := flag.Annotations[cobra.BashCompFilenameExt]
require.True(t, ok, "env flag should have filename extension annotation")
assert.Equal(t, []string{"env"}, exts,
"env flag should complete with .env extension")
})

Expand Down
12 changes: 12 additions & 0 deletions internal/cmd/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ Local usage:
awmg proxy \
--guard-wasm guards/github-guard/github_guard.wasm \
--policy '{"allow-only":{"repos":["org/repo"],"min-integrity":"approved"}}' \
--listen localhost:8443 --tls`,
Example: ` # Run with auto-detected baked-in guard (container image)
awmg proxy --policy '{"allow-only":{"repos":["org/repo"],"min-integrity":"approved"}}'

# Run locally with explicit guard WASM and TLS
awmg proxy \
--guard-wasm guards/github-guard/github_guard.wasm \
--policy '{"allow-only":{"repos":["org/repo"]}}' \
--listen localhost:8443 --tls`,
SilenceUsage: true,
RunE: runProxy,
Expand All @@ -107,6 +115,10 @@ Local usage:
} else {
guardHelp += " (required)"
}
// Note: --listen and --log-dir are re-declared here (not inherited from rootCmd as
// persistent flags) because the proxy subcommand has different defaults and a distinct
// purpose: it runs as a standalone HTTPS forward proxy, not an MCP gateway. Keeping
// them independent avoids confusion and allows each command to evolve separately.
cmd.Flags().StringVar(&proxyGuardWasm, "guard-wasm", defaultGuard, guardHelp)
cmd.Flags().StringVar(&proxyPolicy, "policy", os.Getenv("MCP_GATEWAY_GUARD_POLICY_JSON"), "Guard policy JSON")
cmd.Flags().StringVar(&proxyToken, "github-token", "", "Fallback GitHub API token (default: forwards client Authorization header)")
Expand Down
20 changes: 17 additions & 3 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,19 @@ var (
)

var rootCmd = &cobra.Command{
Use: "awmg",
Short: "MCPG MCP proxy server",
Version: cliVersion,
Use: "awmg",
Short: "MCPG MCP proxy server",
Long: `MCPG is a proxy server for Model Context Protocol (MCP) servers.
It provides routing, aggregation, and management of multiple MCP backend servers.`,
Example: ` # Start in routed mode with a config file
awmg --config config.toml --routed

# Start in unified mode reading config from stdin
cat config.json | awmg --config-stdin --unified --listen 0.0.0.0:3000

# Run with debug logging
DEBUG=* awmg --config config.toml`,
Version: cliVersion,
Args: cobra.NoArgs,
SilenceUsage: true, // Don't show help on runtime errors
SilenceErrors: true, // Prevent cobra from printing errors — Execute() caller handles display
Expand All @@ -73,6 +81,12 @@ func init() {
// Register custom flag completions
registerFlagCompletions(rootCmd)

// Group subcommands for organized help output
rootCmd.AddGroup(
&cobra.Group{ID: "modes", Title: "Operation Modes:"},
&cobra.Group{ID: "utils", Title: "Utilities:"},
)

// Add completion command
rootCmd.AddCommand(newCompletionCmd())
}
Expand Down
Loading