Вы можете легко создавать ИИ-рабочие процессы с Anakin AI без каких-либо знаний в кодировании. Подключайтесь к API LLM, таким как: GPT-4, Claude 3.5 Sonnet, Uncensored Dolphin-Mixtral, Stable Diffusion, DALLE, Веб-скрапинг.... в одном рабочем процессе!
Забудьте о сложном кодировании, автоматизируйте свою повседневную работу с Anakin AI!
На ограниченное время вы также можете использовать Google Gemini 1.5 и Stable Diffusion бесплатно!

Введение
Протокол контекста моделей (MCP) представляет собой значительное достижение в экосистеме ИИ, предлагая стандартизированный способ общения с большими языковыми моделями. Вместо того, чтобы каждая платформа ИИ реализовывала свой собственный уникальный формат для сообщений, MCP стремится предоставить единообразный интерфейс для запросов, ответов и вызовов функций на различных моделях и платформах.
Хотя сам протокол продолжает эволюционировать, создание базового сервера, совместимого с MCP, может быть достаточно простым. В этом руководстве я проведу вас через создание простого, но функционального сервера, который соблюдает основные принципы обработки сообщений в современных ИИ-системах. Наша реализация будет сосредоточена на создании фундамента, который вы сможете впоследствии расширить более продвинутыми функциями.
10 минут достаточно времени? Для готовой к производству системы — определенно нет. Но для работающего прототипа, демонстрирующего ключевые концепции? Абсолютно. Давайте начнем!
Предварительные условия
Прежде чем мы начнем, вам понадобится:
- Установленный Node.js (v16+)
- Базовые знания JavaScript/TypeScript
- Знакомство с Express.js или аналогичными веб-фреймворками
- Редактор кода (рекомендуется VS Code)
- Доступ к терминалу/командной строке
- Менеджер пакетов npm или yarn
Шаг 1: Настройка вашего проекта (2 минуты)
Сначала создадим новую директорию и инициализируем наш проект:
mkdir mcp-server
cd mcp-server
npm init -y
Теперь установим необходимые зависимости:
npm install express cors typescript ts-node @types/node @types/express @types/cors
npm install --save-dev nodemon
Создайте файл конфигурации TypeScript:
npx tsc --init
Отредактируйте сгенерированный tsconfig.json
, чтобы включить эти основные настройки:
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
Обновите раздел скриптов вашего package.json
:
"scripts": {
"start": "node dist/index.js",
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc"
}
Шаг 2: Создание основного сервера (3 минуты)
Создайте свою папку источника и основной файл сервера:
mkdir -p src/handlers
touch src/index.ts
touch src/handlers/messageHandler.ts
touch src/types.ts
Сначала определим наши типы в src/types.ts
:
// Основная структура сообщения
export interface Content {
type: string;
text?: string;
}
export interface Message {
role: "user" | "assistant" | "system";
content: Content[] | string;
}
// Структуры запроса и ответа
export interface ModelRequest {
messages: Message[];
max_tokens?: number;
temperature?: number;
stream?: boolean;
}
export interface ModelResponse {
message: Message;
}
// Интерфейсы вызовов инструментов
export interface Tool {
name: string;
description: string;
input_schema: {
type: string;
properties: Record<string, any>;
required?: string[];
};
}
export interface ToolCall {
type: "tool_call";
id: string;
name: string;
input: Record<string, any>;
}
export interface ToolResult {
type: "tool_result";
tool_call_id: string;
content: string;
}
Теперь реализуем базовый сервер в src/index.ts
:
import express from 'express';
import cors from 'cors';
import { handleMessageRequest } from './handlers/messageHandler';
const app = express();
const PORT = process.env.PORT || 3000;
// Промежуточное программное обеспечение
app.use(cors());
app.use(express.json({ limit: '10mb' }));
// Основная конечная точка для обработки сообщений
app.post('/v1/messages', handleMessageRequest);
// Конечная точка проверки состояния
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});
// Запуск сервера
app.listen(PORT, () => {
console.log(`Сервер работает на порту ${PORT}`);
console.log(`Проверка состояния: http://localhost:${PORT}/health`);
console.log(`Конечная точка сообщений: http://localhost:${PORT}/v1/messages`);
});
Далее реализуем обработчик сообщений в src/handlers/messageHandler.ts
:
import { Request, Response } from 'express';
import { ModelRequest, ModelResponse, Message, Content } from '../types';
export async function handleMessageRequest(req: Request, res: Response) {
try {
const request = req.body as ModelRequest;
// Базовая валидация
if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
return res.status(400).json({ error: 'Некорректный формат запроса. Массив сообщений обязателен.' });
}
// Логируем входящий запрос (для отладки)
console.log('Получен запрос с', request.messages.length, 'сообщениями');
// Обрабатываем сообщения
const response = processMessages(request.messages);
// Возвращаем ответ
return res.status(200).json(response);
} catch (error) {
console.error('Ошибка при обработке запроса:', error);
return res.status(500).json({ error: 'Внутренняя ошибка сервера' });
}
}
function processMessages(messages: Message[]): ModelResponse {
// Извлекаем последнее сообщение пользователя
const lastUserMessage = findLastUserMessage(messages);
if (!lastUserMessage) {
return createErrorResponse("Сообщение пользователя не найдено в разговоре");
}
const userQuery = extractTextContent(lastUserMessage);
// Простая логика генерации ответа
let responseText = "";
if (userQuery.toLowerCase().includes('привет') || userQuery.toLowerCase().includes('здравствуй')) {
responseText = "Привет! Чем могу помочь вам сегодня?";
} else if (userQuery.toLowerCase().includes('погода')) {
responseText = "У меня нет доступа к данным о погоде в реальном времени, но я могу помочь вам понять тенденции погоды в целом.";
} else if (userQuery.toLowerCase().includes('время')) {
responseText = `Текущее время сервера: ${new Date().toLocaleTimeString()}.`;
} else {
responseText = "Я получил ваше сообщение. Это простой ответ сервера модели.";
}
// Конструируем и возвращаем ответ
return {
message: {
role: "assistant",
content: [{
type: "text",
text: responseText
}]
}
};
}
function findLastUserMessage(messages: Message[]): Message | undefined {
// Находим последнее сообщение с ролью 'user'
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === 'user') {
return messages[i];
}
}
return undefined;
}
function extractTextContent(message: Message): string {
if (typeof message.content === 'string') {
return message.content;
} else if (Array.isArray(message.content)) {
return message.content
.filter(item => item.type === 'text' && item.text)
.map(item => item.text)
.join(' ');
}
return '';
}
function createErrorResponse(errorMessage: string): ModelResponse {
return {
message: {
role: "assistant",
content: [{
type: "text",
text: `Ошибка: ${errorMessage}`
}]
}
};
}
Шаг 3: Добавление возможности вызова инструментов (3 минуты)
Создайте новый файл для определения и реализации инструментов:
touch src/tools.ts
Реализуйте несколько базовых инструментов в src/tools.ts
:
import { Tool } from './types';
// Определения инструментов
export const availableTools: Tool[] = [
{
name: "get_current_time",
description: "Получить текущее время сервера",
input_schema: {
type: "object",
properties: {
timezone: {
type: "string",
description: "Необязательный часовой пояс (по умолчанию используется часовой пояс сервера)"
}
}
}
},
{
name: "calculate",
description: "Выполнить математическое вычисление",
input_schema: {
type: "object",
properties: {
expression: {
type: "string",
description: "Математическое выражение для оценки"
}
},
required: ["expression"]
}
}
];
// Реализации инструментов
export function executeToolCall(name: string, params: Record<string, any>): string {
switch (name) {
case "get_current_time":
return getTime(params.timezone);
case "calculate":
return calculate(params.expression);
default:
throw new Error(`Неизвестный инструмент: ${name}`);
}
}
function getTime(timezone?: string): string {
const options: Intl.DateTimeFormatOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short'
};
try {
if (timezone) {
options.timeZone = timezone;
}
return new Date().toLocaleTimeString('ru-RU', options);
} catch (error) {
return `${new Date().toLocaleTimeString()} (Время сервера)`;
}
}
function calculate(expression: string): string {
try {
// ВНИМАНИЕ: В реальном приложении следует использовать более безопасный метод оценки
// Это упрощенный пример только для демонстрации
const sanitizedExpression = expression.replace(/[^0-9+\-*/().\s]/g, '');
const result = eval(sanitizedExpression);
return `${expression} = ${result}`;
} catch (error) {
return `Ошибка при вычислении ${expression}: ${error instanceof Error ? error.message : String(error)}`;
}
}
Теперь обновите обработчик сообщений, чтобы поддерживать вызовы инструментов, изменив src/handlers/messageHandler.ts
:
// Добавьте эти импорты вверху
import { availableTools, executeToolCall } from '../tools';
import { ToolCall, ToolResult } from '../types';
// Обновите функцию handleMessageRequest
export async function handleMessageRequest(req: Request, res: Response) {
try {
const request = req.body as ModelRequest;
// Базовая валидация
if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
return res.status(400).json({ error: 'Некорректный формат запроса. Массив сообщений обязателен.' });
}
// Проверьте, является ли это запросом с результатами инструмента
const lastMessage = request.messages[request.messages.length - 1];
if (lastMessage.role === 'assistant' && Array.isArray(lastMessage.content)) {
const toolCalls = lastMessage.content.filter(item =>
item.type === 'tool_call') as ToolCall[];
if (toolCalls.length > 0) {
// Это последующий запрос с результатами инструмента
return handleToolResultsResponse(request, res);
}
}
// Обрабатываем как обычное сообщение
const response = processMessages(request.messages);
// Возвращаем ответ
return res.status(200).json(response);
} catch (error) {
console.error('Ошибка при обработке запроса:', error);
return res.status(500).json({ error: 'Внутренняя ошибка сервера' });
}
}
// Добавьте эту функцию для обработки вызовов инструментов
function handleToolResultsResponse(request: ModelRequest, res: Response) {
const messages = request.messages;
const lastAssistantMessage = messages[messages.length - 1];
if (lastAssistantMessage.role !== 'assistant' || !Array.isArray(lastAssistantMessage.content)) {
return res.status(400).json({ error: 'Некорректный формат результатов инструмента' });
}
// Находим вызовы инструментов и результаты
const toolCalls = lastAssistantMessage.content.filter(
item => item.type === 'tool_call'
) as ToolCall[];
const toolResults = lastAssistantMessage.content.filter(
item => item.type === 'tool_result'
) as ToolResult[];
// Обрабатываем результаты
let finalResponse = "Я обработал следующую информацию:\n\n";
toolResults.forEach(result => {
const relatedCall = toolCalls.find(call => call.id === result.tool_call_id);
if (relatedCall) {
finalResponse += `- ${relatedCall.name}: ${result.content}\n`;
}
});
return res.status(200).json({
message: {
role: "assistant",
content: [{ type: "text", text: finalResponse }]
}
});
}
// Измените функцию processMessages, чтобы обрабатывать потенциальные вызовы инструментов
function processMessages(messages: Message[]): ModelResponse {
const lastUserMessage = findLastUserMessage(messages);
if (!lastUserMessage) {
return createErrorResponse("Сообщение пользователя не найдено в разговоре");
}
const userQuery = extractTextContent(lastUserMessage);
// Ищем ключевые слова, которые могут приводить к использованию инструмента
if (userQuery.toLowerCase().includes('время')) {
return {
message: {
role: "assistant",
content: [
{ type: "tool_call", id: "call_001", name: "get_current_time", input: {} },
{
type: "text",
text: "Я проверю текущее время для вас."
}
]
}
};
} else if (userQuery.toLowerCase().match(/calculate|compute|каковы \d+[\+\-\*\/]/)) {
// Извлекаем потенциальное вычисление
const expression = userQuery.match(/(\d+[\+\-\*\/\(\)\.]*\d+)/)?.[0] || "1+1";
return {
message: {
role: "assistant",
content: [
{
type: "tool_call",
id: "call_002",
name: "calculate",
input: { expression }
},
{
type: "text",
text: `Я посчитаю ${expression} для вас.`
}
]
}
};
}
// Ответ по умолчанию для других запросов
return {
message: {
role: "assistant",
content: [{
type: "text",
text: `Я получил ваше сообщение: "${userQuery}". Как я могу помочь вам дальше?`
}]
}
};
}
Шаг 4: Тестирование и развертывание (2 минуты)
Создадим простой тестовый скрипт для проверки нашего сервера. Создайте файл test.js
в корневой директории:
const fetch = require('node-fetch');
async function testServer() {
const url = 'http://localhost:3000/v1/messages';
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Который час сейчас?"
}
]
}
]
})
});
const data = await response.json();
console.log(JSON.stringify(data, null, 2));
}
testServer().catch(console.error);
Чтобы протестировать свой сервер:
# Запустите сервер
npm run dev
# В другом терминале запустите тест
node test.js
Для быстрого развертывания давайте создадим простой Dockerfile:
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
Постройте и запустите контейнер:
docker build -t mcp-server .
docker run -p 3000:3000 mcp-server
Заключение
Всего за 10 минут мы построили базовый сервер, который реализует ключевые концепции современных протоколов сообщений ИИ. Наш сервер может:
- Обрабатывать структурированные запросы сообщений
- Отвечать в стандартном формате
- Обрабатывать базовую функциональность вызова инструментов
- Обрабатывать и отвечать на результаты инструментов
Хотя эта реализация упрощена, она предоставляет прочную основу для дальнейшей разработки. Чтобы расширить этот сервер для использования в производстве, рассмотрите возможность:
- Добавления аутентификации и ограничения скорости
- Реализации надлежащей обработки ошибок и валидации
- Подключения к реальным моделям ИИ для обработки
- Добавления более сложных инструментов
- Реализации потоковых ответов
- Добавления комплексной регистрации и мониторинга
Помните, что хотя наша реализация следует общим принципам современных протоколов сообщений ИИ, конкретные реализации, такие как API OpenAI или API Claude от Anthropic, могут иметь дополнительные требования или небольшие вариации в своих ожидаемых форматах. Всегда консультируйтесь с официальной документацией для конкретного сервиса, с которым вы интегрируетесь.
Сфера протоколов сообщений ИИ быстро развивается, поэтому оставайтесь в курсе последних новостей и будьте готовы адаптировать вашу реализацию по мере изменения стандартов. Понимая основные концепции, продемонстрированные в этом руководстве, вы будете хорошо подготовлены к работе с любыми новыми достижениями, которые возникнут в этой захватывающей области.