Skip to contents

shinymcp supports two runtime-first shinychat paths:

  1. Wrap a small McpApp as an ellmer tool with as_shinychat_tool().
  2. Embed a live McpApp directly in Shiny with mcp_host_ui() / mcp_host_server() or mcp_embed().

In both paths, the card is a portable MCP App iframe. When it runs inside Shiny, the active Shiny session owns the live host state and executes tool calls in R. The iframe stays lightweight HTML plus the shinymcp bridge rather than starting a nested Shiny runtime for every chat card.

shinymcp also sets shinychat’s display$full_screen flag for tool cards when available, and the embedded host shell includes its own full-screen control for direct Shiny embeds and older shinychat development builds.

Tool-card path with chat_mod_server()

library(shiny)
library(bslib)
library(ellmer)
library(shinychat)
library(shinymcp)

card_app <- mcp_app(
  ui = htmltools::tagList(
    mcp_text_input("name", "Name"),
    mcp_text("greeting")
  ),
  tools = list(
    list(
      name = "greet",
      description = "Generate a greeting card",
      inputSchema = list(
        type = "object",
        properties = list(name = list(type = "string"))
      ),
      fun = function(name = "world") {
        list(greeting = paste("Hello", name))
      }
    )
  ),
  name = "greeting-card"
)

greet_tool <- as_shinychat_tool(
  card_app,
  value_fn = function(raw_result) list(greeting = raw_result$greeting),
  summary = function(raw_result) raw_result$greeting,
  title = "Greeting Card"
)

ui <- page_fillable(chat_mod_ui("chat"))

server <- function(input, output, session) {
  client <- ellmer::chat("openai/gpt-4.1-nano")
  client$register_tool(greet_tool)
  chat_mod_server("chat", client)
}

shinyApp(ui, server)

The wrapped tool returns:

  • A machine-facing value, which defaults to the raw tool result transformed by value_fn.
  • A human-facing shinychat tool card, which defaults to a live embedded McpApp.
  • Full-screen affordances for larger card inspection, using shinychat’s native tool-card mode when available.

Content-streaming path with chat_ui() + chat_append()

Use mcp_content_result() when you already have a live Shiny session and want to append a card yourself:

ui <- page_fillable(chat_ui("chat"))

server <- function(input, output, session) {
  observeEvent(input$chat_user_input, {
    card <- mcp_content_result(
      app = card_app,
      value = list(status = "ready"),
      title = "Greeting Card"
    )

    chat_append("chat", card)
  })
}

Direct embedding in Shiny

For authored Shiny UIs, use the host shell helpers directly:

ui <- page_fillable(
  mcp_host_ui("card")
)

server <- function(input, output, session) {
  host <- mcp_host_server(
    "card",
    app = card_app,
    trigger = "submit"
  )
}

mcp_embed(card_app) is the convenience helper for dynamic server-side contexts where a live session already exists.

The returned host object also exposes read-only reactives so the outer Shiny app can observe the embedded card:

server <- function(input, output, session) {
  host <- mcp_host_server("card", app = card_app)

  observeEvent(host$model_context(), {
    # Current values the card is sending to the model context.
    str(host$model_context())
  })

  observeEvent(host$last_tool_call(), {
    # Raw R result plus formatted MCP structuredContent.
    str(host$last_raw_result())
    str(host$last_result()$structuredContent)
  })
}

Interaction modes

mcp_embed() and mcp_host_server() support four trigger modes:

  • change: execute immediately on input change.
  • debounce: execute after a short quiet period.
  • submit: require the host shell Apply button.
  • manual: require an explicit host-side command such as host$execute().
  • Prefer small single-purpose cards.
  • Prefer sequential tool execution in shinychat until you have a strong reason to expose many parallel cards.
  • Treat the chat card as the human surface and the value_fn output as the model-facing value.
  • Use Shiny modules directly when you need a Shiny-only app with full reactive UI semantics.
  • Use McpApp cards when you want the same experience to work in shinychat and MCP hosts.