Skip to content

A lightweight, annotation-based library for wrapping Go CLI applications (especially Cobra-based) as Model Context Protocol (MCP) servers.

Notifications You must be signed in to change notification settings

aleksadvaisly/mcp-go-wrapper

Repository files navigation

MCP Go Wrapper

A lightweight, annotation-based library for wrapping Go CLI applications (especially Cobra-based) as Model Context Protocol (MCP) servers.

Features

  • 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

Installation

go get github.com/aleksadvaisly/mcp-go-wrapper

Recommended: Use with AI Coding Agents

This library is designed to be easily integrated into existing CLI applications by AI coding agents. The documentation is structured to enable autonomous implementation.

For Users

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."

For AI Agents

After reading this README, you should be able to autonomously:

  1. Analyze the target CLI: Identify existing commands, their arguments, and business logic
  2. Create argument structs: Define typed structs with json, jsonschema, and validate tags for each command
  3. Implement handlers: Wrap existing command logic in Handler functions that accept typed arguments
  4. Add serve subcommand: Create a new serve command in the existing CLI that starts the MCP server (don't modify the main application)
  5. Register tools: Use wrapper.Register() or wrapper.RegisterCobra() to expose commands as MCP tools
  6. 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 stderr for 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.

Quick Start

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)
    }
}

Struct Tag Reference

JSON Tags (json:"...")

Standard Go JSON tags for field naming:

type Args struct {
    FieldName string `json:"field_name"`  // JSON key: "field_name"
}

JSON Schema Tags (jsonschema:"...")

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"`

Validation Tags (validate:"...")

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"`
}

API Documentation

Creating a Wrapper

func New(server *server.MCPServer) *Wrapper

Creates a new wrapper around an existing mcp-go server instance.

Registering Tools

Direct Registration

func (w *Wrapper) Register(
    name string,
    description string,
    argsType interface{},
    handler Handler,
) error

Register a tool with explicit name and description. The argsType should be an empty instance of your arguments struct.

Cobra Command Registration

func (w *Wrapper) RegisterCobra(
    cmd *cobra.Command,
    argsType interface{},
    handler Handler,
) error

Auto-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)

Handler Function

type Handler func(ctx context.Context, args interface{}) (interface{}, error)

Your handler receives:

  • ctx: Context for cancellation and deadlines
  • args: Pointer to your validated arguments struct

Return:

  • interface{}: Any JSON-serializable result
  • error: 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
}

Complete Example

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.go

Error Handling

The wrapper provides clear error messages for common issues:

Validation Errors

{
  "error": "validation failed: Name: is required; Format: must be one of: formal casual"
}

Handler Errors

{
  "error": "handler error: division by zero"
}

Schema Errors

Caught at registration time:

failed to build schema for tool xyz: argsType must be a struct, got string

Architecture

┌─────────────────┐
│   Your CLI      │
│   (Cobra)       │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  MCP Wrapper    │  ← Struct tags → Schema + Validation
│  (this library) │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│   mcp-go        │  ← MCP Protocol
│   (transport)   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  MCP Client     │
│  (Claude, etc)  │
└─────────────────┘

Integration Pattern: The serve Command

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

CRITICAL: stdout vs stderr

The MCP protocol uses stdio (stdin/stdout) for JSON-RPC communication. Any output to stdout will corrupt the protocol.

Rules

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 PROTOCOL

Why This Matters

MCP 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.

Design Principles

  1. Direct Invocation: Handlers are called directly as Go functions, not via subprocess
  2. Type Safety: Strong typing throughout with reflection only for schema generation
  3. Zero Magic: What you see is what you get - no hidden code generation
  4. Standard Tags: Uses familiar Go conventions (json, validate)
  5. Fail Fast: Validation happens before handler invocation
  6. Clear Errors: All errors include context for debugging

Use Cases

  • 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

Dependencies

Limitations

  • 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)

Future Improvements

  • Support for custom JSON Schema generators
  • Automatic flag extraction from Cobra commands
  • Streaming response support
  • Tool middleware/interceptors
  • Enhanced schema generation for complex types

License

MIT

Contributing

Contributions welcome! Please ensure:

  • Tests pass (go test ./...)
  • Code is formatted (go fmt)
  • Examples still work
  • Documentation is updated

Credits

Built on top of the excellent mcp-go library by Mark3 Labs.

About

A lightweight, annotation-based library for wrapping Go CLI applications (especially Cobra-based) as Model Context Protocol (MCP) servers.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages