Transform any Cobra CLI into an MCP server
Ophis automatically converts your existing Cobra commands into MCP tools, and provides CLI commands for integration with Claude and VSCode.
go get github.com/njayp/ophisMCP commands can be added anywhere in a command tree. Below is an example of a main() that adds MCP commands to a root command. Alternatively, this logic can be placed in your createMyRootCommand().
package main
import (
"os"
"github.com/njayp/ophis"
)
func main() {
rootCmd := createMyRootCommand()
// Add MCP server commands
rootCmd.AddCommand(ophis.Command(nil))
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}./my-cli mcp claude enable
# Restart Claude Desktop./my-cli mcp vscode enable
# Ensure Copilot is in Agent ModeYour CLI commands are now available as mcp server tools!
The ophis.Command() function accepts an optional *ophis.Config parameter to customize the MCP server behavior:
config := &ophis.Config{
// Customize tool creation and execution
Selectors: []ophis.Selector{
{
CmdSelector: ophis.ExcludeCmd("dangerous"),
PreRun: func(ctx context.Context, req *mcp.CallToolRequest, in bridge.CmdToolInput) (context.Context, *mcp.CallToolRequest, bridge.CmdToolInput) {
// Add timeout
ctx, _ = context.WithTimeout(ctx, time.Minute)
return ctx, req, in
},
},
},
// Configure logging (logs to stderr)
SloggerOptions: &slog.HandlerOptions{
Level: slog.LevelDebug,
},
}Selectors control which commands become MCP tools and which flags they include.
// Default: expose all commands with all their flags
config := &ophis.Config{}// Basic selection
config := &ophis.Config{
Selectors: []ophis.Selector{
{
// Only these commands
CmdSelector: ophis.AllowCmd("get", "helm repo list"),
// Without this flag
FlagSelector: ophis.ExcludeFlag("kubeconfig"),
},
},
}config := &ophis.Config{
Selectors: []ophis.Selector{
{
// Only these commands, with all flags
CmdSelector: ophis.AllowCmd("get", "helm repo list"),
},
},
}config := &ophis.Config{
Selectors: []ophis.Selector{
{
// All commands, without this flag
FlagSelector: ophis.ExcludeFlag("kubeconfig"),
},
},
}- Safety first: Hidden, deprecated, and non-runnable commands/flags are always excluded
- First match wins: Selectors are evaluated in order; the first matching
CmdSelectordetermines whichFlagSelectoris used - No match = no tool: Commands that don't match any selector are not exposed
Each selector can include middleware hooks that run before and after tool execution. Different selectors can specify different PreRun and PostRun functions for different commands.
config := &ophis.Config{
Selectors: []ophis.Selector{
{
// PreRun executes before each tool call
// Return a cancelled context to prevent execution
PreRun: func(ctx context.Context, req *mcp.CallToolRequest, in bridge.CmdToolInput) (context.Context, *mcp.CallToolRequest, bridge.CmdToolInput) {
// Add timeout
ctx, _ = context.WithTimeout(ctx, time.Minute)
return ctx, req, in
},
// PostRun executes after each tool call
PostRun: func(ctx context.Context, req *mcp.CallToolRequest, in bridge.CmdToolInput, res *mcp.CallToolResult, out bridge.CmdToolOutput, err error) (*mcp.CallToolResult, bridge.CmdToolOutput, error) {
// Your middleware here
return res, out, err
},
},
},
}Common use cases for middleware:
- PreRun: Add timeouts, rate limiting, authentication checks, request logging
- PostRun: Error handling, response filtering, metrics collection, output sanitization
Different commands can have different flag rules and execution hooks:
config := &ophis.Config{
Selectors: []ophis.Selector{
{
// Read operations: only output flags
CmdSelector: ophis.AllowCmd("get", "list", "logs"),
FlagSelector: ophis.AllowFlag("output", "format"),
PreRun: timeoutFn(),
PostRun: limitOutputFn(),
},
{
// Write operations: exclude dangerous flags
CmdSelector: ophis.AllowCmd("create", "apply"),
FlagSelector: ophis.ExcludeFlag("force", "token", "insecure"),
PreRun: timeoutFn(),
},
{
// Everything else: with common flag exclusions
FlagSelector: ophis.ExcludeFlag("token", "insecure"),
PreRun: timeoutFn(),
},
},
}For complex logic, use custom functions:
ophis.Selector{
// Match commands based on custom logic
CmdSelector: func(cmd *cobra.Command) bool {
// Only expose commands that have been annotated as "mcp"
return cmd.Annotations["mcp"] == "true"
},
FlagSelector: func(flag *pflag.Flag) bool {
return flag.Annotations["mcp"] == "true"
},
}ophis.Command returns the following commands:
mcp
├── start # Start MCP server on stdio
├── tools # Export available MCP tools as JSON
├── claude
│ ├── enable # Enable Helm MCP in Claude Desktop
│ ├── disable # Disable Helm MCP in Claude Desktop
│ └── list # List MCP configurations in Claude Desktop
└── vscode
├── enable # Enable Helm MCP in VS Code
├── disable # Disable Helm MCP in VS Code
└── list # List MCP configurations in VS Code
Run make build to build all examples to ophis/bin.
Contributions welcome! See CONTRIBUTING.md