Documentation Index
Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
Use this file to discover all available pages before exploring further.
When you build an API with Contract, you write your business logic once. But different clients need to talk to your API in different ways. That’s what transports are for - they let the same code speak multiple languages.
What Is a Transport?
Think of a transport like a translator at the United Nations. The speaker (your service) gives the same message, but each translator converts it into a language their audience understands.
In Contract terms:
- Your service is the speaker - it has the business logic
- Transports are the translators - they convert requests/responses
- Clients are the audience - browsers, apps, AI assistants, etc.
┌──────────────────────────────────────────────────────────────────┐
│ Your Service │
│ (Written once, in Go) │
└──────────────────────────────────────────────────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ REST │ │ JSON-RPC │ │ MCP │
│ (Familiar) │ │ (Powerful) │ │ (AI-Ready) │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
Web Browsers Backend Services AI Assistants
Mobile Apps Microservices Claude Desktop
Available Transports
Contract supports three transports plus documentation generation:
| Transport | What It Is | Best For |
|---|
| REST | Traditional HTTP API | Web apps, mobile apps, curl |
| JSON-RPC | Remote procedure calls over HTTP | Batch operations, microservices |
| MCP | Model Context Protocol | AI assistants like Claude |
| OpenAPI | API documentation standard | Docs, code generation |
Decision Guide: Which Transport Should I Use?
Use this flowchart to pick the right transport for your use case:
Start Here
│
▼
Who is calling your API?
│
├─► Web browsers, mobile apps, or curl
│ → Use REST (most familiar)
│
├─► Other backend services
│ → Use JSON-RPC (supports batching)
│
├─► AI assistants (Claude, etc.)
│ → Use MCP (designed for AI)
│
└─► Need API documentation
→ Add OpenAPI (generates docs)
Pro tip: You can (and should!) use multiple transports at once. They don’t conflict.
Detailed Comparison
Feature Comparison
| Feature | REST | JSON-RPC | MCP |
|---|
| HTTP Methods | GET/POST/PUT/DELETE | POST only | POST only |
| Batch Requests | No | Yes | No |
| Cacheable | Yes (GET requests) | No | No |
| Self-Documenting | Via OpenAPI | No | Via tools/list |
| Error Format | HTTP status codes | JSON-RPC errors | isError flag |
REST - The Classic Choice
REST is the most widely known API style. It uses HTTP methods to represent actions:
# Create a todo (POST = create new resource)
curl -X POST http://localhost:8080/todos \
-H "Content-Type: application/json" \
-d '{"title": "Buy milk"}'
# List all todos (GET = retrieve data)
curl http://localhost:8080/todos
# Get one todo (GET with ID)
curl http://localhost:8080/todos/123
# Update a todo (PUT = replace resource)
curl -X PUT http://localhost:8080/todos/123 \
-d '{"title": "Buy milk", "completed": true}'
# Delete a todo (DELETE = remove resource)
curl -X DELETE http://localhost:8080/todos/123
When to use REST:
- Your clients are web browsers or mobile apps
- You want caching (browsers cache GET requests)
- Your team is familiar with REST APIs
- You’re building a public API
Pros: Familiar, cacheable, works everywhere
Cons: No batching, multiple round trips for complex operations
JSON-RPC - The Power User’s Choice
JSON-RPC is a simple protocol where you explicitly name the method to call. All requests use POST.
# Call a method
curl -X POST http://localhost:8080/rpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "todos.create",
"params": {"title": "Buy milk"}
}'
# Response
{"jsonrpc": "2.0", "id": 1, "result": {"id": "1", "title": "Buy milk"}}
The killer feature is batching - send multiple operations in one request:
curl -X POST http://localhost:8080/rpc \
-H "Content-Type: application/json" \
-d '[
{"jsonrpc": "2.0", "id": 1, "method": "todos.create", "params": {"title": "First"}},
{"jsonrpc": "2.0", "id": 2, "method": "todos.create", "params": {"title": "Second"}},
{"jsonrpc": "2.0", "id": 3, "method": "todos.list"}
]'
# All three operations execute, returns array of results
When to use JSON-RPC:
- Service-to-service communication
- You need to batch multiple operations
- Network latency is a concern
- Your operations don’t map cleanly to REST verbs
Pros: Batching, explicit method names, standard protocol
Cons: POST only, less familiar to web developers
MCP - The AI-Native Choice
MCP (Model Context Protocol) is designed for AI assistants. It lets AI models discover and use your API as “tools”.
# Step 1: Initialize the connection
curl -X POST http://localhost:8080/mcp \
-d '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18"}}'
# Step 2: Discover available tools
curl -X POST http://localhost:8080/mcp \
-d '{"jsonrpc": "2.0", "id": 2, "method": "tools/list"}'
# Response shows what the AI can do:
{
"result": {
"tools": [
{
"name": "todos.create",
"description": "Create a new todo",
"inputSchema": {...}
}
]
}
}
# Step 3: Call 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 milk"}
}
}'
When to use MCP:
- You want Claude or other AI assistants to use your API
- You’re building AI-powered applications
- You want automatic tool discovery
Pros: AI assistants understand it natively, self-documenting
Cons: Specialized use case, more complex protocol
OpenAPI - The Documentation Choice
OpenAPI isn’t really a transport - it generates documentation from your service. Use it alongside other transports.
# Get the OpenAPI specification
curl http://localhost:8080/openapi.json
The spec can be used with:
- Swagger UI: Interactive API documentation
- Code generators: Generate client SDKs in any language
- API testing tools: Import into Postman, Insomnia, etc.
Using All Transports Together
Here’s how to serve your API via all protocols at once:
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/jsonrpc"
"github.com/go-mizu/mizu/contract/v2/transport/mcp"
"github.com/go-mizu/mizu/contract/v2/transport/rest"
"yourapp/todo"
)
func main() {
// Create your service implementation
impl := todo.NewService()
// Register your service
svc := contract.Register[todo.API](impl,
contract.WithDefaultResource("todos"),
)
app := mizu.New()
// REST: Traditional HTTP API
// Endpoints: POST/GET /todos, GET/PUT/DELETE /todos/{id}
rest.Mount(app.Router, svc)
// JSON-RPC: RPC-style with batching
// Endpoint: POST /rpc
jsonrpc.Mount(app.Router, "/rpc", svc)
// MCP: AI assistant integration
// Endpoint: POST /mcp
mcp.Mount(app.Router, "/mcp", svc)
// OpenAPI: Serve API documentation
spec, _ := rest.OpenAPI(svc.Descriptor())
app.Get("/openapi.json", func(c *mizu.Ctx) error {
return c.JSON(200, spec)
})
fmt.Println("Server running on http://localhost:8080")
app.Listen(":8080")
}
Now the same service is accessible via:
http://localhost:8080/todos (REST)
http://localhost:8080/rpc (JSON-RPC)
http://localhost:8080/mcp (MCP)
http://localhost:8080/openapi.json (OpenAPI spec)
How Errors Work Across Transports
One of Contract’s best features is consistent error handling. When you return an error:
return nil, contract.ErrNotFound("todo not found")
Each transport formats it appropriately:
| Transport | Error Response |
|---|
| REST | HTTP 404 status, body: {"error": {"code": "NOT_FOUND", "message": "todo not found"}} |
| JSON-RPC | {"error": {"code": -32603, "message": "todo not found", "data": {"code": "NOT_FOUND"}}} |
| MCP | {"content": [{"type": "text", "text": "todo not found"}], "isError": true} |
You don’t need to handle each protocol separately.
Complete Example
Here’s a complete example with all transports. We organize the code with the todo service in its own package:
// 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" path:"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)
}
// 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
}
// 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/jsonrpc"
"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 all transports
rest.Mount(app.Router, svc)
jsonrpc.Mount(app.Router, "/rpc", svc)
mcp.Mount(app.Router, "/mcp", svc,
mcp.WithInstructions("Use these tools to manage a todo list."),
)
// Serve OpenAPI spec
spec, _ := rest.OpenAPI(svc.Descriptor())
app.Get("/openapi.json", func(c *mizu.Ctx) error {
return c.JSON(200, spec)
})
fmt.Println("Server running on http://localhost:8080")
fmt.Println()
fmt.Println("Endpoints:")
fmt.Println(" REST: POST/GET /todos, GET /todos/{id}")
fmt.Println(" JSON-RPC: POST /rpc")
fmt.Println(" MCP: POST /mcp")
fmt.Println(" OpenAPI: GET /openapi.json")
app.Listen(":8080")
}
Practical Recommendations
Starting a New Project
Start with REST + OpenAPI:
rest.Mount(app.Router, svc)
spec, _ := rest.OpenAPI(svc.Descriptor())
app.Get("/openapi.json", func(c *mizu.Ctx) error {
return c.JSON(200, spec)
})
Add more transports as needed.
Building a Web Application
REST is perfect for web applications:
rest.Mount(app.Router, svc)
Building Microservices
JSON-RPC for service-to-service (batching is valuable):
jsonrpc.Mount(app.Router, "/rpc", svc)
Building AI-Powered Apps
MCP for AI assistants, plus REST for human debugging:
mcp.Mount(app.Router, "/mcp", svc)
rest.Mount(app.Router, svc) // For testing with curl
Building for Everything
Use all of them! There’s no conflict:
rest.Mount(app.Router, svc)
jsonrpc.Mount(app.Router, "/rpc", svc)
mcp.Mount(app.Router, "/mcp", svc)
spec, _ := rest.OpenAPI(svc.Descriptor())
app.Get("/openapi.json", func(c *mizu.Ctx) error {
return c.JSON(200, spec)
})
Common Questions
Can I use multiple transports at once?
Yes! Each transport uses different paths, so they don’t conflict. This is actually recommended.
Which transport is fastest?
They’re all similar in performance. The overhead is minimal compared to your actual business logic.
Do I need to write different code for each transport?
No! That’s the whole point of Contract. Write your service once, expose it via any transport.
Can I customize how a transport works?
Yes, each transport has options for customization. See the individual transport pages for details.
All transports use the resource.method pattern:
| Interface Method | Transport Method Name |
|---|
Create | todos.create |
List | todos.list |
Get | todos.get |
For REST, method names are mapped to HTTP verbs and paths automatically.
What’s Next?
Each transport has its own detailed documentation: