A lightweight, annotation-based library for wrapping Go CLI applications (especially Cobra-based) as Model Context Protocol (MCP) servers.
- Struct Tag Support: Define MCP tool schemas using standard Go struct tags (
json,jsonschema,validate) - Automatic Validation: Built-in argument validation using
go-playground/validator - Cobra Integration: Auto-register Cobra commands as MCP tools
- Type Safety: Direct function invocation with typed structs (no subprocess overhead)
- Clean API: Simple, idiomatic Go interface
- Lightweight: < 500 lines of core code
go get github.com/aleksadvaisly/mcp-go-wrapperThis library is designed to be easily integrated into existing CLI applications by AI coding agents. The documentation is structured to enable autonomous implementation.
If you have an existing CLI application and want to add MCP support, we recommend delegating the implementation to an AI coding agent:
"Please read the README.md from github.com/aleksadvaisly/mcp-go-wrapper and integrate MCP
support into my CLI application. Register all available commands as MCP tools with proper
argument schemas and validation."
After reading this README, you should be able to autonomously:
- Analyze the target CLI: Identify existing commands, their arguments, and business logic
- Create argument structs: Define typed structs with
json,jsonschema, andvalidatetags for each command - Implement handlers: Wrap existing command logic in
Handlerfunctions that accept typed arguments - Add
servesubcommand: Create a newservecommand in the existing CLI that starts the MCP server (don't modify the main application) - Register tools: Use
wrapper.Register()orwrapper.RegisterCobra()to expose commands as MCP tools - Setup server: Initialize the MCP server with instructions and configure stdio transport
CRITICAL: Server Instructions
- ALWAYS add server.WithInstructions() when creating the MCP server
- The instructions should describe the PURPOSE and CAPABILITIES of the server
- Without instructions, AI agents won't understand what the server is for or when to use its tools
- Instructions appear in the MCP initialize response and help clients discover your server's capabilities
CRITICAL: Logging and Output
- NEVER use
stdout(fmt.Print, fmt.Println, log.SetOutput(os.Stdout), etc.) - stdout is reserved for MCP protocol communication - ALWAYS use
stderrfor all logging, debug output, and informational messages:log.SetOutput(os.Stderr) - Using stdout will break the MCP protocol and cause JSON parsing errors in clients
The API is intentionally minimal and follows Go idioms. All required interfaces are documented in the "API Documentation" section below.
package main
import (
"context"
"fmt"
"log"
mcpwrapper "github.com/aleksadvaisly/mcp-go-wrapper"
"github.com/mark3labs/mcp-go/server"
)
type GreetArgs struct {
Name string `json:"name"
jsonschema:"required,description=Name to greet"
validate:"required,min=1"`
Format string `json:"format"
jsonschema:"enum=formal,enum=casual,description=Greeting style"
validate:"omitempty,oneof=formal casual"`
}
type GreetResult struct {
Message string `json:"message"`
}
func main() {
// CRITICAL: Set log output to stderr (stdout is reserved for MCP protocol)
log.SetOutput(os.Stderr)
mcpServer := server.NewMCPServer(
"my-app",
"1.0.0",
server.WithInstructions("A greeting service that provides personalized greetings in various formats. Helps language models generate appropriate greetings for different contexts."),
)
wrapper := mcpwrapper.New(mcpServer)
wrapper.Register(
"greet",
"Greet someone by name",
GreetArgs{},
func(ctx context.Context, args interface{}) (interface{}, error) {
a := args.(*GreetArgs)
message := fmt.Sprintf("Hey %s!", a.Name)
if a.Format == "formal" {
message = fmt.Sprintf("Good day, %s", a.Name)
}
return &GreetResult{Message: message}, nil
},
)
log.Println("Starting MCP server...")
if err := server.ServeStdio(mcpServer); err != nil {
log.Fatalf("Server error: %v", err)
}
}Standard Go JSON tags for field naming:
type Args struct {
FieldName string `json:"field_name"` // JSON key: "field_name"
}Define MCP tool input schema:
| Tag | Description | Example |
|---|---|---|
required |
Mark field as required | jsonschema:"required" |
description=<text> |
Field description | jsonschema:"description=User's email address" |
enum=<value> |
Allowed values (repeat for multiple) | jsonschema:"enum=small,enum=medium,enum=large" |
minimum=<num> |
Minimum numeric value | jsonschema:"minimum=0" |
maximum=<num> |
Maximum numeric value | jsonschema:"maximum=100" |
minLength=<num> |
Minimum string length | jsonschema:"minLength=3" |
maxLength=<num> |
Maximum string length | jsonschema:"maxLength=50" |
Multiple tags can be combined with commas:
Age int `json:"age" jsonschema:"required,minimum=0,maximum=120,description=User age in years"`Runtime validation using go-playground/validator:
| Tag | Description | Example |
|---|---|---|
required |
Field cannot be zero value | validate:"required" |
min=<n> |
Minimum length/value | validate:"min=3" |
max=<n> |
Maximum length/value | validate:"max=50" |
email |
Valid email format | validate:"email" |
url |
Valid URL format | validate:"url" |
oneof=<vals> |
Value must be one of list | validate:"oneof=red blue green" |
gte=<n> |
Greater than or equal | validate:"gte=0" |
lte=<n> |
Less than or equal | validate:"lte=100" |
omitempty |
Skip validation if empty | validate:"omitempty,email" |
Example combining all three tag types:
type CreateUserArgs struct {
Email string `json:"email"
jsonschema:"required,description=User email address"
validate:"required,email"`
Age int `json:"age"
jsonschema:"required,minimum=0,maximum=120,description=User age"
validate:"required,gte=0,lte=120"`
Role string `json:"role"
jsonschema:"enum=admin,enum=user,enum=guest,description=User role"
validate:"required,oneof=admin user guest"`
Nickname string `json:"nickname"
jsonschema:"description=Optional display name"
validate:"omitempty,min=3,max=20"`
}func New(server *server.MCPServer) *WrapperCreates a new wrapper around an existing mcp-go server instance.
func (w *Wrapper) Register(
name string,
description string,
argsType interface{},
handler Handler,
) errorRegister a tool with explicit name and description. The argsType should be an empty instance of your arguments struct.
func (w *Wrapper) RegisterCobra(
cmd *cobra.Command,
argsType interface{},
handler Handler,
) errorAuto-register from a Cobra command. Extracts name from cmd.Use and description from cmd.Short or cmd.Long.
Example:
greetCmd := &cobra.Command{
Use: "greet",
Short: "Greet someone by name",
}
wrapper.RegisterCobra(greetCmd, GreetArgs{}, greetHandler)type Handler func(ctx context.Context, args interface{}) (interface{}, error)Your handler receives:
ctx: Context for cancellation and deadlinesargs: Pointer to your validated arguments struct
Return:
interface{}: Any JSON-serializable resulterror: Error if operation failed
Example:
func myHandler(ctx context.Context, args interface{}) (interface{}, error) {
a := args.(*MyArgs)
// Your logic here
result := processData(a.Field1, a.Field2)
return &MyResult{Output: result}, nil
}See examples/simple/main.go for a working example with multiple tools demonstrating:
- Basic tool registration
- Validation rules
- Enum handling
- Error handling
- Cobra command integration
To run the example:
cd examples/simple
go run main.goThe wrapper provides clear error messages for common issues:
{
"error": "validation failed: Name: is required; Format: must be one of: formal casual"
}{
"error": "handler error: division by zero"
}Caught at registration time:
failed to build schema for tool xyz: argsType must be a struct, got string
┌─────────────────┐
│ Your CLI │
│ (Cobra) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ MCP Wrapper │ ← Struct tags → Schema + Validation
│ (this library) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ mcp-go │ ← MCP Protocol
│ (transport) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ MCP Client │
│ (Claude, etc) │
└─────────────────┘
When adding MCP support to an existing CLI application, create a new serve subcommand instead of modifying the main application:
// cmd/serve.go
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Start MCP server",
Run: func(cmd *cobra.Command, args []string) {
log.SetOutput(os.Stderr) // CRITICAL: use stderr for logs
mcpServer := server.NewMCPServer(
"my-app",
"1.0.0",
server.WithInstructions("Describe what your MCP server does and what capabilities it provides to language models. This helps AI agents understand when and how to use your tools."),
)
wrapper := mcpwrapper.New(mcpServer)
// Register all your commands as MCP tools
wrapper.RegisterCobra(myCmd, MyArgs{}, myHandler)
if err := server.ServeStdio(mcpServer); err != nil {
log.Fatal(err)
}
},
}This approach keeps your CLI application working normally while adding MCP capability:
./my-app command- runs as regular CLI./my-app serve- starts MCP server for AI integration
The MCP protocol uses stdio (stdin/stdout) for JSON-RPC communication. Any output to stdout will corrupt the protocol.
✅ DO: Use stderr for all logging and debug output
log.SetOutput(os.Stderr) // Configure logger
fmt.Fprintln(os.Stderr, "message") // Direct stderr writes❌ DON'T: Use stdout for anything
fmt.Println("message") // BREAKS PROTOCOL
log.Println("message") // BREAKS PROTOCOL (if not configured)
fmt.Printf("debug: %v", x) // BREAKS PROTOCOLMCP clients expect valid JSON-RPC messages on stdout:
{"jsonrpc":"2.0","id":1,"method":"tools/list"}If you write logs to stdout:
Starting server...
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
The client will fail to parse the message and the connection breaks.
- Direct Invocation: Handlers are called directly as Go functions, not via subprocess
- Type Safety: Strong typing throughout with reflection only for schema generation
- Zero Magic: What you see is what you get - no hidden code generation
- Standard Tags: Uses familiar Go conventions (json, validate)
- Fail Fast: Validation happens before handler invocation
- Clear Errors: All errors include context for debugging
- CLI to API: Expose your Cobra CLI as an MCP server for LLM integration
- Development Tools: Make dev tools accessible to AI assistants
- Automation: Bridge command-line utilities with AI workflows
- Testing: Validate tool schemas and argument handling
- mark3labs/mcp-go - MCP protocol implementation
- go-playground/validator - Struct validation
- spf13/cobra - CLI framework (optional, for
RegisterCobra)
- JSON Schema generation is basic (covers common types)
- Complex nested structs may require manual schema definition
- Validation errors are formatted for clarity, not strict JSON Schema compliance
- Cobra integration doesn't automatically extract flag definitions (use struct tags instead)
- Support for custom JSON Schema generators
- Automatic flag extraction from Cobra commands
- Streaming response support
- Tool middleware/interceptors
- Enhanced schema generation for complex types
MIT
Contributions welcome! Please ensure:
- Tests pass (
go test ./...) - Code is formatted (
go fmt) - Examples still work
- Documentation is updated
Built on top of the excellent mcp-go library by Mark3 Labs.