cobrax

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 27 Imported by: 0

README

Project Logo

Transform any Cobra CLI into an MCP server

Ophis automatically converts your Cobra commands into MCP tools, and provides CLI commands for integration with Claude Desktop, VSCode, and Cursor.

Quick Start

Install

go get github.com/onexstack/cobrax

Add to your CLI

package main

import (
    "os"
    "github.com/onexstack/cobrax"
)

func main() {
    rootCmd := createMyRootCommand()
    rootCmd.AddCommand(cobrax.Command(nil))

    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

Enable in Claude Desktop, VSCode, or Cursor

# Claude Desktop
./my-cli mcp claude enable
# Restart Claude Desktop

# VSCode (requires Copilot in Agent Mode)
./my-cli mcp vscode enable

# Cursor
./my-cli mcp cursor enable

Your CLI commands are now available as MCP tools!

Stream over HTTP

Expose your MCP server over HTTP for remote access:

./my-cli mcp stream --host localhost --port 8080

Commands

The cobrax.Command(nil) adds these subcommands to your CLI (the default command name is mcp, configurable via Config.CommandName):

mcp
├── start            # Start MCP server on stdio
├── stream           # Stream MCP server over HTTP
├── tools            # Export available MCP tools as JSON
├── claude
│   ├── enable       # Add server to Claude Desktop config
│   ├── disable      # Remove server from Claude Desktop config
│   └── list         # List Claude Desktop MCP servers
├── vscode
│   ├── enable       # Add server to VSCode config
│   ├── disable      # Remove server from VSCode config
│   └── list         # List VSCode MCP servers
└── cursor
    ├── enable       # Add server to Cursor config
    ├── disable      # Remove server from Cursor config
    └── list         # List Cursor MCP servers

Configuration

Control which commands and flags are exposed as MCP tools using selectors. By default, all commands and flags are exposed (except hidden/deprecated).

config := &cobrax.Config{
    Selectors: []cobrax.Selector{
        {
            CmdSelector: cobrax.AllowCmdsContaining("get", "list"),
            LocalFlagSelector: cobrax.ExcludeFlags("token", "secret"),
            InheritedFlagSelector: cobrax.NoFlags,  // Exclude persistent flags

            // Middleware wraps command execution
            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)
            },
        },
    },
}

rootCmd.AddCommand(cobrax.Command(config))

Custom Command Name

By default the cobrax command is named mcp. If your CLI already uses mcp for something else, set CommandName to avoid the collision:

config := &cobrax.Config{
    CommandName: "agent",
}

rootCmd.AddCommand(cobrax.Command(config))

The command tree, editor config (enable/disable), and internal filters all use the configured name automatically.

Default Environment Variables

Editors launch MCP server subprocesses with a minimal environment. On macOS this means a PATH of just /usr/bin:/bin:/usr/sbin:/sbin, so tools like helm, kubectl, or docker installed via mise/homebrew/nix won't be found. Use DefaultEnv to capture the current PATH (or any other variables) at enable time:

config := &cobrax.Config{
    DefaultEnv: map[string]string{
        "PATH": os.Getenv("PATH"),
    },
}

rootCmd.AddCommand(cobrax.Command(config))

These are merged into the editor config written by enable. User-provided --env values take precedence on conflict.

See docs/config.md for detailed configuration options.

How It Works

Ophis bridges Cobra commands and the Model Context Protocol:

  1. Command Discovery: Recursively walks your Cobra command tree
  2. Schema Generation: Creates JSON schemas from command flags and arguments (docs/schema.md)
  3. Tool Execution: Spawns your CLI as a subprocess and captures output (docs/execution.md)

Contributing

Contributions welcome! See CONTRIBUTING.md.

Documentation

Overview

Package cobrax transforms Cobra CLI applications into MCP (Model Context Protocol) servers, enabling AI assistants to interact with command-line tools.

Ophis automatically converts existing Cobra commands into MCP tools, handling protocol complexity, command execution, and tool registration.

Two Execution Models

Ophis supports two execution models:

  1. Subprocess model (Command / Config): The MCP server re-invokes the same binary as a subprocess for each tool call. This works well when all necessary state can be reconstructed from CLI flags alone.

  2. In-process model (NewMCPServer / MCPServer): The MCP server calls the cobra.Command's Run/RunE function directly in-process. Because the command closures already hold references to runtime dependencies (API clients, database handles, business-logic objects, etc.), those objects are available without any additional wiring. This is the preferred model when commands depend on non-serialisable state.

In-Process Usage (NewMCPServer)

Provide a factory function that produces a fresh *cobra.Command tree on each call. The factory is invoked once at registration time (read-only, for schema generation) and once per tool invocation at runtime. Using a factory ensures that every tool call gets its own Options structs, flag values, and closure-captured variables — eliminating all shared-state hazards and making concurrent tool calls fully independent.

package main

import (
    "context"
    "log"
    "github.com/onexstack/cobrax"
)

func main() {
    // Dependencies are injected at construction time and shared safely
    // because they are read-only after initialisation.
    biz := newBotSreBiz(apiClient, dbClient)

    // Factory produces a fresh command tree per invocation.
    // Each call allocates new Options structs and closure state.
    factory := func() *cobra.Command {
        return buildRootCommand(biz) // adds all subcommands with biz in closures
    }

    srv, err := cobrax.NewMCPServer(cobrax.MCPOptions{
        Enabled: true,
        Addr:    ":8090",
        Name:    "myapp",
        Version: "1.0.0",
    }, factory)
    if err != nil {
        log.Fatal(err)
    }

    if err := srv.Start(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Subprocess Usage (Command)

Add MCP server management subcommands to an existing Cobra application:

package main

import (
    "os"
    "github.com/onexstack/cobrax"
)

func main() {
    rootCmd := createMyRootCommand()

    // Adds: mcp start, mcp tools, mcp claude enable/disable/list, etc.
    rootCmd.AddCommand(cobrax.Command(nil))

    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

Configuration (Subprocess model)

The Config struct provides fine-grained control over which commands and flags are exposed as MCP tools through a selector system.

Basic filters are always applied automatically:

  • Hidden and deprecated commands/flags are excluded
  • Commands without executable functions are excluded
  • Built-in commands (mcp, help, completion) are excluded

Example with selectors:

config := &cobrax.Config{
    Selectors: []cobrax.Selector{
        {
            CmdSelector:           cobrax.AllowCmdsContaining("get", "list"),
            LocalFlagSelector:     cobrax.AllowFlags("namespace", "output"),
            InheritedFlagSelector: cobrax.NoFlags,
        },
        {
            CmdSelector:           cobrax.AllowCmds("mycli delete"),
            LocalFlagSelector:     cobrax.ExcludeFlags("all", "force"),
            InheritedFlagSelector: cobrax.NoFlags,
        },
    },
    SloggerOptions: &slog.HandlerOptions{Level: slog.LevelDebug},
}

Index

Constants

View Source
const (
	// AnnotationTitle sets the human-readable title for the tool.
	AnnotationTitle = "title"

	// AnnotationReadOnly hints that the tool does not modify its environment.
	AnnotationReadOnly = "readOnlyHint"

	// AnnotationDestructive hints that the tool may perform destructive updates.
	// Only meaningful when ReadOnlyHint is false.
	AnnotationDestructive = "destructiveHint"

	// AnnotationIdempotent hints that calling the tool repeatedly with the same
	// arguments has no additional effect. Only meaningful when ReadOnlyHint is false.
	AnnotationIdempotent = "idempotentHint"

	// AnnotationOpenWorld hints that the tool may interact with external entities
	// outside its closed domain.
	AnnotationOpenWorld = "openWorldHint"

	// AnnotationMCPTool controls whether a command is registered as an MCP tool.
	// Set to "false" in cmd.Annotations to exclude the command from MCP tool
	// registration entirely. The check happens before any safety filters or
	// selectors are evaluated.
	//
	// Boolean values are parsed with strconv.ParseBool (accepts "1", "t", "true",
	// "0", "f", "false", etc.).
	//
	// When absent or set to "true", the command proceeds through normal
	// selector processing.
	//
	// Example:
	//
	//	cmd.Annotations = map[string]string{
	//	    cobrax.AnnotationMCPTool: "false",  // exclude this command from MCP
	//	}
	AnnotationMCPTool = "cobrax.mcp.tool"
)

Cobra command annotation keys for MCP tool annotations. Set these in cmd.Annotations to populate mcp.ToolAnnotation on the generated tool.

Boolean values are parsed with strconv.ParseBool (accepts "1", "t", "true", "0", "f", "false", etc.).

Example:

cmd.Annotations = map[string]string{
    cobrax.AnnotationReadOnly: "true",
    cobrax.AnnotationTitle:    "List files",
}
View Source
const AnnotationArgPrefix = "cobrax.arg."

Cobra command annotation keys for positional argument metadata. Set these in cmd.Annotations to provide semantic descriptions for each positional argument, which are then surfaced in the MCP Tool JSON Schema so that LLMs know exactly what to supply for each argument position.

Convention:

AnnotationArgPrefix + "0" = description for the first positional arg
AnnotationArgPrefix + "1" = description for the second positional arg
... and so on

Example:

cmd.Annotations = map[string]string{
    cobrax.AnnotationArgPrefix + "0": "The on-call module name (e.g. 'bke', 'kafka')",
    cobrax.AnnotationArgPrefix + "1": "Brief title describing the incident",
}

Variables

This section is empty.

Functions

func Command

func Command(config *Config) *cobra.Command

Command creates MCP server management commands for a Cobra CLI. Pass nil for default configuration or provide a Config for customization.

func NoFlags

func NoFlags(_ *pflag.Flag) bool

NoFlags is a FlagSelector that excludes all flags.

Types

type ArgSpec

type ArgSpec struct {
	// Name is the canonical field name used in the JSON Schema (e.g. "module", "title").
	// Derived from the bracket-stripped token in cmd.Use (e.g. "<module>" → "module").
	Name string

	// Description is a human-readable explanation of what this argument means.
	// Populated from the AnnotationArgPrefix+index annotation when available,
	// or synthesised from the usage pattern token otherwise.
	Description string

	// Required indicates whether this argument must be present.
	// true  → angle-bracket syntax  e.g. <name>
	// false → square-bracket syntax e.g. [name]
	Required bool

	// Variadic indicates this argument accepts one or more values.
	// true when the token ends with "..." (e.g. [targets...] or <files...>).
	Variadic bool

	// Index is the zero-based position of this argument in the CLI invocation.
	Index int
}

ArgSpec describes a single positional argument parsed from cmd.Use.

type CmdSelector

type CmdSelector func(*cobra.Command) bool

CmdSelector determines if a command should become an MCP tool. Return true to include the command as a tool. Note: Basic safety filters (hidden, deprecated, non-runnable) are always applied first. Commands are tested against selectors in order; the first matching selector wins.

func AllowCmds

func AllowCmds(cmds ...string) CmdSelector

AllowCmds creates a selector that only accepts commands whose path is listed. Example: AllowCmds("kubectl get", "helm list") includes only those exact commands.

func AllowCmdsContaining

func AllowCmdsContaining(substrings ...string) CmdSelector

AllowCmdsContaining creates a selector that only accepts commands whose path contains a listed phrase. Example: AllowCmdsContaining("get", "helm list") includes "kubectl get pods" and "helm list".

func ExcludeCmds

func ExcludeCmds(cmds ...string) CmdSelector

ExcludeCmds creates a selector that rejects commands whose path is listed. Example: ExcludeCmds("kubectl delete", "helm uninstall") excludes those exact commands.

func ExcludeCmdsContaining

func ExcludeCmdsContaining(substrings ...string) CmdSelector

ExcludeCmdsContaining creates a selector that rejects commands whose path contains any listed phrase. Example: ExcludeCmdsContaining("kubectl delete", "admin") excludes "kubectl delete" and "cli admin user".

type Config

type Config struct {
	// CommandName is the Use name for the top-level command returned by Command().
	// It is also used by GetCmdPath to locate the cobrax command in the Cobra tree
	// and by cmdFilter to exclude cobrax subcommands from tool exposure.
	// Default: "mcp".
	CommandName string

	// Selectors defines rules for converting commands to MCP tools.
	// Each selector specifies which commands to match and which flags to include.
	//
	// Basic safety filters are always applied first:
	//   - Hidden/deprecated commands and flags are excluded
	//   - Non-runnable commands are excluded
	//   - Built-in commands (the cobrax command, help, completion) are excluded
	//
	// Then selectors are evaluated in order for each command:
	//   1. The first selector whose CmdSelector returns true is used
	//   2. That selector's FlagSelector determines which flags are included
	//   3. If no selectors match, the command is not exposed as a tool
	//
	// If nil or empty, defaults to exposing all commands with all flags.
	Selectors []Selector

	// 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.
	//
	// A common use is to capture PATH so the MCP server subprocess can
	// find executables that live outside the system PATH:
	//
	//   &cobrax.Config{
	//       DefaultEnv: map[string]string{
	//           "PATH": os.Getenv("PATH"),
	//       },
	//   }
	//
	// If nil, no default environment variables are added (current behavior).
	DefaultEnv map[string]string

	// ToolNamePrefix replaces the root command name in tool names.
	// This is useful for shortening tool names to comply with API limits (e.g., Claude's 64 char limit).
	// For example, if root command is "omnistrate-ctl" and ToolNamePrefix is "omctl",
	// a command "omnistrate-ctl cost by-cell list" becomes "omctl_cost_by-cell_list" instead of
	// "omnistrate-ctl_cost_by-cell_list".
	// If empty, the root command name is used as-is.
	ToolNamePrefix string

	// SloggerOptions configures logging to stderr.
	// Default: Info level logging.
	SloggerOptions *slog.HandlerOptions

	// BaseURL is the base URL for the SSE server (e.g. "http://localhost:8080").
	// Required when using serveHTTP (the stream command).
	// If empty, defaults to "http://localhost:8080".
	BaseURL string
	// contains filtered or unexported fields
}

Config customizes MCP server behavior and command-to-tool conversion.

type ExecuteFunc

ExecuteFunc defines the function signature for executing a tool.

type FlagSelector

type FlagSelector func(*pflag.Flag) bool

FlagSelector determines if a flag should be included in an MCP tool. Return true to include the flag. Note: Hidden and deprecated flags are always excluded regardless of this selector. This selector is only applied to commands that match the associated CmdSelector.

func AllowFlags

func AllowFlags(names ...string) FlagSelector

AllowFlags creates a selector that only accepts flags whose name is listed. Example: AllowFlags("namespace", "output") includes only flags named "namespace" and "output".

func ExcludeFlags

func ExcludeFlags(names ...string) FlagSelector

ExcludeFlags creates a selector that rejects flags whose name is listed. Example: ExcludeFlags("color", "kubeconfig") excludes flags named "color" and "kubeconfig".

type MCPOptions

type MCPOptions struct {
	// Enabled controls whether the MCP server is started.
	// When false, Start returns immediately without binding any port.
	Enabled bool `json:"enabled" mapstructure:"enabled"`

	// Addr is the listen address for the MCP server (e.g. ":8090").
	// Required when Enabled is true.
	Addr string `json:"addr" mapstructure:"addr"`

	// Name is the MCP server name advertised to clients.
	Name string `json:"name" mapstructure:"name"`

	// Version is the MCP server version advertised to clients.
	Version string `json:"version" mapstructure:"version"`

	// BaseURL is the publicly reachable base URL for the SSE server
	// (e.g. "http://localhost:8090"). Used to construct the SSE endpoint
	// advertised to clients. If empty, it is derived from Addr.
	BaseURL string `json:"baseURL" mapstructure:"baseURL"`

	// Subprocess controls whether tools are executed in a separate OS process.
	// When true, each tool invocation re-invokes the current binary as a
	// subprocess via execSubprocess. The subprocess survives MCP server
	// termination, so long-running commands (e.g. project scaffolding) are
	// not killed when the MCP client times out and closes the transport.
	// Default is false (in-process execution via runInProcess).
	Subprocess bool `json:"subprocess" mapstructure:"subprocess"`
}

MCPOptions holds configuration for the MCP (Model Context Protocol) server. It is used with NewMCPServer to control how the server advertises itself and which network address it listens on.

type MCPServer

type MCPServer struct {
	// contains filtered or unexported fields
}

MCPServer is an MCP server that exposes Cobra commands as MCP tools.

Each tool invocation calls cmdFactory to produce a fresh *cobra.Command tree, which means every call gets its own Options structs, flag values, ctx fields, and closure-captured variables. No shared mutable state exists between concurrent or sequential tool calls, so no mutex or reset logic is required.

Example:

biz := newBotSreBiz(client, clientset, ...)

factory := func() *cobra.Command {
    root := &cobra.Command{Use: "myapp"}
    root.AddCommand(sre.NewOpenCmd(biz, ioStreams))
    return root
}

srv, err := cobrax.NewMCPServer(cobrax.MCPOptions{
    Enabled: true,
    Addr:    ":8090",
    Name:    "myapp",
    Version: "1.0.0",
}, factory)
if err != nil {
    log.Fatal(err)
}
if err := srv.Start(ctx); err != nil {
    log.Fatal(err)
}

func NewMCPServer

func NewMCPServer(opts MCPOptions, cmdFactory func() *cobra.Command, serverOpts ...ServerOption) (*MCPServer, error)

NewMCPServer constructs an MCPServer that exposes Cobra commands as MCP tools.

cmdFactory is called once immediately to obtain a reference command tree for tool schema registration (a read-only traversal). It is then called once per tool invocation at runtime to produce a fresh *cobra.Command tree, ensuring that every call gets its own Options structs, flag values, ctx fields, and closure-captured variables with no shared mutable state between calls.

cmdFactory must not be nil and must return a non-nil *cobra.Command each time it is called; NewMCPServer returns an error if either condition is violated.

opts.Enabled must be true for Start to actually bind a port; if false, Start is a no-op, which lets callers gate the MCP server behind a feature flag.

func (*MCPServer) EinoTools added in v0.3.0

func (s *MCPServer) EinoTools() ([]einotool.BaseTool, error)

EinoTools returns the registered MCP tools as a slice of github.com/cloudwego/eino/components/tool.BaseTool.

Each tool's InputSchema is converted from the MCP ToolInputSchema representation into an eino-compatible *jsonschema.Schema via JSON round-trip. The returned tools implement BaseTool (Info method only) and can be passed directly to an eino ChatModel via WithTools / BindTools.

func (*MCPServer) RawMCPServer

func (s *MCPServer) RawMCPServer() *mcpserver.MCPServer

RawMCPServer returns the underlying mcpserver.MCPServer instance.

func (*MCPServer) Start

func (s *MCPServer) Start(ctx context.Context) error

Start begins serving the MCP server over SSE on opts.Addr.

It blocks until the context is cancelled or an OS signal (SIGINT/SIGTERM) is received, at which point it performs a graceful shutdown. If opts.Enabled is false, Start returns nil immediately.

func (*MCPServer) Tools

func (s *MCPServer) Tools() []*mcp.Tool

Tools returns the list of MCP tools registered on this server. The slice is ordered leaf-first (depth-first traversal order). It can be used for introspection, testing, or generating tool manifests.

type MiddlewareFunc

MiddlewareFunc is middleware hook that runs after each tool call Common uses: error handling, response filtering, metrics collection.

type Selector

type Selector struct {
	// CmdSelector determines if this selector applies to a command.
	// If nil, accepts all commands that pass basic safety filters.
	// Cannot be used to bypass safety filters (hidden, deprecated, non-runnable).
	CmdSelector CmdSelector

	// LocalFlagSelector determines which flags to include for commands matched by CmdSelector.
	// If nil, includes all flags that pass basic safety filters.
	// Cannot be used to bypass safety filters (hidden, deprecated flags).
	LocalFlagSelector FlagSelector

	// InheritedFlagSelector determines which persistent flags to include for commands matched by CmdSelector.
	// If nil, includes all flags that pass basic safety filters.
	// Cannot be used to bypass safety filters (hidden, deprecated flags).
	InheritedFlagSelector FlagSelector

	// Middleware is an optional middleware hook that wraps around tool execution.
	// Common uses: error handling, response filtering, metrics collection.
	// If nil, no middleware is applied.
	Middleware MiddlewareFunc
}

Selector contains selectors for filtering commands and flags. When multiple selectors are configured, they are evaluated in order. The first selector whose CmdSelector matches a command is used, and its FlagSelector determines which flags are included for that command.

Basic safety filters are always applied automatically:

  • Hidden/deprecated commands and flags are excluded
  • Non-runnable commands are excluded
  • Built-in commands (mcp, help, completion) are excluded

This allows fine-grained control within safe boundaries, such as:

  • Exposing different flags for different command groups
  • Applying stricter flag filtering to dangerous commands
  • Having a default catch-all selector with common flag exclusions

type ServerOption

type ServerOption func(*MCPServer)

ServerOption is a functional option for configuring the MCPServer.

func WithSelectors

func WithSelectors(selectors ...Selector) ServerOption

WithSelectors sets the selector rules used during tool registration. If provided, it overrides the default catch-all Selector{}.

type ToolInput

type ToolInput struct {
	// FlatInput is the complete flat parameter map received from the MCP client.
	// Both flag values and positional argument values live here, keyed by their
	// property name (e.g. "output", "verbose", "resource").
	FlatInput map[string]any

	// FlagNames is the set of property names that correspond to cobra flags.
	// Populated from the command's flag set at registration time.
	FlagNames map[string]struct{}

	// ArgNames is the ordered list of positional argument names, in the order
	// they appear in cmd.Use.  Ordering matters for positional args.
	ArgNames []string
}

ToolInput represents the decoded input for a tool invocation.

After the schema format change, MCP clients send all parameters (both flags and positional arguments) as flat top-level properties in the JSON object. The FlatInput field holds the complete flat map as received from the MCP client. The FlagNames and ArgNames sets are populated during tool registration and are used at execution time to split the flat map back into flag arguments and positional arguments for the underlying cobra command.

type ToolOutput

type ToolOutput struct {
	StdOut   string `json:"stdout,omitempty" jsonschema:"Standard output"`
	StdErr   string `json:"stderr,omitempty" jsonschema:"Standard error"`
	ExitCode int    `json:"exitCode" jsonschema:"Exit code"`
}

ToolOutput represents the output structure for command tools.

Directories

Path Synopsis
internal
bridge/flags
Package flags provides JSON schema generation from Cobra command flags.
Package flags provides JSON schema generation from Cobra command flags.
cfgmgr/cmd/claude
Package claude provides CLI commands for managing Claude Desktop MCP servers.
Package claude provides CLI commands for managing Claude Desktop MCP servers.
cfgmgr/cmd/cursor
Package cursor provides CLI commands for managing Cursor MCP servers.
Package cursor provides CLI commands for managing Cursor MCP servers.
cfgmgr/cmd/vscode
Package vscode provides CLI commands for managing VSCode MCP servers.
Package vscode provides CLI commands for managing VSCode MCP servers.
cfgmgr/manager
Package manager provides configuration management for MCP servers across platforms.
Package manager provides configuration management for MCP servers across platforms.
cfgmgr/manager/claude
Package claude provides configuration management for Claude Desktop MCP servers.
Package claude provides configuration management for Claude Desktop MCP servers.
cfgmgr/manager/cursor
Package cursor provides configuration management for Cursor MCP servers.
Package cursor provides configuration management for Cursor MCP servers.
cfgmgr/manager/vscode
Package vscode provides configuration management for VSCode MCP servers.
Package vscode provides configuration management for VSCode MCP servers.
schema
Package schema provides JSON schema caching utilities.
Package schema provides JSON schema caching utilities.
Package test provides integration testing helper functions for cobrax It assumes cobrax.Command is a root level subcommand
Package test provides integration testing helper functions for cobrax It assumes cobrax.Command is a root level subcommand

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL