Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
abb2759
1
Parent(s): 91deb88
Add tests; update default issuer
Browse files
src/fastmcp/server/auth/providers/bearer.py
CHANGED
|
@@ -177,7 +177,7 @@ class BearerAuthProvider(OAuthProvider):
|
|
| 177 |
raise ValueError("Provide either public_key or jwks_uri, not both")
|
| 178 |
|
| 179 |
super().__init__(
|
| 180 |
-
issuer_url=issuer or "
|
| 181 |
client_registration_options=ClientRegistrationOptions(enabled=False),
|
| 182 |
revocation_options=RevocationOptions(enabled=False),
|
| 183 |
required_scopes=required_scopes,
|
|
|
|
| 177 |
raise ValueError("Provide either public_key or jwks_uri, not both")
|
| 178 |
|
| 179 |
super().__init__(
|
| 180 |
+
issuer_url=issuer or "https://fastmcp.example.com",
|
| 181 |
client_registration_options=ClientRegistrationOptions(enabled=False),
|
| 182 |
revocation_options=RevocationOptions(enabled=False),
|
| 183 |
required_scopes=required_scopes,
|
tests/auth/providers/test_bearer.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
from collections.abc import Generator
|
|
|
|
| 2 |
|
| 3 |
import httpx
|
| 4 |
import pytest
|
|
@@ -32,11 +33,17 @@ def bearer_provider(rsa_key_pair: RSAKeyPair) -> BearerAuthProvider:
|
|
| 32 |
)
|
| 33 |
|
| 34 |
|
| 35 |
-
def run_mcp_server(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
mcp = FastMCP(
|
| 37 |
auth=BearerAuthProvider(
|
| 38 |
-
issuer="https://test.example.com",
|
| 39 |
public_key=public_key,
|
|
|
|
| 40 |
)
|
| 41 |
)
|
| 42 |
|
|
@@ -44,13 +51,15 @@ def run_mcp_server(public_key: str, host: str, port: int, **kwargs) -> None:
|
|
| 44 |
def add(a: int, b: int) -> int:
|
| 45 |
return a + b
|
| 46 |
|
| 47 |
-
mcp.run(host=host, port=port, **
|
| 48 |
|
| 49 |
|
| 50 |
@pytest.fixture(scope="module")
|
| 51 |
def mcp_server_url(rsa_key_pair: RSAKeyPair) -> Generator[str]:
|
| 52 |
with run_server_in_process(
|
| 53 |
-
run_mcp_server,
|
|
|
|
|
|
|
| 54 |
) as url:
|
| 55 |
yield f"{url}/mcp"
|
| 56 |
|
|
@@ -75,7 +84,8 @@ class TestRSAKeyPair:
|
|
| 75 |
def test_create_basic_token(self, rsa_key_pair: RSAKeyPair):
|
| 76 |
"""Test basic token creation."""
|
| 77 |
token = rsa_key_pair.create_token(
|
| 78 |
-
subject="test-user",
|
|
|
|
| 79 |
)
|
| 80 |
|
| 81 |
assert isinstance(token, str)
|
|
@@ -359,8 +369,38 @@ class TestFastMCPBearerAuth:
|
|
| 359 |
async def test_unauthorized_access(self, mcp_server_url: str):
|
| 360 |
with pytest.raises(httpx.HTTPStatusError, match="401"):
|
| 361 |
async with Client(mcp_server_url) as client:
|
| 362 |
-
await client.
|
|
|
|
| 363 |
|
| 364 |
async def test_authorized_access(self, mcp_server_url: str, bearer_token):
|
| 365 |
async with Client(mcp_server_url, auth=BearerAuth(bearer_token)) as client:
|
| 366 |
-
await client.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from collections.abc import Generator
|
| 2 |
+
from typing import Any
|
| 3 |
|
| 4 |
import httpx
|
| 5 |
import pytest
|
|
|
|
| 33 |
)
|
| 34 |
|
| 35 |
|
| 36 |
+
def run_mcp_server(
|
| 37 |
+
public_key: str,
|
| 38 |
+
host: str,
|
| 39 |
+
port: int,
|
| 40 |
+
auth_kwargs: dict[str, Any] | None = None,
|
| 41 |
+
run_kwargs: dict[str, Any] | None = None,
|
| 42 |
+
) -> None:
|
| 43 |
mcp = FastMCP(
|
| 44 |
auth=BearerAuthProvider(
|
|
|
|
| 45 |
public_key=public_key,
|
| 46 |
+
**auth_kwargs or {},
|
| 47 |
)
|
| 48 |
)
|
| 49 |
|
|
|
|
| 51 |
def add(a: int, b: int) -> int:
|
| 52 |
return a + b
|
| 53 |
|
| 54 |
+
mcp.run(host=host, port=port, **run_kwargs or {})
|
| 55 |
|
| 56 |
|
| 57 |
@pytest.fixture(scope="module")
|
| 58 |
def mcp_server_url(rsa_key_pair: RSAKeyPair) -> Generator[str]:
|
| 59 |
with run_server_in_process(
|
| 60 |
+
run_mcp_server,
|
| 61 |
+
public_key=rsa_key_pair.public_key,
|
| 62 |
+
run_kwargs=dict(transport="streamable-http"),
|
| 63 |
) as url:
|
| 64 |
yield f"{url}/mcp"
|
| 65 |
|
|
|
|
| 84 |
def test_create_basic_token(self, rsa_key_pair: RSAKeyPair):
|
| 85 |
"""Test basic token creation."""
|
| 86 |
token = rsa_key_pair.create_token(
|
| 87 |
+
subject="test-user",
|
| 88 |
+
issuer="https://test.example.com",
|
| 89 |
)
|
| 90 |
|
| 91 |
assert isinstance(token, str)
|
|
|
|
| 369 |
async def test_unauthorized_access(self, mcp_server_url: str):
|
| 370 |
with pytest.raises(httpx.HTTPStatusError, match="401"):
|
| 371 |
async with Client(mcp_server_url) as client:
|
| 372 |
+
tools = await client.list_tools() # noqa: F841
|
| 373 |
+
assert "tools" not in locals()
|
| 374 |
|
| 375 |
async def test_authorized_access(self, mcp_server_url: str, bearer_token):
|
| 376 |
async with Client(mcp_server_url, auth=BearerAuth(bearer_token)) as client:
|
| 377 |
+
tools = await client.list_tools() # noqa: F841
|
| 378 |
+
assert tools
|
| 379 |
+
|
| 380 |
+
async def test_invalid_token_raises_401(self, mcp_server_url: str):
|
| 381 |
+
with pytest.raises(httpx.HTTPStatusError, match="401"):
|
| 382 |
+
async with Client(mcp_server_url, auth=BearerAuth("invalid")) as client:
|
| 383 |
+
tools = await client.list_tools() # noqa: F841
|
| 384 |
+
assert "tools" not in locals()
|
| 385 |
+
|
| 386 |
+
async def test_expired_token(self, mcp_server_url: str, rsa_key_pair: RSAKeyPair):
|
| 387 |
+
token = rsa_key_pair.create_token(
|
| 388 |
+
subject="test-user",
|
| 389 |
+
issuer="https://test.example.com",
|
| 390 |
+
audience="https://api.example.com",
|
| 391 |
+
expires_in_seconds=-3600,
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
with pytest.raises(httpx.HTTPStatusError, match="401"):
|
| 395 |
+
async with Client(mcp_server_url, auth=BearerAuth(token)) as client:
|
| 396 |
+
tools = await client.list_tools() # noqa: F841
|
| 397 |
+
assert "tools" not in locals()
|
| 398 |
+
|
| 399 |
+
async def test_token_with_bad_signature(self, mcp_server_url: str):
|
| 400 |
+
rsa_key_pair = RSAKeyPair.generate()
|
| 401 |
+
token = rsa_key_pair.create_token()
|
| 402 |
+
|
| 403 |
+
with pytest.raises(httpx.HTTPStatusError, match="401"):
|
| 404 |
+
async with Client(mcp_server_url, auth=BearerAuth(token)) as client:
|
| 405 |
+
tools = await client.list_tools() # noqa: F841
|
| 406 |
+
assert "tools" not in locals()
|