Skip to main content
Glama
MacJedi42

MCP App Test Value Picker Server

by MacJedi42
README.md
# MCP App Test Value Picker Server

A debug tool for **platform developers** building MCP Apps hosts. Use this MCP server to verify that your platform correctly implements the `ui/update-model-context` and `ui/message` protocols defined in the [MCP Apps Specification](https://github.com/modelcontextprotocol/ext-apps/tree/main/specification).

## Why Use This?

When building an MCP Apps host, you need to verify that:

1. **Context injection works** — Values selected in the UI iframe reach the AI model via `ui/update-model-context`
2. **Message sending works** — The UI can trigger follow-up messages via `ui/message`
3. **The complete flow works** — View → Host → Model communication is functioning end-to-end

This tool provides a simple "blind selection" test: the user picks a value in the UI, and the AI must correctly identify what was selected. If the AI knows the value, your platform is working. If not, something is broken in your `ui/update-model-context` implementation.

---

## Installation

### Via npm (Recommended)

Install from [npmjs.com/package/mcp-server-value-picker](https://www.npmjs.com/package/mcp-server-value-picker):

```bash
npm install -g mcp-server-value-picker
```

Run the server:

```bash
# Auto-detects transport mode
mcp-server-value-picker

# Force STDIO mode (for Claude Desktop)
mcp-server-value-picker --stdio

# Force HTTP mode
mcp-server-value-picker --http
```

Or run directly with npx (no install required):

```bash
npx mcp-server-value-picker
```

### Local Development

For modifying the example or contributing:

```bash
npm install
npm run build
npm start
```

Default HTTP endpoint: `http://localhost:3456/mcp`

---

## How the Test Works

1. Connect this MCP server to your platform
2. The AI calls the `pick_value` tool, which displays 10 selectable values in the UI
3. User clicks a value card
4. The UI sends `ui/update-model-context` with the selection details
5. The UI sends `ui/message` asking "I have picked a value, can you tell me what it is?"
6. **If your platform works**: The AI responds with the correct value
7. **If something is broken**: The AI won't know what was selected

### Expected Model Response

The tool description explicitly tells the AI this is a debug test. A correct response looks like:

> "You selected **Alpha Protocol** (id: alpha)."

If the AI elaborates extensively on the value meanings, the test still passed (context injection worked), but the AI didn't follow the debug instructions.

---

## What This Tests

### Platform Requirements Verified

| Your Platform Must...               | Protocol Message                 | Verified By                  |
| ----------------------------------- | -------------------------------- | ---------------------------- |
| Inject UI context into model        | `ui/update-model-context`      | AI knows selected value      |
| Forward UI messages to conversation | `ui/message`                   | AI receives follow-up prompt |
| Deliver tool results to UI          | `ui/notifications/tool-result` | UI renders value cards       |
| Pass tool arguments to UI           | `ui/notifications/tool-input`  | UI receives empty args       |
| Handle teardown gracefully          | `ui/resource-teardown`         | No errors on close           |

### Full Specification Coverage

[Full Specification](https://github.com/modelcontextprotocol/ext-apps/tree/main/specification)

| Feature                                              | Spec Section         |
| ---------------------------------------------------- | -------------------- |
| `text/html;profile=mcp-app` MIME type              | UI Resource Format   |
| `ui://` URI scheme                                 | UI Resource Format   |
| `_meta.ui.resourceUri` linkage                     | Resource Discovery   |
| `ui/initialize` / `ui/notifications/initialized` | Lifecycle            |
| `ui/notifications/tool-input`                      | Data Passing         |
| `ui/notifications/tool-result`                     | Data Passing         |
| `ui/notifications/tool-cancelled`                  | Notifications        |
| `ui/resource-teardown`                             | Cleanup              |
| `ui/update-model-context`                          | Requests             |
| `ui/message`                                       | Requests             |
| `ui/notifications/host-context-changed`            | Notifications        |
| `HostContext.theme`                                | Theming              |
| `HostContext.styles.variables`                     | Theming              |
| `HostContext.styles.css.fonts`                     | Custom Fonts         |
| `HostContext.safeAreaInsets`                       | Container Dimensions |
| `content` + `structuredContent` dual model       | Data Passing         |

---

## Files

| File                | Purpose                                                |
| ------------------- | ------------------------------------------------------ |
| `server.ts`       | MCP server with `pick_value` tool and UI resource    |
| `src/mcp-app.ts`  | View implementation (`App` class, handlers, context) |
| `src/mcp-app.css` | View styling with CSS variable fallbacks               |
| `mcp-app.html`    | HTML template with color-scheme meta tag               |
| `main.ts`         | Entry point with STDIO/HTTP transport selection        |

---

## Implementation Details

<details>
<summary>Server-side implementation</summary>

### Tool Registration with UI Metadata

```typescript
registerAppTool(server, "pick_value", {
  title: "Pick a Value",
  description: "DEBUG/TEST TOOL: Tests MCP Apps communication between UI and model...",
  inputSchema: {},
  _meta: { ui: { resourceUri: "ui://pick-value/mcp-app.html" } },
}, async () => { /* handler */ });
```

### Dual Content Model

```typescript
return {
  content: [{
    type: "text",
    text: `[MCP Apps Test] This is a debug tool...`,
  }],
  structuredContent: {
    values: VALUES,  // UI-only, not sent to model
  },
};
```

</details>

<details>
<summary>View-side implementation</summary>

### Context Update

```typescript
await app.updateModelContext({
  content: [{
    type: "text",
    text: `The user selected "${value.label}" (id: ${value.id}).`,
  }],
});
```

### Message Sending

```typescript
await app.sendMessage({
  role: "user",
  content: [{
    type: "text",
    text: `I have picked a value, can you tell me what it is?`,
  }],
});
```

### Host Context Handling

```typescript
app.onhostcontextchanged = (ctx) => {
  if (ctx.theme) applyDocumentTheme(ctx.theme);
  if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
  if (ctx.styles?.css?.fonts) applyHostFonts(ctx.styles.css.fonts);
  if (ctx.safeAreaInsets) { /* apply padding */ }
};
```

</details>

By Sebastian Whincop - https://github.com/MacJedi42