mcp_chat_host() gives the model in a shinychat conversation the tools
of one or more MCP Apps, and shows each app it opens in the
conversation, live. The apps can be McpApps in the same R process, or
come from any MCP server through mcp_client().
It also does what an app expects of the chat that hosts it:
What the person does in an app reaches the model. Before each message the person sends, the model is told what each open app reports about what it shows (its model context). The model sees the apps' current state, not earlier ones, and the saved conversation doesn't keep it.
Apps can suggest messages. A message an app asks to post goes into the chat's input box, for the person to read and send.
Saved conversations come back live. Cards restored with a conversation show their apps again, without calling the tools again.
What apps report is written by the apps, so treat it like anything else a tool returns: the model is told where it came from, and each app's report is cut to 2,000 characters, from the five apps that changed most recently.
A session can have several chats, each with its own mcp_chat_host().
Each chat's model is told about, and gets messages from, only the cards
its tools opened. A card built with mcp_content_result() belongs to
the session's chat when there's only one.
on_app_call checks the tool calls that apps' pages make in the chat's
cards, such as when the person presses a button in one; the model's own
calls aren't passed to it. It applies to the cards the chat's tools
open, cards restored with the conversation, and cards built with
mcp_content_result() while it's the session's only chat.
Usage
mcp_chat_host(
chat,
sources,
context = TRUE,
messages = c("compose", "submit", "ignore"),
chat_id = NULL,
...,
on_app_call = NULL,
session = shiny::getDefaultReactiveDomain()
)Arguments
- chat
The value of
shinychat::chat_server(), or an ellmer chat. With an ellmer chat, passchat_idtoo, so apps' messages can reach the input box.- sources
Where the apps come from: an McpApp, an McpClient, or a list of them.
- context
Whether to tell the model what the open apps show. The report goes to the model as a message of its own, just before the person's, and some providers (AWS Bedrock) refuse two user messages in a row. For those, set
context = FALSEand addhost$context()to the person's message yourself.- messages
What to do with messages apps ask to post:
"compose"puts them in the input box,"submit"sends them at once (for apps you trust),"ignore"drops them.- chat_id
With an ellmer chat, the id of the
shinychat::chat_ui().- ...
Passed to
as_shinychat_tool():value_fn,summary,title,icon,open,show_request, andfull_screen.- on_app_call
A function that checks each tool call the pages of the chat's cards make before it's sent, to let it through, refuse it, or record it. See "Checking the app's calls" below.
NULL, the default, lets through every call the apps may make.- session
The Shiny session.
Value
Invisibly, a list with context(), which returns the text the
model is given about the open apps (or NULL), and tools, the ellmer
tools registered with the chat.
Checking the app's calls
The app's page calls its tools as the person uses it: to fill its
outputs when an input changes, or when they press a button such as
"Save". on_app_call sees each of these calls before it's sent to the
app's server, to let it through, refuse it, or keep a record of who did
what. It's called with a list describing the call:
name: the tool's name.arguments: its arguments, as the page sent them (parsed JSON: arrays are lists).tool: the tool's definition from the app's server, with itsannotations, such asdestructiveHint.instance_id: the id of the pane or card.kind:"pane"or"card".source: the name of where the app comes from: the McpApp's name, or themcp_client()'sname.chat: for a card, themcp_chat_host()it belongs to: itschat_id, else"chat-1","chat-2", and so on, by its place among the session's chat hosts. OtherwiseNULL.title: the app's title.session: the Shiny session. On Posit Connect,session$useris the signed-in user.
Return TRUE to let the call through, and FALSE or a string to refuse
it. The app's page is given the string as the call's error, so write it
for the person using the app. To ask someone first, return a promise
that resolves to one of these: the app waits for the answer, and the
rest of the session carries on. Anything else, an error, or a rejected
promise refuses the call, with a warning. A refused call never reaches
the app's server.
A page can call a tool each time an input changes, so keep the function quick, and ask a person only about the tools that need it.
Only the tool calls the app's page makes are checked. Reading the app's
resources isn't, and neither is the call that opens the app. The
model's calls are the chat's to check, with ellmer's on_tool_request()
callback (see ellmer::tool_reject()).
A function can't be saved with a conversation. A card restored in a new
session goes through the checks that session gives for the card's app,
through mcp_chat_host() or as_shinychat_tool(). If the card had a
check of its own and the new session gives none, its page's calls are
refused. That mark is saved in the browser with the conversation, so it
guards against a missing check, not against the person who edits their
saved conversation: to check every call, give the check to
mcp_chat_host() or as_shinychat_tool().
See also
Other hosting:
as_shinychat_tool(),
mcp_client(),
mcp_host_ui()
Examples
if (FALSE) { # \dontrun{
library(shiny)
library(shinychat)
ui <- bslib::page_fillable(chat_ui("chat"))
server <- function(input, output, session) {
chat <- chat_server("chat", ellmer::chat("anthropic/claude-sonnet-5"))
mcp_chat_host(chat, list(
cars_app,
mcp_client("https://connect.example.com/sales/mcp")
))
}
shinyApp(ui, server)
} # }
