-
Notifications
You must be signed in to change notification settings - Fork 266
Expand file tree
/
Copy pathapp-bridge.examples.ts
More file actions
523 lines (477 loc) · 14.8 KB
/
app-bridge.examples.ts
File metadata and controls
523 lines (477 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
/**
* Type-checked examples for {@link AppBridge `AppBridge`}.
*
* These examples are included in the API documentation via `@includeCode` tags.
* Each function's region markers define the code snippet that appears in the docs.
*
* @module
*/
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import {
CallToolResult,
CallToolResultSchema,
CreateMessageRequest,
CreateMessageResult,
ListResourcesResultSchema,
ReadResourceResultSchema,
ListPromptsResultSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { AppBridge, PostMessageTransport } from "./app-bridge.js";
import type { McpUiDisplayMode } from "./types.js";
/**
* Example: Basic usage of the AppBridge class with PostMessageTransport.
*/
async function AppBridge_basicUsage(serverTransport: Transport) {
//#region AppBridge_basicUsage
// Create MCP client for the server
const client = new Client({
name: "MyHost",
version: "1.0.0",
});
await client.connect(serverTransport);
// Create bridge for the View
const bridge = new AppBridge(
client,
{ name: "MyHost", version: "1.0.0" },
{ openLinks: {}, serverTools: {}, logging: {} },
);
// Set up iframe and connect
const iframe = document.getElementById("app") as HTMLIFrameElement;
const transport = new PostMessageTransport(
iframe.contentWindow!,
iframe.contentWindow!,
);
bridge.oninitialized = () => {
console.log("View initialized");
// Now safe to send tool input
bridge.sendToolInput({ arguments: { location: "NYC" } });
};
await bridge.connect(transport);
//#endregion AppBridge_basicUsage
}
/**
* Example: Creating an AppBridge with an MCP client for automatic forwarding.
*/
function AppBridge_constructor_withMcpClient(mcpClient: Client) {
//#region AppBridge_constructor_withMcpClient
const bridge = new AppBridge(
mcpClient,
{ name: "MyHost", version: "1.0.0" },
{ openLinks: {}, serverTools: {}, logging: {} },
);
//#endregion AppBridge_constructor_withMcpClient
}
/**
* Example: Creating an AppBridge without an MCP client, using manual handlers.
*/
function AppBridge_constructor_withoutMcpClient() {
//#region AppBridge_constructor_withoutMcpClient
const bridge = new AppBridge(
null,
{ name: "MyHost", version: "1.0.0" },
{ openLinks: {}, serverTools: {}, logging: {} },
);
bridge.oncalltool = async (params, extra) => {
// Handle tool calls manually
return { content: [] };
};
//#endregion AppBridge_constructor_withoutMcpClient
}
/**
* Example: Check View capabilities after initialization.
*/
function AppBridge_getAppCapabilities_checkAfterInit(bridge: AppBridge) {
//#region AppBridge_getAppCapabilities_checkAfterInit
bridge.oninitialized = () => {
const caps = bridge.getAppCapabilities();
if (caps?.tools) {
console.log("View provides tools");
}
};
//#endregion AppBridge_getAppCapabilities_checkAfterInit
}
/**
* Example: Log View information after initialization.
*/
function AppBridge_getAppVersion_logAfterInit(bridge: AppBridge) {
//#region AppBridge_getAppVersion_logAfterInit
bridge.oninitialized = () => {
const appInfo = bridge.getAppVersion();
if (appInfo) {
console.log(`View: ${appInfo.name} v${appInfo.version}`);
}
};
//#endregion AppBridge_getAppVersion_logAfterInit
}
/**
* Example: Handle View initialization and send tool input.
*/
function AppBridge_oninitialized_sendToolInput(
bridge: AppBridge,
toolArgs: Record<string, unknown>,
) {
//#region AppBridge_oninitialized_sendToolInput
bridge.oninitialized = () => {
console.log("View ready");
bridge.sendToolInput({ arguments: toolArgs });
};
//#endregion AppBridge_oninitialized_sendToolInput
}
/**
* Example: Handle message requests from the View.
*/
function AppBridge_onmessage_logMessage(bridge: AppBridge) {
//#region AppBridge_onmessage_logMessage
bridge.onmessage = async ({ role, content }, extra) => {
try {
await chatManager.addMessage({ role, content, source: "app" });
return {}; // Success
} catch (error) {
console.error("Failed to add message:", error);
return { isError: true };
}
};
//#endregion AppBridge_onmessage_logMessage
}
// Stub for example code - represents a hypothetical chat manager
declare const chatManager: {
addMessage(message: {
role: string;
content: unknown;
source: string;
}): Promise<void>;
};
// Stub for example code - represents a hypothetical URL validator
declare function isAllowedDomain(url: string): boolean;
// Stub for example code - represents a hypothetical dialog API
declare function showDialog(options: {
message: string;
buttons: string[];
}): Promise<boolean>;
// Stub for example code - represents a hypothetical model context manager
declare const modelContextManager: {
update(context: { content?: unknown; structuredContent?: unknown }): void;
};
/**
* Example: Handle external link requests from the View.
*/
function AppBridge_onopenlink_handleRequest(bridge: AppBridge) {
//#region AppBridge_onopenlink_handleRequest
bridge.onopenlink = async ({ url }, extra) => {
if (!isAllowedDomain(url)) {
console.warn("Blocked external link:", url);
return { isError: true };
}
const confirmed = await showDialog({
message: `Open external link?\n${url}`,
buttons: ["Open", "Cancel"],
});
if (confirmed) {
window.open(url, "_blank", "noopener,noreferrer");
return {};
}
return { isError: true };
};
//#endregion AppBridge_onopenlink_handleRequest
}
/**
* Example: Store model context updates from the View.
*/
function AppBridge_onupdatemodelcontext_storeContext(bridge: AppBridge) {
//#region AppBridge_onupdatemodelcontext_storeContext
bridge.onupdatemodelcontext = async (
{ content, structuredContent },
extra,
) => {
// Store the context snapshot for inclusion in the next model request
modelContextManager.update({ content, structuredContent });
return {};
};
//#endregion AppBridge_onupdatemodelcontext_storeContext
}
/**
* Example: Forward tool calls to the MCP server.
*/
function AppBridge_oncalltool_forwardToServer(
bridge: AppBridge,
mcpClient: Client,
) {
//#region AppBridge_oncalltool_forwardToServer
bridge.oncalltool = async (params, extra) => {
return mcpClient.request(
{ method: "tools/call", params },
CallToolResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_oncalltool_forwardToServer
}
/**
* Example: Forward sampling requests to your LLM provider.
*/
function AppBridge_oncreatesamplingmessage_forwardToLlm(
bridge: AppBridge,
myLlmProvider: {
complete: (
params: CreateMessageRequest["params"],
opts: { signal: AbortSignal },
) => Promise<CreateMessageResult>;
},
) {
//#region AppBridge_oncreatesamplingmessage_forwardToLlm
bridge.oncreatesamplingmessage = async (params, extra) => {
// Apply rate limiting, user approval, cost controls here
return await myLlmProvider.complete(params, { signal: extra.signal });
};
//#endregion AppBridge_oncreatesamplingmessage_forwardToLlm
}
/**
* Example: Forward list resources requests to the MCP server.
*/
function AppBridge_onlistresources_returnResources(
bridge: AppBridge,
mcpClient: Client,
) {
//#region AppBridge_onlistresources_returnResources
bridge.onlistresources = async (params, extra) => {
return mcpClient.request(
{ method: "resources/list", params },
ListResourcesResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_onlistresources_returnResources
}
/**
* Example: Forward read resource requests to the MCP server.
*/
function AppBridge_onreadresource_returnResource(
bridge: AppBridge,
mcpClient: Client,
) {
//#region AppBridge_onreadresource_returnResource
bridge.onreadresource = async (params, extra) => {
return mcpClient.request(
{ method: "resources/read", params },
ReadResourceResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_onreadresource_returnResource
}
/**
* Example: Forward list prompts requests to the MCP server.
*/
function AppBridge_onlistprompts_returnPrompts(
bridge: AppBridge,
mcpClient: Client,
) {
//#region AppBridge_onlistprompts_returnPrompts
bridge.onlistprompts = async (params, extra) => {
return mcpClient.request(
{ method: "prompts/list", params },
ListPromptsResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_onlistprompts_returnPrompts
}
/**
* Example: Handle ping requests from the View.
*/
function AppBridge_onping_handleRequest(bridge: AppBridge) {
//#region AppBridge_onping_handleRequest
bridge.onping = (params, extra) => {
console.log("Received ping from view");
};
//#endregion AppBridge_onping_handleRequest
}
/**
* Example: Handle size change notifications from the View.
*/
function AppBridge_onsizechange_handleResize(
bridge: AppBridge,
iframe: HTMLIFrameElement,
) {
//#region AppBridge_onsizechange_handleResize
bridge.onsizechange = ({ width, height }) => {
if (width != null) {
iframe.style.width = `${width}px`;
}
if (height != null) {
iframe.style.height = `${height}px`;
}
};
//#endregion AppBridge_onsizechange_handleResize
}
/**
* Example: Handle display mode requests from the View.
*/
function AppBridge_onrequestdisplaymode_handleRequest(
bridge: AppBridge,
currentDisplayMode: McpUiDisplayMode,
availableDisplayModes: McpUiDisplayMode[],
) {
//#region AppBridge_onrequestdisplaymode_handleRequest
bridge.onrequestdisplaymode = async ({ mode }, extra) => {
if (availableDisplayModes.includes(mode)) {
currentDisplayMode = mode;
}
return { mode: currentDisplayMode };
};
//#endregion AppBridge_onrequestdisplaymode_handleRequest
}
/**
* Example: Handle logging messages from the View.
*/
function AppBridge_onloggingmessage_handleLog(bridge: AppBridge) {
//#region AppBridge_onloggingmessage_handleLog
bridge.onloggingmessage = ({ level, logger, data }) => {
console[level === "error" ? "error" : "log"](
`[${logger ?? "View"}] ${level.toUpperCase()}:`,
data,
);
};
//#endregion AppBridge_onloggingmessage_handleLog
}
/**
* Example: Gracefully tear down the View before unmounting.
*/
async function AppBridge_teardownResource_gracefulShutdown(
bridge: AppBridge,
iframe: HTMLIFrameElement,
) {
//#region AppBridge_teardownResource_gracefulShutdown
try {
await bridge.teardownResource({});
// View is ready, safe to unmount iframe
iframe.remove();
} catch (error) {
console.error("Teardown failed:", error);
}
//#endregion AppBridge_teardownResource_gracefulShutdown
}
/**
* Example: Update theme when user toggles dark mode.
*/
function AppBridge_setHostContext_updateTheme(bridge: AppBridge) {
//#region AppBridge_setHostContext_updateTheme
bridge.setHostContext({ theme: "dark" });
//#endregion AppBridge_setHostContext_updateTheme
}
/**
* Example: Update multiple context fields at once.
*/
function AppBridge_setHostContext_updateMultiple(bridge: AppBridge) {
//#region AppBridge_setHostContext_updateMultiple
bridge.setHostContext({
theme: "dark",
containerDimensions: { maxHeight: 600, width: 800 },
});
//#endregion AppBridge_setHostContext_updateMultiple
}
/**
* Example: Send tool input after initialization.
*/
function AppBridge_sendToolInput_afterInit(bridge: AppBridge) {
//#region AppBridge_sendToolInput_afterInit
bridge.oninitialized = () => {
bridge.sendToolInput({
arguments: { location: "New York", units: "metric" },
});
};
//#endregion AppBridge_sendToolInput_afterInit
}
/**
* Example: Stream partial arguments as they arrive.
*/
function AppBridge_sendToolInputPartial_streaming(bridge: AppBridge) {
//#region AppBridge_sendToolInputPartial_streaming
// As streaming progresses...
bridge.sendToolInputPartial({ arguments: { loc: "N" } });
bridge.sendToolInputPartial({ arguments: { location: "New" } });
bridge.sendToolInputPartial({ arguments: { location: "New York" } });
// When complete, send final input
bridge.sendToolInput({
arguments: { location: "New York", units: "metric" },
});
//#endregion AppBridge_sendToolInputPartial_streaming
}
/**
* Example: Send tool result after execution.
*/
async function AppBridge_sendToolResult_afterExecution(
bridge: AppBridge,
mcpClient: Client,
args: Record<string, unknown>,
) {
//#region AppBridge_sendToolResult_afterExecution
const result = await mcpClient.request(
{ method: "tools/call", params: { name: "get_weather", arguments: args } },
CallToolResultSchema,
);
bridge.sendToolResult(result);
//#endregion AppBridge_sendToolResult_afterExecution
}
/**
* Example: User-initiated cancellation.
*/
function AppBridge_sendToolCancelled_userInitiated(bridge: AppBridge) {
//#region AppBridge_sendToolCancelled_userInitiated
// User clicked "Cancel" button
bridge.sendToolCancelled({ reason: "User cancelled the operation" });
//#endregion AppBridge_sendToolCancelled_userInitiated
}
/**
* Example: System-level cancellation.
*/
function AppBridge_sendToolCancelled_systemLevel(bridge: AppBridge) {
//#region AppBridge_sendToolCancelled_systemLevel
// Sampling error or timeout
bridge.sendToolCancelled({ reason: "Request timeout after 30 seconds" });
// Classifier intervention
bridge.sendToolCancelled({ reason: "Content policy violation detected" });
//#endregion AppBridge_sendToolCancelled_systemLevel
}
/**
* Example: Connect with MCP client for automatic forwarding.
*/
async function AppBridge_connect_withMcpClient(
iframe: HTMLIFrameElement,
mcpClient: Client,
hostInfo: { name: string; version: string },
capabilities: { openLinks: {}; serverTools: {}; logging: {} },
toolArgs: Record<string, unknown>,
) {
//#region AppBridge_connect_withMcpClient
const bridge = new AppBridge(mcpClient, hostInfo, capabilities);
const transport = new PostMessageTransport(
iframe.contentWindow!,
iframe.contentWindow!,
);
bridge.oninitialized = () => {
console.log("View ready");
bridge.sendToolInput({ arguments: toolArgs });
};
await bridge.connect(transport);
//#endregion AppBridge_connect_withMcpClient
}
/**
* Example: Connect without MCP client using manual handlers.
*/
async function AppBridge_connect_withoutMcpClient(
hostInfo: { name: string; version: string },
capabilities: { openLinks: {}; serverTools: {}; logging: {} },
transport: Transport,
) {
//#region AppBridge_connect_withoutMcpClient
const bridge = new AppBridge(null, hostInfo, capabilities);
// Register handlers manually
bridge.oncalltool = async (params, extra) => {
// Custom tool call handling
return { content: [] };
};
await bridge.connect(transport);
//#endregion AppBridge_connect_withoutMcpClient
}