Skip to content

Commit 75cbf86

Browse files
authored
feat(basic-host): URL params, fullscreen mode, unified layout (#266)
* basic-host: handle containerDimensions * feat(basic-host): Unified tool call layout with shared collapsible panels * snapshots * format
1 parent 75ecd62 commit 75cbf86

File tree

33 files changed

+218
-141
lines changed

33 files changed

+218
-141
lines changed

examples/basic-host/src/implementation.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,19 @@ export type AppMessage = McpUiMessageRequest["params"];
228228
export interface AppBridgeCallbacks {
229229
onContextUpdate?: (context: ModelContext | null) => void;
230230
onMessage?: (message: AppMessage) => void;
231+
onDisplayModeChange?: (mode: "inline" | "fullscreen") => void;
232+
}
233+
234+
export interface AppBridgeOptions {
235+
containerDimensions?: { maxHeight?: number; width?: number } | { height: number; width?: number };
236+
displayMode?: "inline" | "fullscreen";
231237
}
232238

233239
export function newAppBridge(
234240
serverInfo: ServerInfo,
235241
iframe: HTMLIFrameElement,
236242
callbacks?: AppBridgeCallbacks,
243+
options?: AppBridgeOptions,
237244
): AppBridge {
238245
const serverCapabilities = serverInfo.client.getServerCapabilities();
239246
const appBridge = new AppBridge(serverInfo.client, IMPLEMENTATION, {
@@ -242,6 +249,12 @@ export function newAppBridge(
242249
serverResources: serverCapabilities?.resources,
243250
// Declare support for model context updates
244251
updateModelContext: { text: {} },
252+
}, {
253+
hostContext: {
254+
containerDimensions: options?.containerDimensions ?? { maxHeight: 600 },
255+
displayMode: options?.displayMode ?? "inline",
256+
availableDisplayModes: ["inline", "fullscreen"],
257+
},
245258
});
246259

247260
// Register all handlers before calling connect(). The Guest UI can start
@@ -308,5 +321,18 @@ export function newAppBridge(
308321
iframe.animate([from, to], { duration: 300, easing: "ease-out" });
309322
};
310323

324+
// Handle display mode change requests from the app
325+
appBridge.onrequestdisplaymode = async (params) => {
326+
log.info("Display mode request from MCP App:", params);
327+
const newMode = params.mode === "fullscreen" ? "fullscreen" : "inline";
328+
// Update host context and notify the app
329+
appBridge.sendHostContextChange({
330+
displayMode: newMode,
331+
});
332+
// Notify the host UI (via callback)
333+
callbacks?.onDisplayModeChange?.(newMode);
334+
return { mode: newMode };
335+
};
336+
311337
return appBridge;
312338
}

examples/basic-host/src/index.module.css

Lines changed: 58 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -72,65 +72,15 @@
7272

7373
.toolCallInfoPanel {
7474
display: flex;
75-
gap: 1rem;
75+
flex-direction: column;
76+
gap: 0.5rem;
7677
animation: slideDown 0.3s ease-out;
7778
}
7879

7980
@keyframes slideDown {
8081
from { opacity: 0; transform: translateY(-12px); }
8182
}
8283

83-
.inputInfoPanel {
84-
display: flex;
85-
flex: 3;
86-
flex-direction: column;
87-
gap: 0.5rem;
88-
min-width: 0;
89-
90-
h2 {
91-
display: flex;
92-
flex-direction: column;
93-
margin: 0;
94-
font-size: 1.5rem;
95-
position: relative;
96-
97-
.toolName {
98-
font-family: monospace;
99-
}
100-
101-
.closeButton {
102-
position: absolute;
103-
top: 0;
104-
right: 0;
105-
width: 1.5rem;
106-
height: 1.5rem;
107-
padding: 0;
108-
border: none;
109-
border-radius: 4px;
110-
background: #e0e0e0;
111-
font-size: 1.25rem;
112-
line-height: 1;
113-
color: #666;
114-
cursor: pointer;
115-
116-
&:hover {
117-
background: #d0d0d0;
118-
color: #333;
119-
}
120-
}
121-
}
122-
}
123-
124-
.outputInfoPanel {
125-
flex: 4;
126-
min-width: 0;
127-
}
128-
129-
.appOutputPanel {
130-
flex: 1;
131-
min-width: 0;
132-
}
133-
13484
.appHeader {
13585
display: flex;
13686
align-items: center;
@@ -166,16 +116,6 @@
166116
}
167117
}
168118

169-
.jsonBlock {
170-
flex-grow: 1;
171-
min-height: 0;
172-
margin: 0;
173-
padding: 1rem;
174-
border-radius: 4px;
175-
background-color: #f5f5f5;
176-
overflow: auto;
177-
}
178-
179119
.appIframePanel {
180120
min-height: 200px;
181121

@@ -186,6 +126,62 @@
186126
border: 3px dashed #888;
187127
border-radius: 4px;
188128
}
129+
130+
&.fullscreen {
131+
position: fixed;
132+
top: 0;
133+
left: 0;
134+
right: 0;
135+
bottom: 0;
136+
z-index: 1000;
137+
margin: 0;
138+
padding: 1rem;
139+
max-width: none;
140+
background: white;
141+
border: none;
142+
border-radius: 0;
143+
display: flex;
144+
flex-direction: column;
145+
146+
/* Hide collapsible panels in fullscreen */
147+
.collapsiblePanel {
148+
display: none;
149+
}
150+
151+
iframe {
152+
flex: 1;
153+
height: 100%;
154+
border: none;
155+
border-radius: 0;
156+
}
157+
}
158+
}
159+
160+
.appToolbar {
161+
display: flex;
162+
align-items: center;
163+
gap: 0.5rem;
164+
margin-bottom: 0.5rem;
165+
166+
.collapsiblePanel {
167+
flex: 1;
168+
margin: 0;
169+
}
170+
}
171+
172+
.fullscreenButton {
173+
width: 32px;
174+
height: 32px;
175+
padding: 0;
176+
border: 1px solid #ccc;
177+
border-radius: 4px;
178+
background: #f5f5f5;
179+
font-size: 1.25rem;
180+
cursor: pointer;
181+
182+
&:hover {
183+
background: #e0e0e0;
184+
}
189185
}
190186

191187
.collapsiblePanel {

0 commit comments

Comments
 (0)