mcp-server-value-picker 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +450 -0
- package/dist/mcp-app.html +1 -1
- package/dist/server.js +6 -5
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
# Value Picker Server
|
|
2
|
+
|
|
3
|
+
A **debug/test MCP server** for validating that values selected in an MCP App UI are correctly passed to the AI model via `ui/update-model-context`. This package tests the complete View ↔ Host ↔ Model communication flow defined in [SEP-1865](../../specification/2026-01-26/apps.mdx).
|
|
4
|
+
|
|
5
|
+
The tool explicitly tells the AI model that this is a test — the model should simply confirm which value it received, not analyze or elaborate on the selection.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
This example validates MCP Apps protocol features across all three layers:
|
|
10
|
+
|
|
11
|
+
- **Server**: Tool registration, UI resource serving, `structuredContent` data passing
|
|
12
|
+
- **Host**: Context injection, message proxying, lifecycle notifications
|
|
13
|
+
- **View**: Theme handling, safe areas, tool result rendering, model context updates
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Server-Side Features Tested
|
|
18
|
+
|
|
19
|
+
### Tool Registration with UI Metadata
|
|
20
|
+
|
|
21
|
+
Uses `registerAppTool()` to associate a tool with a UI resource via `_meta.ui.resourceUri`:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// From server.ts
|
|
25
|
+
registerAppTool(server, "pick_value", {
|
|
26
|
+
title: "Pick a Value",
|
|
27
|
+
description: "DEBUG/TEST TOOL: Tests MCP Apps communication between UI and model. The user picks a value in the UI, and you must confirm whether you received it. This validates that ui/update-model-context is working correctly. Do not treat this as a real decision — just report what value you received.",
|
|
28
|
+
inputSchema: {},
|
|
29
|
+
outputSchema: z.object({
|
|
30
|
+
values: z.array(z.object({
|
|
31
|
+
id: z.string(),
|
|
32
|
+
label: z.string(),
|
|
33
|
+
description: z.string(),
|
|
34
|
+
})),
|
|
35
|
+
}),
|
|
36
|
+
_meta: { ui: { resourceUri: "ui://pick-value/mcp-app.html" } },
|
|
37
|
+
}, async () => { /* handler */ });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Spec Reference** ([Resource Discovery](../../specification/2026-01-26/apps.mdx#resource-discovery)):
|
|
41
|
+
```typescript
|
|
42
|
+
interface Tool {
|
|
43
|
+
_meta?: {
|
|
44
|
+
ui?: {
|
|
45
|
+
resourceUri?: string; // URI of UI resource for rendering
|
|
46
|
+
visibility?: Array<"model" | "app">;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### UI Resource Registration
|
|
53
|
+
|
|
54
|
+
Uses `registerAppResource()` to serve HTML content with the required MIME type:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// From server.ts
|
|
58
|
+
registerAppResource(server, resourceUri, resourceUri,
|
|
59
|
+
{ mimeType: RESOURCE_MIME_TYPE }, // "text/html;profile=mcp-app"
|
|
60
|
+
async () => ({
|
|
61
|
+
contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }],
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Spec Reference** ([UI Resource Format](../../specification/2026-01-26/apps.mdx#ui-resource-format)):
|
|
67
|
+
> `mimeType` MUST be `text/html;profile=mcp-app`
|
|
68
|
+
|
|
69
|
+
### Dual Content Model (`content` + `structuredContent`)
|
|
70
|
+
|
|
71
|
+
Tool returns both model-facing text and UI-facing structured data:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// From server.ts
|
|
75
|
+
return {
|
|
76
|
+
content: [{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: `[MCP Apps Test] This is a debug tool for testing value communication...
|
|
79
|
+
Your job: Simply report back the value you received. This tests whether the MCP Apps context injection is working. Do not provide detailed analysis of the values — just confirm what was selected.`,
|
|
80
|
+
}],
|
|
81
|
+
structuredContent: {
|
|
82
|
+
values: VALUES, // Array of {id, label, description}
|
|
83
|
+
instruction: "Wait for the user to select a value via the UI.",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Spec Reference** ([Data Passing](../../specification/2026-01-26/apps.mdx#data-passing)):
|
|
89
|
+
> - `content`: Text representation for model context and text-only hosts
|
|
90
|
+
> - `structuredContent`: Structured data optimized for UI rendering (not added to model context)
|
|
91
|
+
|
|
92
|
+
### Transport Modes
|
|
93
|
+
|
|
94
|
+
Supports both STDIO and Streamable HTTP transports with auto-detection:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// From main.ts
|
|
98
|
+
function resolveTransport(): "stdio" | "http" {
|
|
99
|
+
if (process.argv.includes("--stdio")) return "stdio";
|
|
100
|
+
if (process.argv.includes("--http")) return "http";
|
|
101
|
+
return process.stdin.isTTY ? "http" : "stdio";
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## View-Side Features Tested
|
|
108
|
+
|
|
109
|
+
### App Initialization & Connection
|
|
110
|
+
|
|
111
|
+
Uses the `App` class with `PostMessageTransport` to connect to the Host:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// From src/mcp-app.ts
|
|
115
|
+
const app = new App({ name: "Value Picker", version: "1.0.0" });
|
|
116
|
+
await app.connect();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Spec Reference** ([Transport Layer](../../specification/2026-01-26/apps.mdx#transport-layer)):
|
|
120
|
+
```typescript
|
|
121
|
+
const transport = new MessageTransport(window.parent);
|
|
122
|
+
const client = new Client({ name: "ui-view", version: "1.0.0" });
|
|
123
|
+
await client.connect(transport);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Host Context Handling
|
|
127
|
+
|
|
128
|
+
Retrieves and responds to host context after connection:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// From src/mcp-app.ts
|
|
132
|
+
app.connect().then(() => {
|
|
133
|
+
const ctx = app.getHostContext();
|
|
134
|
+
if (ctx) handleHostContextChanged(ctx);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
app.onhostcontextchanged = handleHostContextChanged;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Spec Reference** ([Host Context in McpUiInitializeResult](../../specification/2026-01-26/apps.mdx#host-context-in-mcpuiinitializeresult)):
|
|
141
|
+
```typescript
|
|
142
|
+
interface HostContext {
|
|
143
|
+
theme?: "light" | "dark";
|
|
144
|
+
styles?: { variables?: Record<string, string>; css?: { fonts?: string } };
|
|
145
|
+
safeAreaInsets?: { top: number; right: number; bottom: number; left: number };
|
|
146
|
+
// ... other fields
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Theme Application
|
|
151
|
+
|
|
152
|
+
Applies the host's color scheme preference:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// From src/mcp-app.ts
|
|
156
|
+
if (ctx.theme) {
|
|
157
|
+
applyDocumentTheme(ctx.theme);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Spec Reference** ([Theming](../../specification/2026-01-26/apps.mdx#theming)):
|
|
162
|
+
> Views can use the `applyDocumentTheme` utility to easily respond to Host Context `theme` changes
|
|
163
|
+
|
|
164
|
+
### CSS Variable Injection
|
|
165
|
+
|
|
166
|
+
Applies host-provided CSS custom properties for visual cohesion:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// From src/mcp-app.ts
|
|
170
|
+
if (ctx.styles?.variables) {
|
|
171
|
+
applyHostStyleVariables(ctx.styles.variables);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Spec Reference** ([Theming - Current Standardized Variables](../../specification/2026-01-26/apps.mdx#current-standardized-variables)):
|
|
176
|
+
```typescript
|
|
177
|
+
type McpUiStyleVariableKey =
|
|
178
|
+
| "--color-background-primary"
|
|
179
|
+
| "--color-text-primary"
|
|
180
|
+
| "--font-sans"
|
|
181
|
+
// ... 80+ standardized variables
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Custom Font Loading
|
|
185
|
+
|
|
186
|
+
Injects host-provided font CSS (e.g., `@font-face` or `@import`):
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// From src/mcp-app.ts
|
|
190
|
+
if (ctx.styles?.css?.fonts) {
|
|
191
|
+
applyHostFonts(ctx.styles.css.fonts);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Spec Reference** ([Custom Fonts](../../specification/2026-01-26/apps.mdx#custom-fonts)):
|
|
196
|
+
> Hosts can provide custom fonts via `styles.css.fonts`
|
|
197
|
+
|
|
198
|
+
### Safe Area Insets
|
|
199
|
+
|
|
200
|
+
Respects host-provided padding for notches, toolbars, etc.:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// From src/mcp-app.ts
|
|
204
|
+
if (ctx.safeAreaInsets) {
|
|
205
|
+
mainEl.style.paddingTop = `${ctx.safeAreaInsets.top}px`;
|
|
206
|
+
mainEl.style.paddingRight = `${ctx.safeAreaInsets.right}px`;
|
|
207
|
+
mainEl.style.paddingBottom = `${ctx.safeAreaInsets.bottom}px`;
|
|
208
|
+
mainEl.style.paddingLeft = `${ctx.safeAreaInsets.left}px`;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Tool Result Handler
|
|
213
|
+
|
|
214
|
+
Receives structured data via `ui/notifications/tool-result`:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// From src/mcp-app.ts
|
|
218
|
+
app.ontoolresult = (result) => {
|
|
219
|
+
const structured = result.structuredContent as { values?: Value[] } | undefined;
|
|
220
|
+
if (structured?.values) {
|
|
221
|
+
renderValues(structured.values);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Spec Reference** ([ui/notifications/tool-result](../../specification/2026-01-26/apps.mdx#notifications-host--view)):
|
|
227
|
+
```typescript
|
|
228
|
+
{
|
|
229
|
+
jsonrpc: "2.0",
|
|
230
|
+
method: "ui/notifications/tool-result",
|
|
231
|
+
params: CallToolResult // { content, structuredContent, _meta }
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Tool Input Handler
|
|
236
|
+
|
|
237
|
+
Receives tool call arguments (empty in this case):
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// From src/mcp-app.ts
|
|
241
|
+
app.ontoolinput = (params) => {
|
|
242
|
+
console.info("Received tool input:", params);
|
|
243
|
+
};
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Spec Reference** ([ui/notifications/tool-input](../../specification/2026-01-26/apps.mdx#notifications-host--view)):
|
|
247
|
+
```typescript
|
|
248
|
+
{
|
|
249
|
+
jsonrpc: "2.0",
|
|
250
|
+
method: "ui/notifications/tool-input",
|
|
251
|
+
params: { arguments: Record<string, unknown> }
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Tool Cancellation Handler
|
|
256
|
+
|
|
257
|
+
Handles cancelled tool execution:
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// From src/mcp-app.ts
|
|
261
|
+
app.ontoolcancelled = (params) => {
|
|
262
|
+
statusEl.textContent = "Tool call was cancelled";
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Spec Reference** ([ui/notifications/tool-cancelled](../../specification/2026-01-26/apps.mdx#notifications-host--view)):
|
|
267
|
+
> Host MUST send this notification if the tool execution was cancelled, for any reason
|
|
268
|
+
|
|
269
|
+
### Teardown Handler
|
|
270
|
+
|
|
271
|
+
Gracefully handles resource teardown:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// From src/mcp-app.ts
|
|
275
|
+
app.onteardown = async () => {
|
|
276
|
+
console.info("Value Picker app is being torn down");
|
|
277
|
+
return {};
|
|
278
|
+
};
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Spec Reference** ([ui/resource-teardown](../../specification/2026-01-26/apps.mdx#notifications-host--view)):
|
|
282
|
+
> Host MUST send this notification before tearing down the UI resource
|
|
283
|
+
|
|
284
|
+
### Update Model Context
|
|
285
|
+
|
|
286
|
+
Injects the user's selection into the model's context:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// From src/mcp-app.ts
|
|
290
|
+
await app.updateModelContext({
|
|
291
|
+
content: [{
|
|
292
|
+
type: "text",
|
|
293
|
+
text: `The user selected "${value.label}" (id: ${value.id}). Description: ${value.description}.`,
|
|
294
|
+
}],
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Spec Reference** ([ui/update-model-context](../../specification/2026-01-26/apps.mdx#requests-view--host)):
|
|
299
|
+
```typescript
|
|
300
|
+
{
|
|
301
|
+
jsonrpc: "2.0",
|
|
302
|
+
method: "ui/update-model-context",
|
|
303
|
+
params: {
|
|
304
|
+
content?: ContentBlock[],
|
|
305
|
+
structuredContent?: Record<string, unknown>
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
> The View MAY send this request to update the Host's model context. This context will be used in future turns.
|
|
311
|
+
|
|
312
|
+
### Send User Message
|
|
313
|
+
|
|
314
|
+
Triggers an AI response after selection:
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// From src/mcp-app.ts
|
|
318
|
+
await app.sendMessage({
|
|
319
|
+
role: "user",
|
|
320
|
+
content: [{
|
|
321
|
+
type: "text",
|
|
322
|
+
text: `I have picked a value, can you tell me what it is?`,
|
|
323
|
+
}],
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Spec Reference** ([ui/message](../../specification/2026-01-26/apps.mdx#requests-view--host)):
|
|
328
|
+
```typescript
|
|
329
|
+
{
|
|
330
|
+
jsonrpc: "2.0",
|
|
331
|
+
method: "ui/message",
|
|
332
|
+
params: {
|
|
333
|
+
role: "user",
|
|
334
|
+
content: { type: "text", text: string }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
> Host SHOULD add the message to the conversation context, preserving the specified role.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Test Pattern: "Blind Selection"
|
|
344
|
+
|
|
345
|
+
This example implements a "blind selection" test pattern to validate context injection:
|
|
346
|
+
|
|
347
|
+
1. User clicks a value card in the UI
|
|
348
|
+
2. View sends `ui/update-model-context` with the selection details
|
|
349
|
+
3. View sends `ui/message` with "I have picked a value, can you tell me what it is?"
|
|
350
|
+
4. The AI must read the context to respond with the correct value
|
|
351
|
+
|
|
352
|
+
This proves that `updateModelContext` successfully injects data that the model can access, without the user message containing the answer.
|
|
353
|
+
|
|
354
|
+
### Expected Model Behavior
|
|
355
|
+
|
|
356
|
+
The tool description explicitly tells the model this is a debug/test tool. The model should:
|
|
357
|
+
|
|
358
|
+
- **Do**: Simply confirm which value it received (e.g., "You selected Alpha Protocol")
|
|
359
|
+
- **Don't**: Provide detailed analysis, recommendations, or elaborate on the value meanings
|
|
360
|
+
|
|
361
|
+
If the model goes into detail about the values, the test still passes (context injection worked), but the model didn't follow the debug instructions.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Installation
|
|
366
|
+
|
|
367
|
+
### Via npm (Recommended)
|
|
368
|
+
|
|
369
|
+
Install the published package from [npmjs.com/package/mcp-server-value-picker](https://www.npmjs.com/package/mcp-server-value-picker):
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
npm install -g mcp-server-value-picker
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Run the server:
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
# Run (auto-detects transport mode)
|
|
379
|
+
mcp-server-value-picker
|
|
380
|
+
|
|
381
|
+
# Force STDIO mode (for Claude Desktop)
|
|
382
|
+
mcp-server-value-picker --stdio
|
|
383
|
+
|
|
384
|
+
# Force HTTP mode
|
|
385
|
+
mcp-server-value-picker --http
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Or run directly with npx (no install required):
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
npx mcp-server-value-picker
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Local Development
|
|
395
|
+
|
|
396
|
+
For modifying the example or contributing:
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
# Install dependencies
|
|
400
|
+
npm install
|
|
401
|
+
|
|
402
|
+
# Build UI and server
|
|
403
|
+
npm run build
|
|
404
|
+
|
|
405
|
+
# Run (auto-detects transport mode)
|
|
406
|
+
npm start
|
|
407
|
+
|
|
408
|
+
# Force STDIO mode
|
|
409
|
+
npm start -- --stdio
|
|
410
|
+
|
|
411
|
+
# Force HTTP mode
|
|
412
|
+
npm start -- --http
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Default HTTP endpoint: `http://localhost:3456/mcp`
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Files
|
|
420
|
+
|
|
421
|
+
| File | Purpose |
|
|
422
|
+
|------|---------|
|
|
423
|
+
| `server.ts` | MCP server with `pick_value` tool and UI resource |
|
|
424
|
+
| `src/mcp-app.ts` | View implementation (`App` class, handlers, context) |
|
|
425
|
+
| `src/mcp-app.css` | View styling with CSS variable fallbacks |
|
|
426
|
+
| `mcp-app.html` | HTML template with color-scheme meta tag |
|
|
427
|
+
| `main.ts` | Entry point with STDIO/HTTP transport selection |
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Specification Coverage
|
|
432
|
+
|
|
433
|
+
| Feature | Spec Section | Tested |
|
|
434
|
+
|---------|--------------|--------|
|
|
435
|
+
| `text/html;profile=mcp-app` MIME type | UI Resource Format | ✓ |
|
|
436
|
+
| `ui://` URI scheme | UI Resource Format | ✓ |
|
|
437
|
+
| `_meta.ui.resourceUri` linkage | Resource Discovery | ✓ |
|
|
438
|
+
| `ui/initialize` / `ui/notifications/initialized` | Lifecycle | ✓ |
|
|
439
|
+
| `ui/notifications/tool-input` | Data Passing | ✓ |
|
|
440
|
+
| `ui/notifications/tool-result` | Data Passing | ✓ |
|
|
441
|
+
| `ui/notifications/tool-cancelled` | Notifications | ✓ |
|
|
442
|
+
| `ui/resource-teardown` | Cleanup | ✓ |
|
|
443
|
+
| `ui/update-model-context` | Requests | ✓ |
|
|
444
|
+
| `ui/message` | Requests | ✓ |
|
|
445
|
+
| `ui/notifications/host-context-changed` | Notifications | ✓ |
|
|
446
|
+
| `HostContext.theme` | Theming | ✓ |
|
|
447
|
+
| `HostContext.styles.variables` | Theming | ✓ |
|
|
448
|
+
| `HostContext.styles.css.fonts` | Custom Fonts | ✓ |
|
|
449
|
+
| `HostContext.safeAreaInsets` | Container Dimensions | ✓ |
|
|
450
|
+
| `content` + `structuredContent` dual model | Data Passing | ✓ |
|
package/dist/mcp-app.html
CHANGED
|
@@ -106,7 +106,7 @@ for compatibility with Zod schema generation. Both are functionally equivalent f
|
|
|
106
106
|
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:m.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:m.string().optional().describe("User's timezone in IANA format."),userAgent:m.string().optional().describe("Host application identifier."),platform:m.union([m.literal("web"),m.literal("desktop"),m.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:m.object({touch:m.boolean().optional().describe("Whether the device supports touch input."),hover:m.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:m.object({top:m.number().describe("Top safe area inset in pixels."),right:m.number().describe("Right safe area inset in pixels."),bottom:m.number().describe("Bottom safe area inset in pixels."),left:m.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),yI=m.object({method:m.literal("ui/notifications/host-context-changed"),params:Ff.describe("Partial context update containing only changed fields.")});m.object({method:m.literal("ui/update-model-context"),params:m.object({content:m.array(At).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:m.record(m.string(),m.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});m.object({method:m.literal("ui/initialize"),params:m.object({appInfo:Rn.describe("App identification (name and version)."),appCapabilities:gI.describe("Features and capabilities this app provides."),protocolVersion:m.string().describe("Protocol version this app supports.")})});var kI=m.object({protocolVersion:m.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Rn.describe("Host application identification and version."),hostCapabilities:hI.describe("Features and capabilities provided by the host."),hostContext:Ff.describe("Rich context about the host environment.")}).passthrough();function wI(e){let t=document.documentElement;t.setAttribute("data-theme",e),t.style.colorScheme=e}function II(e,t=document.documentElement){for(let[i,r]of Object.entries(e))r!==void 0&&t.style.setProperty(i,r)}function SI(e){if(document.getElementById("__mcp-host-fonts"))return;let t=document.createElement("style");t.id="__mcp-host-fonts",t.textContent=e,document.head.appendChild(t)}class zI extends Ib{constructor(i,r={},n={autoResize:!0}){super(n);se(this,"_appInfo");se(this,"_capabilities");se(this,"options");se(this,"_hostCapabilities");se(this,"_hostInfo");se(this,"_hostContext");se(this,"sendOpenLink",this.openLink);this._appInfo=i,this._capabilities=r,this.options=n,this.setRequestHandler(An,o=>(console.log("Received ping:",o.params),{})),this.onhostcontextchanged=()=>{}}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}set ontoolinput(i){this.setNotificationHandler(cI,r=>i(r.params))}set ontoolinputpartial(i){this.setNotificationHandler(dI,r=>i(r.params))}set ontoolresult(i){this.setNotificationHandler(bI,r=>i(r.params))}set ontoolcancelled(i){this.setNotificationHandler(mI,r=>i(r.params))}set onhostcontextchanged(i){this.setNotificationHandler(yI,r=>{this._hostContext={...this._hostContext,...r.params},i(r.params)})}set onteardown(i){this.setRequestHandler(vI,(r,n)=>i(r.params,n))}set oncalltool(i){this.setRequestHandler(Qs,(r,n)=>i(r.params,n))}set onlisttools(i){this.setRequestHandler(Ys,(r,n)=>i(r.params,n))}assertCapabilityForMethod(i){}assertRequestHandlerCapability(i){switch(i){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${i})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${i} registered`)}}assertNotificationCapability(i){}assertTaskCapability(i){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(i){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(i,r){return await this.request({method:"tools/call",params:i},Cn,r)}sendMessage(i,r){return this.request({method:"ui/message",params:i},lI,r)}sendLog(i){return this.notification({method:"notifications/message",params:i})}updateModelContext(i,r){return this.request({method:"ui/update-model-context",params:i},er,r)}openLink(i,r){return this.request({method:"ui/open-link",params:i},uI,r)}requestDisplayMode(i,r){return this.request({method:"ui/request-display-mode",params:i},_I,r)}sendSizeChanged(i){return this.notification({method:"ui/notifications/size-changed",params:i})}setupSizeChangedNotifications(){let i=!1,r=0,n=0,o=()=>{i||(i=!0,requestAnimationFrame(()=>{i=!1;let s=document.documentElement,u=s.style.width,l=s.style.height;s.style.width="fit-content",s.style.height="fit-content";let c=s.getBoundingClientRect();s.style.width=u,s.style.height=l;let d=window.innerWidth-s.clientWidth,f=Math.ceil(c.width+d),h=Math.ceil(c.height);(f!==r||h!==n)&&(r=f,n=h,this.sendSizeChanged({width:f,height:h}))}))};o();let a=new ResizeObserver(o);return a.observe(document.documentElement),a.observe(document.body),()=>a.disconnect()}async connect(i=new zb(window.parent,window.parent),r){var n;await super.connect(i);try{let o=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:xb}},kI,r);if(o===void 0)throw Error(`Server sent invalid initialize result: ${o}`);this._hostCapabilities=o.hostCapabilities,this._hostInfo=o.hostInfo,this._hostContext=o.hostContext,await this.notification({method:"ui/notifications/initialized"}),(n=this.options)!=null&&n.autoResize&&this.setupSizeChangedNotifications()}catch(o){throw this.close(),o}}}const gn=document.querySelector(".main"),ts=document.getElementById("values-grid"),Ue=document.getElementById("status");let ns=null;function qf(e){var t,i,r;e.theme&&wI(e.theme),(t=e.styles)!=null&&t.variables&&II(e.styles.variables),(r=(i=e.styles)==null?void 0:i.css)!=null&&r.fonts&&SI(e.styles.css.fonts),e.safeAreaInsets&&(gn.style.paddingTop=`${e.safeAreaInsets.top}px`,gn.style.paddingRight=`${e.safeAreaInsets.right}px`,gn.style.paddingBottom=`${e.safeAreaInsets.bottom}px`,gn.style.paddingLeft=`${e.safeAreaInsets.left}px`)}function xI(e){ts.innerHTML="";for(const t of e){const i=document.createElement("button");i.className="value-card",i.dataset.id=t.id,i.innerHTML=`
|
|
107
107
|
<span class="value-label">${t.label}</span>
|
|
108
108
|
<span class="value-desc">${t.description}</span>
|
|
109
|
-
`,i.addEventListener("click",()=>OI(t)),ts.appendChild(i)}}async function OI(e){ns=e.id,document.querySelectorAll(".value-card").forEach(t=>{t.classList.toggle("selected",t.dataset.id===e.id)}),console.info(`Selection changed to: ${ns}`),Ue.textContent=`Selected: ${e.label} — sending to AI...`,Ue.className="status sending";try{await Pe.updateModelContext({content:[{type:"text",text:`The user selected "${e.label}" (id: ${e.id}). Description: ${e.description}. Please acknowledge their selection and provide relevant information about this choice.`}]}),(await Pe.sendMessage({role:"user",content:[{type:"text",text
|
|
109
|
+
`,i.addEventListener("click",()=>OI(t)),ts.appendChild(i)}}async function OI(e){ns=e.id,document.querySelectorAll(".value-card").forEach(t=>{t.classList.toggle("selected",t.dataset.id===e.id)}),console.info(`Selection changed to: ${ns}`),Ue.textContent=`Selected: ${e.label} — sending to AI...`,Ue.className="status sending";try{await Pe.updateModelContext({content:[{type:"text",text:`The user selected "${e.label}" (id: ${e.id}). Description: ${e.description}. Please acknowledge their selection and provide relevant information about this choice.`}]}),(await Pe.sendMessage({role:"user",content:[{type:"text",text:"I have picked a value, can you tell me what it is?"}]})).isError?(Ue.textContent=`Selected: ${e.label} (context updated, message send was rejected)`,Ue.className="status warning"):(Ue.textContent=`Selected: ${e.label} — AI has been notified!`,Ue.className="status success")}catch(t){console.error("Failed to send selection:",t),Ue.textContent=`Selected: ${e.label} — failed to notify AI`,Ue.className="status error"}}const Pe=new zI({name:"Value Picker",version:"1.0.0"});Pe.onteardown=async()=>(console.info("Value Picker app is being torn down"),{});Pe.ontoolinput=e=>{console.info("Received tool input:",e)};Pe.ontoolresult=e=>{console.info("Received tool result:",e);const t=e.structuredContent;t!=null&&t.values&&xI(t.values)};Pe.ontoolcancelled=e=>{console.info("Tool call cancelled:",e.reason),Ue.textContent="Tool call was cancelled",Ue.className="status warning"};Pe.onerror=e=>{console.error("App error:",e)};Pe.onhostcontextchanged=qf;Pe.connect().then(()=>{const e=Pe.getHostContext();e&&qf(e)});</script>
|
|
110
110
|
<style rel="stylesheet" crossorigin>*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:var(--host-background, #ffffff);color:var(--host-foreground, #1a1a1a);min-height:100vh}@media(prefers-color-scheme:dark){body{background:var(--host-background, #1a1a1a);color:var(--host-foreground, #e5e5e5)}}.main{padding:1rem;max-width:600px;margin:0 auto}.title{font-size:1.1rem;font-weight:600;margin-bottom:.25rem}.subtitle{font-size:.85rem;opacity:.7;margin-bottom:1rem}.values-grid{display:grid;grid-template-columns:1fr 1fr;gap:.5rem}.value-card{display:flex;flex-direction:column;align-items:flex-start;gap:.2rem;padding:.75rem;border:1.5px solid rgba(128,128,128,.25);border-radius:8px;background:transparent;color:inherit;cursor:pointer;text-align:left;transition:all .15s ease;font-family:inherit}.value-card:hover{border-color:#3b82f680;background:#3b82f60d}.value-card.selected{border-color:#3b82f6;background:#3b82f61a;box-shadow:0 0 0 1px #3b82f6}.value-label{font-weight:600;font-size:.85rem}.value-desc{font-size:.75rem;opacity:.65}.status{margin-top:1rem;padding:.5rem .75rem;border-radius:6px;font-size:.8rem;min-height:2rem}.status:empty{display:none}.status.sending{background:#3b82f61a;color:#3b82f6}.status.success{background:#22c55e1a;color:#16a34a}.status.warning{background:#eab3081a;color:#ca8a04}.status.error{background:#ef44441a;color:#dc2626}</style>
|
|
111
111
|
</head>
|
|
112
112
|
<body>
|
package/dist/server.js
CHANGED
|
@@ -35779,7 +35779,7 @@ function createServer() {
|
|
|
35779
35779
|
const resourceUri = "ui://pick-value/mcp-app.html";
|
|
35780
35780
|
hk(server, "pick_value", {
|
|
35781
35781
|
title: "Pick a Value",
|
|
35782
|
-
description: "
|
|
35782
|
+
description: "DEBUG/TEST TOOL: Tests MCP Apps communication between UI and model. The user picks a value in the UI, and you must confirm whether you received it. This validates that ui/update-model-context is working correctly. Do not treat this as a real decision — just report what value you received.",
|
|
35783
35783
|
inputSchema: {},
|
|
35784
35784
|
outputSchema: exports_external.object({
|
|
35785
35785
|
values: exports_external.array(exports_external.object({
|
|
@@ -35794,12 +35794,13 @@ function createServer() {
|
|
|
35794
35794
|
return {
|
|
35795
35795
|
content: [{
|
|
35796
35796
|
type: "text",
|
|
35797
|
-
text: `
|
|
35797
|
+
text: `[MCP Apps Test] This is a debug tool for testing value communication between the UI and the model.
|
|
35798
35798
|
|
|
35799
|
-
|
|
35800
|
-
`)}
|
|
35799
|
+
The user will select one of 10 test values via the interactive UI. Their selection will be injected into your context via ui/update-model-context. The user will then ask you to confirm which value you received.
|
|
35801
35800
|
|
|
35802
|
-
|
|
35801
|
+
Your job: Simply report back the value you received. This tests whether the MCP Apps context injection is working. Do not provide detailed analysis of the values — just confirm what was selected.
|
|
35802
|
+
|
|
35803
|
+
Test values: ${VALUES.map((v) => v.label).join(", ")}`
|
|
35803
35804
|
}],
|
|
35804
35805
|
structuredContent: {
|
|
35805
35806
|
values: VALUES,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-server-value-picker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Debug/test MCP App: Validates that values selected in UI are correctly passed to the AI model via ui/update-model-context",
|
|
6
6
|
"main": "dist/server.js",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist"
|