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
|
@@ -26,7 +26,7 @@ from flock.core.serialization.serialization_utils import (
|
|
|
26
26
|
serialize_item,
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
-
logger = get_logger("
|
|
29
|
+
logger = get_logger("mcp.server")
|
|
30
30
|
tracer = trace.get_tracer(__name__)
|
|
31
31
|
T = TypeVar("T", bound="FlockMCPServerBase")
|
|
32
32
|
|
|
@@ -206,7 +206,6 @@ class FlockMCPServerBase(BaseModel, Serializable, ABC):
|
|
|
206
206
|
async with self.condition:
|
|
207
207
|
try:
|
|
208
208
|
await self.pre_mcp_call()
|
|
209
|
-
# TODO: inject additional params here.
|
|
210
209
|
additional_params: dict[str, Any] = {}
|
|
211
210
|
additional_params = await self.before_connect(
|
|
212
211
|
additional_params=additional_params
|
|
@@ -314,7 +313,7 @@ class FlockMCPServerBase(BaseModel, Serializable, ABC):
|
|
|
314
313
|
async def post_terminate(self) -> None:
|
|
315
314
|
"""Run post-terminate hooks on modules."""
|
|
316
315
|
logger.debug(
|
|
317
|
-
f"Running
|
|
316
|
+
f"Running post_terminate hooks for modules in server: '{self.config.name}'"
|
|
318
317
|
)
|
|
319
318
|
with tracer.start_as_current_span("server.post_terminate") as span:
|
|
320
319
|
span.set_attribute("server.name", self.config.name)
|
|
@@ -437,7 +436,7 @@ class FlockMCPServerBase(BaseModel, Serializable, ABC):
|
|
|
437
436
|
|
|
438
437
|
FlockRegistry = get_registry()
|
|
439
438
|
|
|
440
|
-
exclude = ["modules"]
|
|
439
|
+
exclude = ["modules", "config"]
|
|
441
440
|
|
|
442
441
|
logger.debug(f"Serializing server '{self.config.name}' to dict.")
|
|
443
442
|
# Use Pydantic's dump, exclued manually handled fields.
|
|
@@ -447,6 +446,11 @@ class FlockMCPServerBase(BaseModel, Serializable, ABC):
|
|
|
447
446
|
exclude_none=True, # Exclude None values for cleaner output
|
|
448
447
|
)
|
|
449
448
|
|
|
449
|
+
# --- Let the config handle its own serialization ---
|
|
450
|
+
config_data = self.config.to_dict(path_type=path_type)
|
|
451
|
+
data["config"] = config_data
|
|
452
|
+
|
|
453
|
+
|
|
450
454
|
builtin_by_transport = {}
|
|
451
455
|
|
|
452
456
|
try:
|
|
@@ -454,12 +458,16 @@ class FlockMCPServerBase(BaseModel, Serializable, ABC):
|
|
|
454
458
|
from flock.mcp.servers.stdio.flock_stdio_server import (
|
|
455
459
|
FlockMCPStdioServer,
|
|
456
460
|
)
|
|
461
|
+
from flock.mcp.servers.streamable_http.flock_streamable_http_server import (
|
|
462
|
+
FlockStreamableHttpServer,
|
|
463
|
+
)
|
|
457
464
|
from flock.mcp.servers.websockets.flock_websocket_server import (
|
|
458
465
|
FlockWSServer,
|
|
459
466
|
)
|
|
460
467
|
|
|
461
468
|
builtin_by_transport = {
|
|
462
469
|
"stdio": FlockMCPStdioServer,
|
|
470
|
+
"streamable_http": FlockStreamableHttpServer,
|
|
463
471
|
"sse": FlockSSEServer,
|
|
464
472
|
"websockets": FlockWSServer,
|
|
465
473
|
}
|
|
@@ -570,6 +578,9 @@ class FlockMCPServerBase(BaseModel, Serializable, ABC):
|
|
|
570
578
|
from flock.mcp.servers.stdio.flock_stdio_server import (
|
|
571
579
|
FlockMCPStdioServer,
|
|
572
580
|
)
|
|
581
|
+
from flock.mcp.servers.streamable_http.flock_streamable_http_server import (
|
|
582
|
+
FlockStreamableHttpServer,
|
|
583
|
+
)
|
|
573
584
|
from flock.mcp.servers.websockets.flock_websocket_server import (
|
|
574
585
|
FlockWSServer,
|
|
575
586
|
)
|
|
@@ -577,6 +588,7 @@ class FlockMCPServerBase(BaseModel, Serializable, ABC):
|
|
|
577
588
|
builtin_by_transport = {
|
|
578
589
|
"stdio": FlockMCPStdioServer,
|
|
579
590
|
"sse": FlockSSEServer,
|
|
591
|
+
"streamable_http": FlockStreamableHttpServer,
|
|
580
592
|
"websockets": FlockWSServer,
|
|
581
593
|
}
|
|
582
594
|
except ImportError:
|
|
@@ -592,6 +604,20 @@ class FlockMCPServerBase(BaseModel, Serializable, ABC):
|
|
|
592
604
|
transport = data["config"]["connection_config"]["transport_type"]
|
|
593
605
|
real_cls = builtin_by_transport.get(transport, cls)
|
|
594
606
|
|
|
607
|
+
# deserialize the config:
|
|
608
|
+
config_data = data.pop("config", None)
|
|
609
|
+
if config_data:
|
|
610
|
+
# Forcing a square into a round hole
|
|
611
|
+
# pretty ugly, but gets the job done.
|
|
612
|
+
try:
|
|
613
|
+
config_field = real_cls.model_fields["config"]
|
|
614
|
+
config_cls = config_field.annotation
|
|
615
|
+
except (AttributeError, KeyError):
|
|
616
|
+
# fallback if Pydantic v1 or missing
|
|
617
|
+
config_cls = FlockMCPConfigurationBase
|
|
618
|
+
config_object = config_cls.from_dict(config_data)
|
|
619
|
+
data["config"] = config_object
|
|
620
|
+
|
|
595
621
|
# now construct
|
|
596
622
|
server = real_cls(**{k: v for k, v in data.items() if k != "modules"})
|
|
597
623
|
|
|
@@ -10,7 +10,7 @@ from pydantic import BaseModel, Field
|
|
|
10
10
|
|
|
11
11
|
from flock.core.logging.logging import get_logger
|
|
12
12
|
|
|
13
|
-
logger = get_logger("
|
|
13
|
+
logger = get_logger("mcp.tool")
|
|
14
14
|
tracer = trace.get_tracer(__name__)
|
|
15
15
|
|
|
16
16
|
T = TypeVar("T", bound="FlockMCPToolBase")
|
flock/core/mcp/mcp_client.py
CHANGED
|
@@ -4,9 +4,10 @@ import asyncio
|
|
|
4
4
|
import random
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from asyncio import Lock
|
|
7
|
+
from collections.abc import Callable
|
|
7
8
|
from contextlib import (
|
|
8
|
-
AbstractAsyncContextManager,
|
|
9
9
|
AsyncExitStack,
|
|
10
|
+
asynccontextmanager,
|
|
10
11
|
)
|
|
11
12
|
from datetime import timedelta
|
|
12
13
|
from typing import (
|
|
@@ -27,7 +28,7 @@ from mcp import (
|
|
|
27
28
|
McpError,
|
|
28
29
|
ServerCapabilities,
|
|
29
30
|
)
|
|
30
|
-
from mcp.types import CallToolResult
|
|
31
|
+
from mcp.types import CallToolResult
|
|
31
32
|
from opentelemetry import trace
|
|
32
33
|
from pydantic import (
|
|
33
34
|
BaseModel,
|
|
@@ -54,9 +55,11 @@ from flock.core.mcp.types.types import (
|
|
|
54
55
|
)
|
|
55
56
|
from flock.core.mcp.util.helpers import cache_key_generator
|
|
56
57
|
|
|
57
|
-
logger = get_logger("
|
|
58
|
+
logger = get_logger("mcp.client")
|
|
58
59
|
tracer = trace.get_tracer(__name__)
|
|
59
60
|
|
|
61
|
+
GetSessionIdCallback = Callable[[], str | None]
|
|
62
|
+
|
|
60
63
|
|
|
61
64
|
class FlockMCPClientBase(BaseModel, ABC):
|
|
62
65
|
"""Wrapper for mcp ClientSession.
|
|
@@ -159,11 +162,9 @@ class FlockMCPClientBase(BaseModel, ABC):
|
|
|
159
162
|
max_tries = cfg.connection_config.max_retries or 1
|
|
160
163
|
base_delay = 0.1
|
|
161
164
|
span.set_attribute("client.name", client.config.name)
|
|
165
|
+
span.set_attribute("max_tries", max_tries)
|
|
162
166
|
|
|
163
167
|
for attempt in range(1, max_tries + 2):
|
|
164
|
-
span.set_attribute(
|
|
165
|
-
"max_tries", max_tries
|
|
166
|
-
) # TODO: shift outside of loop
|
|
167
168
|
span.set_attribute("base_delay", base_delay)
|
|
168
169
|
span.set_attribute("attempt", attempt)
|
|
169
170
|
await client._ensure_connected()
|
|
@@ -364,12 +365,7 @@ class FlockMCPClientBase(BaseModel, ABC):
|
|
|
364
365
|
self,
|
|
365
366
|
params: ServerParameters,
|
|
366
367
|
additional_params: dict[str, Any] | None = None,
|
|
367
|
-
) ->
|
|
368
|
-
tuple[
|
|
369
|
-
MemoryObjectReceiveStream[JSONRPCMessage | Exception],
|
|
370
|
-
MemoryObjectSendStream[JSONRPCMessage],
|
|
371
|
-
]
|
|
372
|
-
]:
|
|
368
|
+
) -> Any:
|
|
373
369
|
"""Given your custom ServerParameters, return an async-contextmgr whose __aenter yields (read_stream, write_stream)."""
|
|
374
370
|
...
|
|
375
371
|
|
|
@@ -390,6 +386,7 @@ class FlockMCPClientBase(BaseModel, ABC):
|
|
|
390
386
|
return []
|
|
391
387
|
|
|
392
388
|
async def _get_tools_internal() -> list[FlockMCPToolBase]:
|
|
389
|
+
# TODO: Crash
|
|
393
390
|
response: ListToolsResult = await self.session.list_tools()
|
|
394
391
|
flock_tools = []
|
|
395
392
|
|
|
@@ -520,13 +517,31 @@ class FlockMCPClientBase(BaseModel, ABC):
|
|
|
520
517
|
if self.session_stack:
|
|
521
518
|
# manually __aexit__
|
|
522
519
|
await self.session_stack.aclose()
|
|
523
|
-
self.
|
|
524
|
-
self.client_session = None
|
|
520
|
+
self.client_session = None # remove the reference
|
|
525
521
|
|
|
526
522
|
# --- Private Methods ---
|
|
523
|
+
@asynccontextmanager
|
|
524
|
+
async def _safe_transport_ctx(self, cm: Any):
|
|
525
|
+
"""Enter the real transport ctxmg, yield its value, but on __aexit__ always swallow all errors."""
|
|
526
|
+
val = await cm.__aenter__()
|
|
527
|
+
try:
|
|
528
|
+
yield val
|
|
529
|
+
finally:
|
|
530
|
+
try:
|
|
531
|
+
await cm.__aexit__(None, None, None)
|
|
532
|
+
except Exception as e:
|
|
533
|
+
logger.debug(
|
|
534
|
+
f"Suppressed transport-ctx exit error "
|
|
535
|
+
f"for server '{self.config.name}': {e!r}"
|
|
536
|
+
)
|
|
537
|
+
|
|
527
538
|
async def _create_session(self) -> None:
|
|
528
|
-
"""Create and
|
|
539
|
+
"""Create and hold onto a single ClientSession + ExitStack."""
|
|
529
540
|
logger.debug(f"Creating Client Session for server '{self.config.name}'")
|
|
541
|
+
if self.session_stack:
|
|
542
|
+
await self.session_stack.aclose()
|
|
543
|
+
if self.client_session:
|
|
544
|
+
self.client_session = None
|
|
530
545
|
stack = AsyncExitStack()
|
|
531
546
|
await stack.__aenter__()
|
|
532
547
|
|
|
@@ -536,7 +551,30 @@ class FlockMCPClientBase(BaseModel, ABC):
|
|
|
536
551
|
transport_ctx = await self.create_transport(
|
|
537
552
|
server_params, self.additional_params
|
|
538
553
|
)
|
|
539
|
-
|
|
554
|
+
safe_transport = self._safe_transport_ctx(transport_ctx)
|
|
555
|
+
result = await stack.enter_async_context(safe_transport)
|
|
556
|
+
|
|
557
|
+
# support old (read, write) or new (read, write, get_sesssion_id_callback)
|
|
558
|
+
read: MemoryObjectReceiveStream | None = None
|
|
559
|
+
write: MemoryObjectSendStream | None = None
|
|
560
|
+
get_session_id_callback: GetSessionIdCallback | None = None
|
|
561
|
+
if isinstance(result, tuple) and len(result) == 2:
|
|
562
|
+
# old type
|
|
563
|
+
read, write = result
|
|
564
|
+
get_session_id_callback = None
|
|
565
|
+
elif isinstance(result, tuple) and len(result) == 3:
|
|
566
|
+
# new type
|
|
567
|
+
read, write, get_session_id_callback = result
|
|
568
|
+
else:
|
|
569
|
+
raise RuntimeError(
|
|
570
|
+
f"create_transport returned unexpected tuple of {result}"
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
if read is None or write is None:
|
|
574
|
+
raise RuntimeError(
|
|
575
|
+
f"create_transport did not create any read or write streams."
|
|
576
|
+
)
|
|
577
|
+
|
|
540
578
|
read_timeout = self.config.connection_config.read_timeout_seconds
|
|
541
579
|
|
|
542
580
|
if (
|
|
@@ -553,6 +591,8 @@ class FlockMCPClientBase(BaseModel, ABC):
|
|
|
553
591
|
else timedelta(seconds=float(read_timeout))
|
|
554
592
|
)
|
|
555
593
|
|
|
594
|
+
# TODO: get_session_id_callback is currently ignored.
|
|
595
|
+
|
|
556
596
|
session = await stack.enter_async_context(
|
|
557
597
|
ClientSession(
|
|
558
598
|
read_stream=read,
|
|
@@ -603,18 +643,7 @@ class FlockMCPClientBase(BaseModel, ABC):
|
|
|
603
643
|
|
|
604
644
|
self.connected_server_capabilities = init
|
|
605
645
|
|
|
606
|
-
init_report = f""
|
|
607
|
-
Server Init Handshake completed Server '{self.config.name}'
|
|
608
|
-
Lists the following Capabilities:
|
|
609
|
-
|
|
610
|
-
- Protocol Version: {init.protocolVersion}
|
|
611
|
-
- Instructions: {init.instructions or "No specific Instructions"}
|
|
612
|
-
- MCP Implementation:
|
|
613
|
-
- Name: {init.serverInfo.name}
|
|
614
|
-
- Version: {init.serverInfo.version}
|
|
615
|
-
- Capabilities:
|
|
616
|
-
{init.capabilities}
|
|
617
|
-
"""
|
|
646
|
+
init_report = f"Server: '{self.config.name}': Protocol-Version: {init.protocolVersion}, Instructions: {init.instructions or 'No specific instructions'}, MCP_Implementation: Name: {init.serverInfo.name}, Version: {init.serverInfo.version}, Capabilities: {init.capabilities}"
|
|
618
647
|
|
|
619
648
|
logger.debug(init_report)
|
|
620
649
|
|
|
@@ -19,7 +19,7 @@ from flock.core.mcp.mcp_client import (
|
|
|
19
19
|
)
|
|
20
20
|
from flock.core.mcp.mcp_config import FlockMCPConfigurationBase
|
|
21
21
|
|
|
22
|
-
logger = get_logger("
|
|
22
|
+
logger = get_logger("mcp.client_manager")
|
|
23
23
|
tracer = trace.get_tracer(__name__)
|
|
24
24
|
|
|
25
25
|
TClient = TypeVar("TClient", bound="FlockMCPClientBase")
|
flock/core/mcp/mcp_config.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""Base Config for MCP Clients."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import importlib
|
|
4
|
+
from typing import Any, Literal, TypeVar
|
|
4
5
|
|
|
6
|
+
import httpx
|
|
5
7
|
from pydantic import BaseModel, ConfigDict, Field, create_model
|
|
6
8
|
|
|
7
9
|
from flock.core.mcp.types.types import (
|
|
@@ -11,6 +13,15 @@ from flock.core.mcp.types.types import (
|
|
|
11
13
|
FlockSamplingMCPCallback,
|
|
12
14
|
MCPRoot,
|
|
13
15
|
ServerParameters,
|
|
16
|
+
SseServerParameters,
|
|
17
|
+
StdioServerParameters,
|
|
18
|
+
StreamableHttpServerParameters,
|
|
19
|
+
WebsocketServerParameters,
|
|
20
|
+
)
|
|
21
|
+
from flock.core.serialization.serializable import Serializable
|
|
22
|
+
from flock.core.serialization.serialization_utils import (
|
|
23
|
+
deserialize_item,
|
|
24
|
+
serialize_item,
|
|
14
25
|
)
|
|
15
26
|
|
|
16
27
|
LoggingLevel = Literal[
|
|
@@ -32,7 +43,7 @@ D = TypeVar("D", bound="FlockMCPCachingConfigurationBase")
|
|
|
32
43
|
E = TypeVar("E", bound="FlockMCPFeatureConfigurationBase")
|
|
33
44
|
|
|
34
45
|
|
|
35
|
-
class FlockMCPCachingConfigurationBase(BaseModel):
|
|
46
|
+
class FlockMCPCachingConfigurationBase(BaseModel, Serializable):
|
|
36
47
|
"""Configuration for Caching in Clients."""
|
|
37
48
|
|
|
38
49
|
tool_cache_max_size: float = Field(
|
|
@@ -79,6 +90,18 @@ class FlockMCPCachingConfigurationBase(BaseModel):
|
|
|
79
90
|
extra="allow",
|
|
80
91
|
)
|
|
81
92
|
|
|
93
|
+
def to_dict(self, path_type: str = "relative"):
|
|
94
|
+
"""Serialize the config object."""
|
|
95
|
+
return self.model_dump(
|
|
96
|
+
exclude_none=True,
|
|
97
|
+
mode="json",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_dict(cls: type[D], data: dict[str, Any]) -> D:
|
|
102
|
+
"""Deserialize from a dict."""
|
|
103
|
+
return cls(**{k: v for k, v in data.items()})
|
|
104
|
+
|
|
82
105
|
@classmethod
|
|
83
106
|
def with_fields(cls: type[D], **field_definitions) -> type[D]:
|
|
84
107
|
"""Create a new config class with additional fields."""
|
|
@@ -87,7 +110,7 @@ class FlockMCPCachingConfigurationBase(BaseModel):
|
|
|
87
110
|
)
|
|
88
111
|
|
|
89
112
|
|
|
90
|
-
class FlockMCPCallbackConfigurationBase(BaseModel):
|
|
113
|
+
class FlockMCPCallbackConfigurationBase(BaseModel, Serializable):
|
|
91
114
|
"""Base Configuration Class for Callbacks for Clients."""
|
|
92
115
|
|
|
93
116
|
sampling_callback: FlockSamplingMCPCallback | None = Field(
|
|
@@ -111,6 +134,52 @@ class FlockMCPCallbackConfigurationBase(BaseModel):
|
|
|
111
134
|
|
|
112
135
|
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
|
|
113
136
|
|
|
137
|
+
def to_dict(self, path_type: str = "relative"):
|
|
138
|
+
"""Serialize the object."""
|
|
139
|
+
# we need to register callables.
|
|
140
|
+
data: dict[str, Any] = {}
|
|
141
|
+
if self.sampling_callback:
|
|
142
|
+
sampling_callback_data = serialize_item(self.sampling_callback)
|
|
143
|
+
data["sampling_callback"] = sampling_callback_data
|
|
144
|
+
|
|
145
|
+
if self.list_roots_callback:
|
|
146
|
+
list_roots_callback_data = serialize_item(self.list_roots_callback)
|
|
147
|
+
data["list_roots_callback"] = list_roots_callback_data
|
|
148
|
+
|
|
149
|
+
if self.logging_callback:
|
|
150
|
+
logging_callback_data = serialize_item(self.logging_callback)
|
|
151
|
+
data["logging_callback"] = logging_callback_data
|
|
152
|
+
|
|
153
|
+
if self.message_handler:
|
|
154
|
+
message_handler_data = serialize_item(self.message_handler)
|
|
155
|
+
data["message_handler"] = message_handler_data
|
|
156
|
+
|
|
157
|
+
return data
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def from_dict(cls: type[A], data: dict[str, Any]) -> A:
|
|
161
|
+
"""Deserialize from a dict."""
|
|
162
|
+
instance = cls()
|
|
163
|
+
if data:
|
|
164
|
+
if "sampling_callback" in data:
|
|
165
|
+
instance.sampling_callback = deserialize_item(
|
|
166
|
+
data["sampling_callback"]
|
|
167
|
+
)
|
|
168
|
+
if "list_roots_callback" in data:
|
|
169
|
+
instance.list_roots_callback = deserialize_item(
|
|
170
|
+
data["list_roots_callback"]
|
|
171
|
+
)
|
|
172
|
+
if "logging_callback" in data:
|
|
173
|
+
instance.logging_callback = deserialize_item(
|
|
174
|
+
data["logging_callback"]
|
|
175
|
+
)
|
|
176
|
+
if "message_handler" in data:
|
|
177
|
+
instance.message_handler = deserialize_item(
|
|
178
|
+
data["message_handler"]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return instance
|
|
182
|
+
|
|
114
183
|
@classmethod
|
|
115
184
|
def with_fields(cls: type[A], **field_definitions) -> type[A]:
|
|
116
185
|
"""Create a new config class with additional fields."""
|
|
@@ -119,7 +188,7 @@ class FlockMCPCallbackConfigurationBase(BaseModel):
|
|
|
119
188
|
)
|
|
120
189
|
|
|
121
190
|
|
|
122
|
-
class FlockMCPConnectionConfigurationBase(BaseModel):
|
|
191
|
+
class FlockMCPConnectionConfigurationBase(BaseModel, Serializable):
|
|
123
192
|
"""Base Configuration Class for Connection Parameters for a client."""
|
|
124
193
|
|
|
125
194
|
max_retries: int = Field(
|
|
@@ -131,9 +200,9 @@ class FlockMCPConnectionConfigurationBase(BaseModel):
|
|
|
131
200
|
..., description="Connection parameters for the server."
|
|
132
201
|
)
|
|
133
202
|
|
|
134
|
-
transport_type: Literal[
|
|
135
|
-
|
|
136
|
-
)
|
|
203
|
+
transport_type: Literal[
|
|
204
|
+
"stdio", "websockets", "sse", "streamable_http", "custom"
|
|
205
|
+
] = Field(..., description="Type of transport to use.")
|
|
137
206
|
|
|
138
207
|
mount_points: list[MCPRoot] | None = Field(
|
|
139
208
|
default=None, description="Initial Mountpoints to operate under."
|
|
@@ -150,6 +219,77 @@ class FlockMCPConnectionConfigurationBase(BaseModel):
|
|
|
150
219
|
|
|
151
220
|
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
|
|
152
221
|
|
|
222
|
+
def to_dict(self, path_type: str = "relative") -> dict[str, Any]:
|
|
223
|
+
"""Serialize object to a dict."""
|
|
224
|
+
exclude = ["connection_parameters"]
|
|
225
|
+
|
|
226
|
+
data = self.model_dump(
|
|
227
|
+
exclude=exclude,
|
|
228
|
+
exclude_defaults=False,
|
|
229
|
+
exclude_none=True,
|
|
230
|
+
mode="json",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
data["connection_parameters"] = self.connection_parameters.to_dict(
|
|
234
|
+
path_type=path_type
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return data
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def from_dict(cls: type[B], data: dict[str, Any]) -> B:
|
|
241
|
+
"""Deserialize from dict."""
|
|
242
|
+
connection_params = data.get("connection_parameters")
|
|
243
|
+
connection_params_obj = None
|
|
244
|
+
auth_obj: httpx.Auth | None = None
|
|
245
|
+
if connection_params:
|
|
246
|
+
kind = connection_params.get("transport_type", None)
|
|
247
|
+
auth_spec = connection_params.get("auth", None)
|
|
248
|
+
if auth_spec:
|
|
249
|
+
# find the concrete implementation and
|
|
250
|
+
# instantiate it.
|
|
251
|
+
# find the concrete implementation for auth and instatiate it.
|
|
252
|
+
impl = auth_spec.get("implementation", None)
|
|
253
|
+
params = auth_spec.get("params", None)
|
|
254
|
+
if impl and params:
|
|
255
|
+
mod = importlib.import_module(impl["module_path"])
|
|
256
|
+
real_cls = getattr(mod, impl["class_name"])
|
|
257
|
+
auth_obj = real_cls(**{k: v for k, v in params.items()})
|
|
258
|
+
|
|
259
|
+
if auth_obj:
|
|
260
|
+
connection_params["auth"] = auth_obj
|
|
261
|
+
else:
|
|
262
|
+
# just to be sure
|
|
263
|
+
connection_params.pop("auth", None)
|
|
264
|
+
match kind:
|
|
265
|
+
case "stdio":
|
|
266
|
+
connection_params_obj = StdioServerParameters(
|
|
267
|
+
**{k: v for k, v in connection_params.items()}
|
|
268
|
+
)
|
|
269
|
+
case "websockets":
|
|
270
|
+
connection_params_obj = WebsocketServerParameters(
|
|
271
|
+
**{k: v for k, v in connection_params.items()}
|
|
272
|
+
)
|
|
273
|
+
case "streamable_http":
|
|
274
|
+
connection_params_obj = StreamableHttpServerParameters(
|
|
275
|
+
**{k: v for k, v in connection_params.items()}
|
|
276
|
+
)
|
|
277
|
+
case "sse":
|
|
278
|
+
connection_params_obj = SseServerParameters(
|
|
279
|
+
**{k: v for k, v in connection_params.items()}
|
|
280
|
+
)
|
|
281
|
+
case _:
|
|
282
|
+
# handle custom server params
|
|
283
|
+
connection_params_obj = ServerParameters(
|
|
284
|
+
**{k: v for k, v in connection_params.items()}
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if connection_params_obj:
|
|
288
|
+
data["connection_parameters"] = connection_params_obj
|
|
289
|
+
return cls(**{k: v for k, v in data.items()})
|
|
290
|
+
else:
|
|
291
|
+
raise ValueError("No connection parameters provided.")
|
|
292
|
+
|
|
153
293
|
@classmethod
|
|
154
294
|
def with_fields(cls: type[B], **field_definitions) -> type[B]:
|
|
155
295
|
"""Create a new config class with additional fields."""
|
|
@@ -158,7 +298,7 @@ class FlockMCPConnectionConfigurationBase(BaseModel):
|
|
|
158
298
|
)
|
|
159
299
|
|
|
160
300
|
|
|
161
|
-
class FlockMCPFeatureConfigurationBase(BaseModel):
|
|
301
|
+
class FlockMCPFeatureConfigurationBase(BaseModel, Serializable):
|
|
162
302
|
"""Base Configuration Class for switching MCP Features on and off."""
|
|
163
303
|
|
|
164
304
|
roots_enabled: bool = Field(
|
|
@@ -186,6 +326,18 @@ class FlockMCPFeatureConfigurationBase(BaseModel):
|
|
|
186
326
|
extra="allow",
|
|
187
327
|
)
|
|
188
328
|
|
|
329
|
+
def to_dict(self, path_type: str = "relative"):
|
|
330
|
+
"""Serialize the object."""
|
|
331
|
+
return self.model_dump(
|
|
332
|
+
mode="json",
|
|
333
|
+
exclude_none=True,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
@classmethod
|
|
337
|
+
def from_dict(cls, data: dict[str, Any]):
|
|
338
|
+
"""Deserialize from a dict."""
|
|
339
|
+
return cls(**{k: v for k, v in data.items()})
|
|
340
|
+
|
|
189
341
|
@classmethod
|
|
190
342
|
def with_fields(cls: type[E], **field_definitions) -> type[E]:
|
|
191
343
|
"""Create a new config class with additional fields."""
|
|
@@ -194,7 +346,7 @@ class FlockMCPFeatureConfigurationBase(BaseModel):
|
|
|
194
346
|
)
|
|
195
347
|
|
|
196
348
|
|
|
197
|
-
class FlockMCPConfigurationBase(BaseModel):
|
|
349
|
+
class FlockMCPConfigurationBase(BaseModel, Serializable):
|
|
198
350
|
"""Base Configuration Class for MCP Clients.
|
|
199
351
|
|
|
200
352
|
Each Client should implement their own config
|
|
@@ -229,6 +381,90 @@ class FlockMCPConfigurationBase(BaseModel):
|
|
|
229
381
|
extra="allow",
|
|
230
382
|
)
|
|
231
383
|
|
|
384
|
+
def to_dict(self, path_type: str = "relative") -> dict[str, Any]:
|
|
385
|
+
"""Serialize the object to a dict."""
|
|
386
|
+
# each built-in type should serialize, deserialize it self.
|
|
387
|
+
exclude = [
|
|
388
|
+
"connection_config",
|
|
389
|
+
"caching_config",
|
|
390
|
+
"callback_config",
|
|
391
|
+
"feature_config",
|
|
392
|
+
]
|
|
393
|
+
|
|
394
|
+
data = self.model_dump(
|
|
395
|
+
exclude=exclude,
|
|
396
|
+
exclude_defaults=False,
|
|
397
|
+
exclude_none=True,
|
|
398
|
+
mode="json",
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# add the core properties
|
|
402
|
+
data["connection_config"] = self.connection_config.to_dict(path_type)
|
|
403
|
+
data["caching_config"] = self.caching_config.to_dict(path_type)
|
|
404
|
+
data["callback_config"] = self.callback_config.to_dict(path_type)
|
|
405
|
+
data["feature_config"] = self.feature_config.to_dict(path_type)
|
|
406
|
+
|
|
407
|
+
return data
|
|
408
|
+
|
|
409
|
+
@classmethod
|
|
410
|
+
def from_dict(cls: type[C], data: dict[str, Any]) -> C:
|
|
411
|
+
"""Deserialize the class."""
|
|
412
|
+
connection_config = data.pop("connection_config", None)
|
|
413
|
+
caching_config = data.pop("caching_config", None)
|
|
414
|
+
feature_config = data.pop("feature_config", None)
|
|
415
|
+
callback_config = data.pop("callback_config", None)
|
|
416
|
+
|
|
417
|
+
instance_data: dict[str, Any] = {
|
|
418
|
+
"name": data["name"]
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if connection_config:
|
|
422
|
+
# Forcing a square into a round hole
|
|
423
|
+
try:
|
|
424
|
+
config_field = cls.model_fields["connection_config"]
|
|
425
|
+
config_cls = config_field.annotation
|
|
426
|
+
except (AttributeError, KeyError):
|
|
427
|
+
# fallback
|
|
428
|
+
config_cls = FlockMCPConnectionConfigurationBase
|
|
429
|
+
instance_data["connection_config"] = config_cls.from_dict(connection_config)
|
|
430
|
+
else:
|
|
431
|
+
raise ValueError(f"connection_config MUST be specified for '{data.get('name', 'unknown_server')}")
|
|
432
|
+
|
|
433
|
+
if caching_config:
|
|
434
|
+
try:
|
|
435
|
+
config_field = cls.model_fields["caching_config"]
|
|
436
|
+
config_cls = config_field.annotation
|
|
437
|
+
except (AttributeError, KeyError):
|
|
438
|
+
# fallback
|
|
439
|
+
config_cls = FlockMCPCachingConfigurationBase
|
|
440
|
+
instance_data["caching_config"] = config_cls.from_dict(caching_config)
|
|
441
|
+
else:
|
|
442
|
+
instance_data["caching_config"] = FlockMCPCachingConfigurationBase()
|
|
443
|
+
|
|
444
|
+
if feature_config:
|
|
445
|
+
try:
|
|
446
|
+
config_field = cls.model_fields["feature_config"]
|
|
447
|
+
config_cls = config_field.annotation
|
|
448
|
+
except (AttributeError, KeyError):
|
|
449
|
+
# fallback
|
|
450
|
+
config_cls = FlockMCPFeatureConfigurationBase
|
|
451
|
+
instance_data["feature_config"] = config_cls.from_dict(feature_config)
|
|
452
|
+
else:
|
|
453
|
+
instance_data["feature_config"] = FlockMCPFeatureConfigurationBase()
|
|
454
|
+
|
|
455
|
+
if callback_config:
|
|
456
|
+
try:
|
|
457
|
+
config_field = cls.model_fields["callback_config"]
|
|
458
|
+
config_cls = config_field.annotation
|
|
459
|
+
except (AttributeError, KeyError):
|
|
460
|
+
# fallback
|
|
461
|
+
config_cls = FlockMCPCallbackConfigurationBase
|
|
462
|
+
instance_data["callback_config"] = config_cls.from_dict(callback_config)
|
|
463
|
+
else:
|
|
464
|
+
instance_data["callback_config"] = FlockMCPCallbackConfigurationBase()
|
|
465
|
+
|
|
466
|
+
return cls(**{k: v for k, v in instance_data.items()})
|
|
467
|
+
|
|
232
468
|
@classmethod
|
|
233
469
|
def with_fields(cls: type[C], **field_definitions) -> type[C]:
|
|
234
470
|
"""Create a new config class with additional fields."""
|
|
@@ -8,6 +8,8 @@ from mcp.types import (
|
|
|
8
8
|
CreateMessageRequestParams,
|
|
9
9
|
ErrorData,
|
|
10
10
|
ListRootsResult,
|
|
11
|
+
LoggingMessageNotificationParams,
|
|
12
|
+
ServerNotification,
|
|
11
13
|
ServerRequest,
|
|
12
14
|
)
|
|
13
15
|
|
|
@@ -19,10 +21,6 @@ from flock.core.mcp.types.handlers import (
|
|
|
19
21
|
handle_incoming_server_notification,
|
|
20
22
|
handle_logging_message,
|
|
21
23
|
)
|
|
22
|
-
from flock.core.mcp.types.types import (
|
|
23
|
-
FlockLoggingMessageNotificationParams,
|
|
24
|
-
ServerNotification,
|
|
25
|
-
)
|
|
26
24
|
|
|
27
25
|
|
|
28
26
|
async def default_sampling_callback(
|
|
@@ -76,7 +74,7 @@ async def default_list_roots_callback(
|
|
|
76
74
|
|
|
77
75
|
|
|
78
76
|
async def default_logging_callback(
|
|
79
|
-
params:
|
|
77
|
+
params: LoggingMessageNotificationParams,
|
|
80
78
|
logger: FlockLogger,
|
|
81
79
|
server_name: str,
|
|
82
80
|
) -> None:
|