Commit graph go-sdk/internal
Author SHA1 Message Date
Guglielmo Colombo
5cb8339210
mcp: update return meta in DiscoverResult (SEP-2575) (#1097)
This PR implements the latest changes introduced by
https://github.com/modelcontextprotocol/modelcontextprotocol/pull/3002
2026-07-16 16:10:12 +02:00
Guglielmo Colombo
fa548d7401
docs: update docs (#1089) 2026-07-16 11:11:22 +02:00
Guglielmo Colombo
9576afc114
mcp: wrap unmarshalParams with jsonrpc error (#1087)
Fixes #976
2026-07-14 14:26:18 +00:00
Guglielmo Colombo
c79a30ae0a
mcp: add param check for CompleteResult (#1080)
Fixes #1071
2026-07-13 12:14:21 +00:00
Guglielmo Colombo
22d4988a16
mcp: update docs references (#1023)
Update docs
2026-06-24 12:39:39 +00:00
Guglielmo Colombo
b9e6fc5a66
mcp: add documentation for new protocol version (#1019)
Add documentation for new protocol version in the `internal/docs`
2026-06-23 17:07:38 +02:00
Guglielmo Colombo
b6649db4f9
mcp: Deprecate roots, sampling and logging (SEP-2577) (#1017)
Fixes #1013
2026-06-23 11:11:08 +02:00
Guglielmo Colombo
cd7d80c591
mcp: Implement subscriptions/listen rpc (SEP-2575) (#1007)
Fixes #966
2026-06-22 18:57:36 +02:00
Guglielmo Colombo
3486f6bcaf
mco: update JSON-RPC error codes to align with updated MCP specification (#1016)
This PR updates the error codes to align with the updated specifications
in
https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2907

ErrServerClosing and ErrClientClosing are reverted to their 1.6.0
version codes, as they do not overlap anymore with the newly introduced
error codes.
2026-06-22 10:27:31 +02:00
Guglielmo Colombo
b85574f685
mcp: Enable legacy initialize fallback on any error (#1014)
To guarantee a better compatibility with legacy server, enable the
fallback to the legacy initialize rpc in case of any error returned by
the server, in case of `server/discover` request.
2026-06-18 15:52:11 +02:00
Guglielmo Colombo
71ffcb412a
mcp: add MCPGPDEBUG for opt-in Content-Type check (#1012)
Copy of existing
[PR](https://github.com/modelcontextprotocol/go-sdk/pull/972) patching
1.6.0 version
2026-06-17 11:41:35 +02:00
Thomas Bechtold
d9728a88f3
auth: bind refreshing token source to a background context, not the request ctx (#988)
## Problem

`AuthorizationCodeHandler.exchangeAuthorizationCode` builds the
long-lived token source from the **request context** passed to
`Authorize()`:

```go
clientCtx := context.WithValue(ctx, oauth2.HTTPClient, h.config.Client)
...
h.tokenSource = cfg.TokenSource(clientCtx, token)
```

`golang.org/x/oauth2` captures that context and reuses it for **every**
future refresh:
- `oauth2.go`: `Config.TokenSource(ctx, …)` stores `ctx` in
`tokenRefresher{ctx}`
- `tokenRefresher.Token()` (no ctx arg) calls `retrieveToken(tf.ctx, …)`
- `internal/token.go`: `ContextClient(ctx).Do(req.WithContext(ctx))`

`Authorize()` is normally invoked from a request- or connect-scoped
context — e.g. the streamable client transport calls it during
`setMCPHeaders`/`initialize`, and that context is cancelled once the
operation completes. The cached access token works until expiry; the
**first refresh after expiry** then fails instantly with `context
canceled`, before any HTTP request is sent. The connection can no longer
refresh.

`setMCPHeaders` only re-invokes `Authorize()` for an `invalid_grant`
`oauth2.RetrieveError` (#917). A `context canceled` is not a
`RetrieveError`, so it is never recovered — the connection is wedged
until the client reconnects.

### Reproduce

1. Connect a `StreamableClientTransport` with an
`AuthorizationCodeHandler` to a server whose `initialize` returns 401
(so `Authorize()` runs during connect, under the connect context).
2. Let the connect context be cancelled after connect returns (the
common pattern: a `context.WithTimeout(parent, …)` + `defer cancel()`
bounding the interactive flow).
3. Wait for the access token to expire and issue a request.
4. Every request fails with `Post "<token endpoint>": context canceled`
and `dur≈0` — the refresh never hits the network.

## Fix

A token source outlives the request that created it, so bind its
refreshes to `context.Background()` (still carrying the configured HTTP
client via `oauth2.HTTPClient`). The one-shot token exchange keeps using
the request context.

```go
refreshCtx := context.WithValue(context.Background(), oauth2.HTTPClient, h.config.Client)
h.tokenSource = cfg.TokenSource(refreshCtx, token)
```

`auth` package tests pass. Behaviour is otherwise unchanged: the same
HTTP client value is carried; only the cancellation parent differs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-06-16 16:42:53 +02:00
Guglielmo Colombo
79e1a13dcf
mcp: tool.InputSchema and tool.OutputSchema validation (SEP-2106) (#1009)
Fixes #990
2026-06-16 13:55:47 +02:00
Piyush Bag
bcbfe0ce36
fix(jsonrpc2): decode requests when method key is present (#1000)
## Summary

Fixes #976.

`DecodeMessage` used `msg.Method != ""` to classify messages. A wire
payload with `"method":""` was decoded as a **response**, so stdio
servers dropped it without a correlated JSON-RPC error.

## Approach

Decode into a `wireDecode` struct whose `method` field is
`json.RawMessage`. Treat the message as a **request** when the `method`
key is **present on the wire** (including `""`), which matches JSON-RPC
semantics.

This revision:

- Uses a **single JSON parse** per message (dedicated decode struct, no
secondary fields-map unmarshal)
- Documents why `json.RawMessage` is used for `method`
- Adds regression coverage so normal responses and id-only payloads
without a `method` key stay on the response path

## Test plan

- [x] `go test ./internal/jsonrpc2/... -v`
- [x] `go test ./mcp/... -run TestIOConnRead_EmptyMethod -v`
- [x] `go test ./... -count=1`
- [x] `go test -race ./internal/jsonrpc2/... -count=1`
- [x] Server conformance (`npx @modelcontextprotocol/conformance server
--url http://127.0.0.1:3000/mcp`) — 40 passed, 0 failed
- [x] Client conformance (`./scripts/client-conformance.sh`) — 217
passed, 0 failed

## Scope note

This PR addresses the primary repro in #976 (empty `method` with valid
`id`). Other cases listed in the issue (`id: null`, structurally invalid
envelopes) are unchanged.

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-06-16 09:57:18 +02:00
Guglielmo Colombo
dd978160ab
mcp: Implement server-side support for discover method (SEP-2575) (#987)
## Summary
Part 3 of the SEP-2575 ("Make MCP Stateless") implementation. Builds on
PR #975 which landed the client-side discover/fallback flow and the
per-request `_meta` plumbing. This PR adds the **server-side**
counterparts:

- `server/discover` handler (replaces the
stub),ProtocolVersionSupporter`
- transport hook for per-transport version filtering,
- transport-agnostic version validation and method-removal enforcement
in `ServerSession.handle`,
- HTTP-level wire compliance for SEP-2575 error codes/status codes,
- bug fix in `internal/jsonrpc2`,`processResult` with an `MCPGODEBUG`
opt-out for backward compatibility.
2026-06-08 17:52:10 +02:00
Guglielmo Colombo
c60a318d22
mcp: Implement support for SEP-2575 on client side (#975)
Implements the client half of the SEP-2575 / `2026-06-30` protocol. This
CL is client-only; server-side `server/discover` is still a
`MethodNotFound` stub.
### Changes
- **`server/discover` probe in `Client.Connect()`** (`client.go`). When
the caller opts into `protocolVersion >= 2026-06-30`, the client probes
the server with `server/discover` before falling back to the legacy
`initialize`
  handshake.
- **Per-request `_meta` injection** (`shared.go`, `injectRequestMeta`).
For`>= 2026-06-30` sessions every outgoing request gets the SEP-2575
triple (`protocolVersion`, `clientInfo`, `clientCapabilities`).
- **Server-side fix**: `ServerSession.handle` no longer pre-populates
`state.InitializeParams` from a `server/discover` request's `_meta`, so
a subsequent fallback `initialize` is not rejected as a duplicate.
### Fallback rules
Client falls back to the legacy `initialize` handshake when discover
returns:
- `MethodNotFound` (-32601),
- `UnsupportedProtocolVersionError` (-32004),
- an HTTP `Bad Request` (substring-matched on the error message, to
interop
  with pre-2026-06-30 servers).

Fixes #966
2026-06-02 14:17:01 +02:00
Guglielmo Colombo
dfb45f1e11
mcp: add optional issuer validator for pre-registered client validation (SEP-2352) (#946)
Fixes #978
2026-05-29 12:46:34 +00:00
Max Gerber
2d47cc9664
auth: issuer mix-up mitigation (#859)
This PR functions as a Reference implementation of
[SEP-2468](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2468)
/ [RFC9207](https://datatracker.ietf.org/doc/rfc9207/).

This PR hardens the MCP OAuth Client functionality against Mix-Up
attacks:
>   Mix-up attacks aim to steal an authorization code or access token by
>   tricking the client into sending the authorization code or access
> token to the attacker instead of the honest authorization or resource
>   server

This PR hardens the client by adding support for a new `iss` parameter
in authorization responses:
- Authorization Servers broadcast support for the `iss` parameter via
the `authorization_response_iss_parameter_supported` metadata parameter
- If the parameter is supported, clients expect to receive the `iss`
parameter in the authorization response
- Clients compare the `iss` parameter in the authorization response to
the `Issuer` parameter in the authorization metadata. The two must match
exactly for the response to be processed.

Fixes #941
2026-05-27 11:39:08 +02:00
Pavel Bazin
c044cdb734
fix: do not omitempty ReadOnlyHint in ToolAnnotations (#908)
`ReadOnlyHint` is a `bool` with omitempty, so the zero value (false) is
indistinguishable from unset, and **drops out** of marshaled JSON.

The current behavior causes an issue with the OpenAI MCP app submission
because they require explicit hints.

Consumers that explicitly set `ReadOnlyHint: false` on write tools lose
the field on the wire. Removing `omitempty` ensures `false` is always
serialized, which matches the MCP spec default.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Maciej Kisiel <mkisiel@google.com>
Co-authored-by: guglielmoc <guglielmoc@google.com>
2026-05-25 15:52:15 +02:00
Guglielmo Colombo
bf69179066
mcp: Add client-side scope accumulation (#944)
## Description

This PR implements
https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2350

Fixes #943

---------

Co-authored-by: Maciej Kisiel <mkisiel@google.com>
2026-05-18 08:26:02 +00:00
Maciej Kisiel
8eb54632ad
mcp: remove session header handling in stateless mode (#952) 2026-05-15 09:51:14 +02:00
Guglielmo Colombo
438cd4384c
mcp: OIDC-Flavored Refresh Token Guidance (#939)
## Description

Implemets SEP-2207
(https://modelcontextprotocol.io/seps/2207-oidc-refresh-token-guidance)

Fixes #940
2026-05-06 15:43:26 +02:00
Yaroslav
2b7c9937e6
mcp: align resource not found error code with sep-2164 (#931)
Change the error code returned by ResourceNotFoundError from -32002 to
-32602 (jsonrpc.CodeInvalidParams), aligning with
[SEP-2164](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2164).

* Not gated with a protocol version check, because the previous error
code was never formally specified and the behaviour varied across SDKs.
* In case clients which rely on the old error code exist, the previous
behaviour can be restored with `MCPGODEBUG=customresnotfounderrcode=1`.
* `CodeResourceNotFound` deprecated since its only purpose going forward
is backward compatibility. The new code should use
`jsonrpc.CodeInvalidParams` directly.
* `CodeResourceNotFound` changed to `var` so it can stay in sync with
`ResourceNotFoundError` and existing code doing `code ==
mcp.CodeResourceNotFound` keeps working in both the default and compat
modes.
2026-05-05 16:20:02 +02:00
Maciej Kisiel
f5f2015413
MCPGODEBUG update for 1.6.0 (#893) 2026-04-30 09:54:32 +02:00
Maciej Kisiel
93a41b2cfa
internal/jsonrpc2: remove unused code (#910)
Remove code inherited from x/tools/internal/jsonrpc2_v2 that is not used
by MCP transports. MCP transports implement their own framing and
connection management, using only the Connection engine and message
encode/decode from this package.

Removed files:
- serve.go: Server, Listener, Dialer, idleListener, Dial, NewServer
- net.go: NetListener, NetDialer, NetPipeListener
- serve_test.go, jsonrpc2_test.go: tests for removed infrastructure

Removed from remaining files:
- frame.go: Framer interface, RawFramer, HeaderFramer implementations
- conn.go: Binder, BinderFunc, ConnectionOptions, bindConnection
- jsonrpc2.go: ErrIdleTimeout, PreempterFunc, defaultHandler, async
helper
- wire.go: ErrServerOverloaded
- conn.go: AsyncCall.IsReady (unused method)
2026-04-28 09:56:15 +02:00
Ravish Gupta
2e21834ad3
extauth: add ClientCredentialsHandler for OAuth client credentials grant (#895)
## Summary

- Add `ClientCredentialsHandler` implementing `auth.OAuthHandler` using
the OAuth 2.0 Client Credentials grant (RFC 6749 Section 4.4) for
service-to-service authentication with pre-registered credentials.
- Bypasses both dynamic client registration and the authorization code
flow — the handler takes a client ID + secret directly and exchanges
them at the token endpoint.
- Supports two modes: direct `TokenEndpoint` URL, or metadata discovery
via `AuthServerURL` (RFC 8414).
- Add `client_credentials` grant type support to the fake authorization
server in `internal/oauthtest` for testing.

## Context

Per @jba's [comment on
#627](https://github.com/modelcontextprotocol/go-sdk/issues/627#issuecomment-3640982691):

> Our default implementation for that framework should support client ID
and client secret as options, to handle that variant. [...] If those are
set, our implementation would bypass dynamic client reservation. That is
just a couple of lines of code, I believe.

The client-side OAuth scaffolding (#785) that this was blocked on is now
complete, as @brkane noted.

Implements the client credentials variant of
[SEP-1046](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1046).
The JWT Assertions variant (RFC 7523) is left for a follow-up as its API
surface needs more design discussion.

## Test plan

- [x] `TestNewClientCredentialsHandler_Validation` — validates all
config error cases (nil config, missing fields, mutual exclusivity)
- [x] `TestClientCredentialsHandler_Authorize/direct_token_endpoint` —
end-to-end with fake auth server
- [x] `TestClientCredentialsHandler_Authorize/metadata_discovery` —
discovers token endpoint via RFC 8414 metadata
- [x] `TestClientCredentialsHandler_Authorize/bad_credentials` —
verifies failure with wrong secret
- [x] `go test ./... -count=1` passes
- [x] `go vet ./...` clean

Refs #627

Co-authored-by: Maciej Kisiel <mkisiel@google.com>
2026-04-27 13:28:35 +02:00
Maciej Kisiel
41e1f941bb
mcp: remove default cross origin protection (#906)
Fixes #902
2026-04-24 11:35:22 +02:00
Maciej Kisiel
15e93a231c
mcp: add DNS rebinding and cross origin protections to SSE transport (#891)
Similar protections were already introduced before to the Streamable
transport.
2026-04-14 11:36:20 +02:00
Dinesh
1209861858
Add documentation backed by mkdocs (#883)
Affects /docs - (Refined Documentation for deployment and added Some
Supporting documentation)
Added a github action for deployment

Fixes #835  

Added Support for mkdocs
Example -  https://dinesht04.github.io/go-sdk/

Requires Configuration of Github pages as follows:-

1. Enable Github Pages
2. Select gh-pages branch for deployment

<img width="1237" height="817" alt="gh-pages"
src="https://github.com/user-attachments/assets/01f0b012-d5f3-4011-9af9-7269e657ae6a"
/>

## Sources
# For mkdocs.yaml
1.
https://github.com/modelcontextprotocol/python-sdk/blob/main/mkdocs.yml
2. https://github.com/modelcontextprotocol/java-sdk/blob/main/mkdocs.yml
  3. MKDocs official documentation
# For Github Action
1.
https://github.com/modelcontextprotocol/python-sdk/blob/main/.github/workflows/publish-docs-manually.yml
2.
https://github.com/modelcontextprotocol/java-sdk/blob/main/.github/workflows/docs.yml
3.
https://github.com/james-willett/material-mkdocs-youtube-2024/blob/main/.github/workflows/ci.yml
- From Youtube Tutorial
2026-04-09 14:25:43 +02:00
Maciej Kisiel
f36591bf8a
extauth: refactor enterprise auth tests (#879) 2026-04-08 13:42:59 +02:00
Oleksandr Redko
e23b75cd3f
internal: remove unused util functions (#871)
The functions util.Sorted and util.KeySlice are unused.
2026-03-31 15:40:06 +02:00
Maciej Kisiel
638d404e0d
all: stabilize client OAuth support (#861)
Also fix lint errors that were hidden because of the build tag.
2026-03-31 12:36:31 +02:00
Oleksandr Redko
a1f808c86e
all: modernize code (#868)
Use `go fix ./...` to modernize the code.
2026-03-31 10:58:02 +02:00
Oleksandr Redko
b71a8391a6
all: fix typos (#869)
Grammar issues in docs, comments, and error messages.
2026-03-30 18:43:01 +02:00
Pranav RK
2d43865b97
Enterprise managed authorization (#770)
`auth`, `oauthex`: implement Enterprise Managed Authorization (SEP-990)
This PR implements Enterprise Managed Authorization (SEP-990) for the Go
MCP SDK, enabling MCP Clients and Servers to leverage enterprise
Identity Providers for seamless authorization without requiring users to
authenticate separately to each MCP Server.
 Overview
Enterprise Managed Authorization follows the Identity Assertion
Authorization Grant specification
(draft-ietf-oauth-identity-assertion-authz-grant), implementing a
three-step flow:
1. **Single Sign-On (SSO)**: User authenticates to the MCP Client via
enterprise IdP (Okta, Auth0, Azure AD, etc.)
2. **Token Exchange (RFC 8693)**: Client exchanges ID Token for Identity
Assertion JWT Authorization Grant (ID-JAG) at the IdP
3. **JWT Bearer Grant (RFC 7523)**: Client exchanges ID-JAG for Access
Token at the MCP Server
This enables:
- **For end users**: Single sign-on across MCP Clients and Servers—no
manual connection/authorization per server
- **For enterprise admins**: Centralized visibility and control over
which MCP Servers can be used within the organization
- **For MCP clients**: Automatic token acquisition without user
interaction for each server

Closes:
[#628](https://github.com/modelcontextprotocol/go-sdk/issues/628)

---------

Co-authored-by: Maciej Kisiel <mkisiel@google.com>
2026-03-30 16:29:30 +02:00
Maciej Kisiel
05277f6e3c
oauthex: use internal JSON library for decoding. (#866) 2026-03-30 14:52:15 +02:00
Maciej Kisiel
ea161959d7
all: clean up Go 1.24 specific code. (#850) 2026-03-20 12:28:56 +01:00
Maciej Kisiel
a433a831d6
mcp: verify 'Origin' and 'Content-Type' headers (#842)
This PR introduces protection against cross-origin requests by
installing `http.CrossOriginProtection`.
By default, the zero value is used which does not contain any trusted
origins or bypass patterns.
The protection can be customized by providing a custom
`http.CrossOriginProtection` in `mcp.StreamableHTTPOptions`. Currently,
the deny handler set on the protection is ignored.
This default has an MCPGODEBUG option (`disableoriginverification`) to
disable the default protection.
It will be available until `v1.6.0` of the SDK.
We also increase the Go version required by the SDK to 1.25, since this
is the version that introduced `http.CrossOriginProtection`. This is in
line with the SDK's policy of supporting two newest Go versions (1.26
was released in February 2026).

Additionally, we start validating if the `Content-Type` header for
`POST` requests is set to `application/json` to avoid accepting
CORS-safelisted requests.
2026-03-13 11:36:41 +01:00
Maciej Kisiel
d836ceb87d
auth: allow passing custom http.Client to AuthorizationCodeHandler (#840) 2026-03-13 10:06:58 +01:00
Maciej Kisiel
724dd47aa3
internal: fix Unicode zero character handling (#841)
A fix was submitted to our dependency, tests added to validate the
problematic scenario.

We also added some fuzz tests to check for additional issues, none were
found.
2026-03-12 21:54:23 +01:00
Maciej Kisiel
c9317fb5b7
all: client side OAuth support (#785)
This PR introduces client-side OAuth support.

# Overall design philosophy

The main new interface introduced to support client-side OAuth is the
`OAuthHandler`. Contrary to its counter-parts in some other SDKs, it is
intentionally small and generic so that it can support most of the OAuth
authorization flow variants. The main `mcp` package purposefully
delegates almost all auth handling to `OAuthHandler` implementations.
This way, the `mcp` package continues to be largely OAuth agnostic.

The `auth` package comes with a default `OAuthHandler` implementation –
`AuthorizationCodeHandler`, which implements the authorization flow
for the `authorization_code` grant type, as defined in the [MCP
specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization).
It is expected that the `OAuthHandler` implementation granularity will
be roughly per grant type, as this parameter influences the
authorization flow the most.

# Detailed design

## `OAuthHandler` (auth/client.go)

The main interface for the integration consists of two methods:

```go
type OAuthHandler interface {
	// TokenSource returns a token source to be used for outgoing requests.
	// Returned token source might be nil. In that case, the transport will not
	// add any authorization headers to the request.
	TokenSource(context.Context) (oauth2.TokenSource, error)

	// Authorize is called when an HTTP request results in an error that may
	// be addressed by the authorization flow (currently 401 Unauthorized and 403 Forbidden).
	// It is responsible for performing the OAuth flow to obtain an access token.
	// The arguments are the request that failed and the response that was received for it.
	// The headers of the request are available, but the body will have already been consumed
	// when Authorize is called.
	// If the returned error is nil, TokenSource is expected to return a non-nil token source.
	// After a successful call to Authorize, the HTTP request will be retried by the transport.
	// The function is responsible for closing the response body.
	Authorize(context.Context, *http.Request, *http.Response) error
}
```

`StreamableClientTransport` accepts this interface as a field and uses
the `TokenSource` method to add an `Authorization: Bearer` header to
outgoing requests to the MCP server. If the request fails with `401:
Unauthorized` or `403: Forbidden`, the transport calls `Authorize` to
perform the authorization flow. If `Authorize` returns a non-nil error,
the request is retried once. `Authorize` errors do not result in
termination of the client session (unless they happen during
connection initialization), as some OAuth flows are multi-legged and it should be
acceptable to retry for example when the authorization grant was
received.

*Note:* There was already an `OAuthHandler` type defined as part of a
previous iteration of OAuth support. It was renamed to
`OAuthHandlerLegacy`. This is the only backwards incompatible API change
in the experimental client `auth` part (protected by the
`mcp_go_client_oauth` Go build tag). The cost of replacing previous
usages is deemed worthwhile to maintain clear naming, under the
assumption that the experimental client `auth` part was not widely used.

## `AuthorizationCodeHandler` (auth/authorization_code.go)

This is the `OAuthHandler` implementation that fulfills the [MCP
specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization).
In particular, in supports:

- Protected Resource Metadata discovery and fetching
- Authorization Server Metadata discovery and fetching
- Client registration
  - OpenID Connect metadata document
  - Pre-registration
  - Dynamic Client Registration
- PKCE
- `client_secret_post`, `client_secret_basic` token endpoint
auth methods
- Automatic refresh of tokens

### Authorization Server redirect

As part of the flow for the `authorization_code` grant type, the client
needs to point the user to the authorization URL, so that they confirm
the access request. This design proposes that it will be supported via a
pluggable dependency that will initiate this process in an
application-specific way:

```go
type AuthorizationCodeHandler struct {
	...

	// AuthorizationCodeFetcher is a required function called to initiate the authorization flow.
	// It is responsible for opening the URL in a browser for the user to start the authorization process.
	// It should return the authorization code and state once the Authorization Server
	// redirects back to the RedirectURL.
	AuthorizationCodeFetcher func(ctx context.Context, args *AuthorizationArgs) (*AuthorizationResult, error)

	...
}
```

The function is supposed perform any application specific logic to handle the redirect
from the Authorization Server and return the resulting code and state.

## `oauthex` changes

Some additional/adjusted building blocks that may be useful when
creating `OAuthHandler` implementations are present in the `oauthex`
package:

- `auth_meta.go`: Authorization Server Metadata utilities:
  - `GetAuthServerMeta`: get Authorization Server Metadata from a URL
- `dcr.go`: Dynamic Client Registration utilities (unchanged)
- `resource_meta.go`: Protected Resource Metadata utilities:
- `GetProtectedResourceMetadata`: get Protected Resource Metadata from a
URL
  - `ParseWWWAuthenticate` – as per name (unchanged)
- [deprecated] `GetProtectedResourceMetadataFromID` and
`GetProtectedResourceMetadataFromHeader` in favor of more generic
`GetProtectedResourceMetadata`. They will be kept under the Go build tag
and removed one version of the SDK in the future.
2026-02-27 15:30:13 +01:00
Maciej Kisiel
51d256cadc
chore: Configure OSSF Scorecard action (#811) 2026-02-19 15:02:15 +01:00
Maciej Kisiel
7b8d81c264
all: use case-sensitive JSON unmarshaling (#807)
As reported in
https://github.com/modelcontextprotocol/go-sdk/issues/805, case
insensitive unmarshaling can pose security issues. This change adjusts
all non-test and non-example unmarshaling to use a case sensitive mode.

This introduces a new external dependency
`github.com/segmentio/encoding`.
2026-02-18 16:38:36 +01:00
Robert Findley
3cf9f9908d
mcp: implement sampling with tools (#699)
Summary

Add support for tool use within sampling requests, as described in the
MCP 2025-11-25 spec's sampling.tools capability.
  New types

  Content types:
- ToolUseContent — represents a tool invocation request from the
assistant
- ToolResultContent — represents the result of a tool invocation, with
nested []Content for the result blocks

  Capability types:
  - SamplingCapabilities gains Tools and Context sub-fields
  - SamplingToolsCapabilities, SamplingContextCapabilities

  Tool-enabled sampling (parallel tool calls):
- CreateMessageWithToolsParams with SamplingMessageV2 (supports array
content)
  - CreateMessageWithToolsResult (supports array content)
  - ToolChoice — controls tool invocation mode (auto, required, none)
  - ServerSession.CreateMessageWithTools
  - ClientOptions.CreateMessageWithToolsHandler

  Design

Following the TypeScript SDK's pattern, tool-enabled sampling uses
separate types from basic sampling to avoid breaking the existing API.
The basic CreateMessage/CreateMessageResult path
   is unchanged.

Both paths share the same wire method (sampling/createMessage) and go
through the method info table. The table uses the broader
CreateMessageWithToolsResult type internally;
CreateMessage downconverts (erroring if multiple content blocks are
returned). This is documented as a rough edge to unify in v2.

Setting CreateMessageWithToolsHandler automatically infers the
sampling.tools capability. It is a panic to set both
CreateMessageHandler and CreateMessageWithToolsHandler.

  References

-
https://modelcontextprotocol.io/specification/2025-11-25/client/sampling
- TypeScript SDK: CreateMessageResultWithToolsSchema /
CreateMessageRequestParamsWithTools

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:07:08 +01:00
Nithin P
23ab1bfd26 jsonrpc2: fix Content-Length header parsing to be case-insensitive 2026-02-16 12:20:41 +01:00
Paul Carleton
67bd3f2e2b
mcp: add automatic DNS rebinding protection for localhost servers (#760)
## Summary

Add DNS rebinding protection that is automatically enabled when requests
arrive via localhost (127.0.0.1, [::1]). This protects against malicious
websites using DNS rebinding to interact with local MCP servers.

(note: this was a claude assisted PR, mostly wanted to see how difficult
passing this test would be for this SDK)

## Design Goal: Secure by Default

The primary goal is to make it difficult to run a localhost server
without these protections by mistake. There are other approaches that
could provide secure defaults (e.g., a helper for `ListenAndServe` that
does the localhost check), but using `http.LocalAddrContextKey` for
runtime detection seemed like the most backwards compatible approach and
least likely to be disabled by accident.

With this implementation:
- **No code changes required** - existing servers get protection
automatically
- **No opt-in needed** - protection activates based on the connection's
local address
- **Explicit opt-out** - users must deliberately set
`DisableLocalhostProtection: true`

## Changes

- Add `DisableLocalhostProtection` option to `StreamableHTTPOptions`
- Add `isLocalhostAddr` and `isLocalhostHost` helper functions  
- Validate Host header at start of `ServeHTTP`, rejecting non-localhost
Host headers with 403 Forbidden

## How it works

The protection uses `http.LocalAddrContextKey` to detect the
connection's local address at runtime. When a request arrives via
localhost (127.0.0.1 or [::1]), the handler validates that the Host
header also matches a localhost value. If not, the request is rejected
with 403 Forbidden.

This approach means:
- Protection is enabled for requests arriving via localhost, regardless
of whether the server listens on `127.0.0.1` or `0.0.0.0`
- Requests arriving via non-localhost IPs (e.g., external network
requests) are **not** affected

### Edge case: Reverse proxies

If a reverse proxy (e.g., Envoy, nginx) runs on the same host and
forwards requests to the MCP server via localhost while preserving the
original Host header, those requests would be rejected. In this case,
users should either:
1. Set `DisableLocalhostProtection: true`
2. Configure the proxy to rewrite the Host header to localhost

## Testing

- Added unit tests for `isLocalhostAddr` and `isLocalhostHost` helper
functions
- Added integration tests for the full protection flow
- Verified against the MCP conformance test suite:
  - `localhost-host-rebinding-rejected`: PASS
  - `localhost-host-valid-accepted`: PASS

## Related

- Spec:
https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise
- TypeScript SDK implementation: `localhostHostValidation()` middleware
- Conformance test: `dns-rebinding-protection` scenario

---------

Co-authored-by: Maciek Kisiel <mkisiel@google.com>
2026-02-13 16:23:32 +01:00
Maciek Kisiel
6a19c95fea mcp: finalize cleanup of Go 1.23, make Go version support explicit 2026-02-06 12:50:01 +01:00
Maciej Kisiel
bba5acdc8b
mcp: update JSON marshaling to not HTML-escape messages (#769)
It's causing some servers to fail: #767. I checked with Typescript
implementation and it doesn't do any escaping.

It's still a behavior change, so we'll expose a way to bring back the previous behavior via an env var.

Fixes #238 
Fixes #767
2026-01-30 17:13:30 +01:00
adam jones
fe6d3cf74a
chore: update licensing to Apache 2.0 for new contributions (#750)
As part of MCP joining the Linux Foundation, this PR updates licensing
to Apache 2.0 for new contributions while grandfathering existing MIT
code. See modelcontextprotocol/modelcontextprotocol#1994

---------

Co-authored-by: Maciek Kisiel <mkisiel@google.com>
2026-01-28 09:17:41 +01:00
Sam Morrow
c2c7edc3f0
perf: add schema caching to avoid repeated reflection (#685)
## Summary

This PR adds schema caching that dramatically reduces the cost of tool
registration in stateless server patterns.

**Contributors:** Thanks to @omgitsads and @almaleksia for collaborating
on and researching this solution!

## Problem

In stateless deployments like `github/github-mcp-server`, a new
`*mcp.Server` is created for each incoming request. This means `AddTool`
is called repeatedly for the same tools, causing:

1. **For typed handlers**: `jsonschema.ForType()` reflection is called
every time
2. **For pre-defined schemas**: `schema.Resolve()` is called every time

## Solution

Add a `schemaCache` that stores:
- Schemas by `reflect.Type` (for auto-generated schemas from typed
handlers)
- Resolved schemas by schema pointer (for pre-defined schemas)

The cache is:
- Concurrent-safe using `sync.Map`
- Unbounded (typical MCP servers have &lt;100 tools)
- Global (lives across server instances)

## Benchmark Results

```
BenchmarkAddToolTypedHandler-22          977080    1223 ns/op     1208 B/op    21 allocs/op
BenchmarkAddToolTypedHandlerNoCache-22    6764  161463 ns/op    39262 B/op  1072 allocs/op
```

| Metric | With Cache | Without Cache | Improvement |
|--------|------------|---------------|-------------|
| Time | 1,223 ns/op | 161,463 ns/op | **132x faster** |
| Allocations | 21 allocs | 1,072 allocs | **51x fewer** |
| Memory | 1,208 B/op | 39,262 B/op | **32x less** |

## Files Changed

- `mcp/schema_cache.go` - New cache implementation
- `mcp/server.go` - Modified `setSchema` to use cache
- `mcp/schema_cache_test.go` - Unit tests for caching behavior
- `mcp/schema_cache_benchmark_test.go` - Benchmarks

## Impact for Integrators

**Automatic** - no code changes required. Integrators using:
- Typed handlers (`AddTool[In, Out]`) → cache by type
- Pre-defined schemas (`Tool{InputSchema: schema}`) → cache by pointer

Both patterns benefit from caching after the first call.

---

## Real-World Performance Validation

The following benchmarks were conducted using `github/github-mcp-server`
(a production MCP server with ~130 tools) to validate the performance
impact in a real-world scenario.

### Test Environment
- **Server:** github-mcp-server (stateless HTTP deployment)
- **Tools registered per request:** ~130 tools
- **Test methodology:** Python benchmark client, 30 iterations
(latency), 100 iterations (stress)
- **Date:** December 4, 2025

### Configurations Tested

| Configuration | Description |
|--------------|-------------|
| **main (mcp-go)** | Original implementation using mcp-go library |
| **go-sdk (no cache)** | go-sdk WITHOUT schema caching (broken) |
| **go-sdk (with cache)** | go-sdk WITH this PR&#39;s schema caching fix
|

### Latency Test Results (n=30)

#### Operation: `initialize`

| Configuration | P50 | P99 | Status |
|--------------|-----|-----|--------|
| main (mcp-go) | 11.48ms | 14.00ms | ✅ Baseline |
| go-sdk (no cache) | 20.47ms | 25.30ms | 🔴 **+78% REGRESSION** |
| go-sdk (with cache) | 10.36ms | 14.33ms | ✅ **FIXED (-10%)** |

#### Operation: `tools/list`

| Configuration | P50 | P99 | Status |
|--------------|-----|-----|--------|
| main (mcp-go) | 13.46ms | 22.47ms | ✅ Baseline |
| go-sdk (no cache) | 22.98ms | 29.11ms | 🔴 **+71% REGRESSION** |
| go-sdk (with cache) | 14.15ms | 15.91ms | ✅ **FIXED (+5%)** |

#### Operation: `prompts/list`

| Configuration | P50 | P99 | Status |
|--------------|-----|-----|--------|
| main (mcp-go) | 11.56ms | 13.81ms | ✅ Baseline |
| go-sdk (no cache) | 21.00ms | 26.42ms | 🔴 **+82% REGRESSION** |
| go-sdk (with cache) | 10.77ms | 15.25ms | ✅ **FIXED (-7%)** |

### Stress Test Results (n=100)

| Configuration | P50 (initialize) | P50 (tools/list) | P50
(prompts/list) |

|--------------|------------------|------------------|-------------------|
| main (mcp-go) | 12.06ms | 14.30ms | 11.34ms |
| go-sdk (no cache) | 20.06ms | 23.44ms | 19.59ms |
| go-sdk (with cache) | **11.83ms** | **14.58ms** | **11.05ms** |

### Memory/Allocation Comparison (from pprof)

| Configuration | Total Allocations | Comparison |
|--------------|-------------------|------------|
| main (mcp-go) | 355.91 MB | Baseline |
| go-sdk (no cache) | **1208.70 MB** | 🔴 **3.4x MORE allocations** |

#### Top Allocation Sources - go-sdk WITHOUT cache (broken)

| Function | Size | % | Issue |
|----------|------|---|-------|
| `jsonschema.UnmarshalJSON` | 324.60 MB | 27% | 🚨 Schema re-parsing |
| `encoding/json.Unmarshal` | 341.10 MB | 28% | JSON deserialization |
| `jsonschema.resolve` | 219.51 MB | 18% | 🚨 Schema re-resolution |
| `jsonschema.MarshalJSON` | 92.52 MB | 8% | Schema JSON encoding |

### Root Cause Analysis

The `google/jsonschema-go` library regenerates JSON schemas on every
request instead of caching them. In a server with ~130 tools, this
causes:
- **70-80% latency regression** on all MCP operations
- **3.4x more memory allocations** per request
- **~70% of all allocations** are schema-related operations

### Key Findings

1. ✅ **REGRESSION CONFIRMED:** go-sdk without schema caching is 70-80%
slower than mcp-go
2. ✅ **FIX VERIFIED:** Schema caching restores performance to baseline
(or better)
3. ✅ **MEMORY IMPACT:** 3.4x reduction in allocations with caching
4. ✅ **PRODUCTION READY:** Fixed version performs equivalently to mcp-go
baseline

---------

Co-authored-by: Adam Holt <4619+omgitsads@users.noreply.github.com>
Co-authored-by: Ksenia Bobrova <1885174+almaleksia@users.noreply.github.com>
2026-01-20 20:16:46 -05:00