Перейти к содержимому

TypeScript SDK

Создание MCP серверов на TypeScript с официальным SDK для Node.js

Официальный TypeScript SDK предоставляет полную реализацию MCP протокола для Node.js с поддержкой stdio, HTTP и SSE транспортов.

Terminal
# Стабильная v1.x
npm install @modelcontextprotocol/sdk
# Альфа v2.x с StreamableHTTP
npm install @modelcontextprotocol/sdk@next

Требования:

  • Node.js 18+
  • TypeScript 5.0+
tsconfig.json
{
"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",
});
// Запуск через stdio
const transport = new StdioServerTransport();
await server.connect(transport);
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",
},
],
};
}
);
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),
})),
};
});
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}`,
},
},
],
})
);
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);
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);
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.md
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;
}
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) }],
})
);
}

Рекомендуется использовать готовый шаблон:

Окно терминала
# Клонирование шаблона
git clone https://github.com/alexanderop/mcp-server-starter-ts.git my-server
cd my-server
npm install
# Разработка
npm run dev
# Сборка
npm run build
# Тестирование с инспектором
npm run inspector

Шаблон включает:

  • Dual transport (stdio + HTTP/SSE)
  • Docker support с multi-stage builds
  • MCP Inspector интеграция
  • Hygen генераторы для быстрого добавления tools
claude_desktop_config.json
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/path/to/dist/index.js"]
}
}
}
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);
});
});
Окно терминала
npx @modelcontextprotocol/inspector node dist/index.js