Skip to main content

What is MCP?

MCP (Model Context Protocol) is a protocol developed by Anthropic that lets AI assistants interact with external tools and services. When you expose your service via MCP:
  1. AI can discover your tools: Claude can see all your methods and their parameters
  2. AI can call your tools: Claude can execute methods with proper inputs
  3. AI understands the results: Responses are formatted for AI interpretation
Think of it like giving an AI a β€œtoolbox” - each method in your service becomes a tool the AI can use.

Quick Start

import (
    "github.com/go-mizu/mizu"
    contract "github.com/go-mizu/mizu/contract/v2"
    "github.com/go-mizu/mizu/contract/v2/transport/mcp"

    "yourapp/todo"
)

// Your interface is defined in the todo package as todo.API:
// type API interface {
//     Create(ctx context.Context, in *CreateInput) (*Todo, error)
//     List(ctx context.Context) (*ListOutput, error)
//     Get(ctx context.Context, in *GetInput) (*Todo, error)
// }

// Create your service implementation
impl := todo.NewService()

// Register your service
svc := contract.Register[todo.API](impl,
    contract.WithDefaultResource("todos"),
)

// Create mizu app
app := mizu.New()

// Mount MCP endpoint
mcp.Mount(app.Router, "/mcp", svc)

// Start server
app.Listen(":8080")
Your service methods are now available as AI tools.

How Methods Become Tools

Each method becomes an MCP tool:
Interface MethodMCP Tool Name
Createtodos.create
Listtodos.list
Gettodos.get
Deletetodos.delete
Tool names follow the pattern resource.method.

The MCP Protocol

MCP uses JSON-RPC 2.0 as its transport. Here’s how an AI interacts with your server:

Step 1: Initialize

The AI establishes a connection:
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18"
    }
  }'
Response:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "tools": {"listChanged": false}
    },
    "serverInfo": {
      "name": "mizu-contract",
      "version": "0.1.0"
    }
  }
}

Step 2: Discover Tools

The AI asks what tools are available:
curl -X POST http://localhost:8080/mcp \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/list"
  }'
Response:
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "todos.create",
        "description": "Create a new todo",
        "inputSchema": {
          "type": "object",
          "properties": {
            "title": {"type": "string"}
          },
          "required": ["title"]
        }
      },
      {
        "name": "todos.list",
        "description": "List all todos",
        "inputSchema": {
          "type": "object",
          "properties": {}
        }
      }
    ]
  }
}

Step 3: Call a Tool

The AI calls a tool:
curl -X POST http://localhost:8080/mcp \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "todos.create",
      "arguments": {
        "title": "Buy groceries"
      }
    }
  }'
Success response:
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"id\":\"1\",\"title\":\"Buy groceries\",\"completed\":false}"
      }
    ],
    "isError": false
  }
}
Error response:
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "todo not found"
      }
    ],
    "isError": true
  }
}

Configuration Options

Customize your MCP server:
mcp.Mount(app.Router, "/mcp", svc,
    // Server information
    mcp.WithServerInfo(mcp.ServerInfo{
        Name:    "my-todo-api",
        Version: "1.0.0",
    }),

    // Instructions for the AI
    mcp.WithInstructions("Use these tools to manage todos. Be careful with delete operations."),
)

Available Options

OptionDescription
WithServerInfoSet server name and version
WithInstructionsProvide usage guidance to the AI

Complete Example

This example shows a complete MCP service using the recommended package-based organization:
// todo/types.go
package todo

type Todo struct {
    ID        string `json:"id"`
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

type CreateInput struct {
    Title string `json:"title"`
}

type GetInput struct {
    ID string `json:"id"`
}

type CompleteInput struct {
    ID string `json:"id"`
}

type ListOutput struct {
    Items []*Todo `json:"items"`
}
// todo/api.go
package todo

import "context"

// API defines the contract for todo operations
type API interface {
    Create(ctx context.Context, in *CreateInput) (*Todo, error)
    List(ctx context.Context) (*ListOutput, error)
    Get(ctx context.Context, in *GetInput) (*Todo, error)
    Complete(ctx context.Context, in *CompleteInput) (*Todo, error)
}
// todo/service.go
package todo

import (
    "context"
    "fmt"
    "sync"

    contract "github.com/go-mizu/mizu/contract/v2"
)

// Service implements todo.API
type Service struct {
    mu     sync.RWMutex
    todos  map[string]*Todo
    nextID int
}

// Compile-time check
var _ API = (*Service)(nil)

func NewService() *Service {
    return &Service{todos: make(map[string]*Todo)}
}

func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
    if in.Title == "" {
        return nil, contract.ErrInvalidArgument("title is required")
    }

    s.mu.Lock()
    defer s.mu.Unlock()

    s.nextID++
    todo := &Todo{
        ID:    fmt.Sprintf("%d", s.nextID),
        Title: in.Title,
    }
    s.todos[todo.ID] = todo
    return todo, nil
}

func (s *Service) List(ctx context.Context) (*ListOutput, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    items := make([]*Todo, 0, len(s.todos))
    for _, t := range s.todos {
        items = append(items, t)
    }
    return &ListOutput{Items: items}, nil
}

func (s *Service) Get(ctx context.Context, in *GetInput) (*Todo, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    todo, ok := s.todos[in.ID]
    if !ok {
        return nil, contract.ErrNotFound("todo not found")
    }
    return todo, nil
}

func (s *Service) Complete(ctx context.Context, in *CompleteInput) (*Todo, error) {
    s.mu.Lock()
    defer s.mu.Unlock()

    todo, ok := s.todos[in.ID]
    if !ok {
        return nil, contract.ErrNotFound("todo not found")
    }
    todo.Completed = true
    return todo, nil
}
// main.go
package main

import (
    "fmt"

    "github.com/go-mizu/mizu"
    contract "github.com/go-mizu/mizu/contract/v2"
    "github.com/go-mizu/mizu/contract/v2/transport/mcp"
    "github.com/go-mizu/mizu/contract/v2/transport/rest"

    "yourapp/todo"
)

func main() {
    impl := todo.NewService()

    svc := contract.Register[todo.API](impl,
        contract.WithName("Todo"),
        contract.WithDefaultResource("todos"),
    )

    app := mizu.New()

    // Mount MCP for AI assistants
    mcp.Mount(app.Router, "/mcp", svc,
        mcp.WithInstructions("Use these tools to manage a todo list. Available operations: create todos, list all todos, get a specific todo, and mark todos as complete."),
    )

    // Also mount REST for testing
    rest.Mount(app.Router, svc)

    fmt.Println("Server running on http://localhost:8080")
    fmt.Println("MCP endpoint: /mcp (for AI assistants)")
    fmt.Println("REST endpoint: /todos (for testing)")
    app.Listen(":8080")
}

Test with curl

# Initialize connection
curl -X POST http://localhost:8080/mcp \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}'

# List available tools
curl -X POST http://localhost:8080/mcp \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# Create a todo
curl -X POST http://localhost:8080/mcp \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"todos.create","arguments":{"title":"Buy milk"}}}'

# List todos
curl -X POST http://localhost:8080/mcp \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"todos.list","arguments":{}}}'

# Complete a todo
curl -X POST http://localhost:8080/mcp \
  -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"todos.complete","arguments":{"id":"1"}}}'

Using with Claude Desktop

Claude Desktop can connect to MCP servers:
  1. Start your server:
go run main.go
# Server running on http://localhost:8080
  1. Configure Claude Desktop to connect to http://localhost:8080/mcp
  2. Ask Claude:
    • β€œCreate a todo to buy groceries”
    • β€œShow me all my todos”
    • β€œMark the first todo as complete”
Claude will use your MCP tools to fulfill these requests.

Security

Input Validation

Always validate inputs - don’t trust AI-provided data:
func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
    if in.Title == "" {
        return nil, contract.ErrInvalidArgument("title is required")
    }
    if len(in.Title) > 500 {
        return nil, contract.ErrInvalidArgument("title too long")
    }
    // Proceed with validated input
}

Authentication

Add authentication with middleware:
func authMiddleware(next mizu.Handler) mizu.Handler {
    return func(c *mizu.Ctx) error {
        token := c.Get("Authorization")
        if !isValidToken(token) {
            return c.Status(401).SendString("unauthorized")
        }
        return next(c)
    }
}

app := mizu.New()
app.Use(authMiddleware)
mcp.Mount(app.Router, "/mcp", svc)

Error Handling

Method Errors

When your method returns an error, MCP wraps it with isError: true:
func (s *Service) Get(ctx context.Context, in *GetInput) (*Todo, error) {
    return nil, contract.ErrNotFound("todo not found")
}
Response:
{
  "result": {
    "content": [{"type": "text", "text": "todo not found"}],
    "isError": true
  }
}

Protocol Errors

Invalid requests return JSON-RPC errors:
{
  "error": {
    "code": -32601,
    "message": "Method not found"
  }
}

Multiple Services

Each service needs its own MCP endpoint:
todoSvc := contract.Register[todo.API](todoImpl, contract.WithDefaultResource("todos"))
userSvc := contract.Register[user.API](userImpl, contract.WithDefaultResource("users"))

mcp.Mount(app.Router, "/mcp/todos", todoSvc)
mcp.Mount(app.Router, "/mcp/users", userSvc)

Combining Transports

Use MCP alongside REST and JSON-RPC:
import (
    "github.com/go-mizu/mizu/contract/v2/transport/rest"
    "github.com/go-mizu/mizu/contract/v2/transport/jsonrpc"
    "github.com/go-mizu/mizu/contract/v2/transport/mcp"
)

app := mizu.New()

// REST for browsers and testing
rest.Mount(app.Router, svc)

// JSON-RPC for service-to-service
jsonrpc.Mount(app.Router, "/rpc", svc)

// MCP for AI assistants
mcp.Mount(app.Router, "/mcp", svc)

app.Listen(":8080")
Same business logic, multiple access methods.

Common Questions

How does the AI know what parameters to use?

The tools/list response includes inputSchema for each tool. This JSON Schema tells the AI what parameters are expected, their types, and which are required.

Can I add descriptions to tools?

Tool descriptions come from your method organization. Use clear method names and the WithInstructions option to guide the AI.

What if the AI sends invalid data?

Your method receives the data and should validate it. Return contract.ErrInvalidArgument for validation failures - this shows as an error to the AI.

Can I use MCP and REST together?

Yes, and it’s recommended. REST is great for testing and debugging while MCP serves AI assistants:
rest.Mount(app.Router, svc)     // /todos
mcp.Mount(app.Router, "/mcp", svc)  // /mcp

What’s Next?