Skip to content

Commit baea5d8

Browse files
authored
Merge branch 'main' into add-io-transport
2 parents 2d54a39 + eddef06 commit baea5d8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3927
-628
lines changed

.github/workflows/readme-check.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,13 @@ jobs:
1919
uses: actions/checkout@v4
2020
- name: Check README is up-to-date
2121
run: |
22-
cd internal/readme
23-
make
22+
go generate ./internal/readme
2423
if [ -n "$(git status --porcelain)" ]; then
2524
echo "ERROR: README.md is not up-to-date!"
2625
echo ""
27-
echo "The README.md file differs from what would be generated by running 'make' in internal/readme/."
26+
echo "The README.md file differs from what would be generated by `go generate ./internal/readme`."
2827
echo "Please update internal/readme/README.src.md instead of README.md directly,"
29-
echo "then run 'make' in the internal/readme/ directory to regenerate README.md."
28+
echo "then run `go generate ./internal/readme` to regenerate README.md."
3029
echo ""
3130
echo "Changes:"
3231
git status --porcelain

README.md

Lines changed: 58 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<!-- Autogenerated by weave; DO NOT EDIT -->
2-
# MCP Go SDK v0.3.0
2+
# MCP Go SDK v0.4.0
33

44
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/modelcontextprotocol/go-sdk)
55

66
***BREAKING CHANGES***
77

88
This version contains breaking changes.
99
See the [release notes](
10-
https://github.com/modelcontextprotocol/go-sdk/releases/tag/v0.3.0) for details.
10+
https://github.com/modelcontextprotocol/go-sdk/releases/tag/v0.4.0) for details.
1111

1212
[![PkgGoDev](https://pkg.go.dev/badge/github.com/modelcontextprotocol/go-sdk)](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk)
1313

@@ -19,18 +19,6 @@ software development kit (SDK) for the Model Context Protocol (MCP).
1919
> changes. We aim to tag v1.0.0 in September, 2025. See
2020
> https://github.com/modelcontextprotocol/go-sdk/issues/328 for details.
2121
22-
## Design
23-
24-
The design doc for this SDK is at [design.md](./design/design.md), which was
25-
initially reviewed at
26-
[modelcontextprotocol/discussions/364](https://github.com/orgs/modelcontextprotocol/discussions/364).
27-
28-
Further design discussion should occur in
29-
[issues](https://github.com/modelcontextprotocol/go-sdk/issues) (for concrete
30-
proposals) or
31-
[discussions](https://github.com/modelcontextprotocol/go-sdk/discussions) for
32-
open-ended discussion. See [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
33-
3422
## Package documentation
3523

3624
The SDK consists of three importable packages:
@@ -39,19 +27,56 @@ The SDK consists of three importable packages:
3927
[`github.com/modelcontextprotocol/go-sdk/mcp`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp)
4028
package defines the primary APIs for constructing and using MCP clients and
4129
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.
4630
- The
4731
[`github.com/modelcontextprotocol/go-sdk/jsonrpc`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/jsonrpc) package is for users implementing
4832
their own transports.
49-
33+
- The
34+
[`github.com/modelcontextprotocol/go-sdk/auth`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/auth)
35+
package provides some primitives for supporting oauth.
5036

51-
## Example
37+
## Getting started
5238

53-
In this example, an MCP client communicates with an MCP server running in a
54-
sidecar process:
39+
To get started creating an MCP server, create an `mcp.Server` instance, add
40+
features to it, and then run it over an `mcp.Transport`. For example, this
41+
server adds a single simple tool, and then connects it to clients over
42+
stdin/stdout:
43+
44+
```go
45+
package main
46+
47+
import (
48+
"context"
49+
"log"
50+
51+
"github.com/modelcontextprotocol/go-sdk/mcp"
52+
)
53+
54+
type Input struct {
55+
Name string `json:"name" jsonschema:"the name of the person to greet"`
56+
}
57+
58+
type Output struct {
59+
Greeting string `json:"greeting" jsonschema:"the greeting to tell to the user"`
60+
}
61+
62+
func SayHi(ctx context.Context, req *mcp.CallToolRequest, input Input) (*mcp.CallToolResult, Output, error) {
63+
return nil, Output{Greeting: "Hi " + input.Name}, nil
64+
}
65+
66+
func main() {
67+
// Create a server with a single tool.
68+
server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
69+
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
70+
// Run the server over stdin/stdout, until the client disconnects
71+
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
72+
log.Fatal(err)
73+
}
74+
}
75+
```
76+
77+
To communicate with that server, we can similarly create an `mcp.Client` and
78+
connect it to the corresponding server, by running the server command and
79+
communicating over its stdin/stdout:
5580

5681
```go
5782
package main
@@ -96,42 +121,20 @@ func main() {
96121
}
97122
```
98123

99-
Here's an example of the corresponding server component, which communicates
100-
with its client over stdin/stdout:
101-
102-
```go
103-
package main
104-
105-
import (
106-
"context"
107-
"log"
108-
109-
"github.com/modelcontextprotocol/go-sdk/mcp"
110-
)
111-
112-
type HiParams struct {
113-
Name string `json:"name" jsonschema:"the name of the person to greet"`
114-
}
115-
116-
func SayHi(ctx context.Context, req *mcp.CallToolRequest, args HiParams) (*mcp.CallToolResult, any, error) {
117-
return &mcp.CallToolResult{
118-
Content: []mcp.Content{&mcp.TextContent{Text: "Hi " + args.Name}},
119-
}, nil, nil
120-
}
124+
The [`examples/`](/examples/) directory contains more example clients and
125+
servers.
121126

122-
func main() {
123-
// Create a server with a single tool.
124-
server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
127+
## Design
125128

126-
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
127-
// Run the server over stdin/stdout, until the client disconnects
128-
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
129-
log.Fatal(err)
130-
}
131-
}
132-
```
129+
The design doc for this SDK is at [design.md](./design/design.md), which was
130+
initially reviewed at
131+
[modelcontextprotocol/discussions/364](https://github.com/orgs/modelcontextprotocol/discussions/364).
133132

134-
The [`examples/`](/examples/) directory contains more example clients and servers.
133+
Further design discussion should occur in
134+
[issues](https://github.com/modelcontextprotocol/go-sdk/issues) (for concrete
135+
proposals) or
136+
[discussions](https://github.com/modelcontextprotocol/go-sdk/discussions) for
137+
open-ended discussion. See [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
135138

136139
## Acknowledgements
137140

auth/auth.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,13 @@ type TokenInfo struct {
2424
// The error that a TokenVerifier should return if the token cannot be verified.
2525
var ErrInvalidToken = errors.New("invalid token")
2626

27+
// The error that a TokenVerifier should return for OAuth-specific protocol errors.
28+
var ErrOAuth = errors.New("oauth error")
29+
2730
// A TokenVerifier checks the validity of a bearer token, and extracts information
2831
// from it. If verification fails, it should return an error that unwraps to ErrInvalidToken.
29-
type TokenVerifier func(ctx context.Context, token string) (*TokenInfo, error)
32+
// The HTTP request is provided in case verifying the token involves checking it.
33+
type TokenVerifier func(ctx context.Context, token string, req *http.Request) (*TokenInfo, error)
3034

3135
// RequireBearerTokenOptions are options for [RequireBearerToken].
3236
type RequireBearerTokenOptions struct {
@@ -52,14 +56,16 @@ func TokenInfoFromContext(ctx context.Context) *TokenInfo {
5256
// If verification succeeds, the [TokenInfo] is added to the request's context and the request proceeds.
5357
// If verification fails, the request fails with a 401 Unauthenticated, and the WWW-Authenticate header
5458
// is populated to enable [protected resource metadata].
59+
//
60+
5561
//
5662
// [protected resource metadata]: https://datatracker.ietf.org/doc/rfc9728
5763
func RequireBearerToken(verifier TokenVerifier, opts *RequireBearerTokenOptions) func(http.Handler) http.Handler {
5864
// Based on typescript-sdk/src/server/auth/middleware/bearerAuth.ts.
5965

6066
return func(handler http.Handler) http.Handler {
6167
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
62-
tokenInfo, errmsg, code := verify(r.Context(), verifier, opts, r.Header.Get("Authorization"))
68+
tokenInfo, errmsg, code := verify(r, verifier, opts)
6369
if code != 0 {
6470
if code == http.StatusUnauthorized || code == http.StatusForbidden {
6571
if opts != nil && opts.ResourceMetadataURL != "" {
@@ -75,22 +81,23 @@ func RequireBearerToken(verifier TokenVerifier, opts *RequireBearerTokenOptions)
7581
}
7682
}
7783

78-
func verify(ctx context.Context, verifier TokenVerifier, opts *RequireBearerTokenOptions, authHeader string) (_ *TokenInfo, errmsg string, code int) {
84+
func verify(req *http.Request, verifier TokenVerifier, opts *RequireBearerTokenOptions) (_ *TokenInfo, errmsg string, code int) {
7985
// Extract bearer token.
86+
authHeader := req.Header.Get("Authorization")
8087
fields := strings.Fields(authHeader)
8188
if len(fields) != 2 || strings.ToLower(fields[0]) != "bearer" {
8289
return nil, "no bearer token", http.StatusUnauthorized
8390
}
8491

8592
// Verify the token and get information from it.
86-
tokenInfo, err := verifier(ctx, fields[1])
93+
tokenInfo, err := verifier(req.Context(), fields[1], req)
8794
if err != nil {
8895
if errors.Is(err, ErrInvalidToken) {
8996
return nil, err.Error(), http.StatusUnauthorized
9097
}
91-
// TODO: the TS SDK distinguishes another error, OAuthError, and returns a 400.
92-
// Investigate how that works.
93-
// See typescript-sdk/src/server/auth/middleware/bearerAuth.ts.
98+
if errors.Is(err, ErrOAuth) {
99+
return nil, err.Error(), http.StatusBadRequest
100+
}
94101
return nil, err.Error(), http.StatusInternalServerError
95102
}
96103

auth/auth_test.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,20 @@ package auth
77
import (
88
"context"
99
"errors"
10+
"net/http"
1011
"testing"
1112
"time"
1213
)
1314

1415
func TestVerify(t *testing.T) {
15-
ctx := context.Background()
16-
verifier := func(_ context.Context, token string) (*TokenInfo, error) {
16+
verifier := func(_ context.Context, token string, _ *http.Request) (*TokenInfo, error) {
1717
switch token {
1818
case "valid":
1919
return &TokenInfo{Expiration: time.Now().Add(time.Hour)}, nil
2020
case "invalid":
2121
return nil, ErrInvalidToken
22+
case "oauth":
23+
return nil, ErrOAuth
2224
case "noexp":
2325
return &TokenInfo{}, nil
2426
case "expired":
@@ -47,6 +49,10 @@ func TestVerify(t *testing.T) {
4749
"invalid", nil, "bearer invalid",
4850
"invalid token", 401,
4951
},
52+
{
53+
"oauth error", nil, "Bearer oauth",
54+
"oauth error", 400,
55+
},
5056
{
5157
"no expiration", nil, "Bearer noexp",
5258
"token missing expiration", 401,
@@ -61,7 +67,9 @@ func TestVerify(t *testing.T) {
6167
},
6268
} {
6369
t.Run(tt.name, func(t *testing.T) {
64-
_, gotMsg, gotCode := verify(ctx, verifier, tt.opts, tt.header)
70+
_, gotMsg, gotCode := verify(&http.Request{
71+
Header: http.Header{"Authorization": {tt.header}},
72+
}, verifier, tt.opts)
6573
if gotMsg != tt.wantMsg || gotCode != tt.wantCode {
6674
t.Errorf("got (%q, %d), want (%q, %d)", gotMsg, gotCode, tt.wantMsg, tt.wantCode)
6775
}

copyright_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 gosdk
6+
7+
import (
8+
"fmt"
9+
"go/parser"
10+
"go/token"
11+
"io/fs"
12+
"path/filepath"
13+
"regexp"
14+
"strings"
15+
"testing"
16+
)
17+
18+
func TestCopyrightHeaders(t *testing.T) {
19+
var re = regexp.MustCompile(`Copyright \d{4} The Go MCP SDK Authors. All rights reserved.
20+
Use of this source code is governed by an MIT-style
21+
license that can be found in the LICENSE file.`)
22+
23+
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
24+
if err != nil {
25+
return err
26+
}
27+
28+
// Skip directories starting with "." or "_", and testdata directories.
29+
if d.IsDir() && d.Name() != "." &&
30+
(strings.HasPrefix(d.Name(), ".") ||
31+
strings.HasPrefix(d.Name(), "_") ||
32+
filepath.Base(d.Name()) == "testdata") {
33+
34+
return filepath.SkipDir
35+
}
36+
37+
// Skip non-go files.
38+
if !strings.HasSuffix(path, ".go") {
39+
return nil
40+
}
41+
42+
// Check the copyright header.
43+
f, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ParseComments|parser.PackageClauseOnly)
44+
if err != nil {
45+
return fmt.Errorf("parsing %s: %v", path, err)
46+
}
47+
if len(f.Comments) == 0 {
48+
t.Errorf("File %s must start with a copyright header matching %s", path, re)
49+
} else if !re.MatchString(f.Comments[0].Text()) {
50+
t.Errorf("Header comment for %s does not match expected copyright header.\ngot:\n%s\nwant matching:%s", path, f.Comments[0].Text(), re)
51+
}
52+
return nil
53+
})
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
}

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

0 commit comments

Comments
 (0)