flock-core 0.5.8__py3-none-any.whl → 0.5.10__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/agent.py +149 -62
- flock/api/themes.py +6 -2
- flock/artifact_collector.py +6 -3
- flock/batch_accumulator.py +3 -1
- flock/cli.py +3 -1
- flock/components.py +45 -56
- flock/context_provider.py +531 -0
- flock/correlation_engine.py +8 -4
- flock/dashboard/collector.py +48 -29
- flock/dashboard/events.py +10 -4
- flock/dashboard/launcher.py +3 -1
- flock/dashboard/models/graph.py +9 -3
- flock/dashboard/service.py +143 -72
- flock/dashboard/websocket.py +17 -4
- flock/engines/dspy_engine.py +174 -98
- flock/engines/examples/simple_batch_engine.py +9 -3
- flock/examples.py +6 -2
- flock/frontend/src/services/indexeddb.test.ts +4 -4
- flock/frontend/src/services/indexeddb.ts +1 -1
- flock/helper/cli_helper.py +14 -1
- flock/logging/auto_trace.py +6 -1
- flock/logging/formatters/enum_builder.py +3 -1
- flock/logging/formatters/theme_builder.py +32 -17
- flock/logging/formatters/themed_formatter.py +38 -22
- flock/logging/logging.py +21 -7
- flock/logging/telemetry.py +9 -3
- flock/logging/telemetry_exporter/duckdb_exporter.py +27 -25
- flock/logging/trace_and_logged.py +14 -5
- flock/mcp/__init__.py +3 -6
- flock/mcp/client.py +49 -19
- flock/mcp/config.py +12 -6
- flock/mcp/manager.py +6 -2
- flock/mcp/servers/sse/flock_sse_server.py +9 -3
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +6 -2
- flock/mcp/tool.py +18 -6
- flock/mcp/types/handlers.py +3 -1
- flock/mcp/types/types.py +9 -3
- flock/orchestrator.py +204 -50
- flock/orchestrator_component.py +15 -5
- flock/patches/dspy_streaming_patch.py +12 -4
- flock/registry.py +9 -3
- flock/runtime.py +69 -18
- flock/service.py +19 -6
- flock/store.py +29 -10
- flock/subscription.py +6 -4
- flock/utilities.py +41 -13
- flock/utility/output_utility_component.py +31 -11
- {flock_core-0.5.8.dist-info → flock_core-0.5.10.dist-info}/METADATA +134 -4
- {flock_core-0.5.8.dist-info → flock_core-0.5.10.dist-info}/RECORD +52 -51
- {flock_core-0.5.8.dist-info → flock_core-0.5.10.dist-info}/WHEEL +0 -0
- {flock_core-0.5.8.dist-info → flock_core-0.5.10.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.8.dist-info → flock_core-0.5.10.dist-info}/licenses/LICENSE +0 -0
flock/mcp/client.py
CHANGED
|
@@ -76,7 +76,9 @@ class FlockMCPClient(BaseModel, ABC):
|
|
|
76
76
|
"""
|
|
77
77
|
|
|
78
78
|
# --- Properties ---
|
|
79
|
-
config: FlockMCPConfiguration = Field(
|
|
79
|
+
config: FlockMCPConfiguration = Field(
|
|
80
|
+
..., description="The config for this client instance."
|
|
81
|
+
)
|
|
80
82
|
|
|
81
83
|
tool_cache: TTLCache | None = Field(
|
|
82
84
|
default=None,
|
|
@@ -174,14 +176,18 @@ class FlockMCPClient(BaseModel, ABC):
|
|
|
174
176
|
await client._ensure_connected()
|
|
175
177
|
try:
|
|
176
178
|
# delegate the real session
|
|
177
|
-
return await getattr(client.client_session, name)(
|
|
179
|
+
return await getattr(client.client_session, name)(
|
|
180
|
+
*args, **kwargs
|
|
181
|
+
)
|
|
178
182
|
except McpError as e:
|
|
179
183
|
# only retry on a transport timeout
|
|
180
184
|
if e.error.code == httpx.codes.REQUEST_TIMEOUT:
|
|
181
185
|
kind = "timeout"
|
|
182
186
|
else:
|
|
183
187
|
# application-level MCP error -> give up immediately
|
|
184
|
-
logger.exception(
|
|
188
|
+
logger.exception(
|
|
189
|
+
f"MCP error in session.{name}: {e.error}"
|
|
190
|
+
)
|
|
185
191
|
return None
|
|
186
192
|
except (BrokenPipeError, ClosedResourceError) as e:
|
|
187
193
|
kind = type(e).__name__
|
|
@@ -301,21 +307,27 @@ class FlockMCPClient(BaseModel, ABC):
|
|
|
301
307
|
|
|
302
308
|
if not self.message_handler:
|
|
303
309
|
if not self.config.callback_config.message_handler:
|
|
304
|
-
self.message_handler =
|
|
305
|
-
|
|
306
|
-
|
|
310
|
+
self.message_handler = (
|
|
311
|
+
default_flock_mcp_message_handler_callback_factory(
|
|
312
|
+
associated_client=self,
|
|
313
|
+
logger=logger,
|
|
314
|
+
)
|
|
307
315
|
)
|
|
308
316
|
else:
|
|
309
317
|
self.message_handler = self.config.callback_config.message_handler
|
|
310
318
|
|
|
311
319
|
if not self.list_roots_callback:
|
|
312
320
|
if not self.config.callback_config.list_roots_callback:
|
|
313
|
-
self.list_roots_callback =
|
|
314
|
-
|
|
315
|
-
|
|
321
|
+
self.list_roots_callback = (
|
|
322
|
+
default_flock_mcp_list_roots_callback_factory(
|
|
323
|
+
associated_client=self,
|
|
324
|
+
logger=logger,
|
|
325
|
+
)
|
|
316
326
|
)
|
|
317
327
|
else:
|
|
318
|
-
self.list_roots_callback =
|
|
328
|
+
self.list_roots_callback = (
|
|
329
|
+
self.config.callback_config.list_roots_callback
|
|
330
|
+
)
|
|
319
331
|
|
|
320
332
|
if not self.sampling_callback:
|
|
321
333
|
if not self.config.callback_config.sampling_callback:
|
|
@@ -407,7 +419,9 @@ class FlockMCPClient(BaseModel, ABC):
|
|
|
407
419
|
if cache_key in self.tool_result_cache:
|
|
408
420
|
return self.tool_result_cache[cache_key]
|
|
409
421
|
|
|
410
|
-
async def _call_tool_internal(
|
|
422
|
+
async def _call_tool_internal(
|
|
423
|
+
name: str, arguments: dict[str, Any]
|
|
424
|
+
) -> CallToolResult:
|
|
411
425
|
logger.debug(f"Calling tool '{name}' with arguments {arguments}")
|
|
412
426
|
return await self.session.call_tool(
|
|
413
427
|
name=name,
|
|
@@ -465,19 +479,27 @@ class FlockMCPClient(BaseModel, ABC):
|
|
|
465
479
|
|
|
466
480
|
async def invalidate_resource_list_cache(self) -> None:
|
|
467
481
|
"""Invalidate the entries in the resource list cache."""
|
|
468
|
-
logger.debug(
|
|
482
|
+
logger.debug(
|
|
483
|
+
f"Invalidating resource_list_cache for server '{self.config.name}'"
|
|
484
|
+
)
|
|
469
485
|
async with self.lock:
|
|
470
486
|
if self.resource_list_cache:
|
|
471
487
|
self.resource_list_cache.clear()
|
|
472
|
-
logger.debug(
|
|
488
|
+
logger.debug(
|
|
489
|
+
f"Invalidated resource_list_cache for server '{self.config.name}'"
|
|
490
|
+
)
|
|
473
491
|
|
|
474
492
|
async def invalidate_resource_contents_cache(self) -> None:
|
|
475
493
|
"""Invalidate the entries in the resource contents cache."""
|
|
476
|
-
logger.debug(
|
|
494
|
+
logger.debug(
|
|
495
|
+
f"Invalidating resource_contents_cache for server '{self.config.name}'."
|
|
496
|
+
)
|
|
477
497
|
async with self.lock:
|
|
478
498
|
if self.resource_contents_cache:
|
|
479
499
|
self.resource_contents_cache.clear()
|
|
480
|
-
logger.debug(
|
|
500
|
+
logger.debug(
|
|
501
|
+
f"Invalidated resource_contents_cache for server '{self.config.name}'"
|
|
502
|
+
)
|
|
481
503
|
|
|
482
504
|
async def invalidate_resource_contents_cache_entry(self, key: str) -> None:
|
|
483
505
|
"""Invalidate a single entry in the resource contents cache."""
|
|
@@ -533,7 +555,9 @@ class FlockMCPClient(BaseModel, ABC):
|
|
|
533
555
|
server_params = self.config.connection_config.connection_parameters
|
|
534
556
|
|
|
535
557
|
# Single Hook
|
|
536
|
-
transport_ctx = await self.create_transport(
|
|
558
|
+
transport_ctx = await self.create_transport(
|
|
559
|
+
server_params, self.additional_params
|
|
560
|
+
)
|
|
537
561
|
safe_transport = self._safe_transport_ctx(transport_ctx)
|
|
538
562
|
result = await stack.enter_async_context(safe_transport)
|
|
539
563
|
|
|
@@ -547,15 +571,21 @@ class FlockMCPClient(BaseModel, ABC):
|
|
|
547
571
|
# new type
|
|
548
572
|
read, write, _get_session_id_callback = result
|
|
549
573
|
else:
|
|
550
|
-
raise RuntimeError(
|
|
574
|
+
raise RuntimeError(
|
|
575
|
+
f"create_transport returned unexpected tuple of {result}"
|
|
576
|
+
)
|
|
551
577
|
|
|
552
578
|
if read is None or write is None:
|
|
553
|
-
raise RuntimeError(
|
|
579
|
+
raise RuntimeError(
|
|
580
|
+
"create_transport did not create any read or write streams."
|
|
581
|
+
)
|
|
554
582
|
|
|
555
583
|
read_timeout = self.config.connection_config.read_timeout_seconds
|
|
556
584
|
|
|
557
585
|
if self.additional_params and "read_timeout_seconds" in self.additional_params:
|
|
558
|
-
read_timeout = self.additional_params.get(
|
|
586
|
+
read_timeout = self.additional_params.get(
|
|
587
|
+
"read_timeout_seconds", read_timeout
|
|
588
|
+
)
|
|
559
589
|
|
|
560
590
|
timeout_seconds = (
|
|
561
591
|
read_timeout
|
flock/mcp/config.py
CHANGED
|
@@ -159,15 +159,17 @@ class FlockMCPConnectionConfiguration(BaseModel):
|
|
|
159
159
|
..., description="Connection parameters for the server."
|
|
160
160
|
)
|
|
161
161
|
|
|
162
|
-
transport_type: Literal[
|
|
163
|
-
|
|
164
|
-
)
|
|
162
|
+
transport_type: Literal[
|
|
163
|
+
"stdio", "websockets", "sse", "streamable_http", "custom"
|
|
164
|
+
] = Field(..., description="Type of transport to use.")
|
|
165
165
|
|
|
166
166
|
mount_points: list[MCPRoot] | None = Field(
|
|
167
167
|
default=None, description="Initial Mountpoints to operate under."
|
|
168
168
|
)
|
|
169
169
|
|
|
170
|
-
read_timeout_seconds: float | int = Field(
|
|
170
|
+
read_timeout_seconds: float | int = Field(
|
|
171
|
+
default=60 * 5, description="Read Timeout."
|
|
172
|
+
)
|
|
171
173
|
|
|
172
174
|
server_logging_level: LoggingLevel = Field(
|
|
173
175
|
default="error",
|
|
@@ -187,7 +189,9 @@ class FlockMCPConnectionConfiguration(BaseModel):
|
|
|
187
189
|
mode="json",
|
|
188
190
|
)
|
|
189
191
|
|
|
190
|
-
data["connection_parameters"] = self.connection_parameters.to_dict(
|
|
192
|
+
data["connection_parameters"] = self.connection_parameters.to_dict(
|
|
193
|
+
path_type=path_type
|
|
194
|
+
)
|
|
191
195
|
|
|
192
196
|
return data
|
|
193
197
|
|
|
@@ -235,7 +239,9 @@ class FlockMCPConnectionConfiguration(BaseModel):
|
|
|
235
239
|
)
|
|
236
240
|
case _:
|
|
237
241
|
# handle custom server params
|
|
238
|
-
connection_params_obj = ServerParameters(
|
|
242
|
+
connection_params_obj = ServerParameters(
|
|
243
|
+
**dict(connection_params.items())
|
|
244
|
+
)
|
|
239
245
|
|
|
240
246
|
if connection_params_obj:
|
|
241
247
|
data["connection_parameters"] = connection_params_obj
|
flock/mcp/manager.py
CHANGED
|
@@ -39,7 +39,7 @@ class FlockMCPClientManager:
|
|
|
39
39
|
"filesystem": FlockMCPConfiguration(
|
|
40
40
|
name="filesystem",
|
|
41
41
|
connection_config=connection_config,
|
|
42
|
-
feature_config=feature_config
|
|
42
|
+
feature_config=feature_config,
|
|
43
43
|
)
|
|
44
44
|
}
|
|
45
45
|
manager = FlockMCPClientManager(configs)
|
|
@@ -69,7 +69,11 @@ class FlockMCPClientManager:
|
|
|
69
69
|
self._lock = asyncio.Lock()
|
|
70
70
|
|
|
71
71
|
async def get_client(
|
|
72
|
-
self,
|
|
72
|
+
self,
|
|
73
|
+
server_name: str,
|
|
74
|
+
agent_id: str,
|
|
75
|
+
run_id: str,
|
|
76
|
+
mount_points: list[str] | None = None,
|
|
73
77
|
) -> FlockMCPClient:
|
|
74
78
|
"""Get or create an MCP client for the given context.
|
|
75
79
|
|
|
@@ -32,7 +32,9 @@ class FlockSSEConnectionConfig(FlockMCPConnectionConfiguration):
|
|
|
32
32
|
|
|
33
33
|
# Only thing we need to override here is the concrete transport_type
|
|
34
34
|
# and connection_parameters fields.
|
|
35
|
-
transport_type: Literal["sse"] = Field(
|
|
35
|
+
transport_type: Literal["sse"] = Field(
|
|
36
|
+
default="sse", description="Use the sse transport type."
|
|
37
|
+
)
|
|
36
38
|
|
|
37
39
|
connection_parameters: SseServerParameters = Field(
|
|
38
40
|
..., description="SSE Server Connection Parameters."
|
|
@@ -73,11 +75,15 @@ class FlockSSEClient(FlockMCPClient):
|
|
|
73
75
|
override_headers = bool(additional_params.get("override_headers", False))
|
|
74
76
|
if "headers" in additional_params:
|
|
75
77
|
if override_headers:
|
|
76
|
-
param_copy.headers = additional_params.get(
|
|
78
|
+
param_copy.headers = additional_params.get(
|
|
79
|
+
"headers", params.headers
|
|
80
|
+
)
|
|
77
81
|
else:
|
|
78
82
|
param_copy.headers.update(additional_params.get("headers", {}))
|
|
79
83
|
if "read_timeout_seconds" in additional_params:
|
|
80
|
-
param_copy.timeout = additional_params.get(
|
|
84
|
+
param_copy.timeout = additional_params.get(
|
|
85
|
+
"read_timeout_seconds", params.timeout
|
|
86
|
+
)
|
|
81
87
|
|
|
82
88
|
if "sse_read_timeout" in additional_params:
|
|
83
89
|
param_copy.sse_read_timeout = additional_params.get(
|
|
@@ -84,7 +84,9 @@ class FlockStreamableHttpClient(FlockMCPClient):
|
|
|
84
84
|
|
|
85
85
|
if "headers" in additional_params:
|
|
86
86
|
if override_headers:
|
|
87
|
-
param_copy.headers = additional_params.get(
|
|
87
|
+
param_copy.headers = additional_params.get(
|
|
88
|
+
"headers", params.headers
|
|
89
|
+
)
|
|
88
90
|
else:
|
|
89
91
|
param_copy.headers.update(additional_params.get("headers", {}))
|
|
90
92
|
if "auth" in additional_params and isinstance(
|
|
@@ -93,7 +95,9 @@ class FlockStreamableHttpClient(FlockMCPClient):
|
|
|
93
95
|
param_copy.auth = additional_params.get("auth", param_copy.auth)
|
|
94
96
|
|
|
95
97
|
if "read_timeout_seconds" in additional_params:
|
|
96
|
-
param_copy.timeout = additional_params.get(
|
|
98
|
+
param_copy.timeout = additional_params.get(
|
|
99
|
+
"read_timeout_seconds", params.timeout
|
|
100
|
+
)
|
|
97
101
|
|
|
98
102
|
if "sse_read_timeout" in additional_params:
|
|
99
103
|
param_copy.sse_read_timeout = additional_params.get(
|
flock/mcp/tool.py
CHANGED
|
@@ -32,11 +32,17 @@ class FlockMCPTool(BaseModel):
|
|
|
32
32
|
|
|
33
33
|
name: str = Field(..., description="Name of the tool")
|
|
34
34
|
|
|
35
|
-
agent_id: str = Field(
|
|
35
|
+
agent_id: str = Field(
|
|
36
|
+
..., description="Associated agent_id. Used for internal tracking."
|
|
37
|
+
)
|
|
36
38
|
|
|
37
|
-
run_id: str = Field(
|
|
39
|
+
run_id: str = Field(
|
|
40
|
+
..., description="Associated run_id. Used for internal tracking."
|
|
41
|
+
)
|
|
38
42
|
|
|
39
|
-
description: str | None = Field(
|
|
43
|
+
description: str | None = Field(
|
|
44
|
+
..., description="A human-readable description of the tool"
|
|
45
|
+
)
|
|
40
46
|
|
|
41
47
|
input_schema: dict[str, Any] = Field(
|
|
42
48
|
...,
|
|
@@ -76,11 +82,15 @@ class FlockMCPTool(BaseModel):
|
|
|
76
82
|
try:
|
|
77
83
|
return convert_input_schema_to_tool_args(input_schema)
|
|
78
84
|
except Exception as e: # pragma: no cover - defensive
|
|
79
|
-
logger.exception(
|
|
85
|
+
logger.exception(
|
|
86
|
+
"Failed to convert MCP tool schema to DSPy tool args: %s", e
|
|
87
|
+
)
|
|
80
88
|
# Fallback to empty definitions to avoid breaking execution
|
|
81
89
|
return {}, {}, {}
|
|
82
90
|
|
|
83
|
-
def _convert_mcp_tool_result(
|
|
91
|
+
def _convert_mcp_tool_result(
|
|
92
|
+
self, call_tool_result: CallToolResult
|
|
93
|
+
) -> str | list[Any]:
|
|
84
94
|
text_contents: list[TextContent] = []
|
|
85
95
|
non_text_contents = []
|
|
86
96
|
|
|
@@ -107,7 +117,9 @@ class FlockMCPTool(BaseModel):
|
|
|
107
117
|
|
|
108
118
|
def as_dspy_tool(self, server: Any) -> DSPyTool:
|
|
109
119
|
"""Wrap this tool as a DSPyTool for downstream."""
|
|
110
|
-
args, arg_type, args_desc = self._convert_input_schema_to_tool_args(
|
|
120
|
+
args, arg_type, args_desc = self._convert_input_schema_to_tool_args(
|
|
121
|
+
self.input_schema
|
|
122
|
+
)
|
|
111
123
|
|
|
112
124
|
async def func(*args, **kwargs):
|
|
113
125
|
with tracer.start_as_current_span(f"tool.{self.name}.call") as span:
|
flock/mcp/types/handlers.py
CHANGED
|
@@ -161,7 +161,9 @@ async def handle_logging_message(
|
|
|
161
161
|
metadata: dict[str, Any] = params.meta or {} # type: ignore[assignment]
|
|
162
162
|
|
|
163
163
|
str_level = "DEBUG"
|
|
164
|
-
prefix =
|
|
164
|
+
prefix = (
|
|
165
|
+
f"Message from Remote MCP Logger '{logger_name}' for server '{server_name}': "
|
|
166
|
+
)
|
|
165
167
|
|
|
166
168
|
match level:
|
|
167
169
|
case "info":
|
flock/mcp/types/types.py
CHANGED
|
@@ -128,7 +128,9 @@ class ServerParameters(BaseModel):
|
|
|
128
128
|
class StdioServerParameters(_MCPStdioServerParameters, ServerParameters):
|
|
129
129
|
"""Base Type for Stdio Server parameters."""
|
|
130
130
|
|
|
131
|
-
transport_type: Literal["stdio"] = Field(
|
|
131
|
+
transport_type: Literal["stdio"] = Field(
|
|
132
|
+
default="stdio", description="Use stdio params."
|
|
133
|
+
)
|
|
132
134
|
|
|
133
135
|
env: dict[str, str] | None = Field(
|
|
134
136
|
default_factory=get_default_env,
|
|
@@ -169,7 +171,9 @@ class StreamableHttpServerParameters(ServerParameters):
|
|
|
169
171
|
description="How long the client will wait before disconnecting from the server.",
|
|
170
172
|
)
|
|
171
173
|
|
|
172
|
-
terminate_on_close: bool = Field(
|
|
174
|
+
terminate_on_close: bool = Field(
|
|
175
|
+
default=True, description="Terminate connection on close"
|
|
176
|
+
)
|
|
173
177
|
|
|
174
178
|
auth: httpx.Auth | None = Field(default=None, description="Httpx Auth Scheme")
|
|
175
179
|
|
|
@@ -241,7 +245,9 @@ class StreamableHttpServerParameters(ServerParameters):
|
|
|
241
245
|
class SseServerParameters(ServerParameters):
|
|
242
246
|
"""Base Type for SSE Server params."""
|
|
243
247
|
|
|
244
|
-
transport_type: Literal["sse"] = Field(
|
|
248
|
+
transport_type: Literal["sse"] = Field(
|
|
249
|
+
default="sse", description="Use sse server params."
|
|
250
|
+
)
|
|
245
251
|
|
|
246
252
|
url: str | AnyUrl = Field(..., description="The url the server listens at.")
|
|
247
253
|
|