Skip to main content
Glama
mwilber

Joke Button MCP Server

by mwilber
README.md
# MCP App Experiments

This project is a tiny MCP server with three MCP App experiments:

- **Joke Button** sends a message to the host asking the assistant for a short joke.
- **Popup Messenger** presents one button labeled **Open Sesame**, opens a separate message window, and lets the assistant send text into that window through an MCP tool.
- **Colorific** displays a color square, lets the assistant translate natural-language colors into hex values, and asks the assistant for a creative color name when clicked.

It intentionally uses only Node.js built-in modules. There are no runtime package dependencies.

## What It Demonstrates

- MCP core protocol version `2026-07-28`, with legacy Streamable HTTP compatibility for the 2025 revisions
- Stateless Streamable HTTP on one endpoint: `/mcp`
- `server/discover`, `tools/list`, `tools/call`, `resources/list`, and `resources/read`
- MCP Apps UI resource metadata with `_meta.ui.resourceUri`
- A sandbox-friendly `text/html;profile=mcp-app` widget
- The MCP Apps `ui/initialize` lifecycle, host context, size reporting, and `ui/message` over `postMessage`
- Tool-result-driven UI updates through `ui/notifications/tool-result`
- Decoupled Colorific render and data tools that update one mounted app panel
- Model-to-window messaging through `send_popup_message` and an ephemeral server-side channel
- Standards-based `ui/open-link` fallback when a host iframe blocks direct popups

## Popup Feasibility and Design

MCP Apps run in host-controlled sandboxed iframes. The stable MCP Apps permissions cover camera, microphone, geolocation, and clipboard write, but do not provide a way for an app to request the iframe `allow-popups` sandbox token. Popup behavior is therefore host-dependent.

Popup Messenger attempts `window.open()` synchronously from the **Open Sesame** click, which produces a sized popup in browsers that allow it. If the sandbox blocks that call, the app requests `ui/open-link`; compatible hosts should open the same URL in the default browser or a new tab. A true popup cannot be guaranteed across all MCP hosts.

The model-to-popup path does not depend on direct iframe access:

1. The app generates an unguessable channel ID and registers it with the app-only `register_popup_channel` tool.
2. The app gives that channel ID to the model with `ui/update-model-context` and `ui/message`.
3. The model calls `send_popup_message` with the channel ID and its text.
4. The server queues the message in memory, and the standalone window polls the server and displays it.

This completes the message requirements on a single server process. Channels expire after two hours and keep at most 100 messages. Because storage is in memory, channels do not survive a restart and are not shared across multiple server instances. A durable shared store would be required for production scaling.

## Colorific Interaction

Call `show_colorific` once to open a transparent square and create an ephemeral game ID. When a user asks to see a color, the assistant converts the request to a six-digit hex value and calls `set_colorific_color` with that active game ID:

```text
Show me electric lavender.
```

Only `show_colorific` owns the UI resource, so later color changes do not render more app panels. The mounted app polls the app-only `get_colorific_state` tool and repaints itself when the server-side revision changes. It also publishes the active game ID through `ui/update-model-context` so the assistant can keep routing later requests to the same game.

Clicking the square sends a follow-up request asking the assistant to reply with the exact current value and an original two-to-four-word color name.

Colorific game state is held in memory, expires after two hours of inactivity, and does not survive server restarts or span multiple server processes. Production scaling would require shared storage.

## Requirements

Install Node.js 20 or newer from [nodejs.org](https://nodejs.org/). The LTS download is fine.

You do not need to run `npm install`; this project has no third-party libraries.

## Start the Server

Open a terminal in this folder and run:

```powershell
npm start
```

You should see:

```text
MCP App Experiments server listening on http://127.0.0.1:8787/mcp
Preview Joke Button at http://127.0.0.1:8787/preview
Preview Popup Messenger at http://127.0.0.1:8787/preview/popup-messenger
Preview Colorific at http://127.0.0.1:8787/preview/colorific
```

Leave that terminal open while you test.

MCP requests are logged by method while the server is running. For an interactive app render,
the log should include a successful UI resource read similar to:

```text
[mcp] request method=resources/read name=ui://joke-button/app.html
[mcp] response method=resources/read status=200
```

Set `MCP_LOG_REQUESTS=false` if you want to disable these development logs.

## Heroku Deployment

The `joke-button` Heroku app is deployed at:

```text
https://joke-button-31cddc4bc964.herokuapp.com
```

Its MCP endpoint is:

```text
https://joke-button-31cddc4bc964.herokuapp.com/mcp
```

Deploy the current branch with:

```powershell
git push heroku HEAD:main
```

## Quick Local Check

Open this address in a browser:

```text
http://127.0.0.1:8787/preview
```

Preview Popup Messenger at:

```text
http://127.0.0.1:8787/preview/popup-messenger
```

Preview Colorific at:

```text
http://127.0.0.1:8787/preview/colorific
```

The Popup Messenger preview can open its standalone window, but only an MCP Apps host can ask the assistant to send messages into it.

## Use with ChatGPT Desktop or ChatGPT Web

ChatGPT needs an HTTPS URL for a remote MCP server during development. Keep `npm start` running, then expose port `8787` with a tunnel tool such as ngrok or Cloudflare Tunnel.

Example with ngrok:

```powershell
ngrok http 8787
```

Copy the HTTPS forwarding URL and add `/mcp` to the end, for example:

```text
https://example.ngrok.app/mcp
```

Then connect it in ChatGPT:

1. Open ChatGPT.
2. Open Settings.
3. Open Security and login.
4. Turn on Developer mode if it is not already enabled.
5. Open Plugins. If you are in the desktop app and do not see it in the sidebar, open `https://chatgpt.com/plugins` in a browser while signed in to the same account.
6. Select the plus button to add a developer MCP server connection.
7. Paste the HTTPS URL ending in `/mcp`.
8. Name it `MCP App Experiments`.
9. Start a new chat, select the plugin from the More menu, and ask: `Show the Popup Messenger.`
10. Click `Open Sesame`.
11. The app asks the assistant to send a welcome message. Ask for more with a prompt such as: `Send another message to the open popup saying hello again.`

You do not need a setting named `MCP servers` for this ChatGPT UI test. That setting is for Codex's local MCP host configuration, not for adding a ChatGPT developer plugin connection.

If Developer mode or the plus button is missing, check that the desktop app is up to date and that your account or workspace allows plugin development.

If the app is working, ChatGPT should receive the button's message and respond with a short joke.

## Use with Codex in the ChatGPT Desktop App

Codex can connect to Streamable HTTP MCP servers from the shared MCP settings.

The `MCP servers` setting appears only on Codex-capable local clients and may not appear in a regular ChatGPT chat settings view. If it is available:

1. Keep `npm start` running.
2. Open the ChatGPT desktop app.
3. Open Settings, then MCP servers.
4. Select Add server.
5. Choose Streamable HTTP.
6. Name the server `joke_button`.
7. Use this URL:

```text
http://127.0.0.1:8787/mcp
```

8. Save, then restart when prompted.
9. In a Codex task, type `/mcp` to confirm the server is connected.
10. Ask Codex: `Use the Joke Button MCP server.`

Codex MCP tool support may show the text fallback rather than rendering the iframe UI, depending on the surface. For the actual one-button UI, use ChatGPT with MCP Apps UI support.

If the setting is not visible, copy the sample configuration from `codex-config.example.toml` into a trusted project's `.codex/config.toml` or into your user `~/.codex/config.toml`, then restart the ChatGPT desktop app or Codex client.

## Useful URLs

- Health check: `http://127.0.0.1:8787/health`
- MCP endpoint: `http://127.0.0.1:8787/mcp`
- Joke Button preview: `http://127.0.0.1:8787/preview`
- Popup Messenger preview: `http://127.0.0.1:8787/preview/popup-messenger`
- Colorific preview: `http://127.0.0.1:8787/preview/colorific`

## Security Notes

The server validates the protocol version and mirrored MCP headers required by the `2026-07-28` Streamable HTTP transport. The same endpoint also accepts the legacy `initialize` handshake used by protocol versions `2025-03-26`, `2025-06-18`, and `2025-11-25`.

For local development, requests without an `Origin` header are accepted, loopback browser origins are accepted, and common OpenAI host origins are accepted. For another host origin, start the server with:

```powershell
$env:MCP_ALLOWED_ORIGINS="https://your-host.example"
npm start
```

Use authentication before exposing a real MCP server that can access private data or perform actions. This sample does neither.

## Project Files

- `server.js`: dependency-free MCP server
- `public/joke-button.html`: MCP App UI widget
- `public/popup-messenger.html`: Open Sesame MCP App UI
- `public/popup-window.html`: standalone assistant-message window
- `public/colorific.html`: Colorific MCP App UI
- `codex-config.example.toml`: sample Codex MCP configuration