Host — AppHost
The AppHost mediator: host-side app management and bidirectional tool calls.
========================================================================
AppHost and host-side app management
Demonstrates AppHost mediating between an MCP server and an app bridge with bidirectional tool calls.
9 steps ¶
Step 1: Create MCP server with tools ¶
Refs: MCP Specification
Srv ->> Srv: RegisterTool("server_echo")
Srv ->> Srv: RegisterTool("server_time")
The server provides two tools: echo (returns input) and time
(returns current time).
Press Enter to run this step...
Server created with 2 tools: server_echo, server_time
Step 2: Connect client to server via in-process transport ¶
Client ->> Srv: initialize
Srv -->> Client: capabilities, serverInfo
The client connects without HTTP, using InProcessTransport for
direct dispatch.
Press Enter to run this step...
Connected to demo-server 1.0
Server tools: server_echo, server_time
Step 3: Create InProcessAppBridge with app-provided tools ¶
Refs: MCP Apps Extension
Bridge ->> Bridge: RegisterTool("app_greet")
Bridge ->> Bridge: RegisterTool("app_counter")
The bridge simulates an MCP App (iframe). It registers two tools
that the host/model can call directly.
Press Enter to run this step...
Bridge created with 2 app tools: app_greet, app_counter
Step 4: Create AppHost and wire everything together ¶
Host ->> Host: WithHostHandlers (ui/* host capabilities)
Host ->> Bridge: SetRequestHandler (app→host)
Host ->> Bridge: SetNotificationHandler (list_changed)
Host ->> Bridge: Start()
Host ->> Bridge: Send(tools/list), initial fetch
Bridge -->> Host: {tools: [app_greet, app_counter]}
AppHost wires up bidirectional routing and fetches the initial app
tool list. HostHandlers supplies the ui/* methods that belong to the
host, not the server.
Press Enter to run this step...
AppHost started: bridge handlers wired, initial tool list fetched
Step 5: ListAllTools, the aggregated server + app tools ¶
Host ->> Client: ListTools(), server tools
Client -->> Host: [server_echo, server_time]
Host ->> Bridge: cached app tools
Bridge -->> Host: [app_greet, app_counter]
ListAllTools merges tools from the MCP server and the app bridge
into a single list.
Press Enter to run this step...
All tools (4 total):
- server_echo (Echo back the input)
- server_time (Get current time)
- app_counter (Increment and return a counter)
- app_greet (Greet someone by name)
Step 6: CallAppTool, where the host invokes an app-provided tool ¶
Host ->> Bridge: Send(tools/call, {name: "app_greet", args: {name: "World"}})
Bridge -->> Host: ToolResult {text: "Hello, World!"}
The host calls a tool registered by the app. The bridge dispatches
to the Go handler.
Press Enter to run this step...
Result: Hello, World!
Counter after 2 calls: counter = 2
Step 7: App calls server tool via bridge → AppHost → Client ¶
Bridge ->> Host: SendToHost(tools/call, {name: "server_echo"})
Host ->> Client: Call(tools/call, params)
Client ->> Srv: JSON-RPC tools/call
Srv -->> Client: ToolResult
Client -->> Host: CallResult
Host -->> Bridge: Response
The app calls a server-side tool through the bridge. AppHost
forwards to the MCP server via the Client.
Press Enter to run this step...
App called server_echo → echo: from the app
Step 8: App calls the host, not the server ¶
Refs: MCP Apps Extension
Bridge ->> Host: ui/update-model-context {content, structuredContent}
Host -->> Bridge: {}
Bridge ->> Host: ui/message {role: user, content}
Host -->> Bridge: {}
Bridge ->> Host: ui/open-link, ui/request-display-mode
Four of the app's requests are host capabilities the MCP server has
never heard of. AppHost answers them from HostHandlers and forwards
only tools/call and resources/read.
Press Enter to run this step...
Model context slot: “Map is centred on Ann Arbor” (the Detroit update was replaced)
Follow-up turns queued for the model: 1 (“What’s near here?”)
[host] would open https://example.com/detroit in the user’s browser
Asked for pip, host granted {“mode”:“inline”}
Step 9: Dynamic registration, where the app adds a tool at runtime ¶
Bridge ->> Bridge: RegisterTool("app_dice")
Bridge ->> Host: notifications/tools/list_changed
Host ->> Bridge: Send(tools/list), refresh
Bridge -->> Host: {tools: [app_greet, app_counter, app_dice]}
The app registers a new tool after startup. AppHost detects the
change and refreshes its cache.
Press Enter to run this step...
App tools after dynamic registration (3):
- app_counter
- app_dice
- app_greet
Called app_dice → rolled: 4
— Cleanup —
AppHost.Close() closes the bridge. The caller closes the Client
separately.
In a real application, you’d defer these in the appropriate scope.
=== Done ===