Skip to main content
Glama
rohanprichard

MCP Events Starter

MCP Events starter

A small TypeScript server that shows a task.created event with MCP 2.0 webhooks.

The server supports protocol version 2026-07-28. It supplies server/discover, events/list, events/subscribe, events/unsubscribe, and the create_task tool at /mcp. Each event subscription uses a signed callback challenge before delivery. The server keeps subscriptions in a local JSON file. The task board stays in memory.

This repo is a starter. It uses one bearer token for all requests. It does not include OAuth, a production database, or a public plugin package.

Run the local loop

Use Node.js 20 or later. Use three terminal windows in this directory.

  1. Install the packages and make local secrets.

    npm install
    npm run setup

    The setup command writes .env once. Git ignores this file. npm install && npm run dev also starts the server without .env. In that case, the server prints a temporary bearer token.

  2. Start the MCP server in the first terminal.

    npm run dev
  3. Start the callback receiver in the second terminal.

    npm run receiver
  4. Load the local secrets in the third terminal.

    set -a
    . ./.env
    set +a
  5. Subscribe to tasks in the demo project.

    curl -sS http://127.0.0.1:3000/mcp \
      -H "Authorization: Bearer $MCP_API_KEY" \
      -H 'MCP-Protocol-Version: 2026-07-28' \
      -H 'Mcp-Method: events/subscribe' \
      -H 'Content-Type: application/json' \
      --data "$(node -e 'console.log(JSON.stringify({jsonrpc:"2.0",id:1,method:"events/subscribe",params:{name:"task.created",arguments:{project_id:"demo"},delivery:{mode:"webhook",url:"http://127.0.0.1:4000/hook",secret:process.env.WEBHOOK_SECRET},_meta:{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}))')"

    The response contains an id and refreshBefore. The receiver answers the signed, single-use challenge.

  6. Add a task.

    curl -sS http://127.0.0.1:3000/mcp \
      -H "Authorization: Bearer $MCP_API_KEY" \
      -H 'MCP-Protocol-Version: 2026-07-28' \
      -H 'Mcp-Method: tools/call' \
      -H 'Mcp-Name: create_task' \
      -H 'Content-Type: application/json' \
      --data '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"create_task","arguments":{"title":"Ship the demo","project_id":"demo"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'

    The receiver prints the task.created event. Use another project ID to test the filter. You can also add a task with POST /demo/tasks and the same bearer token.

  7. Stop the subscription.

    curl -sS http://127.0.0.1:3000/mcp \
      -H "Authorization: Bearer $MCP_API_KEY" \
      -H 'MCP-Protocol-Version: 2026-07-28' \
      -H 'Mcp-Method: events/unsubscribe' \
      -H 'Content-Type: application/json' \
      --data '{"jsonrpc":"2.0","id":3,"method":"events/unsubscribe","params":{"name":"task.created","arguments":{"project_id":"demo"},"delivery":{"mode":"webhook","url":"http://127.0.0.1:4000/hook"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'

Related MCP server: task-queue-mcp

The event contract

Call server/discover to get the supported protocol version and the events capability. Call events/list to get the event schema. Send the 2026-07-28 version in each request's _meta and MCP-Protocol-Version header. Send Mcp-Method with each request. Send Mcp-Name when you call a tool. The task.created filter has an optional project_id value. The event data contains id, title, project_id, and created_at.

The subscription ID depends on the owner, callback URL, event name, and filter. A repeated request with the same values refreshes the subscription. The server grants a lifetime of at most 24 hours and returns its end time in refreshBefore. This event does not support replay, so its cursor is null.

The server sends one event per POST. It signs the exact JSON bytes with Standard Webhooks HMAC-SHA256. Each delivery has webhook-id, webhook-timestamp, webhook-signature, and X-MCP-Subscription-Id. The body stays below 256 KiB. The server retries temporary failures twice. It does not retry HTTP 410 or 413 responses. A changed signing secret gets a five-minute rotation window. The server also caches a successful callback challenge for five minutes.

Connect to ChatGPT

Use an HTTPS tunnel, such as ngrok, to expose http://127.0.0.1:3000. The MCP URL is https://YOUR-TUNNEL/mcp. Register that URL as an MCP server in ChatGPT developer mode. Then add it to a plugin and rescan its tools and events. See the MCP Events guide and the plugin guide.

The local bearer token is for curl and other clients that can send it. ChatGPT cannot send a custom API key. A tunnel alone does not make this demo authenticate with ChatGPT. To test a live ChatGPT connection, put an OAuth 2.1 layer in front of this server or replace its token check with OAuth token checks. Use the ChatGPT authentication guide for that work. A public plugin also needs a stable HTTPS service, a plugin package, and the required review steps.

DEMO_ALLOW_HTTP_CALLBACKS=true permits only local loopback HTTP callback URLs. It lets the local receiver work. ChatGPT production requires HTTPS callbacks. For other callback URLs, the server checks public IP addresses on each connection and does not follow redirects.

Check the code

npm run build
npm test

The tests cover the signed challenge, the signature bytes, callback URL checks, subscription changes, and task fan-out.

Limits

This server has one shared owner and one process. The JSON file keeps subscriptions after a restart, but the task board does not. The server has no event replay, OAuth, delivery queue, or cross-process file lock. Add these parts before you use it for real user data or public traffic.

MIT. Copyright 2026 Rohan Richard.

Related MCP Connectors

Related MCP Servers