Compare commits

..
104 changed files with 1362 additions and 10144 deletions

View file

@ -16,15 +16,11 @@ updates:
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" interval: "daily"
labels: labels:
# Mark PRs as CI related change. # Mark PRs as CI related change.
- T-CI - T-CI
open-pull-requests-limit: 3 open-pull-requests-limit: 3
groups:
codeql-action:
patterns:
- "github/codeql-action*"
commit-message: commit-message:
prefix: "chore" prefix: "chore"
include: "scope" include: "scope"

View file

@ -3,9 +3,6 @@ on:
# Runs workflow when activity on a PR in the workflow's repository occurs. # Runs workflow when activity on a PR in the workflow's repository occurs.
pull_request_target: pull_request_target:
permissions:
contents: read
jobs: jobs:
auto-label: auto-label:
permissions: permissions:
@ -23,7 +20,7 @@ jobs:
PR_URL: ${{ github.event.pull_request.html_url }} PR_URL: ${{ github.event.pull_request.html_url }}
steps: steps:
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7 - uses: actions/labeler@v7
with: with:
# Auto-include paths starting with dot (e.g. .github) # Auto-include paths starting with dot (e.g. .github)
dot: true dot: true

View file

@ -12,35 +12,34 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
ARTIFACT_DIR: release-artifacts ARTIFACT_DIR: release-artifacts
permissions:
contents: read
jobs: jobs:
commit-lint: commit-lint:
name: Lint Commit Messages name: Lint Commit Messages
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 uses: actions/setup-node@v7
with: with:
node-version: '22' node-version: '22'
- name: Install commitlint - name: Install commitlint
run: npm install --global @commitlint/cli@20.4.3 @commitlint/config-conventional@20.4.3 run: |
npm install --save-dev @commitlint/cli @commitlint/config-conventional
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
- name: Lint commit messages - name: Lint commit messages
run: commitlint --extends @commitlint/config-conventional --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose
fmt: fmt:
name: Code Formatting name: Code Formatting
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
- name: Install Rust fmt - name: Install Rust fmt
run: rustup toolchain install nightly --component rustfmt run: rustup toolchain install nightly --component rustfmt
@ -52,40 +51,32 @@ jobs:
name: Lint with Clippy name: Lint with Clippy
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Run clippy (all features) - name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings run: cargo clippy --all-targets --all-features -- -D warnings
- name: Run clippy (all features except local)
run: |
FEATURES=$(cargo metadata --no-deps --format-version 1 \
| jq -r '[.packages[] | select(.name == "rmcp") | .features | keys[]
| select(startswith("__") | not)
| select(. != "local")] | join(",")')
cargo clippy --package rmcp --all-targets --no-default-features --features "$FEATURES" -- -D warnings
semver: semver:
name: SemVer Check name: SemVer Check
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Install cargo-semver-checks - name: Install cargo-semver-checks
uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2.87.12 uses: taiki-e/install-action@v2.85.13
with: with:
tool: cargo-semver-checks tool: cargo-semver-checks
@ -126,7 +117,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
@ -134,12 +125,12 @@ jobs:
# to be installed (it does not need to be the default; the tool invokes it # to be installed (it does not need to be the default; the tool invokes it
# via `cargo +nightly`). # via `cargo +nightly`).
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@7c8d7d138f5c09cef361f8214cf96882cd029cdb # nightly uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Install cargo-public-api - name: Install cargo-public-api
uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2.87.12 uses: taiki-e/install-action@v2.85.13
with: with:
tool: cargo-public-api tool: cargo-public-api
@ -210,9 +201,9 @@ jobs:
name: spell check with typos name: spell check with typos
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
- name: Spell Check Repo - name: Spell Check Repo
uses: crate-ci/typos@d43b6c087ac471e2ea7b8af622ff15f05c0c365b # master uses: crate-ci/typos@master
msrv: msrv:
name: Check MSRV name: Check MSRV
@ -220,7 +211,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
- name: Read MSRV from Cargo.toml - name: Read MSRV from Cargo.toml
run: | run: |
@ -229,11 +220,11 @@ jobs:
echo "MSRV=$MSRV" >> "$GITHUB_ENV" echo "MSRV=$MSRV" >> "$GITHUB_ENV"
- name: Install MSRV Rust - name: Install MSRV Rust
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master uses: dtolnay/rust-toolchain@master
with: with:
toolchain: ${{ env.MSRV }} toolchain: ${{ env.MSRV }}
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Check default workspace members with MSRV - name: Check default workspace members with MSRV
run: cargo +${{ env.MSRV }} check --all-targets --all-features run: cargo +${{ env.MSRV }} check --all-targets --all-features
@ -242,19 +233,19 @@ jobs:
name: Run Tests name: Run Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
# install nodejs # install nodejs
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 uses: actions/setup-node@v7
with: with:
node-version: '22' node-version: '22'
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 uses: astral-sh/setup-uv@v7
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
- name: Set up Python - name: Set up Python
run: uv python install run: uv python install
@ -262,7 +253,7 @@ jobs:
- name: Create venv for python - name: Create venv for python
run: uv venv run: uv venv
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Run tests - name: Run tests
run: cargo test --all-features run: cargo test --all-features
@ -271,19 +262,19 @@ jobs:
name: Run Tests (no local feature) name: Run Tests (no local feature)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
# install nodejs # install nodejs
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 uses: actions/setup-node@v7
with: with:
node-version: '22' node-version: '22'
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 uses: astral-sh/setup-uv@v7
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
- name: Set up Python - name: Set up Python
run: uv python install run: uv python install
@ -291,7 +282,7 @@ jobs:
- name: Create venv for python - name: Create venv for python
run: uv venv run: uv venv
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Run tests without local feature - name: Run tests without local feature
run: | run: |
@ -305,21 +296,21 @@ jobs:
name: Code Coverage name: Code Coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: write
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
# install nodejs # install nodejs
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 uses: actions/setup-node@v7
with: with:
node-version: '22' node-version: '22'
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 uses: astral-sh/setup-uv@v7
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
- name: Set up Python - name: Set up Python
run: uv python install run: uv python install
@ -327,7 +318,7 @@ jobs:
- name: Create venv for python - name: Create venv for python
run: uv venv run: uv venv
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Install cargo-llvm-cov - name: Install cargo-llvm-cov
run: cargo install cargo-llvm-cov run: cargo install cargo-llvm-cov
@ -342,19 +333,19 @@ jobs:
name: Example test name: Example test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
# install nodejs # install nodejs
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 uses: actions/setup-node@v7
with: with:
node-version: '22' node-version: '22'
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 uses: astral-sh/setup-uv@v7
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
- name: Set up Python - name: Set up Python
run: uv python install run: uv python install
@ -362,7 +353,7 @@ jobs:
- name: Create venv for python - name: Create venv for python
run: uv venv run: uv venv
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Add target WASI preview 2 - name: Add target WASI preview 2
run: | run: |
@ -398,12 +389,12 @@ jobs:
name: Security Audit name: Security Audit
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Install cargo-audit - name: Install cargo-audit
run: cargo install cargo-audit run: cargo install cargo-audit
@ -415,12 +406,12 @@ jobs:
name: Generate Documentation name: Generate Documentation
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@7c8d7d138f5c09cef361f8214cf96882cd029cdb # nightly uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Generate documentation - name: Generate documentation
run: | run: |
@ -441,7 +432,7 @@ jobs:
# This happened recently in the attack on `tj-actions/changed-files`, but # This happened recently in the attack on `tj-actions/changed-files`, but
# has happened many times before as well. # has happened many times before as well.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
- name: Update Rust - name: Update Rust
run: | run: |

View file

@ -8,9 +8,6 @@ on:
schedule: schedule:
- cron: '0 0 * * 1' # Weekly on Monday - cron: '0 0 * * 1' # Weekly on Monday
permissions:
contents: read
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
@ -24,16 +21,16 @@ jobs:
language: [rust, javascript-typescript, python, actions] language: [rust, javascript-typescript, python, actions]
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 uses: actions/checkout@v7
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 uses: github/codeql-action/init@v4.37.7
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
config-file: ./.github/codeql/codeql-config.yml config-file: ./.github/codeql/codeql-config.yml
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 uses: github/codeql-action/autobuild@v4.37.7
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 uses: github/codeql-action/analyze@v4.37.7

View file

@ -14,9 +14,6 @@ concurrency:
env: env:
CONFORMANCE_VERSION: "0.2.0-alpha.10" CONFORMANCE_VERSION: "0.2.0-alpha.10"
permissions:
contents: read
jobs: jobs:
server: server:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -24,12 +21,12 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Build conformance binaries - name: Build conformance binaries
run: cargo build -p mcp-conformance run: cargo build -p mcp-conformance
@ -94,7 +91,7 @@ jobs:
- name: Upload results - name: Upload results
if: always() if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@v7
with: with:
name: conformance-server-results name: conformance-server-results
path: | path: |
@ -107,12 +104,12 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - uses: Swatinem/rust-cache@v2
- name: Build conformance binaries - name: Build conformance binaries
run: cargo build -p mcp-conformance run: cargo build -p mcp-conformance
@ -143,7 +140,7 @@ jobs:
- name: Upload results - name: Upload results
if: always() if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@v7
with: with:
name: conformance-client-results name: conformance-client-results
path: conformance-client-results path: conformance-client-results

View file

@ -1,6 +1,8 @@
name: Release-plz name: Release-plz
permissions: {} permissions:
pull-requests: write
contents: write
on: on:
push: push:
@ -16,20 +18,19 @@ jobs:
if: ${{ github.repository_owner == 'modelcontextprotocol' }} if: ${{ github.repository_owner == 'modelcontextprotocol' }}
permissions: permissions:
contents: write contents: write
pull-requests: read
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: "1.92" toolchain: "1.92"
# Using fork until semver_check_features support is released upstream. # Using fork until semver_check_features support is released upstream.
# See: https://github.com/release-plz/release-plz/pull/2757 # See: https://github.com/release-plz/release-plz/pull/2757
- name: Install release-plz from fork - name: Install release-plz from fork
run: cargo install --locked --git https://github.com/DaleSeo/release-plz --rev c2ea743bacf9bbd56e344df68219e60b0ad35d04 release-plz run: cargo install --locked --git https://github.com/DaleSeo/release-plz --branch feat/semver-check-features release-plz
- name: Run release-plz release - name: Run release-plz release
run: release-plz release run: release-plz release
env: env:
@ -50,17 +51,17 @@ jobs:
cancel-in-progress: false cancel-in-progress: false
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 uses: actions/checkout@v7
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: "1.92" toolchain: "1.92"
# Using fork until semver_check_features support is released upstream. # Using fork until semver_check_features support is released upstream.
# See: https://github.com/release-plz/release-plz/pull/2757 # See: https://github.com/release-plz/release-plz/pull/2757
- name: Install release-plz from fork - name: Install release-plz from fork
run: cargo install --locked --git https://github.com/DaleSeo/release-plz --rev c2ea743bacf9bbd56e344df68219e60b0ad35d04 release-plz run: cargo install --locked --git https://github.com/DaleSeo/release-plz --branch feat/semver-check-features release-plz
- name: Run release-plz release-pr - name: Run release-plz release-pr
run: release-plz release-pr run: release-plz release-pr
env: env:

View file

@ -1,48 +0,0 @@
# OpenSSF Scorecard supply-chain security analysis.
name: Scorecard supply-chain security
on:
branch_protection_rule:
schedule:
- cron: '5 23 * * 5'
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# Publishing only succeeds on the default branch. PR and manual runs
# still validate the analysis and produce SARIF without publishing.
publish_results: ${{ github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
- name: Upload SARIF artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: scorecard-results
path: results.sarif
retention-days: 5
- name: Upload to code scanning
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
with:
sarif_file: results.sarif

View file

@ -4,9 +4,6 @@ on:
- cron: "0 0 * * *" - cron: "0 0 * * *"
workflow_dispatch: workflow_dispatch:
permissions:
contents: read
jobs: jobs:
stale: stale:
name: Clean Up name: Clean Up
@ -15,7 +12,7 @@ jobs:
issues: write issues: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11 - uses: actions/stale@v11
with: with:
days-before-stale: 60 days-before-stale: 60
days-before-close: 14 days-before-close: 14

View file

@ -39,7 +39,7 @@ jobs:
TRIAGE_MODEL: ${{ vars.TRIAGE_MODEL || 'gpt-4o-mini' }} TRIAGE_MODEL: ${{ vars.TRIAGE_MODEL || 'gpt-4o-mini' }}
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@v7
- name: Install jq - name: Install jq
run: sudo apt-get install -y jq run: sudo apt-get install -y jq

View file

@ -4,13 +4,13 @@ default-members = ["crates/rmcp", "crates/rmcp-macros"]
resolver = "2" resolver = "2"
[workspace.dependencies] [workspace.dependencies]
rmcp = { version = "3.4.0", path = "./crates/rmcp" } rmcp = { version = "3.1.4", path = "./crates/rmcp" }
rmcp-macros = { version = "3.4.0", path = "./crates/rmcp-macros" } rmcp-macros = { version = "3.1.4", path = "./crates/rmcp-macros" }
[workspace.package] [workspace.package]
edition = "2024" edition = "2024"
rust-version = "1.88" rust-version = "1.88"
version = "3.4.0" version = "3.1.4"
authors = ["4t145 <u4t145@163.com>"] authors = ["4t145 <u4t145@163.com>"]
license = "Apache-2.0" license = "Apache-2.0"
repository = "https://github.com/modelcontextprotocol/rust-sdk/" repository = "https://github.com/modelcontextprotocol/rust-sdk/"

View file

@ -99,10 +99,10 @@ Use [`ClientServiceExt::serve_with_lifecycle`](crates/rmcp/src/service/client.rs
select another lifecycle explicitly: select another lifecycle explicitly:
```rust, ignore ```rust, ignore
use rmcp::{ClientLifecycleMode, ClientServiceExt, ProtocolVersion, model::ClientConfig}; use rmcp::{ClientInfo, ClientLifecycleMode, ClientServiceExt, ProtocolVersion};
// Start directly with server/discover and include client metadata on every request. // Start directly with server/discover and include client metadata on every request.
let client = ClientConfig::default() let client = ClientInfo::default()
.serve_with_lifecycle( .serve_with_lifecycle(
transport, transport,
ClientLifecycleMode::Discover { ClientLifecycleMode::Discover {
@ -113,7 +113,7 @@ let client = ClientConfig::default()
// Or probe the discover lifecycle and fall back when a legacy server reports // Or probe the discover lifecycle and fall back when a legacy server reports
// that server/discover is not implemented or does not respond within 10 seconds. // that server/discover is not implemented or does not respond within 10 seconds.
let client = ClientConfig::default() let client = ClientInfo::default()
.serve_with_lifecycle( .serve_with_lifecycle(
transport, transport,
ClientLifecycleMode::Auto { ClientLifecycleMode::Auto {
@ -369,8 +369,8 @@ use serde_json::json;
struct MyServer; struct MyServer;
impl ServerHandler for MyServer { impl ServerHandler for MyServer {
fn get_info(&self) -> ServerConfig { fn get_info(&self) -> ServerInfo {
ServerConfig::new( ServerInfo::new(
ServerCapabilities::builder() ServerCapabilities::builder()
.enable_resources() .enable_resources()
.build(), .build(),
@ -582,8 +582,8 @@ impl MyServer {
#[prompt_handler] #[prompt_handler]
impl ServerHandler for MyServer { impl ServerHandler for MyServer {
fn get_info(&self) -> ServerConfig { fn get_info(&self) -> ServerInfo {
ServerConfig::new(ServerCapabilities::builder().enable_prompts().build()) ServerInfo::new(ServerCapabilities::builder().enable_prompts().build())
} }
} }
``` ```
@ -934,8 +934,8 @@ Enable the logging capability, handle level changes from the client, and send lo
use rmcp::{ServerHandler, model::*, service::RequestContext}; use rmcp::{ServerHandler, model::*, service::RequestContext};
impl ServerHandler for MyServer { impl ServerHandler for MyServer {
fn get_info(&self) -> ServerConfig { fn get_info(&self) -> ServerInfo {
ServerConfig::new( ServerInfo::new(
ServerCapabilities::builder() ServerCapabilities::builder()
.enable_logging() .enable_logging()
.build(), .build(),
@ -1007,8 +1007,8 @@ Enable the completions capability and implement the `complete()` handler. Use `r
use rmcp::{ErrorData as McpError, ServerHandler, model::*, service::RequestContext, RoleServer}; use rmcp::{ErrorData as McpError, ServerHandler, model::*, service::RequestContext, RoleServer};
impl ServerHandler for MyServer { impl ServerHandler for MyServer {
fn get_info(&self) -> ServerConfig { fn get_info(&self) -> ServerInfo {
ServerConfig::new( ServerInfo::new(
ServerCapabilities::builder() ServerCapabilities::builder()
.enable_completions() .enable_completions()
.enable_prompts() .enable_prompts()
@ -1253,8 +1253,8 @@ use rmcp::{
}; };
impl ServerHandler for MyServer { impl ServerHandler for MyServer {
fn get_info(&self) -> ServerConfig { fn get_info(&self) -> ServerInfo {
ServerConfig::new( ServerInfo::new(
ServerCapabilities::builder() ServerCapabilities::builder()
.enable_tools() .enable_tools()
.enable_tool_list_changed() .enable_tool_list_changed()
@ -1363,21 +1363,11 @@ async fn call_tool(&self, request: CallToolRequestParams, _ctx: RequestContext<R
} }
``` ```
> **`requestState` is untrusted.** [SEP-2322 requires servers to validate > **`requestState` is untrusted.** The client echoes it back verbatim, so a
> it](https://modelcontextprotocol.io/seps/2322-MRTR#protocol-requirements-for-ephemeral-workflow) > stateless server that stores meaningful data in it MUST verify integrity
> because the client echoes it back verbatim. A stateless server that stores > first. Enable the `request-state` feature and use `RequestStateCodec` to seal
> meaningful data in it MUST verify integrity first. Enable the `request-state` > and open it (HMAC-tagged), or keep state server-side and use `requestState`
> feature and use `RequestStateCodec` to seal and open it (HMAC-tagged), or keep > only as an opaque handle.
> state server-side and use `requestState` only as an opaque handle.
For multi-replica deployments, use `RequestStateCodec::new_with_keyring` to
rotate signing keys without invalidating in-flight requests:
1. Deploy the old and new keys everywhere, continuing to emit `rs1` with the
old key via `with_rs1_signing("old")`.
2. Start emitting `rs2` with the new key while retaining the old key via
`with_rs1_fallback("old")`.
3. After the maximum `requestState` lifetime has elapsed, remove the old key.
### Client-side ### Client-side
@ -1584,7 +1574,7 @@ use rmcp::transport::StreamableHttpClientTransport;
// Defaults are stateless-friendly. // Defaults are stateless-friendly.
let transport = StreamableHttpClientTransport::from_uri("http://localhost:8000/mcp"); let transport = StreamableHttpClientTransport::from_uri("http://localhost:8000/mcp");
let client = ClientConfig::default().serve(transport).await?; let client = ClientInfo::default().serve(transport).await?;
``` ```
**Example:** [`examples/servers/src/counter_streamhttp.rs`](examples/servers/src/counter_streamhttp.rs) (server), [`examples/clients/src/streamable_http.rs`](examples/clients/src/streamable_http.rs) (client) **Example:** [`examples/servers/src/counter_streamhttp.rs`](examples/servers/src/counter_streamhttp.rs) (server), [`examples/clients/src/streamable_http.rs`](examples/clients/src/streamable_http.rs) (client)
@ -1638,27 +1628,9 @@ server example). The client transport connects with a single URI:
use rmcp::transport::StreamableHttpClientTransport; use rmcp::transport::StreamableHttpClientTransport;
let transport = StreamableHttpClientTransport::from_uri("http://localhost:8000/mcp"); let transport = StreamableHttpClientTransport::from_uri("http://localhost:8000/mcp");
let client = ClientConfig::default().serve(transport).await?; let client = ClientInfo::default().serve(transport).await?;
``` ```
The client allows up to 16 ordinary http POSTs at once. Configure this with
`StreamableHttpClientTransportConfig::with_uri(url).max_concurrent_requests(n)`;
`1` keeps ordinary POSTs serial, and `0` is treated as `1`. An open sse response
stream does not count against this limit. Cancellation and replies use a
separate queue with one extra POST slot. Configure their timeout with
`control_request_timeout` (default: five seconds). The timeout starts when the
POST starts, excluding time in the queue. Cancellation stops a queued or active
POST immediately.
For an open legacy response stream, the client stops reading but keeps the stream
alive until the cancellation send finishes or is dropped. This lets custom http
adapters handle cancellation before their stream state is removed.
Session recovery waits up to five seconds for old POSTs, then stops any that
remain. Those POSTs are not retried because the server may have processed them.
Configure this wait and the separate
reinitialization timeout with `session_recovery_timeout`. Callers still decide
which tools may run at the same time and which need approval.
#### Server-Sent Events (SSE) #### Server-Sent Events (SSE)
Streamable HTTP responses arrive as either a single `application/json` body or a Streamable HTTP responses arrive as either a single `application/json` body or a
@ -1742,10 +1714,10 @@ knows what the other supports. Declare yours with the `ServerCapabilities`
builder in `get_info()`: builder in `get_info()`:
```rust,ignore ```rust,ignore
use rmcp::model::{ServerCapabilities, ServerConfig}; use rmcp::model::{ServerCapabilities, ServerInfo};
fn get_info(&self) -> ServerConfig { fn get_info(&self) -> ServerInfo {
ServerConfig::new( ServerInfo::new(
ServerCapabilities::builder() ServerCapabilities::builder()
.enable_tools() .enable_tools()
.enable_prompts() .enable_prompts()

View file

@ -19,7 +19,6 @@ rmcp = { path = "../crates/rmcp", features = [
"elicitation", "elicitation",
"auth", "auth",
"auth-client-credentials-jwt", "auth-client-credentials-jwt",
"auth-enterprise-managed",
"request-state", "request-state",
"transport-streamable-http-server", "transport-streamable-http-server",
"transport-streamable-http-client-reqwest", "transport-streamable-http-client-reqwest",
@ -32,7 +31,6 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
axum = { version = "0.8", features = ["macros"] } axum = { version = "0.8", features = ["macros"] }
anyhow = "1" anyhow = "1"
oauth2 = { version = "5.0", default-features = false }
reqwest = { version = "0.13", features = ["json"] } reqwest = { version = "0.13", features = ["json"] }
urlencoding = "2" urlencoding = "2"
url = "2" url = "2"

View file

@ -1,10 +1,3 @@
#![expect(
deprecated,
reason = "The conformance suite still exercises deprecated sampling scenarios"
)]
use anyhow::Context;
use oauth2::{ClientSecret, RefreshToken};
use rmcp::{ use rmcp::{
ClientHandler, ClientLifecycleMode, ClientServiceExt, ErrorData, RoleClient, ServiceExt, ClientHandler, ClientLifecycleMode, ClientServiceExt, ErrorData, RoleClient, ServiceExt,
model::*, model::*,
@ -13,8 +6,7 @@ use rmcp::{
AuthClient, AuthorizationManager, StreamableHttpClientTransport, AuthClient, AuthorizationManager, StreamableHttpClientTransport,
auth::{ auth::{
AuthorizationCallback, AuthorizationRequest, ClientCredentialsConfig, AuthorizationCallback, AuthorizationRequest, ClientCredentialsConfig,
InMemoryCredentialStore, JwtSigningAlgorithm, OAuthState, default_oauth_http_client, InMemoryCredentialStore, JwtSigningAlgorithm, OAuthState,
enterprise::{EmaAuthorizationServer, EmaClientAuthentication, EmaExchangeRequest},
}, },
streamable_http_client::StreamableHttpClientTransportConfig, streamable_http_client::StreamableHttpClientTransportConfig,
}, },
@ -44,17 +36,6 @@ struct ConformanceContext {
private_key_pem: Option<String>, private_key_pem: Option<String>,
#[serde(default)] #[serde(default)]
signing_algorithm: Option<String>, signing_algorithm: Option<String>,
// enterprise-managed-authorization-refresh-token
#[serde(default)]
idp_client_id: Option<String>,
#[serde(default)]
idp_client_secret: Option<String>,
#[serde(default)]
idp_refresh_token: Option<String>,
#[serde(default)]
idp_issuer: Option<String>,
#[serde(default)]
idp_token_endpoint: Option<String>,
} }
fn load_context() -> ConformanceContext { fn load_context() -> ConformanceContext {
@ -74,8 +55,8 @@ impl ClientHandler for BasicClientHandler {}
struct ElicitationDefaultsClientHandler; struct ElicitationDefaultsClientHandler;
impl ClientHandler for ElicitationDefaultsClientHandler { impl ClientHandler for ElicitationDefaultsClientHandler {
fn get_info(&self) -> ClientConfig { fn get_info(&self) -> ClientInfo {
let mut info = ClientConfig::default(); let mut info = ClientInfo::default();
info.capabilities.elicitation = Some( info.capabilities.elicitation = Some(
ElicitationCapability::new() ElicitationCapability::new()
.with_form(FormElicitationCapability::new().with_schema_validation(true)), .with_form(FormElicitationCapability::new().with_schema_validation(true)),
@ -163,8 +144,8 @@ impl ClientHandler for ElicitationDefaultsClientHandler {
struct FullClientHandler; struct FullClientHandler;
impl ClientHandler for FullClientHandler { impl ClientHandler for FullClientHandler {
fn get_info(&self) -> ClientConfig { fn get_info(&self) -> ClientInfo {
let mut info = ClientConfig::default(); let mut info = ClientInfo::default();
info.capabilities.elicitation = Some( info.capabilities.elicitation = Some(
ElicitationCapability::new() ElicitationCapability::new()
.with_form(FormElicitationCapability::new().with_schema_validation(true)), .with_form(FormElicitationCapability::new().with_schema_validation(true)),
@ -779,66 +760,6 @@ async fn run_client_credentials_jwt(
Ok(()) Ok(())
} }
/// Exchange the fixture's IdP refresh token, then exercise authenticated MCP access.
async fn run_ema_refresh_token_client(
server_url: &str,
ctx: &ConformanceContext,
) -> anyhow::Result<()> {
let manager = AuthorizationManager::new(server_url).await?;
let metadata = manager.resolve_metadata().await?.metadata;
let idp = EmaAuthorizationServer::new(
ctx.idp_issuer.as_deref().context("Missing idp_issuer")?,
ctx.idp_token_endpoint
.as_deref()
.context("Missing idp_token_endpoint")?,
ctx.idp_client_id
.as_deref()
.context("Missing idp_client_id")?,
)
.with_client_authentication(EmaClientAuthentication::ClientSecretBasic(
ClientSecret::new(
ctx.idp_client_secret
.clone()
.context("Missing idp_client_secret")?,
),
));
let resource_as = EmaAuthorizationServer::new(
metadata
.issuer
.context("Missing authorization server issuer")?,
metadata.token_endpoint,
ctx.client_id.as_deref().context("Missing client_id")?,
)
.with_client_authentication(EmaClientAuthentication::ClientSecretBasic(
ClientSecret::new(ctx.client_secret.clone().context("Missing client_secret")?),
));
let refresh_token = RefreshToken::new(
ctx.idp_refresh_token
.clone()
.context("Missing idp_refresh_token")?,
);
let http = default_oauth_http_client()?;
let token = EmaExchangeRequest::new(idp, resource_as, server_url, &refresh_token)
.with_scopes(manager.select_scopes(None, &[]))
.exchange(&http, &http)
.await?;
let transport = StreamableHttpClientTransport::from_config(
StreamableHttpClientTransportConfig::with_uri(server_url)
.auth_header(token.access_token.secret()),
);
let client = BasicClientHandler
.serve_with_lifecycle(transport, conformance_lifecycle())
.await?;
let tools = client.list_tools(Default::default()).await?;
for tool in tools.tools {
let args = build_tool_arguments(&tool);
client.call_tool(call_tool_params(tool.name, args)).await?;
}
client.cancel().await?;
Ok(())
}
/// Cross-app access flow (SEP-1046 extension). /// Cross-app access flow (SEP-1046 extension).
async fn run_cross_app_access_client( async fn run_cross_app_access_client(
server_url: &str, server_url: &str,
@ -1189,11 +1110,6 @@ async fn run_scenario(
"auth/client-credentials-basic" => run_client_credentials_basic(server_url, ctx).await?, "auth/client-credentials-basic" => run_client_credentials_basic(server_url, ctx).await?,
"auth/client-credentials-jwt" => run_client_credentials_jwt(server_url, ctx).await?, "auth/client-credentials-jwt" => run_client_credentials_jwt(server_url, ctx).await?,
// Auth - enterprise-managed authorization with a refresh-token subject
"auth/enterprise-managed-authorization-refresh-token" => {
run_ema_refresh_token_client(server_url, ctx).await?
}
// Auth - cross-app access // Auth - cross-app access
"auth/cross-app-access-complete-flow" => { "auth/cross-app-access-complete-flow" => {
run_cross_app_access_client(server_url, ctx).await? run_cross_app_access_client(server_url, ctx).await?

View file

@ -704,8 +704,8 @@ impl ServerHandler for ConformanceServer {
(name == "test_custom_header").then(custom_header_tool) (name == "test_custom_header").then(custom_header_tool)
} }
fn get_info(&self) -> ServerConfig { fn get_info(&self) -> ServerInfo {
ServerConfig::new( ServerInfo::new(
ServerCapabilities::builder() ServerCapabilities::builder()
.enable_prompts() .enable_prompts()
.enable_prompts_list_changed() .enable_prompts_list_changed()
@ -1744,9 +1744,7 @@ async fn main() -> anyhow::Result<()> {
tracing::info!("Starting conformance server on {}", bind_addr); tracing::info!("Starting conformance server on {}", bind_addr);
let server = ConformanceServer::new(); let server = ConformanceServer::new();
let config = StreamableHttpServerConfig::default() let config = StreamableHttpServerConfig::default();
.with_allowed_origins([format!("http://{bind_addr}")])
.enforce_origin_validation();
let service = StreamableHttpService::new( let service = StreamableHttpService::new(
move || Ok(server.clone()), move || Ok(server.clone()),
LocalSessionManager::default().into(), LocalSessionManager::default().into(),

View file

@ -7,32 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [3.4.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-macros-v3.3.0...rmcp-macros-v3.4.0) - 2026-09-15
### Added
- *(model)* add ServerConfig and ClientConfig ([#1266](https://github.com/modelcontextprotocol/rust-sdk/pull/1266))
### Fixed
- *(model)* deprecate ServerInfo and ClientInfo aliases ([#1156](https://github.com/modelcontextprotocol/rust-sdk/pull/1156))
## [3.3.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-macros-v3.2.0...rmcp-macros-v3.3.0) - 2026-09-10
### Added
- *(macros)* reject empty tool_router ([#1233](https://github.com/modelcontextprotocol/rust-sdk/pull/1233))
## [3.2.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-macros-v3.1.4...rmcp-macros-v3.2.0) - 2026-08-31
### Added
- add request-state key rotation ([#1128](https://github.com/modelcontextprotocol/rust-sdk/pull/1128))
### Fixed
- allow concurrent streamable http requests ([#1186](https://github.com/modelcontextprotocol/rust-sdk/pull/1186))
## [3.1.3](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-macros-v3.1.2...rmcp-macros-v3.1.3) - 2026-08-17 ## [3.1.3](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-macros-v3.1.2...rmcp-macros-v3.1.3) - 2026-08-17
### Fixed ### Fixed

View file

@ -56,7 +56,6 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream {
/// | `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. | /// | `router` | `Ident` | The name of the router function to be generated. Defaults to `tool_router`. |
/// | `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. | /// | `vis` | `Visibility` | The visibility of the generated router function. Defaults to empty. |
/// | `server_handler` | `flag` | When set, also emits `#[::rmcp::tool_handler]` on `impl ServerHandler for Self` so you can omit a separate `#[tool_handler]` block. | /// | `server_handler` | `flag` | When set, also emits `#[::rmcp::tool_handler]` on `impl ServerHandler for Self` so you can omit a separate `#[tool_handler]` block. |
/// | `allow_empty` | `flag` | When set, accepts an impl block with no `#[tool]` fn. Without it, an empty router is a compile error. |
/// ///
/// ## Example /// ## Example
/// ///
@ -123,37 +122,6 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> TokenStream {
/// } /// }
/// } /// }
/// ``` /// ```
///
/// ### Empty routers
///
/// Collecting tools is this attribute's whole purpose, so an impl block with no `#[tool]` fn is a
/// compile error rather than a router that silently serves nothing. Pass `allow_empty` when that
/// is what you want:
///
/// ```rust,ignore
/// #[tool_router(allow_empty)]
/// impl MyToolHandler {}
/// ```
///
/// The usual way to hit this by accident is a `macro_rules!` helper *inside* the impl block. An
/// attribute macro receives the unexpanded item, so `#[tool]` fns produced by such a helper are
/// invisible to `#[tool_router]`. Let the `macro_rules!` emit the whole annotated impl instead:
///
/// ```rust,ignore
/// macro_rules! define_tools {
/// ($($name:ident => $description:literal),* $(,)?) => {
/// #[tool_router]
/// impl MyToolHandler {
/// $(
/// #[tool(description = $description)]
/// async fn $name(&self) -> String { stringify!($name).to_owned() }
/// )*
/// }
/// };
/// }
///
/// define_tools!(my_tool => "what my tool does");
/// ```
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream { pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
tool_router::tool_router(attr.into(), input.into()) tool_router::tool_router(attr.into(), input.into())
@ -217,8 +185,8 @@ pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
/// ```rust,ignore /// ```rust,ignore
/// #[tool_handler] /// #[tool_handler]
/// impl ServerHandler for MyToolHandler { /// impl ServerHandler for MyToolHandler {
/// fn get_info(&self) -> ServerConfig { /// fn get_info(&self) -> ServerInfo {
/// ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) /// ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
/// } /// }
/// } /// }
/// ``` /// ```

View file

@ -116,7 +116,7 @@ pub(crate) enum CallerCapability {
Prompts, Prompts,
} }
/// Build a `get_info()` method that returns `ServerConfig` with the appropriate capabilities. /// Build a `get_info()` method that returns `ServerInfo` with the appropriate capabilities.
/// ///
/// The caller declares its own capability via `caller`. Sibling handler attributes /// The caller declares its own capability via `caller`. Sibling handler attributes
/// (`prompt_handler`, `tool_handler`) are detected automatically /// (`prompt_handler`, `tool_handler`) are detected automatically
@ -157,8 +157,8 @@ pub(crate) fn build_get_info(
} }
syn::parse2::<ImplItem>(quote! { syn::parse2::<ImplItem>(quote! {
fn get_info(&self) -> rmcp::model::ServerConfig { fn get_info(&self) -> rmcp::model::ServerInfo {
rmcp::model::ServerConfig::new( rmcp::model::ServerInfo::new(
rmcp::model::ServerCapabilities::builder() rmcp::model::ServerCapabilities::builder()
#(#capability_calls)* #(#capability_calls)*
.build() .build()

View file

@ -17,8 +17,6 @@ pub struct ToolRouterAttribute {
/// When set, also emit `#[::rmcp::tool_handler]` on `impl ServerHandler for Self` so callers /// When set, also emit `#[::rmcp::tool_handler]` on `impl ServerHandler for Self` so callers
/// can skip a separate `#[tool_handler]` block (expanded in a later macro pass). /// can skip a separate `#[tool_handler]` block (expanded in a later macro pass).
pub server_handler: bool, pub server_handler: bool,
/// When set, accept an impl block with no `#[tool]` fn instead of reporting an error.
pub allow_empty: bool,
} }
impl Default for ToolRouterAttribute { impl Default for ToolRouterAttribute {
@ -27,7 +25,6 @@ impl Default for ToolRouterAttribute {
router: format_ident!("tool_router"), router: format_ident!("tool_router"),
vis: None, vis: None,
server_handler: false, server_handler: false,
allow_empty: false,
} }
} }
} }
@ -38,7 +35,6 @@ pub fn tool_router(attr: TokenStream, input: TokenStream) -> syn::Result<TokenSt
router, router,
vis, vis,
server_handler, server_handler,
allow_empty,
} = ToolRouterAttribute::from_list(&attr_args)?; } = ToolRouterAttribute::from_list(&attr_args)?;
let mut item_impl = syn::parse2::<ItemImpl>(input)?; let mut item_impl = syn::parse2::<ItemImpl>(input)?;
// find all function marked with `#[rmcp::tool]` // find all function marked with `#[rmcp::tool]`
@ -62,19 +58,6 @@ pub fn tool_router(attr: TokenStream, input: TokenStream) -> syn::Result<TokenSt
} }
}) })
.collect(); .collect();
if tool_attr_fns.is_empty() && !allow_empty {
return Err(syn::Error::new_spanned(
&item_impl.self_ty,
format!(
"`#[tool_router]` found no `#[tool]` fn in this impl block, so `Self::{router}()` \
would serve no tools\n\
note: a `macro_rules!` invocation inside the impl block is not expanded before \
this attribute runs, so any `#[tool]` fn it generates is invisible here; let the \
`macro_rules!` emit the whole `#[tool_router] impl` instead\n\
note: use `#[tool_router(allow_empty)]` if an empty router is intended"
),
));
}
let mut routers = Vec::with_capacity(tool_attr_fns.len()); let mut routers = Vec::with_capacity(tool_attr_fns.len());
for handler in tool_attr_fns { for handler in tool_attr_fns {
let tool_attr_fn_ident = format_ident!("{handler}_tool_attr"); let tool_attr_fn_ident = format_ident!("{handler}_tool_attr");
@ -128,7 +111,6 @@ mod test {
router, router,
vis, vis,
server_handler, server_handler,
allow_empty,
} = ToolRouterAttribute::from_list(&attr_args)?; } = ToolRouterAttribute::from_list(&attr_args)?;
assert_eq!(router.to_string(), "test_router"); assert_eq!(router.to_string(), "test_router");
assert!(vis.is_some(), "vis = \"pub(crate)\" should parse"); assert!(vis.is_some(), "vis = \"pub(crate)\" should parse");
@ -136,10 +118,6 @@ mod test {
!server_handler, !server_handler,
"server_handler should default to false when omitted" "server_handler should default to false when omitted"
); );
assert!(
!allow_empty,
"allow_empty should default to false when omitted"
);
Ok(()) Ok(())
} }
@ -159,54 +137,4 @@ mod test {
assert!(server_handler); assert!(server_handler);
Ok(()) Ok(())
} }
#[test]
fn tool_router_rejects_an_impl_without_directly_visible_tool_fns() {
let input = quote! {
impl Probe {
a_capability!(broadcast = "what a capability would own");
}
};
let message = tool_router(TokenStream::new(), input)
.expect_err("an impl with no `#[tool]` fn should not compile")
.to_string();
assert!(
message.contains("`Self::tool_router()` would serve no tools"),
"{message}"
);
assert!(message.contains("macro_rules!"), "{message}");
assert!(message.contains("allow_empty"), "{message}");
}
#[test]
fn tool_router_names_the_custom_router_fn_when_it_is_empty() {
let message = tool_router(quote! { router = custom_router }, quote! { impl Probe {} })
.expect_err("an impl with no `#[tool]` fn should not compile")
.to_string();
assert!(
message.contains("`Self::custom_router()` would serve no tools"),
"{message}"
);
}
#[test]
fn tool_router_accepts_an_impl_with_a_tool_fn() -> syn::Result<()> {
let input = quote! {
impl Probe {
#[tool(description = "probe")]
async fn probe(&self) -> String { "probed".to_owned() }
}
};
let generated = tool_router(TokenStream::new(), input)?.to_string();
assert!(generated.contains("with_route"), "{generated}");
Ok(())
}
#[test]
fn tool_router_allow_empty_generates_a_router_without_routes() -> syn::Result<()> {
let generated = tool_router(quote! { allow_empty }, quote! { impl Probe {} })?.to_string();
assert!(generated.contains("fn tool_router"), "{generated}");
assert!(!generated.contains("with_route"), "{generated}");
Ok(())
}
} }

View file

@ -7,55 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [3.4.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-v3.3.0...rmcp-v3.4.0) - 2026-09-15
### Added
- *(model)* add ServerConfig and ClientConfig ([#1266](https://github.com/modelcontextprotocol/rust-sdk/pull/1266))
### Fixed
- *(rmcp)* use ServerConfig in cancellation test ([#1273](https://github.com/modelcontextprotocol/rust-sdk/pull/1273))
- *(rmcp)* route peer cancellation by lifecycle ([#1262](https://github.com/modelcontextprotocol/rust-sdk/pull/1262))
- run first pre-init request in service loop ([#1263](https://github.com/modelcontextprotocol/rust-sdk/pull/1263))
- *(auth)* let url origin decide prm discovery ([#1264](https://github.com/modelcontextprotocol/rust-sdk/pull/1264))
- *(model)* deprecate ServerInfo and ClientInfo aliases ([#1156](https://github.com/modelcontextprotocol/rust-sdk/pull/1156))
- *(auth)* ignore non-metadata JSON when probing for protected resource metadata ([#1204](https://github.com/modelcontextprotocol/rust-sdk/pull/1204))
- do not treat malformed JSON 200 as Accepted for requests ([#1208](https://github.com/modelcontextprotocol/rust-sdk/pull/1208))
- *(streamable-http-server)* map handler-generated HeaderMismatch to HTTP 400 ([#1259](https://github.com/modelcontextprotocol/rust-sdk/pull/1259))
- *(http)* enforce Origin validation semantics ([#1192](https://github.com/modelcontextprotocol/rust-sdk/pull/1192))
## [3.3.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-v3.2.0...rmcp-v3.3.0) - 2026-09-10
### Added
- add ServerHandler::negotiate_initialize ([#1247](https://github.com/modelcontextprotocol/rust-sdk/pull/1247))
- *(macros)* reject empty tool_router ([#1233](https://github.com/modelcontextprotocol/rust-sdk/pull/1233))
- *(auth)* add enterprise refresh-token and ID-JAG exchanges ([#1234](https://github.com/modelcontextprotocol/rust-sdk/pull/1234))
### Fixed
- *(sse)* saturate exponential reconnect backoff to avoid overflow panic ([#1231](https://github.com/modelcontextprotocol/rust-sdk/pull/1231))
- resolve clippy warnings across workspace ([#1195](https://github.com/modelcontextprotocol/rust-sdk/pull/1195))
- *(auth)* unify refresh checks and error handling ([#1236](https://github.com/modelcontextprotocol/rust-sdk/pull/1236))
### Other
- *(deps)* update process-wrap requirement from 9.0 to 10.0 ([#1229](https://github.com/modelcontextprotocol/rust-sdk/pull/1229))
## [3.2.0](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-v3.1.4...rmcp-v3.2.0) - 2026-08-31
### Added
- *(auth)* coordinate OAuth refreshes through credential stores ([#1232](https://github.com/modelcontextprotocol/rust-sdk/pull/1232))
- add request-state key rotation ([#1128](https://github.com/modelcontextprotocol/rust-sdk/pull/1128))
### Fixed
- keep initialize on legacy protocol versions ([#1228](https://github.com/modelcontextprotocol/rust-sdk/pull/1228))
- *(transport)* fall back after sessionless HTTP discover rejections ([#1211](https://github.com/modelcontextprotocol/rust-sdk/pull/1211))
- allow concurrent streamable http requests ([#1186](https://github.com/modelcontextprotocol/rust-sdk/pull/1186))
## [3.1.4](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-v3.1.3...rmcp-v3.1.4) - 2026-08-18 ## [3.1.4](https://github.com/modelcontextprotocol/rust-sdk/compare/rmcp-v3.1.3...rmcp-v3.1.4) - 2026-08-18
### Fixed ### Fixed

View file

@ -18,7 +18,6 @@ exhaustive_enums = "warn"
features = [ features = [
"auth", "auth",
"auth-client-credentials-jwt", "auth-client-credentials-jwt",
"auth-enterprise-managed",
"base64", "base64",
"client", "client",
"client-side-sse", "client-side-sse",
@ -88,7 +87,7 @@ url = { version = "2.4", optional = true }
tower-service = { version = "0.3", optional = true } tower-service = { version = "0.3", optional = true }
# for child process transport # for child process transport
process-wrap = { version = "10.0", features = ["tokio1"], optional = true } process-wrap = { version = "9.0", features = ["tokio1"], optional = true }
# for cross-platform executable path resolution # for cross-platform executable path resolution
which = { version = "8", optional = true } which = { version = "8", optional = true }
@ -197,11 +196,10 @@ transport-streamable-http-server-session = [
tower = ["dep:tower-service"] tower = ["dep:tower-service"]
auth = ["dep:async-trait", "dep:oauth2", "__reqwest", "dep:url"] auth = ["dep:async-trait", "dep:oauth2", "__reqwest", "dep:url"]
auth-client-credentials-jwt = ["auth", "dep:jsonwebtoken", "uuid"] auth-client-credentials-jwt = ["auth", "dep:jsonwebtoken", "uuid"]
auth-enterprise-managed = ["auth", "base64"]
schemars = ["dep:schemars"] schemars = ["dep:schemars"]
[dev-dependencies] [dev-dependencies]
tokio = { version = "1", features = ["full", "test-util"] } tokio = { version = "1", features = ["full"] }
schemars = { version = "1.1.0", features = ["chrono04"] } schemars = { version = "1.1.0", features = ["chrono04"] }
axum = { version = "0.8", default-features = false, features = ["http1", "tokio"] } axum = { version = "0.8", default-features = false, features = ["http1", "tokio"] }
hyper = { version = "1", features = ["server", "http1"] } hyper = { version = "1", features = ["server", "http1"] }
@ -215,7 +213,7 @@ tracing-subscriber = { version = "0.3", features = [
"fmt", "fmt",
] } ] }
async-trait = "0.1" async-trait = "0.1"
rstest = "0.27.0" rstest = "0.26.1"
[[test]] [[test]]
name = "test_tool_macros" name = "test_tool_macros"
required-features = ["server", "client"] required-features = ["server", "client"]
@ -298,11 +296,6 @@ name = "test_streamable_http_json_response"
required-features = ["server", "client", "transport-streamable-http-server", "reqwest"] required-features = ["server", "client", "transport-streamable-http-server", "reqwest"]
path = "tests/test_streamable_http_json_response.rs" path = "tests/test_streamable_http_json_response.rs"
[[test]]
name = "test_streamable_http_client_concurrency"
required-features = ["client", "transport-streamable-http-client"]
path = "tests/test_streamable_http_client_concurrency.rs"
[[test]] [[test]]
name = "test_streamable_http_protocol_version" name = "test_streamable_http_protocol_version"
required-features = ["server", "client", "transport-streamable-http-server", "reqwest"] required-features = ["server", "client", "transport-streamable-http-server", "reqwest"]
@ -348,15 +341,6 @@ name = "test_streamable_http_4xx_error_body"
required-features = ["transport-streamable-http-client", "transport-streamable-http-client-reqwest"] required-features = ["transport-streamable-http-client", "transport-streamable-http-client-reqwest"]
path = "tests/test_streamable_http_4xx_error_body.rs" path = "tests/test_streamable_http_4xx_error_body.rs"
[[test]]
name = "test_streamable_http_malformed_json_request"
required-features = [
"client",
"transport-streamable-http-client",
"transport-streamable-http-client-reqwest",
]
path = "tests/test_streamable_http_malformed_json_request.rs"
[[test]] [[test]]
name = "test_custom_request" name = "test_custom_request"

Some files were not shown because too many files have changed in this diff Show more