Skip to content

Commit 1f6dbae

Browse files
Merge branch 'main' into fix/858-seterror-preserve-content
2 parents cc2ecda + 7c57071 commit 1f6dbae

91 files changed

Lines changed: 7221 additions & 3016 deletions

File tree

Some content is hidden

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

.github/workflows/codeql.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: "CodeQL Advanced"
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
schedule:
9+
- cron: '31 9 * * 4'
10+
11+
# Declare default permissions as read only.
12+
permissions: read-all
13+
14+
jobs:
15+
analyze:
16+
name: Analyze (${{ matrix.language }})
17+
runs-on: ubuntu-latest
18+
permissions:
19+
# required for all workflows
20+
security-events: write
21+
22+
# required to fetch internal or private CodeQL packs
23+
packages: read
24+
25+
# only required for workflows in private repositories
26+
actions: read
27+
contents: read
28+
29+
strategy:
30+
fail-fast: false
31+
matrix:
32+
include:
33+
- language: actions
34+
build-mode: none
35+
- language: go
36+
build-mode: autobuild
37+
steps:
38+
- name: Checkout repository
39+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
40+
- name: Initialize CodeQL
41+
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
42+
with:
43+
languages: ${{ matrix.language }}
44+
build-mode: ${{ matrix.build-mode }}
45+
- name: Perform CodeQL Analysis
46+
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
47+
with:
48+
category: "/language:${{matrix.language}}"

.github/workflows/conformance.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ jobs:
2020
- name: Check out code
2121
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2222
- name: Set up Go
23-
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
23+
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
2424
with:
25-
go-version: "^1.25"
25+
go-version: "^1.26"
2626
- name: Start everything-server
2727
run: |
2828
go run ./conformance/everything-server/main.go -http=":3001" &
@@ -43,14 +43,14 @@ jobs:
4343
- name: Check out code
4444
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
4545
- name: Set up Go
46-
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
46+
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
4747
with:
48-
go-version: "^1.25"
48+
go-version: "^1.26"
4949
- name: "Run conformance tests"
5050
uses: modelcontextprotocol/conformance@a2855b03582a6c0b31065ad4d9af248316ce61a3 # v0.1.15
5151
with:
5252
mode: client
53-
command: go run ./conformance/everything-client/main.go
53+
command: go run ./conformance/everything-client
5454
suite: core
5555
expected-failures: ./conformance/baseline.yml
5656
node-version: 22

.github/workflows/docs-check.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ jobs:
1616
runs-on: ubuntu-latest
1717
steps:
1818
- name: Set up Go
19-
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
19+
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
20+
with:
21+
go-version: "^1.26"
2022
- name: Check out code
2123
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2224
- name: Check docs are up-to-date

.github/workflows/nightly.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ jobs:
2525
- name: Check out code
2626
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2727
- name: Set up Go
28-
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
28+
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
2929
with:
30-
go-version: "^1.25"
30+
go-version: "^1.26"
3131
- name: Set up Node.js
32-
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
32+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
3333
with:
3434
node-version: "22"
3535
- name: Run server conformance tests

.github/workflows/scorecard.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ jobs:
3434

3535
steps:
3636
- name: "Checkout code"
37-
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
37+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
3838
with:
3939
persist-credentials: false
4040

4141
- name: "Run analysis"
42-
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
42+
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
4343
with:
4444
results_file: results.sarif
4545
results_format: sarif
@@ -64,7 +64,7 @@ jobs:
6464
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
6565
# format to the repository Actions tab.
6666
- name: "Upload artifact"
67-
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
67+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
6868
with:
6969
name: SARIF file
7070
path: results.sarif
@@ -73,6 +73,6 @@ jobs:
7373
# Upload the results to GitHub's code scanning dashboard (optional).
7474
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
7575
- name: "Upload to code-scanning"
76-
uses: github/codeql-action/upload-sarif@v3
76+
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
7777
with:
7878
sarif_file: results.sarif

.github/workflows/test.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ jobs:
1616
- name: Check out code
1717
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
1818
- name: Set up Go
19-
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
19+
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
2020
with:
21-
go-version: "^1.25"
21+
go-version: "^1.26"
2222
- name: Check formatting
2323
run: |
2424
unformatted=$(gofmt -l .)
@@ -40,12 +40,12 @@ jobs:
4040
runs-on: ubuntu-latest
4141
strategy:
4242
matrix:
43-
go: ["1.24", "1.25"]
43+
go: ["1.25", "1.26"]
4444
steps:
4545
- name: Check out code
4646
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
4747
- name: Set up Go
48-
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
48+
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
4949
with:
5050
go-version: ${{ matrix.go }}
5151
- name: Test
@@ -57,8 +57,8 @@ jobs:
5757
- name: Check out code
5858
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5959
- name: Set up Go
60-
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
60+
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
6161
with:
62-
go-version: "1.24"
62+
go-version: "1.26"
6363
- name: Test with -race
6464
run: go test -v -race ./...

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@ contains feature documentation, mapping the MCP spec to the packages above.
3434

3535
The following table shows which versions of the Go SDK support which versions of the MCP specification:
3636

37-
| SDK Version | Latest MCP Spec | All Supported MCP Specs |
38-
|-----------------|-------------------|------------------------------------------------|
39-
| v1.2.0+ | 2025-06-18 | 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05 |
40-
| v1.0.0 - v1.1.0 | 2025-06-18 | 2025-06-18, 2025-03-26, 2024-11-05 |
37+
| SDK Version | Latest MCP Spec | All Supported MCP Specs |
38+
|-----------------|-------------------|----------------------------------------------------|
39+
| v1.4.0+ | 2025-11-25\* | 2025-11-25\*, 2025-06-18, 2025-03-26, 2024-11-05 |
40+
| v1.2.0 - v1.3.1 | 2025-11-25\*\* | 2025-11-25\*\*, 2025-06-18, 2025-03-26, 2024-11-05 |
41+
| v1.0.0 - v1.1.0 | 2025-06-18 | 2025-06-18, 2025-03-26, 2024-11-05 |
42+
43+
\* Client side OAuth has experimental support.
44+
45+
\*\* Partial support for 2025-11-25 (client side OAuth and Sampling with tools not available).
4146

4247
New releases of the SDK target only supported versions of Go. See
4348
https://go.dev/doc/devel/release#policy for more information.

auth/auth.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"encoding/json"
1010
"errors"
11+
"fmt"
1112
"net/http"
1213
"slices"
1314
"strings"
@@ -25,8 +26,7 @@ type TokenInfo struct {
2526
// session hijacking by ensuring that all requests for a given session
2627
// come from the same user.
2728
UserID string
28-
// TODO: add standard JWT fields
29-
Extra map[string]any
29+
Extra map[string]any
3030
}
3131

3232
// The error that a TokenVerifier should return if the token cannot be verified.
@@ -74,8 +74,17 @@ func RequireBearerToken(verifier TokenVerifier, opts *RequireBearerTokenOptions)
7474
tokenInfo, errmsg, code := verify(r, verifier, opts)
7575
if code != 0 {
7676
if code == http.StatusUnauthorized || code == http.StatusForbidden {
77-
if opts != nil && opts.ResourceMetadataURL != "" {
78-
w.Header().Add("WWW-Authenticate", "Bearer resource_metadata="+opts.ResourceMetadataURL)
77+
if opts != nil {
78+
var params []string
79+
if opts.ResourceMetadataURL != "" {
80+
params = append(params, fmt.Sprintf("resource_metadata=%q", opts.ResourceMetadataURL))
81+
}
82+
if len(opts.Scopes) > 0 {
83+
params = append(params, fmt.Sprintf("scope=%q", strings.Join(opts.Scopes, " ")))
84+
}
85+
if len(params) > 0 {
86+
w.Header().Add("WWW-Authenticate", "Bearer "+strings.Join(params, ", "))
87+
}
7988
}
8089
}
8190
http.Error(w, errmsg, code)
@@ -106,6 +115,9 @@ func verify(req *http.Request, verifier TokenVerifier, opts *RequireBearerTokenO
106115
}
107116
return nil, err.Error(), http.StatusInternalServerError
108117
}
118+
if tokenInfo == nil {
119+
return nil, "token validation failed", http.StatusInternalServerError
120+
}
109121

110122
// Check scopes. All must be present.
111123
if opts != nil {

auth/auth_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,99 @@ func TestProtectedResourceMetadataHandler(t *testing.T) {
188188
})
189189
}
190190
}
191+
192+
func TestRequireBearerToken(t *testing.T) {
193+
verifier := func(_ context.Context, token string, _ *http.Request) (*TokenInfo, error) {
194+
if token == "valid" {
195+
return &TokenInfo{Expiration: time.Now().Add(time.Hour), Scopes: []string{"read"}}, nil
196+
}
197+
return nil, ErrInvalidToken
198+
}
199+
200+
tests := []struct {
201+
name string
202+
opts *RequireBearerTokenOptions
203+
authHeader string
204+
wantHeader string
205+
wantStatus int
206+
}{
207+
{
208+
name: "no middleware options",
209+
opts: nil,
210+
authHeader: "Bearer invalid",
211+
wantHeader: "",
212+
wantStatus: http.StatusUnauthorized,
213+
},
214+
{
215+
name: "metadata only",
216+
opts: &RequireBearerTokenOptions{
217+
ResourceMetadataURL: "https://example.com/resource-metadata",
218+
},
219+
authHeader: "Bearer invalid",
220+
wantHeader: "Bearer resource_metadata=\"https://example.com/resource-metadata\"",
221+
wantStatus: http.StatusUnauthorized,
222+
},
223+
{
224+
name: "scopes only",
225+
opts: &RequireBearerTokenOptions{
226+
Scopes: []string{"read", "write"},
227+
},
228+
authHeader: "Bearer invalid",
229+
wantHeader: "Bearer scope=\"read write\"",
230+
wantStatus: http.StatusUnauthorized,
231+
},
232+
{
233+
name: "metadata and scopes",
234+
opts: &RequireBearerTokenOptions{
235+
ResourceMetadataURL: "https://example.com/resource-metadata",
236+
Scopes: []string{"read", "write"},
237+
},
238+
authHeader: "Bearer invalid",
239+
wantHeader: "Bearer resource_metadata=\"https://example.com/resource-metadata\", scope=\"read write\"",
240+
wantStatus: http.StatusUnauthorized,
241+
},
242+
{
243+
name: "insufficient scope",
244+
opts: &RequireBearerTokenOptions{
245+
Scopes: []string{"admin"},
246+
},
247+
authHeader: "Bearer valid", // Has "read", needs "admin" -> 403
248+
wantHeader: "Bearer scope=\"admin\"",
249+
wantStatus: http.StatusForbidden,
250+
},
251+
{
252+
name: "success",
253+
opts: &RequireBearerTokenOptions{
254+
Scopes: []string{"read"},
255+
},
256+
authHeader: "Bearer valid",
257+
wantHeader: "",
258+
wantStatus: http.StatusOK,
259+
},
260+
}
261+
262+
for _, tt := range tests {
263+
t.Run(tt.name, func(t *testing.T) {
264+
handler := RequireBearerToken(verifier, tt.opts)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
265+
w.WriteHeader(http.StatusOK)
266+
}))
267+
268+
req := httptest.NewRequest("GET", "/", nil)
269+
if tt.authHeader != "" {
270+
req.Header.Set("Authorization", tt.authHeader)
271+
}
272+
rec := httptest.NewRecorder()
273+
274+
handler.ServeHTTP(rec, req)
275+
276+
if rec.Code != tt.wantStatus {
277+
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
278+
}
279+
280+
got := rec.Header().Get("WWW-Authenticate")
281+
if got != tt.wantHeader {
282+
t.Errorf("WWW-Authenticate = %q, want %q", got, tt.wantHeader)
283+
}
284+
})
285+
}
286+
}

0 commit comments

Comments
 (0)