Skip to content
2 changes: 1 addition & 1 deletion examples/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The client will:
At any given time you can pass a custom URL to the program to run it on a custom host/port:

```
go run main.go server http://0.0.0.0:9000
go run main.go -host 0.0.0.0 -port 9000 server
```

## Testing with real-world MCP Clients
Expand Down
61 changes: 31 additions & 30 deletions examples/http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,53 +10,55 @@ import (
"fmt"
"log"
"net/http"
"net/url"
"os"
"time"

"github.com/modelcontextprotocol/go-sdk/mcp"
)

var (
host = flag.String("host", "localhost", "host to connect to/listen on")
port = flag.Int("port", 8000, "port number to connect to/listen on")
proto = flag.String("proto", "http", "if set, use as proto:// part of URL (ignored for server)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the server is always http, what's the point of letting the client be something different?

Copy link
Contributor Author

@wkoszek wkoszek Jul 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jba The very good use case of this example is testing with real-world MCP client. In my testing, I take this code and put it on the remote VPS server behind the HTTPS load-balancer, and then I can use our client to connect to this https. And then I can use real-world client like Claude.

In other words: server is listening on 8080 HTTP but the HTTPS is provided by Caddy. And then I can test different clients with our server code

)

func main() {
out := flag.CommandLine.Output()
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s <client|server> [proto://<host>:<port>]\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")
fmt.Fprintf(out, "Usage: %s <client|server> [-proto <http|https>] [-port <port] [-host <host>]\n\n", os.Args[0])
fmt.Fprintf(out, "This program demonstrates MCP over HTTP using the streamable transport.\n")
fmt.Fprintf(out, "It can run as either a server or client.\n\n")
fmt.Fprintf(out, "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 https://0.0.0.0:8000\n", os.Args[0])
fmt.Fprintf(out, "\nExamples:\n")
fmt.Fprintf(out, " Run as server: %s server\n", os.Args[0])
fmt.Fprintf(out, " Run as client: %s client\n", os.Args[0])
fmt.Fprintf(out, " Custom host/port: %s -port 9000 -host 0.0.0.0 server\n", os.Args[0])
os.Exit(1)
}
flag.Parse()

if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "Error: Must specify 'client' or 'server' as first argument\n\n")
if flag.NArg() != 1 {
fmt.Fprintf(out, "Error: Must specify 'client' or 'server' as first argument\n")
flag.Usage()
}
mode := os.Args[1]

rawurl := "http://localhost:8000"
if len(os.Args) >= 3 {
rawurl = os.Args[2]
}
url, err := url.Parse(rawurl)
if err != nil {
log.Fatalf("Server failed: %v", err)
}
mode := flag.Arg(0)

switch mode {
case "server":
runServer(url)
if (*proto != "http") {
log.Fatalf("Server only works with 'http' (you passed proto=%s)", *proto)
}
runServer(fmt.Sprintf("%s:%d", *host, *port))
case "client":
runClient(url)
runClient(fmt.Sprintf("%s://%s:%d", *proto, *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 cityTime tool.
type GetTimeParams struct {
City string `json:"city" jsonschema:"City to get time for (nyc, sf, or boston)"`
Expand Down Expand Up @@ -109,7 +111,7 @@ func getTime(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolPar
}, nil
}

func runServer(url *url.URL) {
func runServer(url string) {
// Create an MCP server.
server := mcp.NewServer(&mcp.Implementation{
Name: "time-server",
Expand All @@ -129,24 +131,23 @@ func runServer(url *url.URL) {

handlerWithLogging := loggingHandler(handler)

laddr := fmt.Sprintf("%s:%s", url.Hostname(), url.Port())
log.Printf("MCP server listening on %s", laddr)
log.Printf("MCP server listening on %s", url)
log.Printf("Available tool: cityTime (cities: nyc, sf, boston)")

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

func runClient(url *url.URL) {
func runClient(url string) {
ctx := context.Background()

// Create the URL for the server.
log.Printf("Connecting to MCP server at %s", url.String())
log.Printf("Connecting to MCP server at %s", url)

// Create a streamable client transport.
transport := mcp.NewStreamableClientTransport(url.String(), nil)
transport := mcp.NewStreamableClientTransport(url, nil)

// Create an MCP client.
client := mcp.NewClient(&mcp.Implementation{
Expand Down
Loading