Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
b30e5c9
1
Parent(s): 3dd2080
Condense client kwargs
Browse files- .gitignore +47 -4
- src/fastmcp/client/base.py +28 -8
- src/fastmcp/client/sse.py +5 -28
- src/fastmcp/client/stdio.py +5 -28
- src/fastmcp/client/websocket.py +5 -28
.gitignore
CHANGED
|
@@ -1,19 +1,62 @@
|
|
| 1 |
# Python-generated files
|
| 2 |
__pycache__/
|
| 3 |
-
*.py[
|
|
|
|
| 4 |
build/
|
| 5 |
dist/
|
| 6 |
wheels/
|
| 7 |
-
*.egg-info
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
# Virtual environments
|
| 10 |
.venv
|
| 11 |
-
|
|
|
|
|
|
|
| 12 |
.env
|
| 13 |
|
|
|
|
|
|
|
| 14 |
|
|
|
|
| 15 |
src/fastmcp/_version.py
|
| 16 |
|
| 17 |
-
#
|
| 18 |
.cursorrules
|
| 19 |
.vscode/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Python-generated files
|
| 2 |
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
build/
|
| 6 |
dist/
|
| 7 |
wheels/
|
| 8 |
+
*.egg-info/
|
| 9 |
+
*.egg
|
| 10 |
+
MANIFEST
|
| 11 |
+
.pytest_cache/
|
| 12 |
+
.coverage
|
| 13 |
+
htmlcov/
|
| 14 |
+
.tox/
|
| 15 |
+
nosetests.xml
|
| 16 |
+
coverage.xml
|
| 17 |
+
*.cover
|
| 18 |
|
| 19 |
# Virtual environments
|
| 20 |
.venv
|
| 21 |
+
venv/
|
| 22 |
+
env/
|
| 23 |
+
ENV/
|
| 24 |
.env
|
| 25 |
|
| 26 |
+
# System files
|
| 27 |
+
.DS_Store
|
| 28 |
|
| 29 |
+
# Version file
|
| 30 |
src/fastmcp/_version.py
|
| 31 |
|
| 32 |
+
# Editors and IDEs
|
| 33 |
.cursorrules
|
| 34 |
.vscode/
|
| 35 |
+
.idea/
|
| 36 |
+
*.swp
|
| 37 |
+
*.swo
|
| 38 |
+
*~
|
| 39 |
+
.project
|
| 40 |
+
.pydevproject
|
| 41 |
+
.settings/
|
| 42 |
+
|
| 43 |
+
# Jupyter Notebook
|
| 44 |
+
.ipynb_checkpoints
|
| 45 |
+
|
| 46 |
+
# Type checking
|
| 47 |
+
.mypy_cache/
|
| 48 |
+
.dmypy.json
|
| 49 |
+
dmypy.json
|
| 50 |
+
.pyre/
|
| 51 |
+
.pytype/
|
| 52 |
+
|
| 53 |
+
# Local development
|
| 54 |
+
.python-version
|
| 55 |
+
.envrc
|
| 56 |
+
.direnv/
|
| 57 |
+
|
| 58 |
+
# Logs and databases
|
| 59 |
+
*.log
|
| 60 |
+
*.sqlite
|
| 61 |
+
*.db
|
| 62 |
+
*.ddb
|
src/fastmcp/client/base.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import abc
|
| 2 |
import contextlib
|
| 3 |
import datetime
|
| 4 |
-
from typing import Any, AsyncContextManager, Optional
|
| 5 |
|
| 6 |
import mcp.types
|
| 7 |
from mcp import ClientSession
|
|
@@ -19,6 +19,23 @@ def _get_roots_callback(roots: list[mcp.types.Root]) -> ListRootsFnT | None:
|
|
| 19 |
return _roots_callback
|
| 20 |
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
class BaseClient(abc.ABC):
|
| 23 |
def __init__(
|
| 24 |
self,
|
|
@@ -48,6 +65,15 @@ class BaseClient(abc.ABC):
|
|
| 48 |
self._message_handler = message_handler
|
| 49 |
self._read_timeout_seconds = read_timeout_seconds
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
@property
|
| 52 |
def transport(self):
|
| 53 |
"""Get the current transport connection"""
|
|
@@ -71,13 +97,7 @@ class BaseClient(abc.ABC):
|
|
| 71 |
return self._session is not None
|
| 72 |
|
| 73 |
@abc.abstractmethod
|
| 74 |
-
def _connect(
|
| 75 |
-
self,
|
| 76 |
-
sampling_callback: SamplingFnT | None = None,
|
| 77 |
-
list_roots_callback: ListRootsFnT | None = None,
|
| 78 |
-
logging_callback: LoggingFnT | None = None,
|
| 79 |
-
message_handler: MessageHandlerFnT | None = None,
|
| 80 |
-
) -> AsyncContextManager:
|
| 81 |
"""Return an async context manager that handles connection lifecycle.
|
| 82 |
This will be called by __aenter__ to establish the connection."""
|
| 83 |
raise NotImplementedError("Subclasses must implement this method")
|
|
|
|
| 1 |
import abc
|
| 2 |
import contextlib
|
| 3 |
import datetime
|
| 4 |
+
from typing import Any, AsyncContextManager, Optional, TypedDict
|
| 5 |
|
| 6 |
import mcp.types
|
| 7 |
from mcp import ClientSession
|
|
|
|
| 19 |
return _roots_callback
|
| 20 |
|
| 21 |
|
| 22 |
+
class ClientKwargs(TypedDict, total=False):
|
| 23 |
+
roots: list[mcp.types.Root] | None
|
| 24 |
+
sampling_callback: SamplingFnT | None
|
| 25 |
+
list_roots_callback: ListRootsFnT | None
|
| 26 |
+
logging_callback: LoggingFnT | None
|
| 27 |
+
message_handler: MessageHandlerFnT | None
|
| 28 |
+
read_timeout_seconds: datetime.timedelta | None
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class SessionKwargs(TypedDict, total=False):
|
| 32 |
+
sampling_callback: SamplingFnT | None
|
| 33 |
+
list_roots_callback: ListRootsFnT | None
|
| 34 |
+
logging_callback: LoggingFnT | None
|
| 35 |
+
message_handler: MessageHandlerFnT | None
|
| 36 |
+
read_timeout_seconds: datetime.timedelta | None
|
| 37 |
+
|
| 38 |
+
|
| 39 |
class BaseClient(abc.ABC):
|
| 40 |
def __init__(
|
| 41 |
self,
|
|
|
|
| 65 |
self._message_handler = message_handler
|
| 66 |
self._read_timeout_seconds = read_timeout_seconds
|
| 67 |
|
| 68 |
+
def _session_kwargs(self) -> SessionKwargs:
|
| 69 |
+
return SessionKwargs(
|
| 70 |
+
sampling_callback=self._sampling_callback,
|
| 71 |
+
list_roots_callback=self._list_roots_callback,
|
| 72 |
+
logging_callback=self._logging_callback,
|
| 73 |
+
message_handler=self._message_handler,
|
| 74 |
+
read_timeout_seconds=self._read_timeout_seconds,
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
@property
|
| 78 |
def transport(self):
|
| 79 |
"""Get the current transport connection"""
|
|
|
|
| 97 |
return self._session is not None
|
| 98 |
|
| 99 |
@abc.abstractmethod
|
| 100 |
+
def _connect(self) -> AsyncContextManager:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
"""Return an async context manager that handles connection lifecycle.
|
| 102 |
This will be called by __aenter__ to establish the connection."""
|
| 103 |
raise NotImplementedError("Subclasses must implement this method")
|
src/fastmcp/client/sse.py
CHANGED
|
@@ -1,17 +1,10 @@
|
|
| 1 |
import contextlib
|
| 2 |
-
import datetime
|
| 3 |
|
| 4 |
-
import mcp.types
|
| 5 |
from mcp import ClientSession
|
| 6 |
from mcp.client.sse import sse_client
|
|
|
|
| 7 |
|
| 8 |
-
from fastmcp.client.base import
|
| 9 |
-
BaseClient,
|
| 10 |
-
ListRootsFnT,
|
| 11 |
-
LoggingFnT,
|
| 12 |
-
MessageHandlerFnT,
|
| 13 |
-
SamplingFnT,
|
| 14 |
-
)
|
| 15 |
|
| 16 |
|
| 17 |
class SSEClient(BaseClient):
|
|
@@ -19,21 +12,9 @@ class SSEClient(BaseClient):
|
|
| 19 |
self,
|
| 20 |
url: str,
|
| 21 |
headers: dict[str, str] | None = None,
|
| 22 |
-
|
| 23 |
-
sampling_callback: SamplingFnT | None = None,
|
| 24 |
-
list_roots_callback: ListRootsFnT | None = None,
|
| 25 |
-
logging_callback: LoggingFnT | None = None,
|
| 26 |
-
message_handler: MessageHandlerFnT | None = None,
|
| 27 |
-
read_timeout_seconds: datetime.timedelta | None = None,
|
| 28 |
):
|
| 29 |
-
super().__init__(
|
| 30 |
-
roots=roots,
|
| 31 |
-
sampling_callback=sampling_callback,
|
| 32 |
-
list_roots_callback=list_roots_callback,
|
| 33 |
-
logging_callback=logging_callback,
|
| 34 |
-
message_handler=message_handler,
|
| 35 |
-
read_timeout_seconds=read_timeout_seconds,
|
| 36 |
-
)
|
| 37 |
self.url = url
|
| 38 |
self.headers = headers or {}
|
| 39 |
|
|
@@ -45,11 +26,7 @@ class SSEClient(BaseClient):
|
|
| 45 |
async with ClientSession(
|
| 46 |
read_stream=read_stream,
|
| 47 |
write_stream=write_stream,
|
| 48 |
-
|
| 49 |
-
list_roots_callback=self._list_roots_callback,
|
| 50 |
-
logging_callback=self._logging_callback,
|
| 51 |
-
message_handler=self._message_handler,
|
| 52 |
-
read_timeout_seconds=self._read_timeout_seconds,
|
| 53 |
) as session:
|
| 54 |
async with self._set_session(transport, session):
|
| 55 |
yield self
|
|
|
|
| 1 |
import contextlib
|
|
|
|
| 2 |
|
|
|
|
| 3 |
from mcp import ClientSession
|
| 4 |
from mcp.client.sse import sse_client
|
| 5 |
+
from typing_extensions import Unpack
|
| 6 |
|
| 7 |
+
from fastmcp.client.base import BaseClient, ClientKwargs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
class SSEClient(BaseClient):
|
|
|
|
| 12 |
self,
|
| 13 |
url: str,
|
| 14 |
headers: dict[str, str] | None = None,
|
| 15 |
+
**kwargs: Unpack[ClientKwargs],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
):
|
| 17 |
+
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
self.url = url
|
| 19 |
self.headers = headers or {}
|
| 20 |
|
|
|
|
| 26 |
async with ClientSession(
|
| 27 |
read_stream=read_stream,
|
| 28 |
write_stream=write_stream,
|
| 29 |
+
**self._session_kwargs(),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
) as session:
|
| 31 |
async with self._set_session(transport, session):
|
| 32 |
yield self
|
src/fastmcp/client/stdio.py
CHANGED
|
@@ -1,38 +1,19 @@
|
|
| 1 |
import contextlib
|
| 2 |
-
import datetime
|
| 3 |
|
| 4 |
-
import mcp.types
|
| 5 |
from mcp import ClientSession, StdioServerParameters
|
| 6 |
from mcp.client.stdio import stdio_client
|
|
|
|
| 7 |
|
| 8 |
-
from fastmcp.client.base import
|
| 9 |
-
BaseClient,
|
| 10 |
-
ListRootsFnT,
|
| 11 |
-
LoggingFnT,
|
| 12 |
-
MessageHandlerFnT,
|
| 13 |
-
SamplingFnT,
|
| 14 |
-
)
|
| 15 |
|
| 16 |
|
| 17 |
class StdioClient(BaseClient):
|
| 18 |
def __init__(
|
| 19 |
self,
|
| 20 |
server_script_path: str,
|
| 21 |
-
|
| 22 |
-
sampling_callback: SamplingFnT | None = None,
|
| 23 |
-
list_roots_callback: ListRootsFnT | None = None,
|
| 24 |
-
logging_callback: LoggingFnT | None = None,
|
| 25 |
-
message_handler: MessageHandlerFnT | None = None,
|
| 26 |
-
read_timeout_seconds: datetime.timedelta | None = None,
|
| 27 |
):
|
| 28 |
-
super().__init__(
|
| 29 |
-
roots=roots,
|
| 30 |
-
sampling_callback=sampling_callback,
|
| 31 |
-
list_roots_callback=list_roots_callback,
|
| 32 |
-
logging_callback=logging_callback,
|
| 33 |
-
message_handler=message_handler,
|
| 34 |
-
read_timeout_seconds=read_timeout_seconds,
|
| 35 |
-
)
|
| 36 |
self.server_script_path = server_script_path
|
| 37 |
|
| 38 |
@contextlib.asynccontextmanager
|
|
@@ -54,11 +35,7 @@ class StdioClient(BaseClient):
|
|
| 54 |
async with ClientSession(
|
| 55 |
read_stream=stdio,
|
| 56 |
write_stream=write,
|
| 57 |
-
|
| 58 |
-
list_roots_callback=self._list_roots_callback,
|
| 59 |
-
logging_callback=self._logging_callback,
|
| 60 |
-
message_handler=self._message_handler,
|
| 61 |
-
read_timeout_seconds=self._read_timeout_seconds,
|
| 62 |
) as session:
|
| 63 |
async with self._set_session(transport, session):
|
| 64 |
yield self
|
|
|
|
| 1 |
import contextlib
|
|
|
|
| 2 |
|
|
|
|
| 3 |
from mcp import ClientSession, StdioServerParameters
|
| 4 |
from mcp.client.stdio import stdio_client
|
| 5 |
+
from typing_extensions import Unpack
|
| 6 |
|
| 7 |
+
from fastmcp.client.base import BaseClient, ClientKwargs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
class StdioClient(BaseClient):
|
| 11 |
def __init__(
|
| 12 |
self,
|
| 13 |
server_script_path: str,
|
| 14 |
+
**kwargs: Unpack[ClientKwargs],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
):
|
| 16 |
+
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
self.server_script_path = server_script_path
|
| 18 |
|
| 19 |
@contextlib.asynccontextmanager
|
|
|
|
| 35 |
async with ClientSession(
|
| 36 |
read_stream=stdio,
|
| 37 |
write_stream=write,
|
| 38 |
+
**self._session_kwargs(),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
) as session:
|
| 40 |
async with self._set_session(transport, session):
|
| 41 |
yield self
|
src/fastmcp/client/websocket.py
CHANGED
|
@@ -1,38 +1,19 @@
|
|
| 1 |
import contextlib
|
| 2 |
-
import datetime
|
| 3 |
|
| 4 |
-
import mcp.types
|
| 5 |
from mcp import ClientSession
|
| 6 |
from mcp.client.websocket import websocket_client
|
|
|
|
| 7 |
|
| 8 |
-
from fastmcp.client.base import
|
| 9 |
-
BaseClient,
|
| 10 |
-
ListRootsFnT,
|
| 11 |
-
LoggingFnT,
|
| 12 |
-
MessageHandlerFnT,
|
| 13 |
-
SamplingFnT,
|
| 14 |
-
)
|
| 15 |
|
| 16 |
|
| 17 |
class WebSocketClient(BaseClient):
|
| 18 |
def __init__(
|
| 19 |
self,
|
| 20 |
url: str,
|
| 21 |
-
|
| 22 |
-
sampling_callback: SamplingFnT | None = None,
|
| 23 |
-
list_roots_callback: ListRootsFnT | None = None,
|
| 24 |
-
logging_callback: LoggingFnT | None = None,
|
| 25 |
-
message_handler: MessageHandlerFnT | None = None,
|
| 26 |
-
read_timeout_seconds: datetime.timedelta | None = None,
|
| 27 |
):
|
| 28 |
-
super().__init__(
|
| 29 |
-
roots=roots,
|
| 30 |
-
sampling_callback=sampling_callback,
|
| 31 |
-
list_roots_callback=list_roots_callback,
|
| 32 |
-
logging_callback=logging_callback,
|
| 33 |
-
message_handler=message_handler,
|
| 34 |
-
read_timeout_seconds=read_timeout_seconds,
|
| 35 |
-
)
|
| 36 |
self.url = url
|
| 37 |
|
| 38 |
@contextlib.asynccontextmanager
|
|
@@ -44,11 +25,7 @@ class WebSocketClient(BaseClient):
|
|
| 44 |
async with ClientSession(
|
| 45 |
read_stream=read_stream,
|
| 46 |
write_stream=write_stream,
|
| 47 |
-
|
| 48 |
-
list_roots_callback=self._list_roots_callback,
|
| 49 |
-
logging_callback=self._logging_callback,
|
| 50 |
-
message_handler=self._message_handler,
|
| 51 |
-
read_timeout_seconds=self._read_timeout_seconds,
|
| 52 |
) as session:
|
| 53 |
async with self._set_session(transport, session):
|
| 54 |
yield self
|
|
|
|
| 1 |
import contextlib
|
|
|
|
| 2 |
|
|
|
|
| 3 |
from mcp import ClientSession
|
| 4 |
from mcp.client.websocket import websocket_client
|
| 5 |
+
from typing_extensions import Unpack
|
| 6 |
|
| 7 |
+
from fastmcp.client.base import BaseClient, ClientKwargs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
class WebSocketClient(BaseClient):
|
| 11 |
def __init__(
|
| 12 |
self,
|
| 13 |
url: str,
|
| 14 |
+
**kwargs: Unpack[ClientKwargs],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
):
|
| 16 |
+
super().__init__(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
self.url = url
|
| 18 |
|
| 19 |
@contextlib.asynccontextmanager
|
|
|
|
| 25 |
async with ClientSession(
|
| 26 |
read_stream=read_stream,
|
| 27 |
write_stream=write_stream,
|
| 28 |
+
**self._session_kwargs(),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
) as session:
|
| 30 |
async with self._set_session(transport, session):
|
| 31 |
yield self
|