Bind a Cobra CLI¶
A tool built on Cobra becomes an MCP server by mounting one command:
import (
mcpcli "gitlab.com/phpboyscout/go/mcp/cli"
mcpcobra "gitlab.com/phpboyscout/go/mcp/cobra"
)
root.AddCommand(mcpcli.Command(mcpcli.WithBinding(mcpcobra.WithExposure(isExposed))))
my-tool mcp start now serves every exposed command over stdio, my-tool mcp
tools exports the catalogue, and my-tool mcp vscode enable registers the
server with the editor. The command tree reference lists
the rest.
The remainder of this page is the long way, for a host that wants to own the
binding and the server itself. It is what cli.Command does inside.
Import the optional binding:
Bind once the whole tree is registered. Pass the exposure policy the application already has, and the logger:
binding, err := mcpcobra.Bind(root,
mcpcobra.WithExposure(isExposed),
mcpcobra.WithLogger(logger),
)
if err != nil {
return err // an unsupported flag type or a name collision, named precisely
}
Every runnable, unhidden command the policy accepts is now an operation named
<root>_<path>, with the command's flags as a flags object, positional
arguments as args, and stdout, stderr and the exit code as the result.
Register and host:
registry, err := mcp.New(binding.Operations(), mcp.WithPolicy(mcp.AllowRegistered()))
if err != nil {
return err
}
host, err := server.New(registry, server.WithIdentity("my-tool", version))
if err != nil {
return err
}
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = host.Shutdown(ctx)
_ = binding.Shutdown(ctx)
}()
return host.RunStdio(ctx)
Each invocation runs the command as a subprocess of the same binary, with the
host's environment and working directory, stdin closed, a five-minute
timeout and one MiB of retained output. One command runs at a time; a second
call gets a retryable busy failure while search and inspection stay
available.
Cancelling a call stops the command and everything it started. If that cannot
be confirmed within five seconds, binding.Status() says so until it can.
A command with a custom flag type needs a codec, or an exclusion:
mcpcobra.WithCodec("level", mcpcobra.TextCodec(json.RawMessage(`{"enum":["debug","info","warn"]}`)))
Read the Cobra binding reference for the filters, the codec table and the cleanup boundary.