diff --git a/client-sdks/stainless/openapi.yml b/client-sdks/stainless/openapi.yml index a2b6880ab..96e8830f7 100644 --- a/client-sdks/stainless/openapi.yml +++ b/client-sdks/stainless/openapi.yml @@ -7258,8 +7258,13 @@ components: type: string title: Server Label server_url: - type: string - title: Server Url + anyOf: + - type: string + - type: 'null' + connector_id: + anyOf: + - type: string + - type: 'null' headers: anyOf: - additionalProperties: true @@ -7292,7 +7297,6 @@ components: type: object required: - server_label - - server_url title: OpenAIResponseInputToolMCP description: Model Context Protocol (MCP) tool configuration for OpenAI response inputs. CreateOpenaiResponseRequest: diff --git a/docs/static/deprecated-llama-stack-spec.yaml b/docs/static/deprecated-llama-stack-spec.yaml index 559b27133..012a08df2 100644 --- a/docs/static/deprecated-llama-stack-spec.yaml +++ b/docs/static/deprecated-llama-stack-spec.yaml @@ -4039,8 +4039,13 @@ components: type: string title: Server Label server_url: - type: string - title: Server Url + anyOf: + - type: string + - type: 'null' + connector_id: + anyOf: + - type: string + - type: 'null' headers: anyOf: - additionalProperties: true @@ -4073,7 +4078,6 @@ components: type: object required: - server_label - - server_url title: OpenAIResponseInputToolMCP description: Model Context Protocol (MCP) tool configuration for OpenAI response inputs. CreateOpenaiResponseRequest: diff --git a/docs/static/experimental-llama-stack-spec.yaml b/docs/static/experimental-llama-stack-spec.yaml index 05746ca52..38c86c556 100644 --- a/docs/static/experimental-llama-stack-spec.yaml +++ b/docs/static/experimental-llama-stack-spec.yaml @@ -3792,8 +3792,13 @@ components: type: string title: Server Label server_url: - type: string - title: Server Url + anyOf: + - type: string + - type: 'null' + connector_id: + anyOf: + - type: string + - type: 'null' headers: anyOf: - additionalProperties: true @@ -3826,7 +3831,6 @@ components: type: object required: - server_label - - server_url title: OpenAIResponseInputToolMCP description: Model Context Protocol (MCP) tool configuration for OpenAI response inputs. OpenAIResponseObject: diff --git a/docs/static/llama-stack-spec.yaml b/docs/static/llama-stack-spec.yaml index 30dc10bdc..f90543b01 100644 --- a/docs/static/llama-stack-spec.yaml +++ b/docs/static/llama-stack-spec.yaml @@ -5850,8 +5850,13 @@ components: type: string title: Server Label server_url: - type: string - title: Server Url + anyOf: + - type: string + - type: 'null' + connector_id: + anyOf: + - type: string + - type: 'null' headers: anyOf: - additionalProperties: true @@ -5884,7 +5889,6 @@ components: type: object required: - server_label - - server_url title: OpenAIResponseInputToolMCP description: Model Context Protocol (MCP) tool configuration for OpenAI response inputs. CreateOpenaiResponseRequest: diff --git a/docs/static/stainless-llama-stack-spec.yaml b/docs/static/stainless-llama-stack-spec.yaml index a2b6880ab..96e8830f7 100644 --- a/docs/static/stainless-llama-stack-spec.yaml +++ b/docs/static/stainless-llama-stack-spec.yaml @@ -7258,8 +7258,13 @@ components: type: string title: Server Label server_url: - type: string - title: Server Url + anyOf: + - type: string + - type: 'null' + connector_id: + anyOf: + - type: string + - type: 'null' headers: anyOf: - additionalProperties: true @@ -7292,7 +7297,6 @@ components: type: object required: - server_label - - server_url title: OpenAIResponseInputToolMCP description: Model Context Protocol (MCP) tool configuration for OpenAI response inputs. CreateOpenaiResponseRequest: diff --git a/src/llama_stack/core/connectors/connectors.py b/src/llama_stack/core/connectors/connectors.py index 027b6fd1b..cb6a54bfb 100644 --- a/src/llama_stack/core/connectors/connectors.py +++ b/src/llama_stack/core/connectors/connectors.py @@ -14,18 +14,16 @@ from llama_stack.log import get_logger from llama_stack.providers.utils.tools.mcp import get_mcp_server_info, list_mcp_tools from llama_stack_api import ( Connector, + ConnectorNotFoundError, Connectors, + ConnectorToolNotFoundError, ConnectorType, ListConnectorsResponse, ListRegistriesResponse, ListToolsResponse, Registry, - ToolDef, -) -from llama_stack_api.common.errors import ( - ConnectorNotFoundError, - ConnectorToolNotFoundError, RegistryNotFoundError, + ToolDef, ) logger = get_logger(name=__name__, category="connectors") @@ -55,6 +53,17 @@ class ConnectorServiceImpl(Connectors): self.connectors_map: dict[str, Connector] = {} self.registries_map: dict[str, Registry] = {} + def get_connector_url(self, connector_id: str) -> str | None: + """Get the URL of a connector by its ID. + + :param connector_id: The ID of the connector to get the URL for. + :returns: The URL of the connector. + """ + connector = self.connectors_map.get(connector_id) + if connector is None: + return None + return connector.url + async def register_connector( self, url: str, diff --git a/src/llama_stack/providers/inline/agents/meta_reference/__init__.py b/src/llama_stack/providers/inline/agents/meta_reference/__init__.py index c9c7d348a..1550ed7f4 100644 --- a/src/llama_stack/providers/inline/agents/meta_reference/__init__.py +++ b/src/llama_stack/providers/inline/agents/meta_reference/__init__.py @@ -28,6 +28,7 @@ async def get_provider_impl( deps[Api.conversations], deps[Api.prompts], deps[Api.files], + deps[Api.connectors], policy, ) await impl.initialize() diff --git a/src/llama_stack/providers/inline/agents/meta_reference/agents.py b/src/llama_stack/providers/inline/agents/meta_reference/agents.py index 39cc22be7..e86666561 100644 --- a/src/llama_stack/providers/inline/agents/meta_reference/agents.py +++ b/src/llama_stack/providers/inline/agents/meta_reference/agents.py @@ -11,6 +11,7 @@ from llama_stack.log import get_logger from llama_stack.providers.utils.responses.responses_store import ResponsesStore from llama_stack_api import ( Agents, + Connectors, Conversations, Files, Inference, @@ -49,6 +50,7 @@ class MetaReferenceAgentsImpl(Agents): conversations_api: Conversations, prompts_api: Prompts, files_api: Files, + connectors_api: Connectors, policy: list[AccessRule], ): self.config = config @@ -60,6 +62,7 @@ class MetaReferenceAgentsImpl(Agents): self.conversations_api = conversations_api self.prompts_api = prompts_api self.files_api = files_api + self.connectors_api = connectors_api self.in_memory_store = InmemoryKVStoreImpl() self.openai_responses_impl: OpenAIResponsesImpl | None = None self.policy = policy @@ -78,6 +81,7 @@ class MetaReferenceAgentsImpl(Agents): conversations_api=self.conversations_api, prompts_api=self.prompts_api, files_api=self.files_api, + connectors_api=self.connectors_api, ) async def shutdown(self) -> None: diff --git a/src/llama_stack/providers/inline/agents/meta_reference/responses/openai_responses.py b/src/llama_stack/providers/inline/agents/meta_reference/responses/openai_responses.py index 9cf30908c..79b3c0e9e 100644 --- a/src/llama_stack/providers/inline/agents/meta_reference/responses/openai_responses.py +++ b/src/llama_stack/providers/inline/agents/meta_reference/responses/openai_responses.py @@ -17,6 +17,7 @@ from llama_stack.providers.utils.responses.responses_store import ( _OpenAIResponseObjectWithInputAndMessages, ) from llama_stack_api import ( + Connectors, ConversationItem, Conversations, Files, @@ -79,6 +80,7 @@ class OpenAIResponsesImpl: conversations_api: Conversations, prompts_api: Prompts, files_api: Files, + connectors_api: Connectors, ): self.inference_api = inference_api self.tool_groups_api = tool_groups_api @@ -94,6 +96,7 @@ class OpenAIResponsesImpl: ) self.prompts_api = prompts_api self.files_api = files_api + self.connectors_api = connectors_api async def _prepend_previous_response( self, @@ -494,6 +497,7 @@ class OpenAIResponsesImpl: instructions=instructions, max_tool_calls=max_tool_calls, metadata=metadata, + connectors_api=self.connectors_api, ) # Stream the response diff --git a/src/llama_stack/providers/inline/agents/meta_reference/responses/streaming.py b/src/llama_stack/providers/inline/agents/meta_reference/responses/streaming.py index c778d65e7..10c0f8171 100644 --- a/src/llama_stack/providers/inline/agents/meta_reference/responses/streaming.py +++ b/src/llama_stack/providers/inline/agents/meta_reference/responses/streaming.py @@ -15,6 +15,7 @@ from llama_stack.providers.utils.inference.prompt_adapter import interleaved_con from llama_stack_api import ( AllowedToolsFilter, ApprovalFilter, + Connectors, Inference, MCPListToolsTool, ModelNotFoundError, @@ -121,8 +122,10 @@ class StreamingResponseOrchestrator: parallel_tool_calls: bool | None = None, max_tool_calls: int | None = None, metadata: dict[str, str] | None = None, + connectors_api: Connectors | None = None, ): self.inference_api = inference_api + self.connectors_api = connectors_api self.ctx = ctx self.response_id = response_id self.created_at = created_at @@ -1088,6 +1091,15 @@ class StreamingResponseOrchestrator: """Process an MCP tool configuration and emit appropriate streaming events.""" from llama_stack.providers.utils.tools.mcp import list_mcp_tools + # Resolve connector_id to server_url if provided + if mcp_tool.connector_id and not mcp_tool.server_url: + if self.connectors_api is None: + raise ValueError("Connectors API not available to resolve connector_id") + server_url = self.connectors_api.get_connector_url(mcp_tool.connector_id) + if not server_url: + raise ValueError(f"Connector {mcp_tool.connector_id} not found") + mcp_tool = mcp_tool.model_copy(update={"server_url": server_url}) + # Emit mcp_list_tools.in_progress self.sequence_number += 1 yield OpenAIResponseObjectStreamResponseMcpListToolsInProgress( diff --git a/src/llama_stack/providers/registry/agents.py b/src/llama_stack/providers/registry/agents.py index 22bb45faf..56a37ca4c 100644 --- a/src/llama_stack/providers/registry/agents.py +++ b/src/llama_stack/providers/registry/agents.py @@ -36,6 +36,7 @@ def available_providers() -> list[ProviderSpec]: Api.conversations, Api.prompts, Api.files, + Api.connectors, ], optional_api_dependencies=[ Api.safety, diff --git a/src/llama_stack_api/__init__.py b/src/llama_stack_api/__init__.py index ad92c0a59..761d8c094 100644 --- a/src/llama_stack_api/__init__.py +++ b/src/llama_stack_api/__init__.py @@ -46,10 +46,13 @@ from .common.content_types import ( ) from .common.errors import ( ConflictError, + ConnectorNotFoundError, + ConnectorToolNotFoundError, DatasetNotFoundError, InvalidConversationIdError, ModelNotFoundError, ModelTypeError, + RegistryNotFoundError, ResourceNotFoundError, TokenValidationError, ToolGroupNotFoundError, @@ -500,6 +503,8 @@ __all__ = [ "ConnectorInput", "Connectors", "ConnectorType", + "ConnectorNotFoundError", + "ConnectorToolNotFoundError", "Conversation", "ConversationDeletedResource", "ConversationItem", @@ -775,6 +780,7 @@ __all__ = [ "Ranker", "RegexParserScoringFnParams", "Registry", + "RegistryNotFoundError", "RegistryType", "RegistryInput", "RemoteProviderConfig", diff --git a/src/llama_stack_api/openai_responses.py b/src/llama_stack_api/openai_responses.py index 177d2314a..f0cccda0a 100644 --- a/src/llama_stack_api/openai_responses.py +++ b/src/llama_stack_api/openai_responses.py @@ -5,7 +5,7 @@ # the root directory of this source tree. from collections.abc import Sequence -from typing import Annotated, Any, Literal +from typing import Annotated, Any, Literal, Self from pydantic import BaseModel, Field, model_validator from typing_extensions import TypedDict @@ -488,7 +488,8 @@ class OpenAIResponseInputToolMCP(BaseModel): :param type: Tool type identifier, always "mcp" :param server_label: Label to identify this MCP server - :param server_url: URL endpoint of the MCP server + :param server_url: (Optional) URL endpoint of the MCP server + :param connector_id: (Optional) ID of the connector to use for this MCP server :param headers: (Optional) HTTP headers to include when connecting to the server :param authorization: (Optional) OAuth access token for authenticating with the MCP server :param require_approval: Approval requirement for tool calls ("always", "never", or filter) @@ -497,13 +498,20 @@ class OpenAIResponseInputToolMCP(BaseModel): type: Literal["mcp"] = "mcp" server_label: str - server_url: str + server_url: str | None = None + connector_id: str | None = None headers: dict[str, Any] | None = None authorization: str | None = Field(default=None, exclude=True) require_approval: Literal["always"] | Literal["never"] | ApprovalFilter = "never" allowed_tools: list[str] | AllowedToolsFilter | None = None + @model_validator(mode="after") + def validate_server_or_connector(self) -> Self: + if not self.server_url and not self.connector_id: + raise ValueError("Either 'server_url' or 'connector_id' must be provided for MCP tool") + return self + OpenAIResponseInputTool = Annotated[ OpenAIResponseInputToolWebSearch