trainee-mcp-server
by whmi1
README.md
# Test Task: **Trainee Mcp Server**
Учебный MCP-сервер на TypeScript: даёт Claude два инструмента (`add`, `get_weather`) и один ресурс (`favorite-cities`). Работает локально, общается с хостом по транспорту **stdio**, подключается к **Claude Desktop**.
Источник данных о погоде — [Open-Meteo](https://open-meteo.com/). **Ключ API не нужен**, регистрация не требуется.
---
## **Что умеет**
| Тип | Имя | Что делает |
|---|---|---|
| **tool** | `add` | Складывает два числа и возвращает сумму |
| **tool** | `get_weather` | Текущая погода в городе: температура, влажность, скорость ветра |
| **resource** | `favorite-cities`<br>(`config://favorite-cities`) | JSON-список избранных городов; задаётся переменной окружения |
### **Схема инструмента** `get_weather`
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
| `city` | `string`, минимум 1 символ | **да** | Название города: `Vilnius`, `Москва`, `Berlin` |
| `units` | `"celsius"` \| `"fahrenheit"` | нет | Единицы измерения температуры. Если не указано — берётся значение `DEFAULT_UNITS` |
Схема описана через [Zod](https://zod.dev/); SDK превращает её в JSON Schema, которую видит модель. Значения вне перечисленных (например, `kelvin`) отсекаются до попадания в код инструмента.


---
## Требования
- **Node.js 18+** (рекомендуется актуальная LTS) — используются встроенные `fetch` и `AbortSignal.timeout`
- **Claude Desktop** или другой MCP-хост
- Доступ в интернет для запросов к Open-Meteo
## Установка и сборка
```bash
git clone <ссылка-на-репозиторий>
cd trainee-mcp-server
npm install
npm run build
```
После сборки появится `dist/index.js` — именно этот файл запускает хост.
Проверить, что сервер стартует:
```bash
npm start
```
Ожидаемое поведение: в консоль (stderr) выводится `MCP-сервер запущен на stdio`, после чего процесс **остаётся висеть** — он ждёт JSON-RPC-сообщения на stdin. Это нормально, выход по `Ctrl+C`.
---
## Подключение к Claude Desktop
**1.** Открой файл конфигурации:
| ОС | Путь |
|---|---|
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
Быстрый путь: меню **Claude → Settings → вкладка Developer → Edit Config**.
**2.** Добавь блок сервера. Путь до `dist/index.js` должен быть **абсолютным**:
```json
{
"mcpServers": {
"trainee-mcp-server": {
"command": "node",
"args": ["C:/Users/Имя/trainee-mcp-server/dist/index.js"],
"env": {
"FAVORITE_CITIES": "Minsk,Gomel,Moscow",
"DEFAULT_UNITS": "celsius",
"REQUEST_TIMEOUT_MS": "8000"
}
}
}
}
```
Блок `env` опционален — без него применяются значения по умолчанию (см. ниже).
> **Windows:** в JSON обратный слэш — управляющий символ, поэтому путь пишется либо через прямые слэши (`C:/Users/...`), либо через двойные обратные (`C:\\Users\\...`).
**3.** Полностью закрой и заново запусти Claude Desktop. Закрыть окно недостаточно — конфиг читается только при старте приложения.
**4.** Проверь подключение: **Settings → Developer** — сервер должен быть в списке. Список его инструментов виден в меню вложений рядом с полем ввода.

---
## Переменные окружения
Все переменные необязательны. Значения читаются при старте и валидируются через Zod: при некорректном значении сервер завершается с ненулевым кодом и пишет причину в stderr — вместо того чтобы молча работать с мусором.
| Переменная | Назначение | По умолчанию |
|---|---|---|
| `FAVORITE_CITIES` | Список избранных городов через запятую. Отдаётся ресурсом `favorite-cities` | `Minsk,Gomel,Moscow` |
| `DEFAULT_UNITS` | Единицы температуры, если инструмент вызван без `units`. Допустимо: `celsius`, `fahrenheit` | `celsius` |
| `REQUEST_TIMEOUT_MS` | Таймаут HTTP-запроса к Open-Meteo, мс | `8000` |
Значения в `claude_desktop_config.json` задаются **строками**, включая числовые (требование JSON) — схема приводит их к нужному типу самостоятельно.
> Сервер, запущенный из Claude Desktop, не наследует пользовательское окружение: переменные, выставленные в терминале, до него не дойдут. Задавать их нужно в блоке `env` конфига.
---
## Примеры запросов к Claude
| Что спросить | Что должно произойти |
|---|---|
| «Сколько будет 9176 плюс 912730?» | Вызов `add`, точная сумма из результата инструмента |
| «Какая сейчас погода в Вильнюсе?» | Вызов `get_weather`, температура в цельсиях |
| «Погода в Нью-Йорке в фаренгейтах» | Вызов `get_weather` с параметром `units: "fahrenheit"` |
| Приложить ресурс «Избранные города» и спросить: «Какие города в списке? Покажи погоду для первого» | Чтение ресурса, затем вызов `get_weather` для города из списка |


> Ресурс, в отличие от инструмента, модель не запрашивает сама: его прикладывает пользователь через меню вложений.
---
## Обработка ошибок
Все ошибки возвращаются как **результат вызова** с флагом `isError: true`, а не выбрасываются исключением. Разница существенная: при исключении модель получает протокольную ошибку без деталей, а так текст ошибки приходит ей как обычный ответ инструмента — и она может на него осмысленно отреагировать. Процесс сервера при этом не падает и продолжает обслуживать следующие вызовы.
| Сценарий | Что получает Claude | Как воспроизвести |
|---|---|---|
| Город не найден | Сообщение с предложением проверить написание | Спросить погоду в `asdasdasd` |
| Сервис вернул неполные данные | Сообщение о том, что город определён верно, но данных нет и повтор с другим написанием не поможет | Закомментировать установку параметра `current` в URL прогноза |
| Превышен таймаут | Сообщение с указанием лимита в секундах | Выставить `REQUEST_TIMEOUT_MS=1` |
Отдельно: `fetch` не выбрасывает исключение на статусах 4xx/5xx, поэтому `res.ok` проверяется явно. Таймаут реализован через `AbortSignal.timeout()` — без него зависший внешний сервис подвесил бы вызов инструмента на неопределённое время.



Контрольная проверка — обычный запрос после серии сбоев: сервер жив, инструмент отрабатывает штатно.

---
## Что такое MCP
**MCP** простыми словами - это "USB-порт" для подключения различных инструментов к модели. В силу того, что модель сама **не ходит в интернет**, а также писать кучу отдельных интеграций под каждый инструмент **нецелесообразно**, MCP выступает **удобным единым протоколом**.
Сам по себе **MCP-сервер** включает в себя 3 основных составляющие:
- **tools** — действия/функции, которые использует сама модель (два примера реализованы в данном тестовом задании);
- **resources** — данные для чтения, дополнительные справочники для модели (файлы, БД);
- **prompts** — уже готовые шаблоны для запросов.
---
## Структура проекта
```
trainee-mcp-server/
├── src/
│ └── index.ts # типы, конфигурация, инструменты, ресурс, запуск
├── screenshots/ # скриншоты вызовов из Claude Desktop
├── *dist/ # результат сборки, в репозиторий не коммитится
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json
```
Логи сервера пишутся только в **stderr**: stdout занят транспортом JSON-RPC, и любой вывод туда ломает обмен сообщениями с хостом.This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues