Skip to main content
Glama
virtualsms-io

VirtualSMS MCP Server

wait_for_sms_code

Purchase a virtual phone number and automatically wait for SMS verification codes from services like Telegram or WhatsApp. Uses WebSocket delivery with polling fallback and provides recovery options for timeout scenarios.

Instructions

RECOMMENDED: One-step tool that buys a number AND waits for the SMS code automatically. Uses real-time WebSocket delivery with automatic polling fallback. Always returns order_id in the response — even on timeout — so you can use check_sms to recover.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
serviceYesService code (e.g. "telegram", "whatsapp", "google")
countryYesCountry ISO code (e.g. "US", "GB", "RU")
timeout_secondsNoHow long to wait for SMS code in seconds (default: 120, max: 600)

Implementation Reference

  • The handler for `wait_for_sms_code`. It handles buying the number and then waiting for an SMS via WebSocket, with a fallback to polling.
    export async function handleWaitForCode(
      client: VirtualSMSClient,
      args: z.infer<typeof WaitForCodeInput>
    ) {
      const timeoutMs = (args.timeout_seconds ?? 120) * 1000;
      const pollIntervalMs = 5000;
      const startTime = Date.now();
    
      // Step 1: Buy the number
      let order;
      try {
        order = await client.createOrder(args.service, args.country);
      } catch (err) {
        throw new Error(`Failed to buy number: ${(err as Error).message}`);
      }
    
      const orderId = order.order_id;
      const phoneNumber = order.phone_number;
      const apiKey = client.getApiKey();
      const baseUrl = client.getBaseUrl();
    
      // Step 2: Try WebSocket first (if we have an API key)
      if (apiKey) {
        const remainingMs = timeoutMs - (Date.now() - startTime);
        const wsResult = await waitForSMSViaWebSocket(baseUrl, apiKey, orderId, remainingMs);
    
        if (wsResult) {
          return {
            content: [
              {
                type: 'text' as const,
                text: JSON.stringify(
                  {
                    success: true,
                    phone_number: phoneNumber,
                    sms_code: wsResult.sms_code,
                    sms_text: wsResult.sms_text,
                    order_id: orderId,
                    delivery_method: wsResult.delivery_method,
                    elapsed_seconds: Math.round((Date.now() - startTime) / 1000),
                  },
                  null,
                  2
                ),
              },
            ],
          };
        }
        // WS timed out or failed — fall through to polling
      }
    
      // Step 3: Polling fallback
      let attempts = 0;
      while (Date.now() - startTime < timeoutMs) {
        attempts++;
    
        try {
          const status = await client.getOrder(orderId);
    
          if (status.sms_code) {
            return {
              content: [
                {
                  type: 'text' as const,
                  text: JSON.stringify(
                    {
                      success: true,
                      phone_number: phoneNumber,
                      sms_code: status.sms_code,
                      sms_text: status.sms_text,
                      order_id: orderId,
                      delivery_method: 'polling',
                      elapsed_seconds: Math.round((Date.now() - startTime) / 1000),
                      poll_attempts: attempts,
                    },
                    null,
                    2
                  ),
                },
              ],
            };
          }
    
          if (status.status === 'cancelled' || status.status === 'failed') {
            throw new Error(
              `Order ${orderId} was ${status.status} before SMS arrived.`
            );
          }
        } catch (err) {
          const message = (err as Error).message;
          if (!message.includes('waiting') && !message.includes('pending')) {
            throw err;
          }
        }
    
        const remaining = timeoutMs - (Date.now() - startTime);
        if (remaining <= 0) break;
        await sleep(Math.min(pollIntervalMs, remaining));
      }
    
      // Timeout — return order_id for crash recovery (don't cancel automatically)
      return {
        content: [
          {
            type: 'text' as const,
            text: JSON.stringify(
              {
                success: false,
                error: 'timeout',
                message: `No SMS received within ${args.timeout_seconds} seconds.`,
                order_id: orderId,
                phone_number: phoneNumber,
                tip: 'Use check_sms with this order_id to check if code arrived later, or cancel_order to get a refund.',
              },
              null,
              2
            ),
          },
        ],
      };
    }
  • Input schema for the `wait_for_sms_code` tool.
    export const WaitForCodeInput = z.object({
      service: z.string().describe('Service code (e.g. "telegram", "whatsapp", "google")'),
      country: z.string().describe('Country ISO code (e.g. "US", "GB", "RU")'),
      timeout_seconds: z.number()
        .int()
        .min(10)
        .max(600)
        .default(120)
        .describe('How long to wait for SMS code in seconds (default: 120, max: 600)'),
    });
  • src/tools.ts:236-268 (registration)
    Registration of `wait_for_sms_code` in the `TOOL_DEFINITIONS` list.
      name: 'wait_for_sms_code',
      title: 'Buy Number and Wait for SMS Code',
      description:
        'RECOMMENDED: One-step tool that buys a number AND waits for the SMS code automatically. ' +
        'Uses real-time WebSocket delivery with automatic polling fallback. ' +
        'Always returns order_id in the response — even on timeout — so you can use check_sms to recover.',
      inputSchema: {
        type: 'object' as const,
        properties: {
          service: {
            type: 'string',
            description: 'Service code (e.g. "telegram", "whatsapp", "google")',
          },
          country: {
            type: 'string',
            description: 'Country ISO code (e.g. "US", "GB", "RU")',
          },
          timeout_seconds: {
            type: 'number',
            description: 'How long to wait for SMS code in seconds (default: 120, max: 600)',
            default: 120,
          },
        },
        required: ['service', 'country'],
      },
      annotations: {
        title: 'Buy Number and Wait for SMS Code',
        readOnlyHint: false,
        destructiveHint: false,
        idempotentHint: false,
        openWorldHint: true,
      },
    },
  • Helper function for `wait_for_sms_code` to handle WebSocket communication.
    function waitForSMSViaWebSocket(
      baseUrl: string,
      apiKey: string,
      orderId: string,
      timeoutMs: number
    ): Promise<SMSResult | null> {
      return new Promise((resolve) => {
        const wsUrl = baseUrl.replace(/^http/, 'ws') + `/ws/orders?order_id=${encodeURIComponent(orderId)}&api_key=${encodeURIComponent(apiKey)}`;
    
        let ws: WebSocket | null = null;
        let resolved = false;
        let reconnected = false;
        const timer = setTimeout(() => {
          if (!resolved) {
            resolved = true;
            ws?.close();
            resolve(null); // trigger polling fallback
          }
        }, timeoutMs);
    
        function connect() {
          ws = new WebSocket(wsUrl);
    
          ws.on('error', () => {
            if (!resolved && !reconnected) {
              reconnected = true;
              ws?.close();
              // Try once more after 1s
              setTimeout(connect, 1000);
            } else if (!resolved) {
              resolved = true;
              clearTimeout(timer);
              resolve(null); // WS failed, use polling
            }
          });
    
          ws.on('close', () => {
            if (!resolved && !reconnected) {
              reconnected = true;
              setTimeout(connect, 1000);
            } else if (!resolved) {
              resolved = true;
              clearTimeout(timer);
              resolve(null); // WS closed, use polling
            }
          });
    
          ws.on('message', (data: Buffer) => {
            try {
              const msg = JSON.parse(data.toString());
              // Message format from server: {type: "sms", code: "...", full_text: "..."}
              if (msg.type === 'sms' && msg.code) {
                if (!resolved) {
                  resolved = true;
                  clearTimeout(timer);
                  ws?.close();
                  resolve({
                    sms_code: msg.code,
                    sms_text: msg.full_text,
                    delivery_method: 'websocket',
                  });
                }
              }
              // Also handle sms_received type from backend
              if (msg.type === 'sms_received' && msg.code) {
                if (!resolved) {
                  resolved = true;
                  clearTimeout(timer);
                  ws?.close();
                  resolve({
                    sms_code: msg.code,
                    sms_text: msg.message,
                    delivery_method: 'websocket',
                  });
                }
              }
            } catch {
              // ignore parse errors
            }
          });
        }
    
        connect();
      });
    }

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/virtualsms-io/mcp-server'

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