Skip to main content
Glama
coder-mil

Ask Agent

by coder-mil

Ask Agent — MCP ↔ PWA bridge

A local-first system that lets any MCP-compatible AI agent (OpenCode, Claude Code, Hermes Agent, etc.) ask the human questions in real time via a mobile-friendly PWA with browser push notifications. No Telegram, no Discord, no third-party middlemen.

┌─────────────┐    ask_user()    ┌──────────────┐    Socket.IO    ┌────────────┐
│  Agent /    │ ───────────────► │  MCP server  │ ──────────────► │   PWA in   │
│  OpenCode   │ ◄─────────────── │  (Node.js)   │ ◄────────────── │  your phone│
└─────────────┘   user's reply   └──────────────┘    answer       └────────────┘
                                       │
                                       ▼   Web Push (VAPID)
                                  ┌──────────────┐
                                  │  Browser     │
                                  │  Notification│
                                  └──────────────┘

Why?

When an agent runs for hours, it shouldn't have to guess — it should ask. This lets you stay in flow on your phone/PC while the agent waits, without being glued to a terminal or a Telegram session.

Features

  • 5 question types: free text, yes/no, multiple choice, 1–5 scale, confirm

  • Multiple concurrent questions (each with a UUID, separate timeout)

  • Real-time delivery via Socket.IO with automatic reconnect

  • Push notifications even when the PWA tab is in the background (VAPID/web-push)

  • Installable PWA (manifest + service worker + maskable icons)

  • Stateless MCP — each agent session gets its own server, scales horizontally

  • One command to run: npm start on port 3010

Quick start

npm install
npm start
# open http://localhost:3010

That's it. VAPID keys auto-generate on first boot (printed in the console — set VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT env vars to persist them).

MCP client config

OpenCode / Claude Code / Hermes Agent

Add to your MCP config (~/.config/opencode/mcp.json or equivalent):

{
  "mcpServers": {
    "ask-user": {
      "type": "http",
      "url": "http://localhost:3010/mcp"
    }
  }
}

The agent will see one tool: ask_user(question, type, options?, timeoutMs?).

GitHub Actions

Expose the server with ngrok/cloudflared and use the same URL:

- run: npm install
- run: npm start &
- run: npx ngrok http 3010 --authtoken=$NGROK_TOKEN > ngrok.log &
- run: sleep 5 && cat ngrok.log  # extract https URL

Then point your MCP client at the ngrok URL.

Question types

type

UI

Response shape

text

Textarea + Enter to submit

string

yesno

Two big buttons (Sim / Não)

true or false

multiple_choice

Grid of buttons (requires options)

string (selected option)

scale

Slider 1–5

integer 1–5

confirm

Single "OK, confirmado" button

true

Default timeout: 5 minutes. Override per call with timeoutMs.

Endpoints

Path

Purpose

GET /

The PWA

GET /health

Liveness probe

POST /mcp

MCP Streamable HTTP

GET /api/vapid-public-key

VAPID public key (for push subscribe)

POST /api/push/subscribe

Register a push subscription

POST /api/push/unsubscribe

Unregister

GET /api/push/test

Send a test push

Architecture

src/
├── server.js         Entry point: Express + Socket.IO + MCP, all on 3010
├── questionManager.js  In-memory store + EventEmitter; UUID-keyed Promises
├── mcp.js            Streamable HTTP MCP, stateless (new McpServer per session)
├── socket.js         Socket.IO ↔ QuestionManager glue
├── push.js           Web-push init + VAPID key handling
└── pushRoutes.js     REST endpoints for push subscription
public/
├── index.html        PWA shell
├── app.js            Vanilla JS frontend (Socket.IO client, push subscribe)
├── styles.css        Dark-mode mobile-first styles
├── manifest.webmanifest  PWA manifest (installable, maskable icons)
├── sw.js             Service worker (push handler + asset cache)
└── icons/            192/512/maskable SVG icons

Key design choices

  • Stateless MCP sessions: each agent that calls /mcp gets its own McpServer instance. The McpServer SDK enforces one transport per server — this is the cleanest workaround that still allows multiple concurrent agents.

  • Shared state via QuestionManager singleton: questions and answers flow through a single in-memory Map. Restart loses pending questions (intentional — these are short-lived conversational exchanges).

  • Push is best-effort: if no one is subscribed, the question still arrives via Socket.IO. Push is only for when the PWA tab isn't active.

Running in production

This is a local-first tool by design. If you want 24/7 availability:

  1. Run on a VPS with HTTPS (Cloudflare Tunnel, Caddy, or nginx + Let's Encrypt)

  2. Set VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT env vars to persist VAPID across restarts

  3. Persist subscriptions somewhere (the current in-memory Map dies on restart) — easy swap to SQLite/Redis

  4. Add auth to /api/push/* and /mcp if exposed beyond localhost

License

MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/coder-mil/ask-agent'

If you have feedback or need assistance with the MCP directory API, please join our Discord server