Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
282875e
1
Parent(s): 86e2822
Fix passing token string to client auth
Browse files
src/fastmcp/client/transports.py
CHANGED
|
@@ -32,6 +32,7 @@ from mcp.shared.memory import create_connected_server_and_client_session
|
|
| 32 |
from pydantic import AnyUrl
|
| 33 |
from typing_extensions import Unpack
|
| 34 |
|
|
|
|
| 35 |
from fastmcp.client.auth.oauth import OAuth
|
| 36 |
from fastmcp.server.dependencies import get_http_headers
|
| 37 |
from fastmcp.server.server import FastMCP
|
|
@@ -152,7 +153,7 @@ class WSTransport(ClientTransport):
|
|
| 152 |
yield session
|
| 153 |
|
| 154 |
def __repr__(self) -> str:
|
| 155 |
-
return f"<
|
| 156 |
|
| 157 |
|
| 158 |
class SSETransport(ClientTransport):
|
|
@@ -183,8 +184,7 @@ class SSETransport(ClientTransport):
|
|
| 183 |
if auth == "oauth":
|
| 184 |
auth = OAuth(self.url)
|
| 185 |
elif isinstance(auth, str):
|
| 186 |
-
|
| 187 |
-
auth = None
|
| 188 |
self.auth = auth
|
| 189 |
|
| 190 |
@contextlib.asynccontextmanager
|
|
@@ -221,7 +221,7 @@ class SSETransport(ClientTransport):
|
|
| 221 |
yield session
|
| 222 |
|
| 223 |
def __repr__(self) -> str:
|
| 224 |
-
return f"<
|
| 225 |
|
| 226 |
|
| 227 |
class StreamableHttpTransport(ClientTransport):
|
|
@@ -252,8 +252,7 @@ class StreamableHttpTransport(ClientTransport):
|
|
| 252 |
if auth == "oauth":
|
| 253 |
auth = OAuth(self.url)
|
| 254 |
elif isinstance(auth, str):
|
| 255 |
-
|
| 256 |
-
auth = None
|
| 257 |
self.auth = auth
|
| 258 |
|
| 259 |
@contextlib.asynccontextmanager
|
|
@@ -291,7 +290,7 @@ class StreamableHttpTransport(ClientTransport):
|
|
| 291 |
yield session
|
| 292 |
|
| 293 |
def __repr__(self) -> str:
|
| 294 |
-
return f"<
|
| 295 |
|
| 296 |
|
| 297 |
class StdioTransport(ClientTransport):
|
|
@@ -683,7 +682,7 @@ class FastMCPTransport(ClientTransport):
|
|
| 683 |
yield session
|
| 684 |
|
| 685 |
def __repr__(self) -> str:
|
| 686 |
-
return f"<
|
| 687 |
|
| 688 |
|
| 689 |
class MCPConfigTransport(ClientTransport):
|
|
@@ -769,7 +768,7 @@ class MCPConfigTransport(ClientTransport):
|
|
| 769 |
yield session
|
| 770 |
|
| 771 |
def __repr__(self) -> str:
|
| 772 |
-
return f"<
|
| 773 |
|
| 774 |
|
| 775 |
@overload
|
|
|
|
| 32 |
from pydantic import AnyUrl
|
| 33 |
from typing_extensions import Unpack
|
| 34 |
|
| 35 |
+
from fastmcp.client.auth.bearer import BearerAuth
|
| 36 |
from fastmcp.client.auth.oauth import OAuth
|
| 37 |
from fastmcp.server.dependencies import get_http_headers
|
| 38 |
from fastmcp.server.server import FastMCP
|
|
|
|
| 153 |
yield session
|
| 154 |
|
| 155 |
def __repr__(self) -> str:
|
| 156 |
+
return f"<WebSocketTransport(url='{self.url}')>"
|
| 157 |
|
| 158 |
|
| 159 |
class SSETransport(ClientTransport):
|
|
|
|
| 184 |
if auth == "oauth":
|
| 185 |
auth = OAuth(self.url)
|
| 186 |
elif isinstance(auth, str):
|
| 187 |
+
auth = BearerAuth(auth)
|
|
|
|
| 188 |
self.auth = auth
|
| 189 |
|
| 190 |
@contextlib.asynccontextmanager
|
|
|
|
| 221 |
yield session
|
| 222 |
|
| 223 |
def __repr__(self) -> str:
|
| 224 |
+
return f"<SSETransport(url='{self.url}')>"
|
| 225 |
|
| 226 |
|
| 227 |
class StreamableHttpTransport(ClientTransport):
|
|
|
|
| 252 |
if auth == "oauth":
|
| 253 |
auth = OAuth(self.url)
|
| 254 |
elif isinstance(auth, str):
|
| 255 |
+
auth = BearerAuth(auth)
|
|
|
|
| 256 |
self.auth = auth
|
| 257 |
|
| 258 |
@contextlib.asynccontextmanager
|
|
|
|
| 290 |
yield session
|
| 291 |
|
| 292 |
def __repr__(self) -> str:
|
| 293 |
+
return f"<StreamableHttpTransport(url='{self.url}')>"
|
| 294 |
|
| 295 |
|
| 296 |
class StdioTransport(ClientTransport):
|
|
|
|
| 682 |
yield session
|
| 683 |
|
| 684 |
def __repr__(self) -> str:
|
| 685 |
+
return f"<FastMCPTransport(server='{self.server.name}')>"
|
| 686 |
|
| 687 |
|
| 688 |
class MCPConfigTransport(ClientTransport):
|
|
|
|
| 768 |
yield session
|
| 769 |
|
| 770 |
def __repr__(self) -> str:
|
| 771 |
+
return f"<MCPConfigTransport(config='{self.config}')>"
|
| 772 |
|
| 773 |
|
| 774 |
@overload
|
tests/client/test_client.py
CHANGED
|
@@ -4,9 +4,11 @@ from typing import cast
|
|
| 4 |
|
| 5 |
import pytest
|
| 6 |
from mcp import McpError
|
|
|
|
| 7 |
from pydantic import AnyUrl
|
| 8 |
|
| 9 |
from fastmcp.client import Client
|
|
|
|
| 10 |
from fastmcp.client.transports import (
|
| 11 |
FastMCPTransport,
|
| 12 |
MCPConfigTransport,
|
|
@@ -810,3 +812,73 @@ class TestInferTransport:
|
|
| 810 |
server = FastMCP1()
|
| 811 |
transport = infer_transport(server)
|
| 812 |
assert isinstance(transport, FastMCPTransport)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
import pytest
|
| 6 |
from mcp import McpError
|
| 7 |
+
from mcp.client.auth import OAuthClientProvider
|
| 8 |
from pydantic import AnyUrl
|
| 9 |
|
| 10 |
from fastmcp.client import Client
|
| 11 |
+
from fastmcp.client.auth.bearer import BearerAuth
|
| 12 |
from fastmcp.client.transports import (
|
| 13 |
FastMCPTransport,
|
| 14 |
MCPConfigTransport,
|
|
|
|
| 812 |
server = FastMCP1()
|
| 813 |
transport = infer_transport(server)
|
| 814 |
assert isinstance(transport, FastMCPTransport)
|
| 815 |
+
|
| 816 |
+
|
| 817 |
+
class TestAuth:
|
| 818 |
+
def test_default_auth_is_none(self):
|
| 819 |
+
client = Client(transport=StreamableHttpTransport("http://localhost:8000"))
|
| 820 |
+
assert client.transport.auth is None
|
| 821 |
+
|
| 822 |
+
def test_stdio_doesnt_support_auth(self):
|
| 823 |
+
with pytest.raises(ValueError, match="This transport does not support auth"):
|
| 824 |
+
Client(transport=StdioTransport("echo", ["hello"]), auth="oauth")
|
| 825 |
+
|
| 826 |
+
def test_oauth_literal_sets_up_oauth_shttp(self):
|
| 827 |
+
client = Client(
|
| 828 |
+
transport=StreamableHttpTransport("http://localhost:8000"), auth="oauth"
|
| 829 |
+
)
|
| 830 |
+
assert isinstance(client.transport, StreamableHttpTransport)
|
| 831 |
+
assert isinstance(client.transport.auth, OAuthClientProvider)
|
| 832 |
+
|
| 833 |
+
def test_oauth_literal_pass_direct_to_transport(self):
|
| 834 |
+
client = Client(
|
| 835 |
+
transport=StreamableHttpTransport("http://localhost:8000", auth="oauth"),
|
| 836 |
+
)
|
| 837 |
+
assert isinstance(client.transport, StreamableHttpTransport)
|
| 838 |
+
assert isinstance(client.transport.auth, OAuthClientProvider)
|
| 839 |
+
|
| 840 |
+
def test_oauth_literal_sets_up_oauth_sse(self):
|
| 841 |
+
client = Client(transport=SSETransport("http://localhost:8000"), auth="oauth")
|
| 842 |
+
assert isinstance(client.transport, SSETransport)
|
| 843 |
+
assert isinstance(client.transport.auth, OAuthClientProvider)
|
| 844 |
+
|
| 845 |
+
def test_oauth_literal_pass_direct_to_transport_sse(self):
|
| 846 |
+
client = Client(transport=SSETransport("http://localhost:8000", auth="oauth"))
|
| 847 |
+
assert isinstance(client.transport, SSETransport)
|
| 848 |
+
assert isinstance(client.transport.auth, OAuthClientProvider)
|
| 849 |
+
|
| 850 |
+
def test_auth_string_sets_up_bearer_auth_shttp(self):
|
| 851 |
+
client = Client(
|
| 852 |
+
transport=StreamableHttpTransport("http://localhost:8000"),
|
| 853 |
+
auth="test_token",
|
| 854 |
+
)
|
| 855 |
+
assert isinstance(client.transport, StreamableHttpTransport)
|
| 856 |
+
assert isinstance(client.transport.auth, BearerAuth)
|
| 857 |
+
assert client.transport.auth.token.get_secret_value() == "test_token"
|
| 858 |
+
|
| 859 |
+
def test_auth_string_pass_direct_to_transport_shttp(self):
|
| 860 |
+
client = Client(
|
| 861 |
+
transport=StreamableHttpTransport(
|
| 862 |
+
"http://localhost:8000", auth="test_token"
|
| 863 |
+
),
|
| 864 |
+
)
|
| 865 |
+
assert isinstance(client.transport, StreamableHttpTransport)
|
| 866 |
+
assert isinstance(client.transport.auth, BearerAuth)
|
| 867 |
+
assert client.transport.auth.token.get_secret_value() == "test_token"
|
| 868 |
+
|
| 869 |
+
def test_auth_string_sets_up_bearer_auth_sse(self):
|
| 870 |
+
client = Client(
|
| 871 |
+
transport=SSETransport("http://localhost:8000"),
|
| 872 |
+
auth="test_token",
|
| 873 |
+
)
|
| 874 |
+
assert isinstance(client.transport, SSETransport)
|
| 875 |
+
assert isinstance(client.transport.auth, BearerAuth)
|
| 876 |
+
assert client.transport.auth.token.get_secret_value() == "test_token"
|
| 877 |
+
|
| 878 |
+
def test_auth_string_pass_direct_to_transport_sse(self):
|
| 879 |
+
client = Client(
|
| 880 |
+
transport=SSETransport("http://localhost:8000", auth="test_token"),
|
| 881 |
+
)
|
| 882 |
+
assert isinstance(client.transport, SSETransport)
|
| 883 |
+
assert isinstance(client.transport.auth, BearerAuth)
|
| 884 |
+
assert client.transport.auth.token.get_secret_value() == "test_token"
|
tests/server/test_proxy.py
CHANGED
|
@@ -9,7 +9,7 @@ from pydantic import AnyUrl
|
|
| 9 |
|
| 10 |
from fastmcp import FastMCP
|
| 11 |
from fastmcp.client import Client
|
| 12 |
-
from fastmcp.client.transports import FastMCPTransport
|
| 13 |
from fastmcp.exceptions import ToolError
|
| 14 |
from fastmcp.server.proxy import FastMCPProxy
|
| 15 |
|
|
@@ -104,7 +104,8 @@ def test_as_proxy_with_url():
|
|
| 104 |
"""FastMCP.as_proxy should accept a URL without connecting."""
|
| 105 |
proxy = FastMCP.as_proxy("http://example.com/mcp")
|
| 106 |
assert isinstance(proxy, FastMCPProxy)
|
| 107 |
-
assert
|
|
|
|
| 108 |
|
| 109 |
|
| 110 |
class TestTools:
|
|
|
|
| 9 |
|
| 10 |
from fastmcp import FastMCP
|
| 11 |
from fastmcp.client import Client
|
| 12 |
+
from fastmcp.client.transports import FastMCPTransport, StreamableHttpTransport
|
| 13 |
from fastmcp.exceptions import ToolError
|
| 14 |
from fastmcp.server.proxy import FastMCPProxy
|
| 15 |
|
|
|
|
| 104 |
"""FastMCP.as_proxy should accept a URL without connecting."""
|
| 105 |
proxy = FastMCP.as_proxy("http://example.com/mcp")
|
| 106 |
assert isinstance(proxy, FastMCPProxy)
|
| 107 |
+
assert isinstance(proxy.client.transport, StreamableHttpTransport)
|
| 108 |
+
assert proxy.client.transport.url == "http://example.com/mcp"
|
| 109 |
|
| 110 |
|
| 111 |
class TestTools:
|