Skip to content

Commit eb96101

Browse files
Merge branch 'modelcontextprotocol:main' into issue/552
2 parents dc75b26 + d297272 commit eb96101

File tree

9 files changed

+64
-36
lines changed

9 files changed

+64
-36
lines changed

auth/client.go

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,19 @@ package auth
88

99
import (
1010
"bytes"
11-
"context"
1211
"errors"
1312
"io"
1413
"net/http"
1514
"sync"
1615

17-
"github.com/modelcontextprotocol/go-sdk/oauthex"
1816
"golang.org/x/oauth2"
1917
)
2018

2119
// An OAuthHandler conducts an OAuth flow and returns a [oauth2.TokenSource] if the authorization
2220
// is approved, or an error if not.
23-
type OAuthHandler func(context.Context, OAuthHandlerArgs) (oauth2.TokenSource, error)
24-
25-
// OAuthHandlerArgs are arguments to an [OAuthHandler].
26-
type OAuthHandlerArgs struct {
27-
// The URL to fetch protected resource metadata, extracted from the WWW-Authenticate header.
28-
// Empty if not present or there was an error obtaining it.
29-
ResourceMetadataURL string
30-
}
21+
// The handler receives the HTTP request and response that triggered the authentication flow.
22+
// To obtain the protected resource metadata, call [oauthex.GetProtectedResourceMetadataFromHeader].
23+
type OAuthHandler func(req *http.Request, res *http.Response) (oauth2.TokenSource, error)
3124

3225
// HTTPTransport is an [http.RoundTripper] that follows the MCP
3326
// OAuth protocol when it encounters a 401 Unauthorized response.
@@ -112,10 +105,7 @@ func (t *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
112105
// TODO: We hold the lock for the entire OAuth flow. This could be a long
113106
// time. Is there a better way?
114107
if _, ok := t.opts.Base.(*oauth2.Transport); !ok {
115-
authHeaders := resp.Header[http.CanonicalHeaderKey("WWW-Authenticate")]
116-
ts, err := t.handler(req.Context(), OAuthHandlerArgs{
117-
ResourceMetadataURL: extractResourceMetadataURL(authHeaders),
118-
})
108+
ts, err := t.handler(req, resp)
119109
if err != nil {
120110
return nil, err
121111
}
@@ -131,11 +121,3 @@ func (t *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
131121

132122
return t.opts.Base.RoundTrip(req)
133123
}
134-
135-
func extractResourceMetadataURL(authHeaders []string) string {
136-
cs, err := oauthex.ParseWWWAuthenticate(authHeaders)
137-
if err != nil {
138-
return ""
139-
}
140-
return oauthex.ResourceMetadataURL(cs)
141-
}

auth/client_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package auth
88

99
import (
10-
"context"
1110
"errors"
1211
"fmt"
1312
"io"
@@ -65,10 +64,11 @@ func TestHTTPTransport(t *testing.T) {
6564

6665
t.Run("successful auth flow", func(t *testing.T) {
6766
var handlerCalls int
68-
handler := func(ctx context.Context, args OAuthHandlerArgs) (oauth2.TokenSource, error) {
67+
handler := func(req *http.Request, res *http.Response) (oauth2.TokenSource, error) {
6968
handlerCalls++
70-
if args.ResourceMetadataURL != "http://metadata.example.com" {
71-
t.Errorf("handler got metadata URL %q, want %q", args.ResourceMetadataURL, "http://metadata.example.com")
69+
// Verify that the response has the expected WWW-Authenticate header
70+
if res.Header.Get("WWW-Authenticate") != `Bearer resource_metadata="http://metadata.example.com"` {
71+
t.Errorf("handler got WWW-Authenticate header %q", res.Header.Get("WWW-Authenticate"))
7272
}
7373
return fakeTokenSource, nil
7474
}
@@ -108,9 +108,10 @@ func TestHTTPTransport(t *testing.T) {
108108
})
109109

110110
t.Run("request body is cloned", func(t *testing.T) {
111-
handler := func(ctx context.Context, args OAuthHandlerArgs) (oauth2.TokenSource, error) {
112-
if args.ResourceMetadataURL != "http://metadata.example.com" {
113-
t.Errorf("handler got metadata URL %q, want %q", args.ResourceMetadataURL, "http://metadata.example.com")
111+
handler := func(req *http.Request, res *http.Response) (oauth2.TokenSource, error) {
112+
// Verify that the response has the expected WWW-Authenticate header
113+
if res.Header.Get("WWW-Authenticate") != `Bearer resource_metadata="http://metadata.example.com"` {
114+
t.Errorf("handler got WWW-Authenticate header %q", res.Header.Get("WWW-Authenticate"))
114115
}
115116
return fakeTokenSource, nil
116117
}
@@ -134,7 +135,7 @@ func TestHTTPTransport(t *testing.T) {
134135

135136
t.Run("handler returns error", func(t *testing.T) {
136137
handlerErr := errors.New("user rejected auth")
137-
handler := func(ctx context.Context, args OAuthHandlerArgs) (oauth2.TokenSource, error) {
138+
handler := func(req *http.Request, res *http.Response) (oauth2.TokenSource, error) {
138139
return nil, handlerErr
139140
}
140141

docs/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,9 @@ protocol.
3838
# TroubleShooting
3939

4040
See [troubleshooting.md](troubleshooting.md) for a troubleshooting guide.
41+
42+
# Rough edges
43+
44+
See [rough_edges.md](rough_edges.md) for a list of rough edges or API
45+
oversights that can't be addressed due to our compatibility promise. We'll
46+
revisit these if/when we move to a v2 of the SDK.

docs/rough_edges.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!-- Autogenerated by weave; DO NOT EDIT -->
2+
# Rough edges: API decisions to reconsider for v2
3+
4+
This file collects a list of API oversights or rough edges that we've uncovered
5+
post v1.0.0, along with their current workarounds. These issues can't be
6+
addressed without breaking backward compatibility, but we'll revisit them for
7+
v2.
8+
9+
- `EventStore.Open` is unnecessary. This was an artifact of an earlier version
10+
of the SDK where event persistence and delivery were combined.
11+
12+
**Workaround**: `Open` may be implemented as a no-op.

internal/docs/README.src.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,9 @@ protocol.
3737
# TroubleShooting
3838

3939
See [troubleshooting.md](troubleshooting.md) for a troubleshooting guide.
40+
41+
# Rough edges
42+
43+
See [rough_edges.md](rough_edges.md) for a list of rough edges or API
44+
oversights that can't be addressed due to our compatibility promise. We'll
45+
revisit these if/when we move to a v2 of the SDK.

internal/docs/doc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//go:generate weave -o ../../docs/client.md ./client.src.md
99
//go:generate weave -o ../../docs/server.md ./server.src.md
1010
//go:generate weave -o ../../docs/troubleshooting.md ./troubleshooting.src.md
11+
//go:generate weave -o ../../docs/rough_edges.md ./rough_edges.src.md
1112

1213
// The doc package generates the documentation at /doc, via go:generate.
1314
//

internal/docs/rough_edges.src.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Rough edges: API decisions to reconsider for v2
2+
3+
This file collects a list of API oversights or rough edges that we've uncovered
4+
post v1.0.0, along with their current workarounds. These issues can't be
5+
addressed without breaking backward compatibility, but we'll revisit them for
6+
v2.
7+
8+
- `EventStore.Open` is unnecessary. This was an artifact of an earlier version
9+
of the SDK where event persistence and delivery were combined.
10+
11+
**Workaround**: `Open` may be implemented as a no-op.

mcp/streamable.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,13 +1377,14 @@ func (c *streamableClientConn) connectStandaloneSSE() {
13771377
resp.Body.Close()
13781378
return
13791379
}
1380-
if resp.StatusCode == http.StatusNotFound && !c.strict {
1381-
// modelcontextprotocol/gosdk#393: some servers return NotFound instead
1382-
// of MethodNotAllowed for the standalone SSE stream.
1380+
if resp.StatusCode >= 400 && resp.StatusCode < 500 && !c.strict {
1381+
// modelcontextprotocol/go-sdk#393,#610: some servers return NotFound or
1382+
// other status codes instead of MethodNotAllowed for the standalone SSE
1383+
// stream.
13831384
//
13841385
// Treat this like MethodNotAllowed in non-strict mode.
13851386
if c.logger != nil {
1386-
c.logger.Warn("got 404 instead of 405 for standalone SSE stream")
1387+
c.logger.Warn(fmt.Sprintf("got %d instead of 405 for standalone SSE stream", resp.StatusCode))
13871388
}
13881389
resp.Body.Close()
13891390
return

mcp/streamable_client_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,11 @@ func TestStreamableClientGETHandling(t *testing.T) {
239239
}{
240240
{http.StatusOK, ""},
241241
{http.StatusMethodNotAllowed, ""},
242-
{http.StatusBadRequest, "standalone SSE"},
242+
// The client error status code is not treated as an error in non-strict
243+
// mode.
244+
{http.StatusNotFound, ""},
245+
{http.StatusBadRequest, ""},
246+
{http.StatusInternalServerError, "standalone SSE"},
243247
}
244248

245249
for _, test := range tests {
@@ -305,7 +309,11 @@ func TestStreamableClientStrictness(t *testing.T) {
305309
{"strict initialized", true, http.StatusOK, http.StatusMethodNotAllowed, true},
306310
{"unstrict initialized", false, http.StatusOK, http.StatusMethodNotAllowed, false},
307311
{"strict GET", true, http.StatusAccepted, http.StatusNotFound, true},
308-
{"unstrict GET", false, http.StatusOK, http.StatusNotFound, false},
312+
// The client error status code is not treated as an error in non-strict
313+
// mode.
314+
{"unstrict GET on StatusNotFound", false, http.StatusOK, http.StatusNotFound, false},
315+
{"unstrict GET on StatusBadRequest", false, http.StatusOK, http.StatusBadRequest, false},
316+
{"GET on InternlServerError", false, http.StatusOK, http.StatusInternalServerError, true},
309317
}
310318
for _, test := range tests {
311319
t.Run(test.label, func(t *testing.T) {

0 commit comments

Comments
 (0)