Selectors control which commands and flags become MCP tools. Ophis evaluates selectors in order and uses the first matching selector for each command. If no selectors match a command, the command is not exposed as a MCP tool.
- If
Config.Selectorsis nil/empty, all commands and flags are exposed - If
Config.DefaultEnvis nil, no default environment variables are added to editor configs - If
CmdSelectoris nil, the selector matches all commands - If
LocalFlagSelectororInheritedFlagSelectoris nil, all flags are included
Always filtered regardless of configuration:
- Hidden and deprecated commands/flags
- Non-runnable commands (no Run/RunE)
- Built-in commands (the cobrax command, help, completion). The cobrax command name defaults to "mcp" but can be changed via Config.CommandName
Editors like Claude Desktop, VSCode, and Cursor launch MCP server subprocesses with a minimal environment. On macOS this typically means a PATH of just /usr/bin:/bin:/usr/sbin:/sbin, which cannot find executables managed by mise, asdf, homebrew, nix, or installed to non-standard paths.
DefaultEnv specifies environment variables that are automatically included when enable writes a server config for any editor. These are merged with user-provided --env values; user values take precedence on conflict.
The most common use is to capture the current shell's PATH so the MCP server subprocess can find tools like helm, kubectl, docker, terraform, etc.:
config := &cobrax.Config{
DefaultEnv: map[string]string{
"PATH": os.Getenv("PATH"),
},
}config := &cobrax.Config{
DefaultEnv: map[string]string{
"PATH": os.Getenv("PATH"),
"KUBECONFIG": os.Getenv("KUBECONFIG"),
"HOME": os.Getenv("HOME"),
},
}User-provided --env values always take precedence over DefaultEnv:
# Uses DefaultEnv PATH
./my-cli mcp claude enable
# Overrides DefaultEnv PATH with user value, keeps other DefaultEnv vars
./my-cli mcp claude enable --env PATH=/custom/pathconfig := &cobrax.Config{
Selectors: []cobrax.Selector{
{
CmdSelector: cobrax.AllowCmds("kubectl get", "kubectl describe"),
},
},
}config := &cobrax.Config{
Selectors: []cobrax.Selector{
{
LocalFlagSelector: cobrax.ExcludeFlags("token", "password"),
InheritedFlagSelector: cobrax.NoFlags,
},
},
}config := &cobrax.Config{
Selectors: []cobrax.Selector{
{
// Read ops: timeout
CmdSelector: cobrax.AllowCmdsContaining("get", "list"),
Middleware: func(ctx context.Context, req *mcp.CallToolRequest, in cobrax.ToolInput, next func(context.Context, *mcp.CallToolRequest, cobrax.ToolInput) (*mcp.CallToolResult, cobrax.ToolOutput, error)) (*mcp.CallToolResult, cobrax.ToolOutput, error) {
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
return next(ctx, req, in)
},
},
{
// Write ops: restrict flags
CmdSelector: cobrax.AllowCmdsContaining("delete", "apply"),
LocalFlagSelector: cobrax.ExcludeFlags("force", "all"),
},
},
}AllowCmds(cmds ...string)- Exact matchesExcludeCmds(cmds ...string)- Exact exclusionsAllowCmdsContaining(substrings ...string)- Contains anyExcludeCmdsContaining(substrings ...string)- Excludes all
AllowFlags(names ...string)- Include only theseExcludeFlags(names ...string)- Exclude theseNoFlags- Exclude all
config := &cobrax.Config{
Selectors: []cobrax.Selector{
{
CmdSelector: func(cmd *cobra.Command) bool {
// Only expose commands that have been annotated as "mcp"
return cmd.Annotations["mcp"] == "true"
},
LocalFlagSelector: func(flag *pflag.Flag) bool {
return flag.Annotations["mcp"] == "true"
},
InheritedFlagSelector: func(flag *pflag.Flag) bool {
return flag.Annotations["mcp"] == "true"
},
},
},
}Wrap execution with custom logic:
Middleware: func(ctx context.Context, req *mcp.CallToolRequest, in cobrax.ToolInput, next func(context.Context, *mcp.CallToolRequest, cobrax.ToolInput) (*mcp.CallToolResult, cobrax.ToolOutput, error)) (*mcp.CallToolResult, cobrax.ToolOutput, error) {
// Pre-execution: Add timeout
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
// Pre-execution: Validate input
if len(in.Args) > 10 {
in.Args = in.Args[:10]
}
// Execute the command
res, out, err := next(ctx, req, in)
// Post-execution: Filter output
if strings.Contains(out.StdOut, "SECRET") {
out.StdOut = "[REDACTED]"
}
return res, out, err
}Set MCP tool annotations on Cobra commands using cmd.Annotations. These hints help AI clients make informed decisions about tool invocation.
| Key | Type | Description |
|---|---|---|
cobrax.AnnotationTitle ("title") |
string | Human-readable title for the tool |
cobrax.AnnotationReadOnly ("readOnlyHint") |
bool | Tool does not modify its environment |
cobrax.AnnotationDestructive ("destructiveHint") |
bool | Tool may perform destructive updates |
cobrax.AnnotationIdempotent ("idempotentHint") |
bool | Repeated calls have no additional effect |
cobrax.AnnotationOpenWorld ("openWorldHint") |
bool | Tool may interact with external entities |
Boolean values are parsed with strconv.ParseBool ("true", "1", "t", "false", "0", "f", etc.). Invalid values are skipped with a warning.
listCmd := &cobra.Command{
Use: "list",
Short: "List all resources",
Annotations: map[string]string{
cobrax.AnnotationTitle: "List Resources",
cobrax.AnnotationReadOnly: "true",
},
}
deleteCmd := &cobra.Command{
Use: "delete [id]",
Short: "Delete a resource",
Annotations: map[string]string{
cobrax.AnnotationTitle: "Delete Resource",
cobrax.AnnotationDestructive: "true",
cobrax.AnnotationIdempotent: "true",
cobrax.AnnotationOpenWorld: "false",
},
}config := &cobrax.Config{
SloggerOptions: &slog.HandlerOptions{
Level: slog.LevelDebug,
},
}Or via CLI:
./my-cli mcp vscode enable --log-level debug