Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
b86019e
1
Parent(s): 282875e
Support remote auth in mcpconfig
Browse files
src/fastmcp/utilities/mcp_config.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
-
from typing import TYPE_CHECKING, Any, Literal
|
| 4 |
from urllib.parse import urlparse
|
| 5 |
|
| 6 |
from pydantic import AnyUrl, Field
|
|
@@ -56,6 +56,12 @@ class RemoteMCPServer(FastMCPBaseModel):
|
|
| 56 |
url: str
|
| 57 |
headers: dict[str, str] = Field(default_factory=dict)
|
| 58 |
transport: Literal["streamable-http", "sse", "http"] | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
def to_transport(self) -> StreamableHttpTransport | SSETransport:
|
| 61 |
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
|
|
@@ -66,9 +72,11 @@ class RemoteMCPServer(FastMCPBaseModel):
|
|
| 66 |
transport = self.transport
|
| 67 |
|
| 68 |
if transport == "sse":
|
| 69 |
-
return SSETransport(self.url, headers=self.headers)
|
| 70 |
else:
|
| 71 |
-
return StreamableHttpTransport(
|
|
|
|
|
|
|
| 72 |
|
| 73 |
|
| 74 |
class MCPConfig(FastMCPBaseModel):
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
from typing import TYPE_CHECKING, Annotated, Any, Literal
|
| 4 |
from urllib.parse import urlparse
|
| 5 |
|
| 6 |
from pydantic import AnyUrl, Field
|
|
|
|
| 56 |
url: str
|
| 57 |
headers: dict[str, str] = Field(default_factory=dict)
|
| 58 |
transport: Literal["streamable-http", "sse", "http"] | None = None
|
| 59 |
+
auth: Annotated[
|
| 60 |
+
str | Literal["oauth"] | None,
|
| 61 |
+
Field(
|
| 62 |
+
description='Either a string representing a Bearer token or the literal "oauth" to use OAuth authentication.'
|
| 63 |
+
),
|
| 64 |
+
] = None
|
| 65 |
|
| 66 |
def to_transport(self) -> StreamableHttpTransport | SSETransport:
|
| 67 |
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
|
|
|
|
| 72 |
transport = self.transport
|
| 73 |
|
| 74 |
if transport == "sse":
|
| 75 |
+
return SSETransport(self.url, headers=self.headers, auth=self.auth)
|
| 76 |
else:
|
| 77 |
+
return StreamableHttpTransport(
|
| 78 |
+
self.url, headers=self.headers, auth=self.auth
|
| 79 |
+
)
|
| 80 |
|
| 81 |
|
| 82 |
class MCPConfig(FastMCPBaseModel):
|
tests/utilities/test_mcp_config.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
import inspect
|
| 2 |
from pathlib import Path
|
| 3 |
|
|
|
|
|
|
|
| 4 |
from fastmcp.client.client import Client
|
| 5 |
from fastmcp.client.transports import (
|
| 6 |
SSETransport,
|
|
@@ -136,3 +138,60 @@ async def test_multi_client(tmp_path: Path):
|
|
| 136 |
result_2 = await client.call_tool("test_2_add", {"a": 1, "b": 2})
|
| 137 |
assert result_1[0].text == "3" # type: ignore[attr-dict]
|
| 138 |
assert result_2[0].text == "3" # type: ignore[attr-dict]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import inspect
|
| 2 |
from pathlib import Path
|
| 3 |
|
| 4 |
+
from fastmcp.client.auth.bearer import BearerAuth
|
| 5 |
+
from fastmcp.client.auth.oauth import OAuthClientProvider
|
| 6 |
from fastmcp.client.client import Client
|
| 7 |
from fastmcp.client.transports import (
|
| 8 |
SSETransport,
|
|
|
|
| 138 |
result_2 = await client.call_tool("test_2_add", {"a": 1, "b": 2})
|
| 139 |
assert result_1[0].text == "3" # type: ignore[attr-dict]
|
| 140 |
assert result_2[0].text == "3" # type: ignore[attr-dict]
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
async def test_remote_config_default_no_auth():
|
| 144 |
+
config = {
|
| 145 |
+
"mcpServers": {
|
| 146 |
+
"test_server": {
|
| 147 |
+
"url": "http://localhost:8000",
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
client = Client(config)
|
| 152 |
+
assert isinstance(client.transport.transport, StreamableHttpTransport)
|
| 153 |
+
assert client.transport.transport.auth is None
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
async def test_remote_config_with_auth_token():
|
| 157 |
+
config = {
|
| 158 |
+
"mcpServers": {
|
| 159 |
+
"test_server": {
|
| 160 |
+
"url": "http://localhost:8000",
|
| 161 |
+
"auth": "test_token",
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
client = Client(config)
|
| 166 |
+
assert isinstance(client.transport.transport, StreamableHttpTransport)
|
| 167 |
+
assert isinstance(client.transport.transport.auth, BearerAuth)
|
| 168 |
+
assert client.transport.transport.auth.token.get_secret_value() == "test_token"
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
async def test_remote_config_sse_with_auth_token():
|
| 172 |
+
config = {
|
| 173 |
+
"mcpServers": {
|
| 174 |
+
"test_server": {
|
| 175 |
+
"url": "http://localhost:8000/sse",
|
| 176 |
+
"auth": "test_token",
|
| 177 |
+
}
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
client = Client(config)
|
| 181 |
+
assert isinstance(client.transport.transport, SSETransport)
|
| 182 |
+
assert isinstance(client.transport.transport.auth, BearerAuth)
|
| 183 |
+
assert client.transport.transport.auth.token.get_secret_value() == "test_token"
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
async def test_remote_config_with_oauth_literal():
|
| 187 |
+
config = {
|
| 188 |
+
"mcpServers": {
|
| 189 |
+
"test_server": {
|
| 190 |
+
"url": "http://localhost:8000",
|
| 191 |
+
"auth": "oauth",
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
}
|
| 195 |
+
client = Client(config)
|
| 196 |
+
assert isinstance(client.transport.transport, StreamableHttpTransport)
|
| 197 |
+
assert isinstance(client.transport.transport.auth, OAuthClientProvider)
|