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.
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
| Piece | Value in this server | Defined by |
|---|---|---|
| Tool name | validate_phone | MCP specification naming rules |
| Input | one string, phone | zod shape converted to JSON Schema by the SDK |
| Success result | JSON text of the lookup data | LookValid phone response |
| Lookup not found | found false with a reason, not an error | LookValid: a normal free answer |
| Failure | isError true with the API error code | MCP 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
- Save the file as
server.tsand setLOOKVALID_API_KEYin the environment of the process that launches it. - Add the command that starts it to your MCP client's server list, in whatever configuration format that client uses.
- 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
- Model Context Protocol specification 2026-07-28: Tools, read 2026-10-19
- MCP TypeScript SDK: README, read 2026-10-19