TypeScript SDK
Создание MCP серверов на TypeScript с официальным SDK для Node.js
Официальный TypeScript SDK предоставляет полную реализацию MCP протокола для Node.js с поддержкой stdio, HTTP и SSE транспортов.
Установка
Заголовок раздела «Установка»# Стабильная v1.xnpm install @modelcontextprotocol/sdk
# Альфа v2.x с StreamableHTTPnpm install @modelcontextprotocol/sdk@nextpnpm add @modelcontextprotocol/sdkyarn add @modelcontextprotocol/sdkТребования:
- Node.js 18+
- TypeScript 5.0+
Конфигурация TypeScript
Заголовок раздела «Конфигурация TypeScript»{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "outDir": "./dist" }, "include": ["src/**/*"]}Базовый сервер
Заголовок раздела «Базовый сервер»import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({ name: "my-server", version: "1.0.0",});
// Запуск через stdioconst transport = new StdioServerTransport();await server.connect(transport);Инструменты (Tools)
Заголовок раздела «Инструменты (Tools)»Базовый инструмент
Заголовок раздела «Базовый инструмент»server.tool( "add", { a: { type: "number", description: "Первое число" }, b: { type: "number", description: "Второе число" }, }, async ({ a, b }) => ({ content: [{ type: "text", text: `Результат: ${a + b}` }], }));Инструмент с валидацией
Заголовок раздела «Инструмент с валидацией»import { z } from "zod";
const SearchSchema = z.object({ query: z.string().min(1).describe("Поисковый запрос"), limit: z.number().int().min(1).max(100).default(10).describe("Лимит результатов"), offset: z.number().int().min(0).default(0).describe("Смещение"),});
server.tool( "search", SearchSchema, async (params) => { const { query, limit, offset } = params; const results = await database.search(query, { limit, offset });
return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; });Инструмент с обработкой ошибок
Заголовок раздела «Инструмент с обработкой ошибок»server.tool( "divide", { a: { type: "number" }, b: { type: "number" }, }, async ({ a, b }) => { if (b === 0) { return { content: [{ type: "text", text: "Ошибка: деление на ноль" }], isError: true, }; }
return { content: [{ type: "text", text: `Результат: ${a / b}` }], }; });Инструмент с изображением
Заголовок раздела «Инструмент с изображением»import fs from "fs/promises";
server.tool( "get_screenshot", { path: { type: "string", description: "Путь к изображению" }, }, async ({ path }) => { const data = await fs.readFile(path); const base64 = data.toString("base64");
return { content: [ { type: "image", data: base64, mimeType: "image/png", }, ], }; });Ресурсы (Resources)
Заголовок раздела «Ресурсы (Resources)»Статический ресурс
Заголовок раздела «Статический ресурс»server.resource( "config://app", async () => ({ contents: [ { uri: "config://app", mimeType: "application/json", text: JSON.stringify({ version: "1.0.0", environment: "production", }), }, ], }));Динамический ресурс с шаблоном
Заголовок раздела «Динамический ресурс с шаблоном»server.resource( "file://{path}", async ({ path }) => { const content = await fs.readFile(path, "utf-8"); const mimeType = getMimeType(path);
return { contents: [ { uri: `file://${path}`, mimeType, text: content, }, ], }; });Список ресурсов
Заголовок раздела «Список ресурсов»server.setResourcesHandler(async () => { const files = await fs.readdir("./data");
return { resources: files.map((file) => ({ uri: `file://data/${file}`, name: file, description: `Файл ${file}`, mimeType: getMimeType(file), })), };});Промпты (Prompts)
Заголовок раздела «Промпты (Prompts)»Базовый промпт
Заголовок раздела «Базовый промпт»server.prompt( "code_review", { language: { type: "string", description: "Язык программирования" }, code: { type: "string", description: "Код для ревью" }, }, async ({ language, code }) => ({ messages: [ { role: "user", content: { type: "text", text: `Проведи код-ревью следующего ${language} кода:\n\n\`\`\`${language}\n${code}\n\`\`\``, }, }, ], }));Промпт с системным сообщением
Заголовок раздела «Промпт с системным сообщением»server.prompt( "sql_expert", { question: { type: "string" }, schema: { type: "string" }, }, async ({ question, schema }) => ({ messages: [ { role: "assistant", content: { type: "text", text: "Я SQL эксперт. Помогу с оптимизацией запросов и проектированием схемы.", }, }, { role: "user", content: { type: "text", text: `Схема БД:\n${schema}\n\nВопрос: ${question}`, }, }, ], }));Транспорты
Заголовок раздела «Транспорты»Stdio (стандартный)
Заголовок раздела «Stdio (стандартный)»import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({ name: "my-server", version: "1.0.0" });const transport = new StdioServerTransport();await server.connect(transport);SSE (Server-Sent Events)
Заголовок раздела «SSE (Server-Sent Events)»import express from "express";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
const app = express();const server = new McpServer({ name: "sse-server", version: "1.0.0" });
// Хранение сессийconst sessions = new Map<string, SSEServerTransport>();
app.get("/sse", (req, res) => { const sessionId = crypto.randomUUID(); const transport = new SSEServerTransport("/message", res); sessions.set(sessionId, transport);
server.connect(transport);
res.on("close", () => sessions.delete(sessionId));});
app.post("/message", async (req, res) => { const sessionId = req.query.sessionId as string; const transport = sessions.get(sessionId);
if (!transport) { return res.status(404).json({ error: "Session not found" }); }
await transport.handlePostMessage(req, res);});
app.listen(3000);StreamableHTTP (v2 alpha)
Заголовок раздела «StreamableHTTP (v2 alpha)»import express from "express";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
const app = express();app.use(express.json());
const server = new McpServer({ name: "http-server", version: "1.0.0" });
// Управление сессиямиconst transports = new Map<string, StreamableHTTPServerTransport>();
app.post("/mcp", async (req, res) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; let transport: StreamableHTTPServerTransport;
if (sessionId && transports.has(sessionId)) { transport = transports.get(sessionId)!; } else if (!sessionId && isInitializeRequest(req.body)) { transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => crypto.randomUUID(), onsessioninitialized: (id) => { transports.set(id, transport); }, }); await server.connect(transport); } else { return res.status(400).json({ error: "Invalid session" }); }
await transport.handleRequest(req, res);});
app.delete("/mcp", async (req, res) => { const sessionId = req.headers["mcp-session-id"] as string; const transport = transports.get(sessionId);
if (transport) { await transport.close(); transports.delete(sessionId); } res.status(204).end();});
app.listen(3000);Модульная структура
Заголовок раздела «Модульная структура»my-mcp-server/├── src/│ ├── index.ts # Entry point│ ├── server.ts # Server setup│ ├── tools/│ │ ├── index.ts│ │ ├── calculator.ts│ │ └── filesystem.ts│ ├── resources/│ │ ├── index.ts│ │ └── config.ts│ └── prompts/│ ├── index.ts│ └── templates.ts├── tests/├── package.json├── tsconfig.json└── README.mdsrc/server.ts
Заголовок раздела «src/server.ts»import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { registerCalculatorTools } from "./tools/calculator.js";import { registerFilesystemTools } from "./tools/filesystem.js";import { registerResources } from "./resources/index.js";import { registerPrompts } from "./prompts/index.js";
export function createServer() { const server = new McpServer({ name: "modular-server", version: "1.0.0", });
registerCalculatorTools(server); registerFilesystemTools(server); registerResources(server); registerPrompts(server);
return server;}src/tools/calculator.ts
Заголовок раздела «src/tools/calculator.ts»import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
export function registerCalculatorTools(server: McpServer) { server.tool( "add", { a: { type: "number" }, b: { type: "number" } }, async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }], }) );
server.tool( "multiply", { a: { type: "number" }, b: { type: "number" } }, async ({ a, b }) => ({ content: [{ type: "text", text: String(a * b) }], }) );}Starter Template
Заголовок раздела «Starter Template»Рекомендуется использовать готовый шаблон:
# Клонирование шаблонаgit clone https://github.com/alexanderop/mcp-server-starter-ts.git my-servercd my-servernpm install
# Разработкаnpm run dev
# Сборкаnpm run build
# Тестирование с инспекторомnpm run inspectorШаблон включает:
- Dual transport (stdio + HTTP/SSE)
- Docker support с multi-stage builds
- MCP Inspector интеграция
- Hygen генераторы для быстрого добавления tools
Конфигурация клиентов
Заголовок раздела «Конфигурация клиентов»{ "mcpServers": { "my-server": { "command": "node", "args": ["/path/to/dist/index.js"] } }}{ "mcpServers": { "my-server": { "command": "npx", "args": ["-y", "@myorg/mcp-server"] } }}Тестирование
Заголовок раздела «Тестирование»Unit тесты с Jest
Заголовок раздела «Unit тесты с Jest»import { createServer } from "../src/server.js";
describe("Calculator Tools", () => { const server = createServer();
test("add returns correct sum", async () => { const result = await server.callTool("add", { a: 2, b: 3 }); expect(result.content[0].text).toBe("5"); });
test("divide by zero returns error", async () => { const result = await server.callTool("divide", { a: 1, b: 0 }); expect(result.isError).toBe(true); });});MCP Inspector
Заголовок раздела «MCP Inspector»npx @modelcontextprotocol/inspector node dist/index.js