Skip to content

add auth client sse #760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions examples/clients/simple-auth-client/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Simple Auth Client Example

A demonstration of how to use the MCP Python SDK with OAuth authentication over streamable HTTP transport.
A demonstration of how to use the MCP Python SDK with OAuth authentication over streamable HTTP or SSE transport.

## Features

- OAuth 2.0 authentication with PKCE
- Streamable HTTP transport
- Support for both StreamableHTTP and SSE transports
- Interactive command-line interface

## Installation
Expand All @@ -32,6 +32,9 @@ uv run mcp-simple-auth-client

# Or with custom server URL
MCP_SERVER_URL=http://localhost:3001 uv run mcp-simple-auth-client

# Use SSE transport
MCP_TRANSPORT_TYPE=sse uv run mcp-simple-auth-client
```

### 3. Complete OAuth flow
Expand Down Expand Up @@ -68,3 +71,4 @@ mcp> quit
## Configuration

- `MCP_SERVER_URL` - Server URL (default: http://localhost:3001)
- `MCP_TRANSPORT_TYPE` - Transport type: `streamable_http` (default) or `sse`
68 changes: 41 additions & 27 deletions examples/clients/simple-auth-client/mcp_simple_auth_client/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.client.session import ClientSession
from mcp.client.sse import sse_client
from mcp.client.streamable_http import streamablehttp_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken

Expand Down Expand Up @@ -149,8 +150,9 @@ def get_state(self):
class SimpleAuthClient:
"""Simple MCP client with auth support."""

def __init__(self, server_url: str):
def __init__(self, server_url: str, transport_type: str = "streamable_http"):
self.server_url = server_url
self.transport_type = transport_type
self.session: ClientSession | None = None

async def connect(self):
Expand Down Expand Up @@ -195,38 +197,48 @@ async def _default_redirect_handler(authorization_url: str) -> None:
callback_handler=callback_handler,
)

# Create streamable HTTP transport with auth handler
stream_context = streamablehttp_client(
url=self.server_url,
auth=oauth_auth,
timeout=timedelta(seconds=60),
)

print(
"📡 Opening transport connection (HTTPX handles auth automatically)..."
)
async with stream_context as (read_stream, write_stream, get_session_id):
print("🤝 Initializing MCP session...")
async with ClientSession(read_stream, write_stream) as session:
self.session = session
print("⚡ Starting session initialization...")
await session.initialize()
print("✨ Session initialization complete!")

print(f"\n✅ Connected to MCP server at {self.server_url}")
session_id = get_session_id()
if session_id:
print(f"Session ID: {session_id}")

# Run interactive loop
await self.interactive_loop()
# Create transport with auth handler based on transport type
if self.transport_type == "sse":
print("📡 Opening SSE transport connection with auth...")
async with sse_client(
url=self.server_url,
auth=oauth_auth,
timeout=60,
) as (read_stream, write_stream):
await self._run_session(read_stream, write_stream, None)
else:
print("📡 Opening StreamableHTTP transport connection with auth...")
async with streamablehttp_client(
url=self.server_url,
auth=oauth_auth,
timeout=timedelta(seconds=60),
) as (read_stream, write_stream, get_session_id):
await self._run_session(read_stream, write_stream, get_session_id)

except Exception as e:
print(f"❌ Failed to connect: {e}")
import traceback

traceback.print_exc()

async def _run_session(self, read_stream, write_stream, get_session_id):
"""Run the MCP session with the given streams."""
print("🤝 Initializing MCP session...")
async with ClientSession(read_stream, write_stream) as session:
self.session = session
print("⚡ Starting session initialization...")
await session.initialize()
print("✨ Session initialization complete!")

print(f"\n✅ Connected to MCP server at {self.server_url}")
if get_session_id:
session_id = get_session_id()
if session_id:
print(f"Session ID: {session_id}")

# Run interactive loop
await self.interactive_loop()

async def list_tools(self):
"""List available tools from the server."""
if not self.session:
Expand Down Expand Up @@ -327,12 +339,14 @@ async def main():
# Default server URL - can be overridden with environment variable
# Most MCP streamable HTTP servers use /mcp as the endpoint
server_url = os.getenv("MCP_SERVER_URL", "http://localhost:8000/mcp")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: might change this to be /sse if the transport is set since that's the default binding we get. might get a little funky making sure you don't overwrite it though.

https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/server/fastmcp/server.py#L93

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, something is weird, I thought I changed that to port!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't push it 😞

transport_type = os.getenv("MCP_TRANSPORT_TYPE", "streamable_http")

print("🚀 Simple MCP Auth Client")
print(f"Connecting to: {server_url}")
print(f"Transport type: {transport_type}")

# Start connection flow - OAuth will be handled automatically
client = SimpleAuthClient(server_url)
client = SimpleAuthClient(server_url, transport_type)
await client.connect()


Expand Down
10 changes: 9 additions & 1 deletion src/mcp/client/sse.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,20 @@ async def sse_client(
headers: dict[str, Any] | None = None,
timeout: float = 5,
sse_read_timeout: float = 60 * 5,
auth: httpx.Auth | None = None,
):
"""
Client transport for SSE.

`sse_read_timeout` determines how long (in seconds) the client will wait for a new
event before disconnecting. All other HTTP operations are controlled by `timeout`.

Args:
url: The SSE endpoint URL.
headers: Optional headers to include in requests.
timeout: HTTP timeout for regular operations.
sse_read_timeout: Timeout for SSE read operations.
auth: Optional HTTPX authentication handler.
"""
read_stream: MemoryObjectReceiveStream[SessionMessage | Exception]
read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception]
Expand All @@ -45,7 +53,7 @@ async def sse_client(
async with anyio.create_task_group() as tg:
try:
logger.info(f"Connecting to SSE endpoint: {remove_request_params(url)}")
async with create_mcp_http_client(headers=headers) as client:
async with create_mcp_http_client(headers=headers, auth=auth) as client:
async with aconnect_sse(
client,
"GET",
Expand Down
Loading