Skip to content
12 changes: 6 additions & 6 deletions examples/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ This example demonstrates how to use the Model Context Protocol (MCP) over HTTP
## Overview

The example implements:
- A server that provides a `get_time` tool
- A client that connects to the server, lists available tools, and calls the `get_time` tool
- A server that provides a `cityTime` tool
- A client that connects to the server, lists available tools, and calls the `cityTime` tool

## Running the Example

Expand All @@ -20,7 +20,7 @@ Server and client support passing custom -port and -host.
go run main.go -server
```

This starts an MCP server on `localhost:8080` (default) that provides a `get_time` tool.
This starts an MCP server on `localhost:8080` (default) that provides a `cityTime` tool.

### Run the Client

Expand All @@ -33,7 +33,7 @@ go run main.go -client
The client will:
1. Connect to the server
2. List available tools
3. Call the `get_time` tool for NYC, San Francisco, and Boston
3. Call the `cityTime` tool for NYC, San Francisco, and Boston
4. Display the results


Expand All @@ -44,15 +44,15 @@ localhost:8080, you can try to add it to a popular MCP client:

claude mcp add -t http timezone http://localhost:8080

Once added, Claude Code will be able to discover and use the `get_time` tool provided by this server.
Once added, Claude Code will be able to discover and use the `cityTime` tool provided by this server.

In Claude Code:

> what's the timezone

⏺ I'll get the current time in a major US city for you.

⏺ timezone - get_time (MCP)(city: "nyc")
⏺ timezone - cityTime (MCP)(city: "nyc")
⎿ The current time in New York City is 7:30:16 PM EDT on Wedn
esday, July 23, 2025

Expand Down
51 changes: 51 additions & 0 deletions examples/http/logging_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package main

import (
"log"
"net/http"
"time"
)

// responseWriter wraps http.ResponseWriter to capture the status code
type responseWriter struct {
http.ResponseWriter
statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}

func loggingHandler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()

// Create a response writer wrapper to capture status code
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}

// Log request details
log.Printf("[REQUEST] %s | %s | %s %s",
start.Format(time.RFC3339),
r.RemoteAddr,
r.Method,
r.URL.Path)

// Call the actual handler
handler.ServeHTTP(wrapped, r)

// Log response details
duration := time.Since(start)
log.Printf("[RESPONSE] %s | %s | %s %s | Status: %d | Duration: %v",
time.Now().Format(time.RFC3339),
r.RemoteAddr,
r.Method,
r.URL.Path,
wrapped.statusCode,
duration)
})
}
112 changes: 40 additions & 72 deletions examples/http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,45 @@ import (

func main() {
var (
serverMode = flag.Bool("server", false, "Run as server")
clientMode = flag.Bool("client", false, "Run as client")
host = flag.String("host", "localhost", "Host to connect to or listen on")
port = flag.String("port", "8080", "Port to connect to or listen on")
host = flag.String("host", "localhost", "Host to connect to or listen on")
port = flag.String("port", "8080", "Port to connect to or listen on")
)

flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Usage: %s <client|server> [options]\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "This program demonstrates MCP over HTTP using the streamable transport.\n")
fmt.Fprintf(os.Stderr, "It can run as either a server or client.\n\n")
fmt.Fprintf(os.Stderr, "Options:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " Run as server: %s -server\n", os.Args[0])
fmt.Fprintf(os.Stderr, " Run as client: %s -client\n", os.Args[0])
fmt.Fprintf(os.Stderr, " Custom host/port: %s -server -host 0.0.0.0 -port 9090\n", os.Args[0])
fmt.Fprintf(os.Stderr, " Run as server: %s server\n", os.Args[0])
fmt.Fprintf(os.Stderr, " Run as client: %s client\n", os.Args[0])
fmt.Fprintf(os.Stderr, " Custom host/port: %s server -host 0.0.0.0 -port 9090\n", os.Args[0])
os.Exit(1)
}

flag.Parse()

if (*serverMode && *clientMode) || (!*serverMode && !*clientMode) {
fmt.Fprintf(os.Stderr, "Error: Must specify exactly one of -server or -client\n\n")
// Check if we have at least one argument
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "Error: Must specify 'client' or 'server' as first argument\n\n")
flag.Usage()
}
mode := os.Args[1]

os.Args = append(os.Args[:1], os.Args[2:]...)
flag.Parse()

if *serverMode {
switch mode {
case "server":
runServer(*host, *port)
} else {
case "client":
runClient(*host, *port)
default:
fmt.Fprintf(os.Stderr, "Error: Invalid mode '%s'. Must be 'client' or 'server'\n\n", mode)
flag.Usage()
}
}

// GetTimeParams defines the parameters for the get_time tool
// GetTimeParams defines the parameters for the cityTime tool
type GetTimeParams struct {
City string `json:"city" jsonschema:"City to get time for (nyc, sf, or boston)"`
}
Expand Down Expand Up @@ -92,9 +97,9 @@ func getTime(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolPar
"boston": "Boston",
}

response := fmt.Sprintf("The current time in %s is %s",
cityNames[city],
now.Format("3:04:05 PM MST on Monday, January 2, 2006"))
response := fmt.Sprintf("The current time in %s is %s",
cityNames[city],
now.Format(time.RFC3339))

return &mcp.CallToolResultFor[any]{
Content: []mcp.Content{
Expand All @@ -103,27 +108,16 @@ func getTime(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolPar
}, nil
}

// responseWriter wraps http.ResponseWriter to capture the status code
type responseWriter struct {
http.ResponseWriter
statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}

func runServer(host, port string) {
// Create an MCP server
server := mcp.NewServer(&mcp.Implementation{
Name: "time-server",
Version: "1.0.0",
}, nil)

// Add the get_time tool
// Add the cityTime tool
mcp.AddTool(server, &mcp.Tool{
Name: "get_time",
Name: "cityTime",
Description: "Get the current time in NYC, San Francisco, or Boston",
}, getTime)

Expand All @@ -132,47 +126,21 @@ func runServer(host, port string) {
return server
}, nil)

// Wrap the handler with logging middleware
loggingHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()

// Create a response writer wrapper to capture status code
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}

// Log request details
log.Printf("[REQUEST] %s | %s | %s %s",
start.Format("2006-01-02 15:04:05.000"),
r.RemoteAddr,
r.Method,
r.URL.Path)

// Call the actual handler
handler.ServeHTTP(wrapped, r)

// Log response details
duration := time.Since(start)
log.Printf("[RESPONSE] %s | %s | %s %s | Status: %d | Duration: %v",
time.Now().Format("2006-01-02 15:04:05.000"),
r.RemoteAddr,
r.Method,
r.URL.Path,
wrapped.statusCode,
duration)
})
handlerWithLogging := loggingHandler(handler)

addr := fmt.Sprintf("%s:%s", host, port)
log.Printf("MCP server listening on http://%s", addr)
log.Printf("Available tool: get_time (cities: nyc, sf, boston)")
log.Printf("Available tool: cityTime (cities: nyc, sf, boston)")

// Start the HTTP server with logging handler
if err := http.ListenAndServe(addr, loggingHandler); err != nil {
if err := http.ListenAndServe(addr, handlerWithLogging); err != nil {
log.Fatalf("Server failed: %v", err)
}
}

func runClient(host, port string) {
ctx := context.Background()

// Create the URL for the server
url := fmt.Sprintf("http://%s:%s", host, port)
log.Printf("Connecting to MCP server at %s", url)
Expand All @@ -196,30 +164,30 @@ func runClient(host, port string) {
log.Printf("Connected to server (session ID: %s)", session.ID())

// First, list available tools
log.Println("\nListing available tools...")
toolsResult, err := session.ListTools(ctx, &mcp.ListToolsParams{})
log.Println("Listing available tools...")
toolsResult, err := session.ListTools(ctx, nil)
if err != nil {
log.Fatalf("Failed to list tools: %v", err)
}

for _, tool := range toolsResult.Tools {
log.Printf(" - %s: %s", tool.Name, tool.Description)
log.Printf(" - %s: %s\n", tool.Name, tool.Description)
}

// Call the get_time tool for each city
// Call the cityTime tool for each city
cities := []string{"nyc", "sf", "boston"}
log.Println("\nGetting time for each city...")

log.Println("Getting time for each city...")
for _, city := range cities {
// Call the tool
result, err := session.CallTool(ctx, &mcp.CallToolParams{
Name: "get_time",
Arguments: map[string]interface{}{
Name: "cityTime",
Arguments: map[string]any{
"city": city,
},
})
if err != nil {
log.Printf("Failed to get time for %s: %v", city, err)
log.Printf("Failed to get time for %s: %v\n", city, err)
continue
}

Expand All @@ -231,5 +199,5 @@ func runClient(host, port string) {
}
}

log.Println("\nClient completed successfully")
}
log.Println("Client completed successfully")
}