MCP (Model Context Protocol) lets you build tools that AI models can use. Here are three approaches in Go, from simple to interactive.
Part 1: Basic MCP Server
Start with a simple addition server using the mcp-go
package:
package main
import (
"context"
"fmt"
"log"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
s := server.NewStdioServer()
// Add our tool
s.AddTool("add_numbers", "Add two numbers together", map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"a": map[string]interface{}{"type": "number"},
"b": map[string]interface{}{"type": "number"},
},
"required": []string{"a", "b"},
}, handleAdd)
log.Fatal(s.Serve())
}
func handleAdd(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := req.Params.Arguments
a := args["a"].(float64)
b := args["b"].(float64)
result := a + b
return &mcp.CallToolResult{
Content: []mcp.Content{{
Type: "text",
Text: fmt.Sprintf("%.1f", result),
}},
}, nil
}
Debugging with mcp-inspector
Install the inspector:
npm install -g @modelcontextprotocol/inspector
Run your server and connect:
# Terminal 1
go run main.go
# Terminal 2
mcp-inspector go run main.go
The inspector gives you a web interface to test tool calls and debug issues.
Part 2: Streaming HTTP
Standard MCP uses stdio, but you can also do HTTP with streaming responses:
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
type Request struct {
Arguments map[string]interface{} `json:"arguments"`
}
func handleAdd(w http.ResponseWriter, r *http.Request) {
var req Request
json.NewDecoder(r.Body).Decode(&req)
w.Header().Set("Content-Type", "application/json")
a := req.Arguments["a"].(float64)
b := req.Arguments["b"].(float64)
encoder := json.NewEncoder(w)
// Send progress updates (streaming demo)
for i := 25; i <= 100; i += 25 {
encoder.Encode(map[string]interface{}{
"type": "progress",
"value": i,
})
w.(http.Flusher).Flush()
time.Sleep(200 * time.Millisecond)
}
// Send result
result := a + b
encoder.Encode(map[string]interface{}{
"result": fmt.Sprintf("%.1f", result),
})
}
func main() {
http.HandleFunc("/add", handleAdd)
fmt.Println("Server running on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Testing with curl
# Call the add tool
curl -X POST http://localhost:8080/add \
-H "Content-Type: application/json" \
-d '{"arguments":{"a":5,"b":3}}' \
--no-buffer
# Shows streaming progress updates followed by the result
The --no-buffer
flag shows streaming responses as they arrive.
Part 3: Elicitation for Interactive Tools
Here's where it gets interesting - tools that ask users for additional input:
package main
import (
"context"
"fmt"
"log"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
s := server.NewStdioServer()
// Interactive tool - only needs one number
s.AddTool("add_interactive", "Add numbers with user prompt", map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"number": map[string]interface{}{"type": "number"},
},
"required": []string{"number"},
}, handleInteractive)
log.Fatal(s.Serve())
}
func handleInteractive(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
args := req.Params.Arguments
num := args["number"].(float64)
return &mcp.CallToolResult{
Content: []mcp.Content{{
Type: "text",
Text: fmt.Sprintf("Got first number: %.1f", num),
}},
Meta: map[string]interface{}{
"elicit": map[string]interface{}{
"prompt": "Enter the second number:",
"callback": "complete_add",
},
},
}, nil
}
The elicitation pattern lets tools gather additional info from users when needed. The tool starts with one number and prompts for the second.
Why This Matters
MCP isn't just about building static tools - it's about creating dynamic experiences that adapt to what AI models and users need.
These three patterns cover most use cases:
- Basic stdio: Simple, reliable tools
- HTTP streaming: Complex integrations with progress feedback
- Elicitation: Interactive experiences that gather user input
The protocol is designed to be extended, so there's room for creativity here.