Skip to content

Commit 63b05b4

Browse files
Merge pull request #252 from jonathanhefner/update-examples-for-skill
Improve consistency and organization of examples for AI learning
2 parents 6166242 + 4140565 commit 63b05b4

40 files changed

Lines changed: 449 additions & 363 deletions

File tree

examples/basic-host/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/

examples/basic-server-preact/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ npm run dev
2828
1. The server registers a `get-time` tool with metadata linking it to a UI HTML resource (`ui://get-time/mcp-app.html`).
2929
2. When the tool is invoked, the Host renders the UI from the resource.
3030
3. The UI uses the MCP App SDK API to communicate with the host and call server tools.
31+
32+
## Build System
33+
34+
This example bundles into a single HTML file using Vite with `vite-plugin-singlefile` — see [`vite.config.ts`](vite.config.ts). This allows all UI content to be served as a single MCP resource. Alternatively, MCP apps can load external resources by defining [`_meta.ui.csp.resourceDomains`](https://modelcontextprotocol.github.io/ext-apps/api/interfaces/app.McpUiResourceCsp.html#resourcedomains) in the UI resource metadata.

examples/basic-server-preact/src/mcp-app.tsx

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
/**
22
* @file App that demonstrates a few features using MCP Apps SDK + Preact.
33
*/
4-
import { App, type McpUiHostContext } from "@modelcontextprotocol/ext-apps";
4+
import {
5+
App,
6+
applyDocumentTheme,
7+
applyHostFonts,
8+
applyHostStyleVariables,
9+
type McpUiHostContext,
10+
} from "@modelcontextprotocol/ext-apps";
511
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
612
import { useCallback, useEffect, useState } from "preact/hooks";
713
import { render } from "preact";
814
import styles from "./mcp-app.module.css";
915

10-
11-
const IMPLEMENTATION = { name: "Get Time App", version: "1.0.0" };
12-
13-
14-
const log = {
15-
info: console.log.bind(console, "[APP]"),
16-
warn: console.warn.bind(console, "[APP]"),
17-
error: console.error.bind(console, "[APP]"),
18-
};
19-
20-
2116
function extractTime(callToolResult: CallToolResult): string {
2217
const { text } = callToolResult.content?.find((c) => c.type === "text")!;
2318
return text;
@@ -30,19 +25,36 @@ function GetTimeApp() {
3025
const [toolResult, setToolResult] = useState<CallToolResult | null>(null);
3126
const [hostContext, setHostContext] = useState<McpUiHostContext | undefined>();
3227

28+
// Apply host styles reactively when hostContext changes
29+
useEffect(() => {
30+
if (hostContext?.theme) {
31+
applyDocumentTheme(hostContext.theme);
32+
}
33+
if (hostContext?.styles?.variables) {
34+
applyHostStyleVariables(hostContext.styles.variables);
35+
}
36+
if (hostContext?.styles?.css?.fonts) {
37+
applyHostFonts(hostContext.styles.css.fonts);
38+
}
39+
}, [hostContext]);
40+
3341
useEffect(() => {
34-
const instance = new App(IMPLEMENTATION);
42+
const instance = new App({ name: "Get Time App", version: "1.0.0" });
3543

3644
instance.ontoolinput = async (input) => {
37-
log.info("Received tool call input:", input);
45+
console.info("Received tool call input:", input);
3846
};
3947

4048
instance.ontoolresult = async (result) => {
41-
log.info("Received tool call result:", result);
49+
console.info("Received tool call result:", result);
4250
setToolResult(result);
4351
};
4452

45-
instance.onerror = log.error;
53+
instance.ontoolcancelled = (params) => {
54+
console.info("Tool call cancelled:", params.reason);
55+
};
56+
57+
instance.onerror = console.error;
4658

4759
instance.onhostcontextchanged = (params) => {
4860
setHostContext((prev) => ({ ...prev, ...params }));
@@ -83,39 +95,39 @@ function GetTimeAppInner({ app, toolResult, hostContext }: GetTimeAppInnerProps)
8395

8496
const handleGetTime = useCallback(async () => {
8597
try {
86-
log.info("Calling get-time tool...");
98+
console.info("Calling get-time tool...");
8799
const result = await app.callServerTool({ name: "get-time", arguments: {} });
88-
log.info("get-time result:", result);
100+
console.info("get-time result:", result);
89101
setServerTime(extractTime(result));
90102
} catch (e) {
91-
log.error(e);
103+
console.error(e);
92104
setServerTime("[ERROR]");
93105
}
94106
}, [app]);
95107

96108
const handleSendMessage = useCallback(async () => {
97109
const signal = AbortSignal.timeout(5000);
98110
try {
99-
log.info("Sending message text to Host:", messageText);
111+
console.info("Sending message text to Host:", messageText);
100112
const { isError } = await app.sendMessage(
101113
{ role: "user", content: [{ type: "text", text: messageText }] },
102114
{ signal },
103115
);
104-
log.info("Message", isError ? "rejected" : "accepted");
116+
console.info("Message", isError ? "rejected" : "accepted");
105117
} catch (e) {
106-
log.error("Message send error:", signal.aborted ? "timed out" : e);
118+
console.error("Message send error:", signal.aborted ? "timed out" : e);
107119
}
108120
}, [app, messageText]);
109121

110122
const handleSendLog = useCallback(async () => {
111-
log.info("Sending log text to Host:", logText);
123+
console.info("Sending log text to Host:", logText);
112124
await app.sendLog({ level: "info", data: logText });
113125
}, [app, logText]);
114126

115127
const handleOpenLink = useCallback(async () => {
116-
log.info("Sending open link request to Host:", linkUrl);
128+
console.info("Sending open link request to Host:", linkUrl);
117129
const { isError } = await app.openLink({ url: linkUrl });
118-
log.info("Open link request", isError ? "rejected" : "accepted");
130+
console.info("Open link request", isError ? "rejected" : "accepted");
119131
}, [app, linkUrl]);
120132

121133
return (
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/

examples/basic-server-react/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,7 @@ npm run dev
3030
1. The server registers a `get-time` tool with metadata linking it to a UI HTML resource (`ui://get-time/mcp-app.html`).
3131
2. When the tool is invoked, the Host renders the UI from the resource.
3232
3. The UI uses the MCP App SDK API to communicate with the host and call server tools.
33+
34+
## Build System
35+
36+
This example bundles into a single HTML file using Vite with `vite-plugin-singlefile` — see [`vite.config.ts`](vite.config.ts). This allows all UI content to be served as a single MCP resource. Alternatively, MCP apps can load external resources by defining [`_meta.ui.csp.resourceDomains`](https://modelcontextprotocol.github.io/ext-apps/api/interfaces/app.McpUiResourceCsp.html#resourcedomains) in the UI resource metadata.

examples/basic-server-react/src/mcp-app.tsx

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,6 @@ import { createRoot } from "react-dom/client";
99
import styles from "./mcp-app.module.css";
1010

1111

12-
const IMPLEMENTATION = { name: "Get Time App", version: "1.0.0" };
13-
14-
15-
const log = {
16-
info: console.log.bind(console, "[APP]"),
17-
warn: console.warn.bind(console, "[APP]"),
18-
error: console.error.bind(console, "[APP]"),
19-
};
20-
21-
2212
function extractTime(callToolResult: CallToolResult): string {
2313
const { text } = callToolResult.content?.find((c) => c.type === "text")!;
2414
return text;
@@ -28,24 +18,31 @@ function extractTime(callToolResult: CallToolResult): string {
2818
function GetTimeApp() {
2919
const [toolResult, setToolResult] = useState<CallToolResult | null>(null);
3020
const [hostContext, setHostContext] = useState<McpUiHostContext | undefined>();
21+
22+
// `useApp` (1) creates an `App` instance, (2) calls `onAppCreated` to
23+
// register handlers, and (3) calls `connect()` on the `App` instance.
3124
const { app, error } = useApp({
32-
appInfo: IMPLEMENTATION,
25+
appInfo: { name: "Get Time App", version: "1.0.0" },
3326
capabilities: {},
3427
onAppCreated: (app) => {
3528
app.onteardown = async () => {
36-
log.info("App is being torn down");
29+
console.info("App is being torn down");
3730
return {};
3831
};
3932
app.ontoolinput = async (input) => {
40-
log.info("Received tool call input:", input);
33+
console.info("Received tool call input:", input);
4134
};
4235

4336
app.ontoolresult = async (result) => {
44-
log.info("Received tool call result:", result);
37+
console.info("Received tool call result:", result);
4538
setToolResult(result);
4639
};
4740

48-
app.onerror = log.error;
41+
app.ontoolcancelled = (params) => {
42+
console.info("Tool call cancelled:", params.reason);
43+
};
44+
45+
app.onerror = console.error;
4946

5047
app.onhostcontextchanged = (params) => {
5148
setHostContext((prev) => ({ ...prev, ...params }));
@@ -85,39 +82,39 @@ function GetTimeAppInner({ app, toolResult, hostContext }: GetTimeAppInnerProps)
8582

8683
const handleGetTime = useCallback(async () => {
8784
try {
88-
log.info("Calling get-time tool...");
85+
console.info("Calling get-time tool...");
8986
const result = await app.callServerTool({ name: "get-time", arguments: {} });
90-
log.info("get-time result:", result);
87+
console.info("get-time result:", result);
9188
setServerTime(extractTime(result));
9289
} catch (e) {
93-
log.error(e);
90+
console.error(e);
9491
setServerTime("[ERROR]");
9592
}
9693
}, [app]);
9794

9895
const handleSendMessage = useCallback(async () => {
9996
const signal = AbortSignal.timeout(5000);
10097
try {
101-
log.info("Sending message text to Host:", messageText);
98+
console.info("Sending message text to Host:", messageText);
10299
const { isError } = await app.sendMessage(
103100
{ role: "user", content: [{ type: "text", text: messageText }] },
104101
{ signal },
105102
);
106-
log.info("Message", isError ? "rejected" : "accepted");
103+
console.info("Message", isError ? "rejected" : "accepted");
107104
} catch (e) {
108-
log.error("Message send error:", signal.aborted ? "timed out" : e);
105+
console.error("Message send error:", signal.aborted ? "timed out" : e);
109106
}
110107
}, [app, messageText]);
111108

112109
const handleSendLog = useCallback(async () => {
113-
log.info("Sending log text to Host:", logText);
110+
console.info("Sending log text to Host:", logText);
114111
await app.sendLog({ level: "info", data: logText });
115112
}, [app, logText]);
116113

117114
const handleOpenLink = useCallback(async () => {
118-
log.info("Sending open link request to Host:", linkUrl);
115+
console.info("Sending open link request to Host:", linkUrl);
119116
const { isError } = await app.openLink({ url: linkUrl });
120-
log.info("Open link request", isError ? "rejected" : "accepted");
117+
console.info("Open link request", isError ? "rejected" : "accepted");
121118
}, [app, linkUrl]);
122119

123120
return (
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/

examples/basic-server-solid/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ npm run dev
2828
1. The server registers a `get-time` tool with metadata linking it to a UI HTML resource (`ui://get-time/mcp-app.html`).
2929
2. When the tool is invoked, the Host renders the UI from the resource.
3030
3. The UI uses the MCP App SDK API to communicate with the host and call server tools.
31+
32+
## Build System
33+
34+
This example bundles into a single HTML file using Vite with `vite-plugin-singlefile` — see [`vite.config.ts`](vite.config.ts). This allows all UI content to be served as a single MCP resource. Alternatively, MCP apps can load external resources by defining [`_meta.ui.csp.resourceDomains`](https://modelcontextprotocol.github.io/ext-apps/api/interfaces/app.McpUiResourceCsp.html#resourcedomains) in the UI resource metadata.

examples/basic-server-solid/src/mcp-app.tsx

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
/**
22
* @file App that demonstrates a few features using MCP Apps SDK + Solid.
33
*/
4-
import { App, type McpUiHostContext } from "@modelcontextprotocol/ext-apps";
4+
import {
5+
App,
6+
applyDocumentTheme,
7+
applyHostFonts,
8+
applyHostStyleVariables,
9+
type McpUiHostContext,
10+
} from "@modelcontextprotocol/ext-apps";
511
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
612
import { createEffect, createSignal, onMount, Show } from "solid-js";
713
import { render } from "solid-js/web";
814
import styles from "./mcp-app.module.css";
915

10-
11-
const IMPLEMENTATION = { name: "Get Time App", version: "1.0.0" };
12-
13-
14-
const log = {
15-
info: console.log.bind(console, "[APP]"),
16-
warn: console.warn.bind(console, "[APP]"),
17-
error: console.error.bind(console, "[APP]"),
18-
};
19-
20-
2116
function extractTime(callToolResult: CallToolResult): string {
2217
const { text } = callToolResult.content?.find((c) => c.type === "text")!;
2318
return text;
@@ -30,19 +25,37 @@ function GetTimeApp() {
3025
const [toolResult, setToolResult] = createSignal<CallToolResult | null>(null);
3126
const [hostContext, setHostContext] = createSignal<McpUiHostContext | undefined>();
3227

28+
// Apply host styles reactively when hostContext changes
29+
createEffect(() => {
30+
const ctx = hostContext();
31+
if (ctx?.theme) {
32+
applyDocumentTheme(ctx.theme);
33+
}
34+
if (ctx?.styles?.variables) {
35+
applyHostStyleVariables(ctx.styles.variables);
36+
}
37+
if (ctx?.styles?.css?.fonts) {
38+
applyHostFonts(ctx.styles.css.fonts);
39+
}
40+
});
41+
3342
onMount(async () => {
34-
const instance = new App(IMPLEMENTATION);
43+
const instance = new App({ name: "Get Time App", version: "1.0.0" });
3544

3645
instance.ontoolinput = async (input) => {
37-
log.info("Received tool call input:", input);
46+
console.info("Received tool call input:", input);
3847
};
3948

4049
instance.ontoolresult = async (result) => {
41-
log.info("Received tool call result:", result);
50+
console.info("Received tool call result:", result);
4251
setToolResult(result);
4352
};
4453

45-
instance.onerror = log.error;
54+
instance.ontoolcancelled = (params) => {
55+
console.info("Tool call cancelled:", params.reason);
56+
};
57+
58+
instance.onerror = console.error;
4659

4760
instance.onhostcontextchanged = (params) => {
4861
setHostContext((prev) => ({ ...prev, ...params }));
@@ -87,39 +100,39 @@ function GetTimeAppInner(props: GetTimeAppInnerProps) {
87100

88101
async function handleGetTime() {
89102
try {
90-
log.info("Calling get-time tool...");
103+
console.info("Calling get-time tool...");
91104
const result = await props.app.callServerTool({ name: "get-time", arguments: {} });
92-
log.info("get-time result:", result);
105+
console.info("get-time result:", result);
93106
setServerTime(extractTime(result));
94107
} catch (e) {
95-
log.error(e);
108+
console.error(e);
96109
setServerTime("[ERROR]");
97110
}
98111
}
99112

100113
async function handleSendMessage() {
101114
const signal = AbortSignal.timeout(5000);
102115
try {
103-
log.info("Sending message text to Host:", messageText());
116+
console.info("Sending message text to Host:", messageText());
104117
const { isError } = await props.app.sendMessage(
105118
{ role: "user", content: [{ type: "text", text: messageText() }] },
106119
{ signal },
107120
);
108-
log.info("Message", isError ? "rejected" : "accepted");
121+
console.info("Message", isError ? "rejected" : "accepted");
109122
} catch (e) {
110-
log.error("Message send error:", signal.aborted ? "timed out" : e);
123+
console.error("Message send error:", signal.aborted ? "timed out" : e);
111124
}
112125
}
113126

114127
async function handleSendLog() {
115-
log.info("Sending log text to Host:", logText());
128+
console.info("Sending log text to Host:", logText());
116129
await props.app.sendLog({ level: "info", data: logText() });
117130
}
118131

119132
async function handleOpenLink() {
120-
log.info("Sending open link request to Host:", linkUrl());
133+
console.info("Sending open link request to Host:", linkUrl());
121134
const { isError } = await props.app.openLink({ url: linkUrl() });
122-
log.info("Open link request", isError ? "rejected" : "accepted");
135+
console.info("Open link request", isError ? "rejected" : "accepted");
123136
}
124137

125138
return (

0 commit comments

Comments
 (0)