Compare commits
189 changed files with 4782 additions and 35505 deletions
|
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
name: fix-bug
|
|
||||||
description: Confirm, debug and fix bugs reported via GitHub issues.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Step-by-step instructions
|
|
||||||
|
|
||||||
1. Use the `gh` CLI to fetch information about the issue.
|
|
||||||
2. Create a reproduction test case.
|
|
||||||
3. Create an implementation plan that describes how you intend to fix the bug. Don't proceed without approval.
|
|
||||||
4. Implement the fix.
|
|
||||||
5. Verify the fix with the test case and run all tests to ensure no regressions.
|
|
||||||
6. Provide a summary of the changes.
|
|
||||||
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,25 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
type: Bug
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1.
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Logs**
|
|
||||||
If applicable, add logs to help explain your problem.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
21
.github/ISSUE_TEMPLATE/enhancement.md
vendored
21
.github/ISSUE_TEMPLATE/enhancement.md
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
name: Enhancement
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
type: Enhancement
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
15
.github/dependabot.yml
vendored
15
.github/dependabot.yml
vendored
|
|
@ -1,15 +0,0 @@
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
# Check for updates to GitHub Actions every month.
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "monthly"
|
|
||||||
# Group all github/codeql-action/* sub-paths (init, analyze, ...) into a
|
|
||||||
# single PR so they are always bumped together. Bumping them independently
|
|
||||||
# leaves the workflow with mismatched versions across steps, which
|
|
||||||
# github/codeql-action rejects at runtime.
|
|
||||||
groups:
|
|
||||||
codeql-action:
|
|
||||||
patterns:
|
|
||||||
- "github/codeql-action/*"
|
|
||||||
48
.github/workflows/codeql.yml
vendored
48
.github/workflows/codeql.yml
vendored
|
|
@ -1,48 +0,0 @@
|
||||||
name: "CodeQL Advanced"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
schedule:
|
|
||||||
- cron: '31 9 * * 4'
|
|
||||||
|
|
||||||
# Declare default permissions as read only.
|
|
||||||
permissions: read-all
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze (${{ matrix.language }})
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
# required for all workflows
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
# required to fetch internal or private CodeQL packs
|
|
||||||
packages: read
|
|
||||||
|
|
||||||
# only required for workflows in private repositories
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- language: actions
|
|
||||||
build-mode: none
|
|
||||||
- language: go
|
|
||||||
build-mode: autobuild
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
build-mode: ${{ matrix.build-mode }}
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
|
||||||
with:
|
|
||||||
category: "/language:${{matrix.language}}"
|
|
||||||
74
.github/workflows/conformance.yml
vendored
74
.github/workflows/conformance.yml
vendored
|
|
@ -1,74 +0,0 @@
|
||||||
name: Conformance Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: conformance-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
|
||||||
CONFORMANCE_VERSION: "0.2.0-alpha.11"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
server-conformance:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
||||||
with:
|
|
||||||
go-version: "^1.26"
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
- name: Build everything-server
|
|
||||||
run: go build -o everything-server ./conformance/everything-server
|
|
||||||
- name: Run conformance tests (2025-11-25, stateful)
|
|
||||||
run: |
|
|
||||||
./everything-server -http=":3001" -stateless=false &
|
|
||||||
SERVER_PID=$!
|
|
||||||
trap "kill -9 $SERVER_PID 2>/dev/null || true; wait $SERVER_PID 2>/dev/null || true" EXIT
|
|
||||||
timeout 30 bash -c 'until curl -s http://localhost:3001/mcp > /dev/null; do sleep 0.5; done'
|
|
||||||
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
|
|
||||||
--url http://localhost:3001/mcp \
|
|
||||||
--requirements 2025-11-25 \
|
|
||||||
--expected-failures ./conformance/baseline.yml
|
|
||||||
- name: Run conformance tests (2026-07-28, stateless)
|
|
||||||
run: |
|
|
||||||
./everything-server -http=":3001" -stateless &
|
|
||||||
SERVER_PID=$!
|
|
||||||
trap "kill -9 $SERVER_PID 2>/dev/null || true; wait $SERVER_PID 2>/dev/null || true" EXIT
|
|
||||||
timeout 30 bash -c 'until curl -s http://localhost:3001/mcp > /dev/null; do sleep 0.5; done'
|
|
||||||
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
|
|
||||||
--url http://localhost:3001/mcp \
|
|
||||||
--requirements 2026-07-28 \
|
|
||||||
--expected-failures ./conformance/baseline.yml
|
|
||||||
|
|
||||||
client-conformance:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
||||||
with:
|
|
||||||
go-version: "^1.26"
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
- name: Run conformance tests (full suite)
|
|
||||||
run: |
|
|
||||||
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" client \
|
|
||||||
--command "go run ./conformance/everything-client" \
|
|
||||||
--suite all \
|
|
||||||
--expected-failures ./conformance/baseline.yml
|
|
||||||
6
.github/workflows/docs-check.yml
vendored
6
.github/workflows/docs-check.yml
vendored
|
|
@ -16,11 +16,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
|
||||||
with:
|
|
||||||
go-version: "^1.26"
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||||
- name: Check docs are up-to-date
|
- name: Check docs are up-to-date
|
||||||
run: |
|
run: |
|
||||||
go generate ./...
|
go generate ./...
|
||||||
|
|
|
||||||
22
.github/workflows/nightly.yml
vendored
22
.github/workflows/nightly.yml
vendored
|
|
@ -11,30 +11,10 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
vulncheck:
|
vulncheck:
|
||||||
# Don't run scheduled builds on forks.
|
|
||||||
if: github.event_name != 'schedule' || github.repository == 'modelcontextprotocol/go-sdk'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Run govulncheck
|
- name: Run govulncheck
|
||||||
uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f # v1.1.0
|
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
go-package: ./...
|
go-package: ./...
|
||||||
|
|
||||||
conformance:
|
|
||||||
# Don't run scheduled builds on forks.
|
|
||||||
if: github.event_name != 'schedule' || github.repository == 'modelcontextprotocol/go-sdk'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
||||||
with:
|
|
||||||
go-version: "^1.26"
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
||||||
with:
|
|
||||||
node-version: "22"
|
|
||||||
- name: Run server conformance tests
|
|
||||||
run: ./scripts/server-conformance.sh
|
|
||||||
|
|
|
||||||
32
.github/workflows/publish-docs.yml
vendored
32
.github/workflows/publish-docs.yml
vendored
|
|
@ -1,32 +1,24 @@
|
||||||
name: Publish Docs
|
name: Publish Docs to GitHub Pages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions: read-all
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- name: Check out code
|
||||||
- name: Configure Git Credentials
|
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||||
run: |
|
|
||||||
git config user.name github-actions[bot]
|
- name: Deploy to GitHub Pages
|
||||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4
|
||||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
publish_dir: ./docs
|
||||||
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
publish_branch: gh-pages
|
||||||
with:
|
enable_jekyll: true
|
||||||
key: mkdocs-material-${{ env.cache_id }}
|
|
||||||
path: .cache
|
|
||||||
restore-keys: |
|
|
||||||
mkdocs-material-
|
|
||||||
- run: pip install mkdocs-material
|
|
||||||
- run: mkdocs gh-deploy --force
|
|
||||||
|
|
|
||||||
78
.github/workflows/scorecard.yml
vendored
78
.github/workflows/scorecard.yml
vendored
|
|
@ -1,78 +0,0 @@
|
||||||
# This workflow uses actions that are not certified by GitHub. They are provided
|
|
||||||
# by a third-party and are governed by separate terms of service, privacy
|
|
||||||
# policy, and support documentation.
|
|
||||||
|
|
||||||
name: Scorecard supply-chain security
|
|
||||||
on:
|
|
||||||
# For Branch-Protection check. Only the default branch is supported. See
|
|
||||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
|
||||||
branch_protection_rule:
|
|
||||||
# To guarantee Maintained check is occasionally updated. See
|
|
||||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
|
||||||
schedule:
|
|
||||||
- cron: '41 23 * * 5'
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
# Declare default permissions as read only.
|
|
||||||
permissions: read-all
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analysis:
|
|
||||||
name: Scorecard analysis
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
|
|
||||||
if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
|
|
||||||
permissions:
|
|
||||||
# Needed to upload the results to code-scanning dashboard.
|
|
||||||
security-events: write
|
|
||||||
# Needed to publish results and get a badge (see publish_results below).
|
|
||||||
id-token: write
|
|
||||||
# Uncomment the permissions below if installing in a private repository.
|
|
||||||
# contents: read
|
|
||||||
# actions: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: "Checkout code"
|
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: "Run analysis"
|
|
||||||
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
|
|
||||||
with:
|
|
||||||
results_file: results.sarif
|
|
||||||
results_format: sarif
|
|
||||||
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
|
||||||
# - you want to enable the Branch-Protection check on a *public* repository, or
|
|
||||||
# - you are installing Scorecard on a *private* repository
|
|
||||||
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
|
|
||||||
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
|
||||||
|
|
||||||
# Public repositories:
|
|
||||||
# - Publish results to OpenSSF REST API for easy access by consumers
|
|
||||||
# - Allows the repository to include the Scorecard badge.
|
|
||||||
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
|
||||||
# For private repositories:
|
|
||||||
# - `publish_results` will always be set to `false`, regardless
|
|
||||||
# of the value entered here.
|
|
||||||
publish_results: true
|
|
||||||
|
|
||||||
# (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore
|
|
||||||
# file_mode: git
|
|
||||||
|
|
||||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
|
||||||
# format to the repository Actions tab.
|
|
||||||
- name: "Upload artifact"
|
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
||||||
with:
|
|
||||||
name: SARIF file
|
|
||||||
path: results.sarif
|
|
||||||
retention-days: 5
|
|
||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard (optional).
|
|
||||||
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
|
||||||
- name: "Upload to code-scanning"
|
|
||||||
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
|
||||||
with:
|
|
||||||
sarif_file: results.sarif
|
|
||||||
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
|
|
@ -14,11 +14,11 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
|
||||||
with:
|
with:
|
||||||
go-version: "^1.26"
|
go-version: "^1.25"
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
run: |
|
run: |
|
||||||
unformatted=$(gofmt -l .)
|
unformatted=$(gofmt -l .)
|
||||||
|
|
@ -31,7 +31,7 @@ jobs:
|
||||||
- name: Run Go vet
|
- name: Run Go vet
|
||||||
run: go vet ./...
|
run: go vet ./...
|
||||||
- name: Run staticcheck
|
- name: Run staticcheck
|
||||||
uses: dominikh/staticcheck-action@9716614d4101e79b4340dd97b10e54d68234e431 # v1.4.1
|
uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1
|
||||||
with:
|
with:
|
||||||
install-go: false
|
install-go: false
|
||||||
version: "v0.6.1"
|
version: "v0.6.1"
|
||||||
|
|
@ -40,12 +40,12 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: ["1.25", "1.26"]
|
go: ["1.23", "1.24", "1.25"]
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
- name: Test
|
- name: Test
|
||||||
|
|
@ -55,10 +55,10 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
|
||||||
with:
|
with:
|
||||||
go-version: "1.26"
|
go-version: "1.24"
|
||||||
- name: Test with -race
|
- name: Test with -race
|
||||||
run: go test -v -race ./...
|
run: go test -v -race ./...
|
||||||
|
|
|
||||||
58
AGENTS.md
58
AGENTS.md
|
|
@ -1,58 +0,0 @@
|
||||||
# AGENTS.md
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
This repository contains the official Go SDK for the Model Context Protocol (MCP).
|
|
||||||
The SDK is designed to be idiomatic, future-proof, and extensible.
|
|
||||||
|
|
||||||
### Key Packages
|
|
||||||
|
|
||||||
- `mcp`: The core package defining the primary APIs for constructing and using MCP clients and servers. This is where most logic resides.
|
|
||||||
- `jsonrpc`: Provides the JSON-RPC 2.0 transport layer. Use this if implementing custom transports.
|
|
||||||
- `auth`: Primitives for supporting OAuth.
|
|
||||||
- `oauthex`: Extensions to the OAuth protocol, such as Protected Resource Metadata.
|
|
||||||
- `internal`: Internal implementation details not exposed to users.
|
|
||||||
- `examples`: Example clients and servers. Use these as references for usage patterns.
|
|
||||||
|
|
||||||
## Development Setup
|
|
||||||
|
|
||||||
The project uses the standard Go toolchain.
|
|
||||||
|
|
||||||
- **Build**: `go build ./...`
|
|
||||||
- **Test**: `go test ./...`
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
- **Unit Tests**: Run `go test ./...` to run all unit tests.
|
|
||||||
- **Conformance Tests**: Use the following scripts to run the official MCP conformance tests against the SDK.
|
|
||||||
- `./scripts/server-conformance.sh` for server tests.
|
|
||||||
- `./scripts/client-conformance.sh` for client tests.
|
|
||||||
- The scripts download the latest conformance suite from npm by default.
|
|
||||||
- To get possible options pass the `--help` flag to the script.
|
|
||||||
|
|
||||||
## Development Guidelines
|
|
||||||
|
|
||||||
### Code Style
|
|
||||||
|
|
||||||
- Follow standard Go conventions (Effective Go).
|
|
||||||
- Use `gofmt` to format code.
|
|
||||||
- Add copyright headers to all new Go files:
|
|
||||||
```go
|
|
||||||
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
```
|
|
||||||
- Do not add comments to the code unless they are really necessary:
|
|
||||||
- Prefer self-documenting code.
|
|
||||||
- Focus on the "why" not the "what" in comments.
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
- **README.md**: Do NOT edit `README.md` directly. It is generated from `internal/readme/README.src.md`.
|
|
||||||
- Edit `internal/readme/README.src.md`.
|
|
||||||
- Run `go generate ./internal/readme` to regenerate.
|
|
||||||
- Commit both files.
|
|
||||||
- **docs/**: Do NOT edit `docs/` directory directly. It is generated from files in `internal/docs`.
|
|
||||||
- Edit `internal/docs/*.src.md`.
|
|
||||||
- Run `go generate ./internal/docs` to regenerate.
|
|
||||||
- Commit files from both directories.
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
<!-- Autogenerated by weave; DO NOT EDIT -->
|
|
||||||
# Contributing to the Go MCP SDK
|
# Contributing to the Go MCP SDK
|
||||||
|
|
||||||
Thank you for your interest in contributing! The Go SDK needs active
|
Thank you for your interest in contributing! The Go SDK needs active
|
||||||
|
|
@ -7,31 +6,11 @@ new and emerging use-cases. We welcome all forms of contribution, from filing
|
||||||
and reviewing issues, to contributing fixes, to proposing and implementing new
|
and reviewing issues, to contributing fixes, to proposing and implementing new
|
||||||
features.
|
features.
|
||||||
|
|
||||||
As described in the [design document](https://github.com/modelcontextprotocol/go-sdk/blob/main/design/design.md), it is important for
|
As described in the [design document](./design/design.md), it is important for
|
||||||
the MCP SDK to remain idiomatic, future-proof, and extensible. The process
|
the MCP SDK to remain idiomatic, future-proof, and extensible. The process
|
||||||
described here is intended to ensure that the SDK evolves safely and
|
described here is intended to ensure that the SDK evolves safely and
|
||||||
transparently, while adhering to these goals.
|
transparently, while adhering to these goals.
|
||||||
|
|
||||||
1. [Development setup](#development-setup)
|
|
||||||
1. [Conformance tests](#conformance-tests)
|
|
||||||
1. [Filing issues](#filing-issues)
|
|
||||||
1. [Bugs](#bugs)
|
|
||||||
1. [Proposals](#proposals)
|
|
||||||
1. [Design discussion](#design-discussion)
|
|
||||||
1. [Contributing code](#contributing-code)
|
|
||||||
1. [Adding and updating dependencies](#adding-and-updating-dependencies)
|
|
||||||
1. [Updating the README](#updating-the-readme)
|
|
||||||
1. [Versioning](#versioning)
|
|
||||||
1. [What counts as a breaking change](#what-counts-as-a-breaking-change)
|
|
||||||
1. [Deprecation](#deprecation)
|
|
||||||
1. [Timeouts](#timeouts)
|
|
||||||
1. [Code of conduct](#code-of-conduct)
|
|
||||||
1. [Governance](#governance)
|
|
||||||
1. [Working Group meetings](#working-group-meetings)
|
|
||||||
1. [Discord](#discord)
|
|
||||||
1. [Antitrust considerations](#antitrust-considerations)
|
|
||||||
|
|
||||||
|
|
||||||
## Development setup
|
## Development setup
|
||||||
|
|
||||||
This module can be built and tested using the standard Go toolchain. Run `go
|
This module can be built and tested using the standard Go toolchain. Run `go
|
||||||
|
|
@ -144,14 +123,14 @@ Changes should be high quality and well tested, and should generally follow the
|
||||||
messages should follow the [format used by the Go
|
messages should follow the [format used by the Go
|
||||||
project](https://go.dev/wiki/CommitMessage).
|
project](https://go.dev/wiki/CommitMessage).
|
||||||
|
|
||||||
Unless otherwise noted, the Go source files are distributed under the license
|
Unless otherwise noted, the Go source files are distributed under the MIT-style
|
||||||
found in the LICENSE file. New contributions are licensed under Apache 2.0. All
|
license found in the LICENSE file. All Go files in the SDK should have a
|
||||||
Go files in the SDK should have a copyright header following the format below:
|
copyright header following the format below:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
|
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
|
||||||
// Use of this source code is governed by the license
|
// Use of this source code is governed by an MIT-style
|
||||||
// that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Adding and updating dependencies
|
### Adding and updating dependencies
|
||||||
|
|
@ -191,57 +170,6 @@ The CI system will automatically check that the README is up-to-date by running
|
||||||
`go generate ./internal/readme` and verifying no changes result. If you see a CI failure about the
|
`go generate ./internal/readme` and verifying no changes result. If you see a CI failure about the
|
||||||
README being out of sync, follow the steps above to regenerate it.
|
README being out of sync, follow the steps above to regenerate it.
|
||||||
|
|
||||||
## Versioning
|
|
||||||
|
|
||||||
The SDK follows [semantic versioning](https://semver.org), as the Go module
|
|
||||||
system requires: the major version is part of the module path, so a breaking
|
|
||||||
release would import as `github.com/modelcontextprotocol/go-sdk/v2`. A breaking
|
|
||||||
change therefore cannot reach existing users by accident; they have to change
|
|
||||||
their import path to receive one.
|
|
||||||
|
|
||||||
This policy covers the exported API of the SDK's importable packages — `mcp`,
|
|
||||||
`jsonrpc`, `auth`, `auth/extauth` and `oauthex`. Everything under `internal/`
|
|
||||||
is not importable outside the module and may change in any release.
|
|
||||||
|
|
||||||
Which MCP spec revisions each SDK version speaks is documented in the
|
|
||||||
[README](README.md#version-compatibility). Support for a new revision arrives
|
|
||||||
in a minor release; dropping a revision the SDK previously negotiated is a
|
|
||||||
breaking change.
|
|
||||||
|
|
||||||
### What counts as a breaking change
|
|
||||||
|
|
||||||
- Removing or renaming an exported identifier.
|
|
||||||
- Changing the signature of an exported function or method.
|
|
||||||
- Removing or renaming a field of an exported struct, or changing its type.
|
|
||||||
- Adding a method to an exported interface that users are meant to implement.
|
|
||||||
- Changing documented behavior that callers could reasonably rely on.
|
|
||||||
- Dropping support for an MCP spec revision.
|
|
||||||
|
|
||||||
The following are not breaking, and may land in a minor or patch release:
|
|
||||||
|
|
||||||
- Adding an exported identifier, or a field to an exported struct.
|
|
||||||
- Fixing behavior that contradicted its documentation.
|
|
||||||
- Refactoring that leaves the exported API unchanged.
|
|
||||||
- Adding support for a new MCP spec revision.
|
|
||||||
- Raising the minimum Go version to one still supported upstream, per the
|
|
||||||
[README](README.md#version-compatibility).
|
|
||||||
|
|
||||||
### Deprecation
|
|
||||||
|
|
||||||
An API on its way out is marked with a `// Deprecated:` comment naming its
|
|
||||||
replacement, which `gopls` and `staticcheck` surface at call sites. It keeps
|
|
||||||
working until the next major version, since removing it would itself be a
|
|
||||||
breaking change.
|
|
||||||
|
|
||||||
Protocol features that the *specification* deprecates are a separate matter and
|
|
||||||
follow the spec's timeline rather than this policy. The features deprecated by
|
|
||||||
[SEP-2577](https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging)
|
|
||||||
— roots, sampling and logging — remain supported for at least twelve months, as
|
|
||||||
noted in the [README](README.md#version-compatibility).
|
|
||||||
|
|
||||||
Any change to the exported API, breaking or not, goes through the
|
|
||||||
[proposal process](#proposals).
|
|
||||||
|
|
||||||
## Timeouts
|
## Timeouts
|
||||||
|
|
||||||
If a contributor hasn't responded to issue questions or PR comments in two weeks,
|
If a contributor hasn't responded to issue questions or PR comments in two weeks,
|
||||||
|
|
|
||||||
197
LICENSE
197
LICENSE
|
|
@ -1,193 +1,6 @@
|
||||||
The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0.
|
|
||||||
|
|
||||||
Contributions for which relicensing consent has been obtained are licensed under Apache-2.0. Contributions made by authors who originally licensed their work under the MIT License and who have not yet granted explicit permission to relicense remain licensed under the MIT License.
|
|
||||||
|
|
||||||
No rights beyond those granted by the applicable original license are conveyed for such contributions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright
|
|
||||||
owner or by an individual or Legal Entity authorized to submit on behalf
|
|
||||||
of the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC.
|
Copyright (c) 2025 Go MCP SDK Authors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
@ -206,11 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Creative Commons Attribution 4.0 International (CC-BY-4.0)
|
|
||||||
|
|
||||||
Documentation in this project (excluding specifications) is licensed under
|
|
||||||
CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for
|
|
||||||
the full license text.
|
|
||||||
|
|
|
||||||
30
README.md
30
README.md
|
|
@ -4,7 +4,6 @@
|
||||||
[](https://codespaces.new/modelcontextprotocol/go-sdk)
|
[](https://codespaces.new/modelcontextprotocol/go-sdk)
|
||||||
|
|
||||||
[](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk)
|
[](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk)
|
||||||
[](https://scorecard.dev/viewer/?uri=github.com/modelcontextprotocol/go-sdk)
|
|
||||||
|
|
||||||
This repository contains an implementation of the official Go software
|
This repository contains an implementation of the official Go software
|
||||||
development kit (SDK) for the Model Context Protocol (MCP).
|
development kit (SDK) for the Model Context Protocol (MCP).
|
||||||
|
|
@ -30,31 +29,6 @@ The SDK consists of several importable packages:
|
||||||
The SDK endeavors to implement the full MCP spec. The [`docs/`](/docs/) directory
|
The SDK endeavors to implement the full MCP spec. The [`docs/`](/docs/) directory
|
||||||
contains feature documentation, mapping the MCP spec to the packages above.
|
contains feature documentation, mapping the MCP spec to the packages above.
|
||||||
|
|
||||||
## Version Compatibility
|
|
||||||
|
|
||||||
The following table shows which versions of the Go SDK support which versions of the MCP specification:
|
|
||||||
|
|
||||||
| SDK Version | Latest MCP Spec | All Supported MCP Specs |
|
|
||||||
|-----------------|-------------------|------------------------------------------------------------------|
|
|
||||||
| v1.7.0+ | 2026-07-28 | 2026-07-28, 2025-11-25\*, 2025-06-18, 2025-03-26, 2024-11-05 |
|
|
||||||
| v1.4.0 - v1.6.1 | 2025-11-25\* | 2025-11-25\*, 2025-06-18, 2025-03-26, 2024-11-05 |
|
|
||||||
| v1.2.0 - v1.3.1 | 2025-11-25\*\* | 2025-11-25\*\*, 2025-06-18, 2025-03-26, 2024-11-05 |
|
|
||||||
| v1.0.0 - v1.1.0 | 2025-06-18 | 2025-06-18, 2025-03-26, 2024-11-05 |
|
|
||||||
|
|
||||||
\* Client side OAuth has experimental support.
|
|
||||||
|
|
||||||
\*\* Partial support for 2025-11-25 (client side OAuth and Sampling with tools not available).
|
|
||||||
|
|
||||||
The roots, sampling, and logging features are deprecated as of protocol version
|
|
||||||
2026-07-28 by
|
|
||||||
[SEP-2577](https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging).
|
|
||||||
The SDK continues to support them for compatibility during the deprecation
|
|
||||||
window (at least twelve months). See the individual feature documentation for
|
|
||||||
migration guidance.
|
|
||||||
|
|
||||||
New releases of the SDK target only supported versions of Go. See
|
|
||||||
https://go.dev/doc/devel/release#policy for more information.
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
To get started creating an MCP server, create an `mcp.Server` instance, add
|
To get started creating an MCP server, create an `mcp.Server` instance, add
|
||||||
|
|
@ -167,5 +141,5 @@ is a thriving ecosystem of Go MCP clients and servers.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under Apache 2.0 for new contributions, with existing
|
This project is licensed under the MIT License - see the [LICENSE](./LICENSE)
|
||||||
code under MIT - see the [LICENSE](./LICENSE) file for details.
|
file for details.
|
||||||
|
|
|
||||||
32
ROADMAP.md
32
ROADMAP.md
|
|
@ -1,32 +0,0 @@
|
||||||
# MCP Go SDK Roadmap
|
|
||||||
|
|
||||||
## Current focus
|
|
||||||
|
|
||||||
The following items are planned for the next release (v1.4.0):
|
|
||||||
|
|
||||||
- **SEP-1730: Tier 1 SDK support** (https://github.com/modelcontextprotocol/go-sdk/issues/675)
|
|
||||||
- Description: We aim to be rated as Tier 1 SDK.
|
|
||||||
- **SEP-1577: support Sampling With Tools** (https://github.com/modelcontextprotocol/go-sdk/issues/629)
|
|
||||||
- Description: Sampling with Tools is the last SEP to be implemented
|
|
||||||
to implement the full scope of 2025-11-25 MCP specification.
|
|
||||||
- **OAuth Support** (https://github.com/modelcontextprotocol/go-sdk/issues/19)
|
|
||||||
- Description: Client-side OAuth support is the last feature
|
|
||||||
needed to provide the Authorization solution as per specification.
|
|
||||||
|
|
||||||
## Future work
|
|
||||||
|
|
||||||
Once the tiering system is in place, we will draft a new roadmap to describe
|
|
||||||
our long-term strategy. It may include experimental features or extensions
|
|
||||||
to the MCP specification listed in the next section.
|
|
||||||
|
|
||||||
## Experimental features
|
|
||||||
|
|
||||||
- **SEP-1686 (experimental): Implement Tasks** (https://github.com/modelcontextprotocol/go-sdk/issues/626)
|
|
||||||
|
|
||||||
## Extensions
|
|
||||||
|
|
||||||
### Authorization extensions (https://github.com/modelcontextprotocol/ext-auth)
|
|
||||||
|
|
||||||
- **ext-auth: Enterprise Managed Authorization** (https://github.com/modelcontextprotocol/go-sdk/issues/628)
|
|
||||||
- Status: In progress - https://github.com/modelcontextprotocol/go-sdk/pull/770
|
|
||||||
- **ext-auth: OAuth Client Credentials** (https://github.com/modelcontextprotocol/go-sdk/issues/627)
|
|
||||||
18
SECURITY.md
18
SECURITY.md
|
|
@ -1,21 +1,15 @@
|
||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
Thank you for helping keep the Model Context Protocol and its ecosystem secure.
|
Thank you for helping us keep the SDKs and systems they interact with secure.
|
||||||
|
|
||||||
## Reporting Security Issues
|
## Reporting Security Issues
|
||||||
|
|
||||||
If you discover a security vulnerability in this repository, please report it through
|
This SDK is maintained by [Anthropic](https://www.anthropic.com/) as part of the Model Context Protocol project.
|
||||||
the [GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
|
|
||||||
for this repository.
|
|
||||||
|
|
||||||
Please **do not** report security vulnerabilities through public GitHub issues, discussions,
|
The security of our systems and user data is Anthropic’s top priority. We appreciate the work of security researchers acting in good faith in identifying and reporting potential vulnerabilities.
|
||||||
or pull requests.
|
|
||||||
|
|
||||||
## What to Include
|
Our security program is managed on HackerOne and we ask that any validated vulnerability in this functionality be reported through their [submission form](https://hackerone.com/anthropic-vdp/reports/new?type=team&report_type=vulnerability).
|
||||||
|
|
||||||
To help us triage and respond quickly, please include:
|
## Vulnerability Disclosure Program
|
||||||
|
|
||||||
- A description of the vulnerability
|
Our Vulnerability Program Guidelines are defined on our [HackerOne program page](https://hackerone.com/anthropic-vdp).
|
||||||
- Steps to reproduce the issue
|
|
||||||
- The potential impact
|
|
||||||
- Any suggested fixes (optional)
|
|
||||||
|
|
|
||||||
62
auth/auth.go
62
auth/auth.go
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -26,7 +25,8 @@ type TokenInfo struct {
|
||||||
// session hijacking by ensuring that all requests for a given session
|
// session hijacking by ensuring that all requests for a given session
|
||||||
// come from the same user.
|
// come from the same user.
|
||||||
UserID string
|
UserID string
|
||||||
Extra map[string]any
|
// TODO: add standard JWT fields
|
||||||
|
Extra map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
// The error that a TokenVerifier should return if the token cannot be verified.
|
// The error that a TokenVerifier should return if the token cannot be verified.
|
||||||
|
|
@ -47,34 +47,6 @@ type RequireBearerTokenOptions struct {
|
||||||
ResourceMetadataURL string
|
ResourceMetadataURL string
|
||||||
// The required scopes.
|
// The required scopes.
|
||||||
Scopes []string
|
Scopes []string
|
||||||
// AllowMissingExpiration opts the middleware out of the
|
|
||||||
// `tokenInfo.Expiration.IsZero()` reject. Default false preserves the
|
|
||||||
// existing strict behaviour (every TokenInfo must carry an Expiration).
|
|
||||||
//
|
|
||||||
// Some IdPs emit session-bound bearer tokens that do not carry a standalone
|
|
||||||
// `exp` claim — the token's lifetime is bounded by an external session and
|
|
||||||
// is not advertised in-band. Resource servers integrating with such IdPs
|
|
||||||
// need to opt in to validating the rest of the token (scopes, signature
|
|
||||||
// via the verifier callback, etc.) without requiring the expiration field
|
|
||||||
// to be present.
|
|
||||||
//
|
|
||||||
// When enabled, the verifier is still responsible for any session-level
|
|
||||||
// validity check it can perform; this option only relaxes the middleware's
|
|
||||||
// own expiration enforcement.
|
|
||||||
AllowMissingExpiration bool
|
|
||||||
// ClockSkew bounds the tolerance applied to a token's Expiration when
|
|
||||||
// deciding whether it has elapsed. A token is rejected only if
|
|
||||||
// Expiration + ClockSkew is before the current time. Zero (the default)
|
|
||||||
// preserves strict comparison: any expired token is rejected immediately.
|
|
||||||
//
|
|
||||||
// Resource servers running behind a CDN, in distributed deployments, or
|
|
||||||
// communicating with an authorization server whose clock drifts a few
|
|
||||||
// seconds (common with cloud-managed IdPs) need a small positive value
|
|
||||||
// here to avoid rejecting tokens that are valid by the issuer's clock
|
|
||||||
// but momentarily appear expired by the verifier's. The same tolerance
|
|
||||||
// guards against an issuer's clock running slightly fast at /token
|
|
||||||
// issuance time.
|
|
||||||
ClockSkew time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type tokenInfoKey struct{}
|
type tokenInfoKey struct{}
|
||||||
|
|
@ -102,17 +74,8 @@ func RequireBearerToken(verifier TokenVerifier, opts *RequireBearerTokenOptions)
|
||||||
tokenInfo, errmsg, code := verify(r, verifier, opts)
|
tokenInfo, errmsg, code := verify(r, verifier, opts)
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
if code == http.StatusUnauthorized || code == http.StatusForbidden {
|
if code == http.StatusUnauthorized || code == http.StatusForbidden {
|
||||||
if opts != nil {
|
if opts != nil && opts.ResourceMetadataURL != "" {
|
||||||
var params []string
|
w.Header().Add("WWW-Authenticate", "Bearer resource_metadata="+opts.ResourceMetadataURL)
|
||||||
if opts.ResourceMetadataURL != "" {
|
|
||||||
params = append(params, fmt.Sprintf("resource_metadata=%q", opts.ResourceMetadataURL))
|
|
||||||
}
|
|
||||||
if len(opts.Scopes) > 0 {
|
|
||||||
params = append(params, fmt.Sprintf("scope=%q", strings.Join(opts.Scopes, " ")))
|
|
||||||
}
|
|
||||||
if len(params) > 0 {
|
|
||||||
w.Header().Add("WWW-Authenticate", "Bearer "+strings.Join(params, ", "))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
http.Error(w, errmsg, code)
|
http.Error(w, errmsg, code)
|
||||||
|
|
@ -143,9 +106,6 @@ func verify(req *http.Request, verifier TokenVerifier, opts *RequireBearerTokenO
|
||||||
}
|
}
|
||||||
return nil, err.Error(), http.StatusInternalServerError
|
return nil, err.Error(), http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
if tokenInfo == nil {
|
|
||||||
return nil, "token validation failed", http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check scopes. All must be present.
|
// Check scopes. All must be present.
|
||||||
if opts != nil {
|
if opts != nil {
|
||||||
|
|
@ -157,17 +117,11 @@ func verify(req *http.Request, verifier TokenVerifier, opts *RequireBearerTokenO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts == nil {
|
// Check expiration.
|
||||||
opts = &RequireBearerTokenOptions{}
|
|
||||||
}
|
|
||||||
// Check expiration, with optional clock-skew tolerance. Skew only applies
|
|
||||||
// when an expiration is present; a missing expiration is governed solely by
|
|
||||||
// AllowMissingExpiration.
|
|
||||||
if tokenInfo.Expiration.IsZero() {
|
if tokenInfo.Expiration.IsZero() {
|
||||||
if !opts.AllowMissingExpiration {
|
return nil, "token missing expiration", http.StatusUnauthorized
|
||||||
return nil, "token missing expiration", http.StatusUnauthorized
|
}
|
||||||
}
|
if tokenInfo.Expiration.Before(time.Now()) {
|
||||||
} else if tokenInfo.Expiration.Add(opts.ClockSkew).Before(time.Now()) {
|
|
||||||
return nil, "token expired", http.StatusUnauthorized
|
return nil, "token expired", http.StatusUnauthorized
|
||||||
}
|
}
|
||||||
return tokenInfo, "", 0
|
return tokenInfo, "", 0
|
||||||
|
|
|
||||||
|
|
@ -1,222 +0,0 @@
|
||||||
// Copyright 2026 The Go MCP SDK Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by the license
|
|
||||||
// that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package auth_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/auth"
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/oauthex"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// savingTokenSource is an oauth2.TokenSource that passes the oauth2 config and
|
|
||||||
// token to the given saver function each time the access token value changes.
|
|
||||||
type savingTokenSource struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
src oauth2.TokenSource
|
|
||||||
saver func(*oauth2.Config, *oauth2.Token) error
|
|
||||||
config *oauth2.Config
|
|
||||||
accessToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *savingTokenSource) Token() (*oauth2.Token, error) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
tok, err := s.src.Token()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if s.accessToken != tok.AccessToken {
|
|
||||||
s.accessToken = tok.AccessToken
|
|
||||||
// This saver implementation always returns nil.
|
|
||||||
_ = s.saver(s.config, tok)
|
|
||||||
}
|
|
||||||
return tok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSavingTokenSource persists OAuth 2.0 sessions by intercepting token
|
|
||||||
// changes from the wrapped oauth2.TokenSource. When this wrapper detects an
|
|
||||||
// access token change, it calls the provided session saver with the
|
|
||||||
// oauth2.Config and the new oauth2.Token.
|
|
||||||
//
|
|
||||||
// initial is an optional access token the caller may already hold (such as a
|
|
||||||
// token loaded from storage). It initialises the wrapper's state so that if
|
|
||||||
// the first wrapped.Token() call returns the same token, does not trigger a
|
|
||||||
// redundant call to saver(). Pass nil when there is no existing token, in
|
|
||||||
// which case the first token produced by wrapped.Token() is saved.
|
|
||||||
func NewSavingTokenSource(wrapped oauth2.TokenSource, config *oauth2.Config, initial *oauth2.Token, saver func(*oauth2.Config, *oauth2.Token) error) oauth2.TokenSource {
|
|
||||||
if wrapped == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if saver == nil {
|
|
||||||
return wrapped
|
|
||||||
}
|
|
||||||
var accessToken string
|
|
||||||
if initial != nil {
|
|
||||||
accessToken = initial.AccessToken
|
|
||||||
}
|
|
||||||
return &savingTokenSource{
|
|
||||||
src: wrapped,
|
|
||||||
saver: saver,
|
|
||||||
config: config,
|
|
||||||
accessToken: accessToken,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sessionStore is an in-memory OAuth 2.0 session store used to demonstrate
|
|
||||||
// persistence. A production implementation would persist the session to disk or
|
|
||||||
// a secret store.
|
|
||||||
type sessionStore struct {
|
|
||||||
config *oauth2.Config
|
|
||||||
token *oauth2.Token
|
|
||||||
}
|
|
||||||
|
|
||||||
// save persists the given OAuth 2.0 config and token.
|
|
||||||
func (s *sessionStore) save(config *oauth2.Config, token *oauth2.Token) error {
|
|
||||||
fmt.Printf("Saving token: %s\n", token.AccessToken)
|
|
||||||
s.config = config
|
|
||||||
s.token = token
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// restore loads a previously persisted OAuth 2.0 session, if one exists.
|
|
||||||
func (s *sessionStore) restore() (*oauth2.Config, *oauth2.Token, error) {
|
|
||||||
if s.config != nil && s.token != nil {
|
|
||||||
fmt.Println("Restoring session.")
|
|
||||||
} else {
|
|
||||||
fmt.Println("No session found to restore.")
|
|
||||||
}
|
|
||||||
return s.config, s.token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newMockAuthServer returns an httptest.Server that simulates both an MCP
|
|
||||||
// resource server requiring authorization and its OAuth authorization server.
|
|
||||||
func newMockAuthServer() *httptest.Server {
|
|
||||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch r.URL.Path {
|
|
||||||
case "/.well-known/oauth-authorization-server":
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, _ = fmt.Fprintf(w, `{"issuer": "http://%s", "authorization_endpoint": "http://%s/auth", "token_endpoint": "http://%s/token", "code_challenge_methods_supported": ["S256"]}`, r.Host, r.Host, r.Host)
|
|
||||||
case "/token":
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, _ = w.Write([]byte(`{"access_token": "mock-token", "token_type": "bearer"}`))
|
|
||||||
default:
|
|
||||||
// The mock MCP endpoint returns 401 until the client presents a valid
|
|
||||||
// bearer token.
|
|
||||||
if r.Header.Get("Authorization") == "Bearer mock-token" {
|
|
||||||
// A real server would return a valid MCP message here. The empty
|
|
||||||
// response causes Connect to return an error after authorization,
|
|
||||||
// which is ignored for this example which demonstrates token
|
|
||||||
// persistence only.
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("WWW-Authenticate", "Bearer")
|
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example shows how OAuth2 session persistence might be implemented. It
|
|
||||||
// connects twice using a shared in-memory session store: the first connection
|
|
||||||
// authorizes and saves the session, and the second restores and reuses it.
|
|
||||||
func Example_persistence() {
|
|
||||||
// Simulate an MCP server that requires authorization and an OAuth server.
|
|
||||||
mockServer := newMockAuthServer()
|
|
||||||
defer mockServer.Close()
|
|
||||||
|
|
||||||
// store persists the OAuth2 session across both connections.
|
|
||||||
store := &sessionStore{}
|
|
||||||
|
|
||||||
// connect performs a single client connection, restoring any saved session
|
|
||||||
// beforehand and saving any new session acquired during authorization.
|
|
||||||
connect := func() {
|
|
||||||
// Load the OAuth2 session if available, and use it.
|
|
||||||
var initialTS oauth2.TokenSource
|
|
||||||
cfg, tok, err := store.restore()
|
|
||||||
if err == nil && cfg != nil && tok != nil {
|
|
||||||
initialTS = NewSavingTokenSource(
|
|
||||||
cfg.TokenSource(context.Background(), tok),
|
|
||||||
cfg, tok, store.save,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure and initialize the AuthorizationCodeHandler with a
|
|
||||||
// NewTokenSource that saves the session when the access token changes,
|
|
||||||
// and the InitialTokenSource set to the session loaded via restore().
|
|
||||||
config := &auth.AuthorizationCodeHandlerConfig{
|
|
||||||
RedirectURL: "http://localhost/callback",
|
|
||||||
PreregisteredClient: &oauthex.ClientCredentials{
|
|
||||||
ClientID: "example",
|
|
||||||
},
|
|
||||||
Client: mockServer.Client(),
|
|
||||||
AuthorizationCodeFetcher: func(ctx context.Context, args *auth.AuthorizationArgs) (*auth.AuthorizationResult, error) {
|
|
||||||
fmt.Println("No token source found. Transport is calling Authorize()...")
|
|
||||||
// Extract the generated state from the authorization URL
|
|
||||||
u, _ := url.Parse(args.URL)
|
|
||||||
state := u.Query().Get("state")
|
|
||||||
return &auth.AuthorizationResult{Code: "mock-code", State: state}, nil
|
|
||||||
},
|
|
||||||
NewTokenSource: func(ctx context.Context, cfg *oauth2.Config, token *oauth2.Token) (oauth2.TokenSource, error) {
|
|
||||||
// This save implementation always returns nil.
|
|
||||||
_ = store.save(cfg, token)
|
|
||||||
return NewSavingTokenSource(
|
|
||||||
cfg.TokenSource(ctx, token), cfg, token, store.save,
|
|
||||||
), nil
|
|
||||||
},
|
|
||||||
InitialTokenSource: initialTS,
|
|
||||||
}
|
|
||||||
handler, err := auth.NewAuthorizationCodeHandler(config)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating handler: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the constructed handler on a transport.
|
|
||||||
transport := &mcp.StreamableClientTransport{
|
|
||||||
Endpoint: mockServer.URL + "/sse",
|
|
||||||
OAuthHandler: handler,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a client and attempt to connect using the configured
|
|
||||||
// transport. The transport will automatically:
|
|
||||||
// 1. Call TokenSource() to check for an existing session. This will
|
|
||||||
// return InitialTokenSource, if set.
|
|
||||||
// 2. Try the MCP endpoint, and encounter a 401 response from the mock
|
|
||||||
// server.
|
|
||||||
// 3. Call Authorize() to perform the OAuth flow, which calls
|
|
||||||
// NewTokenSource. In this example NewTokenSource saves the newly
|
|
||||||
// acquired session.
|
|
||||||
// 4. Retry the MCP endpoint with a valid bearer token and get a 200.
|
|
||||||
client := mcp.NewClient(
|
|
||||||
&mcp.Implementation{Name: "example", Version: "1.0.0"}, nil,
|
|
||||||
)
|
|
||||||
// Response ignored: this example asserts authorization only.
|
|
||||||
_, _ = client.Connect(context.Background(), transport, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The first connection has no saved session, so it authorizes and saves.
|
|
||||||
fmt.Println("--- First connect ---")
|
|
||||||
connect()
|
|
||||||
// The second connection restores the saved session and reuses it, so no
|
|
||||||
// further authorization or save occurs.
|
|
||||||
fmt.Println("--- Second connect ---")
|
|
||||||
connect()
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// --- First connect ---
|
|
||||||
// No session found to restore.
|
|
||||||
// No token source found. Transport is calling Authorize()...
|
|
||||||
// Saving token: mock-token
|
|
||||||
// --- Second connect ---
|
|
||||||
// Restoring session.
|
|
||||||
}
|
|
||||||
|
|
@ -62,11 +62,6 @@ func TestVerify(t *testing.T) {
|
||||||
"no expiration", nil, "Bearer noexp",
|
"no expiration", nil, "Bearer noexp",
|
||||||
"token missing expiration", 401,
|
"token missing expiration", 401,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"no expiration with AllowMissingExpiration accepts",
|
|
||||||
&RequireBearerTokenOptions{AllowMissingExpiration: true}, "Bearer noexp",
|
|
||||||
"", 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"expired", nil, "Bearer expired",
|
"expired", nil, "Bearer expired",
|
||||||
"token expired", 401,
|
"token expired", 401,
|
||||||
|
|
@ -193,159 +188,3 @@ func TestProtectedResourceMetadataHandler(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequireBearerToken(t *testing.T) {
|
|
||||||
verifier := func(_ context.Context, token string, _ *http.Request) (*TokenInfo, error) {
|
|
||||||
if token == "valid" {
|
|
||||||
return &TokenInfo{Expiration: time.Now().Add(time.Hour), Scopes: []string{"read"}}, nil
|
|
||||||
}
|
|
||||||
return nil, ErrInvalidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
opts *RequireBearerTokenOptions
|
|
||||||
authHeader string
|
|
||||||
wantHeader string
|
|
||||||
wantStatus int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no middleware options",
|
|
||||||
opts: nil,
|
|
||||||
authHeader: "Bearer invalid",
|
|
||||||
wantHeader: "",
|
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "metadata only",
|
|
||||||
opts: &RequireBearerTokenOptions{
|
|
||||||
ResourceMetadataURL: "https://example.com/resource-metadata",
|
|
||||||
},
|
|
||||||
authHeader: "Bearer invalid",
|
|
||||||
wantHeader: "Bearer resource_metadata=\"https://example.com/resource-metadata\"",
|
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "scopes only",
|
|
||||||
opts: &RequireBearerTokenOptions{
|
|
||||||
Scopes: []string{"read", "write"},
|
|
||||||
},
|
|
||||||
authHeader: "Bearer invalid",
|
|
||||||
wantHeader: "Bearer scope=\"read write\"",
|
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "metadata and scopes",
|
|
||||||
opts: &RequireBearerTokenOptions{
|
|
||||||
ResourceMetadataURL: "https://example.com/resource-metadata",
|
|
||||||
Scopes: []string{"read", "write"},
|
|
||||||
},
|
|
||||||
authHeader: "Bearer invalid",
|
|
||||||
wantHeader: "Bearer resource_metadata=\"https://example.com/resource-metadata\", scope=\"read write\"",
|
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "insufficient scope",
|
|
||||||
opts: &RequireBearerTokenOptions{
|
|
||||||
Scopes: []string{"admin"},
|
|
||||||
},
|
|
||||||
authHeader: "Bearer valid", // Has "read", needs "admin" -> 403
|
|
||||||
wantHeader: "Bearer scope=\"admin\"",
|
|
||||||
wantStatus: http.StatusForbidden,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "success",
|
|
||||||
opts: &RequireBearerTokenOptions{
|
|
||||||
Scopes: []string{"read"},
|
|
||||||
},
|
|
||||||
authHeader: "Bearer valid",
|
|
||||||
wantHeader: "",
|
|
||||||
wantStatus: http.StatusOK,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
handler := RequireBearerToken(verifier, tt.opts)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}))
|
|
||||||
|
|
||||||
req := httptest.NewRequest("GET", "/", nil)
|
|
||||||
if tt.authHeader != "" {
|
|
||||||
req.Header.Set("Authorization", tt.authHeader)
|
|
||||||
}
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
if rec.Code != tt.wantStatus {
|
|
||||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := rec.Header().Get("WWW-Authenticate")
|
|
||||||
if got != tt.wantHeader {
|
|
||||||
t.Errorf("WWW-Authenticate = %q, want %q", got, tt.wantHeader)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestRequireBearerToken_ClockSkew verifies that the ClockSkew option
|
|
||||||
// extends the expiration check tolerance: a token whose Expiration is in the
|
|
||||||
// recent past is accepted iff the elapsed interval is within ClockSkew.
|
|
||||||
func TestRequireBearerToken_ClockSkew(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
clockSkew time.Duration
|
|
||||||
expiredAgo time.Duration
|
|
||||||
wantStatus int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no skew, fresh token accepted",
|
|
||||||
clockSkew: 0,
|
|
||||||
expiredAgo: -time.Minute, // expires in 1 minute
|
|
||||||
wantStatus: http.StatusOK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no skew, expired token rejected",
|
|
||||||
clockSkew: 0,
|
|
||||||
expiredAgo: 5 * time.Second, // expired 5s ago
|
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with skew, recently-expired token accepted",
|
|
||||||
clockSkew: 30 * time.Second,
|
|
||||||
expiredAgo: 5 * time.Second,
|
|
||||||
wantStatus: http.StatusOK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "with skew, token expired beyond tolerance rejected",
|
|
||||||
clockSkew: 10 * time.Second,
|
|
||||||
expiredAgo: 30 * time.Second,
|
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
verifier := func(_ context.Context, _ string, _ *http.Request) (*TokenInfo, error) {
|
|
||||||
return &TokenInfo{Expiration: time.Now().Add(-tt.expiredAgo)}, nil
|
|
||||||
}
|
|
||||||
handler := RequireBearerToken(verifier, &RequireBearerTokenOptions{
|
|
||||||
ClockSkew: tt.clockSkew,
|
|
||||||
})(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}))
|
|
||||||
|
|
||||||
req := httptest.NewRequest("GET", "/", nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer anything")
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
|
|
||||||
handler.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
if rec.Code != tt.wantStatus {
|
|
||||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue