Skip to content

Commit 2c33194

Browse files
authored
Merge branch 'main' into ylassoued/feat-request
2 parents a0d0ee5 + ed25167 commit 2c33194

File tree

92 files changed

+9795
-278
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+9795
-278
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,5 @@ cython_debug/
166166

167167
# vscode
168168
.vscode/
169+
.windsurfrules
169170
**/CLAUDE.local.md

CLAUDE.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ This document contains critical information about working with this codebase. Fo
1919
- Line length: 88 chars maximum
2020

2121
3. Testing Requirements
22-
- Framework: `uv run pytest`
22+
- Framework: `uv run --frozen pytest`
2323
- Async testing: use anyio, not asyncio
2424
- Coverage: test edge cases and errors
2525
- New features require tests
@@ -54,9 +54,9 @@ This document contains critical information about working with this codebase. Fo
5454
## Code Formatting
5555

5656
1. Ruff
57-
- Format: `uv run ruff format .`
58-
- Check: `uv run ruff check .`
59-
- Fix: `uv run ruff check . --fix`
57+
- Format: `uv run --frozen ruff format .`
58+
- Check: `uv run --frozen ruff check .`
59+
- Fix: `uv run --frozen ruff check . --fix`
6060
- Critical issues:
6161
- Line length (88 chars)
6262
- Import sorting (I001)
@@ -67,7 +67,7 @@ This document contains critical information about working with this codebase. Fo
6767
- Imports: split into multiple lines
6868

6969
2. Type Checking
70-
- Tool: `uv run pyright`
70+
- Tool: `uv run --frozen pyright`
7171
- Requirements:
7272
- Explicit None checks for Optional
7373
- Type narrowing for strings
@@ -104,6 +104,10 @@ This document contains critical information about working with this codebase. Fo
104104
- Add None checks
105105
- Narrow string types
106106
- Match existing patterns
107+
- Pytest:
108+
- If the tests aren't finding the anyio pytest mark, try adding PYTEST_DISABLE_PLUGIN_AUTOLOAD=""
109+
to the start of the pytest run command eg:
110+
`PYTEST_DISABLE_PLUGIN_AUTOLOAD="" uv run --frozen pytest`
107111

108112
3. Best Practices
109113
- Check git status before commits

README.md

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ The Model Context Protocol allows applications to provide context for LLMs in a
6666

6767
- Build MCP clients that can connect to any MCP server
6868
- Create MCP servers that expose resources, prompts and tools
69-
- Use standard transports like stdio and SSE
69+
- Use standard transports like stdio, SSE, and Streamable HTTP
7070
- Handle all MCP protocol messages and lifecycle events
7171

7272
## Installation
@@ -309,6 +309,33 @@ async def long_task(files: list[str], ctx: Context) -> str:
309309
return "Processing complete"
310310
```
311311

312+
### Authentication
313+
314+
Authentication can be used by servers that want to expose tools accessing protected resources.
315+
316+
`mcp.server.auth` implements an OAuth 2.0 server interface, which servers can use by
317+
providing an implementation of the `OAuthServerProvider` protocol.
318+
319+
```
320+
mcp = FastMCP("My App",
321+
auth_provider=MyOAuthServerProvider(),
322+
auth=AuthSettings(
323+
issuer_url="https://myapp.com",
324+
revocation_options=RevocationOptions(
325+
enabled=True,
326+
),
327+
client_registration_options=ClientRegistrationOptions(
328+
enabled=True,
329+
valid_scopes=["myscope", "myotherscope"],
330+
default_scopes=["myscope"],
331+
),
332+
required_scopes=["myscope"],
333+
),
334+
)
335+
```
336+
337+
See [OAuthServerProvider](src/mcp/server/auth/provider.py) for more details.
338+
312339
## Running Your Server
313340

314341
### Development Mode
@@ -360,8 +387,81 @@ python server.py
360387
mcp run server.py
361388
```
362389

390+
### Streamable HTTP Transport
391+
392+
> **Note**: Streamable HTTP transport is superseding SSE transport for production deployments.
393+
394+
```python
395+
from mcp.server.fastmcp import FastMCP
396+
397+
# Stateful server (maintains session state)
398+
mcp = FastMCP("StatefulServer")
399+
400+
# Stateless server (no session persistence)
401+
mcp = FastMCP("StatelessServer", stateless_http=True)
402+
403+
# Run server with streamable_http transport
404+
mcp.run(transport="streamable-http")
405+
```
406+
407+
You can mount multiple FastMCP servers in a FastAPI application:
408+
409+
```python
410+
# echo.py
411+
from mcp.server.fastmcp import FastMCP
412+
413+
mcp = FastMCP(name="EchoServer", stateless_http=True)
414+
415+
416+
@mcp.tool(description="A simple echo tool")
417+
def echo(message: str) -> str:
418+
return f"Echo: {message}"
419+
```
420+
421+
```python
422+
# math.py
423+
from mcp.server.fastmcp import FastMCP
424+
425+
mcp = FastMCP(name="MathServer", stateless_http=True)
426+
427+
428+
@mcp.tool(description="A simple add tool")
429+
def add_two(n: int) -> str:
430+
return n + 2
431+
```
432+
433+
```python
434+
# main.py
435+
from fastapi import FastAPI
436+
from mcp.echo import echo
437+
from mcp.math import math
438+
439+
440+
app = FastAPI()
441+
442+
# Use the session manager's lifespan
443+
app = FastAPI(lifespan=lambda app: echo.mcp.session_manager.run())
444+
app.mount("/echo", echo.mcp.streamable_http_app())
445+
app.mount("/math", math.mcp.streamable_http_app())
446+
```
447+
448+
For low level server with Streamable HTTP implementations, see:
449+
- Stateful server: [`examples/servers/simple-streamablehttp/`](examples/servers/simple-streamablehttp/)
450+
- Stateless server: [`examples/servers/simple-streamablehttp-stateless/`](examples/servers/simple-streamablehttp-stateless/)
451+
452+
453+
454+
The streamable HTTP transport supports:
455+
- Stateful and stateless operation modes
456+
- Resumability with event stores
457+
- JSON or SSE response formats
458+
- Better scalability for multi-node deployments
459+
460+
363461
### Mounting to an Existing ASGI Server
364462

463+
> **Note**: SSE transport is being superseded by [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http).
464+
365465
You can mount the SSE server to an existing ASGI server using the `sse_app` method. This allows you to integrate the SSE server with other ASGI applications.
366466

367467
```python
@@ -383,6 +483,43 @@ app = Starlette(
383483
app.router.routes.append(Host('mcp.acme.corp', app=mcp.sse_app()))
384484
```
385485

486+
When mounting multiple MCP servers under different paths, you can configure the mount path in several ways:
487+
488+
```python
489+
from starlette.applications import Starlette
490+
from starlette.routing import Mount
491+
from mcp.server.fastmcp import FastMCP
492+
493+
# Create multiple MCP servers
494+
github_mcp = FastMCP("GitHub API")
495+
browser_mcp = FastMCP("Browser")
496+
curl_mcp = FastMCP("Curl")
497+
search_mcp = FastMCP("Search")
498+
499+
# Method 1: Configure mount paths via settings (recommended for persistent configuration)
500+
github_mcp.settings.mount_path = "/github"
501+
browser_mcp.settings.mount_path = "/browser"
502+
503+
# Method 2: Pass mount path directly to sse_app (preferred for ad-hoc mounting)
504+
# This approach doesn't modify the server's settings permanently
505+
506+
# Create Starlette app with multiple mounted servers
507+
app = Starlette(
508+
routes=[
509+
# Using settings-based configuration
510+
Mount("/github", app=github_mcp.sse_app()),
511+
Mount("/browser", app=browser_mcp.sse_app()),
512+
# Using direct mount path parameter
513+
Mount("/curl", app=curl_mcp.sse_app("/curl")),
514+
Mount("/search", app=search_mcp.sse_app("/search")),
515+
]
516+
)
517+
518+
# Method 3: For direct execution, you can also pass the mount path to run()
519+
if __name__ == "__main__":
520+
search_mcp.run(transport="sse", mount_path="/search")
521+
```
522+
386523
For more information on mounting applications in Starlette, see the [Starlette documentation](https://www.starlette.io/routing/#submounting-routes).
387524

388525
## Examples
@@ -557,7 +694,7 @@ if __name__ == "__main__":
557694

558695
### Writing MCP Clients
559696

560-
The SDK provides a high-level client interface for connecting to MCP servers:
697+
The SDK provides a high-level client interface for connecting to MCP servers using various [transports](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports):
561698

562699
```python
563700
from mcp import ClientSession, StdioServerParameters, types
@@ -621,6 +758,28 @@ if __name__ == "__main__":
621758
asyncio.run(run())
622759
```
623760

761+
Clients can also connect using [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http):
762+
763+
```python
764+
from mcp.client.streamable_http import streamablehttp_client
765+
from mcp import ClientSession
766+
767+
768+
async def main():
769+
# Connect to a streamable HTTP server
770+
async with streamablehttp_client("example/mcp") as (
771+
read_stream,
772+
write_stream,
773+
_,
774+
):
775+
# Create a session using the client streams
776+
async with ClientSession(read_stream, write_stream) as session:
777+
# Initialize the connection
778+
await session.initialize()
779+
# Call a tool
780+
tool_result = await session.call_tool("echo", {"message": "hello"})
781+
```
782+
624783
### MCP Primitives
625784

626785
The MCP protocol defines three core primitives that servers can implement:

examples/clients/simple-chatbot/mcp_simple_chatbot/main.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,7 @@ async def process_llm_response(self, llm_response: str) -> str:
323323
total = result["total"]
324324
percentage = (progress / total) * 100
325325
logging.info(
326-
f"Progress: {progress}/{total} "
327-
f"({percentage:.1f}%)"
326+
f"Progress: {progress}/{total} ({percentage:.1f}%)"
328327
)
329328

330329
return f"Tool execution result: {result}"
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Simple MCP Server with GitHub OAuth Authentication
2+
3+
This is a simple example of an MCP server with GitHub OAuth authentication. It demonstrates the essential components needed for OAuth integration with just a single tool.
4+
5+
This is just an example of a server that uses auth, an official GitHub mcp server is [here](https://github.com/github/github-mcp-server)
6+
7+
## Overview
8+
9+
This simple demo to show to set up a server with:
10+
- GitHub OAuth2 authorization flow
11+
- Single tool: `get_user_profile` to retrieve GitHub user information
12+
13+
14+
## Prerequisites
15+
16+
1. Create a GitHub OAuth App:
17+
- Go to GitHub Settings > Developer settings > OAuth Apps > New OAuth App
18+
- Application name: Any name (e.g., "Simple MCP Auth Demo")
19+
- Homepage URL: `http://localhost:8000`
20+
- Authorization callback URL: `http://localhost:8000/github/callback`
21+
- Click "Register application"
22+
- Note down your Client ID and Client Secret
23+
24+
## Required Environment Variables
25+
26+
You MUST set these environment variables before running the server:
27+
28+
```bash
29+
export MCP_GITHUB_GITHUB_CLIENT_ID="your_client_id_here"
30+
export MCP_GITHUB_GITHUB_CLIENT_SECRET="your_client_secret_here"
31+
```
32+
33+
The server will not start without these environment variables properly set.
34+
35+
36+
## Running the Server
37+
38+
```bash
39+
# Set environment variables first (see above)
40+
41+
# Run the server
42+
uv run mcp-simple-auth
43+
```
44+
45+
The server will start on `http://localhost:8000`.
46+
47+
## Available Tool
48+
49+
### get_user_profile
50+
51+
The only tool in this simple example. Returns the authenticated user's GitHub profile information.
52+
53+
**Required scope**: `user`
54+
55+
**Returns**: GitHub user profile data including username, email, bio, etc.
56+
57+
58+
## Troubleshooting
59+
60+
If the server fails to start, check:
61+
1. Environment variables `MCP_GITHUB_GITHUB_CLIENT_ID` and `MCP_GITHUB_GITHUB_CLIENT_SECRET` are set
62+
2. The GitHub OAuth app callback URL matches `http://localhost:8000/github/callback`
63+
3. No other service is using port 8000
64+
65+
You can use [Inspector](https://github.com/modelcontextprotocol/inspector) to test Auth
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Simple MCP server with GitHub OAuth authentication."""
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Main entry point for simple MCP server with GitHub OAuth authentication."""
2+
3+
import sys
4+
5+
from mcp_simple_auth.server import main
6+
7+
sys.exit(main())

0 commit comments

Comments
 (0)