Skip to content

Commit ea8ddfb

Browse files
committed
feat: add readServerResource() and listServerResources() api to App class
Introduced methods enable apps to read individual resources and discover available resources from the MCP server via host proxy. Changes: - Add readServerResource() method to App class for reading resources by URI - Add listServerResources() method to App class for resource discovery - Add type-checked examples in app.examples.ts with proper region markers - Add JSDoc with @example tags linking to companion examples Example enhancements (video-resource-server): - Replace data URIs with Object URLs + URL.revokeObjectURL() to prevent memory leaks - Implement ResourceTemplate list callback for dynamic resource enumeration - Add video picker UI with resource discovery and selection - Add 'Change Video' button to switch between videos - Refactor duplicate resource-fetching logic into shared fetchAndPlayVideo() helper - Preserve video playback state when showing picker CSS improvements (video-resource-server): - Migrate to host style variables (--color-*, --spacing-*, --font-*) - Use light-dark() function for theme-aware colors - Add proper focus-visible outlines for accessibility
1 parent 0bbbfee commit ea8ddfb

File tree

7 files changed

+459
-40
lines changed

7 files changed

+459
-40
lines changed

examples/video-resource-server/mcp-app.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@
2121
<div id="player" class="player" style="display: none;">
2222
<video id="video" controls></video>
2323
<p id="video-info" class="video-info"></p>
24+
<button id="change-video-btn" class="change-btn">Change Video</button>
25+
</div>
26+
27+
<div id="video-picker-container" class="picker-container" style="display: none;">
28+
<h3>Available Videos</h3>
29+
<select id="video-picker" class="video-picker">
30+
<option value="">Select a video...</option>
31+
</select>
32+
<button id="load-video-btn" class="load-btn" disabled>Load Video</button>
2433
</div>
2534
</main>
2635
<script type="module" src="/src/mcp-app.ts"></script>

examples/video-resource-server/server.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,16 @@ export function createServer(): McpServer {
6969

7070
server.registerResource(
7171
"video",
72-
new ResourceTemplate("videos://{id}", { list: undefined }),
72+
new ResourceTemplate("videos://{id}", {
73+
list: async () => ({
74+
resources: Object.entries(VIDEO_LIBRARY).map(([id, video]) => ({
75+
uri: `videos://${id}`,
76+
name: `video-${id}`,
77+
description: `Video: ${video.description}`,
78+
mimeType: "video/mp4",
79+
})),
80+
}),
81+
}),
7382
{
7483
description: "Video served via MCP resource (base64 blob)",
7584
mimeType: "video/mp4",
Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,94 @@
1+
:root {
2+
color-scheme: light dark;
3+
4+
/*
5+
* Fallbacks for host style variables used by this app.
6+
* The host may provide these (and many more) via the host context.
7+
*/
8+
--color-text-primary: light-dark(#1f2937, #f3f4f6);
9+
--color-text-inverse: light-dark(#f3f4f6, #1f2937);
10+
--color-text-info: light-dark(#1d4ed8, #60a5fa);
11+
--color-background-primary: light-dark(#ffffff, #1a1a1a);
12+
--color-background-inverse: light-dark(#1a1a1a, #ffffff);
13+
--color-background-info: light-dark(#eff6ff, #1e3a5f);
14+
--color-ring-primary: light-dark(#3b82f6, #60a5fa);
15+
--border-radius-md: 6px;
16+
--border-width-regular: 1px;
17+
--font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
18+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
19+
--font-weight-normal: 400;
20+
--font-weight-bold: 700;
21+
--font-text-md-size: 1rem;
22+
--font-text-md-line-height: 1.5;
23+
--font-heading-3xl-size: 2.25rem;
24+
--font-heading-3xl-line-height: 1.1;
25+
--font-heading-2xl-size: 1.875rem;
26+
--font-heading-2xl-line-height: 1.2;
27+
--font-heading-xl-size: 1.5rem;
28+
--font-heading-xl-line-height: 1.25;
29+
--font-heading-lg-size: 1.25rem;
30+
--font-heading-lg-line-height: 1.3;
31+
--font-heading-md-size: 1rem;
32+
--font-heading-md-line-height: 1.4;
33+
--font-heading-sm-size: 0.875rem;
34+
--font-heading-sm-line-height: 1.4;
35+
36+
/* Spacing derived from host typography */
37+
--spacing-unit: var(--font-text-md-size);
38+
--spacing-xs: calc(var(--spacing-unit) * 0.25);
39+
--spacing-sm: calc(var(--spacing-unit) * 0.5);
40+
--spacing-md: var(--spacing-unit);
41+
--spacing-lg: calc(var(--spacing-unit) * 1.5);
42+
43+
/* App accent color (customize for your brand) */
44+
--color-accent: #2563eb;
45+
--color-text-on-accent: #ffffff;
46+
}
47+
148
* {
249
box-sizing: border-box;
350
}
451

552
html, body {
6-
font-family: system-ui, -apple-system, sans-serif;
7-
font-size: 1rem;
53+
font-family: var(--font-sans);
54+
font-size: var(--font-text-md-size);
55+
font-weight: var(--font-weight-normal);
56+
line-height: var(--font-text-md-line-height);
57+
color: var(--color-text-primary);
858
margin: 0;
959
padding: 0;
1060
}
61+
62+
h1 {
63+
font-size: var(--font-heading-3xl-size);
64+
line-height: var(--font-heading-3xl-line-height);
65+
}
66+
h2 {
67+
font-size: var(--font-heading-2xl-size);
68+
line-height: var(--font-heading-2xl-line-height);
69+
}
70+
h3 {
71+
font-size: var(--font-heading-xl-size);
72+
line-height: var(--font-heading-xl-line-height);
73+
}
74+
h4 {
75+
font-size: var(--font-heading-lg-size);
76+
line-height: var(--font-heading-lg-line-height);
77+
}
78+
h5 {
79+
font-size: var(--font-heading-md-size);
80+
line-height: var(--font-heading-md-line-height);
81+
}
82+
h6 {
83+
font-size: var(--font-heading-sm-size);
84+
line-height: var(--font-heading-sm-line-height);
85+
}
86+
87+
code, pre, kbd {
88+
font-family: var(--font-mono);
89+
font-size: 1em;
90+
}
91+
92+
b, strong {
93+
font-weight: var(--font-weight-bold);
94+
}

examples/video-resource-server/src/mcp-app.css

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@
88
flex-direction: column;
99
align-items: center;
1010
justify-content: center;
11-
padding: 2rem;
12-
gap: 1rem;
13-
color: #6b7280;
11+
padding: var(--spacing-lg);
12+
gap: var(--spacing-md);
13+
color: var(--color-text-info);
1414
}
1515

1616
.spinner {
1717
width: 48px;
1818
height: 48px;
19-
border: 4px solid #e5e7eb;
20-
border-top-color: #3b82f6;
19+
border: 4px solid color-mix(in srgb, var(--color-text-primary) 20%, transparent);
20+
border-top-color: var(--color-accent);
2121
border-radius: 50%;
2222
animation: spin 1s linear infinite;
2323
}
@@ -27,13 +27,13 @@
2727
}
2828

2929
.error {
30-
padding: 1rem;
30+
padding: var(--spacing-md);
3131
color: #dc2626;
3232
}
3333

3434
.error-title {
35-
font-weight: 600;
36-
margin: 0 0 0.5rem 0;
35+
font-weight: var(--font-weight-bold);
36+
margin: 0 0 var(--spacing-sm) 0;
3737
}
3838

3939
.error p {
@@ -43,12 +43,75 @@
4343
.player video {
4444
width: 100%;
4545
max-height: 480px;
46-
border-radius: 8px;
46+
border-radius: var(--border-radius-md);
4747
background-color: #000;
4848
}
4949

5050
.video-info {
51-
margin-top: 0.5rem;
51+
margin-top: var(--spacing-sm);
5252
font-size: 0.875rem;
53-
color: #6b7280;
53+
color: var(--color-text-info);
54+
}
55+
56+
.picker-container {
57+
padding: var(--spacing-md);
58+
}
59+
60+
.picker-container h3 {
61+
margin: 0 0 var(--spacing-md) 0;
62+
font-size: var(--font-heading-lg-size);
63+
font-weight: var(--font-weight-bold);
64+
}
65+
66+
.video-picker {
67+
width: 100%;
68+
padding: var(--spacing-sm);
69+
font-size: var(--font-text-md-size);
70+
border: var(--border-width-regular) solid color-mix(in srgb, var(--color-text-primary) 20%, transparent);
71+
border-radius: var(--border-radius-md);
72+
margin-bottom: var(--spacing-md);
73+
background-color: var(--color-background-primary);
74+
color: var(--color-text-primary);
75+
font-family: var(--font-sans);
76+
}
77+
78+
@media (prefers-color-scheme: dark) {
79+
.video-picker option {
80+
background-color: var(--color-background-info);
81+
color: var(--color-text-primary);
82+
}
83+
}
84+
85+
.load-btn,
86+
.change-btn {
87+
padding: var(--spacing-sm) var(--spacing-md);
88+
font-size: var(--font-text-md-size);
89+
font-weight: var(--font-weight-bold);
90+
color: var(--color-text-on-accent);
91+
background-color: var(--color-accent);
92+
border: none;
93+
border-radius: var(--border-radius-md);
94+
cursor: pointer;
95+
transition: background-color 0.2s;
96+
}
97+
98+
.load-btn:hover:not(:disabled),
99+
.change-btn:hover {
100+
background-color: color-mix(in srgb, var(--color-accent) 85%, var(--color-background-inverse));
101+
}
102+
103+
.load-btn:focus-visible,
104+
.change-btn:focus-visible {
105+
outline: calc(var(--border-width-regular) * 2) solid var(--color-ring-primary);
106+
outline-offset: var(--border-width-regular);
107+
}
108+
109+
.load-btn:disabled {
110+
background-color: color-mix(in srgb, var(--color-text-primary) 40%, transparent);
111+
cursor: not-allowed;
112+
opacity: 0.6;
113+
}
114+
115+
.change-btn {
116+
margin-top: var(--spacing-md);
54117
}

0 commit comments

Comments
 (0)