You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+107Lines changed: 107 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1885,6 +1885,113 @@ client.tools # will make the call using Bearer auth
1885
1885
1886
1886
You can add any custom headers needed for your authentication scheme, or for any other purpose. The client will include these headers on every request.
1887
1887
1888
+
#### OAuth 2.1 Authorization
1889
+
1890
+
When an MCP server enforces the [MCP Authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization),
1891
+
pass an `MCP::Client::OAuth::Provider` to the transport instead of a static `Authorization` header. The transport will:
1892
+
1893
+
- Send `Authorization: Bearer <access_token>` on every request when a token is available.
1894
+
- On a `401 Unauthorized`, parse the `WWW-Authenticate` header, discover the authorization server (Protected Resource Metadata + RFC 8414 Authorization Server Metadata),
1895
+
perform Dynamic Client Registration if needed, run the OAuth 2.1 Authorization Code flow with PKCE (S256), and retry the failed request with the acquired token.
1896
+
- On subsequent 401s with a saved `refresh_token`, exchange it at the token endpoint before falling back to the full interactive flow (RFC 6749 Section 6).
# Send the user to the authorization URL - typically `Launchy.open(authorization_url)`
1912
+
# or a manual `puts authorization_url` in CLI tools.
1913
+
},
1914
+
callback_handler:-> {
1915
+
# Capture the redirect (for example, by running a small HTTP listener on
1916
+
# `redirect_uri`) and return [code, state] from the query string.
1917
+
},
1918
+
)
1919
+
1920
+
transport =MCP::Client::HTTP.new(
1921
+
url:"https://api.example.com/mcp",
1922
+
oauth: provider,
1923
+
)
1924
+
client =MCP::Client.new(transport: transport)
1925
+
client.connect # `initialize` is sent here; if the server replies 401 the OAuth flow runs and the handshake is retried with the acquired token
1926
+
client.tools
1927
+
```
1928
+
1929
+
Required keyword arguments to `Provider.new`:
1930
+
1931
+
-`client_metadata`: Hash sent to the authorization server's Dynamic Client Registration endpoint. Must include `redirect_uris`, `grant_types`, `response_types`,
1932
+
`token_endpoint_auth_method`. `redirect_uri` (below) must appear in this list, otherwise the constructor raises `Provider::UnregisteredRedirectURIError`.
1933
+
-`redirect_uri`: String. Must use HTTPS or be a loopback URL (`localhost`, `127.0.0.0/8`, `::1`); other values raise `Provider::InsecureRedirectURIError`.
1934
+
-`redirect_handler`: Callable invoked with the fully-built authorization `URI`. Typically opens the user's browser.
1935
+
-`callback_handler`: Callable that returns `[code, state]` after the user is redirected back to `redirect_uri`.
1936
+
1937
+
Optional keyword arguments:
1938
+
1939
+
-`scope`: Space-separated scopes to request when the server's `WWW-Authenticate` does not specify one.
1940
+
-`storage`: Object responding to `tokens`, `save_tokens(t)`, `client_information`, `save_client_information(info)`. Defaults to `MCP::Client::OAuth::InMemoryStorage`,
1941
+
which keeps credentials in process memory only.
1942
+
1943
+
To persist credentials across restarts, supply your own storage:
When `oauth:` is set, the MCP transport URL and every OAuth-facing URL (PRM, Authorization Server metadata, `authorization_endpoint`, `token_endpoint`, `registration_endpoint`,
1987
+
`redirect_uri`) must use HTTPS or a loopback host. Non-loopback `http://` URLs are rejected at the SDK boundary so a bearer token is never sent over plain HTTP to a remote host.
1988
+
1989
+
The transport also snapshots the canonicalized origin, path, and query string of the MCP URL at `initialize` time and re-checks them on every outgoing request through
1990
+
a Faraday middleware that runs after any user-supplied customizer. That means any URL swap raises `MCP::Client::HTTP::InsecureURLError` before the request reaches the adapter,
1991
+
whether the swap was triggered by
1992
+
`instance_variable_set(:@url, ...)`, by a Faraday customizer rewriting `url_prefix`, or by a custom middleware rewriting `env.url` (including just `env.url.query`) at request time,
1993
+
and whether the new URL is `http://`*or*`https://` to a different host or tenant.
1994
+
1888
1995
#### Customizing the Faraday Connection
1889
1996
1890
1997
You can pass a block to `MCP::Client::HTTP.new` to customize the underlying Faraday connection.
0 commit comments