crackerjack 0.32.0__py3-none-any.whl → 0.33.0__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 crackerjack might be problematic. Click here for more details.

Files changed (34) hide show
  1. crackerjack/core/enhanced_container.py +67 -0
  2. crackerjack/core/phase_coordinator.py +183 -44
  3. crackerjack/core/workflow_orchestrator.py +459 -138
  4. crackerjack/managers/publish_manager.py +22 -5
  5. crackerjack/managers/test_command_builder.py +4 -2
  6. crackerjack/managers/test_manager.py +15 -4
  7. crackerjack/mcp/server_core.py +162 -34
  8. crackerjack/mcp/tools/core_tools.py +1 -1
  9. crackerjack/mcp/tools/execution_tools.py +8 -3
  10. crackerjack/mixins/__init__.py +5 -0
  11. crackerjack/mixins/error_handling.py +214 -0
  12. crackerjack/models/config.py +9 -0
  13. crackerjack/models/protocols.py +69 -0
  14. crackerjack/models/task.py +3 -0
  15. crackerjack/security/__init__.py +1 -1
  16. crackerjack/security/audit.py +92 -78
  17. crackerjack/services/config.py +3 -2
  18. crackerjack/services/config_merge.py +11 -5
  19. crackerjack/services/coverage_ratchet.py +22 -0
  20. crackerjack/services/git.py +37 -24
  21. crackerjack/services/initialization.py +25 -9
  22. crackerjack/services/memory_optimizer.py +477 -0
  23. crackerjack/services/parallel_executor.py +474 -0
  24. crackerjack/services/performance_benchmarks.py +292 -577
  25. crackerjack/services/performance_cache.py +443 -0
  26. crackerjack/services/performance_monitor.py +633 -0
  27. crackerjack/services/security.py +63 -0
  28. crackerjack/services/security_logger.py +9 -1
  29. crackerjack/services/terminal_utils.py +0 -0
  30. {crackerjack-0.32.0.dist-info → crackerjack-0.33.0.dist-info}/METADATA +2 -2
  31. {crackerjack-0.32.0.dist-info → crackerjack-0.33.0.dist-info}/RECORD +34 -27
  32. {crackerjack-0.32.0.dist-info → crackerjack-0.33.0.dist-info}/WHEEL +0 -0
  33. {crackerjack-0.32.0.dist-info → crackerjack-0.33.0.dist-info}/entry_points.txt +0 -0
  34. {crackerjack-0.32.0.dist-info → crackerjack-0.33.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,17 +5,34 @@ from pathlib import Path
5
5
 
6
6
  from rich.console import Console
7
7
 
8
- from crackerjack.services.filesystem import FileSystemService
9
- from crackerjack.services.security import SecurityService
8
+ from crackerjack.models.protocols import FileSystemInterface, SecurityServiceProtocol
10
9
 
11
10
 
12
11
  class PublishManagerImpl:
13
- def __init__(self, console: Console, pkg_path: Path, dry_run: bool = False) -> None:
12
+ def __init__(
13
+ self,
14
+ console: Console,
15
+ pkg_path: Path,
16
+ dry_run: bool = False,
17
+ filesystem: FileSystemInterface | None = None,
18
+ security: SecurityServiceProtocol | None = None,
19
+ ) -> None:
14
20
  self.console = console
15
21
  self.pkg_path = pkg_path
16
22
  self.dry_run = dry_run
17
- self.filesystem = FileSystemService()
18
- self.security = SecurityService()
23
+
24
+ if filesystem is None:
25
+ from crackerjack.services.filesystem import FileSystemService
26
+
27
+ filesystem = FileSystemService()
28
+
29
+ if security is None:
30
+ from crackerjack.services.security import SecurityService
31
+
32
+ security = SecurityService()
33
+
34
+ self.filesystem = filesystem
35
+ self.security = security
19
36
 
20
37
  def _run_command(
21
38
  self,
@@ -8,7 +8,7 @@ class TestCommandBuilder:
8
8
  self.pkg_path = pkg_path
9
9
 
10
10
  def build_command(self, options: OptionsProtocol) -> list[str]:
11
- cmd = ["python", "-m", "pytest"]
11
+ cmd = ["uv", "run", "python", "-m", "pytest"]
12
12
 
13
13
  self._add_coverage_options(cmd, options)
14
14
  self._add_worker_options(cmd, options)
@@ -99,7 +99,7 @@ class TestCommandBuilder:
99
99
  cmd.append(str(self.pkg_path))
100
100
 
101
101
  def build_specific_test_command(self, test_pattern: str) -> list[str]:
102
- cmd = ["python", "-m", "pytest", "-v"]
102
+ cmd = ["uv", "run", "python", "-m", "pytest", "-v"]
103
103
 
104
104
  cmd.extend(
105
105
  [
@@ -116,6 +116,8 @@ class TestCommandBuilder:
116
116
 
117
117
  def build_validation_command(self) -> list[str]:
118
118
  return [
119
+ "uv",
120
+ "run",
119
121
  "python",
120
122
  "-m",
121
123
  "pytest",
@@ -5,21 +5,32 @@ from pathlib import Path
5
5
 
6
6
  from rich.console import Console
7
7
 
8
- from crackerjack.models.protocols import OptionsProtocol
9
- from crackerjack.services.coverage_ratchet import CoverageRatchetService
8
+ from crackerjack.models.protocols import CoverageRatchetProtocol, OptionsProtocol
10
9
 
11
10
  from .test_command_builder import TestCommandBuilder
12
11
  from .test_executor import TestExecutor
13
12
 
14
13
 
15
14
  class TestManager:
16
- def __init__(self, console: Console, pkg_path: Path) -> None:
15
+ def __init__(
16
+ self,
17
+ console: Console,
18
+ pkg_path: Path,
19
+ coverage_ratchet: CoverageRatchetProtocol | None = None,
20
+ ) -> None:
17
21
  self.console = console
18
22
  self.pkg_path = pkg_path
19
23
 
20
24
  self.executor = TestExecutor(console, pkg_path)
21
25
  self.command_builder = TestCommandBuilder(pkg_path)
22
- self.coverage_ratchet = CoverageRatchetService(pkg_path, console)
26
+
27
+ if coverage_ratchet is None:
28
+ # Import here to avoid circular imports
29
+ from crackerjack.services.coverage_ratchet import CoverageRatchetService
30
+
31
+ coverage_ratchet = CoverageRatchetService(pkg_path, console)
32
+
33
+ self.coverage_ratchet = coverage_ratchet
23
34
 
24
35
  self._last_test_failures: list[str] = []
25
36
  self._progress_callback: t.Callable[[dict[str, t.Any]], None] | None = None
@@ -7,7 +7,12 @@ from typing import Final
7
7
  from rich.console import Console
8
8
 
9
9
  try:
10
- from mcp.server.fastmcp import FastMCP
10
+ import tomli
11
+ except ImportError:
12
+ tomli = None
13
+
14
+ try:
15
+ from fastmcp import FastMCP
11
16
 
12
17
  _mcp_available = True
13
18
  except ImportError:
@@ -37,6 +42,42 @@ from .tools import (
37
42
  console = Console()
38
43
 
39
44
 
45
+ def _load_mcp_config(project_path: Path) -> dict[str, t.Any]:
46
+ """Load MCP server configuration from pyproject.toml."""
47
+ pyproject_path = project_path / "pyproject.toml"
48
+
49
+ if not pyproject_path.exists() or not tomli:
50
+ return {
51
+ "http_port": 8676,
52
+ "http_host": "127.0.0.1",
53
+ "websocket_port": 8675,
54
+ "http_enabled": False,
55
+ }
56
+
57
+ try:
58
+ with pyproject_path.open("rb") as f:
59
+ pyproject_data = tomli.load(f)
60
+
61
+ crackerjack_config = pyproject_data.get("tool", {}).get("crackerjack", {})
62
+
63
+ return {
64
+ "http_port": crackerjack_config.get("mcp_http_port", 8676),
65
+ "http_host": crackerjack_config.get("mcp_http_host", "127.0.0.1"),
66
+ "websocket_port": crackerjack_config.get("mcp_websocket_port", 8675),
67
+ "http_enabled": crackerjack_config.get("mcp_http_enabled", False),
68
+ }
69
+ except Exception as e:
70
+ console.print(
71
+ f"[yellow]Warning: Failed to load MCP config from pyproject.toml: {e}[/yellow]"
72
+ )
73
+ return {
74
+ "http_port": 8676,
75
+ "http_host": "127.0.0.1",
76
+ "websocket_port": 8675,
77
+ "http_enabled": False,
78
+ }
79
+
80
+
40
81
  class MCPOptions:
41
82
  def __init__(self, **kwargs: t.Any) -> None:
42
83
  self.commit: bool = False
@@ -77,11 +118,14 @@ async def _start_websocket_server() -> bool:
77
118
  return False
78
119
 
79
120
 
80
- def create_mcp_server() -> t.Any | None:
121
+ def create_mcp_server(config: dict[str, t.Any] | None = None) -> t.Any | None:
81
122
  if not MCP_AVAILABLE or FastMCP is None:
82
123
  return None
83
124
 
84
- mcp_app = FastMCP("crackerjack - mcp-server")
125
+ if config is None:
126
+ config = {"http_port": 8676, "http_host": "127.0.0.1"}
127
+
128
+ mcp_app = FastMCP("crackerjack-mcp-server", streamable_http_path="/mcp")
85
129
 
86
130
  from crackerjack.slash_commands import get_slash_command_path
87
131
 
@@ -128,6 +172,8 @@ def handle_mcp_server_command(
128
172
  stop: bool = False,
129
173
  restart: bool = False,
130
174
  websocket_port: int | None = None,
175
+ http_mode: bool = False,
176
+ http_port: int | None = None,
131
177
  ) -> None:
132
178
  if stop or restart:
133
179
  console.print("[yellow]Stopping MCP servers...[/ yellow]")
@@ -157,7 +203,7 @@ def handle_mcp_server_command(
157
203
  if start or restart:
158
204
  console.print("[green]Starting MCP server...[/ green]")
159
205
  try:
160
- main(".", websocket_port)
206
+ main(".", websocket_port, http_mode, http_port)
161
207
  except Exception as e:
162
208
  console.print(f"[red]Failed to start MCP server: {e}[/ red]")
163
209
 
@@ -177,45 +223,111 @@ def _stop_websocket_server() -> None:
177
223
  pass
178
224
 
179
225
 
180
- def main(project_path_arg: str = ".", websocket_port: int | None = None) -> None:
226
+ def _merge_config_with_args(
227
+ mcp_config: dict[str, t.Any],
228
+ http_port: int | None,
229
+ http_mode: bool,
230
+ ) -> dict[str, t.Any]:
231
+ """Merge MCP configuration with command line arguments."""
232
+ if http_port:
233
+ mcp_config["http_port"] = http_port
234
+ if http_mode:
235
+ mcp_config["http_enabled"] = True
236
+ return mcp_config
237
+
238
+
239
+ def _setup_server_context(
240
+ project_path: Path,
241
+ websocket_port: int | None,
242
+ ) -> MCPServerContext:
243
+ """Set up and initialize the MCP server context."""
244
+ config = MCPServerConfig(
245
+ project_path=project_path,
246
+ rate_limit_config=RateLimitConfig(),
247
+ )
248
+
249
+ context = MCPServerContext(config)
250
+ context.console = console
251
+
252
+ if websocket_port:
253
+ context.websocket_server_port = websocket_port
254
+
255
+ _initialize_context(context)
256
+ return context
257
+
258
+
259
+ def _print_server_info(
260
+ project_path: Path,
261
+ mcp_config: dict[str, t.Any],
262
+ websocket_port: int | None,
263
+ http_mode: bool,
264
+ ) -> None:
265
+ """Print server startup information."""
266
+ console.print("[green]Starting Crackerjack MCP Server...[/ green]")
267
+ console.print(f"Project path: {project_path}")
268
+
269
+ if mcp_config.get("http_enabled", False) or http_mode:
270
+ console.print(
271
+ f"[cyan]HTTP Mode: http://{mcp_config['http_host']}:{mcp_config['http_port']}/mcp[/ cyan]"
272
+ )
273
+ else:
274
+ console.print("[cyan]STDIO Mode[/ cyan]")
275
+
276
+ if websocket_port:
277
+ console.print(f"WebSocket port: {websocket_port}")
278
+
279
+
280
+ def _run_mcp_server(
281
+ mcp_app: t.Any, mcp_config: dict[str, t.Any], http_mode: bool
282
+ ) -> None:
283
+ """Execute the MCP server with appropriate transport mode."""
284
+ console.print("[yellow]MCP app created, about to run...[/ yellow]")
285
+
286
+ try:
287
+ if mcp_config.get("http_enabled", False) or http_mode:
288
+ host = mcp_config.get("http_host", "127.0.0.1")
289
+ port = mcp_config.get("http_port", 8676)
290
+ mcp_app.run(transport="streamable-http", host=host, port=port)
291
+ else:
292
+ mcp_app.run()
293
+ except Exception as e:
294
+ console.print(f"[red]MCP run failed: {e}[/ red]")
295
+ import traceback
296
+
297
+ traceback.print_exc()
298
+ raise
299
+
300
+
301
+ def main(
302
+ project_path_arg: str = ".",
303
+ websocket_port: int | None = None,
304
+ http_mode: bool = False,
305
+ http_port: int | None = None,
306
+ ) -> None:
181
307
  if not MCP_AVAILABLE:
182
308
  return
183
309
 
184
310
  try:
185
311
  project_path = Path(project_path_arg).resolve()
186
312
 
187
- config = MCPServerConfig(
188
- project_path=project_path,
189
- rate_limit_config=RateLimitConfig(),
190
- )
191
-
192
- context = MCPServerContext(config)
193
- context.console = console
313
+ # Load and merge configuration
314
+ mcp_config = _load_mcp_config(project_path)
315
+ mcp_config = _merge_config_with_args(mcp_config, http_port, http_mode)
194
316
 
195
- if websocket_port:
196
- context.websocket_server_port = websocket_port
317
+ # Set up server context
318
+ _setup_server_context(project_path, websocket_port)
197
319
 
198
- _initialize_context(context)
199
-
200
- mcp_app = create_mcp_server()
320
+ # Create MCP server
321
+ mcp_app = create_mcp_server(mcp_config)
201
322
  if not mcp_app:
202
323
  console.print("[red]Failed to create MCP server[/ red]")
203
324
  return
204
325
 
205
- console.print("[green]Starting Crackerjack MCP Server...[/ green]")
206
- console.print(f"Project path: {project_path}")
207
- if websocket_port:
208
- console.print(f"WebSocket port: {websocket_port}")
326
+ # Print server information
327
+ _print_server_info(project_path, mcp_config, websocket_port, http_mode)
209
328
 
210
- console.print("[yellow]MCP app created, about to run...[/ yellow]")
211
- try:
212
- mcp_app.run()
213
- except Exception as e:
214
- console.print(f"[red]MCP run failed: {e}[/ red]")
215
- import traceback
216
-
217
- traceback.print_exc()
218
- raise
329
+ # Run the server
330
+ _run_mcp_server(mcp_app, mcp_config, http_mode)
219
331
 
220
332
  except KeyboardInterrupt:
221
333
  console.print("Server stopped by user")
@@ -232,7 +344,23 @@ def main(project_path_arg: str = ".", websocket_port: int | None = None) -> None
232
344
  if __name__ == "__main__":
233
345
  import sys
234
346
 
235
- project_path = sys.argv[1] if len(sys.argv) > 1 else "."
236
- websocket_port = int(sys.argv[2]) if len(sys.argv) > 2 else None
237
-
238
- main(project_path, websocket_port)
347
+ # Initialize defaults
348
+ project_path = "."
349
+ websocket_port = None
350
+ http_mode = "--http" in sys.argv
351
+ http_port = None
352
+
353
+ # Parse project path from non-flag arguments
354
+ non_flag_args = [arg for arg in sys.argv[1:] if not arg.startswith("--")]
355
+ if non_flag_args:
356
+ project_path = non_flag_args[0]
357
+ if len(non_flag_args) > 1 and non_flag_args[1].isdigit():
358
+ websocket_port = int(non_flag_args[1])
359
+
360
+ # Parse HTTP port flag
361
+ if "--http-port" in sys.argv:
362
+ port_idx = sys.argv.index("--http-port")
363
+ if port_idx + 1 < len(sys.argv):
364
+ http_port = int(sys.argv[port_idx + 1])
365
+
366
+ main(project_path, websocket_port, http_mode, http_port)
@@ -186,7 +186,7 @@ def _execute_init_stage(orchestrator) -> bool:
186
186
 
187
187
  init_service = InitializationService(console, filesystem, git_service, pkg_path)
188
188
 
189
- results = init_service.initialize_project(target_path=Path.cwd())
189
+ results = init_service.initialize_project_full(target_path=Path.cwd())
190
190
 
191
191
  return results.get("success", False)
192
192
 
@@ -161,9 +161,14 @@ def _execute_initialization(target_path: t.Any, force: bool) -> dict[str, t.Any]
161
161
 
162
162
  console = Console()
163
163
 
164
- return InitializationService(console, target_path).initialize_project(
165
- force_update=force
166
- )
164
+ from crackerjack.services.filesystem import FileSystemService
165
+ from crackerjack.services.git import GitService
166
+
167
+ filesystem = FileSystemService()
168
+ git_service = GitService()
169
+ return InitializationService(
170
+ console, filesystem, git_service, target_path
171
+ ).initialize_project_full(force=force)
167
172
 
168
173
 
169
174
  def _create_init_error_response(message: str) -> str:
@@ -0,0 +1,5 @@
1
+ """Common mixins for crackerjack components."""
2
+
3
+ from .error_handling import ErrorHandlingMixin
4
+
5
+ __all__ = ["ErrorHandlingMixin"]
@@ -0,0 +1,214 @@
1
+ """Common error handling patterns for crackerjack components."""
2
+
3
+ import subprocess
4
+ import typing as t
5
+ from pathlib import Path
6
+
7
+ from rich.console import Console
8
+
9
+
10
+ class ErrorHandlingMixin:
11
+ """Mixin providing common error handling patterns for crackerjack components."""
12
+
13
+ def __init__(self) -> None:
14
+ # These attributes should be provided by the class using the mixin
15
+ self.console: Console
16
+ self.logger: t.Any # Logger instance
17
+
18
+ def handle_subprocess_error(
19
+ self,
20
+ error: Exception,
21
+ command: list[str],
22
+ operation_name: str,
23
+ critical: bool = False,
24
+ ) -> bool:
25
+ """Handle subprocess errors with consistent logging and user feedback.
26
+
27
+ Args:
28
+ error: The exception that occurred
29
+ command: The command that failed
30
+ operation_name: Human-readable name of the operation
31
+ critical: Whether this is a critical error that should stop execution
32
+
33
+ Returns:
34
+ False to indicate failure
35
+ """
36
+ error_msg = f"{operation_name} failed: {error}"
37
+
38
+ # Log the error
39
+ if hasattr(self, "logger") and self.logger:
40
+ self.logger.error(
41
+ error_msg,
42
+ command=" ".join(command),
43
+ error_type=type(error).__name__,
44
+ critical=critical,
45
+ )
46
+
47
+ # Display user-friendly error message
48
+ if critical:
49
+ self.console.print(f"[red]🚨 CRITICAL: {error_msg}[/red]")
50
+ else:
51
+ self.console.print(f"[red]❌ {error_msg}[/red]")
52
+
53
+ return False
54
+
55
+ def handle_file_operation_error(
56
+ self,
57
+ error: Exception,
58
+ file_path: Path,
59
+ operation: str,
60
+ critical: bool = False,
61
+ ) -> bool:
62
+ """Handle file operation errors with consistent logging and user feedback.
63
+
64
+ Args:
65
+ error: The exception that occurred
66
+ file_path: The file that caused the error
67
+ operation: The operation that failed (e.g., "read", "write", "delete")
68
+ critical: Whether this is a critical error that should stop execution
69
+
70
+ Returns:
71
+ False to indicate failure
72
+ """
73
+ error_msg = f"Failed to {operation} {file_path}: {error}"
74
+
75
+ # Log the error
76
+ if hasattr(self, "logger") and self.logger:
77
+ self.logger.error(
78
+ error_msg,
79
+ file_path=str(file_path),
80
+ operation=operation,
81
+ error_type=type(error).__name__,
82
+ critical=critical,
83
+ )
84
+
85
+ # Display user-friendly error message
86
+ if critical:
87
+ self.console.print(f"[red]🚨 CRITICAL: {error_msg}[/red]")
88
+ else:
89
+ self.console.print(f"[red]❌ {error_msg}[/red]")
90
+
91
+ return False
92
+
93
+ def handle_timeout_error(
94
+ self,
95
+ operation_name: str,
96
+ timeout_seconds: float,
97
+ command: list[str] | None = None,
98
+ ) -> bool:
99
+ """Handle timeout errors with consistent logging and user feedback.
100
+
101
+ Args:
102
+ operation_name: Human-readable name of the operation
103
+ timeout_seconds: The timeout that was exceeded
104
+ command: Optional command that timed out
105
+
106
+ Returns:
107
+ False to indicate failure
108
+ """
109
+ error_msg = f"{operation_name} timed out after {timeout_seconds}s"
110
+
111
+ # Log the error
112
+ if hasattr(self, "logger") and self.logger:
113
+ self.logger.warning(
114
+ error_msg,
115
+ timeout=timeout_seconds,
116
+ command=" ".join(command) if command else None,
117
+ )
118
+
119
+ # Display user-friendly error message
120
+ self.console.print(f"[yellow]⏰ {error_msg}[/yellow]")
121
+
122
+ return False
123
+
124
+ def log_operation_success(
125
+ self,
126
+ operation_name: str,
127
+ details: dict[str, t.Any] | None = None,
128
+ ) -> None:
129
+ """Log successful operations with consistent formatting.
130
+
131
+ Args:
132
+ operation_name: Human-readable name of the operation
133
+ details: Optional additional details to log
134
+ """
135
+ if hasattr(self, "logger") and self.logger:
136
+ self.logger.info(
137
+ f"{operation_name} completed successfully", **(details or {})
138
+ )
139
+
140
+ def validate_required_tools(
141
+ self,
142
+ tools: dict[str, str],
143
+ operation_name: str,
144
+ ) -> bool:
145
+ """Validate that required external tools are available.
146
+
147
+ Args:
148
+ tools: Dict mapping tool names to their expected commands
149
+ operation_name: Name of operation requiring the tools
150
+
151
+ Returns:
152
+ True if all tools are available, False otherwise
153
+ """
154
+ missing_tools = []
155
+
156
+ for tool_name, command in tools.items():
157
+ try:
158
+ subprocess.run(
159
+ [command, "--version"],
160
+ capture_output=True,
161
+ check=True,
162
+ timeout=5,
163
+ )
164
+ except (
165
+ subprocess.CalledProcessError,
166
+ subprocess.TimeoutExpired,
167
+ FileNotFoundError,
168
+ ):
169
+ missing_tools.append(tool_name)
170
+
171
+ if missing_tools:
172
+ error_msg = f"Missing required tools for {operation_name}: {', '.join(missing_tools)}"
173
+
174
+ if hasattr(self, "logger") and self.logger:
175
+ self.logger.error(
176
+ error_msg,
177
+ missing_tools=missing_tools,
178
+ operation=operation_name,
179
+ )
180
+
181
+ self.console.print(f"[red]❌ {error_msg}[/red]")
182
+ return False
183
+
184
+ return True
185
+
186
+ def safe_get_attribute(
187
+ self,
188
+ obj: t.Any,
189
+ attribute: str,
190
+ default: t.Any = None,
191
+ operation_name: str = "attribute access",
192
+ ) -> t.Any:
193
+ """Safely get an attribute with error handling.
194
+
195
+ Args:
196
+ obj: Object to get attribute from
197
+ attribute: Name of attribute to get
198
+ default: Default value if attribute doesn't exist
199
+ operation_name: Name of operation for error logging
200
+
201
+ Returns:
202
+ The attribute value or default
203
+ """
204
+ try:
205
+ return getattr(obj, attribute, default)
206
+ except Exception as e:
207
+ if hasattr(self, "logger") and self.logger:
208
+ self.logger.warning(
209
+ f"Error accessing {attribute} during {operation_name}: {e}",
210
+ attribute=attribute,
211
+ operation=operation_name,
212
+ error_type=type(e).__name__,
213
+ )
214
+ return default
@@ -79,6 +79,14 @@ class EnterpriseConfig:
79
79
  organization: str | None = None
80
80
 
81
81
 
82
+ @dataclass
83
+ class MCPServerConfig:
84
+ http_port: int = 8676
85
+ http_host: str = "127.0.0.1"
86
+ websocket_port: int = 8675
87
+ http_enabled: bool = False
88
+
89
+
82
90
  @dataclass
83
91
  class WorkflowOptions:
84
92
  cleaning: CleaningConfig = field(default_factory=CleaningConfig)
@@ -91,3 +99,4 @@ class WorkflowOptions:
91
99
  progress: ProgressConfig = field(default_factory=ProgressConfig)
92
100
  cleanup: CleanupConfig = field(default_factory=CleanupConfig)
93
101
  enterprise: EnterpriseConfig = field(default_factory=EnterpriseConfig)
102
+ mcp_server: MCPServerConfig = field(default_factory=MCPServerConfig)