npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gtmi/mcp-status-callback

v2.1.0

Published

Public HTTPS callback URLs for local development via a Twilio-account-authenticated WSS relay. No ngrok, no local HTTP listener.

Readme

@gtmi/mcp-status-callback

Public HTTPS callback URLs for local development via a Twilio-account-authenticated WSS relay. No ngrok, no local HTTP listener.

The client opens an outbound WSS to a callback relay (see packages/relay for the reference deploy) and receives a public URL of the shape:

https://<relayHost>/callback/<AccountSid>/<sqid>
https://<relayHost>/callback/<AccountSid>/named/<subscriptionName>

Twilio (or any webhook source) POSTs to that URL; the relay forwards the request as a JSON frame over the open WS, the client invokes your onCallback, and the relay returns 200 to Twilio after you ack.

Installation

pnpm add @gtmi/mcp-status-callback

Requirements

  • Node.js 22 or higher
  • A Twilio Account SID + API Key + Secret
  • A running relay. By default the client targets the reference relay at callback-relay.fly.dev, which is access-controlled — only allowlisted Twilio accounts may connect. If your account isn't allowlisted you'll get a 403 on connect; deploy your own relay (see docs/FLY-DEPLOY.md) and point MCP_CALLBACK_RELAY_HOST at it.

Usage

import { CallbackHandler } from '@gtmi/mcp-status-callback';

const handler = new CallbackHandler({
    twilioAccountSid: process.env.TWILIO_ACCOUNT_SID!,
    twilioApiKey: process.env.TWILIO_API_KEY!,
    twilioApiSecret: process.env.TWILIO_API_SECRET!,
    subscriptionName: 'sms-status', // optional — omit for ephemeral mode
    logger: console,
    onCallback: async ({ queryParameters, body }) => {
        // handle callback (async allowed — the 200 to Twilio waits for you)
    },
});

const url = await handler.start();
// e.g. https://callback-relay.fly.dev/callback/AC.../named/sms-status

Using an OAuth Bearer token instead of an API Key

If you already authenticate to the Twilio REST API with an Account OAuth token, you can reuse it here instead of provisioning a separate API Key — pass a token-provider function via authorization and omit twilioApiKey/twilioApiSecret:

const handler = new CallbackHandler({
    twilioAccountSid: process.env.TWILIO_ACCOUNT_SID!,
    authorization: async () => 'Bearer ' + (await provider.getToken()),
    onCallback: async ({ queryParameters, body }) => {
        // handle callback
    },
});

authorization is called fresh on every connect and reconnect, so returning a token from your own refresh logic keeps the WSS session authenticated across reconnects without any extra plumbing in this package. The client never mints or refreshes tokens itself — that stays with whatever already manages your Twilio OAuth credential (e.g. the twilio SDK's ClientCredentialProvider).

Using it inside an MCP server

This is the primary use case. An MCP server running on your laptop needs a public URL so Twilio can POST status callbacks (delivery receipts, call events, …) back to it. The subtlety is timing: a tool call like "send an SMS" returns immediately — the message is only queued — but the delivery status arrives seconds to minutes later. So you don't return the status from the tool; you collect callbacks as they arrive and feed them back to the model through an MCP resource (or a resource-updated / logging notification).

        LLM / MCP client
              │  (calls your tool, e.g. "send an SMS")
              ▼
   ┌─────────────────────┐   ① start() ⇒ public URL     ┌──────────────────────┐
   │                     │ ───────────────────────────▶ │   callback-relay      │
   │   your MCP server   │       outbound WSS (open)     │       (Fly.io)        │
   │   • CallbackHandler │ ◀───────────────────────────  │                      │
   │   • onCallback()    │   ④ callback frame over WSS   │  pairs POSTs ⇆ WSS    │
   └─────────┬───────────┘                               └──────────▲───────────┘
             │  ② messages.create({ statusCallback: url })          │
             ▼                                                       │ ③ POST status
        ┌──────────┐                                                 │   (queued→delivered)
        │  Twilio  │ ───────── sends SMS, then POSTs status ─────────┘
        └──────────┘

   ⑤ onCallback stores each event → your server exposes it back to the model
      (e.g. an MCP resource:  twilio://status-callbacks/recent)
import { CallbackHandler } from '@gtmi/mcp-status-callback';
import twilio from 'twilio';

const twilioClient = twilio(process.env.TWILIO_API_KEY!, process.env.TWILIO_API_SECRET!, {
    accountSid: process.env.TWILIO_ACCOUNT_SID!,
});

// A small buffer of recent callbacks the server exposes back to the model.
const recent: unknown[] = [];

const callbacks = new CallbackHandler({
    twilioAccountSid: process.env.TWILIO_ACCOUNT_SID!,
    twilioApiKey: process.env.TWILIO_API_KEY!,
    twilioApiSecret: process.env.TWILIO_API_SECRET!,
    subscriptionName: 'twilio-mcp', // stable URL across restarts
    onCallback: async ({ body }) => {
        recent.unshift(body); // e.g. { MessageStatus: 'delivered', MessageSid: 'SM…' }
        recent.length = Math.min(recent.length, 50);
    },
});

// ① Open the WSS once on startup and keep the public URL.
const callbackUrl = await callbacks.start();

// ② A tool that sends an SMS, wiring Twilio's statusCallback to our URL.
server.registerTool('send_sms', schema, async ({ to, from, body }) => {
    const msg = await twilioClient.messages.create({ to, from, body, statusCallback: callbackUrl });
    return { content: [{ type: 'text', text: `queued ${msg.sid}` }] };
});

// ③–⑤ Twilio POSTs each status → relay → onCallback → `recent`.
// Surface the async results back to the model as a resource it can read on demand.
server.registerResource('twilio://status-callbacks/recent', async () => ({
    contents: [{ uri: 'twilio://status-callbacks/recent', text: JSON.stringify(recent, null, 2) }],
}));

The server.registerTool / registerResource calls are illustrative — use your MCP SDK's real API. The wiring is the point: start() once → put callbackUrl on your Twilio calls → collect onCallback events → hand them back through a resource or notification (never straight out of the tool call, since the status lands long after the tool returns).

API

new CallbackHandler(options)

  • twilioAccountSid (required) — Twilio AccountSid (AC...).
  • twilioApiKey — Twilio API Key SID (SK...). Required unless authorization is provided.
  • twilioApiSecret — Twilio API Key Secret. Required unless authorization is provided.
  • authorization (optional) — () => string | Promise<string>. Returns the full Authorization header value (e.g. "Bearer eyJ…"). Invoked fresh on every connect/reconnect. Takes precedence over twilioApiKey/twilioApiSecret when present.
  • subscriptionName (optional) — Stable name. Sets the URL to .../named/<name> and survives reconnects. Omit for ephemeral (fresh sqid per session).
  • relayHost (optional) — Host of the callback relay. Falls back to process.env.MCP_CALLBACK_RELAY_HOST, then callback-relay.fly.dev (the reference relay, access-controlled to allowlisted accounts). Point this at your own relay if you self-host. Do not include a scheme.
  • onCallback (required) — (data: CallbackData) => void | Promise<void>. Awaited before the relay is acked, so throwing/hanging is visible upstream.
  • logger (optional) — { info, warn, error }. Defaults to a silent no-op logger.

Methods

  • start(): Promise<string> — Opens the WSS to the relay. Resolves to the public callback URL from the relay's hello frame.
  • stop(): Promise<void> — Closes the WS and stops any pending reconnect.
  • getPublicUrl(): string | null — Current public URL, or null if not started.

Types

  • CallbackData{ queryParameters: Record<string, unknown>; body: unknown }
  • Logger{ info(msg): void; warn(msg): void; error(msg | Error): void }

Behavior

  • Reconnect. If the relay drops the WS, the client reconnects with exponential backoff up to 30s. Ephemeral mode gets a new sqid (URL changes); named mode reclaims the same URL.
  • Body normalization. application/x-www-form-urlencoded bodies (Twilio's default) are normalized to a plain JSON object on the relay side before the frame is forwarded. body is always a JSON object.
  • Async-aware ack. If your onCallback returns a Promise, the ack frame (and Twilio's 200) waits for it to resolve. Throwing sends { ok: false, error } and the relay returns 500 to Twilio.

Migrating from v1.x (ngrok)

  • Remove NGROK_AUTH_TOKEN / NGROK_CUSTOM_DOMAIN from your env.
  • Add TWILIO_ACCOUNT_SID, TWILIO_API_KEY, TWILIO_API_SECRET.
  • Constructor field renames: ngrokAuthTokentwilioAccountSid + twilioApiKey + twilioApiSecret; customDomainsubscriptionName (semantically closer — a stable URL you control).
  • onCallback and logger are unchanged.
  • If you can't migrate, the 1.x line stays on npm and its source lives at deshartman/mcp-status-callback.

Credits

The move from an ngrok tunnel to a Twilio-authenticated relay is inspired by the internal Twilio project cai-relay. Credit to that team for the pattern this package builds on.

Contributing

From the repo root:

pnpm install
pnpm typecheck
pnpm test

Smoke run against a live relay:

pnpm dlx tsx packages/client/examples/smoke.ts

License

MIT