flock-core 0.4.512__py3-none-any.whl → 0.4.514__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/core/execution/opik_executor.py +103 -0
- flock/core/flock_agent.py +1 -1
- flock/core/flock_factory.py +85 -2
- flock/core/interpreter/python_interpreter.py +87 -81
- flock/core/logging/logging.py +8 -0
- flock/core/mcp/flock_mcp_server.py +30 -4
- flock/core/mcp/flock_mcp_tool_base.py +1 -1
- flock/core/mcp/mcp_client.py +57 -28
- flock/core/mcp/mcp_client_manager.py +1 -1
- flock/core/mcp/mcp_config.py +245 -9
- flock/core/mcp/types/callbacks.py +3 -5
- flock/core/mcp/types/factories.py +12 -14
- flock/core/mcp/types/handlers.py +9 -12
- flock/core/mcp/types/types.py +205 -2
- flock/core/mixin/dspy_integration.py +1 -1
- flock/core/util/input_resolver.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +21 -14
- flock/mcp/servers/streamable_http/__init__.py +0 -0
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +169 -0
- flock/mcp/servers/websockets/flock_websocket_server.py +3 -3
- flock/tools/code_tools.py +111 -0
- flock/webapp/app/api/execution.py +1 -1
- flock/webapp/app/main.py +1 -1
- {flock_core-0.4.512.dist-info → flock_core-0.4.514.dist-info}/METADATA +4 -1
- {flock_core-0.4.512.dist-info → flock_core-0.4.514.dist-info}/RECORD +29 -26
- /flock/core/util/{spliter.py → splitter.py} +0 -0
- {flock_core-0.4.512.dist-info → flock_core-0.4.514.dist-info}/WHEEL +0 -0
- {flock_core-0.4.512.dist-info → flock_core-0.4.514.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.512.dist-info → flock_core-0.4.514.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Factories for default MCP Callbacks."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
from mcp.shared.context import RequestContext
|
|
6
6
|
from mcp.types import (
|
|
@@ -8,6 +8,12 @@ from mcp.types import (
|
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
from flock.core.logging.logging import FlockLogger, get_logger
|
|
11
|
+
from flock.core.mcp.types.callbacks import (
|
|
12
|
+
default_list_roots_callback,
|
|
13
|
+
default_logging_callback,
|
|
14
|
+
default_message_handler,
|
|
15
|
+
default_sampling_callback,
|
|
16
|
+
)
|
|
11
17
|
from flock.core.mcp.types.types import (
|
|
12
18
|
FlockListRootsMCPCallback,
|
|
13
19
|
FlockLoggingMCPCallback,
|
|
@@ -17,18 +23,10 @@ from flock.core.mcp.types.types import (
|
|
|
17
23
|
ServerNotification,
|
|
18
24
|
)
|
|
19
25
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
default_message_handler,
|
|
25
|
-
default_sampling_callback,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
default_logging_callback_logger = get_logger("core.mcp.callback.logging")
|
|
29
|
-
default_sampling_callback_logger = get_logger("core.mcp.callback.sampling")
|
|
30
|
-
default_list_roots_callback_logger = get_logger("core.mcp.callback.sampling")
|
|
31
|
-
default_message_handler_logger = get_logger("core.mcp.callback.message")
|
|
26
|
+
default_logging_callback_logger = get_logger("mcp.callback.logging")
|
|
27
|
+
default_sampling_callback_logger = get_logger("mcp.callback.sampling")
|
|
28
|
+
default_list_roots_callback_logger = get_logger("mcp.callback.roots")
|
|
29
|
+
default_message_handler_logger = get_logger("mcp.callback.message")
|
|
32
30
|
|
|
33
31
|
|
|
34
32
|
def default_flock_mcp_logging_callback_factory(
|
|
@@ -88,7 +86,7 @@ def default_flock_mcp_message_handler_callback_factory(
|
|
|
88
86
|
) -> None:
|
|
89
87
|
await default_message_handler(
|
|
90
88
|
req=n,
|
|
91
|
-
|
|
89
|
+
logger=logger_to_use,
|
|
92
90
|
associated_client=associated_client,
|
|
93
91
|
)
|
|
94
92
|
|
flock/core/mcp/types/handlers.py
CHANGED
|
@@ -8,26 +8,23 @@ from mcp.shared.context import RequestContext
|
|
|
8
8
|
from mcp.shared.session import RequestResponder
|
|
9
9
|
from mcp.types import (
|
|
10
10
|
INTERNAL_ERROR,
|
|
11
|
+
CancelledNotification,
|
|
11
12
|
ClientResult,
|
|
12
13
|
ErrorData,
|
|
13
14
|
ListRootsRequest,
|
|
14
|
-
ServerNotification as _MCPServerNotification,
|
|
15
|
-
ServerRequest,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
from flock.core.logging.logging import FlockLogger
|
|
19
|
-
from flock.core.mcp.mcp_client import Any
|
|
20
|
-
from flock.core.mcp.types.types import (
|
|
21
|
-
CancelledNotification,
|
|
22
|
-
FlockLoggingMessageNotificationParams,
|
|
23
15
|
LoggingMessageNotification,
|
|
16
|
+
LoggingMessageNotificationParams,
|
|
24
17
|
ProgressNotification,
|
|
25
18
|
ResourceListChangedNotification,
|
|
26
19
|
ResourceUpdatedNotification,
|
|
27
20
|
ServerNotification,
|
|
21
|
+
ServerRequest,
|
|
28
22
|
ToolListChangedNotification,
|
|
29
23
|
)
|
|
30
24
|
|
|
25
|
+
from flock.core.logging.logging import FlockLogger
|
|
26
|
+
from flock.core.mcp.mcp_client import Any
|
|
27
|
+
|
|
31
28
|
|
|
32
29
|
async def handle_incoming_exception(
|
|
33
30
|
e: Exception,
|
|
@@ -129,11 +126,11 @@ async def handle_tool_list_changed_notification(
|
|
|
129
126
|
await associated_client.invalidate_tool_cache()
|
|
130
127
|
|
|
131
128
|
|
|
132
|
-
_SERVER_NOTIFICATION_MAP: dict[type[
|
|
129
|
+
_SERVER_NOTIFICATION_MAP: dict[type[ServerNotification], Callable] = {
|
|
133
130
|
ResourceListChangedNotification: handle_resource_list_changed_notification,
|
|
134
131
|
ResourceUpdatedNotification: handle_resource_update_notification,
|
|
135
132
|
LoggingMessageNotification: lambda n, log, client: handle_logging_message(
|
|
136
|
-
params=n,
|
|
133
|
+
params=n.params,
|
|
137
134
|
logger=log,
|
|
138
135
|
server_name=client.config.name,
|
|
139
136
|
),
|
|
@@ -154,7 +151,7 @@ async def handle_incoming_server_notification(
|
|
|
154
151
|
|
|
155
152
|
|
|
156
153
|
async def handle_logging_message(
|
|
157
|
-
params:
|
|
154
|
+
params: LoggingMessageNotificationParams,
|
|
158
155
|
logger: FlockLogger,
|
|
159
156
|
server_name: str,
|
|
160
157
|
) -> None:
|
flock/core/mcp/types/types.py
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"""Types for Flock's MCP functionality."""
|
|
2
2
|
|
|
3
|
+
import importlib
|
|
4
|
+
import inspect
|
|
5
|
+
import os
|
|
3
6
|
from collections.abc import Awaitable, Callable
|
|
4
7
|
from contextlib import AbstractAsyncContextManager
|
|
5
|
-
from typing import Any
|
|
8
|
+
from typing import Any, Literal
|
|
6
9
|
|
|
10
|
+
import httpx
|
|
7
11
|
from anyio.streams.memory import (
|
|
8
12
|
MemoryObjectReceiveStream,
|
|
9
13
|
MemoryObjectSendStream,
|
|
@@ -36,6 +40,7 @@ from mcp.types import (
|
|
|
36
40
|
from pydantic import AnyUrl, BaseModel, ConfigDict, Field
|
|
37
41
|
|
|
38
42
|
from flock.core.mcp.util.helpers import get_default_env
|
|
43
|
+
from flock.core.serialization.serializable import Serializable
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
class ServerNotification(_MCPServerNotification):
|
|
@@ -80,17 +85,40 @@ class MCPRoot(_MCPRoot):
|
|
|
80
85
|
"""Wrapper for mcp.types.Root."""
|
|
81
86
|
|
|
82
87
|
|
|
83
|
-
class ServerParameters(BaseModel):
|
|
88
|
+
class ServerParameters(BaseModel, Serializable):
|
|
84
89
|
"""Base Type for server parameters."""
|
|
85
90
|
|
|
86
91
|
model_config = ConfigDict(
|
|
87
92
|
arbitrary_types_allowed=True,
|
|
88
93
|
)
|
|
89
94
|
|
|
95
|
+
transport_type: Literal["stdio", "websockets", "sse", "streamable_http"] = Field(
|
|
96
|
+
...,
|
|
97
|
+
description="which type of transport these connection params are used for."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def to_dict(self, path_type: str = "relative"):
|
|
101
|
+
"""Serialize."""
|
|
102
|
+
return self.model_dump(
|
|
103
|
+
exclude_defaults=False,
|
|
104
|
+
exclude_none=True,
|
|
105
|
+
mode="json"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def from_dict(cls, data: dict[str, Any]):
|
|
110
|
+
"""Deserialize."""
|
|
111
|
+
return cls(**data)
|
|
112
|
+
|
|
90
113
|
|
|
91
114
|
class StdioServerParameters(_MCPStdioServerParameters, ServerParameters):
|
|
92
115
|
"""Base Type for Stdio Server parameters."""
|
|
93
116
|
|
|
117
|
+
transport_type: Literal["stdio"] = Field(
|
|
118
|
+
default="stdio",
|
|
119
|
+
description="Use stdio params."
|
|
120
|
+
)
|
|
121
|
+
|
|
94
122
|
env: dict[str, str] | None = Field(
|
|
95
123
|
default_factory=get_default_env,
|
|
96
124
|
description="Environment for the MCP Server.",
|
|
@@ -100,12 +128,121 @@ class StdioServerParameters(_MCPStdioServerParameters, ServerParameters):
|
|
|
100
128
|
class WebsocketServerParameters(ServerParameters):
|
|
101
129
|
"""Base Type for Websocket Server params."""
|
|
102
130
|
|
|
131
|
+
transport_type: Literal["websockets"] = Field(
|
|
132
|
+
default="websockets",
|
|
133
|
+
description="Use websocket params."
|
|
134
|
+
)
|
|
135
|
+
|
|
103
136
|
url: str | AnyUrl = Field(..., description="Url the server listens at.")
|
|
104
137
|
|
|
138
|
+
class StreamableHttpServerParameters(ServerParameters):
|
|
139
|
+
"""Base Type for StreamableHttp params."""
|
|
140
|
+
|
|
141
|
+
transport_type: Literal["streamable_http"] = Field(
|
|
142
|
+
default="streamable_http",
|
|
143
|
+
description="Use streamable http params."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
url: str | AnyUrl = Field(
|
|
147
|
+
...,
|
|
148
|
+
description="The url the server listens at."
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
headers: dict[str, Any] | None = Field(
|
|
152
|
+
default=None,
|
|
153
|
+
description="Additional headers to pass to the client."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
timeout: float | int = Field(
|
|
157
|
+
default=5,
|
|
158
|
+
description="Http Timeout",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
sse_read_timeout: float | int = Field(
|
|
162
|
+
default=60*5,
|
|
163
|
+
description="How long the client will wait before disconnecting from the server."
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
terminate_on_close: bool = Field(
|
|
167
|
+
default=True,
|
|
168
|
+
description="Terminate connection on close"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
auth: httpx.Auth | None = Field(
|
|
172
|
+
default=None,
|
|
173
|
+
description="Httpx Auth Scheme"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def from_dict(cls, data: dict[str, Any]):
|
|
178
|
+
"""Deserialize the object from a dict."""
|
|
179
|
+
# find and import the concrete implementation for
|
|
180
|
+
# the auth object
|
|
181
|
+
auth_obj: httpx.Auth | None = None
|
|
182
|
+
auth_impl = data.pop("auth", None)
|
|
183
|
+
if auth_impl:
|
|
184
|
+
# find the concrete implementation
|
|
185
|
+
impl = auth_impl.pop("implementation", None)
|
|
186
|
+
params = auth_impl.pop("params", None)
|
|
187
|
+
if impl:
|
|
188
|
+
mod = importlib.import_module(impl["module_path"])
|
|
189
|
+
real_cls = getattr(mod, impl["classname"])
|
|
190
|
+
if params:
|
|
191
|
+
auth_obj = real_cls(**{k: v for k, v in params.items()})
|
|
192
|
+
else:
|
|
193
|
+
# assume that the implementation handles it.
|
|
194
|
+
auth_obj = real_cls()
|
|
195
|
+
else:
|
|
196
|
+
raise ValueError("No concrete implementation for auth provided.")
|
|
197
|
+
|
|
198
|
+
data["auth"] = auth_obj
|
|
199
|
+
return cls(**{k: v for k, v in data.items()})
|
|
200
|
+
|
|
201
|
+
def to_dict(self, path_type = "relative"):
|
|
202
|
+
"""Serialize the object."""
|
|
203
|
+
exclude = ["auth"]
|
|
204
|
+
|
|
205
|
+
data = self.model_dump(
|
|
206
|
+
exclude=exclude,
|
|
207
|
+
exclude_defaults=False,
|
|
208
|
+
exclude_none=True,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# inject implentation info for auth
|
|
212
|
+
if self.auth is not None:
|
|
213
|
+
file_path = inspect.getsourcefile(type(self.auth))
|
|
214
|
+
if path_type == "relative":
|
|
215
|
+
file_path = os.path.relpath(file_path)
|
|
216
|
+
try:
|
|
217
|
+
# params should be primitive types, keeping with the
|
|
218
|
+
# declarative approach of flock.
|
|
219
|
+
params = {
|
|
220
|
+
k: getattr(self.auth, k)
|
|
221
|
+
for k in getattr(self.auth, "__dict__", {})
|
|
222
|
+
if not k.startswith("_")
|
|
223
|
+
}
|
|
224
|
+
except Exception:
|
|
225
|
+
params = None
|
|
226
|
+
|
|
227
|
+
data["auth"] = {
|
|
228
|
+
"implementation": {
|
|
229
|
+
"class_name": type(self.auth).__name__,
|
|
230
|
+
"module_path": type(self.auth).__module__,
|
|
231
|
+
"file_path": file_path,
|
|
232
|
+
},
|
|
233
|
+
"params": params,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return data
|
|
105
237
|
|
|
106
238
|
class SseServerParameters(ServerParameters):
|
|
107
239
|
"""Base Type for SSE Server params."""
|
|
108
240
|
|
|
241
|
+
transport_type: Literal["sse"] = Field(
|
|
242
|
+
default="sse",
|
|
243
|
+
description="Use sse server params."
|
|
244
|
+
)
|
|
245
|
+
|
|
109
246
|
url: str | AnyUrl = Field(..., description="The url the server listens at.")
|
|
110
247
|
|
|
111
248
|
headers: dict[str, Any] | None = Field(
|
|
@@ -119,6 +256,72 @@ class SseServerParameters(ServerParameters):
|
|
|
119
256
|
description="How long the client will wait before disconnecting from the server.",
|
|
120
257
|
)
|
|
121
258
|
|
|
259
|
+
auth: httpx.Auth | None = Field(
|
|
260
|
+
default=None,
|
|
261
|
+
description="Httpx Auth Scheme."
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
@classmethod
|
|
265
|
+
def from_dict(cls, data: dict[str, Any]):
|
|
266
|
+
"""Deserialize the object from a dict."""
|
|
267
|
+
# find and import the concrete implementation for
|
|
268
|
+
# the auth object.
|
|
269
|
+
auth_obj: httpx.Auth | None = None
|
|
270
|
+
auth_impl = data.pop("auth", None) # get the specs for the auth class
|
|
271
|
+
if auth_impl:
|
|
272
|
+
# find the concrete implementation
|
|
273
|
+
impl = auth_impl.pop("implementation", None)
|
|
274
|
+
params = auth_impl.pop("params", None)
|
|
275
|
+
if impl:
|
|
276
|
+
mod = importlib.import_module(impl["module_path"])
|
|
277
|
+
real_cls = getattr(mod, impl["class_name"])
|
|
278
|
+
if params:
|
|
279
|
+
auth_obj = real_cls(**{k: v for k, v in params.items()})
|
|
280
|
+
else:
|
|
281
|
+
# assume that implementation handles it
|
|
282
|
+
auth_obj = real_cls()
|
|
283
|
+
else:
|
|
284
|
+
raise ValueError("No concrete implementation for auth provided.")
|
|
285
|
+
|
|
286
|
+
data["auth"] = auth_obj
|
|
287
|
+
return cls(**{k: v for k, v in data.items()})
|
|
288
|
+
|
|
289
|
+
def to_dict(self, path_type = "relative"):
|
|
290
|
+
"""Serialize the object."""
|
|
291
|
+
exclude = ["auth"]
|
|
292
|
+
|
|
293
|
+
data = self.model_dump(
|
|
294
|
+
exclude=exclude,
|
|
295
|
+
exclude_defaults=False,
|
|
296
|
+
exclude_none=True,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# inject implentation info for auth
|
|
300
|
+
if self.auth is not None:
|
|
301
|
+
file_path = inspect.getsourcefile(type(self.auth))
|
|
302
|
+
if path_type == "relative":
|
|
303
|
+
file_path = os.path.relpath(file_path)
|
|
304
|
+
try:
|
|
305
|
+
# params should be primitive types, keeping with the
|
|
306
|
+
# declarative approach of flock.
|
|
307
|
+
params = {
|
|
308
|
+
k: getattr(self.auth, k)
|
|
309
|
+
for k in getattr(self.auth, "__dict__", {})
|
|
310
|
+
if not k.startswith("_")
|
|
311
|
+
}
|
|
312
|
+
except Exception:
|
|
313
|
+
params = None
|
|
314
|
+
|
|
315
|
+
data["auth"] = {
|
|
316
|
+
"implementation": {
|
|
317
|
+
"class_name": type(self.auth).__name__,
|
|
318
|
+
"module_path": type(self.auth).__module__,
|
|
319
|
+
"file_path": file_path,
|
|
320
|
+
},
|
|
321
|
+
"params": params,
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return data
|
|
122
325
|
|
|
123
326
|
MCPCLientInitFunction = Callable[
|
|
124
327
|
...,
|
|
@@ -9,7 +9,7 @@ from typing import Any, Literal
|
|
|
9
9
|
from dspy import Tool
|
|
10
10
|
|
|
11
11
|
from flock.core.logging.logging import get_logger
|
|
12
|
-
from flock.core.util.
|
|
12
|
+
from flock.core.util.splitter import split_top_level
|
|
13
13
|
|
|
14
14
|
# Import split_top_level (assuming it's moved or copied appropriately)
|
|
15
15
|
# Option 1: If moved to a shared util
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Utility functions for resolving input keys to their corresponding values."""
|
|
2
2
|
|
|
3
3
|
from flock.core.context.context import FlockContext
|
|
4
|
-
from flock.core.util.
|
|
4
|
+
from flock.core.util.splitter import split_top_level
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def get_callable_members(obj):
|
|
@@ -4,12 +4,13 @@ import copy
|
|
|
4
4
|
from contextlib import AbstractAsyncContextManager
|
|
5
5
|
from typing import Any, Literal
|
|
6
6
|
|
|
7
|
+
import httpx
|
|
7
8
|
from anyio.streams.memory import (
|
|
8
9
|
MemoryObjectReceiveStream,
|
|
9
10
|
MemoryObjectSendStream,
|
|
10
11
|
)
|
|
11
12
|
from mcp.client.sse import sse_client
|
|
12
|
-
from mcp.
|
|
13
|
+
from mcp.shared.message import SessionMessage
|
|
13
14
|
from opentelemetry import trace
|
|
14
15
|
from pydantic import Field
|
|
15
16
|
|
|
@@ -63,45 +64,51 @@ class FlockSSEClient(FlockMCPClientBase):
|
|
|
63
64
|
additional_params: dict[str, Any] | None = None,
|
|
64
65
|
) -> AbstractAsyncContextManager[
|
|
65
66
|
tuple[
|
|
66
|
-
MemoryObjectReceiveStream[
|
|
67
|
-
MemoryObjectSendStream[
|
|
67
|
+
MemoryObjectReceiveStream[SessionMessage | Exception],
|
|
68
|
+
MemoryObjectSendStream[SessionMessage],
|
|
68
69
|
]
|
|
69
70
|
]:
|
|
70
71
|
"""Return an async context manager whose __aenter__ method yields (read_stream, send_stream)."""
|
|
71
72
|
# avoid modifying the config of the client as a side-effect.
|
|
72
73
|
param_copy = copy.deepcopy(params)
|
|
73
74
|
|
|
74
|
-
if
|
|
75
|
+
if additional_params:
|
|
75
76
|
override_headers = bool(
|
|
76
|
-
|
|
77
|
+
additional_params.get("override_headers", False)
|
|
77
78
|
)
|
|
78
|
-
if "headers" in
|
|
79
|
+
if "headers" in additional_params:
|
|
79
80
|
if override_headers:
|
|
80
|
-
param_copy.headers =
|
|
81
|
+
param_copy.headers = additional_params.get(
|
|
81
82
|
"headers", params.headers
|
|
82
83
|
)
|
|
83
84
|
else:
|
|
84
85
|
param_copy.headers.update(
|
|
85
|
-
|
|
86
|
+
additional_params.get("headers", {})
|
|
86
87
|
)
|
|
87
|
-
if "read_timeout_seconds" in
|
|
88
|
-
param_copy.timeout =
|
|
88
|
+
if "read_timeout_seconds" in additional_params:
|
|
89
|
+
param_copy.timeout = additional_params.get(
|
|
89
90
|
"read_timeout_seconds", params.timeout
|
|
90
91
|
)
|
|
91
92
|
|
|
92
|
-
if "sse_read_timeout" in
|
|
93
|
-
param_copy.sse_read_timeout =
|
|
93
|
+
if "sse_read_timeout" in additional_params:
|
|
94
|
+
param_copy.sse_read_timeout = additional_params.get(
|
|
94
95
|
"sse_read_timeout",
|
|
95
96
|
params.sse_read_timeout,
|
|
96
97
|
)
|
|
97
|
-
if "url" in
|
|
98
|
-
param_copy.url =
|
|
98
|
+
if "url" in additional_params:
|
|
99
|
+
param_copy.url = additional_params.get(
|
|
99
100
|
"url",
|
|
100
101
|
params.url,
|
|
101
102
|
)
|
|
102
103
|
|
|
104
|
+
if "auth" in additional_params and isinstance(
|
|
105
|
+
additional_params.get("auth"), httpx.Auth
|
|
106
|
+
):
|
|
107
|
+
param_copy.auth = additional_params.get("auth", param_copy.auth)
|
|
108
|
+
|
|
103
109
|
return sse_client(
|
|
104
110
|
url=param_copy.url,
|
|
111
|
+
auth=param_copy.auth,
|
|
105
112
|
headers=param_copy.headers,
|
|
106
113
|
timeout=float(param_copy.timeout),
|
|
107
114
|
sse_read_timeout=float(param_copy.sse_read_timeout),
|
|
File without changes
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""This module provides the Flock Streamable-Http functionality."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from contextlib import AbstractAsyncContextManager
|
|
6
|
+
from datetime import timedelta
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from anyio.streams.memory import (
|
|
11
|
+
MemoryObjectReceiveStream,
|
|
12
|
+
MemoryObjectSendStream,
|
|
13
|
+
)
|
|
14
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
15
|
+
from mcp.shared.message import SessionMessage
|
|
16
|
+
from opentelemetry import trace
|
|
17
|
+
from pydantic import Field
|
|
18
|
+
|
|
19
|
+
from flock.core.logging.logging import get_logger
|
|
20
|
+
from flock.core.mcp.flock_mcp_server import FlockMCPServerBase
|
|
21
|
+
from flock.core.mcp.mcp_client import FlockMCPClientBase
|
|
22
|
+
from flock.core.mcp.mcp_client_manager import FlockMCPClientManagerBase
|
|
23
|
+
from flock.core.mcp.mcp_config import (
|
|
24
|
+
FlockMCPConfigurationBase,
|
|
25
|
+
FlockMCPConnectionConfigurationBase,
|
|
26
|
+
)
|
|
27
|
+
from flock.core.mcp.types.types import (
|
|
28
|
+
StreamableHttpServerParameters,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
logger = get_logger("mcp.streamable_http.server")
|
|
32
|
+
tracer = trace.get_tracer(__name__)
|
|
33
|
+
|
|
34
|
+
GetSessionIdCallback = Callable[[], str | None]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FlockStreamableHttpConnectionConfig(FlockMCPConnectionConfigurationBase):
|
|
38
|
+
"""Concrete ConnectionConfig for a StreamableHttpClient."""
|
|
39
|
+
|
|
40
|
+
# Only thing we need to override here is the concrete transport_type
|
|
41
|
+
# and connection parameter fields.
|
|
42
|
+
transport_type: Literal["streamable_http"] = Field(
|
|
43
|
+
default="streamable_http",
|
|
44
|
+
description="Use the streamable_http Transport type.",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
connection_parameters: StreamableHttpServerParameters = Field(
|
|
48
|
+
..., description="Streamable HTTP Server Connection Parameters."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FlockStreamableHttpConfig(FlockMCPConfigurationBase):
|
|
53
|
+
"""Configuration for Streamable HTTP Clients."""
|
|
54
|
+
|
|
55
|
+
# The only thing we need to override here is the
|
|
56
|
+
# concrete connection config.
|
|
57
|
+
# The rest is generic enough to handle everything else.
|
|
58
|
+
connection_config: FlockStreamableHttpConnectionConfig = Field(
|
|
59
|
+
..., description="Concrete StreamableHttp Connection Configuration."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class FlockStreamableHttpClient(FlockMCPClientBase):
|
|
64
|
+
"""Client for StreamableHttpServers."""
|
|
65
|
+
|
|
66
|
+
config: FlockStreamableHttpConfig = Field(
|
|
67
|
+
..., description="Client configuration."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
async def create_transport(
|
|
71
|
+
self,
|
|
72
|
+
params: StreamableHttpServerParameters,
|
|
73
|
+
additional_params: dict[str, Any] | None = None,
|
|
74
|
+
) -> AbstractAsyncContextManager[
|
|
75
|
+
tuple[
|
|
76
|
+
MemoryObjectReceiveStream[SessionMessage | Exception],
|
|
77
|
+
MemoryObjectSendStream[SessionMessage],
|
|
78
|
+
GetSessionIdCallback,
|
|
79
|
+
],
|
|
80
|
+
None,
|
|
81
|
+
]:
|
|
82
|
+
"""Return an async context manager whose __aenter__ method yields (read_stream, send_stream)."""
|
|
83
|
+
param_copy = copy.deepcopy(params)
|
|
84
|
+
|
|
85
|
+
if additional_params:
|
|
86
|
+
override_headers = bool(
|
|
87
|
+
additional_params.get("override_headers", False)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if "headers" in additional_params:
|
|
91
|
+
if override_headers:
|
|
92
|
+
param_copy.headers = additional_params.get(
|
|
93
|
+
"headers", params.headers
|
|
94
|
+
)
|
|
95
|
+
else:
|
|
96
|
+
param_copy.headers.update(
|
|
97
|
+
additional_params.get("headers", {})
|
|
98
|
+
)
|
|
99
|
+
if "auth" in additional_params and isinstance(
|
|
100
|
+
additional_params.get("auth"), httpx.Auth
|
|
101
|
+
):
|
|
102
|
+
param_copy.auth = additional_params.get("auth", param_copy.auth)
|
|
103
|
+
|
|
104
|
+
if "read_timeout_seconds" in additional_params:
|
|
105
|
+
param_copy.timeout = additional_params.get(
|
|
106
|
+
"read_timeout_seconds", params.timeout
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if "sse_read_timeout" in additional_params:
|
|
110
|
+
param_copy.sse_read_timeout = additional_params.get(
|
|
111
|
+
"sse_read_timeout",
|
|
112
|
+
params.sse_read_timeout,
|
|
113
|
+
)
|
|
114
|
+
if "url" in additional_params:
|
|
115
|
+
param_copy.url = additional_params.get(
|
|
116
|
+
"url",
|
|
117
|
+
params.url,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if "terminate_on_close" in additional_params:
|
|
121
|
+
param_copy.terminate_on_close = bool(
|
|
122
|
+
additional_params.get("terminate_on_close", True)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
timeout_http = timedelta(seconds=param_copy.timeout)
|
|
126
|
+
sse_timeout = timedelta(seconds=param_copy.sse_read_timeout)
|
|
127
|
+
|
|
128
|
+
return streamablehttp_client(
|
|
129
|
+
url=param_copy.url,
|
|
130
|
+
headers=param_copy.headers,
|
|
131
|
+
timeout=timeout_http,
|
|
132
|
+
sse_read_timeout=sse_timeout,
|
|
133
|
+
terminate_on_close=param_copy.terminate_on_close,
|
|
134
|
+
auth=param_copy.auth,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class FlockStreamableHttpClientManager(FlockMCPClientManagerBase):
|
|
139
|
+
"""Manager for handling StreamableHttpClients."""
|
|
140
|
+
|
|
141
|
+
client_config: FlockStreamableHttpConfig = Field(
|
|
142
|
+
..., description="Configuration for clients."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
async def make_client(
|
|
146
|
+
self, additional_params: dict[str, Any] | None = None
|
|
147
|
+
) -> FlockStreamableHttpClient:
|
|
148
|
+
"""Create a new client instance."""
|
|
149
|
+
new_client = FlockStreamableHttpClient(
|
|
150
|
+
config=self.client_config,
|
|
151
|
+
additional_params=additional_params,
|
|
152
|
+
)
|
|
153
|
+
return new_client
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class FlockStreamableHttpServer(FlockMCPServerBase):
|
|
157
|
+
"""Class which represents a MCP Server using the streamable Http Transport type."""
|
|
158
|
+
|
|
159
|
+
config: FlockStreamableHttpConfig = Field(
|
|
160
|
+
..., description="Config for the server."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
async def initialize(self) -> FlockStreamableHttpClientManager:
|
|
164
|
+
"""Called when initializing the server."""
|
|
165
|
+
client_manager = FlockStreamableHttpClientManager(
|
|
166
|
+
client_config=self.config
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return client_manager
|
|
@@ -9,7 +9,7 @@ from anyio.streams.memory import (
|
|
|
9
9
|
MemoryObjectSendStream,
|
|
10
10
|
)
|
|
11
11
|
from mcp.client.websocket import websocket_client
|
|
12
|
-
from mcp.
|
|
12
|
+
from mcp.shared.message import SessionMessage
|
|
13
13
|
from opentelemetry import trace
|
|
14
14
|
from pydantic import Field
|
|
15
15
|
|
|
@@ -69,8 +69,8 @@ class FlockWSClient(FlockMCPClientBase):
|
|
|
69
69
|
additional_params: dict[str, Any] | None = None,
|
|
70
70
|
) -> AbstractAsyncContextManager[
|
|
71
71
|
tuple[
|
|
72
|
-
MemoryObjectReceiveStream[
|
|
73
|
-
MemoryObjectSendStream[
|
|
72
|
+
MemoryObjectReceiveStream[SessionMessage | Exception],
|
|
73
|
+
MemoryObjectSendStream[SessionMessage],
|
|
74
74
|
]
|
|
75
75
|
]:
|
|
76
76
|
"""Return an async context manager whose __aenter__ method yields a read_stream and a send_stream."""
|