- Введение в MCP
- Что такое MCP-серверы?
- Основные концепции
- Создание вашего первого MCP-сервера
- Примеры использования MCP-серверов
- Продвинутые возможности
- Лучшие практики
- Устранение неполадок
- Заключение
MCP (Model Context Protocol) — это протокол, который позволяет крупным языковым моделям (LLM) и другим интеллектуальным системам взаимодействовать с внешними сервисами и ресурсами. Представьте это как мост, который соединяет мощные ИИ-системы (такие как Claude) с внешним миром — интернетом, базами данных, API и другими источниками информации.
Представьте, что вы разговариваете с умным помощником, но он не может:
- Проверить актуальную погоду
- Найти что-то в интернете
- Взаимодействовать с вашими файлами
- Использовать калькулятор
- Отправить email
Это существенно ограничивает его полезность. MCP решает эту проблему, позволяя ИИ-системам обращаться к внешним сервисам и расширять свои возможности.
Представьте MCP как "USB-порт для ИИ". Так же как USB-порт позволяет подключать к компьютеру разные устройства (камеры, мышки, принтеры), MCP позволяет подключать к ИИ разные инструменты и сервисы.
MCP-сервер — это программа, которая:
- Предоставляет определенную функциональность (например, доступ к погоде, работу с файлами и т.д.)
- Говорит на языке протокола MCP, чтобы ИИ-системы могли ее использовать
- Работает как "мост" между ИИ-системой и внешними сервисами/API
Если представить, что ИИ — это клиент ресторана, то MCP-серверы — это официанты, которые приносят блюда (информацию/сервисы) с кухни (интернета/внешних API).
MCP-серверы могут предоставлять ИИ доступ к:
- Поиску в интернете
- Прогнозу погоды
- Новостям
- Базам данных
- Калькуляторам
- Системным операциям
- API сторонних сервисов (Twitter, Spotify, YouTube и т.д.)
- И многому другому!
Перед тем как мы перейдем к примерам, давайте разберемся с основными концепциями, которые нужно понимать:
Инструменты — это функции или операции, которые MCP-сервер может выполнять. Например, "получить прогноз погоды", "найти информацию в Google", "отправить email".
Аналогия:
Инструменты — это кнопки на пульте телевизора.
Каждая кнопка выполняет определенное действие.
Ресурсы — это данные или информация, к которым MCP-сервер обеспечивает доступ. Например, текущая погода, информация о фильме, данные из базы.
Аналогия:
Ресурсы — это книги в библиотеке.
ИИ может "взять книгу" и прочитать информацию из нее.
Шаблоны ресурсов определяют, как можно обращаться к динамическим ресурсам. Например, шаблон weather://{город}/current позволяет запрашивать погоду для любого города.
Аналогия:
Шаблон ресурса — это как карточный каталог в библиотеке,
который помогает найти нужную книгу по описанию.
URI — это уникальный идентификатор ресурса, который используется для обращения к конкретному ресурсу. Например, weather://Moscow/current.
Аналогия:
URI — это как адрес в городе.
Он позволяет точно найти нужный "дом" (ресурс).
MCP работает по модели запрос-ответ:
- ИИ отправляет запрос на MCP-сервер (например, "какая погода в Москве?")
- MCP-сервер обрабатывает запрос и отправляет ответ (например, "в Москве 22°C, солнечно")
Аналогия:
Это как разговор по телефону — вы задаете вопрос,
собеседник отвечает.
Давайте рассмотрим процесс создания простого MCP-сервера шаг за шагом. Не беспокойтесь, если вы не знаете, как программировать — мы рассмотрим общую концепцию, а потом перейдем к готовым примерам.
Для создания MCP-сервера вам понадобятся:
- Node.js (среда выполнения JavaScript)
- npm (менеджер пакетов)
- MCP SDK (набор инструментов для работы с MCP)
# Создаем новую директорию
mkdir my-weather-server
cd my-weather-server
# Инициализируем проект
npm init -y
# Устанавливаем MCP SDK и другие необходимые пакеты
npm install @modelcontextprotocol/sdk axiosСоздадим файл index.js с базовым кодом MCP-сервера:
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
// Создаем экземпляр сервера
const server = new Server(
{
name: 'hello-world-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
// Добавляем обработчик ошибок
server.onerror = (error) => console.error('[MCP Error]', error);
// Запускаем сервер
const transport = new StdioServerTransport();
server.connect(transport).catch(console.error);
console.error('Hello World MCP server running on stdio');# Компилируем код (если необходимо)
npm run build
# Запускаем сервер
node index.jsЧтобы ИИ-система могла использовать ваш MCP-сервер, необходимо добавить его в конфигурационный файл MCP:
{
"mcpServers": {
"hello-world": {
"command": "node",
"args": ["/путь/к/вашему/серверу/index.js"],
"env": {}
}
}
}Вот и всё! Теперь у вас есть простой MCP-сервер. Конечно, он пока ничего не делает, но это основа, на которой мы будем строить наши примеры.
Теперь давайте рассмотрим конкретные примеры MCP-серверов — от самых простых до сложных и специализированных.
Самый простой MCP-сервер, который просто возвращает приветствие.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
class HelloWorldServer {
constructor() {
this.server = new Server(
{
name: 'hello-world-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'say_hello',
description: 'Returns a friendly greeting',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the person to greet',
}
},
required: ['name'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'say_hello') {
const name = request.params.arguments.name;
return {
content: [
{
type: 'text',
text: `Привет, ${name}! Как дела?`,
},
],
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Hello World MCP server running on stdio');
}
}
const server = new HelloWorldServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Поздоровайся со мной"
ИИ: [использует MCP-сервер] "Привет, [имя]! Как дела?"
MCP-сервер, который предоставляет информацию о погоде.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListResourcesRequestSchema,
ListResourceTemplatesRequestSchema,
ListToolsRequestSchema,
McpError,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
const API_KEY = process.env.OPENWEATHER_API_KEY;
if (!API_KEY) {
throw new Error('OPENWEATHER_API_KEY environment variable is required');
}
class WeatherServer {
constructor() {
this.server = new Server(
{
name: 'weather-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.axiosInstance = axios.create({
baseURL: 'http://api.openweathermap.org/data/2.5',
params: {
appid: API_KEY,
units: 'metric',
},
});
this.setupResourceHandlers();
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupResourceHandlers() {
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
resourceTemplates: [
{
uriTemplate: 'weather://{city}/current',
name: 'Current weather for a given city',
mimeType: 'application/json',
description: 'Real-time weather data for a specified city',
},
],
}));
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const match = request.params.uri.match(/^weather:\/\/([^/]+)\/current$/);
if (!match) {
throw new McpError(
ErrorCode.InvalidRequest,
`Invalid URI format: ${request.params.uri}`
);
}
const city = decodeURIComponent(match[1]);
try {
const response = await this.axiosInstance.get('weather', {
params: { q: city },
});
return {
contents: [
{
uri: request.params.uri,
mimeType: 'application/json',
text: JSON.stringify(
{
temperature: response.data.main.temp,
conditions: response.data.weather[0].description,
humidity: response.data.main.humidity,
wind_speed: response.data.wind.speed,
timestamp: new Date().toISOString(),
},
null,
2
),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
throw new McpError(
ErrorCode.InternalError,
`Weather API error: ${error.response?.data.message ?? error.message}`
);
}
throw error;
}
});
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'get_forecast',
description: 'Get weather forecast for a city',
inputSchema: {
type: 'object',
properties: {
city: {
type: 'string',
description: 'City name',
},
days: {
type: 'number',
description: 'Number of days (1-5)',
minimum: 1,
maximum: 5,
},
},
required: ['city'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'get_forecast') {
const city = request.params.arguments.city;
const days = Math.min(request.params.arguments.days || 3, 5);
try {
const response = await this.axiosInstance.get('forecast', {
params: {
q: city,
cnt: days * 8,
},
});
return {
content: [
{
type: 'text',
text: JSON.stringify(
response.data.list.map(item => ({
date: item.dt_txt,
temperature: item.main.temp,
conditions: item.weather[0].description,
humidity: item.main.humidity,
wind_speed: item.wind.speed,
})),
null,
2
),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: 'text',
text: `Weather API error: ${error.response?.data.message ?? error.message}`,
},
],
isError: true,
};
}
throw error;
}
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Weather MCP server running on stdio');
}
}
const server = new WeatherServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Какая погода в Москве?"
ИИ: [использует MCP-сервер] "В Москве сейчас 22°C, солнечно, влажность 45%, ветер 3 м/с"
MCP-сервер для создания, чтения и управления заметками.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
import path from 'path';
const NOTES_DIR = process.env.NOTES_DIR || './notes';
class NotesServer {
constructor() {
this.server = new Server(
{
name: 'notes-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureNotesDir() {
try {
await fs.mkdir(NOTES_DIR, { recursive: true });
} catch (error) {
console.error('Error creating notes directory:', error);
}
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'create_note',
description: 'Create a new note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note',
},
content: {
type: 'string',
description: 'Content of the note',
},
},
required: ['title', 'content'],
},
},
{
name: 'list_notes',
description: 'List all available notes',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'read_note',
description: 'Read a specific note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note to read',
},
},
required: ['title'],
},
},
{
name: 'delete_note',
description: 'Delete a specific note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note to delete',
},
},
required: ['title'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
await this.ensureNotesDir();
switch (request.params.name) {
case 'create_note': {
const { title, content } = request.params.arguments;
const filename = this.titleToFilename(title);
await fs.writeFile(path.join(NOTES_DIR, filename), content, 'utf8');
return {
content: [
{
type: 'text',
text: `Note "${title}" created successfully.`,
},
],
};
}
case 'list_notes': {
const files = await fs.readdir(NOTES_DIR);
const notes = files
.filter(file => file.endsWith('.txt'))
.map(file => this.filenameToTitle(file));
return {
content: [
{
type: 'text',
text: notes.length > 0
? `Available notes:\n${notes.map(note => `- ${note}`).join('\n')}`
: 'No notes found.',
},
],
};
}
case 'read_note': {
const { title } = request.params.arguments;
const filename = this.titleToFilename(title);
try {
const content = await fs.readFile(path.join(NOTES_DIR, filename), 'utf8');
return {
content: [
{
type: 'text',
text: `# ${title}\n\n${content}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Note "${title}" not found.`,
},
],
isError: true,
};
}
}
case 'delete_note': {
const { title } = request.params.arguments;
const filename = this.titleToFilename(title);
try {
await fs.unlink(path.join(NOTES_DIR, filename));
return {
content: [
{
type: 'text',
text: `Note "${title}" deleted successfully.`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error deleting note "${title}": ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
titleToFilename(title) {
return `${title.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}.txt`;
}
filenameToTitle(filename) {
return filename.replace(/\.txt$/, '').replace(/_/g, ' ');
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Notes MCP server running on stdio');
}
}
const server = new NotesServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Создай заметку с заголовком 'Покупки' и содержанием 'Молоко, хлеб, яйца'"
ИИ: [использует MCP-сервер] "Заметка 'Покупки' успешно создана."
Пользователь: "Покажи мне список всех заметок"
ИИ: [использует MCP-сервер] "Доступные заметки:
- Покупки"
Пользователь: "Прочитай заметку 'Покупки'"
ИИ: [использует MCP-сервер] "# Покупки
Молоко, хлеб, яйца"
MCP-сервер для поиска информации в интернете.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
import cheerio from 'cheerio';
class SearchServer {
constructor() {
this.server = new Server(
{
name: 'search-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'search_web',
description: 'Search the web for information',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query',
},
num_results: {
type: 'number',
description: 'Number of results to return',
minimum: 1,
maximum: 10,
},
},
required: ['query'],
},
},
{
name: 'fetch_url',
description: 'Fetch and summarize the content of a URL',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'URL to fetch',
},
},
required: ['url'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'search_web': {
const { query, num_results = 5 } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой поисковой API
const response = await axios.get('https://api.example.com/search', {
params: {
q: query,
limit: num_results,
},
headers: {
'Authorization': `Bearer ${process.env.SEARCH_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Search results for "${query}":\n\n` +
response.data.results.map((result, index) =>
`${index + 1}. ${result.title}\n ${result.url}\n ${result.snippet}\n`
).join('\n'),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error searching for "${query}": ${error.message}`,
},
],
isError: true,
};
}
}
case 'fetch_url': {
const { url } = request.params.arguments;
try {
const response = await axios.get(url);
const $ = cheerio.load(response.data);
// Базовое извлечение текста
const title = $('title').text().trim();
const metaDescription = $('meta[name="description"]').attr('content') || '';
// Извлечение основного содержимого (это базовая реализация)
let content = '';
$('p').each((i, elem) => {
content += $(elem).text().trim() + '\n\n';
});
return {
content: [
{
type: 'text',
text: `# ${title}\n\n${metaDescription}\n\n## Содержание страницы:\n\n${content}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error fetching URL "${url}": ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Search MCP server running on stdio');
}
}
const server = new SearchServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Найди информацию о солнечной системе"
ИИ: [использует MCP-сервер] "Результаты поиска для 'солнечная система':
1. Солнечная система - Википедия
https://ru.wikipedia.org/wiki/Солнечная_система
Солнечная система — планетная система, включающая центральную звезду Солнце и все естественные космические объекты...
2. ..."
Пользователь: "Покажи информацию с сайта о солнечной системе"
ИИ: [использует MCP-сервер fetch_url со ссылкой из результатов поиска]
MCP-сервер для перевода текста между разными языками.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
const TRANSLATION_API_KEY = process.env.TRANSLATION_API_KEY;
if (!TRANSLATION_API_KEY) {
throw new Error('TRANSLATION_API_KEY environment variable is required');
}
class TranslationServer {
constructor() {
this.server = new Server(
{
name: 'translation-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'translate_text',
description: 'Translate text between languages',
inputSchema: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'Text to translate',
},
source_language: {
type: 'string',
description: 'Source language code (e.g., "en", "ru", "fr")',
},
target_language: {
type: 'string',
description: 'Target language code (e.g., "en", "ru", "fr")',
},
},
required: ['text', 'target_language'],
},
},
{
name: 'detect_language',
description: 'Detect the language of a text',
inputSchema: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'Text to analyze',
},
},
required: ['text'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'translate_text': {
const { text, source_language, target_language } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой API перевода
const response = await axios.post('https://api.example.com/translate', {
text,
source: source_language || 'auto',
target: target_language,
}, {
headers: {
'Authorization': `Bearer ${TRANSLATION_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Перевод: ${response.data.translatedText}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error translating text: ${error.message}`,
},
],
isError: true,
};
}
}
case 'detect_language': {
const { text } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой API
const response = await axios.post('https://api.example.com/detect', {
text,
}, {
headers: {
'Authorization': `Bearer ${TRANSLATION_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Определенный язык: ${response.data.languageName} (${response.data.languageCode})\nУверенность: ${response.data.confidence * 100}%`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error detecting language: ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Translation MCP server running on stdio');
}
}
const server = new TranslationServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Переведи 'Здравствуйте, как дела?' на английский"
ИИ: [использует MCP-сервер] "Перевод: Hello, how are you?"
Пользователь: "Определи язык текста 'Bonjour, comment allez-vous?'"
ИИ: [использует MCP-сервер] "Определенный язык: Французский (fr)
Уверенность: 98%"
MCP-сервер для управления календарем и событиями.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
import path from 'path';
const CALENDAR_FILE = process.env.CALENDAR_FILE || './calendar.json';
class CalendarServer {
constructor() {
this.server = new Server(
{
name: 'calendar-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureCalendarFile() {
try {
await fs.access(CALENDAR_FILE);
} catch (error) {
// Файл не существует, создаем его с пустым календарем
await fs.writeFile(CALENDAR_FILE, JSON.stringify({ events: [] }, null, 2), 'utf8');
}
}
async getCalendar() {
await this.ensureCalendarFile();
const data = await fs.readFile(CALENDAR_FILE, 'utf8');
return JSON.parse(data);
}
async saveCalendar(calendar) {
await fs.writeFile(CALENDAR_FILE, JSON.stringify(calendar, null, 2), 'utf8');
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'add_event',
description: 'Add a new event to the calendar',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the event',
},
date: {
type: 'string',
description: 'Date of the event (YYYY-MM-DD)',
},
time: {
type: 'string',
description: 'Time of the event (HH:MM)',
},
description: {
type: 'string',
description: 'Description of the event',
},
},
required: ['title', 'date'],
},
},
{
name: 'list_events',
description: 'List events for a specific date or date range',
inputSchema: {
type: 'object',
properties: {
date: {
type: 'string',
description: 'Date to list events for (YYYY-MM-DD)',
},
start_date: {
type: 'string',
description: 'Start date for range (YYYY-MM-DD)',
},
end_date: {
type: 'string',
description: 'End date for range (YYYY-MM-DD)',
},
},
},
},
{
name: 'delete_event',
description: 'Delete an event from the calendar',
inputSchema: {
type: 'object',
properties: {
event_id: {
type: 'string',
description: 'ID of the event to delete',
},
},
required: ['event_id'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'add_event': {
const { title, date, time, description } = request.params.arguments;
const calendar = await this.getCalendar();
const newEvent = {
id: Date.now().toString(),
title,
date,
time: time || '',
description: description || '',
created_at: new Date().toISOString(),
};
calendar.events.push(newEvent);
await this.saveCalendar(calendar);
return {
content: [
{
type: 'text',
text: `Событие "${title}" добавлено в календарь на ${date}${time ? ' в ' + time : ''}.`,
},
],
};
}
case 'list_events': {
const { date, start_date, end_date } = request.params.arguments;
const calendar = await this.getCalendar();
let filteredEvents = [];
if (date) {
// Фильтрация по конкретной дате
filteredEvents = calendar.events.filter(event => event.date === date);
} else if (start_date && end_date) {
// Фильтрация по диапазону дат
filteredEvents = calendar.events.filter(event => {
return event.date >= start_date && event.date <= end_date;
});
} else {
// Возвращаем все события
filteredEvents = calendar.events;
}
// Сортировка по дате и времени
filteredEvents.sort((a, b) => {
if (a.date !== b.date) return a.date.localeCompare(b.date);
return a.time.localeCompare(b.time);
});
if (filteredEvents.length === 0) {
return {
content: [
{
type: 'text',
text: 'Нет событий для отображения.',
},
],
};
}
const eventsText = filteredEvents.map(event => {
return `ID: ${event.id}\nДата: ${event.date}${event.time ? ' ' + event.time : ''}\nНазвание: ${event.title}${event.description ? '\nОписание: ' + event.description : ''}`;
}).join('\n\n');
return {
content: [
{
type: 'text',
text: `События:\n\n${eventsText}`,
},
],
};
}
case 'delete_event': {
const { event_id } = request.params.arguments;
const calendar = await this.getCalendar();
const eventIndex = calendar.events.findIndex(event => event.id === event_id);
if (eventIndex === -1) {
return {
content: [
{
type: 'text',
text: `Событие с ID ${event_id} не найдено.`,
},
],
isError: true,
};
}
const deletedEvent = calendar.events[eventIndex];
calendar.events.splice(eventIndex, 1);
await this.saveCalendar(calendar);
return {
content: [
{
type: 'text',
text: `Событие "${deletedEvent.title}" удалено из календаря.`,
},
],
};
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Calendar MCP server running on stdio');
}
}
const server = new CalendarServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Добавь встречу с клиентом на 15 апреля в 14:00"
ИИ: [использует MCP-сервер] "Событие 'Встреча с клиентом' добавлено в календарь на 2025-04-15 в 14:00."
Пользователь: "Покажи мои события на апрель"
ИИ: [использует MCP-сервер] "События:
ID: 1710561723456
Дата: 2025-04-15 14:00
Название: Встреча с клиентом"
MCP-сервер для управления списком задач.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
const TASKS_FILE = process.env.TASKS_FILE || './tasks.json';
class TasksServer {
constructor() {
this.server = new Server(
{
name: 'tasks-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureTasksFile() {
try {
await fs.access(TASKS_FILE);
} catch (error) {
// Файл не существует, создаем его с пустым списком задач
await fs.writeFile(TASKS_FILE, JSON.stringify({ tasks: [] }, null, 2), 'utf8');
}
}
async getTasks() {
await this.ensureTasksFile();
const data = await fs.readFile(TASKS_FILE, 'utf8');
return JSON.parse(data);
}
async saveTasks(tasksData) {
await fs.writeFile(TASKS_FILE, JSON.stringify(tasksData, null, 2), 'utf8');
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'add_task',
description: 'Add a new task to the todo list',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the task',
},
priority: {
type: 'string',
description: 'Priority of the task (low, medium, high)',
enum: ['low', 'medium', 'high'],
},
due_date: {
type: 'string',
description: 'Due date for the task (YYYY-MM-DD)',
},
},
required: ['title'],
},
},
{
name: 'list_tasks',
description: 'List all tasks or filter by status',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
description: 'Filter tasks by status (pending, completed)',
enum: ['pending', 'completed'],
},
priority: {
type: 'string',
description: 'Filter tasks by priority (low, medium, high)',
enum: ['low', 'medium', 'high'],
},
},
},
},
{
name: 'complete_task',
description: 'Mark a task as completed',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'ID of the task to mark as completed',
},
},
required: ['task_id'],
},
},
{
name: 'delete_task',
description: 'Delete a task from the todo list',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'ID of the task to delete',
},
},
required: ['task_id'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'add_task': {
const { title, priority = 'medium', due_date } = request.params.arguments;
const tasksData = await this.getTasks();
const newTask = {
id: Date.now().toString(),
title,
priority,
due_date: due_date || null,
status: 'pending',
created_at: new Date().toISOString(),
};
tasksData.tasks.push(newTask);
await this.saveTasks(tasksData);
return {
content: [
{
type: 'text',
text: `Задача "${title}" добавлена в список.`,
},
],
};
}
case 'list_tasks': {
const { status, priority } = request.params.arguments;
const tasksData = await this.getTasks();
let filteredTasks = tasksData.tasks;
if (status) {
filteredTasks = filteredTasks.filter(task => task.status === status);
}
if (priority) {
filteredTasks = filteredTasks.filter(task => task.priority === priority);
}
// Сортировка по приоритету и дате создания
const priorityOrder = { high: 0, medium: 1, low: 2 };
filteredTasks.sort((a, b) => {
if (priorityOrder[a.priority] !== priorityOrder[b.priority]) {
return priorityOrder[a.priority] - priorityOrder[b.priority];
}
return new Date(a.created_at) - new Date(b.created_at);
});
if (filteredTasks.length === 0) {
return {
content: [
{
type: 'text',
text: 'Нет задач для отображения.',
},
],
};
}
const tasksText = filteredTasks.map(task => {
const statusEmoji = task.status === 'completed' ? '✅' : '⏳';
const priorityEmoji = {
low: '🟢',
medium: '🟡',
high: '🔴',
}[task.priority];
return `${statusEmoji} ${priorityEmoji} [${task.id}] ${task.title}${task.due_date ? ' (до ' + task.due_date + ')' : ''}`;
}).join('\n');
return {
content: [
{
type: 'text',
text: `Задачи:\n\n${tas
# Всеобъемлющее руководство по MCP-серверам
## Содержание
1. [Введение в MCP](#введение-в-mcp)
2. [Что такое MCP-серверы?](#что-такое-mcp-серверы)
3. [Основные концепции](#основные-концепции)
4. [Создание вашего первого MCP-сервера](#создание-вашего-первого-mcp-сервера)
5. [Примеры использования MCP-серверов](#примеры-использования-mcp-серверов)
6. [Продвинутые возможности](#продвинутые-возможности)
7. [Лучшие практики](#лучшие-практики)
8. [Устранение неполадок](#устранение-неполадок)
9. [Заключение](#заключение)
---
## Введение в MCP
MCP (Model Context Protocol) — это протокол, который позволяет крупным языковым моделям (LLM) и другим интеллектуальным системам взаимодействовать с внешними сервисами и ресурсами. Представьте это как мост, который соединяет мощные ИИ-системы (такие как Claude) с внешним миром — интернетом, базами данных, API и другими источниками информации.
### Почему это важно?
Представьте, что вы разговариваете с умным помощником, но он не может:
- Проверить актуальную погоду
- Найти что-то в интернете
- Взаимодействовать с вашими файлами
- Использовать калькулятор
- Отправить email
Это существенно ограничивает его полезность. MCP решает эту проблему, позволяя ИИ-системам обращаться к внешним сервисам и расширять свои возможности.
### Простая аналогия
Представьте MCP как "USB-порт для ИИ". Так же как USB-порт позволяет подключать к компьютеру разные устройства (камеры, мышки, принтеры), MCP позволяет подключать к ИИ разные инструменты и сервисы.
---
## Что такое MCP-серверы?
MCP-сервер — это программа, которая:
1. **Предоставляет определенную функциональность** (например, доступ к погоде, работу с файлами и т.д.)
2. **Говорит на языке протокола MCP**, чтобы ИИ-системы могли ее использовать
3. **Работает как "мост"** между ИИ-системой и внешними сервисами/API
### Простая аналогия
Если представить, что ИИ — это клиент ресторана, то MCP-серверы — это официанты, которые приносят блюда (информацию/сервисы) с кухни (интернета/внешних API).
### Что могут делать MCP-серверы?
MCP-серверы могут предоставлять ИИ доступ к:
- Поиску в интернете
- Прогнозу погоды
- Новостям
- Базам данных
- Калькуляторам
- Системным операциям
- API сторонних сервисов (Twitter, Spotify, YouTube и т.д.)
- И многому другому!
---
## Основные концепции
Перед тем как мы перейдем к примерам, давайте разберемся с основными концепциями, которые нужно понимать:
### 1. Инструменты (Tools)
**Инструменты** — это функции или операции, которые MCP-сервер может выполнять. Например, "получить прогноз погоды", "найти информацию в Google", "отправить email".Аналогия: Инструменты — это кнопки на пульте телевизора. Каждая кнопка выполняет определенное действие.
### 2. Ресурсы (Resources)
**Ресурсы** — это данные или информация, к которым MCP-сервер обеспечивает доступ. Например, текущая погода, информация о фильме, данные из базы.
Аналогия: Ресурсы — это книги в библиотеке. ИИ может "взять книгу" и прочитать информацию из нее.
### 3. Шаблоны ресурсов (Resource Templates)
**Шаблоны ресурсов** определяют, как можно обращаться к динамическим ресурсам. Например, шаблон `weather://{город}/current` позволяет запрашивать погоду для любого города.
Аналогия: Шаблон ресурса — это как карточный каталог в библиотеке, который помогает найти нужную книгу по описанию.
### 4. URI (Uniform Resource Identifier)
**URI** — это уникальный идентификатор ресурса, который используется для обращения к конкретному ресурсу. Например, `weather://Moscow/current`.
Аналогия: URI — это как адрес в городе. Он позволяет точно найти нужный "дом" (ресурс).
### 5. Запросы и ответы (Requests & Responses)
MCP работает по модели запрос-ответ:
- ИИ отправляет **запрос** на MCP-сервер (например, "какая погода в Москве?")
- MCP-сервер обрабатывает запрос и отправляет **ответ** (например, "в Москве 22°C, солнечно")
Аналогия: Это как разговор по телефону — вы задаете вопрос, собеседник отвечает.
---
## Создание вашего первого MCP-сервера
Давайте рассмотрим процесс создания простого MCP-сервера шаг за шагом. Не беспокойтесь, если вы не знаете, как программировать — мы рассмотрим общую концепцию, а потом перейдем к готовым примерам.
### Шаг 1: Установка необходимых инструментов
Для создания MCP-сервера вам понадобятся:
- Node.js (среда выполнения JavaScript)
- npm (менеджер пакетов)
- MCP SDK (набор инструментов для работы с MCP)
### Шаг 2: Инициализация проекта
```bash
# Создаем новую директорию
mkdir my-weather-server
cd my-weather-server
# Инициализируем проект
npm init -y
# Устанавливаем MCP SDK и другие необходимые пакеты
npm install @modelcontextprotocol/sdk axios
Создадим файл index.js с базовым кодом MCP-сервера:
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
// Создаем экземпляр сервера
const server = new Server(
{
name: 'hello-world-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
// Добавляем обработчик ошибок
server.onerror = (error) => console.error('[MCP Error]', error);
// Запускаем сервер
const transport = new StdioServerTransport();
server.connect(transport).catch(console.error);
console.error('Hello World MCP server running on stdio');# Компилируем код (если необходимо)
npm run build
# Запускаем сервер
node index.jsЧтобы ИИ-система могла использовать ваш MCP-сервер, необходимо добавить его в конфигурационный файл MCP:
{
"mcpServers": {
"hello-world": {
"command": "node",
"args": ["/путь/к/вашему/серверу/index.js"],
"env": {}
}
}
}Вот и всё! Теперь у вас есть простой MCP-сервер. Конечно, он пока ничего не делает, но это основа, на которой мы будем строить наши примеры.
Теперь давайте рассмотрим конкретные примеры MCP-серверов — от самых простых до сложных и специализированных.
Самый простой MCP-сервер, который просто возвращает приветствие.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
class HelloWorldServer {
constructor() {
this.server = new Server(
{
name: 'hello-world-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'say_hello',
description: 'Returns a friendly greeting',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the person to greet',
}
},
required: ['name'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'say_hello') {
const name = request.params.arguments.name;
return {
content: [
{
type: 'text',
text: `Привет, ${name}! Как дела?`,
},
],
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Hello World MCP server running on stdio');
}
}
const server = new HelloWorldServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Поздоровайся со мной"
ИИ: [использует MCP-сервер] "Привет, [имя]! Как дела?"
MCP-сервер, который предоставляет информацию о погоде.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListResourcesRequestSchema,
ListResourceTemplatesRequestSchema,
ListToolsRequestSchema,
McpError,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
const API_KEY = process.env.OPENWEATHER_API_KEY;
if (!API_KEY) {
throw new Error('OPENWEATHER_API_KEY environment variable is required');
}
class WeatherServer {
constructor() {
this.server = new Server(
{
name: 'weather-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.axiosInstance = axios.create({
baseURL: 'http://api.openweathermap.org/data/2.5',
params: {
appid: API_KEY,
units: 'metric',
},
});
this.setupResourceHandlers();
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupResourceHandlers() {
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
resourceTemplates: [
{
uriTemplate: 'weather://{city}/current',
name: 'Current weather for a given city',
mimeType: 'application/json',
description: 'Real-time weather data for a specified city',
},
],
}));
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const match = request.params.uri.match(/^weather:\/\/([^/]+)\/current$/);
if (!match) {
throw new McpError(
ErrorCode.InvalidRequest,
`Invalid URI format: ${request.params.uri}`
);
}
const city = decodeURIComponent(match[1]);
try {
const response = await this.axiosInstance.get('weather', {
params: { q: city },
});
return {
contents: [
{
uri: request.params.uri,
mimeType: 'application/json',
text: JSON.stringify(
{
temperature: response.data.main.temp,
conditions: response.data.weather[0].description,
humidity: response.data.main.humidity,
wind_speed: response.data.wind.speed,
timestamp: new Date().toISOString(),
},
null,
2
),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
throw new McpError(
ErrorCode.InternalError,
`Weather API error: ${error.response?.data.message ?? error.message}`
);
}
throw error;
}
});
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'get_forecast',
description: 'Get weather forecast for a city',
inputSchema: {
type: 'object',
properties: {
city: {
type: 'string',
description: 'City name',
},
days: {
type: 'number',
description: 'Number of days (1-5)',
minimum: 1,
maximum: 5,
},
},
required: ['city'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'get_forecast') {
const city = request.params.arguments.city;
const days = Math.min(request.params.arguments.days || 3, 5);
try {
const response = await this.axiosInstance.get('forecast', {
params: {
q: city,
cnt: days * 8,
},
});
return {
content: [
{
type: 'text',
text: JSON.stringify(
response.data.list.map(item => ({
date: item.dt_txt,
temperature: item.main.temp,
conditions: item.weather[0].description,
humidity: item.main.humidity,
wind_speed: item.wind.speed,
})),
null,
2
),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: 'text',
text: `Weather API error: ${error.response?.data.message ?? error.message}`,
},
],
isError: true,
};
}
throw error;
}
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Weather MCP server running on stdio');
}
}
const server = new WeatherServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Какая погода в Москве?"
ИИ: [использует MCP-сервер] "В Москве сейчас 22°C, солнечно, влажность 45%, ветер 3 м/с"
MCP-сервер для создания, чтения и управления заметками.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
import path from 'path';
const NOTES_DIR = process.env.NOTES_DIR || './notes';
class NotesServer {
constructor() {
this.server = new Server(
{
name: 'notes-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureNotesDir() {
try {
await fs.mkdir(NOTES_DIR, { recursive: true });
} catch (error) {
console.error('Error creating notes directory:', error);
}
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'create_note',
description: 'Create a new note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note',
},
content: {
type: 'string',
description: 'Content of the note',
},
},
required: ['title', 'content'],
},
},
{
name: 'list_notes',
description: 'List all available notes',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'read_note',
description: 'Read a specific note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note to read',
},
},
required: ['title'],
},
},
{
name: 'delete_note',
description: 'Delete a specific note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note to delete',
},
},
required: ['title'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
await this.ensureNotesDir();
switch (request.params.name) {
case 'create_note': {
const { title, content } = request.params.arguments;
const filename = this.titleToFilename(title);
await fs.writeFile(path.join(NOTES_DIR, filename), content, 'utf8');
return {
content: [
{
type: 'text',
text: `Note "${title}" created successfully.`,
},
],
};
}
case 'list_notes': {
const files = await fs.readdir(NOTES_DIR);
const notes = files
.filter(file => file.endsWith('.txt'))
.map(file => this.filenameToTitle(file));
return {
content: [
{
type: 'text',
text: notes.length > 0
? `Available notes:\n${notes.map(note => `- ${note}`).join('\n')}`
: 'No notes found.',
},
],
};
}
case 'read_note': {
const { title } = request.params.arguments;
const filename = this.titleToFilename(title);
try {
const content = await fs.readFile(path.join(NOTES_DIR, filename), 'utf8');
return {
content: [
{
type: 'text',
text: `# ${title}\n\n${content}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Note "${title}" not found.`,
},
],
isError: true,
};
}
}
case 'delete_note': {
const { title } = request.params.arguments;
const filename = this.titleToFilename(title);
try {
await fs.unlink(path.join(NOTES_DIR, filename));
return {
content: [
{
type: 'text',
text: `Note "${title}" deleted successfully.`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error deleting note "${title}": ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
titleToFilename(title) {
return `${title.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}.txt`;
}
filenameToTitle(filename) {
return filename.replace(/\.txt$/, '').replace(/_/g, ' ');
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Notes MCP server running on stdio');
}
}
const server = new NotesServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Создай заметку с заголовком 'Покупки' и содержанием 'Молоко, хлеб, яйца'"
ИИ: [использует MCP-сервер] "Заметка 'Покупки' успешно создана."
Пользователь: "Покажи мне список всех заметок"
ИИ: [использует MCP-сервер] "Доступные заметки:
- Покупки"
Пользователь: "Прочитай заметку 'Покупки'"
ИИ: [использует MCP-сервер] "# Покупки
Молоко, хлеб, яйца"
MCP-сервер для поиска информации в интернете.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
import cheerio from 'cheerio';
class SearchServer {
constructor() {
this.server = new Server(
{
name: 'search-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'search_web',
description: 'Search the web for information',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query',
},
num_results: {
type: 'number',
description: 'Number of results to return',
minimum: 1,
maximum: 10,
},
},
required: ['query'],
},
},
{
name: 'fetch_url',
description: 'Fetch and summarize the content of a URL',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'URL to fetch',
},
},
required: ['url'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'search_web': {
const { query, num_results = 5 } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой поисковой API
const response = await axios.get('https://api.example.com/search', {
params: {
q: query,
limit: num_results,
},
headers: {
'Authorization': `Bearer ${process.env.SEARCH_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Search results for "${query}":\n\n` +
response.data.results.map((result, index) =>
`${index + 1}. ${result.title}\n ${result.url}\n ${result.snippet}\n`
).join('\n'),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error searching for "${query}": ${error.message}`,
},
],
isError: true,
};
}
}
case 'fetch_url': {
const { url } = request.params.arguments;
try {
const response = await axios.get(url);
const $ = cheerio.load(response.data);
// Базовое извлечение текста
const title = $('title').text().trim();
const metaDescription = $('meta[name="description"]').attr('content') || '';
// Извлечение основного содержимого (это базовая реализация)
let content = '';
$('p').each((i, elem) => {
content += $(elem).text().trim() + '\n\n';
});
return {
content: [
{
type: 'text',
text: `# ${title}\n\n${metaDescription}\n\n## Содержание страницы:\n\n${content}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error fetching URL "${url}": ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Search MCP server running on stdio');
}
}
const server = new SearchServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Найди информацию о солнечной системе"
ИИ: [использует MCP-сервер] "Результаты поиска для 'солнечная система':
1. Солнечная система - Википедия
https://ru.wikipedia.org/wiki/Солнечная_система
Солнечная система — планетная система, включающая центральную звезду Солнце и все естественные космические объекты...
2. ..."
Пользователь: "Покажи информацию с сайта о солнечной системе"
ИИ: [использует MCP-сервер fetch_url со ссылкой из результатов поиска]
MCP-сервер для перевода текста между разными языками.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
const TRANSLATION_API_KEY = process.env.TRANSLATION_API_KEY;
if (!TRANSLATION_API_KEY) {
throw new Error('TRANSLATION_API_KEY environment variable is required');
}
class TranslationServer {
constructor() {
this.server = new Server(
{
name: 'translation-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'translate_text',
description: 'Translate text between languages',
inputSchema: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'Text to translate',
},
source_language: {
type: 'string',
description: 'Source language code (e.g., "en", "ru", "fr")',
},
target_language: {
type: 'string',
description: 'Target language code (e.g., "en", "ru", "fr")',
},
},
required: ['text', 'target_language'],
},
},
{
name: 'detect_language',
description: 'Detect the language of a text',
inputSchema: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'Text to analyze',
},
},
required: ['text'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'translate_text': {
const { text, source_language, target_language } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой API перевода
const response = await axios.post('https://api.example.com/translate', {
text,
source: source_language || 'auto',
target: target_language,
}, {
headers: {
'Authorization': `Bearer ${TRANSLATION_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Перевод: ${response.data.translatedText}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error translating text: ${error.message}`,
},
],
isError: true,
};
}
}
case 'detect_language': {
const { text } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой API
const response = await axios.post('https://api.example.com/detect', {
text,
}, {
headers: {
'Authorization': `Bearer ${TRANSLATION_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Определенный язык: ${response.data.languageName} (${response.data.languageCode})\nУверенность: ${response.data.confidence * 100}%`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error detecting language: ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Translation MCP server running on stdio');
}
}
const server = new TranslationServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Переведи 'Здравствуйте, как дела?' на английский"
ИИ: [использует MCP-сервер] "Перевод: Hello, how are you?"
Пользователь: "Определи язык текста 'Bonjour, comment allez-vous?'"
ИИ: [использует MCP-сервер] "Определенный язык: Французский (fr)
Уверенность: 98%"
MCP-сервер для управления календарем и событиями.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
import path from 'path';
const CALENDAR_FILE = process.env.CALENDAR_FILE || './calendar.json';
class CalendarServer {
constructor() {
this.server = new Server(
{
name: 'calendar-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureCalendarFile() {
try {
await fs.access(CALENDAR_FILE);
} catch (error) {
// Файл не существует, создаем его с пустым календарем
await fs.writeFile(CALENDAR_FILE, JSON.stringify({ events: [] }, null, 2), 'utf8');
}
}
async getCalendar() {
await this.ensureCalendarFile();
const data = await fs.readFile(CALENDAR_FILE, 'utf8');
return JSON.parse(data);
}
async saveCalendar(calendar) {
await fs.writeFile(CALENDAR_FILE, JSON.stringify(calendar, null, 2), 'utf8');
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'add_event',
description: 'Add a new event to the calendar',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the event',
},
date: {
type: 'string',
description: 'Date of the event (YYYY-MM-DD)',
},
time: {
type: 'string',
description: 'Time of the event (HH:MM)',
},
description: {
type: 'string',
description: 'Description of the event',
},
},
required: ['title', 'date'],
},
},
{
name: 'list_events',
description: 'List events for a specific date or date range',
inputSchema: {
type: 'object',
properties: {
date: {
type: 'string',
description: 'Date to list events for (YYYY-MM-DD)',
},
start_date: {
type: 'string',
description: 'Start date for range (YYYY-MM-DD)',
},
end_date: {
type: 'string',
description: 'End date for range (YYYY-MM-DD)',
},
},
},
},
{
name: 'delete_event',
description: 'Delete an event from the calendar',
inputSchema: {
type: 'object',
properties: {
event_id: {
type: 'string',
description: 'ID of the event to delete',
},
},
required: ['event_id'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'add_event': {
const { title, date, time, description } = request.params.arguments;
const calendar = await this.getCalendar();
const newEvent = {
id: Date.now().toString(),
title,
date,
time: time || '',
description: description || '',
created_at: new Date().toISOString(),
};
calendar.events.push(newEvent);
await this.saveCalendar(calendar);
return {
content: [
{
type: 'text',
text: `Событие "${title}" добавлено в календарь на ${date}${time ? ' в ' + time : ''}.`,
},
],
};
}
case 'list_events': {
const { date, start_date, end_date } = request.params.arguments;
const calendar = await this.getCalendar();
let filteredEvents = [];
if (date) {
// Фильтрация по конкретной дате
filteredEvents = calendar.events.filter(event => event.date === date);
} else if (start_date && end_date) {
// Фильтрация по диапазону дат
filteredEvents = calendar.events.filter(event => {
return event.date >= start_date && event.date <= end_date;
});
} else {
// Возвращаем все события
filteredEvents = calendar.events;
}
// Сортировка по дате и времени
filteredEvents.sort((a, b) => {
if (a.date !== b.date) return a.date.localeCompare(b.date);
return a.time.localeCompare(b.time);
});
if (filteredEvents.length === 0) {
return {
content: [
{
type: 'text',
text: 'Нет событий для отображения.',
},
],
};
}
const eventsText = filteredEvents.map(event => {
return `ID: ${event.id}\nДата: ${event.date}${event.time ? ' ' + event.time : ''}\nНазвание: ${event.title}${event.description ? '\nОписание: ' + event.description : ''}`;
}).join('\n\n');
return {
content: [
{
type: 'text',
text: `События:\n\n${eventsText}`,
},
],
};
}
case 'delete_event': {
const { event_id } = request.params.arguments;
const calendar = await this.getCalendar();
const eventIndex = calendar.events.findIndex(event => event.id === event_id);
if (eventIndex === -1) {
return {
content: [
{
type: 'text',
text: `Событие с ID ${event_id} не найдено.`,
},
],
isError: true,
};
}
const deletedEvent = calendar.events[eventIndex];
calendar.events.splice(eventIndex, 1);
await this.saveCalendar(calendar);
return {
content: [
{
type: 'text',
text: `Событие "${deletedEvent.title}" удалено из календаря.`,
},
],
};
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Calendar MCP server running on stdio');
}
}
const server = new CalendarServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Добавь встречу с клиентом на 15 апреля в 14:00"
ИИ: [использует MCP-сервер] "Событие 'Встреча с клиентом' добавлено в календарь на 2025-04-15 в 14:00."
Пользователь: "Покажи мои события на апрель"
ИИ: [использует MCP-сервер] "События:
ID: 1710561723456
Дата: 2025-04-15 14:00
Название: Встреча с клиентом"
MCP-сервер для управления списком задач.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
const TASKS_FILE = process.env.TASKS_FILE || './tasks.json';
class TasksServer {
constructor() {
this.server = new Server(
{
name: 'tasks-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureTasksFile() {
try {
await fs.access(TASKS_FILE);
} catch (error) {
// Файл не существует, создаем его с пустым списком задач
await fs.writeFile(TASKS_FILE, JSON.stringify({ tasks: [] }, null, 2), 'utf8');
}
}
async getTasks() {
await this.ensureTasksFile();
const data = await fs.readFile(TASKS_FILE, 'utf8');
return JSON.parse(data);
}
async saveTasks(tasksData) {
await fs.writeFile(TASKS_FILE, JSON.stringify(tasksData, null, 2), 'utf8');
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'add_task',
description: 'Add a new task to the todo list',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the task',
},
priority: {
type: 'string',
description: 'Priority of the task (low, medium, high)',
enum: ['low', 'medium', 'high'],
},
due_date: {
type: 'string',
description: 'Due date for the task (YYYY-MM-DD)',
},
},
required: ['title'],
},
},
{
name: 'list_tasks',
description: 'List all tasks or filter by status',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
description: 'Filter tasks by status (pending, completed)',
enum: ['pending', 'completed'],
},
priority: {
type: 'string',
description: 'Filter tasks by priority (low, medium, high)',
enum: ['low', 'medium', 'high'],
},
},
},
},
{
name: 'complete_task',
description: 'Mark a task as completed',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'ID of the task to mark as completed',
},
},
required: ['task_id'],
},
},
{
name: 'delete_task',
description: 'Delete a task from the todo list',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'ID of the task to delete',
},
},
required: ['task_id'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'add_task': {
const { title, priority = 'medium', due_date } = request.params.arguments;
const tasksData = await this.getTasks();
const newTask = {
id: Date.now().toString(),
title,
priority,
due_date: due_date || null,
status: 'pending',
created_at: new Date().toISOString(),
};
tasksData.tasks.push(newTask);
await this.saveTasks(tasksData);
return {
content: [
{
type: 'text',
text: `Задача "${title}" добавлена в список.`,
},
],
};
}
case 'list_tasks': {
const { status, priority } = request.params.arguments;
const tasksData = await this.getTasks();
let filteredTasks = tasksData.tasks;
if (status) {
filteredTasks = filteredTasks.filter(task => task.status === status);
}
if (priority) {
filteredTasks = filteredTasks.filter(task => task.priority === priority);
}
// Сортировка по приоритету и дате создания
const priorityOrder = { high: 0, medium: 1, low: 2 };
filteredTasks.sort((a, b) => {
if (priorityOrder[a.priority] !== priorityOrder[b.priority]) {
return priorityOrder[a.priority] - priorityOrder[b.priority];
}
return new Date(a.created_at) - new Date(b.created_at);
});
if (filteredTasks.length === 0) {
return {
content: [
{
type: 'text',
text: 'Нет задач для отображения.',
},
],
};
}
const tasksText = filteredTasks.map(task => {
const statusEmoji = task.status === 'completed' ? '✅' : '⏳';
const priorityEmoji = {
low: '🟢',
medium: '🟡',
high: '🔴',
}[task.priority];
return `${statusEmoji} ${priorityEmoji} [${task.id}] ${task.title}${task.due_date ? ' (до ' + task.due_date + ')' : ''}`;
}).join('\n');
return {
content: [
{
type: 'text',
text: `Задачи:\n\n${tasksText}`,
},
],
};
}
case 'complete_task': {
const { task_id } = request.params.arguments;
const tasksData = await this.getTasks();
const taskIndex = tasksData.tasks.findIndex(task => task.id === task_id);
if (taskIndex === -1) {
return {
content: [
{
type: 'text',
text: `Задача с ID ${task_id} не найдена.`,
},
],
isError: true,
};
}
tasksData.tasks[taskIndex].status = 'completed';
await this.saveTasks(tasksData);
return {
content: [
{
type: 'text',
text: `Задача "${tasksData.tasks[taskIndex].title}" отмечена как выполненная.`,
},
],
};
}
case 'delete_task': {
const { task_id } = request.params.arguments;
const tasksData = await this.getTasks();
const taskIndex = tasksData.tasks.findIndex(task => task.id === task_id);
if (taskIndex === -1) {
return {
content: [
{
type: 'text',
text: `Задача с ID ${task_id} не найдена.`,
},
],
isError: true,
};
}
const deletedTask = tasksData.tasks[taskIndex];
tasksData.tasks.splice(taskIndex, 1);
await this.saveTasks(tasksData);
return {
content: [
{
type: 'text',
text: `Задача "${deletedTask.title}" удалена из списка.`,
},
],
};
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Tasks MCP server running on stdio');
}
}
const server = new TasksServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Добавь задачу 'Купить продукты' с высоким приоритетом"
ИИ: [использует MCP-сервер] "Задача 'Купить продукты' добавлена в список."
Пользователь: "Покажи мои задачи"
ИИ: [использует MCP-сервер] "Задачи:
🔴 ⏳ [1710561823456] Купить продукты"
Пользователь: "Отметь задачу с ID 1710561823456 как выполненную"
ИИ: [использует MCP-сервер] "Задача 'Купить продукты' отмечена как выполненная."
MCP-сервер для выполнения запросов к базе данных.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
const DB_PATH = process.env.DB_PATH || './database.sqlite';
class DatabaseServer {
constructor() {
this.server = new Server(
{
name: 'database-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async getDbConnection() {
if (!this.db) {
this.db = await open({
filename: DB_PATH,
driver: sqlite3.Database,
});
}
return this.db;
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'execute_query',
description: 'Execute an SQL query on the database',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'SQL query to execute',
},
params: {
type: 'array',
description: 'Parameters for the query',
items: {
type: ['string', 'number', 'boolean', 'null'],
},
},
},
required: ['query'],
},
},
{
name: 'get_tables',
description: 'Get a list of tables in the database',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'get_table_schema',
description: 'Get the schema for a specific table',
inputSchema: {
type: 'object',
properties: {
table_name: {
type: 'string',
description: 'Name of the table',
},
},
required: ['table_name'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const db = await this.getDbConnection();
switch (request.params.name) {
case 'execute_query': {
const { query, params = [] } = request.params.arguments;
try {
// Проверка, является ли запрос SELECT или нет
const isSelect = query.trim().toUpperCase().startsWith('SELECT');
let result;
if (isSelect) {
result = await db.all(query, params);
} else {
result = await db.run(query, params);
}
return {
content: [
{
type: 'text',
text: isSelect
? `Результаты запроса:\n\n${JSON.stringify(result, null, 2)}`
: `Запрос выполнен успешно. Затронуто строк: ${result.changes || 0}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Ошибка выполнения запроса: ${error.message}`,
},
],
isError: true,
};
}
}
case 'get_tables': {
try {
const result = await db.all("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");
const tables = result.map(row => row.name);
return {
content: [
{
type: 'text',
text: tables.length > 0
? `Таблицы в базе данных:\n\n${tables.join('\n')}`
: 'В базе данных нет таблиц.',
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Ошибка получения списка таблиц: ${error.message}`,
},
],
isError: true,
};
}
}
case 'get_table_schema': {
const { table_name } = request.params.arguments;
try {
const result = await db.all(`PRAGMA table_info(${table_name})`);
if (result.length === 0) {
return {
content: [
{
type: 'text',
text: `Таблица '${table_name}' не найдена.`,
},
],
isError: true,
};
}
const columns = result.map(col => {
return `${col.
# Всеобъемлющее руководство по MCP-серверам
## Содержание
1. [Введение в MCP](#введение-в-mcp)
2. [Что такое MCP-серверы?](#что-такое-mcp-серверы)
3. [Основные концепции](#основные-концепции)
4. [Создание вашего первого MCP-сервера](#создание-вашего-первого-mcp-сервера)
5. [Примеры использования MCP-серверов](#примеры-использования-mcp-серверов)
6. [Продвинутые возможности](#продвинутые-возможности)
7. [Лучшие практики](#лучшие-практики)
8. [Устранение неполадок](#устранение-неполадок)
9. [Заключение](#заключение)
---
## Введение в MCP
MCP (Model Context Protocol) — это протокол, который позволяет крупным языковым моделям (LLM) и другим интеллектуальным системам взаимодействовать с внешними сервисами и ресурсами. Представьте это как мост, который соединяет мощные ИИ-системы (такие как Claude) с внешним миром — интернетом, базами данных, API и другими источниками информации.
### Почему это важно?
Представьте, что вы разговариваете с умным помощником, но он не может:
- Проверить актуальную погоду
- Найти что-то в интернете
- Взаимодействовать с вашими файлами
- Использовать калькулятор
- Отправить email
Это существенно ограничивает его полезность. MCP решает эту проблему, позволяя ИИ-системам обращаться к внешним сервисам и расширять свои возможности.
### Простая аналогия
Представьте MCP как "USB-порт для ИИ". Так же как USB-порт позволяет подключать к компьютеру разные устройства (камеры, мышки, принтеры), MCP позволяет подключать к ИИ разные инструменты и сервисы.
---
## Что такое MCP-серверы?
MCP-сервер — это программа, которая:
1. **Предоставляет определенную функциональность** (например, доступ к погоде, работу с файлами и т.д.)
2. **Говорит на языке протокола MCP**, чтобы ИИ-системы могли ее использовать
3. **Работает как "мост"** между ИИ-системой и внешними сервисами/API
### Простая аналогия
Если представить, что ИИ — это клиент ресторана, то MCP-серверы — это официанты, которые приносят блюда (информацию/сервисы) с кухни (интернета/внешних API).
### Что могут делать MCP-серверы?
MCP-серверы могут предоставлять ИИ доступ к:
- Поиску в интернете
- Прогнозу погоды
- Новостям
- Базам данных
- Калькуляторам
- Системным операциям
- API сторонних сервисов (Twitter, Spotify, YouTube и т.д.)
- И многому другому!
---
## Основные концепции
Перед тем как мы перейдем к примерам, давайте разберемся с основными концепциями, которые нужно понимать:
### 1. Инструменты (Tools)
**Инструменты** — это функции или операции, которые MCP-сервер может выполнять. Например, "получить прогноз погоды", "найти информацию в Google", "отправить email".Аналогия: Инструменты — это кнопки на пульте телевизора. Каждая кнопка выполняет определенное действие.
### 2. Ресурсы (Resources)
**Ресурсы** — это данные или информация, к которым MCP-сервер обеспечивает доступ. Например, текущая погода, информация о фильме, данные из базы.
Аналогия: Ресурсы — это книги в библиотеке. ИИ может "взять книгу" и прочитать информацию из нее.
### 3. Шаблоны ресурсов (Resource Templates)
**Шаблоны ресурсов** определяют, как можно обращаться к динамическим ресурсам. Например, шаблон `weather://{город}/current` позволяет запрашивать погоду для любого города.
Аналогия: Шаблон ресурса — это как карточный каталог в библиотеке, который помогает найти нужную книгу по описанию.
### 4. URI (Uniform Resource Identifier)
**URI** — это уникальный идентификатор ресурса, который используется для обращения к конкретному ресурсу. Например, `weather://Moscow/current`.
Аналогия: URI — это как адрес в городе. Он позволяет точно найти нужный "дом" (ресурс).
### 5. Запросы и ответы (Requests & Responses)
MCP работает по модели запрос-ответ:
- ИИ отправляет **запрос** на MCP-сервер (например, "какая погода в Москве?")
- MCP-сервер обрабатывает запрос и отправляет **ответ** (например, "в Москве 22°C, солнечно")
Аналогия: Это как разговор по телефону — вы задаете вопрос, собеседник отвечает.
---
## Создание вашего первого MCP-сервера
Давайте рассмотрим процесс создания простого MCP-сервера шаг за шагом. Не беспокойтесь, если вы не знаете, как программировать — мы рассмотрим общую концепцию, а потом перейдем к готовым примерам.
### Шаг 1: Установка необходимых инструментов
Для создания MCP-сервера вам понадобятся:
- Node.js (среда выполнения JavaScript)
- npm (менеджер пакетов)
- MCP SDK (набор инструментов для работы с MCP)
### Шаг 2: Инициализация проекта
```bash
# Создаем новую директорию
mkdir my-weather-server
cd my-weather-server
# Инициализируем проект
npm init -y
# Устанавливаем MCP SDK и другие необходимые пакеты
npm install @modelcontextprotocol/sdk axios
Создадим файл index.js с базовым кодом MCP-сервера:
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
// Создаем экземпляр сервера
const server = new Server(
{
name: 'hello-world-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
// Добавляем обработчик ошибок
server.onerror = (error) => console.error('[MCP Error]', error);
// Запускаем сервер
const transport = new StdioServerTransport();
server.connect(transport).catch(console.error);
console.error('Hello World MCP server running on stdio');# Компилируем код (если необходимо)
npm run build
# Запускаем сервер
node index.jsЧтобы ИИ-система могла использовать ваш MCP-сервер, необходимо добавить его в конфигурационный файл MCP:
{
"mcpServers": {
"hello-world": {
"command": "node",
"args": ["/путь/к/вашему/серверу/index.js"],
"env": {}
}
}
}Вот и всё! Теперь у вас есть простой MCP-сервер. Конечно, он пока ничего не делает, но это основа, на которой мы будем строить наши примеры.
Теперь давайте рассмотрим конкретные примеры MCP-серверов — от самых простых до сложных и специализированных.
Самый простой MCP-сервер, который просто возвращает приветствие.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
class HelloWorldServer {
constructor() {
this.server = new Server(
{
name: 'hello-world-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'say_hello',
description: 'Returns a friendly greeting',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the person to greet',
}
},
required: ['name'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'say_hello') {
const name = request.params.arguments.name;
return {
content: [
{
type: 'text',
text: `Привет, ${name}! Как дела?`,
},
],
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Hello World MCP server running on stdio');
}
}
const server = new HelloWorldServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Поздоровайся со мной"
ИИ: [использует MCP-сервер] "Привет, [имя]! Как дела?"
MCP-сервер, который предоставляет информацию о погоде.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListResourcesRequestSchema,
ListResourceTemplatesRequestSchema,
ListToolsRequestSchema,
McpError,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
const API_KEY = process.env.OPENWEATHER_API_KEY;
if (!API_KEY) {
throw new Error('OPENWEATHER_API_KEY environment variable is required');
}
class WeatherServer {
constructor() {
this.server = new Server(
{
name: 'weather-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.axiosInstance = axios.create({
baseURL: 'http://api.openweathermap.org/data/2.5',
params: {
appid: API_KEY,
units: 'metric',
},
});
this.setupResourceHandlers();
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupResourceHandlers() {
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
resourceTemplates: [
{
uriTemplate: 'weather://{city}/current',
name: 'Current weather for a given city',
mimeType: 'application/json',
description: 'Real-time weather data for a specified city',
},
],
}));
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const match = request.params.uri.match(/^weather:\/\/([^/]+)\/current$/);
if (!match) {
throw new McpError(
ErrorCode.InvalidRequest,
`Invalid URI format: ${request.params.uri}`
);
}
const city = decodeURIComponent(match[1]);
try {
const response = await this.axiosInstance.get('weather', {
params: { q: city },
});
return {
contents: [
{
uri: request.params.uri,
mimeType: 'application/json',
text: JSON.stringify(
{
temperature: response.data.main.temp,
conditions: response.data.weather[0].description,
humidity: response.data.main.humidity,
wind_speed: response.data.wind.speed,
timestamp: new Date().toISOString(),
},
null,
2
),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
throw new McpError(
ErrorCode.InternalError,
`Weather API error: ${error.response?.data.message ?? error.message}`
);
}
throw error;
}
});
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'get_forecast',
description: 'Get weather forecast for a city',
inputSchema: {
type: 'object',
properties: {
city: {
type: 'string',
description: 'City name',
},
days: {
type: 'number',
description: 'Number of days (1-5)',
minimum: 1,
maximum: 5,
},
},
required: ['city'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'get_forecast') {
const city = request.params.arguments.city;
const days = Math.min(request.params.arguments.days || 3, 5);
try {
const response = await this.axiosInstance.get('forecast', {
params: {
q: city,
cnt: days * 8,
},
});
return {
content: [
{
type: 'text',
text: JSON.stringify(
response.data.list.map(item => ({
date: item.dt_txt,
temperature: item.main.temp,
conditions: item.weather[0].description,
humidity: item.main.humidity,
wind_speed: item.wind.speed,
})),
null,
2
),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: 'text',
text: `Weather API error: ${error.response?.data.message ?? error.message}`,
},
],
isError: true,
};
}
throw error;
}
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Weather MCP server running on stdio');
}
}
const server = new WeatherServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Какая погода в Москве?"
ИИ: [использует MCP-сервер] "В Москве сейчас 22°C, солнечно, влажность 45%, ветер 3 м/с"
MCP-сервер для создания, чтения и управления заметками.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
import path from 'path';
const NOTES_DIR = process.env.NOTES_DIR || './notes';
class NotesServer {
constructor() {
this.server = new Server(
{
name: 'notes-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureNotesDir() {
try {
await fs.mkdir(NOTES_DIR, { recursive: true });
} catch (error) {
console.error('Error creating notes directory:', error);
}
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'create_note',
description: 'Create a new note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note',
},
content: {
type: 'string',
description: 'Content of the note',
},
},
required: ['title', 'content'],
},
},
{
name: 'list_notes',
description: 'List all available notes',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'read_note',
description: 'Read a specific note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note to read',
},
},
required: ['title'],
},
},
{
name: 'delete_note',
description: 'Delete a specific note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note to delete',
},
},
required: ['title'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
await this.ensureNotesDir();
switch (request.params.name) {
case 'create_note': {
const { title, content } = request.params.arguments;
const filename = this.titleToFilename(title);
await fs.writeFile(path.join(NOTES_DIR, filename), content, 'utf8');
return {
content: [
{
type: 'text',
text: `Note "${title}" created successfully.`,
},
],
};
}
case 'list_notes': {
const files = await fs.readdir(NOTES_DIR);
const notes = files
.filter(file => file.endsWith('.txt'))
.map(file => this.filenameToTitle(file));
return {
content: [
{
type: 'text',
text: notes.length > 0
? `Available notes:\n${notes.map(note => `- ${note}`).join('\n')}`
: 'No notes found.',
},
],
};
}
case 'read_note': {
const { title } = request.params.arguments;
const filename = this.titleToFilename(title);
try {
const content = await fs.readFile(path.join(NOTES_DIR, filename), 'utf8');
return {
content: [
{
type: 'text',
text: `# ${title}\n\n${content}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Note "${title}" not found.`,
},
],
isError: true,
};
}
}
case 'delete_note': {
const { title } = request.params.arguments;
const filename = this.titleToFilename(title);
try {
await fs.unlink(path.join(NOTES_DIR, filename));
return {
content: [
{
type: 'text',
text: `Note "${title}" deleted successfully.`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error deleting note "${title}": ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
titleToFilename(title) {
return `${title.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}.txt`;
}
filenameToTitle(filename) {
return filename.replace(/\.txt$/, '').replace(/_/g, ' ');
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Notes MCP server running on stdio');
}
}
const server = new NotesServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Создай заметку с заголовком 'Покупки' и содержанием 'Молоко, хлеб, яйца'"
ИИ: [использует MCP-сервер] "Заметка 'Покупки' успешно создана."
Пользователь: "Покажи мне список всех заметок"
ИИ: [использует MCP-сервер] "Доступные заметки:
- Покупки"
Пользователь: "Прочитай заметку 'Покупки'"
ИИ: [использует MCP-сервер] "# Покупки
Молоко, хлеб, яйца"
MCP-сервер для поиска информации в интернете.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
import cheerio from 'cheerio';
class SearchServer {
constructor() {
this.server = new Server(
{
name: 'search-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'search_web',
description: 'Search the web for information',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query',
},
num_results: {
type: 'number',
description: 'Number of results to return',
minimum: 1,
maximum: 10,
},
},
required: ['query'],
},
},
{
name: 'fetch_url',
description: 'Fetch and summarize the content of a URL',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'URL to fetch',
},
},
required: ['url'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'search_web': {
const { query, num_results = 5 } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой поисковой API
const response = await axios.get('https://api.example.com/search', {
params: {
q: query,
limit: num_results,
},
headers: {
'Authorization': `Bearer ${process.env.SEARCH_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Search results for "${query}":\n\n` +
response.data.results.map((result, index) =>
`${index + 1}. ${result.title}\n ${result.url}\n ${result.snippet}\n`
).join('\n'),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error searching for "${query}": ${error.message}`,
},
],
isError: true,
};
}
}
case 'fetch_url': {
const { url } = request.params.arguments;
try {
const response = await axios.get(url);
const $ = cheerio.load(response.data);
// Базовое извлечение текста
const title = $('title').text().trim();
const metaDescription = $('meta[name="description"]').attr('content') || '';
// Извлечение основного содержимого (это базовая реализация)
let content = '';
$('p').each((i, elem) => {
content += $(elem).text().trim() + '\n\n';
});
return {
content: [
{
type: 'text',
text: `# ${title}\n\n${metaDescription}\n\n## Содержание страницы:\n\n${content}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error fetching URL "${url}": ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Search MCP server running on stdio');
}
}
const server = new SearchServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Найди информацию о солнечной системе"
ИИ: [использует MCP-сервер] "Результаты поиска для 'солнечная система':
1. Солнечная система - Википедия
https://ru.wikipedia.org/wiki/Солнечная_система
Солнечная система — планетная система, включающая центральную звезду Солнце и все естественные космические объекты...
2. ..."
Пользователь: "Покажи информацию с сайта о солнечной системе"
ИИ: [использует MCP-сервер fetch_url со ссылкой из результатов поиска]
MCP-сервер для перевода текста между разными языками.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
const TRANSLATION_API_KEY = process.env.TRANSLATION_API_KEY;
if (!TRANSLATION_API_KEY) {
throw new Error('TRANSLATION_API_KEY environment variable is required');
}
class TranslationServer {
constructor() {
this.server = new Server(
{
name: 'translation-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'translate_text',
description: 'Translate text between languages',
inputSchema: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'Text to translate',
},
source_language: {
type: 'string',
description: 'Source language code (e.g., "en", "ru", "fr")',
},
target_language: {
type: 'string',
description: 'Target language code (e.g., "en", "ru", "fr")',
},
},
required: ['text', 'target_language'],
},
},
{
name: 'detect_language',
description: 'Detect the language of a text',
inputSchema: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'Text to analyze',
},
},
required: ['text'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'translate_text': {
const { text, source_language, target_language } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой API перевода
const response = await axios.post('https://api.example.com/translate', {
text,
source: source_language || 'auto',
target: target_language,
}, {
headers: {
'Authorization': `Bearer ${TRANSLATION_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Перевод: ${response.data.translatedText}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error translating text: ${error.message}`,
},
],
isError: true,
};
}
}
case 'detect_language': {
const { text } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой API
const response = await axios.post('https://api.example.com/detect', {
text,
}, {
headers: {
'Authorization': `Bearer ${TRANSLATION_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Определенный язык: ${response.data.languageName} (${response.data.languageCode})\nУверенность: ${response.data.confidence * 100}%`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error detecting language: ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Translation MCP server running on stdio');
}
}
const server = new TranslationServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Переведи 'Здравствуйте, как дела?' на английский"
ИИ: [использует MCP-сервер] "Перевод: Hello, how are you?"
Пользователь: "Определи язык текста 'Bonjour, comment allez-vous?'"
ИИ: [использует MCP-сервер] "Определенный язык: Французский (fr)
Уверенность: 98%"
MCP-сервер для управления календарем и событиями.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
import path from 'path';
const CALENDAR_FILE = process.env.CALENDAR_FILE || './calendar.json';
class CalendarServer {
constructor() {
this.server = new Server(
{
name: 'calendar-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureCalendarFile() {
try {
await fs.access(CALENDAR_FILE);
} catch (error) {
// Файл не существует, создаем его с пустым календарем
await fs.writeFile(CALENDAR_FILE, JSON.stringify({ events: [] }, null, 2), 'utf8');
}
}
async getCalendar() {
await this.ensureCalendarFile();
const data = await fs.readFile(CALENDAR_FILE, 'utf8');
return JSON.parse(data);
}
async saveCalendar(calendar) {
await fs.writeFile(CALENDAR_FILE, JSON.stringify(calendar, null, 2), 'utf8');
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'add_event',
description: 'Add a new event to the calendar',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the event',
},
date: {
type: 'string',
description: 'Date of the event (YYYY-MM-DD)',
},
time: {
type: 'string',
description: 'Time of the event (HH:MM)',
},
description: {
type: 'string',
description: 'Description of the event',
},
},
required: ['title', 'date'],
},
},
{
name: 'list_events',
description: 'List events for a specific date or date range',
inputSchema: {
type: 'object',
properties: {
date: {
type: 'string',
description: 'Date to list events for (YYYY-MM-DD)',
},
start_date: {
type: 'string',
description: 'Start date for range (YYYY-MM-DD)',
},
end_date: {
type: 'string',
description: 'End date for range (YYYY-MM-DD)',
},
},
},
},
{
name: 'delete_event',
description: 'Delete an event from the calendar',
inputSchema: {
type: 'object',
properties: {
event_id: {
type: 'string',
description: 'ID of the event to delete',
},
},
required: ['event_id'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'add_event': {
const { title, date, time, description } = request.params.arguments;
const calendar = await this.getCalendar();
const newEvent = {
id: Date.now().toString(),
title,
date,
time: time || '',
description: description || '',
created_at: new Date().toISOString(),
};
calendar.events.push(newEvent);
await this.saveCalendar(calendar);
return {
content: [
{
type: 'text',
text: `Событие "${title}" добавлено в календарь на ${date}${time ? ' в ' + time : ''}.`,
},
],
};
}
case 'list_events': {
const { date, start_date, end_date } = request.params.arguments;
const calendar = await this.getCalendar();
let filteredEvents = [];
if (date) {
// Фильтрация по конкретной дате
filteredEvents = calendar.events.filter(event => event.date === date);
} else if (start_date && end_date) {
// Фильтрация по диапазону дат
filteredEvents = calendar.events.filter(event => {
return event.date >= start_date && event.date <= end_date;
});
} else {
// Возвращаем все события
filteredEvents = calendar.events;
}
// Сортировка по дате и времени
filteredEvents.sort((a, b) => {
if (a.date !== b.date) return a.date.localeCompare(b.date);
return a.time.localeCompare(b.time);
});
if (filteredEvents.length === 0) {
return {
content: [
{
type: 'text',
text: 'Нет событий для отображения.',
},
],
};
}
const eventsText = filteredEvents.map(event => {
return `ID: ${event.id}\nДата: ${event.date}${event.time ? ' ' + event.time : ''}\nНазвание: ${event.title}${event.description ? '\nОписание: ' + event.description : ''}`;
}).join('\n\n');
return {
content: [
{
type: 'text',
text: `События:\n\n${eventsText}`,
},
],
};
}
case 'delete_event': {
const { event_id } = request.params.arguments;
const calendar = await this.getCalendar();
const eventIndex = calendar.events.findIndex(event => event.id === event_id);
if (eventIndex === -1) {
return {
content: [
{
type: 'text',
text: `Событие с ID ${event_id} не найдено.`,
},
],
isError: true,
};
}
const deletedEvent = calendar.events[eventIndex];
calendar.events.splice(eventIndex, 1);
await this.saveCalendar(calendar);
return {
content: [
{
type: 'text',
text: `Событие "${deletedEvent.title}" удалено из календаря.`,
},
],
};
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Calendar MCP server running on stdio');
}
}
const server = new CalendarServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Добавь встречу с клиентом на 15 апреля в 14:00"
ИИ: [использует MCP-сервер] "Событие 'Встреча с клиентом' добавлено в календарь на 2025-04-15 в 14:00."
Пользователь: "Покажи мои события на апрель"
ИИ: [использует MCP-сервер] "События:
ID: 1710561723456
Дата: 2025-04-15 14:00
Название: Встреча с клиентом"
MCP-сервер для управления списком задач.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
const TASKS_FILE = process.env.TASKS_FILE || './tasks.json';
class TasksServer {
constructor() {
this.server = new Server(
{
name: 'tasks-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureTasksFile() {
try {
await fs.access(TASKS_FILE);
} catch (error) {
// Файл не существует, создаем его с пустым списком задач
await fs.writeFile(TASKS_FILE, JSON.stringify({ tasks: [] }, null, 2), 'utf8');
}
}
async getTasks() {
await this.ensureTasksFile();
const data = await fs.readFile(TASKS_FILE, 'utf8');
return JSON.parse(data);
}
async saveTasks(tasksData) {
await fs.writeFile(TASKS_FILE, JSON.stringify(tasksData, null, 2), 'utf8');
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'add_task',
description: 'Add a new task to the todo list',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the task',
},
priority: {
type: 'string',
description: 'Priority of the task (low, medium, high)',
enum: ['low', 'medium', 'high'],
},
due_date: {
type: 'string',
description: 'Due date for the task (YYYY-MM-DD)',
},
},
required: ['title'],
},
},
{
name: 'list_tasks',
description: 'List all tasks or filter by status',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
description: 'Filter tasks by status (pending, completed)',
enum: ['pending', 'completed'],
},
priority: {
type: 'string',
description: 'Filter tasks by priority (low, medium, high)',
enum: ['low', 'medium', 'high'],
},
},
},
},
{
name: 'complete_task',
description: 'Mark a task as completed',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'ID of the task to mark as completed',
},
},
required: ['task_id'],
},
},
{
name: 'delete_task',
description: 'Delete a task from the todo list',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'ID of the task to delete',
},
},
required: ['task_id'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'add_task': {
const { title, priority = 'medium', due_date } = request.params.arguments;
const tasksData = await this.getTasks();
const newTask = {
id: Date.now().toString(),
title,
priority,
due_date: due_date || null,
status: 'pending',
created_at: new Date().toISOString(),
};
tasksData.tasks.push(newTask);
await this.saveTasks(tasksData);
return {
content: [
{
type: 'text',
text: `Задача "${title}" добавлена в список.`,
},
],
};
}
case 'list_tasks': {
const { status, priority } = request.params.arguments;
const tasksData = await this.getTasks();
let filteredTasks = tasksData.tasks;
if (status) {
filteredTasks = filteredTasks.filter(task => task.status === status);
}
if (priority) {
filteredTasks = filteredTasks.filter(task => task.priority === priority);
}
// Сортировка по приоритету и дате создания
const priorityOrder = { high: 0, medium: 1, low: 2 };
filteredTasks.sort((a, b) => {
if (priorityOrder[a.priority] !== priorityOrder[b.priority]) {
return priorityOrder[a.priority] - priorityOrder[b.priority];
}
return new Date(a.created_at) - new Date(b.created_at);
});
if (filteredTasks.length === 0) {
return {
content: [
{
type: 'text',
text: 'Нет задач для отображения.',
},
],
};
}
const tasksText = filteredTasks.map(task => {
const statusEmoji = task.status === 'completed' ? '✅' : '⏳';
const priorityEmoji = {
low: '🟢',
medium: '🟡',
high: '🔴',
}[task.priority];
return `${statusEmoji} ${priorityEmoji} [${task.id}] ${task.title}${task.due_date ? ' (до ' + task.due_date + ')' : ''}`;
}).join('\n');
return {
content: [
{
type: 'text',
text: `Задачи:\n\n${tas
# Всеобъемлющее руководство по MCP-серверам
## Содержание
1. [Введение в MCP](#введение-в-mcp)
2. [Что такое MCP-серверы?](#что-такое-mcp-серверы)
3. [Основные концепции](#основные-концепции)
4. [Создание вашего первого MCP-сервера](#создание-вашего-первого-mcp-сервера)
5. [Примеры использования MCP-серверов](#примеры-использования-mcp-серверов)
6. [Продвинутые возможности](#продвинутые-возможности)
7. [Лучшие практики](#лучшие-практики)
8. [Устранение неполадок](#устранение-неполадок)
9. [Заключение](#заключение)
---
## Введение в MCP
MCP (Model Context Protocol) — это протокол, который позволяет крупным языковым моделям (LLM) и другим интеллектуальным системам взаимодействовать с внешними сервисами и ресурсами. Представьте это как мост, который соединяет мощные ИИ-системы (такие как Claude) с внешним миром — интернетом, базами данных, API и другими источниками информации.
### Почему это важно?
Представьте, что вы разговариваете с умным помощником, но он не может:
- Проверить актуальную погоду
- Найти что-то в интернете
- Взаимодействовать с вашими файлами
- Использовать калькулятор
- Отправить email
Это существенно ограничивает его полезность. MCP решает эту проблему, позволяя ИИ-системам обращаться к внешним сервисам и расширять свои возможности.
### Простая аналогия
Представьте MCP как "USB-порт для ИИ". Так же как USB-порт позволяет подключать к компьютеру разные устройства (камеры, мышки, принтеры), MCP позволяет подключать к ИИ разные инструменты и сервисы.
---
## Что такое MCP-серверы?
MCP-сервер — это программа, которая:
1. **Предоставляет определенную функциональность** (например, доступ к погоде, работу с файлами и т.д.)
2. **Говорит на языке протокола MCP**, чтобы ИИ-системы могли ее использовать
3. **Работает как "мост"** между ИИ-системой и внешними сервисами/API
### Простая аналогия
Если представить, что ИИ — это клиент ресторана, то MCP-серверы — это официанты, которые приносят блюда (информацию/сервисы) с кухни (интернета/внешних API).
### Что могут делать MCP-серверы?
MCP-серверы могут предоставлять ИИ доступ к:
- Поиску в интернете
- Прогнозу погоды
- Новостям
- Базам данных
- Калькуляторам
- Системным операциям
- API сторонних сервисов (Twitter, Spotify, YouTube и т.д.)
- И многому другому!
---
## Основные концепции
Перед тем как мы перейдем к примерам, давайте разберемся с основными концепциями, которые нужно понимать:
### 1. Инструменты (Tools)
**Инструменты** — это функции или операции, которые MCP-сервер может выполнять. Например, "получить прогноз погоды", "найти информацию в Google", "отправить email".Аналогия: Инструменты — это кнопки на пульте телевизора. Каждая кнопка выполняет определенное действие.
### 2. Ресурсы (Resources)
**Ресурсы** — это данные или информация, к которым MCP-сервер обеспечивает доступ. Например, текущая погода, информация о фильме, данные из базы.
Аналогия: Ресурсы — это книги в библиотеке. ИИ может "взять книгу" и прочитать информацию из нее.
### 3. Шаблоны ресурсов (Resource Templates)
**Шаблоны ресурсов** определяют, как можно обращаться к динамическим ресурсам. Например, шаблон `weather://{город}/current` позволяет запрашивать погоду для любого города.
Аналогия: Шаблон ресурса — это как карточный каталог в библиотеке, который помогает найти нужную книгу по описанию.
### 4. URI (Uniform Resource Identifier)
**URI** — это уникальный идентификатор ресурса, который используется для обращения к конкретному ресурсу. Например, `weather://Moscow/current`.
Аналогия: URI — это как адрес в городе. Он позволяет точно найти нужный "дом" (ресурс).
### 5. Запросы и ответы (Requests & Responses)
MCP работает по модели запрос-ответ:
- ИИ отправляет **запрос** на MCP-сервер (например, "какая погода в Москве?")
- MCP-сервер обрабатывает запрос и отправляет **ответ** (например, "в Москве 22°C, солнечно")
Аналогия: Это как разговор по телефону — вы задаете вопрос, собеседник отвечает.
---
## Создание вашего первого MCP-сервера
Давайте рассмотрим процесс создания простого MCP-сервера шаг за шагом. Не беспокойтесь, если вы не знаете, как программировать — мы рассмотрим общую концепцию, а потом перейдем к готовым примерам.
### Шаг 1: Установка необходимых инструментов
Для создания MCP-сервера вам понадобятся:
- Node.js (среда выполнения JavaScript)
- npm (менеджер пакетов)
- MCP SDK (набор инструментов для работы с MCP)
### Шаг 2: Инициализация проекта
```bash
# Создаем новую директорию
mkdir my-weather-server
cd my-weather-server
# Инициализируем проект
npm init -y
# Устанавливаем MCP SDK и другие необходимые пакеты
npm install @modelcontextprotocol/sdk axios
Создадим файл index.js с базовым кодом MCP-сервера:
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
// Создаем экземпляр сервера
const server = new Server(
{
name: 'hello-world-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
// Добавляем обработчик ошибок
server.onerror = (error) => console.error('[MCP Error]', error);
// Запускаем сервер
const transport = new StdioServerTransport();
server.connect(transport).catch(console.error);
console.error('Hello World MCP server running on stdio');# Компилируем код (если необходимо)
npm run build
# Запускаем сервер
node index.jsЧтобы ИИ-система могла использовать ваш MCP-сервер, необходимо добавить его в конфигурационный файл MCP:
{
"mcpServers": {
"hello-world": {
"command": "node",
"args": ["/путь/к/вашему/серверу/index.js"],
"env": {}
}
}
}Вот и всё! Теперь у вас есть простой MCP-сервер. Конечно, он пока ничего не делает, но это основа, на которой мы будем строить наши примеры.
Теперь давайте рассмотрим конкретные примеры MCP-серверов — от самых простых до сложных и специализированных.
Самый простой MCP-сервер, который просто возвращает приветствие.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
class HelloWorldServer {
constructor() {
this.server = new Server(
{
name: 'hello-world-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'say_hello',
description: 'Returns a friendly greeting',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Name of the person to greet',
}
},
required: ['name'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'say_hello') {
const name = request.params.arguments.name;
return {
content: [
{
type: 'text',
text: `Привет, ${name}! Как дела?`,
},
],
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Hello World MCP server running on stdio');
}
}
const server = new HelloWorldServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Поздоровайся со мной"
ИИ: [использует MCP-сервер] "Привет, [имя]! Как дела?"
MCP-сервер, который предоставляет информацию о погоде.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListResourcesRequestSchema,
ListResourceTemplatesRequestSchema,
ListToolsRequestSchema,
McpError,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
const API_KEY = process.env.OPENWEATHER_API_KEY;
if (!API_KEY) {
throw new Error('OPENWEATHER_API_KEY environment variable is required');
}
class WeatherServer {
constructor() {
this.server = new Server(
{
name: 'weather-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.axiosInstance = axios.create({
baseURL: 'http://api.openweathermap.org/data/2.5',
params: {
appid: API_KEY,
units: 'metric',
},
});
this.setupResourceHandlers();
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupResourceHandlers() {
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
resourceTemplates: [
{
uriTemplate: 'weather://{city}/current',
name: 'Current weather for a given city',
mimeType: 'application/json',
description: 'Real-time weather data for a specified city',
},
],
}));
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const match = request.params.uri.match(/^weather:\/\/([^/]+)\/current$/);
if (!match) {
throw new McpError(
ErrorCode.InvalidRequest,
`Invalid URI format: ${request.params.uri}`
);
}
const city = decodeURIComponent(match[1]);
try {
const response = await this.axiosInstance.get('weather', {
params: { q: city },
});
return {
contents: [
{
uri: request.params.uri,
mimeType: 'application/json',
text: JSON.stringify(
{
temperature: response.data.main.temp,
conditions: response.data.weather[0].description,
humidity: response.data.main.humidity,
wind_speed: response.data.wind.speed,
timestamp: new Date().toISOString(),
},
null,
2
),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
throw new McpError(
ErrorCode.InternalError,
`Weather API error: ${error.response?.data.message ?? error.message}`
);
}
throw error;
}
});
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'get_forecast',
description: 'Get weather forecast for a city',
inputSchema: {
type: 'object',
properties: {
city: {
type: 'string',
description: 'City name',
},
days: {
type: 'number',
description: 'Number of days (1-5)',
minimum: 1,
maximum: 5,
},
},
required: ['city'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'get_forecast') {
const city = request.params.arguments.city;
const days = Math.min(request.params.arguments.days || 3, 5);
try {
const response = await this.axiosInstance.get('forecast', {
params: {
q: city,
cnt: days * 8,
},
});
return {
content: [
{
type: 'text',
text: JSON.stringify(
response.data.list.map(item => ({
date: item.dt_txt,
temperature: item.main.temp,
conditions: item.weather[0].description,
humidity: item.main.humidity,
wind_speed: item.wind.speed,
})),
null,
2
),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: 'text',
text: `Weather API error: ${error.response?.data.message ?? error.message}`,
},
],
isError: true,
};
}
throw error;
}
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Weather MCP server running on stdio');
}
}
const server = new WeatherServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Какая погода в Москве?"
ИИ: [использует MCP-сервер] "В Москве сейчас 22°C, солнечно, влажность 45%, ветер 3 м/с"
MCP-сервер для создания, чтения и управления заметками.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
import path from 'path';
const NOTES_DIR = process.env.NOTES_DIR || './notes';
class NotesServer {
constructor() {
this.server = new Server(
{
name: 'notes-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureNotesDir() {
try {
await fs.mkdir(NOTES_DIR, { recursive: true });
} catch (error) {
console.error('Error creating notes directory:', error);
}
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'create_note',
description: 'Create a new note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note',
},
content: {
type: 'string',
description: 'Content of the note',
},
},
required: ['title', 'content'],
},
},
{
name: 'list_notes',
description: 'List all available notes',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'read_note',
description: 'Read a specific note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note to read',
},
},
required: ['title'],
},
},
{
name: 'delete_note',
description: 'Delete a specific note',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the note to delete',
},
},
required: ['title'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
await this.ensureNotesDir();
switch (request.params.name) {
case 'create_note': {
const { title, content } = request.params.arguments;
const filename = this.titleToFilename(title);
await fs.writeFile(path.join(NOTES_DIR, filename), content, 'utf8');
return {
content: [
{
type: 'text',
text: `Note "${title}" created successfully.`,
},
],
};
}
case 'list_notes': {
const files = await fs.readdir(NOTES_DIR);
const notes = files
.filter(file => file.endsWith('.txt'))
.map(file => this.filenameToTitle(file));
return {
content: [
{
type: 'text',
text: notes.length > 0
? `Available notes:\n${notes.map(note => `- ${note}`).join('\n')}`
: 'No notes found.',
},
],
};
}
case 'read_note': {
const { title } = request.params.arguments;
const filename = this.titleToFilename(title);
try {
const content = await fs.readFile(path.join(NOTES_DIR, filename), 'utf8');
return {
content: [
{
type: 'text',
text: `# ${title}\n\n${content}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Note "${title}" not found.`,
},
],
isError: true,
};
}
}
case 'delete_note': {
const { title } = request.params.arguments;
const filename = this.titleToFilename(title);
try {
await fs.unlink(path.join(NOTES_DIR, filename));
return {
content: [
{
type: 'text',
text: `Note "${title}" deleted successfully.`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error deleting note "${title}": ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
titleToFilename(title) {
return `${title.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}.txt`;
}
filenameToTitle(filename) {
return filename.replace(/\.txt$/, '').replace(/_/g, ' ');
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Notes MCP server running on stdio');
}
}
const server = new NotesServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Создай заметку с заголовком 'Покупки' и содержанием 'Молоко, хлеб, яйца'"
ИИ: [использует MCP-сервер] "Заметка 'Покупки' успешно создана."
Пользователь: "Покажи мне список всех заметок"
ИИ: [использует MCP-сервер] "Доступные заметки:
- Покупки"
Пользователь: "Прочитай заметку 'Покупки'"
ИИ: [использует MCP-сервер] "# Покупки
Молоко, хлеб, яйца"
MCP-сервер для поиска информации в интернете.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
import cheerio from 'cheerio';
class SearchServer {
constructor() {
this.server = new Server(
{
name: 'search-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'search_web',
description: 'Search the web for information',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query',
},
num_results: {
type: 'number',
description: 'Number of results to return',
minimum: 1,
maximum: 10,
},
},
required: ['query'],
},
},
{
name: 'fetch_url',
description: 'Fetch and summarize the content of a URL',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'URL to fetch',
},
},
required: ['url'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'search_web': {
const { query, num_results = 5 } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой поисковой API
const response = await axios.get('https://api.example.com/search', {
params: {
q: query,
limit: num_results,
},
headers: {
'Authorization': `Bearer ${process.env.SEARCH_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Search results for "${query}":\n\n` +
response.data.results.map((result, index) =>
`${index + 1}. ${result.title}\n ${result.url}\n ${result.snippet}\n`
).join('\n'),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error searching for "${query}": ${error.message}`,
},
],
isError: true,
};
}
}
case 'fetch_url': {
const { url } = request.params.arguments;
try {
const response = await axios.get(url);
const $ = cheerio.load(response.data);
// Базовое извлечение текста
const title = $('title').text().trim();
const metaDescription = $('meta[name="description"]').attr('content') || '';
// Извлечение основного содержимого (это базовая реализация)
let content = '';
$('p').each((i, elem) => {
content += $(elem).text().trim() + '\n\n';
});
return {
content: [
{
type: 'text',
text: `# ${title}\n\n${metaDescription}\n\n## Содержание страницы:\n\n${content}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error fetching URL "${url}": ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Search MCP server running on stdio');
}
}
const server = new SearchServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Найди информацию о солнечной системе"
ИИ: [использует MCP-сервер] "Результаты поиска для 'солнечная система':
1. Солнечная система - Википедия
https://ru.wikipedia.org/wiki/Солнечная_система
Солнечная система — планетная система, включающая центральную звезду Солнце и все естественные космические объекты...
2. ..."
Пользователь: "Покажи информацию с сайта о солнечной системе"
ИИ: [использует MCP-сервер fetch_url со ссылкой из результатов поиска]
MCP-сервер для перевода текста между разными языками.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
const TRANSLATION_API_KEY = process.env.TRANSLATION_API_KEY;
if (!TRANSLATION_API_KEY) {
throw new Error('TRANSLATION_API_KEY environment variable is required');
}
class TranslationServer {
constructor() {
this.server = new Server(
{
name: 'translation-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'translate_text',
description: 'Translate text between languages',
inputSchema: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'Text to translate',
},
source_language: {
type: 'string',
description: 'Source language code (e.g., "en", "ru", "fr")',
},
target_language: {
type: 'string',
description: 'Target language code (e.g., "en", "ru", "fr")',
},
},
required: ['text', 'target_language'],
},
},
{
name: 'detect_language',
description: 'Detect the language of a text',
inputSchema: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'Text to analyze',
},
},
required: ['text'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'translate_text': {
const { text, source_language, target_language } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой API перевода
const response = await axios.post('https://api.example.com/translate', {
text,
source: source_language || 'auto',
target: target_language,
}, {
headers: {
'Authorization': `Bearer ${TRANSLATION_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Перевод: ${response.data.translatedText}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error translating text: ${error.message}`,
},
],
isError: true,
};
}
}
case 'detect_language': {
const { text } = request.params.arguments;
try {
// Эта часть условна и зависит от используемой API
const response = await axios.post('https://api.example.com/detect', {
text,
}, {
headers: {
'Authorization': `Bearer ${TRANSLATION_API_KEY}`,
},
});
return {
content: [
{
type: 'text',
text: `Определенный язык: ${response.data.languageName} (${response.data.languageCode})\nУверенность: ${response.data.confidence * 100}%`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error detecting language: ${error.message}`,
},
],
isError: true,
};
}
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Translation MCP server running on stdio');
}
}
const server = new TranslationServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Переведи 'Здравствуйте, как дела?' на английский"
ИИ: [использует MCP-сервер] "Перевод: Hello, how are you?"
Пользователь: "Определи язык текста 'Bonjour, comment allez-vous?'"
ИИ: [использует MCP-сервер] "Определенный язык: Французский (fr)
Уверенность: 98%"
MCP-сервер для управления календарем и событиями.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
import path from 'path';
const CALENDAR_FILE = process.env.CALENDAR_FILE || './calendar.json';
class CalendarServer {
constructor() {
this.server = new Server(
{
name: 'calendar-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureCalendarFile() {
try {
await fs.access(CALENDAR_FILE);
} catch (error) {
// Файл не существует, создаем его с пустым календарем
await fs.writeFile(CALENDAR_FILE, JSON.stringify({ events: [] }, null, 2), 'utf8');
}
}
async getCalendar() {
await this.ensureCalendarFile();
const data = await fs.readFile(CALENDAR_FILE, 'utf8');
return JSON.parse(data);
}
async saveCalendar(calendar) {
await fs.writeFile(CALENDAR_FILE, JSON.stringify(calendar, null, 2), 'utf8');
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'add_event',
description: 'Add a new event to the calendar',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the event',
},
date: {
type: 'string',
description: 'Date of the event (YYYY-MM-DD)',
},
time: {
type: 'string',
description: 'Time of the event (HH:MM)',
},
description: {
type: 'string',
description: 'Description of the event',
},
},
required: ['title', 'date'],
},
},
{
name: 'list_events',
description: 'List events for a specific date or date range',
inputSchema: {
type: 'object',
properties: {
date: {
type: 'string',
description: 'Date to list events for (YYYY-MM-DD)',
},
start_date: {
type: 'string',
description: 'Start date for range (YYYY-MM-DD)',
},
end_date: {
type: 'string',
description: 'End date for range (YYYY-MM-DD)',
},
},
},
},
{
name: 'delete_event',
description: 'Delete an event from the calendar',
inputSchema: {
type: 'object',
properties: {
event_id: {
type: 'string',
description: 'ID of the event to delete',
},
},
required: ['event_id'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'add_event': {
const { title, date, time, description } = request.params.arguments;
const calendar = await this.getCalendar();
const newEvent = {
id: Date.now().toString(),
title,
date,
time: time || '',
description: description || '',
created_at: new Date().toISOString(),
};
calendar.events.push(newEvent);
await this.saveCalendar(calendar);
return {
content: [
{
type: 'text',
text: `Событие "${title}" добавлено в календарь на ${date}${time ? ' в ' + time : ''}.`,
},
],
};
}
case 'list_events': {
const { date, start_date, end_date } = request.params.arguments;
const calendar = await this.getCalendar();
let filteredEvents = [];
if (date) {
// Фильтрация по конкретной дате
filteredEvents = calendar.events.filter(event => event.date === date);
} else if (start_date && end_date) {
// Фильтрация по диапазону дат
filteredEvents = calendar.events.filter(event => {
return event.date >= start_date && event.date <= end_date;
});
} else {
// Возвращаем все события
filteredEvents = calendar.events;
}
// Сортировка по дате и времени
filteredEvents.sort((a, b) => {
if (a.date !== b.date) return a.date.localeCompare(b.date);
return a.time.localeCompare(b.time);
});
if (filteredEvents.length === 0) {
return {
content: [
{
type: 'text',
text: 'Нет событий для отображения.',
},
],
};
}
const eventsText = filteredEvents.map(event => {
return `ID: ${event.id}\nДата: ${event.date}${event.time ? ' ' + event.time : ''}\nНазвание: ${event.title}${event.description ? '\nОписание: ' + event.description : ''}`;
}).join('\n\n');
return {
content: [
{
type: 'text',
text: `События:\n\n${eventsText}`,
},
],
};
}
case 'delete_event': {
const { event_id } = request.params.arguments;
const calendar = await this.getCalendar();
const eventIndex = calendar.events.findIndex(event => event.id === event_id);
if (eventIndex === -1) {
return {
content: [
{
type: 'text',
text: `Событие с ID ${event_id} не найдено.`,
},
],
isError: true,
};
}
const deletedEvent = calendar.events[eventIndex];
calendar.events.splice(eventIndex, 1);
await this.saveCalendar(calendar);
return {
content: [
{
type: 'text',
text: `Событие "${deletedEvent.title}" удалено из календаря.`,
},
],
};
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Calendar MCP server running on stdio');
}
}
const server = new CalendarServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Добавь встречу с клиентом на 15 апреля в 14:00"
ИИ: [использует MCP-сервер] "Событие 'Встреча с клиентом' добавлено в календарь на 2025-04-15 в 14:00."
Пользователь: "Покажи мои события на апрель"
ИИ: [использует MCP-сервер] "События:
ID: 1710561723456
Дата: 2025-04-15 14:00
Название: Встреча с клиентом"
MCP-сервер для управления списком задач.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
const TASKS_FILE = process.env.TASKS_FILE || './tasks.json';
class TasksServer {
constructor() {
this.server = new Server(
{
name: 'tasks-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async ensureTasksFile() {
try {
await fs.access(TASKS_FILE);
} catch (error) {
// Файл не существует, создаем его с пустым списком задач
await fs.writeFile(TASKS_FILE, JSON.stringify({ tasks: [] }, null, 2), 'utf8');
}
}
async getTasks() {
await this.ensureTasksFile();
const data = await fs.readFile(TASKS_FILE, 'utf8');
return JSON.parse(data);
}
async saveTasks(tasksData) {
await fs.writeFile(TASKS_FILE, JSON.stringify(tasksData, null, 2), 'utf8');
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'add_task',
description: 'Add a new task to the todo list',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Title of the task',
},
priority: {
type: 'string',
description: 'Priority of the task (low, medium, high)',
enum: ['low', 'medium', 'high'],
},
due_date: {
type: 'string',
description: 'Due date for the task (YYYY-MM-DD)',
},
},
required: ['title'],
},
},
{
name: 'list_tasks',
description: 'List all tasks or filter by status',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
description: 'Filter tasks by status (pending, completed)',
enum: ['pending', 'completed'],
},
priority: {
type: 'string',
description: 'Filter tasks by priority (low, medium, high)',
enum: ['low', 'medium', 'high'],
},
},
},
},
{
name: 'complete_task',
description: 'Mark a task as completed',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'ID of the task to mark as completed',
},
},
required: ['task_id'],
},
},
{
name: 'delete_task',
description: 'Delete a task from the todo list',
inputSchema: {
type: 'object',
properties: {
task_id: {
type: 'string',
description: 'ID of the task to delete',
},
},
required: ['task_id'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'add_task': {
const { title, priority = 'medium', due_date } = request.params.arguments;
const tasksData = await this.getTasks();
const newTask = {
id: Date.now().toString(),
title,
priority,
due_date: due_date || null,
status: 'pending',
created_at: new Date().toISOString(),
};
tasksData.tasks.push(newTask);
await this.saveTasks(tasksData);
return {
content: [
{
type: 'text',
text: `Задача "${title}" добавлена в список.`,
},
],
};
}
case 'list_tasks': {
const { status, priority } = request.params.arguments;
const tasksData = await this.getTasks();
let filteredTasks = tasksData.tasks;
if (status) {
filteredTasks = filteredTasks.filter(task => task.status === status);
}
if (priority) {
filteredTasks = filteredTasks.filter(task => task.priority === priority);
}
// Сортировка по приоритету и дате создания
const priorityOrder = { high: 0, medium: 1, low: 2 };
filteredTasks.sort((a, b) => {
if (priorityOrder[a.priority] !== priorityOrder[b.priority]) {
return priorityOrder[a.priority] - priorityOrder[b.priority];
}
return new Date(a.created_at) - new Date(b.created_at);
});
if (filteredTasks.length === 0) {
return {
content: [
{
type: 'text',
text: 'Нет задач для отображения.',
},
],
};
}
const tasksText = filteredTasks.map(task => {
const statusEmoji = task.status === 'completed' ? '✅' : '⏳';
const priorityEmoji = {
low: '🟢',
medium: '🟡',
high: '🔴',
}[task.priority];
return `${statusEmoji} ${priorityEmoji} [${task.id}] ${task.title}${task.due_date ? ' (до ' + task.due_date + ')' : ''}`;
}).join('\n');
return {
content: [
{
type: 'text',
text: `Задачи:\n\n${tasksText}`,
},
],
};
}
case 'complete_task': {
const { task_id } = request.params.arguments;
const tasksData = await this.getTasks();
const taskIndex = tasksData.tasks.findIndex(task => task.id === task_id);
if (taskIndex === -1) {
return {
content: [
{
type: 'text',
text: `Задача с ID ${task_id} не найдена.`,
},
],
isError: true,
};
}
tasksData.tasks[taskIndex].status = 'completed';
await this.saveTasks(tasksData);
return {
content: [
{
type: 'text',
text: `Задача "${tasksData.tasks[taskIndex].title}" отмечена как выполненная.`,
},
],
};
}
case 'delete_task': {
const { task_id } = request.params.arguments;
const tasksData = await this.getTasks();
const taskIndex = tasksData.tasks.findIndex(task => task.id === task_id);
if (taskIndex === -1) {
return {
content: [
{
type: 'text',
text: `Задача с ID ${task_id} не найдена.`,
},
],
isError: true,
};
}
const deletedTask = tasksData.tasks[taskIndex];
tasksData.tasks.splice(taskIndex, 1);
await this.saveTasks(tasksData);
return {
content: [
{
type: 'text',
text: `Задача "${deletedTask.title}" удалена из списка.`,
},
],
};
}
default:
return {
content: [
{
type: 'text',
text: `Unknown tool: ${request.params.name}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Tasks MCP server running on stdio');
}
}
const server = new TasksServer();
server.run().catch(console.error);Как использовать:
Пользователь: "Добавь задачу 'Купить продукты' с высоким приоритетом"
ИИ: [использует MCP-сервер] "Задача 'Купить продукты' добавлена в список."
Пользователь: "Покажи мои задачи"
ИИ: [использует MCP-сервер] "Задачи:
🔴 ⏳ [1710561823456] Купить продукты"
Пользователь: "Отметь задачу с ID 1710561823456 как выполненную"
ИИ: [использует MCP-сервер] "Задача 'Купить продукты' отмечена как выполненная."
MCP-сервер для выполнения запросов к базе данных.
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
const DB_PATH = process.env.DB_PATH || './database.sqlite';
class DatabaseServer {
constructor() {
this.server = new Server(
{
name: 'database-server',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
}
async getDbConnection() {
if (!this.db) {
this.db = await open({
filename: DB_PATH,
driver: sqlite3.Database,
});
}
return this.db;
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'execute_query',
description: 'Execute an SQL query on the database',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'SQL query to execute',
},
params: {
type: 'array',
description: 'Parameters for the query',
items: {
type: ['string', 'number', 'boolean', 'null'],
},
},
},
required: ['query'],
},
},
{
name: 'get_tables',
description: 'Get a list of tables in the database',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'get_table_schema',
description: 'Get the schema for a specific table',
inputSchema: {
type: 'object',
properties: {
table_name: {
type: 'string',
description: 'Name of the table',
},
},
required: ['table_name'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const db = await this.getDbConnection();
switch (request.params.name) {
case 'execute_query': {
const { query, params = [] } = request.params.arguments;
try {
// Проверка, является ли запрос SELECT или нет
const isSelect = query.trim().toUpperCase().startsWith('SELECT');
let result;
if (isSelect) {
result = await db.all(query, params);
} else {
result = await db.run(query, params);
}
return {
content: [
{
type: 'text',
text: isSelect
? `Результаты запроса:\n\n${JSON.stringify(result, null, 2)}`
: `Запрос выполнен успешно. Затронуто строк: ${result.changes || 0}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Ошибка выполнения запроса: ${error.message}`,
},
],
isError: true,
};
}
}
case 'get_tables': {
try {
const result = await db.all("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");
const tables = result.map(row => row.name);
return {
content: [
{
type: 'text',
text: tables.length > 0
? `Таблицы в базе данных:\n\n${tables.join('\n')}`
: 'В базе данных нет таблиц.',
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Ошибка получения списка таблиц: ${error.message}`,
},
],
isError: true,
};
}
}
case 'get_table_schema': {
const { table_name } = request.params.arguments;
try {
const result = await db.all(`PRAGMA table_info(${table_name})`);
if (result.length === 0) {
return {
content: [
{
type: 'text',
text: `Таблица '${table_name}' не найдена.`,
},
],
isError: true,
};
}
const columns = result.map(col => {
return `${col.name} (${col.type})${col.pk ? ' PRIMARY KEY' : ''}${col.notnull ? ' NOT NULL' : ''}`;
});
return {
content: [
{
type: 'text',