Tutorials

Build a small MCP server in TypeScript

An MCP server lets an AI client call a function you define. This tutorial builds the smallest useful one in TypeScript: a single tool that wraps the LookValid phone lookup over REST. We ran the code against the official SDK before publishing it, and the version is named below.

By LookValid engineering. Published 2026-10-19.

What an MCP tool is

The Model Context Protocol lets servers expose tools that language models can invoke. Each tool has a unique name, a description and an inputSchema in JSON Schema; clients discover tools with a tools/list request and run one with tools/call 1. Tool names should be 1 to 128 characters of letters, digits, underscore, hyphen or dot 1.

Two kinds of failure exist. A protocol error, such as an unknown tool, is a JSON-RPC error. A tool execution error, such as an API failure, is a normal result with isError: true so the model can read it and adjust 1. Your tool should use the second kind when the lookup fails.

Set up the SDK

The official TypeScript SDK is the package @modelcontextprotocol/sdk, and it needs zod installed beside it as a peer dependency 2. We ran the server below against SDK version 1.32.1 and zod 4.

npm install @modelcontextprotocol/sdk zod

Install the SDK and zod

Write the server

The SDK builds a server with McpServer and connects it to a transport such as stdio 2. The tool below registers one tool with registerTool and a zod inputSchema, connects over standard input and output with StdioServerTransport, and calls our REST endpoint with fetch. Because stdout carries the protocol, the server logs with console.error; one stray console.log would corrupt the stream.

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import * as z from 'zod/v4';
​
const server = new McpServer({ name: 'phone-check', version: '1.0.0' });
​
server.registerTool(
  'validate_phone',
  {
    description: 'Check a phone number against its numbering plan and return E.164, country and number type.',
    inputSchema: { phone: z.string() },
  },
  async ({ phone }) => {
    const res = await fetch('https://YOUR_API_HOST/v1/phone', {
      method: 'POST',
      headers: {
        Authorization: 'Bearer ' + process.env.LOOKVALID_API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ phone }),
    });
    const body = await res.json();
    if (!res.ok) {
      return { content: [{ type: 'text', text: 'LookValid error: ' + body.error.code }], isError: true };
    }
    return { content: [{ type: 'text', text: JSON.stringify(body.found ? body.data : { found: false, reason: body.reason }) }] };
  },
);
​
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('phone-check MCP server running on stdio');
}
main();

A minimal server with one tool

The handler returns a content array with a text item, which is the result shape in the specification 1. On failure it sets isError: true, the tool execution error described above 1.

What the tool returns

The pieces of the tool and where each is defined
PieceValue in this serverDefined by
Tool namevalidate_phoneMCP specification naming rules
Inputone string, phonezod shape converted to JSON Schema by the SDK
Success resultJSON text of the lookup dataLookValid phone response
Lookup not foundfound false with a reason, not an errorLookValid: a normal free answer
FailureisError true with the API error codeMCP tool execution error

The pieces of the tool and where each is defined

The REST call can be tested on its own before you wrap it.

curl -X POST https://YOUR_API_HOST/v1/phone \
  -H "Authorization: Bearer $LOOKVALID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+14155552671"}'

The REST request the tool makes

Run it and connect a client

  1. Save the file as server.ts and set LOOKVALID_API_KEY in the environment of the process that launches it.
  2. Add the command that starts it to your MCP client's server list, in whatever configuration format that client uses.
  3. Ask the client to validate a number and watch the tool call.

The specification advises keeping a human in the loop with the ability to deny tool calls, so leave your client's confirmation prompts on 1. You do not have to write any of this to use LookValid from an AI client: our hosted server exposes every live lookup as a tool, described in the MCP docs. To call the REST endpoint from your own code, start with the quickstart or the phone product page.

Sources

  1. Model Context Protocol specification 2026-07-28: Tools, read 2026-10-19
  2. MCP TypeScript SDK: README, read 2026-10-19