Skip to content

Commit f6f604b

Browse files
authored
Merge branch 'main' into main
2 parents 05dea67 + 6e418e6 commit f6f604b

File tree

8 files changed

+805
-694
lines changed

8 files changed

+805
-694
lines changed

.github/workflows/publish-docs-manually.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
uses: astral-sh/setup-uv@v3
2020
with:
2121
enable-cache: true
22+
version: 0.7.2
2223

2324
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
2425
- uses: actions/cache@v4

.github/workflows/publish-pypi.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
uses: astral-sh/setup-uv@v3
1717
with:
1818
enable-cache: true
19+
version: 0.7.2
1920

2021
- name: Set up Python 3.12
2122
run: uv python install 3.12
@@ -67,6 +68,7 @@ jobs:
6768
uses: astral-sh/setup-uv@v3
6869
with:
6970
enable-cache: true
71+
version: 0.7.2
7072

7173
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
7274
- uses: actions/cache@v4

.github/workflows/shared.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ jobs:
1313
uses: astral-sh/setup-uv@v3
1414
with:
1515
enable-cache: true
16+
version: 0.7.2
1617

1718
- name: Install the project
1819
run: uv sync --frozen --all-extras --dev --python 3.12
@@ -29,6 +30,7 @@ jobs:
2930
uses: astral-sh/setup-uv@v3
3031
with:
3132
enable-cache: true
33+
version: 0.7.2
3234

3335
- name: Install the project
3436
run: uv sync --frozen --all-extras --dev --python 3.12
@@ -50,6 +52,7 @@ jobs:
5052
uses: astral-sh/setup-uv@v3
5153
with:
5254
enable-cache: true
55+
version: 0.7.2
5356

5457
- name: Install the project
5558
run: uv sync --frozen --all-extras --dev --python ${{ matrix.python-version }}

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ from dataclasses import dataclass
163163

164164
from fake_database import Database # Replace with your actual DB type
165165

166-
from mcp.server.fastmcp import Context, FastMCP
166+
from mcp.server.fastmcp import FastMCP
167167

168168
# Create a named server
169169
mcp = FastMCP("My App")
@@ -195,9 +195,10 @@ mcp = FastMCP("My App", lifespan=app_lifespan)
195195

196196
# Access type-safe lifespan context in tools
197197
@mcp.tool()
198-
def query_db(ctx: Context) -> str:
198+
def query_db() -> str:
199199
"""Tool that uses initialized resources"""
200-
db = ctx.request_context.lifespan_context.db
200+
ctx = mcp.get_context()
201+
db = ctx.request_context.lifespan_context["db"]
201202
return db.query()
202203
```
203204

@@ -772,7 +773,7 @@ server = Server("example-server", lifespan=server_lifespan)
772773
# Access lifespan context in handlers
773774
@server.call_tool()
774775
async def query_db(name: str, arguments: dict) -> list:
775-
ctx = server.get_context()
776+
ctx = server.request_context
776777
db = ctx.lifespan_context["db"]
777778
return await db.query(arguments["query"])
778779
```

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ mcp = "mcp.cli:app [cli]"
4444
[tool.uv]
4545
resolution = "lowest-direct"
4646
default-groups = ["dev", "docs"]
47+
required-version = ">=0.7.2"
4748

4849
[dependency-groups]
4950
dev = [
@@ -55,6 +56,7 @@ dev = [
5556
"pytest-xdist>=3.6.1",
5657
"pytest-examples>=0.0.14",
5758
"pytest-pretty>=1.2.0",
59+
"inline-snapshot>=0.23.0",
5860
]
5961
docs = [
6062
"mkdocs>=1.6.1",
@@ -63,7 +65,6 @@ docs = [
6365
"mkdocstrings-python>=1.12.2",
6466
]
6567

66-
6768
[build-system]
6869
requires = ["hatchling", "uv-dynamic-versioning"]
6970
build-backend = "hatchling.build"

src/mcp/server/auth/routes.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -147,31 +147,19 @@ def create_auth_routes(
147147
return routes
148148

149149

150-
def modify_url_path(url: AnyHttpUrl, path_mapper: Callable[[str], str]) -> AnyHttpUrl:
151-
return AnyHttpUrl.build(
152-
scheme=url.scheme,
153-
username=url.username,
154-
password=url.password,
155-
host=url.host,
156-
port=url.port,
157-
path=path_mapper(url.path or ""),
158-
query=url.query,
159-
fragment=url.fragment,
160-
)
161-
162-
163150
def build_metadata(
164151
issuer_url: AnyHttpUrl,
165152
service_documentation_url: AnyHttpUrl | None,
166153
client_registration_options: ClientRegistrationOptions,
167154
revocation_options: RevocationOptions,
168155
) -> OAuthMetadata:
169-
authorization_url = modify_url_path(
170-
issuer_url, lambda path: path.rstrip("/") + AUTHORIZATION_PATH.lstrip("/")
156+
authorization_url = AnyHttpUrl(
157+
str(issuer_url).rstrip("/") + AUTHORIZATION_PATH
171158
)
172-
token_url = modify_url_path(
173-
issuer_url, lambda path: path.rstrip("/") + TOKEN_PATH.lstrip("/")
159+
token_url = AnyHttpUrl(
160+
str(issuer_url).rstrip("/") + TOKEN_PATH
174161
)
162+
175163
# Create metadata
176164
metadata = OAuthMetadata(
177165
issuer=issuer_url,
@@ -193,14 +181,14 @@ def build_metadata(
193181

194182
# Add registration endpoint if supported
195183
if client_registration_options.enabled:
196-
metadata.registration_endpoint = modify_url_path(
197-
issuer_url, lambda path: path.rstrip("/") + REGISTRATION_PATH.lstrip("/")
184+
metadata.registration_endpoint = AnyHttpUrl(
185+
str(issuer_url).rstrip("/") + REGISTRATION_PATH
198186
)
199187

200188
# Add revocation endpoint if supported
201189
if revocation_options.enabled:
202-
metadata.revocation_endpoint = modify_url_path(
203-
issuer_url, lambda path: path.rstrip("/") + REVOCATION_PATH.lstrip("/")
190+
metadata.revocation_endpoint = AnyHttpUrl(
191+
str(issuer_url).rstrip("/") + REVOCATION_PATH
204192
)
205193
metadata.revocation_endpoint_auth_methods_supported = ["client_secret_post"]
206194

tests/client/test_auth.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010

1111
import httpx
1212
import pytest
13+
from inline_snapshot import snapshot
1314
from pydantic import AnyHttpUrl
1415

1516
from mcp.client.auth import OAuthClientProvider
17+
from mcp.server.auth.routes import build_metadata
18+
from mcp.server.auth.settings import ClientRegistrationOptions, RevocationOptions
1619
from mcp.shared.auth import (
1720
OAuthClientInformationFull,
1821
OAuthClientMetadata,
@@ -905,3 +908,76 @@ async def test_token_exchange_error_basic(self, oauth_provider, oauth_client_inf
905908
await oauth_provider._exchange_code_for_token(
906909
"invalid_auth_code", oauth_client_info
907910
)
911+
912+
913+
@pytest.mark.parametrize(
914+
(
915+
"issuer_url",
916+
"service_documentation_url",
917+
"authorization_endpoint",
918+
"token_endpoint",
919+
"registration_endpoint",
920+
"revocation_endpoint",
921+
),
922+
(
923+
pytest.param(
924+
"https://auth.example.com",
925+
"https://auth.example.com/docs",
926+
"https://auth.example.com/authorize",
927+
"https://auth.example.com/token",
928+
"https://auth.example.com/register",
929+
"https://auth.example.com/revoke",
930+
id="simple-url",
931+
),
932+
pytest.param(
933+
"https://auth.example.com/",
934+
"https://auth.example.com/docs",
935+
"https://auth.example.com/authorize",
936+
"https://auth.example.com/token",
937+
"https://auth.example.com/register",
938+
"https://auth.example.com/revoke",
939+
id="with-trailing-slash",
940+
),
941+
pytest.param(
942+
"https://auth.example.com/v1/mcp",
943+
"https://auth.example.com/v1/mcp/docs",
944+
"https://auth.example.com/v1/mcp/authorize",
945+
"https://auth.example.com/v1/mcp/token",
946+
"https://auth.example.com/v1/mcp/register",
947+
"https://auth.example.com/v1/mcp/revoke",
948+
id="with-path-param",
949+
),
950+
),
951+
)
952+
def test_build_metadata(
953+
issuer_url: str,
954+
service_documentation_url: str,
955+
authorization_endpoint: str,
956+
token_endpoint: str,
957+
registration_endpoint: str,
958+
revocation_endpoint: str,
959+
):
960+
metadata = build_metadata(
961+
issuer_url=AnyHttpUrl(issuer_url),
962+
service_documentation_url=AnyHttpUrl(service_documentation_url),
963+
client_registration_options=ClientRegistrationOptions(
964+
enabled=True, valid_scopes=["read", "write", "admin"]
965+
),
966+
revocation_options=RevocationOptions(enabled=True),
967+
)
968+
969+
assert metadata == snapshot(
970+
OAuthMetadata(
971+
issuer=AnyHttpUrl(issuer_url),
972+
authorization_endpoint=AnyHttpUrl(authorization_endpoint),
973+
token_endpoint=AnyHttpUrl(token_endpoint),
974+
registration_endpoint=AnyHttpUrl(registration_endpoint),
975+
scopes_supported=["read", "write", "admin"],
976+
grant_types_supported=["authorization_code", "refresh_token"],
977+
token_endpoint_auth_methods_supported=["client_secret_post"],
978+
service_documentation=AnyHttpUrl(service_documentation_url),
979+
revocation_endpoint=AnyHttpUrl(revocation_endpoint),
980+
revocation_endpoint_auth_methods_supported=["client_secret_post"],
981+
code_challenge_methods_supported=["S256"],
982+
)
983+
)

0 commit comments

Comments
 (0)