@@ -782,6 +782,47 @@ def apply_high_level_inference(
782782 )
783783
784784
785+ def ensure_mcp_tool_runtime (ls_config : dict [str , Any ]) -> None :
786+ """Ensure the default MCP tool_runtime provider exists in ``ls_config``.
787+
788+ Adds ``tool_runtime`` to ``apis`` when missing, then appends the default
789+ ``model-context-protocol`` provider under ``providers.tool_runtime`` when
790+ no entry with that ``provider_id`` is already present. Existing entries
791+ (including ``rag-runtime``) are left untouched.
792+
793+ Parameters:
794+ ls_config: The Llama Stack configuration being synthesized (modified
795+ in place).
796+
797+ Returns:
798+ None: ``ls_config`` is modified in place.
799+ """
800+ apis = ls_config .setdefault ("apis" , [])
801+ if "tool_runtime" not in apis :
802+ apis .append ("tool_runtime" )
803+
804+ providers_section = ls_config .setdefault ("providers" , {})
805+ tool_runtime = providers_section .setdefault ("tool_runtime" , [])
806+ for existing in tool_runtime :
807+ if (
808+ isinstance (existing , dict )
809+ and existing .get ("provider_id" ) == constants .MCP_TOOL_RUNTIME_PROVIDER_ID
810+ ):
811+ return
812+
813+ tool_runtime .append (
814+ {
815+ "provider_id" : constants .MCP_TOOL_RUNTIME_PROVIDER_ID ,
816+ "provider_type" : constants .MCP_TOOL_RUNTIME_PROVIDER_TYPE ,
817+ "config" : {},
818+ }
819+ )
820+ logger .info (
821+ "Added MCP tool_runtime provider provider_id=%r" ,
822+ constants .MCP_TOOL_RUNTIME_PROVIDER_ID ,
823+ )
824+
825+
785826def _resolve_profile_path (profile : str , config_file_dir : Optional [str ]) -> Path :
786827 """Resolve a ``profile:`` path against the loaded config's directory (R8).
787828
@@ -812,8 +853,9 @@ def synthesize_configuration(
812853 Implements the unified-mode synthesis pipeline: select a baseline (profile
813854 file, empty, or the built-in default), apply the existing enrichment
814855 (Azure Entra ID, BYOK RAG, Solr/OKP) for parity with legacy mode (R7),
815- expand the high-level ``inference.providers`` section, and deep-merge the
816- raw ``native_override`` last (R5).
856+ expand the high-level ``inference.providers`` section, ensure the default
857+ MCP tool_runtime provider when the baseline was not empty, and deep-merge
858+ the raw ``native_override`` last (R5).
817859
818860 Parameters:
819861 lcs_config: The full ``lightspeed-stack.yaml`` parsed into a dict.
@@ -829,13 +871,15 @@ def synthesize_configuration(
829871 unified = llama_stack .get ("config" ) # None when only top-level inputs are set
830872
831873 # 1-2. Select the baseline.
874+ baseline_was_empty = False
832875 if unified and unified .get ("profile" ):
833876 profile_path = _resolve_profile_path (unified ["profile" ], config_file_dir )
834877 logger .info ("Loading synthesis baseline from profile %s" , profile_path )
835878 with open (profile_path , "r" , encoding = "utf-8" ) as file :
836879 baseline = yaml .safe_load (file ) or {}
837880 elif unified and unified .get ("baseline" ) == "empty" :
838881 logger .info ("Synthesizing from an empty baseline" )
882+ baseline_was_empty = True
839883 baseline = {}
840884 else :
841885 baseline = (
@@ -860,11 +904,16 @@ def synthesize_configuration(
860904 if inference .get ("providers" ):
861905 apply_high_level_inference (ls_config , inference )
862906
863- # 6. Raw escape hatch, deep-merged last with list replacement (R5).
907+ # 6. Ensure MCP tool_runtime for default/profile baselines (skipped for
908+ # baseline: empty so migrate round-trips stay lossless).
909+ if not baseline_was_empty :
910+ ensure_mcp_tool_runtime (ls_config )
911+
912+ # 7. Raw escape hatch, deep-merged last with list replacement (R5).
864913 if unified and unified .get ("native_override" ):
865914 ls_config = deep_merge_list_replace (ls_config , unified ["native_override" ])
866915
867- # 7 . Dedupe again in case native_override or enrichment reintroduced dupes.
916+ # 8 . Dedupe again in case native_override or enrichment reintroduced dupes.
868917 dedupe_providers_vector_io (ls_config )
869918
870919 return ls_config
0 commit comments