Skip to content

Commit 4df0706

Browse files
committed
Merge branch 'main' into output-validation
2 parents 7e78ba0 + 07b65d7 commit 4df0706

File tree

24 files changed

+1458
-125
lines changed

24 files changed

+1458
-125
lines changed

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,12 @@ open-ended discussion. See [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
3333

3434
## Package documentation
3535

36-
The SDK consists of three importable packages:
36+
The SDK consists of two importable packages:
3737

3838
- The
3939
[`github.com/modelcontextprotocol/go-sdk/mcp`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp)
4040
package defines the primary APIs for constructing and using MCP clients and
4141
servers.
42-
- The
43-
[`github.com/modelcontextprotocol/go-sdk/jsonschema`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/jsonschema)
44-
package provides an implementation of [JSON
45-
Schema](https://json-schema.org/), used for MCP tool input and output schema.
4642
- The
4743
[`github.com/modelcontextprotocol/go-sdk/jsonrpc`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/jsonrpc) package is for users implementing
4844
their own transports.

examples/client/listfeatures/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ func main() {
3131
flag.Parse()
3232
args := flag.Args()
3333
if len(args) == 0 {
34-
fmt.Fprintf(os.Stderr, "Usage: listfeatures <command> [<args>]")
35-
fmt.Fprintf(os.Stderr, "List all features for a stdio MCP server")
34+
fmt.Fprintln(os.Stderr, "Usage: listfeatures <command> [<args>]")
35+
fmt.Fprintln(os.Stderr, "List all features for a stdio MCP server")
3636
fmt.Fprintln(os.Stderr)
37-
fmt.Fprintf(os.Stderr, "Example: listfeatures npx @modelcontextprotocol/server-everything")
37+
fmt.Fprintln(os.Stderr, "Example:\n\tlistfeatures npx @modelcontextprotocol/server-everything")
3838
os.Exit(2)
3939
}
4040

examples/client/loadtest/main.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
2+
// Use of this source code is governed by an MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
// The load command load tests a streamable MCP server
6+
//
7+
// Usage: loadtest <URL>
8+
//
9+
// For example:
10+
//
11+
// loadtest -tool=greet -args='{"name": "foo"}' http://localhost:8080
12+
package main
13+
14+
import (
15+
"context"
16+
"encoding/json"
17+
"flag"
18+
"fmt"
19+
"log"
20+
"os"
21+
"os/signal"
22+
"sync"
23+
"sync/atomic"
24+
"time"
25+
26+
"github.com/modelcontextprotocol/go-sdk/mcp"
27+
)
28+
29+
var (
30+
duration = flag.Duration("duration", 1*time.Minute, "duration of the load test")
31+
tool = flag.String("tool", "", "tool to call")
32+
jsonArgs = flag.String("args", "", "JSON arguments to pass")
33+
workers = flag.Int("workers", 10, "number of concurrent workers")
34+
timeout = flag.Duration("timeout", 1*time.Second, "request timeout")
35+
qps = flag.Int("qps", 100, "tool calls per second, per worker")
36+
verbose = flag.Bool("v", false, "if set, enable verbose logging")
37+
)
38+
39+
func main() {
40+
flag.Usage = func() {
41+
out := flag.CommandLine.Output()
42+
fmt.Fprintf(out, "Usage: loadtest [flags] <URL>")
43+
fmt.Fprintf(out, "Load test a streamable HTTP server (CTRL-C to end early)")
44+
fmt.Fprintln(out)
45+
fmt.Fprintf(out, "Example: loadtest -tool=greet -args='{\"name\": \"foo\"}' http://localhost:8080\n")
46+
fmt.Fprintln(out)
47+
fmt.Fprintln(out, "Flags:")
48+
flag.PrintDefaults()
49+
}
50+
flag.Parse()
51+
args := flag.Args()
52+
if len(args) != 1 || *tool == "" {
53+
flag.Usage()
54+
os.Exit(2)
55+
}
56+
57+
parentCtx, cancel := context.WithTimeout(context.Background(), *duration)
58+
defer cancel()
59+
parentCtx, stop := signal.NotifyContext(parentCtx, os.Interrupt)
60+
defer stop()
61+
62+
var (
63+
start = time.Now()
64+
success atomic.Int64
65+
failure atomic.Int64
66+
)
67+
68+
// Run the test.
69+
var wg sync.WaitGroup
70+
for range *workers {
71+
wg.Add(1)
72+
go func() {
73+
defer wg.Done()
74+
client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil)
75+
cs, err := client.Connect(parentCtx, &mcp.StreamableClientTransport{Endpoint: args[0]}, nil)
76+
if err != nil {
77+
log.Fatal(err)
78+
}
79+
defer cs.Close()
80+
81+
ticker := time.NewTicker(1 * time.Second / time.Duration(*qps))
82+
defer ticker.Stop()
83+
84+
for range ticker.C {
85+
ctx, cancel := context.WithTimeout(parentCtx, *timeout)
86+
defer cancel()
87+
88+
res, err := cs.CallTool(ctx, &mcp.CallToolParams{Name: *tool, Arguments: json.RawMessage(*jsonArgs)})
89+
if err != nil {
90+
if parentCtx.Err() != nil {
91+
return // test ended
92+
}
93+
failure.Add(1)
94+
if *verbose {
95+
log.Printf("FAILURE: %v", err)
96+
}
97+
} else {
98+
success.Add(1)
99+
if *verbose {
100+
data, err := json.Marshal(res)
101+
if err != nil {
102+
log.Fatalf("marshalling result: %v", err)
103+
}
104+
log.Printf("SUCCESS: %s", string(data))
105+
}
106+
}
107+
}
108+
}()
109+
}
110+
wg.Wait()
111+
stop() // restore the interrupt signal
112+
113+
// Print stats.
114+
var (
115+
dur = time.Since(start)
116+
succ = success.Load()
117+
fail = failure.Load()
118+
)
119+
fmt.Printf("Results (in %s):\n", dur)
120+
fmt.Printf("\tsuccess: %d (%g QPS)\n", succ, float64(succ)/dur.Seconds())
121+
fmt.Printf("\tfailure: %d (%g QPS)\n", fail, float64(fail)/dur.Seconds())
122+
}

examples/http/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# MCP HTTP Example
2+
3+
This example demonstrates how to use the Model Context Protocol (MCP) over HTTP using the streamable transport. It includes both a server and client implementation.
4+
5+
## Overview
6+
7+
The example implements:
8+
- A server that provides a `cityTime` tool
9+
- A client that connects to the server, lists available tools, and calls the `cityTime` tool
10+
11+
## Usage
12+
13+
Start the Server
14+
15+
```bash
16+
go run main.go server
17+
```
18+
This starts an MCP server on `http://localhost:8080` (default) that provides a `cityTime` tool.
19+
20+
To run a client in another terminal:
21+
22+
```bash
23+
go run main.go client
24+
```
25+
26+
The client will:
27+
1. Connect to the server
28+
2. List available tools
29+
3. Call the `cityTime` tool for NYC, San Francisco, and Boston
30+
4. Display the results
31+
32+
At any given time you can pass a custom URL to the program to run it on a custom host/port:
33+
34+
```
35+
go run main.go -host 0.0.0.0 -port 9000 server
36+
```
37+
38+
## Testing with real-world MCP Clients
39+
40+
Once the server is started, assuming it's the default
41+
localhost:8080, you can try to add it to a popular MCP client:
42+
43+
claude mcp add -t http timezone http://localhost:8080
44+
45+
Once added, Claude Code will be able to discover and use the `cityTime` tool provided by this server.
46+
47+
In Claude Code:
48+
49+
> what's the timezone
50+
51+
⏺ I'll get the current time in a major US city for you.
52+
53+
⏺ timezone - cityTime (MCP)(city: "nyc")
54+
⎿ The current time in New York City is 7:30:16 PM EDT on Wedn
55+
esday, July 23, 2025
56+
57+
58+
⏺ The current timezone is EDT (Eastern Daylight Time), and it's
59+
7:30 PM on Wednesday, July 23, 2025.
60+
61+
> what timezones do you support?
62+
63+
⏺ The timezone tool supports three US cities:
64+
- NYC (Eastern Time)
65+
- SF (Pacific Time)
66+
- Boston (Eastern Time)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
2+
// Use of this source code is governed by an MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"log"
9+
"net/http"
10+
"time"
11+
)
12+
13+
// responseWriter wraps http.ResponseWriter to capture the status code.
14+
type responseWriter struct {
15+
http.ResponseWriter
16+
statusCode int
17+
}
18+
19+
func (rw *responseWriter) WriteHeader(code int) {
20+
rw.statusCode = code
21+
rw.ResponseWriter.WriteHeader(code)
22+
}
23+
24+
func loggingHandler(handler http.Handler) http.Handler {
25+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
26+
start := time.Now()
27+
28+
// Create a response writer wrapper to capture status code.
29+
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
30+
31+
// Log request details.
32+
log.Printf("[REQUEST] %s | %s | %s %s",
33+
start.Format(time.RFC3339),
34+
r.RemoteAddr,
35+
r.Method,
36+
r.URL.Path)
37+
38+
// Call the actual handler.
39+
handler.ServeHTTP(wrapped, r)
40+
41+
// Log response details.
42+
duration := time.Since(start)
43+
log.Printf("[RESPONSE] %s | %s | %s %s | Status: %d | Duration: %v",
44+
time.Now().Format(time.RFC3339),
45+
r.RemoteAddr,
46+
r.Method,
47+
r.URL.Path,
48+
wrapped.statusCode,
49+
duration)
50+
})
51+
}

0 commit comments

Comments
 (0)