Skip to content

Commit fe5908e

Browse files
authored
Merge pull request #2142 from Jdubrick/add-mcp-synthesizer
RHIDP-14069: add mcp tools to unified config
2 parents fda5528 + ee21a73 commit fe5908e

7 files changed

Lines changed: 291 additions & 9 deletions

File tree

docs/config.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,13 @@ Per Decision S5 of the design spike, backend-agnostic high-level sections
827827
only the Llama-Stack-specific synthesis controls: which baseline to start
828828
from, an optional profile file, and a raw native_override escape hatch.
829829

830+
During synthesis from the default baseline or a profile, LCORE ensures the
831+
Llama Stack MCP tool_runtime provider (`provider_id: model-context-protocol`,
832+
`provider_type: remote::model-context-protocol`) is present so static
833+
`mcp_servers` and dynamic MCP registration work. That ensure is skipped when
834+
`baseline: empty` (migration / blank-slate); supply MCP via `native_override`
835+
in that case.
836+
830837
Attributes:
831838
baseline: Synthesis starting point. "default" begins from LCORE's
832839
built-in baseline (src/data/default_run.yaml); "empty" begins from

docs/deployment_guide.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,11 @@ starts from a *baseline*. By default that is LCORE's built-in baseline; a
105105

106106
A profile is an ordinary `run.yaml`-shaped YAML file — the same schema Llama
107107
Stack reads natively. Everything else in the unified pipeline (enrichment,
108-
the high-level `inference.providers` section, `native_override`) is applied
109-
*on top* of the profile, in that order.
108+
the high-level `inference.providers` section, ensuring the MCP tool_runtime
109+
provider, then `native_override`) is applied *on top* of the profile, in that
110+
order. The MCP ensure adds `provider_id: model-context-protocol` when missing
111+
so static `mcp_servers` and dynamic MCP registration work; it is skipped only
112+
for `baseline: empty` (use `native_override` there if you need MCP).
110113

111114
**Authoring a profile.** Start from one of the reference profiles shipped in
112115
[`examples/profiles/`](https://github.com/lightspeed-core/lightspeed-stack/tree/main/examples/profiles):

src/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@
160160
MCP_AUTH_CLIENT: Final[str] = "client"
161161
MCP_AUTH_OAUTH: Final[str] = "oauth"
162162

163+
# MCP tool_runtime provider (Llama Stack run.yaml / unified synthesis)
164+
MCP_TOOL_RUNTIME_PROVIDER_ID: Final[str] = "model-context-protocol"
165+
MCP_TOOL_RUNTIME_PROVIDER_TYPE: Final[str] = "remote::model-context-protocol"
166+
163167
# Media type constants for streaming responses
164168
MEDIA_TYPE_JSON: Final[str] = "application/json"
165169
MEDIA_TYPE_TEXT: Final[str] = "text/plain"

src/data/default_run.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
# It is intentionally thinner than the repo-root run.yaml: it carries only the
1010
# APIs and providers needed to boot a minimal, queryable stack (inference,
1111
# safety, vector_io, agents, tool_runtime, files) plus the storage backends
12-
# those providers reference. Operators extend it via high-level sections or
12+
# those providers reference. tool_runtime includes rag-runtime (file_search /
13+
# RAG) and model-context-protocol (MCP) so those capabilities work when
14+
# operators enable them. Operators extend it via high-level sections or
1315
# `native_override`.
1416
#
1517
# NOTE: `external_providers_dir` carries a default (`:=~/.llama/providers.d`)
@@ -54,6 +56,9 @@ providers:
5456
- config: {}
5557
provider_id: rag-runtime
5658
provider_type: inline::rag-runtime
59+
- config: {}
60+
provider_id: model-context-protocol
61+
provider_type: remote::model-context-protocol
5762
vector_io:
5863
- config:
5964
persistence:

src/llama_stack_configuration.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
785826
def _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

tests/e2e/configuration/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ and the active `lightspeed-stack.yaml` is also copied to the repo root — so th
2525
relative `profile:` path resolves to that materialized file, which the unified
2626
synthesizer consumes as its baseline. LS behavior is identical to the legacy
2727
two-file path (requirement R7 in the config-merge design doc); the wiring that
28-
selects a provider config stays unchanged.
28+
selects a provider config stays unchanged. The synthesizer also ensures the
29+
MCP `tool_runtime` provider (`model-context-protocol`) is present on the
30+
profile baseline when missing; many `run-*.yaml` fixtures already include it,
31+
so the ensure is typically a no-op.
2932

3033
The `tests/e2e/configs/run-*.yaml` files therefore serve a dual role: in
3134
server mode they are the run configuration of the standalone Llama Stack

0 commit comments

Comments
 (0)