https://a.storyblok.com/f/270183/1368x665/168191321c/25dev_dev-blog_oc-mcp-pr.jpg

Contribute to the Open Source Vonage MCP Tooling Server

最終更新日 December 17, 2025

所要時間:6 分

The Vonage MCP Tooling Server is open source and beginner-friendly. Add real SDK features through straightforward PRs and clear MCP guidelines.

Introduction

So, let’s say you're playing around with the Vonage MCP Tooling Server. You’ve seen how AI agents can send cheerful holiday messages via WhatsApp, RCS, or you’ve built a Claude chatbot to interact with the MCP Server. Pretty neat, right?

But then your brain sparks: “Wait a sec. What if I want to add MMS? Or maybe check how recently a number has been SIM-swapped before texting them sensitive information? Could I add more features from the Voice API too?”

Spoiler: Yes, you can. And better yet, you’re invited to!

The Vonage MCP Tooling Server is fully open source and ready for your contributions. This guide walks you through how to add a new tool (like verify-number) and submit a pull request (PR), while keeping everything aligned with how the existing tools are structured.

Let's dig in.

Read til the end for a cool, AI shortcut that can help you add tools even faster.

First, Understand the Structure

Before diving into code, take some time to explore the architecture. You don’t have to memorize every line, but understanding how the pieces fit will help your tool slot in smoothly.

Core Components

Environment & Auth Setup

const vonage = new Vonage(
  new Auth({
    apiKey: process.env.VONAGE_API_KEY!,
    apiSecret: process.env.VONAGE_API_SECRET!,
    applicationId: appId!,
    privateKey: privateKey,
  })
);

Modular Channel Config

const CHANNEL_CONFIGS = {
  whatsapp: {
    channel: Channels.WHATSAPP,
    getFrom: () => whatsappNumber,
    requiresValidation: () => !!whatsappNumber,
    validationError: 'VONAGE_WHATSAPP_NUMBER is not set.',
  },
  // ...rcs, sms, etc.
};

Unified Messaging Function

async function sendChannelMessage(channelKey, to, message, useFailover = false) {
  // This one handles everything from validation to failover logic
}

Simple, clean, and designed for reuse.

Our MCP Patterns

We’ve learned a lot while building our first tools, and we’ve settled on some rules of thumb that make AI-agent integrations smoother and more predictable. If you’re contributing, please stick to these:

1. One Tool = One Job

Clear, single-purpose tools are easier for AI agents to reason about. When each tool does exactly one thing, it’s more discoverable, easier to document, and far less likely to confuse the agent's planning logic.

server.registerTool('whatsapp-send-text', {...});
server.registerTool('whatsapp-send-text-with-sms-failover', {...});

Note:server is imported from the MCP server bindings package

Avoid jack-of-all-trades tools like:

server.registerTool('send-message', {
  inputSchema: {
    channel: z.enum(['whatsapp', 'rcs', 'sms']),
    useFailover: z.boolean().optional(),
    // too many optional inputs = confusing agents
  }
});

2. Validate Like You Mean It

Bad inputs? Missing environment variables? Catch those early and clearly.

if (!whatsappNumber) {
  throw new Error('VONAGE_WHATSAPP_NUMBER is not set.');
}

const formatted = await formatPhoneNumber(to);
if (!formatted) {
  throw new Error(`Invalid phone number format: ${to}`);
}

And always respond in a structured way that the agent can parse:

return {
  content: [{ type: 'text', text: `Error: ${error.message}` }],
};

3. Zod for Input Schemas

Keep things type-safe and well-documented:

inputSchema: {
  to: z.string().describe('Recipient phone number in E.164 format'),
  message: z.string().describe('Message content to send'),
}

If you haven’t used Zod before, think of it as a way to define and validate inputs in one place, like TypeScript types, but with runtime checks. It ensures your tool only runs when the inputs are valid, and gives agents clear descriptions of what each field means.

You’ll include this inputSchema as part of the server.registerTool() call, so when your tool is registered, it knows exactly what input shape to expect and how to validate it up front.

4. Docs First, Always

Add your tool to the README’s table and include a usage example. Your future self, and every developer after you, will thank you.

Let’s Add a Tool Together

Let’s say you want to help improve the MCP Tooling Server by adding a focused new tool. In this case, we’ll walk through how to create one that checks whether a phone number has recently undergone a SIM swap, a common indicator of fraud. We’ll call it check-sim-swap.

This tool uses theIdentity Insights API, which provides real-time intelligence about a phone number, including SIM swap events. We’ll keep things scoped: one tool, one job.

Step 1: Set Up Your Local Dev Environment

First, fork the MCP server repo and clone your fork:

# Fork the repo on GitHub
git clone https://github.com/YOUR_USERNAME/vonage-mcp-server-api-bindings.git
cd vonage-mcp-server-api-bindings

make setup

If you haven’t used make before, it’s essentially a task runner that helps bundle multiple commands. Check out the Makefile to see what specific commands are being run.

Although it originated in C and C++ ecosystems, it works well for general-purpose workflows too, including JavaScript and TypeScript projects. In this repository, we use it to streamline routine commands such as installing dependencies or preparing the development environment.

Here, make setup will install dependencies and get your environment ready to work with the project.

Step 2: Write the Tool

Now open src/index.ts and register your new tool with the server.

Paste in the following code:

server.registerTool(
  'check-sim-swap',
  {
    title: 'SIM Swap Check',
    description: 'Check if a phone number has recently had a SIM change.',
    inputSchema: {
      number: z.string().describe('Phone number in E.164 format'),
    },
  },
  async ({ number }) => {
    try {
      const formatted = await formatPhoneNumber(number);
      if (!formatted) {
        throw new Error(`Invalid phone number format: ${number}`);
      }

      const response = await vonage.identityInsights.getInsights({
        phone_number: formatted,
        purpose: 'FraudPreventionAndDetection',
        insights: {
          sim_swap: {
            period: 240 // last 240 hours
          }
        }
      });

      const { sim_swap } = response.insights;

      return {
        content: [{
          type: 'text',
          text: sim_swap?.is_swapped
            ? `SIM swap detected! Most recent swap was on: ${sim_swap.latest_sim_swap_at}`
            : `No recent SIM swap detected.`,
        }],
      };
    } catch (error) {
      return {
        content: [{
          type: 'text',
          text: `Error checking SIM swap: ${error.message}`,
        }],
      };
    }
  }
);

This tool does one thing: given a phone number, it checks whether the SIM card has been swapped in the last 10 days (240 hours). If so, it returns the timestamp. If not, it confirms no recent swap occurred.

Step 3: Update the Docs

Open the README.md file and add your new tool to the tools table:

| **Identity** | `check-sim-swap` | Check if a phone number has had a recent SIM change |

Add a usage example:

#### Check if a phone number has had a SIM swap

Can you check whether +14155550123 has had a SIM swap in the past 10 days?

Step 4: Test Locally

Make sure your tool builds and passes checks:

make check
make build
npm run start

Step 5: Create Your Pull Request

When everything looks good, commit and push your changes:

git checkout -b feature/add-check-sim-swap
git add .
git commit -m "Add check-sim-swap tool with Zod validation and Identity Insights API"
git push origin feature/add-check-sim-swap

Head to GitHub and open your pull request. Bonus points for including a screenshot of your tool working with your AI Agent!

Advanced Patterns for Complex Features

You probably won’t need this for simple tools, but for long-running or bulk processes, consider this structure.

Handling Async Operations with Status Tracking

For operations that take time (like verification workflows):

server.registerTool('start-verification', {
  inputSchema: {
    number: z.string(),
    workflow_id: z.string().optional(),
  },
  async ({ number, workflow_id }) => {
    // Implement tracking logic here
  }
});

Batch Operations

For bulk operations:

server.registerTool('verify-numbers-batch', {
  inputSchema: {
    numbers: z.array(z.string()),
  },
  async ({ numbers }) => {
    // Loop through and verify all
  }
});

A Few Things to Watch Out For

Before submitting your pull request, make sure everything still works as expected. Running make check should catch most linting and type errors. But don’t stop there. Manually test that all existing tools behave correctly, and double-check that your additions haven’t accidentally broken any existing functionality. TypeScript should compile cleanly before you push.

Naming matters more than you might think. Stick with kebab-case for tool names (for example, use check-sim-swap, notcheckSimSwap). Environment variables should follow the UPPER_SNAKE_CASE convention, always prefixed with VONAGE_. And when you're writing functions, use good ol’ reliable camelCase.

Error handling is another critical area: don’t assume things will always go smoothly. Handle edge cases like network failures, invalid credentials, API rate limits, and malformed user input. If your tool doesn’t fail gracefully, it’ll cause headaches for downstream users and agents alike.

Lastly, if your tool introduces new environment variables, document them clearly. Add them to the README’s environment variables table with a short description of what each key does and whether it’s required. That small act of documentation can save your future self (or your fellow contributors) a lot of head-scratching later.

Infographic titled “What We Look For in a PR.” It lists five pull request review criteria with icons: Code Quality, shown with an orange checkmark icon, described as “passes all linting and type checks”; Documentation, shown with a purple document icon, described as “README and code comments updated”; Testing, shown with a pink lab beaker icon, described as “manual testing completed, edge cases covered”; Security, shown with a purple shield icon, described as “no hardcoded credentials, proper error handling”; and Consistency, shown with a blue hierarchy icon, described as “follows established patterns and conventions.”Checklist of contribution requirements for a Vonage open-source pull request, including code quality, documentation, testing, security, and consistency.

Bonus: Using Both MCP Servers Together

Working with an AI agent, you can simplify the contribution process by running both the Vonage MCP Tooling Server and the Vonage Documentation MCP Server in tandem. The combination gives your agent access to the project’s tool definitions as well as the official Vonage API documentation, which makes it easier to draft new tools that follow the established patterns.

With both servers active, you can often start from a prompt like:

“I want to add a new tool to the Vonage MCP Tooling Server that uses the Verify API to start a phone verification workflow. Please follow the one-tool-per-file structure, use Zod for input validation, and include a usage example for the README. Refer to the Vonage docs for the latest parameters.”

This approach won’t replace testing or review, but it can help you move quickly and keep your changes aligned with the project’s conventions.

Wrapping Up

Open source works best when people like you get involved. If you’ve got an idea for a new SDK feature, maybe it’s MMS, or call handling, or even account management, build it! And if you’re unsure where to start, reach out, and we can work on it together. We love PRs!

We’re excited to see what you come up with.

Have a question or something to share? Join the conversation on the Vonage Community Slack, stay up to date with the Developer Newsletter, follow us on X (formerly Twitter), subscribe to our YouTube channel for video tutorials, and follow the Vonage Developer page on LinkedIn, a space for developers to learn and connect with the community. Stay connected, share your progress, and keep up with the latest developer news, tips, and events!

シェア:

https://a.storyblok.com/f/270183/384x384/e4e7d1452e/benjamin-aronov.png
Benjamin AronovDeveloper Advocate

Benjamin Aronov is a developer advocate at Vonage. He is a proven community builder with a background in Ruby on Rails. Benjamin enjoys the beaches of Tel Aviv which he calls home. His Tel Aviv base allows him to meet and learn from some of the world's best startup founders. Outside of tech, Benjamin loves traveling the world in search of the perfect pain au chocolat.