Skip to content

Add Resources API for MCP Apps Support #5430

@anik120

Description

@anik120

🚀 Describe the new functionality needed

MCP Apps is the first official extension to the Model Context Protocol, announced on January 26, 2026. It enables MCP servers to return interactive UI components (charts, tables, dashboards) that render directly in conversation streams.

Core MCP Apps Pattern:

  1. Tools declare UI resources via _meta.ui.resourceUri (e.g., "ui://server/dashboard")
  2. MCP servers expose UI content via the Resources API (resources/list, resources/read)
  3. Hosts fetch HTML/JavaScript using the ui:// URI scheme
  4. UIs render in sandboxed iframes with bidirectional postMessage communication

This feature request is for Implementing a Resources API in llama-stack to support the [MCP Apps extension(https://modelcontextprotocol.io/docs/extensions/apps), enabling MCP servers to serve interactive UI components alongside tool execution.

Proposed Implementation

API interfaces

  1. Data Models
class Resource(BaseModel):
    uri: str
    name: str
    description: str | None
    mime_type: str | None

class ListResourcesResponse(BaseModel):
    resources: list[Resource]

class ReadResourceResponse(BaseModel):
    content: str  # Text or Base64 string
    mime_type: str
    is_binary: bool
  1. API Protocol
class Resources(Protocol):
    async def list_resources(
        self, 
        mcp_endpoint: URL, 
        authorization: str | None = None
    ) -> ListResourcesResponse:
        """Fetch available resources from an MCP server."""

    async def read_resource(
        self, 
        mcp_endpoint: URL, 
        uri: str, 
        authorization: str | None = None
    ) -> ReadResourceResponse:
        """Retrieve content and metadata for a specific resource URI."""

New API Endpoints

Method Endpoint Description
POST /resources/list Retrieves a list of available resources (URIs, names, and descriptions) from a specific MCP server endpoint.
POST /resources/read Fetches the content and metadata (MIME type, binary status) for a specific resource URI from an MCP server.

💡 Why is this needed? What if we don't build it?

Platform Adoption:

  • Supported by Claude, ChatGPT, VS Code, Goose, Postman, MCPJam etc
  • MCP Python SDK (v1.23.0+) includes resources API: session.list_resources(), session.read_resource()
  • Not yet exposed in llama-stack

MCP SDK Support: The underlying mcp Python SDK (v1.23.0+) already supports:

 from mcp import ClientSession
 
 async with session:
      # List resources
      resources = await session.list_resources()
      # Read resource content
      content = await session.read_resource("ui://server/dashboard")

llama-stack just needs to expose this functionality.

Benefits

  1. Standards Compliance: Implements official MCP Apps extension protocol
  2. Platform Parity: Aligns llama-stack with Claude, ChatGPT, VS Code, Goose support
  3. Enhanced UX: Enables interactive dashboards, charts, forms instead of text-only responses
  4. Minimal Code: Leverages existing MCP SDK (session.list_resources(), session.read_resource())
  5. Consistent Pattern: Mirrors existing list_mcp_tools() / invoke_mcp_tool() architecture

Other thoughts

Use Case Example

MCP Server (Using FastMCP)

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Kubernetes Dashboard")

@mcp.tool()
def list_namespaces() -> dict:
     """List Kubernetes namespaces.

     Returns data AND declares a UI resource for interactive visualization.
     """
     return {
         "namespaces": ["default", "kube-system", "prod"],
         "_meta": {
             "ui": {
                 "resourceUri": "ui://kube-mcp/namespaces-dashboard"
             }
         }
     }

 @mcp.resource("ui://kube-mcp/namespaces-dashboard")
 def namespace_dashboard() -> str:
     """Interactive HTML dashboard for namespace visualization."""
     return """
     <!DOCTYPE html>
     <html>
     <head>
         <script type="module">
             import { App } from 'https://esm.sh/@modelcontextprotocol/ext-apps@1.1.2';

             const app = new App();
             await app.connect();

             app.ontoolresult = (result) => {
                 const data = JSON.parse(result.content);
                 document.getElementById('namespaces').innerHTML =
                     data.namespaces.map(ns => `<li>${ns}</li>`).join('');
             };
         </script>
     </head>
     <body>
         <h1>Kubernetes Namespaces</h1>
         <ul id="namespaces"></ul>
     </body>
     </html>
     """

Client Usage

from llama_stack_client import AsyncLlamaStackClient

client = AsyncLlamaStackClient(base_url="http://localhost:8321")

# List available UI resources
resources = await client.resources.list(
    mcp_endpoint={"uri": "http://localhost:8081/sse"}
)

for resource in resources.resources:
    if resource.uri.startswith("ui://"):
        print(f"UI Resource: {resource.name} ({resource.uri})")

# Fetch UI resource HTML
response = await client.resources.read(
    mcp_endpoint={"uri": "http://localhost:8081/sse"},
    uri="ui://kube-mcp/namespaces-dashboard"
)

html_content = response.content  # HTML for sandboxed iframe rendering

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions