Skip to content

Commit a3da384

Browse files
mchockalmchockal-cfeyurtsev
authored
Expose auth in connections (#236)
This PR allows connecting to mcp servers that requires OAuth. I needed this functionality to get my langgraph agent working with cloudflare mcp servers which have auth enabled. This was adapted from `modelcontextprotocol/python-sdk/`[mcp_simple_auth_client](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/clients/simple-auth-client/mcp_simple_auth_client) Sample code for how to use ``` oauth_provider = langchain_mcp_adapters.auth.create_oauth_provider("https://mcpserver.with.oauth.org") client = MultiServerMCPClient( connections={ "server_with_auth": { "transport": "sse", "url": "https://mcpserver.with.oauth.org/sse", "auth": oauth_provider, #server with auth }, } ) # Get list of tools from "server_with_auth" server async with client.session("server_with_auth") as server_with_auth_session: tools = await langchain_mcp_adapters.tools.load_mcp_tools(server_with_auth_session) print(f"\n Found {len(tools)} tools:") for tool in tools: print(f"- {tool.name}: {tool.description or 'No description'}") ``` --------- Co-authored-by: Meena Chockalingam <[email protected]> Co-authored-by: Eugene Yurtsev <[email protected]>
1 parent e17065b commit a3da384

File tree

3 files changed

+25
-4
lines changed

3 files changed

+25
-4
lines changed

langchain_mcp_adapters/sessions.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
from __future__ import annotations
2+
13
import os
24
from contextlib import asynccontextmanager
35
from datetime import timedelta
46
from pathlib import Path
5-
from typing import Any, AsyncIterator, Literal, Protocol, TypedDict
7+
from typing import Any, AsyncIterator, Literal, Protocol
68

79
import httpx
810
from mcp import ClientSession, StdioServerParameters
911
from mcp.client.sse import sse_client
1012
from mcp.client.stdio import stdio_client
1113
from mcp.client.streamable_http import streamablehttp_client
14+
from typing_extensions import NotRequired, TypedDict
1215

1316
EncodingErrorHandler = Literal["strict", "ignore", "replace"]
1417

@@ -82,6 +85,9 @@ class SSEConnection(TypedDict):
8285
httpx_client_factory: McpHttpClientFactory | None
8386
"""Custom factory for httpx.AsyncClient (optional)."""
8487

88+
auth: NotRequired[httpx.Auth]
89+
"""Optional authentication for the HTTP client."""
90+
8591

8692
class StreamableHttpConnection(TypedDict):
8793
transport: Literal["streamable_http"]
@@ -108,6 +114,9 @@ class StreamableHttpConnection(TypedDict):
108114
httpx_client_factory: McpHttpClientFactory | None
109115
"""Custom factory for httpx.AsyncClient (optional)."""
110116

117+
auth: NotRequired[httpx.Auth]
118+
"""Optional authentication for the HTTP client."""
119+
111120

112121
class WebsocketConnection(TypedDict):
113122
transport: Literal["websocket"]
@@ -175,6 +184,7 @@ async def _create_sse_session(
175184
sse_read_timeout: float = DEFAULT_SSE_READ_TIMEOUT,
176185
session_kwargs: dict[str, Any] | None = None,
177186
httpx_client_factory: McpHttpClientFactory | None = None,
187+
auth: httpx.Auth | None = None,
178188
) -> AsyncIterator[ClientSession]:
179189
"""Create a new session to an MCP server using SSE.
180190
@@ -185,13 +195,17 @@ async def _create_sse_session(
185195
sse_read_timeout: SSE read timeout
186196
session_kwargs: Additional keyword arguments to pass to the ClientSession
187197
httpx_client_factory: Custom factory for httpx.AsyncClient (optional)
198+
auth: httpx.Auth | None = None
188199
"""
189200
# Create and store the connection
190201
kwargs = {}
191202
if httpx_client_factory is not None:
192203
kwargs["httpx_client_factory"] = httpx_client_factory
193204

194-
async with sse_client(url, headers, timeout, sse_read_timeout, **kwargs) as (read, write):
205+
async with sse_client(url, headers, timeout, sse_read_timeout, auth=auth, **kwargs) as (
206+
read,
207+
write,
208+
):
195209
async with ClientSession(read, write, **(session_kwargs or {})) as session:
196210
yield session
197211

@@ -206,6 +220,7 @@ async def _create_streamable_http_session(
206220
terminate_on_close: bool = True,
207221
session_kwargs: dict[str, Any] | None = None,
208222
httpx_client_factory: McpHttpClientFactory | None = None,
223+
auth: httpx.Auth | None = None,
209224
) -> AsyncIterator[ClientSession]:
210225
"""Create a new session to an MCP server using Streamable HTTP.
211226
@@ -217,14 +232,15 @@ async def _create_streamable_http_session(
217232
terminate_on_close: Whether to terminate the session on close
218233
session_kwargs: Additional keyword arguments to pass to the ClientSession
219234
httpx_client_factory: Custom factory for httpx.AsyncClient (optional)
235+
auth: httpx.Auth | None = None
220236
"""
221237
# Create and store the connection
222238
kwargs = {}
223239
if httpx_client_factory is not None:
224240
kwargs["httpx_client_factory"] = httpx_client_factory
225241

226242
async with streamablehttp_client(
227-
url, headers, timeout, sse_read_timeout, terminate_on_close, **kwargs
243+
url, headers, timeout, sse_read_timeout, terminate_on_close, auth=auth, **kwargs
228244
) as (read, write, _):
229245
async with ClientSession(read, write, **(session_kwargs or {})) as session:
230246
yield session
@@ -295,6 +311,7 @@ async def create_session(
295311
sse_read_timeout=connection.get("sse_read_timeout", DEFAULT_SSE_READ_TIMEOUT),
296312
session_kwargs=connection.get("session_kwargs"),
297313
httpx_client_factory=connection.get("httpx_client_factory"),
314+
auth=connection.get("auth"),
298315
) as session:
299316
yield session
300317
elif transport == "streamable_http":
@@ -309,6 +326,7 @@ async def create_session(
309326
),
310327
session_kwargs=connection.get("session_kwargs"),
311328
httpx_client_factory=connection.get("httpx_client_factory"),
329+
auth=connection.get("auth"),
312330
) as session:
313331
yield session
314332
elif transport == "stdio":

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ requires-python = ">=3.10"
1616
dependencies = [
1717
"langchain-core>=0.3.36,<0.4",
1818
"mcp>=1.9.2",
19+
"typing-extensions>=4.14.0",
1920
]
2021

2122
[dependency-groups]

uv.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)