feat(api): add readonly connectors API (#4258)

# What does this PR do?
Adds a new API for connectors and MCP registry support along with
required types.
Does not include any implementation for it

<!-- If resolving an issue, uncomment and update the line below -->
Closes #4235 and #4061 (partially)

## Test Plan
no tests included

---------

Signed-off-by: Jaideep Rao <jrao@redhat.com>
Co-authored-by: Francisco Javier Arceo <arceofrancisco@gmail.com>
This commit is contained in:
Jaideep Rao 2025-12-11 23:49:55 +05:30 committed by GitHub
parent 470fe55e87
commit 76e47d811a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1343 additions and 137 deletions

View file

@ -23,6 +23,7 @@ from llama_stack_api import (
Api,
Benchmark,
BenchmarkInput,
ConnectorInput,
Dataset,
DatasetInput,
DatasetIO,
@ -455,6 +456,7 @@ class RegisteredResources(BaseModel):
scoring_fns: list[ScoringFnInput] = Field(default_factory=list)
benchmarks: list[BenchmarkInput] = Field(default_factory=list)
tool_groups: list[ToolGroupInput] = Field(default_factory=list)
connectors: list[ConnectorInput] = Field(default_factory=list)
class ServerConfig(BaseModel):

View file

@ -25,7 +25,7 @@ from llama_stack_api import (
logger = get_logger(name=__name__, category="core")
INTERNAL_APIS = {Api.inspect, Api.providers, Api.prompts, Api.conversations}
INTERNAL_APIS = {Api.inspect, Api.providers, Api.prompts, Api.conversations, Api.connectors}
def stack_apis() -> list[Api]:

View file

@ -28,6 +28,7 @@ from llama_stack_api import (
Batches,
Benchmarks,
BenchmarksProtocolPrivate,
Connectors,
Conversations,
DatasetIO,
Datasets,
@ -100,6 +101,7 @@ def api_protocol_map(external_apis: dict[Api, ExternalApiSpec] | None = None) ->
Api.files: Files,
Api.prompts: Prompts,
Api.conversations: Conversations,
Api.connectors: Connectors,
}
if external_apis:

View file

@ -78,6 +78,14 @@ from .common.type_system import (
ParamType,
StringType,
)
from .connectors import (
Connector,
ConnectorInput,
Connectors,
ConnectorType,
ListConnectorsResponse,
ListToolsResponse,
)
from .conversations import (
Conversation,
ConversationDeletedResource,
@ -497,6 +505,10 @@ __all__ = [
"CommonShieldFields",
"CompletionInputType",
"CompletionRequest",
"Connector",
"ConnectorInput",
"Connectors",
"ConnectorType",
"Conversation",
"ConversationDeletedResource",
"ConversationItem",
@ -576,6 +588,7 @@ __all__ = [
"ListBenchmarksResponse",
"RegisterBenchmarkRequest",
"UnregisterBenchmarkRequest",
"ListConnectorsResponse",
"ListDatasetsResponse",
"ListModelsResponse",
"ListOpenAIChatCompletionResponse",
@ -590,6 +603,7 @@ __all__ = [
"ListShieldsResponse",
"ListToolDefsResponse",
"ListToolGroupsResponse",
"ListToolsResponse",
"LogProbConfig",
"LoraFinetuningConfig",
"MCPListToolsTool",

View file

@ -0,0 +1,146 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree.
from enum import StrEnum
from typing import Literal, Protocol
from pydantic import BaseModel, Field
from typing_extensions import runtime_checkable
from llama_stack_api.resource import Resource, ResourceType
from llama_stack_api.schema_utils import json_schema_type, webmethod
from llama_stack_api.tools import ToolDef
from llama_stack_api.version import LLAMA_STACK_API_V1ALPHA
@json_schema_type
class ConnectorType(StrEnum):
"""Type of connector."""
MCP = "mcp"
class CommonConnectorFields(BaseModel):
"""Common fields for all connectors.
:param connector_type: Type of connector
:param connector_id: Identifier for the connector
:param url: URL of the connector
:param server_label: (Optional) Label of the server
"""
connector_type: ConnectorType = Field(default=ConnectorType.MCP)
connector_id: str = Field(..., description="Identifier for the connector")
url: str = Field(..., description="URL of the connector")
server_label: str | None = Field(default=None, description="Label of the server")
@json_schema_type
class Connector(CommonConnectorFields, Resource):
"""A connector resource representing a connector registered in Llama Stack.
:param type: Type of resource, always 'connector' for connectors
:param server_name: (Optional) Name of the server
:param server_description: (Optional) Description of the server
"""
model_config = {"populate_by_name": True}
type: Literal[ResourceType.connector] = ResourceType.connector
server_name: str | None = Field(default=None, description="Name of the server")
server_description: str | None = Field(default=None, description="Description of the server")
@json_schema_type
class ConnectorInput(CommonConnectorFields):
"""Input for creating a connector
:param type: Type of resource, always 'connector' for connectors
"""
type: Literal[ResourceType.connector] = ResourceType.connector
@json_schema_type
class ListConnectorsResponse(BaseModel):
"""Response containing a list of connectors.
:param data: List of connectors
"""
data: list[Connector]
@json_schema_type
class ListToolsResponse(BaseModel):
"""Response containing a list of tools.
:param data: List of tools
"""
data: list[ToolDef]
@runtime_checkable
class Connectors(Protocol):
# NOTE: Route order matters! More specific routes must come before less specific ones.
# Routes with {param:path} are greedy and will match everything including slashes.
@webmethod(route="/connectors", method="GET", level=LLAMA_STACK_API_V1ALPHA)
async def list_connectors(
self,
) -> ListConnectorsResponse:
"""List all configured connectors.
:returns: A ListConnectorsResponse.
"""
...
@webmethod(route="/connectors/{connector_id}/tools/{tool_name}", method="GET", level=LLAMA_STACK_API_V1ALPHA)
async def get_connector_tool(
self,
connector_id: str,
tool_name: str,
authorization: str | None = None,
) -> ToolDef:
"""Get a tool definition by its name from a connector.
:param connector_id: The ID of the connector to get the tool from.
:param tool_name: The name of the tool to get.
:param authorization: (Optional) OAuth access token for authenticating with the MCP server.
:returns: A ToolDef.
"""
...
@webmethod(route="/connectors/{connector_id}/tools", method="GET", level=LLAMA_STACK_API_V1ALPHA)
async def list_connector_tools(
self,
connector_id: str,
authorization: str | None = None,
) -> ListToolsResponse:
"""List tools available from a connector.
:param connector_id: The ID of the connector to list tools for.
:param authorization: (Optional) OAuth access token for authenticating with the MCP server.
:returns: A ListToolsResponse.
"""
...
@webmethod(route="/connectors/{connector_id}", method="GET", level=LLAMA_STACK_API_V1ALPHA)
async def get_connector(
self,
connector_id: str,
authorization: str | None = None,
) -> Connector:
"""Get a connector by its ID.
:param connector_id: The ID of the connector to get.
:param authorization: (Optional) OAuth access token for authenticating with the MCP server.
:returns: A Connector.
"""
...

View file

@ -111,6 +111,7 @@ class Api(Enum, metaclass=DynamicApiMeta):
:cvar tool_groups: Tool group organization
:cvar files: File storage and management
:cvar prompts: Prompt versions and management
:cvar connectors: External connector management (e.g., MCP servers)
:cvar inspect: Built-in system inspection and introspection
"""
@ -136,6 +137,7 @@ class Api(Enum, metaclass=DynamicApiMeta):
files = "files"
prompts = "prompts"
conversations = "conversations"
connectors = "connectors"
# built-in API
inspect = "inspect"

View file

@ -4,7 +4,6 @@
# This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree.
from enum import StrEnum
from pydantic import BaseModel, Field
@ -20,6 +19,7 @@ class ResourceType(StrEnum):
tool = "tool"
tool_group = "tool_group"
prompt = "prompt"
connector = "connector"
class Resource(BaseModel):