flock-core 0.5.0b16__py3-none-any.whl → 0.5.0b17__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.

@@ -1,6 +1,11 @@
1
- """Factory for creating pre-configured Flock agents."""
1
+ """Factory for creating pre-configured Flock agents.
2
+
3
+ Deprecated: Prefer explicit `DefaultAgent` class for new code. This factory
4
+ remains as a thin adapter to ease migration and preserve backward compatibility.
5
+ """
2
6
 
3
7
  import os
8
+ import warnings
4
9
  from collections.abc import Callable
5
10
  from pathlib import Path
6
11
  from typing import Any, Literal
@@ -12,12 +17,13 @@ from flock.components.utility.metrics_utility_component import (
12
17
  MetricsUtilityComponent,
13
18
  MetricsUtilityConfig,
14
19
  )
20
+ from flock.core.agent.default_agent import DefaultAgent
15
21
 
16
22
  # New unified components imported locally to avoid circular imports
17
- from flock.core.config.flock_agent_config import FlockAgentConfig
18
23
  from flock.core.config.scheduled_agent_config import ScheduledAgentConfig
19
24
  from flock.core.flock_agent import DynamicStr, FlockAgent
20
25
  from flock.core.logging.formatters.themes import OutputTheme
26
+ from flock.core.logging.logging import get_logger
21
27
  from flock.core.mcp.flock_mcp_server import FlockMCPServer
22
28
  from flock.core.mcp.mcp_config import (
23
29
  FlockMCPCachingConfiguration,
@@ -207,6 +213,8 @@ class FlockFactory:
207
213
  tool_result_cache_ttl=100,
208
214
  description: str | Callable[..., str] | None = None,
209
215
  alert_latency_threshold_ms: int = 30000,
216
+ tool_whitelist: list[str] | None = None,
217
+ allow_all_tools: bool = True,
210
218
  ) -> FlockMCPServer:
211
219
  """Create a default MCP Server with common modules.
212
220
 
@@ -215,6 +223,26 @@ class FlockFactory:
215
223
  - SSE-Server (specify "sse" in type)
216
224
  - Stdio-Server (specify "stdio" in type)
217
225
  - Websockets-Server (specifiy "websockets" in type)
226
+
227
+ Args:
228
+ name: Unique identifier for the MCP server
229
+ connection_params: Connection configuration (StdioParams, SSEParams, etc.)
230
+ tool_whitelist: List of tool names to allow from this server. If provided,
231
+ only tools with these names will be available. Used with
232
+ allow_all_tools=False for strict filtering. Agent-level
233
+ filtering is generally preferred over server-level filtering.
234
+ allow_all_tools: Whether to allow all tools from the server. When True
235
+ (default), all tools are available. When False, only tools
236
+ in tool_whitelist (if provided) are available.
237
+ Other args: Various configuration options for caching, callbacks, etc.
238
+
239
+ Returns:
240
+ FlockMCPServer: Configured MCP server instance
241
+
242
+ Note:
243
+ For security and flexibility, prefer using agent-level tool_whitelist
244
+ over server-level filtering. This allows different agents to access
245
+ different tool subsets from the same server.
218
246
  """
219
247
  # infer server type from the pydantic model class
220
248
  if isinstance(connection_params, FlockFactory.StdioParams):
@@ -251,6 +279,7 @@ class FlockFactory:
251
279
  tools_enabled=enable_tools_feature,
252
280
  prompts_enabled=enable_prompts_feature,
253
281
  sampling_enabled=enable_sampling_feature,
282
+ tool_whitelist=tool_whitelist,
254
283
  )
255
284
  callback_config = FlockMCPCallbackConfiguration(
256
285
  sampling_callback=sampling_callback,
@@ -319,6 +348,7 @@ class FlockFactory:
319
348
  feature_config=feature_config,
320
349
  caching_config=caching_config,
321
350
  callback_config=callback_config,
351
+ allow_all_tools=allow_all_tools,
322
352
  )
323
353
 
324
354
  elif server_kind == "sse":
@@ -342,6 +372,7 @@ class FlockFactory:
342
372
  feature_config=feature_config,
343
373
  caching_config=caching_config,
344
374
  callback_config=callback_config,
375
+ allow_all_tools=allow_all_tools,
345
376
  )
346
377
 
347
378
  elif server_kind == "websockets":
@@ -361,6 +392,7 @@ class FlockFactory:
361
392
  feature_config=feature_config,
362
393
  caching_config=caching_config,
363
394
  callback_config=callback_config,
395
+ allow_all_tools=allow_all_tools,
364
396
  )
365
397
 
366
398
  else:
@@ -412,86 +444,41 @@ class FlockFactory:
412
444
  next_agent: DynamicStr | None = None,
413
445
  temporal_activity_config: TemporalActivityConfig | None = None,
414
446
  ) -> FlockAgent:
415
- """Creates a default FlockAgent using unified component architecture.
447
+ """Create a default FlockAgent.
416
448
 
417
- The default agent includes the following unified components:
418
- - DeclarativeEvaluationComponent (core LLM evaluation)
419
- - OutputUtilityComponent (result formatting and display)
420
- - MetricsUtilityComponent (performance tracking)
421
-
422
- This provides a complete, production-ready agent with sensible defaults.
449
+ Deprecated: Use `DefaultAgent(...)` instead. This method now delegates to
450
+ `DefaultAgent` and emits an optional one-time deprecation warning if the
451
+ environment variable `FLOCK_WARN_FACTORY_DEPRECATION` is truthy (default).
423
452
  """
424
- # Import unified components locally to avoid circular imports
425
- from flock.components.evaluation.declarative_evaluation_component import (
426
- DeclarativeEvaluationComponent,
427
- DeclarativeEvaluationConfig,
428
- )
429
- from flock.components.utility.metrics_utility_component import (
430
- MetricsUtilityComponent,
431
- MetricsUtilityConfig,
432
- )
433
- from flock.components.utility.output_utility_component import (
434
- OutputUtilityComponent,
435
- OutputUtilityConfig,
436
- )
437
-
438
- if model and "gpt-oss" in model:
439
- temperature = 1.0
440
- max_tokens = 32768
453
+ _maybe_warn_factory_deprecation()
441
454
 
442
- # Create evaluation component
443
- eval_config = DeclarativeEvaluationConfig(
455
+ return DefaultAgent(
456
+ name=name,
457
+ description=description,
444
458
  model=model,
459
+ input=input,
460
+ output=output,
461
+ tools=tools,
462
+ servers=servers,
445
463
  use_cache=use_cache,
446
- max_tokens=max_tokens,
447
464
  temperature=temperature,
465
+ max_tokens=max_tokens,
448
466
  max_tool_calls=max_tool_calls,
449
467
  max_retries=max_retries,
450
468
  stream=stream,
451
469
  include_thought_process=include_thought_process,
452
470
  include_reasoning=include_reasoning,
453
- )
454
- evaluator = DeclarativeEvaluationComponent(
455
- name="default_evaluator", config=eval_config
456
- )
457
-
458
- # Create output utility component
459
- output_config = OutputUtilityConfig(
460
- render_table=enable_rich_tables,
461
- theme=output_theme,
471
+ enable_rich_tables=enable_rich_tables,
472
+ output_theme=output_theme,
462
473
  no_output=no_output,
463
474
  print_context=print_context,
464
- )
465
- output_component = OutputUtilityComponent(
466
- name="output_formatter", config=output_config
467
- )
468
-
469
- # Create metrics utility component
470
- metrics_config = MetricsUtilityConfig(
471
- latency_threshold_ms=alert_latency_threshold_ms
472
- )
473
- metrics_component = MetricsUtilityComponent(
474
- name="metrics_tracker", config=metrics_config
475
- )
476
-
477
- # Create agent with unified components
478
- agent = FlockAgent(
479
- name=name,
480
- input=input,
481
- output=output,
482
- tools=tools,
483
- servers=servers,
484
- model=model,
485
- description=description,
486
- components=[evaluator, output_component, metrics_component],
487
- config=FlockAgentConfig(write_to_file=write_to_file,
488
- wait_for_input=wait_for_input),
475
+ write_to_file=write_to_file,
476
+ wait_for_input=wait_for_input,
477
+ alert_latency_threshold_ms=alert_latency_threshold_ms,
489
478
  next_agent=next_agent,
490
479
  temporal_activity_config=temporal_activity_config,
491
480
  )
492
481
 
493
- return agent
494
-
495
482
  @staticmethod
496
483
  def create_scheduled_agent(
497
484
  name: str,
@@ -518,22 +505,47 @@ class FlockFactory:
518
505
  **kwargs,
519
506
  )
520
507
 
521
- agent = (
522
- FlockFactory.create_default_agent( # Reuse your existing factory
523
- name=name,
524
- description=description,
525
- model=model,
526
- input="trigger_time: str | Time of scheduled execution",
527
- output=output,
528
- tools=tools,
529
- servers=servers,
530
- temporal_activity_config=temporal_activity_config,
531
- use_cache=use_cache,
532
- temperature=temperature,
533
- next_agent=next_agent,
534
- **kwargs,
535
- )
508
+ agent = DefaultAgent(
509
+ name=name,
510
+ description=description,
511
+ model=model,
512
+ input="trigger_time: str | Time of scheduled execution",
513
+ output=output,
514
+ tools=tools,
515
+ servers=servers,
516
+ temporal_activity_config=temporal_activity_config,
517
+ use_cache=use_cache,
518
+ temperature=temperature,
519
+ next_agent=next_agent,
520
+ **kwargs,
536
521
  )
537
522
  agent.config = agent_config # Assign the scheduled agent config
538
523
 
539
524
  return agent
525
+
526
+
527
+ # ---- one-time deprecation warning helper ----
528
+ _FACTORY_DEPRECATION_WARNED = False
529
+ _factory_logger = get_logger("core.factory")
530
+
531
+
532
+ def _maybe_warn_factory_deprecation() -> None: # pragma: no cover - side-effect
533
+ global _FACTORY_DEPRECATION_WARNED
534
+ if _FACTORY_DEPRECATION_WARNED:
535
+ return
536
+ flag = os.getenv("FLOCK_WARN_FACTORY_DEPRECATION", "1").strip()
537
+ enabled = flag not in {"0", "false", "False", "off", "OFF"}
538
+ if not enabled:
539
+ _FACTORY_DEPRECATION_WARNED = True
540
+ return
541
+ msg = (
542
+ "FlockFactory.create_default_agent is deprecated and will be removed in a future release. "
543
+ "Please use DefaultAgent(...) instead. Set FLOCK_WARN_FACTORY_DEPRECATION=0 to disable this notice."
544
+ )
545
+ # Log and emit a warnings.warn once
546
+ try:
547
+ _factory_logger.warning(msg)
548
+ except Exception:
549
+ pass
550
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
551
+ _FACTORY_DEPRECATION_WARNED = True
@@ -1,8 +1,8 @@
1
1
  """This module sets up OpenTelemetry tracing for a service."""
2
2
 
3
+ import os
3
4
  import sys
4
5
 
5
- import os
6
6
  from opentelemetry import trace
7
7
  from opentelemetry.sdk.resources import Resource
8
8
  from opentelemetry.sdk.trace import TracerProvider
@@ -77,7 +77,9 @@ class TelemetryConfig:
77
77
  return False
78
78
  try:
79
79
  # If a provider is already installed (typically by user/tests), don't override it
80
- from opentelemetry.sdk.trace import TracerProvider as SDKTracerProvider # type: ignore
80
+ from opentelemetry.sdk.trace import (
81
+ TracerProvider as SDKTracerProvider, # type: ignore
82
+ )
81
83
 
82
84
  current = trace.get_tracer_provider()
83
85
  if isinstance(current, SDKTracerProvider):
@@ -184,6 +186,9 @@ class TelemetryConfig:
184
186
  sys.__excepthook__(exc_type, exc_value, exc_traceback)
185
187
  return
186
188
 
189
+ if not self.global_tracer:
190
+ return
191
+
187
192
  # Use OpenTelemetry to record the exception
188
193
  with self.global_tracer.start_as_current_span(
189
194
  "UnhandledException"
@@ -153,6 +153,10 @@ class FlockMCPServer(BaseModel, Serializable, ABC):
153
153
  self.client_manager = await self.initialize()
154
154
  self.initialized = True
155
155
  await self.post_init()
156
+ if not self.config.allow_all_tools:
157
+ whitelist = self.config.feature_config.tool_whitelist
158
+ if whitelist is not None and len(whitelist) > 0 and name not in whitelist:
159
+ return None
156
160
  async with self.condition:
157
161
  try:
158
162
  additional_params: dict[str, Any] = {
@@ -217,6 +221,14 @@ class FlockMCPServer(BaseModel, Serializable, ABC):
217
221
  run_id=run_id,
218
222
  additional_params=additional_params,
219
223
  )
224
+ # filtering based on whitelist
225
+ if not self.config.allow_all_tools:
226
+ whitelist = self.config.feature_config.tool_whitelist
227
+ filtered_results: list[FlockMCPTool] = []
228
+ for tool in result:
229
+ if tool.name in whitelist:
230
+ filtered_results.append(tool)
231
+ result = filtered_results
220
232
  converted_tools = [
221
233
  t.as_dspy_tool(server=self) for t in result
222
234
  ]
@@ -263,6 +275,13 @@ class FlockMCPServer(BaseModel, Serializable, ABC):
263
275
  )
264
276
  with tracer.start_as_current_span("server.pre_init") as span:
265
277
  span.set_attribute("server.name", self.config.name)
278
+ # run whitelist checks
279
+ whitelist = self.config.feature_config
280
+ if whitelist is not None and len(whitelist) > 0:
281
+ self.config.feature_config = False
282
+ else:
283
+ # if the whitelist is none..
284
+ self.config.allow_all_tools = True
266
285
  try:
267
286
  for module in self.get_enabled_components():
268
287
  await module.on_pre_server_init(self)
@@ -3,6 +3,7 @@
3
3
  from typing import Any, TypeVar
4
4
 
5
5
  from dspy import Tool as DSPyTool
6
+ from dspy.adapters.types.tool import convert_input_schema_to_tool_args
6
7
  from mcp import Tool
7
8
  from mcp.types import CallToolResult, TextContent, ToolAnnotations
8
9
  from opentelemetry import trace
@@ -75,59 +76,14 @@ class FlockMCPTool(BaseModel):
75
76
  annotations=instance.annotations,
76
77
  )
77
78
 
78
- def resolve_json_schema_reference(self, schema: dict) -> dict:
79
- """Recursively resolve json model schema, expanding all references."""
80
- if "$defs" not in schema and "definitions" not in schema:
81
- return schema
82
-
83
- def resolve_refs(obj: Any) -> Any:
84
- if not isinstance(obj, dict[list, list]):
85
- return obj
86
- if isinstance(obj, dict) and "$ref" in obj:
87
- # ref_path = obj["$ref"].split("/")[-1]
88
- return {resolve_refs(v) for k, v in obj.items()}
89
-
90
- return [resolve_refs(item) for item in obj]
91
-
92
- resolved_schema = resolve_refs(schema)
93
-
94
- resolved_schema.pop("$defs", None)
95
- return resolved_schema
96
-
97
- def _convert_input_schema_to_tool_args(
98
- self, input_schema: dict[str, Any]
99
- ) -> tuple[dict[str, Any], dict[str, type], dict[str, str]]:
100
- """Convert an input schema to tool arguments compatible with Dspy Tool.
101
-
102
- Args:
103
- schema: an input schema describing the tool's input parameters
104
-
105
- Returns:
106
- A tuple of (args, arg_types, arg_desc) for Dspy Tool definition
107
- """
108
- args, arg_types, arg_desc = {}, {}, {}
109
- properties = input_schema.get("properties")
110
- if properties is None:
111
- return args, arg_types, arg_desc
112
-
113
- required = input_schema.get("required", [])
114
-
115
- defs = input_schema.get("$defs", {})
116
-
117
- for name, prop in properties.items():
118
- if len(defs) > 0:
119
- prop = self.resolve_json_schema_reference(
120
- {"$defs": defs, **prop}
121
- )
122
-
123
- args[name] = prop
124
-
125
- arg_types[name] = TYPE_MAPPING.get(prop.get("type"), Any)
126
- arg_desc[name] = prop.get("description", "No description provided")
127
- if name in required:
128
- arg_desc[name] += " (Required)"
129
-
130
- return args, arg_types, arg_desc
79
+ # Use DSPy's converter for JSON Schema → Tool args to stay aligned with DSPy.
80
+ def _convert_input_schema_to_tool_args(self, input_schema: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any], dict[str, str]]:
81
+ try:
82
+ return convert_input_schema_to_tool_args(input_schema)
83
+ except Exception as e: # pragma: no cover - defensive
84
+ logger.error("Failed to convert MCP tool schema to DSPy tool args: %s", e)
85
+ # Fallback to empty definitions to avoid breaking execution
86
+ return {}, {}, {}
131
87
 
132
88
  def _convert_mcp_tool_result(
133
89
  self, call_tool_result: CallToolResult
@@ -302,22 +302,31 @@ class FlockMCPFeatureConfiguration(BaseModel, Serializable):
302
302
  """Base Configuration Class for switching MCP Features on and off."""
303
303
 
304
304
  roots_enabled: bool = Field(
305
- default=False,
305
+ default=True,
306
306
  description="Whether or not the Roots feature is enabled for this client.",
307
307
  )
308
308
 
309
309
  sampling_enabled: bool = Field(
310
- default=False,
310
+ default=True,
311
311
  description="Whether or not the Sampling feature is enabled for this client.",
312
312
  )
313
313
 
314
314
  tools_enabled: bool = Field(
315
- default=False,
315
+ default=True,
316
316
  description="Whether or not the Tools feature is enabled for this client.",
317
317
  )
318
318
 
319
+ tool_whitelist: list[str] | None = Field(
320
+ default=None,
321
+ description="Whitelist of tool names that are enabled for this MCP server. "
322
+ "If provided, only tools with names in this list will be available "
323
+ "from this server. Used in conjunction with allow_all_tools setting. "
324
+ "Note: Agent-level tool filtering is generally preferred over "
325
+ "server-level filtering for better granular control."
326
+ )
327
+
319
328
  prompts_enabled: bool = Field(
320
- default=False,
329
+ default=True,
321
330
  description="Whether or not the Prompts feature is enabled for this client.",
322
331
  )
323
332
 
@@ -357,6 +366,15 @@ class FlockMCPConfiguration(BaseModel, Serializable):
357
366
  ..., description="Name of the server the client connects to."
358
367
  )
359
368
 
369
+ allow_all_tools: bool = Field(
370
+ default=True,
371
+ description="Whether to allow usage of all tools from this MCP server. "
372
+ "When True (default), all tools are available unless restricted "
373
+ "by tool_whitelist. When False, tool access is controlled entirely "
374
+ "by tool_whitelist (if provided). Setting to False with no whitelist "
375
+ "will block all tools from this server."
376
+ )
377
+
360
378
  connection_config: FlockMCPConnectionConfiguration = Field(
361
379
  ..., description="MCP Connection Configuration for a client."
362
380
  )