Skip to content

Commit f5ab129

Browse files
committed
Merge branch 'main' into fgrosse/goleaks
2 parents a43fb6f + 1dcbf62 commit f5ab129

Some content is hidden

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

58 files changed

+2230
-339
lines changed

README.md

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,14 @@
11
<!-- Autogenerated by weave; DO NOT EDIT -->
2-
# MCP Go SDK v0.5.0
2+
# MCP Go SDK
33

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

6-
***BREAKING CHANGES***
7-
8-
This version contains breaking changes.
9-
See the [release notes](
10-
https://github.com/modelcontextprotocol/go-sdk/releases/tag/v0.5.0) for details.
11-
126
[![PkgGoDev](https://pkg.go.dev/badge/github.com/modelcontextprotocol/go-sdk)](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk)
137

14-
This repository contains an unreleased implementation of the official Go
15-
software development kit (SDK) for the Model Context Protocol (MCP).
8+
This repository contains an implementation of the official Go software
9+
development kit (SDK) for the Model Context Protocol (MCP).
1610

17-
> [!WARNING]
18-
> The SDK is not yet at v1.0.0 and may still be subject to incompatible API
19-
> changes. We aim to tag v1.0.0 in September, 2025. See
20-
> https://github.com/modelcontextprotocol/go-sdk/issues/328 for details.
21-
22-
## Package documentation
11+
## Package / Feature documentation
2312

2413
The SDK consists of several importable packages:
2514

@@ -32,12 +21,14 @@ The SDK consists of several importable packages:
3221
their own transports.
3322
- The
3423
[`github.com/modelcontextprotocol/go-sdk/auth`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/auth)
35-
package provides some primitives for supporting oauth.
36-
24+
package provides some primitives for supporting OAuth.
3725
- The
3826
[`github.com/modelcontextprotocol/go-sdk/oauthex`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/oauthex)
3927
package provides extensions to the OAuth protocol, such as ProtectedResourceMetadata.
4028

29+
The SDK endeavors to implement the full MCP spec. The [`docs/`](/docs/) directory
30+
contains feature documentation, mapping the MCP spec to the packages above.
31+
4132
## Getting started
4233

4334
To get started creating an MCP server, create an `mcp.Server` instance, add
@@ -63,24 +54,28 @@ type Output struct {
6354
Greeting string `json:"greeting" jsonschema:"the greeting to tell to the user"`
6455
}
6556

66-
func SayHi(ctx context.Context, req *mcp.CallToolRequest, input Input) (*mcp.CallToolResult, Output, error) {
57+
func SayHi(ctx context.Context, req *mcp.CallToolRequest, input Input) (
58+
*mcp.CallToolResult,
59+
Output,
60+
error,
61+
) {
6762
return nil, Output{Greeting: "Hi " + input.Name}, nil
6863
}
6964

7065
func main() {
7166
// Create a server with a single tool.
7267
server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
7368
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
74-
// Run the server over stdin/stdout, until the client disconnects
69+
// Run the server over stdin/stdout, until the client disconnects.
7570
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
7671
log.Fatal(err)
7772
}
7873
}
7974
```
8075

81-
To communicate with that server, we can similarly create an `mcp.Client` and
82-
connect it to the corresponding server, by running the server command and
83-
communicating over its stdin/stdout:
76+
To communicate with that server, create an `mcp.Client` and connect it to the
77+
corresponding server, by running the server command and communicating over its
78+
stdin/stdout:
8479

8580
```go
8681
package main
@@ -99,7 +94,7 @@ func main() {
9994
// Create a new client, with no features.
10095
client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil)
10196

102-
// Connect to a server over stdin/stdout
97+
// Connect to a server over stdin/stdout.
10398
transport := &mcp.CommandTransport{Command: exec.Command("myserver")}
10499
session, err := client.Connect(ctx, transport, nil)
105100
if err != nil {
@@ -128,24 +123,18 @@ func main() {
128123
The [`examples/`](/examples/) directory contains more example clients and
129124
servers.
130125

131-
## Design
132-
133-
The design doc for this SDK is at [design.md](./design/design.md), which was
134-
initially reviewed at
135-
[modelcontextprotocol/discussions/364](https://github.com/orgs/modelcontextprotocol/discussions/364).
126+
## Contributing
136127

137-
Further design discussion should occur in
138-
[issues](https://github.com/modelcontextprotocol/go-sdk/issues) (for concrete
139-
proposals) or
140-
[discussions](https://github.com/modelcontextprotocol/go-sdk/discussions) for
141-
open-ended discussion. See [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
128+
We welcome contributions to the SDK! Please see See
129+
[CONTRIBUTING.md](/CONTRIBUTING.md) for details of how to contribute.
142130

143-
## Acknowledgements
131+
## Acknowledgements / Alternatives
144132

145-
Several existing Go MCP SDKs inspired the development and design of this
146-
official SDK, notably [mcp-go](https://github.com/mark3labs/mcp-go), authored
147-
by Ed Zynda. We are grateful to Ed as well as the other contributors to mcp-go,
148-
and to authors and contributors of other SDKs such as
133+
Several third party Go MCP SDKs inspired the development and design of this
134+
official SDK, and continue to be viable alternatives, notably
135+
[mcp-go](https://github.com/mark3labs/mcp-go), originally authored by Ed Zynda.
136+
We are grateful to Ed as well as the other contributors to mcp-go, and to
137+
authors and contributors of other SDKs such as
149138
[mcp-golang](https://github.com/metoro-io/mcp-golang) and
150139
[go-mcp](https://github.com/ThinkInAIXYZ/go-mcp). Thanks to their work, there
151140
is a thriving ecosystem of Go MCP clients and servers.

auth/auth.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ func TokenInfoFromContext(ctx context.Context) *TokenInfo {
5656
// If verification succeeds, the [TokenInfo] is added to the request's context and the request proceeds.
5757
// If verification fails, the request fails with a 401 Unauthenticated, and the WWW-Authenticate header
5858
// is populated to enable [protected resource metadata].
59-
//
60-
6159
//
6260
// [protected resource metadata]: https://datatracker.ietf.org/doc/rfc9728
6361
func RequireBearerToken(verifier TokenVerifier, opts *RequireBearerTokenOptions) func(http.Handler) http.Handler {

auth/client.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
//go:build mcp_go_client_oauth
6+
7+
package auth
8+
9+
import (
10+
"context"
11+
"errors"
12+
"net/http"
13+
"sync"
14+
15+
"github.com/modelcontextprotocol/go-sdk/internal/oauthex"
16+
"golang.org/x/oauth2"
17+
)
18+
19+
// An OAuthHandler conducts an OAuth flow and returns a [oauth2.TokenSource] if the authorization
20+
// is approved, or an error if not.
21+
type OAuthHandler func(context.Context, OAuthHandlerArgs) (oauth2.TokenSource, error)
22+
23+
// OAuthHandlerArgs are arguments to an [OAuthHandler].
24+
type OAuthHandlerArgs struct {
25+
// The URL to fetch protected resource metadata, extracted from the WWW-Authenticate header.
26+
// Empty if not present or there was an error obtaining it.
27+
ResourceMetadataURL string
28+
}
29+
30+
// HTTPTransport is an [http.RoundTripper] that follows the MCP
31+
// OAuth protocol when it encounters a 401 Unauthorized response.
32+
type HTTPTransport struct {
33+
handler OAuthHandler
34+
mu sync.Mutex // protects opts.Base
35+
opts HTTPTransportOptions
36+
}
37+
38+
// NewHTTPTransport returns a new [*HTTPTransport].
39+
// The handler is invoked when an HTTP request results in a 401 Unauthorized status.
40+
// It is called only once per transport. Once a TokenSource is obtained, it is used
41+
// for the lifetime of the transport; subsequent 401s are not processed.
42+
func NewHTTPTransport(handler OAuthHandler, opts *HTTPTransportOptions) (*HTTPTransport, error) {
43+
if handler == nil {
44+
return nil, errors.New("handler cannot be nil")
45+
}
46+
t := &HTTPTransport{
47+
handler: handler,
48+
}
49+
if opts != nil {
50+
t.opts = *opts
51+
}
52+
if t.opts.Base == nil {
53+
t.opts.Base = http.DefaultTransport
54+
}
55+
return t, nil
56+
}
57+
58+
// HTTPTransportOptions are options to [NewHTTPTransport].
59+
type HTTPTransportOptions struct {
60+
// Base is the [http.RoundTripper] to use.
61+
// If nil, [http.DefaultTransport] is used.
62+
Base http.RoundTripper
63+
}
64+
65+
func (t *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
66+
t.mu.Lock()
67+
base := t.opts.Base
68+
t.mu.Unlock()
69+
70+
resp, err := base.RoundTrip(req)
71+
if err != nil {
72+
return nil, err
73+
}
74+
if resp.StatusCode != http.StatusUnauthorized {
75+
return resp, nil
76+
}
77+
if _, ok := base.(*oauth2.Transport); ok {
78+
// We failed to authorize even with a token source; give up.
79+
return resp, nil
80+
}
81+
82+
resp.Body.Close()
83+
// Try to authorize.
84+
t.mu.Lock()
85+
defer t.mu.Unlock()
86+
// If we don't have a token source, get one by following the OAuth flow.
87+
// (We may have obtained one while t.mu was not held above.)
88+
// TODO: We hold the lock for the entire OAuth flow. This could be a long
89+
// time. Is there a better way?
90+
if _, ok := t.opts.Base.(*oauth2.Transport); !ok {
91+
authHeaders := resp.Header[http.CanonicalHeaderKey("WWW-Authenticate")]
92+
ts, err := t.handler(req.Context(), OAuthHandlerArgs{
93+
ResourceMetadataURL: extractResourceMetadataURL(authHeaders),
94+
})
95+
if err != nil {
96+
return nil, err
97+
}
98+
t.opts.Base = &oauth2.Transport{Base: t.opts.Base, Source: ts}
99+
}
100+
return t.opts.Base.RoundTrip(req.Clone(req.Context()))
101+
}
102+
103+
func extractResourceMetadataURL(authHeaders []string) string {
104+
cs, err := oauthex.ParseWWWAuthenticate(authHeaders)
105+
if err != nil {
106+
return ""
107+
}
108+
return oauthex.ResourceMetadataURL(cs)
109+
}

auth/client_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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+
//go:build mcp_go_client_oauth
6+
7+
package auth
8+
9+
import (
10+
"context"
11+
"errors"
12+
"fmt"
13+
"net/http"
14+
"net/http/httptest"
15+
"testing"
16+
17+
"golang.org/x/oauth2"
18+
)
19+
20+
// TestHTTPTransport validates the OAuth HTTPTransport.
21+
func TestHTTPTransport(t *testing.T) {
22+
const testToken = "test-token-123"
23+
fakeTokenSource := oauth2.StaticTokenSource(&oauth2.Token{
24+
AccessToken: testToken,
25+
TokenType: "Bearer",
26+
})
27+
28+
// authServer simulates a resource that requires OAuth.
29+
authServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30+
authHeader := r.Header.Get("Authorization")
31+
if authHeader == fmt.Sprintf("Bearer %s", testToken) {
32+
w.WriteHeader(http.StatusOK)
33+
return
34+
}
35+
36+
w.Header().Set("WWW-Authenticate", `Bearer resource_metadata="http://metadata.example.com"`)
37+
w.WriteHeader(http.StatusUnauthorized)
38+
}))
39+
defer authServer.Close()
40+
41+
t.Run("successful auth flow", func(t *testing.T) {
42+
var handlerCalls int
43+
handler := func(ctx context.Context, args OAuthHandlerArgs) (oauth2.TokenSource, error) {
44+
handlerCalls++
45+
if args.ResourceMetadataURL != "http://metadata.example.com" {
46+
t.Errorf("handler got metadata URL %q, want %q", args.ResourceMetadataURL, "http://metadata.example.com")
47+
}
48+
return fakeTokenSource, nil
49+
}
50+
51+
transport, err := NewHTTPTransport(handler, nil)
52+
if err != nil {
53+
t.Fatalf("NewHTTPTransport() failed: %v", err)
54+
}
55+
client := &http.Client{Transport: transport}
56+
57+
resp, err := client.Get(authServer.URL)
58+
if err != nil {
59+
t.Fatalf("client.Get() failed: %v", err)
60+
}
61+
defer resp.Body.Close()
62+
63+
if resp.StatusCode != http.StatusOK {
64+
t.Errorf("got status %d, want %d", resp.StatusCode, http.StatusOK)
65+
}
66+
if handlerCalls != 1 {
67+
t.Errorf("handler was called %d times, want 1", handlerCalls)
68+
}
69+
70+
// Second request should reuse the token and not call the handler again.
71+
resp2, err := client.Get(authServer.URL)
72+
if err != nil {
73+
t.Fatalf("second client.Get() failed: %v", err)
74+
}
75+
defer resp2.Body.Close()
76+
77+
if resp2.StatusCode != http.StatusOK {
78+
t.Errorf("second request got status %d, want %d", resp2.StatusCode, http.StatusOK)
79+
}
80+
if handlerCalls != 1 {
81+
t.Errorf("handler should still be called only once, but was %d", handlerCalls)
82+
}
83+
})
84+
85+
t.Run("handler returns error", func(t *testing.T) {
86+
handlerErr := errors.New("user rejected auth")
87+
handler := func(ctx context.Context, args OAuthHandlerArgs) (oauth2.TokenSource, error) {
88+
return nil, handlerErr
89+
}
90+
91+
transport, err := NewHTTPTransport(handler, nil)
92+
if err != nil {
93+
t.Fatalf("NewHTTPTransport() failed: %v", err)
94+
}
95+
client := &http.Client{Transport: transport}
96+
97+
_, err = client.Get(authServer.URL)
98+
if !errors.Is(err, handlerErr) {
99+
t.Errorf("client.Get() returned error %v, want %v", err, handlerErr)
100+
}
101+
})
102+
}

docs/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<!-- Autogenerated by weave; DO NOT EDIT -->
2-
These docs are a work-in-progress.
3-
42
# Features
53

64
These docs mirror the [official MCP spec](https://modelcontextprotocol.io/specification/2025-06-18).
5+
Use the index below to learn how the SDK implements a particular aspect of the
6+
protocol.
77

88
## Base Protocol
99

@@ -15,21 +15,21 @@ These docs mirror the [official MCP spec](https://modelcontextprotocol.io/specif
1515
1. [Authorization](protocol.md#authorization)
1616
1. [Security](protocol.md#security)
1717
1. [Utilities](protocol.md#utilities)
18-
1. [Cancellation](utilities.md#cancellation)
19-
1. [Ping](utilities.md#ping)
20-
1. [Progress](utilities.md#progress)
18+
1. [Cancellation](protocol.md#cancellation)
19+
1. [Ping](protocol.md#ping)
20+
1. [Progress](protocol.md#progress)
2121

2222
## Client Features
2323

2424
1. [Roots](client.md#roots)
2525
1. [Sampling](client.md#sampling)
26-
1. [Elicitation](clients.md#elicitation)
26+
1. [Elicitation](client.md#elicitation)
2727

2828
## Server Features
2929

3030
1. [Prompts](server.md#prompts)
3131
1. [Resources](server.md#resources)
32-
1. [Tools](tools.md)
32+
1. [Tools](server.md#tools)
3333
1. [Utilities](server.md#utilities)
3434
1. [Completion](server.md#completion)
3535
1. [Logging](server.md#logging)

0 commit comments

Comments
 (0)