flock-core 0.4.511__py3-none-any.whl → 0.4.513__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.

Files changed (56) hide show
  1. flock/core/config/flock_agent_config.py +11 -0
  2. flock/core/config/scheduled_agent_config.py +40 -0
  3. flock/core/flock_agent.py +7 -1
  4. flock/core/flock_factory.py +129 -2
  5. flock/core/flock_scheduler.py +166 -0
  6. flock/core/logging/logging.py +8 -0
  7. flock/core/mcp/flock_mcp_server.py +30 -4
  8. flock/core/mcp/flock_mcp_tool_base.py +1 -1
  9. flock/core/mcp/mcp_client.py +57 -28
  10. flock/core/mcp/mcp_client_manager.py +1 -1
  11. flock/core/mcp/mcp_config.py +245 -9
  12. flock/core/mcp/types/callbacks.py +3 -5
  13. flock/core/mcp/types/factories.py +12 -14
  14. flock/core/mcp/types/handlers.py +9 -12
  15. flock/core/mcp/types/types.py +205 -2
  16. flock/mcp/servers/sse/flock_sse_server.py +21 -14
  17. flock/mcp/servers/streamable_http/__init__.py +0 -0
  18. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +169 -0
  19. flock/mcp/servers/websockets/flock_websocket_server.py +3 -3
  20. flock/webapp/app/main.py +66 -11
  21. flock/webapp/app/services/sharing_store.py +173 -0
  22. flock/webapp/run.py +3 -1
  23. flock/webapp/templates/base.html +10 -11
  24. flock/webapp/templates/chat.html +7 -10
  25. flock/webapp/templates/chat_settings.html +3 -4
  26. flock/webapp/templates/flock_editor.html +1 -2
  27. flock/webapp/templates/index.html +1 -1
  28. flock/webapp/templates/partials/_agent_detail_form.html +7 -13
  29. flock/webapp/templates/partials/_agent_list.html +1 -2
  30. flock/webapp/templates/partials/_agent_manager_view.html +2 -3
  31. flock/webapp/templates/partials/_chat_container.html +2 -2
  32. flock/webapp/templates/partials/_chat_settings_form.html +6 -8
  33. flock/webapp/templates/partials/_create_flock_form.html +2 -4
  34. flock/webapp/templates/partials/_dashboard_flock_detail.html +2 -3
  35. flock/webapp/templates/partials/_dashboard_flock_file_list.html +1 -2
  36. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +2 -3
  37. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +1 -2
  38. flock/webapp/templates/partials/_env_vars_table.html +2 -4
  39. flock/webapp/templates/partials/_execution_form.html +12 -10
  40. flock/webapp/templates/partials/_execution_view_container.html +2 -3
  41. flock/webapp/templates/partials/_flock_file_list.html +2 -3
  42. flock/webapp/templates/partials/_flock_properties_form.html +2 -2
  43. flock/webapp/templates/partials/_flock_upload_form.html +1 -2
  44. flock/webapp/templates/partials/_load_manager_view.html +2 -3
  45. flock/webapp/templates/partials/_registry_viewer_content.html +4 -5
  46. flock/webapp/templates/partials/_settings_env_content.html +2 -3
  47. flock/webapp/templates/partials/_settings_theme_content.html +2 -2
  48. flock/webapp/templates/partials/_settings_view.html +2 -2
  49. flock/webapp/templates/partials/_sidebar.html +27 -39
  50. flock/webapp/templates/registry_viewer.html +7 -10
  51. flock/webapp/templates/shared_run_page.html +7 -10
  52. {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/METADATA +3 -1
  53. {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/RECORD +56 -51
  54. {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/WHEEL +0 -0
  55. {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/entry_points.txt +0 -0
  56. {flock_core-0.4.511.dist-info → flock_core-0.4.513.dist-info}/licenses/LICENSE +0 -0
@@ -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, JSONRPCMessage
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("core.mcp.client_base")
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
- ) -> AbstractAsyncContextManager[
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.session_stack = None
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 hol onto a single ClientSession + ExitStack."""
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
- read, write = await stack.enter_async_context(transport_ctx)
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("core.mcp.connection_manager_base")
22
+ logger = get_logger("mcp.client_manager")
23
23
  tracer = trace.get_tracer(__name__)
24
24
 
25
25
  TClient = TypeVar("TClient", bound="FlockMCPClientBase")
@@ -1,7 +1,9 @@
1
1
  """Base Config for MCP Clients."""
2
2
 
3
- from typing import Literal, TypeVar
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["stdio", "websockets", "sse", "custom"] = Field(
135
- ..., description="Type of transport to use."
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: FlockLoggingMessageNotificationParams,
77
+ params: LoggingMessageNotificationParams,
80
78
  logger: FlockLogger,
81
79
  server_name: str,
82
80
  ) -> None:
@@ -1,6 +1,6 @@
1
1
  """Factories for default MCP Callbacks."""
2
2
 
3
- from typing import TYPE_CHECKING, Any
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
- if TYPE_CHECKING:
21
- from flock.core.mcp.types.callbacks import (
22
- default_list_roots_callback,
23
- default_logging_callback,
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
- logger_to_use=logger_to_use,
89
+ logger=logger_to_use,
92
90
  associated_client=associated_client,
93
91
  )
94
92
 
@@ -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[_MCPServerNotification], Callable] = {
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: FlockLoggingMessageNotificationParams,
154
+ params: LoggingMessageNotificationParams,
158
155
  logger: FlockLogger,
159
156
  server_name: str,
160
157
  ) -> None: