From a90f9634aafef5fd998cbdf9b2e8c3d1149b3468 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 26 Jun 2026 10:16:46 -0400 Subject: [PATCH] refactor(mcp_client): merge MCP config classes + load_mcp_config from models.py Per the 4-criteria decision rule: MCP config classes (MCPServerConfig, MCPConfiguration, VectorStoreConfig, RAGConfig) + load_mcp_config are used by mcp_client + api_hooks + app_controller (3 systems) but they are tightly coupled to the MCP subsystem's data layer. The test file tests/test_mcp_config.py exists. Per the v2 spec: MERGE into the existing src/mcp_client.py (the destination file IS the MCP subsystem; the data layer belongs with the dispatcher). This commit: 1. Adds MCPServerConfig + MCPConfiguration + VectorStoreConfig + RAGConfig + load_mcp_config class/function definitions to src/mcp_client.py at the top (after the imports + before the mutating tools sentinel). 2. Removes the same class defs from src/models.py. 3. Adds lazy re-export via the existing __getattr__ in src/models.py (EAGER would cycle: mcp_client was previously accessing them via 'models.X'; eager re-export would deadlock). 4. Updates src/mcp_client.py internal references: - 'def __init__(self, config: models.MCPServerConfig)' -> 'MCPServerConfig' - 'async def add_server(self, config: models.MCPServerConfig)' -> 'MCPServerConfig' Verification: VC8 (MCP config classes + load_mcp_config) from src.mcp_client import MCPServerConfig, MCPConfiguration, VectorStoreConfig, RAGConfig, load_mcp_config # OK from src.models import MCPServerConfig, MCPConfiguration, VectorStoreConfig, RAGConfig, load_mcp_config # OK (lazy) identity check: True for all 5 Tests verified (4/4 PASS): tests/test_mcp_config.py (3 tests) tests/test_mcp_client_beads.py (1 test) Consumer check (lazy __getattr__ keeps these working): src/app_controller.py: models.MCPConfiguration, models.RAGConfig, models.load_mcp_config (7+ sites) src/rag_engine.py: models.RAGConfig (1 site) All resolve via the lazy __getattr__. --- src/mcp_client.py | 129 +++++++++++++++++++++++++++++++++++++-- src/models.py | 150 ++++------------------------------------------ 2 files changed, 135 insertions(+), 144 deletions(-) diff --git a/src/mcp_client.py b/src/mcp_client.py index cd1dfe1b..8f409d96 100644 --- a/src/mcp_client.py +++ b/src/mcp_client.py @@ -62,9 +62,10 @@ import subprocess import urllib.request import urllib.parse -from html.parser import HTMLParser -from pathlib import Path -from typing import Optional, Callable, Any, cast +from dataclasses import dataclass, field +from html.parser import HTMLParser +from pathlib import Path +from typing import Dict, List, Optional, Callable, Any, cast from scripts import py_struct_tools @@ -73,7 +74,123 @@ from src import models from src import outline_tool from src import summarize from src import mcp_tool_specs -from src.result_types import ErrorInfo, ErrorKind, NilPath, Result +from src.result_types import ErrorInfo, ErrorKind, NilPath, Result +from src.type_aliases import Metadata + + +# --------------------------------------------------------------------------- MCP config dataclasses +# Moved from src/models.py in module_taxonomy_refactor_20260627 Phase 3i. +# These are the data layer of the MCP subsystem; they belong here. + +@dataclass +class MCPServerConfig: + name: str + command: Optional[str] = None + args: List[str] = field(default_factory=list) + url: Optional[str] = None + auto_start: bool = False + + def to_dict(self) -> Metadata: + res = {'auto_start': self.auto_start} + if self.command: res['command'] = self.command + if self.args: res['args'] = self.args + if self.url: res['url'] = self.url + return res + + @classmethod + def from_dict(cls, name: str, data: Metadata) -> "MCPServerConfig": + return cls( + name = name, + command = data.get('command'), + args = data.get('args', []), + url = data.get('url'), + auto_start = data.get('auto_start', False), + ) + + +@dataclass +class MCPConfiguration: + mcpServers: Dict[str, MCPServerConfig] = field(default_factory=dict) + + def to_dict(self) -> Metadata: + return {'mcpServers': {name: cfg.to_dict() for name, cfg in self.mcpServers.items()}} + + @classmethod + def from_dict(cls, data: Metadata) -> "MCPConfiguration": + raw_servers = data.get('mcpServers', {}) + parsed_servers = {name: MCPServerConfig.from_dict(name, cfg) for name, cfg in raw_servers.items()} + return cls(mcpServers=parsed_servers) + + +@dataclass +class VectorStoreConfig: + provider: str + url: Optional[str] = None + api_key: Optional[str] = None + collection_name: str = 'manual_slop' + mcp_server: Optional[str] = None + mcp_tool: Optional[str] = None + + def to_dict(self) -> Metadata: + return { + "provider": self.provider, + "url": self.url, + "api_key": self.api_key, + "collection_name": self.collection_name, + "mcp_server": self.mcp_server, + "mcp_tool": self.mcp_tool, + } + + @classmethod + def from_dict(cls, data: Metadata) -> "VectorStoreConfig": + return cls( + provider = data["provider"], + url = data.get("url"), + api_key = data.get("api_key"), + collection_name = data.get("collection_name", "manual_slop"), + mcp_server = data.get("mcp_server"), + mcp_tool = data.get("mcp_tool"), + ) + + +@dataclass +class RAGConfig: + enabled: bool = False + vector_store: VectorStoreConfig = field(default_factory=lambda: VectorStoreConfig(provider='mock')) + embedding_provider: str = 'gemini' + chunk_size: int = 1000 + chunk_overlap: int = 200 + + def to_dict(self) -> Metadata: + return { + "enabled": self.enabled, + "vector_store": self.vector_store.to_dict(), + "embedding_provider": self.embedding_provider, + "chunk_size": self.chunk_size, + "chunk_overlap": self.chunk_overlap, + } + + @classmethod + def from_dict(cls, data: Metadata) -> "RAGConfig": + return cls( + enabled = data.get("enabled", False), + vector_store = VectorStoreConfig.from_dict(data.get("vector_store", {"provider": "mock"})), + embedding_provider = data.get("embedding_provider", "gemini"), + chunk_size = data.get("chunk_size", 1000), + chunk_overlap = data.get("chunk_overlap", 200), + ) + + +def load_mcp_config(path: str) -> MCPConfiguration: + if not os.path.exists(path): + return MCPConfiguration() + with open(path, 'r', encoding='utf-8') as f: + try: + data = json.load(f) + return MCPConfiguration.from_dict(data) + except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e: + _mcp_err = Result(data=MCPConfiguration(), errors=[ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=f"failed to load MCP config: {e}", source="mcp_client.load_mcp_config", original=e)]) + return _mcp_err.data # ------------------------------------------------------------------ mutating tools sentinel @@ -1621,7 +1738,7 @@ def get_ui_performance() -> str: # ------------------------------------------------------------------ tool dispatch class StdioMCPServer: - def __init__(self, config: models.MCPServerConfig): + def __init__(self, config: MCPServerConfig): self.config = config self.name = config.name self.proc = None @@ -1720,7 +1837,7 @@ class ExternalMCPManager: """Initialize the manager with an empty server registry.""" self.servers = {} - async def add_server(self, config: models.MCPServerConfig): + async def add_server(self, config: MCPServerConfig): """ Add and start a new MCP server from a configuration object. [C: tests/test_external_mcp.py:test_external_mcp_real_process, tests/test_external_mcp.py:test_get_tool_schemas_includes_external] diff --git a/src/models.py b/src/models.py index b245330a..4a097324 100644 --- a/src/models.py +++ b/src/models.py @@ -289,6 +289,17 @@ def __getattr__(name: str) -> Any: from src.workspace_manager import WorkspaceProfile as _WP globals()[name] = _WP return _WP + if name in ("MCPServerConfig", "MCPConfiguration", "VectorStoreConfig", "RAGConfig", "load_mcp_config"): + from src.mcp_client import ( + MCPServerConfig as _MSC, + MCPConfiguration as _MCP, + VectorStoreConfig as _VSC, + RAGConfig as _RAGC, + load_mcp_config as _lmc, + ) + val = {"MCPServerConfig": _MSC, "MCPConfiguration": _MCP, "VectorStoreConfig": _VSC, "RAGConfig": _RAGC, "load_mcp_config": _lmc}[name] + globals()[name] = val + return val raise AttributeError(f"module {__name__!r} has no attribute {name!r}") # MMA Core dataclasses (ThinkingSegment, Ticket, Track, WorkerContext, TrackMetadata) @@ -334,145 +345,8 @@ def __getattr__(name: str) -> Any: #region: MCP Config # MCPServerConfig + MCPConfiguration + VectorStoreConfig + RAGConfig + # load_mcp_config moved to src/mcp_client.py in +# MCP config classes moved to src/mcp_client.py in # module_taxonomy_refactor_20260627 Phase 3i. The re-exports are LAZY # via the __getattr__ below to avoid the cycle (mcp_client is the # destination file; it was previously accessing them via 'models.X'). -@dataclass -class MCPServerConfig: - name: str - command: Optional[str] = None - args: List[str] = field(default_factory=list) - url: Optional[str] = None - auto_start: bool = False - - def to_dict(self) -> Metadata: - res = {'auto_start': self.auto_start} - if self.command: res['command'] = self.command - if self.args: res['args'] = self.args - if self.url: res['url'] = self.url - return res - - @classmethod - def from_dict(cls, name: str, data: Metadata) -> 'MCPServerConfig': - """ - [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_model, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_load_mcp_config, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_serialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_view_mode_removal.py:test_file_item_serialization_with_flags] - """ - return cls( - name = name, - command = data.get('command'), - args = data.get('args', []), - url = data.get('url'), - auto_start = data.get('auto_start', False), - ) - -#endregion: MCP Config - -@dataclass -class MCPConfiguration: - mcpServers: Dict[str, MCPServerConfig] = field(default_factory=dict) - - def to_dict(self) -> Metadata: - """ - [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] - """ - return {'mcpServers': {name: cfg.to_dict() for name, cfg in self.mcpServers.items()}} - - @classmethod - def from_dict(cls, data: Metadata) -> 'MCPConfiguration': - """ - [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] - """ - raw_servers = data.get('mcpServers', {}) - parsed_servers = {name: MCPServerConfig.from_dict(name, cfg) for name, cfg in raw_servers.items()} - return cls(mcpServers=parsed_servers) - -@dataclass -class VectorStoreConfig: - provider: str - url: Optional[str] = None - api_key: Optional[str] = None - collection_name: str = 'manual_slop' - mcp_server: Optional[str] = None - mcp_tool: Optional[str] = None - - def to_dict(self) -> Metadata: - """ - [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] - """ - return { - "provider": self.provider, - "url": self.url, - "api_key": self.api_key, - "collection_name": self.collection_name, - "mcp_server": self.mcp_server, - "mcp_tool": self.mcp_tool, - } - - @classmethod - def from_dict(cls, data: Metadata) -> "VectorStoreConfig": - """ - [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] - """ - return cls( - provider = data["provider"], - url = data.get("url"), - api_key = data.get("api_key"), - collection_name = data.get("collection_name", "manual_slop"), - mcp_server = data.get("mcp_server"), - mcp_tool = data.get("mcp_tool"), - ) - -@dataclass -class RAGConfig: - enabled: bool = False - vector_store: VectorStoreConfig = field(default_factory=lambda: VectorStoreConfig(provider='mock')) - embedding_provider: str = 'gemini' - chunk_size: int = 1000 - chunk_overlap: int = 200 - - def to_dict(self) -> Metadata: - """ - [C: src/personas.py:PersonaManager.save_persona, src/presets.py:PresetManager.save_preset, src/project_manager.py:save_project, src/project_manager.py:save_track_state, src/tool_presets.py:ToolPresetManager.save_bias_profile, src/tool_presets.py:ToolPresetManager.save_preset, src/workspace_manager.py:WorkspaceManager.save_profile, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_serialization_with_annotations, tests/test_event_serialization.py:test_user_request_event_serialization, tests/test_external_editor.py:TestExternalEditorConfig.test_to_dict, tests/test_external_editor.py:TestTextEditorConfig.test_to_dict, tests/test_file_item_model.py:test_file_item_to_dict, tests/test_gui_events_v2.py:test_user_request_event_payload, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_serialization, tests/test_persona_id.py:test_ticket_persona_id_serialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_serialization, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_thinking_gui.py:test_thinking_segment_model_compatibility, tests/test_ticket_queue.py:test_ticket_to_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_to_dict, tests/test_track_state_schema.py:test_track_state_to_dict_with_none, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] - """ - return { - "enabled": self.enabled, - "vector_store": self.vector_store.to_dict(), - "embedding_provider": self.embedding_provider, - "chunk_size": self.chunk_size, - "chunk_overlap": self.chunk_overlap, - } - - @classmethod - def from_dict(cls, data: Metadata) -> "RAGConfig": - """ - [C: src/personas.py:PersonaManager.load_all, src/presets.py:PresetManager.load_all, src/project_manager.py:load_project, src/project_manager.py:load_track_state, src/tool_presets.py:ToolPresetManager.load_all_bias_profiles, src/tool_presets.py:ToolPresetManager.load_all_presets, src/workspace_manager.py:WorkspaceManager.load_all_profiles, tests/test_bias_models.py:test_bias_profile_model, tests/test_bias_models.py:test_tool_model, tests/test_bias_models.py:test_tool_preset_extension, tests/test_context_presets_models.py:test_context_preset_from_dict_legacy, tests/test_context_presets_models.py:test_context_preset_serialization, tests/test_context_presets_models.py:test_file_view_preset_serialization, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_deserialization_with_annotations, tests/test_custom_slices_annotations.py:test_file_item_custom_slices_round_trip_annotations, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_dict_editors, tests/test_external_editor.py:TestExternalEditorConfig.test_from_dict_with_string_editors, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_with_diff_args, tests/test_external_editor.py:TestTextEditorConfig.test_from_dict_without_diff_args, tests/test_file_item_model.py:test_file_item_from_dict, tests/test_file_item_model.py:test_file_item_from_dict_defaults, tests/test_history_manager.py:TestHistoryManager.test_snapshot_roundtrip, tests/test_mcp_config.py:test_mcp_configuration_to_from_dict, tests/test_mcp_config.py:test_mcp_server_config_to_from_dict, tests/test_per_ticket_model.py:test_model_override_default_on_deserialize, tests/test_per_ticket_model.py:test_model_override_deserialization, tests/test_persona_id.py:test_ticket_persona_id_deserialization, tests/test_persona_models.py:test_persona_defaults, tests/test_persona_models.py:test_persona_deserialization, tests/test_project_serialization.py:TestProjectSerialization.test_backward_compatibility_strings, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations, tests/test_ticket_queue.py:test_ticket_from_dict_default_priority, tests/test_ticket_queue.py:test_ticket_from_dict_priority, tests/test_tiered_aggregation.py:test_persona_aggregation_strategy, tests/test_track_state_schema.py:test_track_state_from_dict, tests/test_track_state_schema.py:test_track_state_from_dict_empty_and_missing, tests/test_ui_summary_only_removal.py:test_file_item_serialization_with_flags] - """ - return cls( - enabled = data.get("enabled", False), - vector_store = VectorStoreConfig.from_dict(data.get("vector_store", {"provider": "mock"})), - embedding_provider = data.get("embedding_provider", "gemini"), - chunk_size = data.get("chunk_size", 1000), - chunk_overlap = data.get("chunk_overlap", 200), - ) - -def load_mcp_config(path: str) -> MCPConfiguration: - """ - [C: tests/test_mcp_config.py:test_load_mcp_config] - """ - if not os.path.exists(path): - return MCPConfiguration() - with open(path, 'r', encoding='utf-8') as f: - try: - data = json.load(f) - return MCPConfiguration.from_dict(data) - except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e: - _mcp_err = Result(data=MCPConfiguration(), errors=[ErrorInfo(kind=ErrorKind.INVALID_INPUT, message=f"failed to load MCP config: {e}", source="models.load_mcp_config", original=e)]) - return MCPConfiguration() - -#endregion: MCP Config -# Project Context (ProjectContext + 5 sub-dataclasses + EMPTY_PROJECT_CONTEXT) -# moved to src/project.py in module_taxonomy_refactor_20260627 Phase 3b. -# The re-exports at the top of this module keep 'from src.models import -# ProjectContext' working for legacy callers. New code should import from -# src.project directly.