MCP Docs ревизия 2026-07-28

Начало / Как написать свой MCP-сервер на Python / Свой MCP-сервер на TypeScript

Свой MCP-сервер на TypeScript

Тот же сервер каталога на TypeScript SDK v2: stdio и Streamable HTTP

РуководствоКод проверен: Node.js 22, @modelcontextprotocol/server 2.3.1, @modelcontextprotocol/client 2.3.1 — по stdio и по HTTPОснова: Документация TypeScript SDK v2Сверено 13.10.2026, ревизия 2026-07-28Открыть .md

TypeScript SDK второй версии разделён на пакеты: @modelcontextprotocol/server для серверов и @modelcontextprotocol/client для клиентов. Прежний пакет @modelcontextprotocol/sdk — это первая версия, её поддерживают в отдельной ветке.

TypeScript SDK v2: фабрика createServer регистрирует возможности; serveStdio обслуживает локальный запуск, createMcpHandler — HTTP.

Проект#

Нужен Node.js 20 или новее. SDK поставляется только как ES-модули.

Создание проекта
mkdir catalog && cd catalog
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server @modelcontextprotocol/node zod tsx
mkdir src

tsx запускает TypeScript без сборки. Схемы параметров пишутся на Zod 4 (zod/v4); подходят и другие библиотеки со стандартом Standard Schema.

Код сервера#

src/server.ts
// MCP-сервер «Каталог» на TypeScript SDK v2: инструмент, ресурс, шаблон запроса
import { createServer as createHttpServer } from 'node:http';
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import { localhostHostValidation, localhostOriginValidation, toNodeHandler } from '@modelcontextprotocol/node';
import * as z from 'zod/v4';

const PRODUCTS = [
    { sku: 'A-100', name: 'Стол обеденный', price: 18900, stock: 4 },
    { sku: 'A-200', name: 'Стул мягкий', price: 5400, stock: 0 },
    { sku: 'B-310', name: 'Полка настенная', price: 2300, stock: 17 }
];

function createServer(): McpServer {
    const server = new McpServer({ name: 'catalog', version: '1.0.0' });

    server.registerTool(
        'find_products',
        {
            description: 'Найти товары по части названия. price — цена в рублях, stock — остаток.',
            inputSchema: z.object({
                query: z.string().min(1),
                in_stock_only: z.boolean().default(false)
            })
        },
        async ({ query, in_stock_only }) => {
            const q = query.toLowerCase();
            const found = PRODUCTS.filter(p => p.name.toLowerCase().includes(q) && (p.stock > 0 || !in_stock_only));
            return {
                content: [{ type: 'text', text: JSON.stringify(found) }],
                structuredContent: { result: found }
            };
        }
    );

    server.registerResource('categories', 'catalog://categories',
        { title: 'Разделы каталога', mimeType: 'text/plain' },
        async uri => ({ contents: [{ uri: uri.href, text: 'Столы\nСтулья\nПолки' }] })
    );

    server.registerPrompt(
        'compare',
        {
            description: 'Сравнить два товара по артикулу',
            argsSchema: z.object({ first_sku: z.string(), second_sku: z.string() })
        },
        ({ first_sku, second_sku }) => ({
            messages: [{
                role: 'user',
                content: { type: 'text', text: `Сравни товары ${first_sku} и ${second_sku}: цена, наличие, назначение.` }
            }]
        })
    );

    return server;
}

if (process.argv.includes('--http')) {
    // Удалённый режим: Streamable HTTP на http://127.0.0.1:3000/mcp
    const handler = toNodeHandler(createMcpHandler(createServer));
    const validateHost = localhostHostValidation();
    const validateOrigin = localhostOriginValidation();
    createHttpServer((req, res) => {
        if (!validateHost(req, res) || !validateOrigin(req, res)) return;
        void handler(req, res);
    }).listen(3000, '127.0.0.1');
    console.error('catalog: http://127.0.0.1:3000/mcp');
} else {
    // Локальный режим: stdio. В stdout писать нельзя — только в stderr
    void serveStdio(createServer);
}
  • createServer — фабрика: по HTTP она вызывается на каждый запрос, поэтому сервер ничего не хранит между запросами. Пулы соединений и кэши создавайте вне фабрики.
  • registerTool принимает имя, описание со схемой Zod и обработчик. SDK проверит аргументы до вызова обработчика и вернёт модели понятную ошибку.
  • Для HTTP перед обработчиком стоят проверки Host и Origin: на локальном адресе они защищают от DNS rebinding — когда вредная страница в браузере обращается к вашему серверу на 127.0.0.1.

Запуск и проверка#

Запуск
npx tsx src/server.ts            # stdio
npx tsx src/server.ts --http     # http://127.0.0.1:3000/mcp
npx @modelcontextprotocol/inspector npx tsx src/server.ts

Клиент для проверки ставится отдельным пакетом @modelcontextprotocol/client:

src/client.ts
// Клиент: запускает сервер как дочерний процесс и вызывает инструмент
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';

const client = new Client({ name: 'catalog-check', version: '1.0.0' });
await client.connect(new StdioClientTransport({ command: 'npx', args: ['tsx', 'src/server.ts'] }));

const { tools } = await client.listTools();
console.log(tools.map(t => t.name));

const result = await client.callTool({ name: 'find_products', arguments: { query: 'стол' } });
console.log(result.structuredContent);

await client.close();
Вывод
[ 'find_products' ]
{
  result: [ { sku: 'A-100', name: 'Стол обеденный', price: 18900, stock: 4 } ]
}

Подключение к клиенту#

Команда запуска для клиентов — npx tsx /абсолютный/путь/src/server.ts; для рабочего варианта соберите проект в JavaScript и запускайте node dist/server.js. Форматы настроек клиентов — в разделе «Подключение».

Авторизация по HTTP в TypeScript SDK передаётся в фабрику через authInfo: токен проверяет промежуточный обработчик до SDK. Подход и требования одинаковы для обоих языков — в руководстве «Авторизация».