# Введение Source: https://picomcp.github.io/picomcp/ru/docs/ > Что такое picomcp, чем он не является, и десять строк, которые всё объясняют. picomcp — это TypeScript-фреймворк для создания серверов [Model Context Protocol](https://modelcontextprotocol.io). MCP-сервер даёт ИИ-приложению (Claude, Cursor, VS Code, вашему собственному агенту) **инструменты**, которые оно может вызывать, **ресурсы**, которые оно может читать, и **промпты**, которые оно может предложить пользователю. ```ts import { pico } from "picomcp"; import { z } from "zod"; pico({ name: "weather" }) .tool("forecast", { description: "Get the forecast for a city.", input: z.object({ city: z.string() }), run: ({ city }) => getForecast(city), }) .serve(); ``` Это полноценный сервер. Запустите его командой `node server.ts` и подключите к любому MCP-клиенту. ## Почему picomcp - **Маленький.** Около 4 кБ после gzip, ноль зависимостей. Всю библиотеку можно держать в голове и проверить за одно ревью. - **Типы из вашей схемы.** Передайте схему Zod, Valibot или ArkType. Обработчик получает типизированные аргументы, клиент получает JSON Schema, а некорректные вызовы получают понятную ошибку. - **Работает везде.** Node 22+, Bun, Deno, Cloudflare Workers и любая среда с `fetch`. В ядре нет встроенных модулей Node. - **Два транспорта, один сервер.** stdio для десктопных клиентов, Streamable HTTP для удалённых серверов. Переключение — одна опция. - **Тестируется без клиента.** `server.call("forecast", { city: "Lyon" })` вызывает инструмент прямо в процессе, с полной типизацией. - **Проверен официальным SDK.** Тесты управляют picomcp через `@modelcontextprotocol/sdk` на обоих транспортах. ## Чем picomcp не является picomcp реализует серверную часть MCP, и только её. В нём нет клиента, цикла агента или SDK для LLM. Он ориентирован на ту часть протокола, что работает без состояния, по схеме «запрос — ответ»: именно она нужна большинству серверов. Возможности, которые инициирует сервер, например sampling и elicitation, пока не поддерживаются. Точный список — в разделе [Покрытие протокола](https://picomcp.github.io/picomcp/ru/docs/protocol). ## Что дальше - [Быстрый старт](https://picomcp.github.io/picomcp/ru/docs/quickstart): создайте и подключите сервер за пять минут. - [Инструменты](https://picomcp.github.io/picomcp/ru/docs/tools): всё, что умеет инструмент. - [Для ИИ-агентов](https://picomcp.github.io/picomcp/ru/docs/agents): дайте документацию своему агенту одной ссылкой. --- # Быстрый старт Source: https://picomcp.github.io/picomcp/ru/docs/quickstart > Создайте, протестируйте и подключите свой первый MCP-сервер за пять минут. Вам понадобится Node 22.18 или новее (он запускает файлы TypeScript напрямую), Bun или Deno. ## Создание проекта ```sh npx picomcp init my-server cd my-server npm install ``` Вы получите готовый к запуску сервер: | Файл | Назначение | | --- | --- | | `server.ts` | Описание сервера: инструменты, ресурсы, промпты | | `main.ts` | Запускает сервер через stdio | | `server.test.ts` | Тест, который вызывает инструмент прямо в процессе | Хотите начать с нуля? Установите два пакета и напишите `server.ts` сами: ```sh npm install picomcp zod ``` ## Пишем инструмент Откройте `server.ts`: ```ts import { pico } from "picomcp"; import { z } from "zod"; export const server = pico({ name: "my-server", version: "0.1.0" }) .tool("greet", { description: "Greets someone by name.", input: z.object({ name: z.string().describe("Who to greet") }), run: ({ name }) => `Hello, ${name}!`, }); ``` - `description` модель читает, чтобы решить, когда вызывать инструмент. Пишите её для модели. - `input` — любая [Standard Schema](https://picomcp.github.io/picomcp/ru/docs/schemas). Внутри `run` поле `name` имеет тип `string`. - Здесь `run` возвращает строку. Он может также возвращать объекты, блоки содержимого или выбрасывать ошибку: см. [Инструменты](https://picomcp.github.io/picomcp/ru/docs/tools). ## Тестируем ```ts import assert from "node:assert/strict"; import { test } from "node:test"; import { server } from "./server.ts"; test("greet says hello", async () => { const result = await server.call("greet", { name: "Ada" }); assert.deepEqual(result.content, [{ type: "text", text: "Hello, Ada!" }]); }); ``` ```sh npm test ``` `server.call` проверяется системой типов: опечатка в имени инструмента или пропущенный аргумент не скомпилируются. ## Подключаем Для Claude Code: ```sh claude mcp add my-server -- node /absolute/path/to/my-server/main.ts ``` Теперь попросите Claude кого-нибудь поприветствовать. Для Claude Desktop, Cursor, VS Code и других клиентов см. [Подключение клиента](https://picomcp.github.io/picomcp/ru/docs/clients). ## Отладка MCP Inspector позволяет вызывать инструменты вручную и видеть сырые сообщения: ```sh npx @modelcontextprotocol/inspector node main.ts ``` --- # Подключение клиента Source: https://picomcp.github.io/picomcp/ru/docs/clients > Настройка Claude Code, Claude Desktop, Cursor, VS Code и MCP Inspector. Все MCP-клиенты запускают локальный сервер одинаково: выполняют команду и общаются с ней через stdio. До удалённых серверов клиенты добираются по URL через Streamable HTTP. Во всех конфигурациях ниже используйте абсолютные пути. ## Claude Code ```sh # local, over stdio claude mcp add weather -- node /absolute/path/main.ts # remote, over HTTP claude mcp add --transport http weather https://example.com/mcp ``` Добавьте `--scope project`, чтобы записать сервер в `.mcp.json` и поделиться им с командой. ## Claude Desktop Отредактируйте `claude_desktop_config.json` (Настройки → Разработчик → Изменить конфигурацию) и перезапустите приложение: ```json { "mcpServers": { "weather": { "command": "node", "args": ["/absolute/path/main.ts"] } } } ``` ## Cursor Создайте `.cursor/mcp.json` в проекте или `~/.cursor/mcp.json` для всех проектов: ```json { "mcpServers": { "weather": { "command": "node", "args": ["/absolute/path/main.ts"] }, "remote-weather": { "url": "https://example.com/mcp" } } } ``` ## VS Code Создайте `.vscode/mcp.json`: ```json { "servers": { "weather": { "type": "stdio", "command": "node", "args": ["/absolute/path/main.ts"] }, "remote-weather": { "type": "http", "url": "https://example.com/mcp" } } } ``` ## MCP Inspector ```sh npx @modelcontextprotocol/inspector node /absolute/path/main.ts ``` Чтобы отладить HTTP-сервер, запустите его через `serve({ http: 3000 })`, откройте Inspector, выберите **Streamable HTTP** и введите `http://127.0.0.1:3000/mcp`. Inspector работает на localhost, а picomcp разрешает его по умолчанию. ## Bun и Deno Замените `node` на свою среду выполнения: ```json { "command": "bun", "args": ["/absolute/path/main.ts"] } ``` ```json { "command": "deno", "args": ["run", "--allow-net", "/absolute/path/main.ts"] } ``` ## Решение проблем **Клиент не показывает ни одного инструмента.** Выполните команду из конфигурации сами. Если она завершается или выводит ошибку, клиент видит то же самое. **«Unexpected token» или ошибки JSON в логе клиента.** Что-то записало в stdout. При работе через stdio picomcp перенаправляет `console.log` в stderr, но другие библиотеки могут писать в `process.stdout` напрямую. **Node не запускает файлы `.ts`.** Обновитесь до Node 22.18 или новее либо запускайте сервер через Bun. --- # Инструменты Source: https://picomcp.github.io/picomcp/ru/docs/tools > Определение инструментов, проверка входных данных, возврат текста, объектов и медиа, ошибки и отмена. Инструмент — это функция, которую может вызвать модель. Зарегистрируйте его через `server.tool(name, config)`: ```ts server.tool("search_issues", { title: "Search issues", description: "Search open GitHub issues in a repository. Use it before filing a new issue.", input: z.object({ repo: z.string().describe("owner/name"), query: z.string(), limit: z.number().int().max(50).default(10), }), annotations: { readOnlyHint: true, openWorldHint: true }, run: async ({ repo, query, limit }, { signal }) => { const res = await fetch(`https://api.github.com/search/issues?q=${encodeURIComponent(`${query} repo:${repo}`)}&per_page=${limit}`, { signal }); const { items } = await res.json(); return items.map((i: { title: string; html_url: string }) => `${i.title} — ${i.html_url}`).join("\n"); }, }); ``` ## Настройки | Параметр | Тип | Описание | | --- | --- | --- | | `description` | `string` | Что делает инструмент и когда его использовать. Этот текст читает модель. | | `title` | `string` | Отображаемое имя для клиентов. | | `input` | Standard Schema | Схема аргументов. Не указывайте её для инструментов без аргументов. | | `output` | Standard Schema | Схема структурированного результата. См. [Структурированный вывод](#структурированный-вывод). | | `annotations` | `ToolAnnotations` | Подсказки для клиентов: `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`. | | `run` | `(input, ctx) => value` | Обработчик. Может быть асинхронным. | Имя инструмента — от 1 до 128 символов, только буквы, цифры, `_`, `.` и `-`. Некорректное или повторяющееся имя сразу приводит к ошибке: проблема обнаружится при запуске сервера, а не когда модель вызовет инструмент. ## Проверка входных данных picomcp проверяет аргументы по вашей схеме до вызова `run`. Значения по умолчанию и преобразования применяются, поэтому в примере `limit` внутри `run` всегда число. Если проверка не прошла, инструмент не вызывается. Клиент получает ошибку инструмента с именем неверного поля, чтобы модель исправила вызов и повторила попытку: ```text Invalid arguments for tool "search_issues": limit: Too big: expected number to be <=50 ``` ## Возвращаемые значения Возвращайте то, что удобно. picomcp превратит это в результат MCP: | `run` возвращает | Клиент получает | | --- | --- | | `string` | Один текстовый блок | | объект, массив или число | Его JSON в виде текста. Объекты также отправляются как `structuredContent`. | | блок содержимого или массив блоков | Эти блоки как есть | | `{ content: [...] }` | Результат как есть, для полного контроля | | `undefined` | Пустой результат | Блоки содержимого создаются вспомогательными функциями: ```ts import { audio, image, link, text } from "picomcp"; run: async () => [ text("Here is the chart:"), image(await renderPng(), "image/png"), // Uint8Array or base64 string link("file:///reports/q3.csv", "q3.csv", { mimeType: "text/csv" }), ]; ``` ## Структурированный вывод Объявите схему `output`, если результат обрабатывают клиенты или код, а не только модель: ```ts server.tool("get_price", { input: z.object({ sku: z.string() }), output: z.object({ sku: z.string(), cents: z.number().int(), currency: z.literal("EUR") }), run: async ({ sku }) => ({ sku, cents: await priceOf(sku), currency: "EUR" }), }); ``` Тогда picomcp: 1. объявляет схему как `outputSchema` в `tools/list`; 2. проверяет на уровне типов значение, которое возвращает `run`; 3. проверяет значение во время выполнения и отправляет его как `structuredContent`, вместе с JSON-копией в тексте для старых клиентов. Если значение не подходит, клиент получает ошибку инструмента вместо некорректных данных. ## Ошибки Выбросьте ошибку в `run`. Её сообщение станет ошибкой инструмента (`isError: true`), которую модель прочитает и на которую сможет отреагировать: ```ts run: async ({ id }) => { const user = await db.users.find(id); if (!user) throw new Error(`No user with id ${id}. Call list_users to see valid ids.`); return user; }; ``` Пишите сообщения об ошибках для модели: что пошло не так и что делать дальше. Ошибки инструментов — часть обычного диалога. Ошибки протокола — нет: используйте `McpError`, только когда неверен сам запрос, например у пользователя нет доступа: ```ts import { McpError } from "picomcp"; throw new McpError(-32001, "Not authorised for this workspace"); ``` ## Отмена Второй аргумент `run` содержит `AbortSignal`. Он срабатывает, когда клиент отправляет `notifications/cancelled`, обычно потому что пользователь нажал «стоп». Передавайте его в `fetch` и другие API, которые его принимают: ```ts run: async ({ url }, { signal }) => { const res = await fetch(url, { signal }); return res.text(); }; ``` После отмены запроса picomcp не отправляет на него ответ, как того требует протокол. ## Метаданные `ctx.meta` содержит объект `_meta`, который клиент отправил вместе с запросом, если он есть. Клиенты используют его для трассировки и токенов прогресса. ## Как писать хорошие инструменты - **Одна задача на инструмент.** `create_invoice` и `send_invoice` лучше, чем `invoice(action)`. - **Описывайте аргументы.** `.describe()` у каждого поля попадает в JSON Schema, которую видит модель. - **Возвращайте меньше.** Модель платит за каждый токен. Возвращайте важные поля, а не весь ответ API. - **Аннотируйте честно.** `readOnlyHint: true` позволяет клиентам не спрашивать подтверждения. Никогда не ставьте его на инструмент, который что-то записывает. --- # Ресурсы Source: https://picomcp.github.io/picomcp/ru/docs/resources > Файлы, записи и живые данные, которые может читать клиент, с типизированными шаблонами URI. Ресурсы — это данные, которые клиент может прочитать и прикрепить к диалогу: файлы, строки базы данных, ответы API. Инструменты вызывает модель, а ресурсы обычно выбирает пользователь или приложение. ```ts server.resource("config://app", { description: "The app configuration", read: () => loadConfig(), }); ``` ## Шаблоны URI Добавьте в URI `{variables}`, чтобы обслуживать целое семейство ресурсов. Типы переменных выводятся из самой строки: ```ts server.resource("github://{owner}/{repo}/readme", { mimeType: "text/markdown", read: async ({ owner, repo }) => { // ^? { owner: string; repo: string } const res = await fetch(`https://raw.githubusercontent.com/${owner}/${repo}/HEAD/README.md`); return res.text(); }, }); ``` | Синтаксис | Совпадает с | Пример | | --- | --- | --- | | `{name}` | Один сегмент пути, без `/`, `?` и `#` | `users://{id}` совпадает с `users://42` | | `{+name}` | Чем угодно, включая `/` | `files://{+path}` совпадает с `files://src/index.ts` | Значения декодируются из URL до передачи в `read`. Статические ресурсы появляются в `resources/list`, шаблоны — в `resources/templates/list`. ## Возвращаемые значения | `read` возвращает | Отправляемое содержимое | | --- | --- | | `string` | Текст, с `mimeType` или `text/plain` | | `Uint8Array` | Blob в base64, с `mimeType` или `application/octet-stream` | | массив `{ uri, text }` или `{ uri, blob }` | Это содержимое как есть | | любое другое значение | Его JSON, как `application/json` | `read` получает контекст вторым аргументом, дополненный запрошенным `uri`: ```ts read: (params, { uri, signal }) => fetch(toHttp(uri), { signal }).then((r) => r.text()); ``` ## Параметры | Параметр | Описание | | --- | --- | | `name` | Идентификатор, который видят пользователи. По умолчанию — URI. | | `title` | Отображаемое имя. | | `description` | Что содержит ресурс. | | `mimeType` | Тип содержимого, которое возвращает `read`. | | `annotations` | Подсказки `audience`, `priority` и `lastModified`. | ## Ресурс не найден Если ни один ресурс не совпадает с URI, picomcp отвечает ошибкой протокола `-32002 Resource not found`. Чтобы отклонить URI, который совпадает с шаблоном, но ни на что не указывает, выбросьте `McpError` с тем же кодом: ```ts import { McpError } from "picomcp"; read: async ({ id }) => { const user = await db.users.find(id); if (!user) throw new McpError(-32002, `Resource not found: users://${id}`); return user; }; ``` ## Ссылки из инструментов Инструмент может указать модели на ресурс, а не встраивать его. Так большие данные не попадают в контекст, пока они не нужны: ```ts import { link } from "picomcp"; run: async () => link("reports://2026-q3", "Q3 report", { mimeType: "application/pdf" }); ``` --- # Промпты Source: https://picomcp.github.io/picomcp/ru/docs/prompts > Шаблоны промптов, которые пользователь выбирает в клиенте, с проверкой аргументов. Промпты — это шаблоны, которые пользователь выбирает в клиенте, часто как slash-команды. Они превращают несколько аргументов в одно или несколько сообщений. ```ts server.prompt("review", { title: "Code review", description: "Review a diff for bugs and style issues.", args: z.object({ diff: z.string().describe("The diff to review"), focus: z.string().optional().describe("What to pay attention to"), }), run: ({ diff, focus }) => `Review this diff${focus ? `, focusing on ${focus}` : ""}:\n\n${diff}`, }); ``` Клиент покажет `diff` как обязательный аргумент, а `focus` как необязательный, вместе с описаниями. Всё это выводится из схемы. ## Аргументы — это строки MCP передаёт аргументы промптов строками. Используйте строковые схемы или преобразование типов: ```ts args: z.object({ count: z.coerce.number().int().min(1) }); ``` Некорректные аргументы отклоняются с ошибкой `Invalid params` ещё до вызова `run`. ## Возвращаемые значения | `run` возвращает | Отправляемые сообщения | | --- | --- | | `string` | Одно сообщение пользователя | | `PromptMessage[]` | Эти сообщения | | `{ description, messages }` | Полный результат | Используйте массив для многошаговых шаблонов или чтобы добавить медиа: ```ts import { image, text } from "picomcp"; run: async ({ id }) => [ { role: "user", content: text("What is wrong with this screenshot?") }, { role: "user", content: image(await screenshot(id), "image/png") }, ]; ``` --- # Схемы Source: https://picomcp.github.io/picomcp/ru/docs/schemas > Zod, Valibot, ArkType или любая библиотека Standard Schema, и как генерируется JSON Schema. В picomcp нет встроенной библиотеки схем. Он принимает любую библиотеку, которая реализует [Standard Schema](https://standardschema.dev), и генерирует нужную MCP-клиентам JSON Schema через [Standard JSON Schema](https://standardschema.dev/json-schema). Вы продолжаете пользоваться привычной библиотекой, а picomcp остаётся маленьким. ## Zod 4 Работает без дополнительных шагов: ```ts import { z } from "zod"; server.tool("forecast", { input: z.object({ city: z.string().describe("City name") }), run: ({ city }) => getForecast(city), }); ``` ## ArkType Работает без дополнительных шагов: ```ts import { type } from "arktype"; server.tool("forecast", { input: type({ city: "string" }), run: ({ city }) => getForecast(city), }); ``` ## Valibot В Valibot поддержка JSON Schema вынесена в отдельный пакет. Оберните схему в `toStandardJsonSchema`: ```ts import * as v from "valibot"; import { toStandardJsonSchema } from "@valibot/to-json-schema"; server.tool("forecast", { input: toStandardJsonSchema(v.object({ city: v.string() })), run: ({ city }) => getForecast(city), }); ``` ## Любая другая библиотека Если библиотека проверяет данные через Standard Schema, но не умеет генерировать JSON Schema (например, Zod 3), добавьте схему сами через `withJsonSchema`: ```ts import { withJsonSchema } from "picomcp"; server.tool("forecast", { input: withJsonSchema(schema, { type: "object", properties: { city: { type: "string" } }, required: ["city"], }), run: ({ city }) => getForecast(city), }); ``` Без этого регистрация инструмента выбросит ошибку, которая скажет ровно это. Проблема никогда не дойдёт до клиента. ## Как передаются типы - `run` получает **выходной** тип схемы, после значений по умолчанию и преобразований. - `server.call()` принимает **входной** тип схемы, поэтому в тестах поля со значением по умолчанию необязательны. - Если задана схема `output`, `run` должен возвращать её входной тип. ## Советы: схемы, понятные моделям - Описывайте каждое поле. Описания — это документация, которую читает модель. - Используйте enum вместо свободного текста, если набор значений известен. - Задавайте значения по умолчанию для необязательного поведения, чтобы модель могла не указывать аргументы. - Делайте объекты плоскими. Глубоко вложенные аргументы моделям заполнять труднее. --- # Транспорты Source: https://picomcp.github.io/picomcp/ru/docs/transports > stdio для локальных клиентов, Streamable HTTP для удалённых и fetch-обработчик, на котором работают оба. Транспорт передаёт сообщения JSON-RPC между клиентом и вашим сервером. picomcp поддерживает оба транспорта, которые определяет MCP. Ваши инструменты при этом не меняются. ## stdio ```ts await server.serve(); ``` Клиент запускает ваш сервер как дочерний процесс и обменивается с ним JSON, разделённым переводами строк, через stdin и stdout. Так с локальными серверами работают десктопные клиенты и агенты для программирования. stdout принадлежит протоколу. При работе через stdio picomcp перенаправляет `console.log`, `console.info` и `console.debug` в stderr, так что случайная строка лога не сломает соединение. Клиенты обычно показывают stderr в своих логах. ## Streamable HTTP ```ts const running = await server.serve({ http: 3000 }); console.error(`Listening on ${running.url}`); // http://127.0.0.1:3000/mcp ``` `serve({ http })` запускает сервер на Bun, Deno или Node — в той среде, где он выполняется. Параметры: | Параметр | По умолчанию | Описание | | --- | --- | --- | | `port` | `3000` | Порт. `0` выбирает свободный. | | `hostname` | `"127.0.0.1"` | Укажите `"0.0.0.0"`, чтобы принимать подключения с других машин. | | `path` | `"/mcp"` | Путь эндпоинта. На другие пути сервер отвечает 404. | Возвращает `{ url, close() }`. ## fetch-обработчик `server.fetch` — это функция `(Request) => Promise` по веб-стандартам. Передайте её любой среде или фреймворку, которые работают с `fetch`: ```ts Bun.serve({ port: 3000, fetch: server.fetch }); ``` ```ts Deno.serve({ port: 3000 }, server.fetch); ``` ```ts export default { fetch: server.fetch }; // Cloudflare Workers ``` `server.fetch` не смотрит на путь: направляйте к нему запросы сами. Другие платформы — в разделе [Развёртывание](https://picomcp.github.io/picomcp/ru/docs/deploy). ## Как picomcp работает с HTTP picomcp использует Streamable HTTP в режиме JSON без состояния: - `POST` с сообщением JSON-RPC или пакетом → `200` с JSON-ответом, либо `202` для уведомлений. - `GET` и `DELETE` → `405`: так клиенты узнают, что поток от сервера к клиенту не предоставляется. - `OPTIONS` → CORS preflight для разрешённых источников. - Неподдерживаемый заголовок `MCP-Protocol-Version` → `400`. Сессий хранить не нужно, поэтому любой экземпляр может ответить на любой запрос. Благодаря этому picomcp без дополнительной работы подходит для serverless-платформ, балансировщиков нагрузки и автомасштабирования. ## Источники и DNS rebinding Браузеры отправляют заголовок `Origin`. Вредоносная страница могла бы с его помощью добраться до сервера на вашей машине, поэтому picomcp его проверяет: - Запросы без `Origin` (CLI, серверы, десктопные приложения) разрешены всегда. - По умолчанию разрешены только источники `localhost`, `127.0.0.1` и `[::1]`, на любом порту. - Остальные источники получают `403`. Разрешите другие источники параметром `origins`: ```ts pico({ name: "notes", origins: ["https://claude.ai", "https://app.example.com"] }); pico({ name: "public", origins: "*" }); // any origin: only for public, unauthenticated servers ``` ## Свои транспорты `server.handle(message)` обрабатывает одно сообщение JSON-RPC или пакет и возвращает ответ. Для уведомлений он возвращает `undefined`. С его помощью picomcp подключается к WebSocket, очередям сообщений и чему угодно ещё: ```ts socket.on("message", async (data) => { const response = await server.handle(JSON.parse(data)); if (response) socket.send(JSON.stringify(response)); }); ``` --- # Тестирование Source: https://picomcp.github.io/picomcp/ru/docs/testing > Тестируйте инструменты прямо в процессе с полной типизацией, затем проверяйте протокол целиком. ## Вызов инструментов в процессе `server.call(name, input)` выполняет инструмент так же, как это сделал бы клиент, но без транспорта и дочернего процесса. Проверка входных данных, значения по умолчанию, проверка вывода и обработка ошибок работают как обычно. ```ts import { describe, expect, it } from "vitest"; import { server } from "./server.ts"; describe("forecast", () => { it("returns the forecast", async () => { const result = await server.call("forecast", { city: "Lyon" }); expect(result.isError).toBeUndefined(); expect(result.structuredContent).toMatchObject({ city: "Lyon" }); }); it("explains invalid input to the model", async () => { const result = await server.call("forecast", { city: "Lyon", days: 30 }); expect(result.isError).toBe(true); }); }); ``` Это работает с любым тест-раннером: Vitest, `node:test`, `bun test`, `deno test`. ## Вызовы с проверкой типов Если инструменты зарегистрированы цепочкой, `call` знает их имена и входные данные: ```ts const server = pico({ name: "weather" }).tool("forecast", { input: z.object({ city: z.string(), days: z.number().default(3) }), run: ({ city, days }) => `${city}: ${days} days`, }); server.call("forecast", { city: "Lyon" }); // ✓ days is optional: it has a default server.call("forecast", {}); // ✗ city is missing server.call("forcast", { city: "Lyon" }); // ✗ no tool named forcast ``` Если регистрировать инструменты отдельными инструкциями, тип `server` не сможет их отследить, и `call` примет любое имя. Используйте цепочку вызовов или переприсваивание: `const app = server.tool(...)`. ## Тестирование протокола `server.handle` принимает сырой JSON-RPC. Это удобно для проверки списков и схем: ```ts const res = await server.handle({ jsonrpc: "2.0", id: 1, method: "tools/list" }); expect(res).toMatchSnapshot(); ``` Снапшот `tools/list` — простой способ заметить, что изменение схемы поменяло то, что видят модели. ## Тестирование с настоящим клиентом Для сквозных тестов управляйте сервером через клиент официального SDK по HTTP: ```ts import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const running = await server.serve({ http: { port: 0 } }); const client = new Client({ name: "test", version: "1.0.0" }); await client.connect(new StreamableHTTPClientTransport(new URL(running.url!))); const { tools } = await client.listTools(); await client.close(); await running.close(); ``` Собственные тесты picomcp делают именно это, через stdio и через HTTP. --- # Развёртывание Source: https://picomcp.github.io/picomcp/ru/docs/deploy > Удалённый MCP-сервер на Cloudflare Workers, Deno Deploy, Bun, Node, Hono или Next.js, с аутентификацией. До удалённого сервера клиенты добираются по URL через Streamable HTTP. Поскольку picomcp не хранит состояние, он работает на serverless-платформах без дополнительной настройки. ## Cloudflare Workers ```ts import { server } from "./server.ts"; export default { fetch(request: Request) { if (new URL(request.url).pathname !== "/mcp") return new Response("Not found", { status: 404 }); return server.fetch(request); }, }; ``` ```sh npx wrangler deploy src/worker.ts ``` ## Deno Deploy ```ts import { server } from "./server.ts"; Deno.serve((request) => server.fetch(request)); ``` ## Bun ```ts Bun.serve({ port: Number(process.env.PORT ?? 3000), routes: { "/mcp": server.fetch }, }); ``` ## Node ```ts await server.serve({ http: { port: Number(process.env.PORT ?? 3000), hostname: "0.0.0.0" } }); ``` ## Hono ```ts import { Hono } from "hono"; const app = new Hono(); app.all("/mcp", (c) => server.fetch(c.req.raw)); export default app; ``` ## Next.js ```ts // app/mcp/route.ts import { server } from "@/lib/mcp"; export const POST = server.fetch; export const GET = server.fetch; export const OPTIONS = server.fetch; ``` ## Аутентификация picomcp сам не занимается аутентификацией: оберните `server.fetch` и проверяйте запрос до того, как он дойдёт до инструментов. ```ts export default { async fetch(request: Request, env: { TOKEN: string }) { if (request.headers.get("authorization") !== `Bearer ${env.TOKEN}`) { return new Response("Unauthorized", { status: 401, headers: { "www-authenticate": "Bearer" } }); } return server.fetch(request); }, }; ``` Клиенты отправляют заголовок из своей конфигурации, например: ```sh claude mcp add --transport http notes https://notes.example.com/mcp --header "Authorization: Bearer $TOKEN" ``` Для OAuth поставьте middleware вашего провайдера перед `server.fetch` и следуйте [спецификации авторизации MCP](https://modelcontextprotocol.io/specification/latest/basic/authorization). ## Чек-лист - Отдавайте сервер по HTTPS. - Требуйте аутентификацию, если только сервер не публичный и не работает только на чтение. - Укажите в `origins` браузерные источники, которые ожидаете, если они есть. - Храните секреты в переменных окружения, а не в описаниях или результатах инструментов. - Считайте аргументы инструментов недоверенными данными: на модель может повлиять содержимое, которое она читает. --- # Справочник API Source: https://picomcp.github.io/picomcp/ru/docs/api > Все экспорты picomcp с сигнатурами. ```ts import { pico, McpError, PROTOCOL_VERSIONS, text, image, audio, link, withJsonSchema } from "picomcp"; import type { Server, ToolConfig, ResourceConfig, PromptConfig, Context, CallToolResult } from "picomcp"; ``` ## pico(info) Создаёт сервер. ```ts function pico(info: ServerInfo): Server; interface ServerInfo { name: string; // machine-friendly name, e.g. "weather" version?: string; // defaults to "0.0.0" title?: string; // display name instructions?: string; // how the model should use the server as a whole origins?: string[] | "*"; // allowed browser origins for HTTP; defaults to localhost } ``` ## server.tool(name, config) Регистрирует инструмент и возвращает сервер. См. [Инструменты](https://picomcp.github.io/picomcp/ru/docs/tools). ```ts interface ToolConfig { title?: string; description?: string; input?: I; // Standard Schema output?: O; // Standard Schema; enables structuredContent annotations?: ToolAnnotations; run(input: Infer, ctx: Context): Return | Promise; } interface ToolAnnotations { title?: string; readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean; } ``` Выбрасывает `TypeError` при некорректном или повторяющемся имени, а также для схемы без поддержки JSON Schema. ## server.resource(uri, config) Регистрирует ресурс или шаблон ресурса, если `uri` содержит `{variables}`. Возвращает сервер. См. [Ресурсы](https://picomcp.github.io/picomcp/ru/docs/resources). ```ts interface ResourceConfig { name?: string; title?: string; description?: string; mimeType?: string; annotations?: Annotations; read(params: UriParams, ctx: Context & { uri: string }): ResourceReturn | Promise; } ``` ## server.prompt(name, config) Регистрирует промпт и возвращает сервер. См. [Промпты](https://picomcp.github.io/picomcp/ru/docs/prompts). ```ts interface PromptConfig { title?: string; description?: string; args?: A; // Standard Schema of an object with string fields run(args: Infer, ctx: Context): string | PromptMessage[] | { description?: string; messages: PromptMessage[] }; } ``` ## server.call(name, input?) Выполняет инструмент в процессе и возвращает его `CallToolResult`. Проверяется системой типов, если инструменты зарегистрированы цепочкой. Для неизвестного имени инструмента промис отклоняется. См. [Тестирование](https://picomcp.github.io/picomcp/ru/docs/testing). ```ts call(name: string, input?: unknown): Promise; interface CallToolResult { content: ContentBlock[]; structuredContent?: Record; isError?: boolean; } ``` ## server.serve(options?) Запускает сервер через stdio или через Streamable HTTP, если задан `http`. См. [Транспорты](https://picomcp.github.io/picomcp/ru/docs/transports). ```ts serve(options?: { http?: number | { port?: number; hostname?: string; path?: string } }): Promise<{ url?: string; close(): Promise }>; ``` ## server.fetch(request) Обработчик Streamable HTTP по веб-стандартам. Привязан к серверу, поэтому его можно передавать как обычное значение. ```ts fetch(request: Request): Promise; ``` ## server.handle(message) Обрабатывает одно сообщение JSON-RPC или пакет. Возвращает ответ, а для уведомлений — `undefined`. ```ts handle(message: unknown): Promise; ``` ## Context Второй аргумент каждого обработчика. ```ts interface Context { signal: AbortSignal; // aborted when the client cancels meta: Record | undefined; // the request's _meta } ``` ## Функции для содержимого ```ts text(text: string, annotations?: Annotations): ContentBlock; image(data: string | Uint8Array, mimeType: string): ContentBlock; audio(data: string | Uint8Array, mimeType: string): ContentBlock; link(uri: string, name: string, extra?: { title?: string; description?: string; mimeType?: string }): ContentBlock; ``` `data` — строка base64 или сырые байты. Байты кодируются автоматически в любой среде. ## withJsonSchema(schema, jsonSchema, output?) Прикрепляет JSON Schema к Standard Schema, которая не умеет её генерировать. См. [Схемы](https://picomcp.github.io/picomcp/ru/docs/schemas). ```ts withJsonSchema(schema: S, jsonSchema: JsonSchema, output?: JsonSchema): StandardSchemaV1, Infer>; ``` ## McpError Выбросьте её в обработчике, чтобы ответить ошибкой JSON-RPC вместо результата. ```ts new McpError(code: number, message: string, data?: unknown); ``` | Код | Значение | | --- | --- | | `-32602` | Некорректные параметры | | `-32002` | Ресурс не найден | | от `-32000` до `-32099` | Свободны для ошибок вашего сервера | ## PROTOCOL_VERSIONS Версии протокола MCP, которые поддерживает picomcp, от новой к старой: `["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"]`. ## Вспомогательные типы | Тип | Описание | | --- | --- | | `Infer` | Выходной тип схемы | | `InferInput` | Входной тип схемы | | `UriParams<"a://{x}/{+y}">` | `{ x: string; y: string }` | | `StandardSchemaV1` | Интерфейс Standard Schema, встроенный в библиотеку | --- # Покрытие протокола Source: https://picomcp.github.io/picomcp/ru/docs/protocol > Какие именно части спецификации MCP реализует picomcp, а какие пока нет. picomcp ориентирован на MCP `2025-11-25` и при согласовании поддерживает `2025-06-18`, `2025-03-26` и `2024-11-05`. Каждая поддерживаемая возможность покрыта тестами, включая сквозные тесты с клиентом официального TypeScript SDK. ## Поддерживается | Область | Возможность | | --- | --- | | Жизненный цикл | `initialize` с согласованием версии, `notifications/initialized`, `ping` | | Инструменты | `tools/list`, `tools/call`, проверка входных данных, `outputSchema` и `structuredContent`, аннотации, заголовки | | Содержимое | текст, изображения, аудио, ссылки на ресурсы, встроенные ресурсы | | Ресурсы | `resources/list`, `resources/templates/list`, `resources/read`, текстовое и двоичное содержимое | | Промпты | `prompts/list`, `prompts/get`, аргументы из схем | | Утилиты | `notifications/cancelled` с `AbortSignal` | | JSON-RPC | Пакеты, коды ошибок, ответы клиента игнорируются | | stdio | JSON, разделённый переводами строк, stdout защищён от случайных логов | | Streamable HTTP | JSON-ответы без состояния, `202` для уведомлений, `405` для `GET`, CORS, проверка `Origin` и `MCP-Protocol-Version` | ## Пока не поддерживается | Возможность | Почему | | --- | --- | | Sampling, elicitation, roots | Для них сервер должен отправлять запросы клиенту. Архитектура picomcp без состояния пока не держит для этого открытый канал. | | Уведомления о логах, прогрессе и `list_changed` | По той же причине: это сообщения от сервера к клиенту. | | Подписки на ресурсы | По той же причине. | | Автодополнение | Запланировано. | | Курсоры пагинации | Списки возвращаются целиком. Этого достаточно для серверов с сотнями записей. | | Сессии (`Mcp-Session-Id`) и потоки SSE | Не нужны в режиме без состояния. | | Tasks | В спецификации пока экспериментальные. | Нужна одна из этих возможностей? [Создайте issue](https://github.com/picomcp/picomcp/issues) и опишите свой сценарий. Новые возможности появляются, когда их можно добавить, не раздувая picomcp. --- # Для ИИ-агентов Source: https://picomcp.github.io/picomcp/ru/docs/agents > Машиночитаемая документация, llms.txt, страницы в Markdown и правила для вашего агента. Агенты для программирования пишут много MCP-серверов. Эта документация устроена так, чтобы её было удобно читать и им, и вам. ## Машиночитаемая документация | URL | Содержимое | | --- | --- | | [`/llms.txt`](https://picomcp.github.io/picomcp/llms.txt) | Индекс всех страниц с описанием в одну строку, в формате [llms.txt](https://llmstxt.org) | | [`/llms-full.txt`](https://picomcp.github.io/picomcp/llms-full.txt) | Вся документация в одном файле Markdown | | `/docs/.md` | Любая страница в Markdown. Добавьте `.md` к её адресу, например [`/docs/tools.md`](https://picomcp.github.io/picomcp/ru/docs/tools.md) | На каждой странице также есть кнопка **Копировать как Markdown**, чтобы вставить её в чат. Эти файлы на английском: это эталонный язык, и модели лучше всего следуют именно ему. У каждого перевода есть и свой `llms-full.txt`, например `/fr/llms-full.txt`. ## Укажите агенту на документацию Вставьте это в инструкции агента, например в `AGENTS.md` или `CLAUDE.md` в корне проекта: ```md ## MCP servers This project builds MCP servers with picomcp. - Read https://picomcp.github.io/picomcp/llms-full.txt before writing picomcp code. - Define servers with `pico({ name })` and chain `.tool()`, `.resource()`, `.prompt()`. - Use Zod 4 schemas with `.describe()` on every field. - Write tool descriptions and error messages for the model: say what the tool does, when to use it, and what to do after an error. - Never write to stdout in a stdio server. Use `console.error` for logs. - Test tools with `server.call(name, input)`. Do not spawn a client in unit tests. ``` ## Дайте агенту сервер документации Документация доступна и как MCP-сервер [`picomcp-docs`](https://www.npmjs.com/package/picomcp-docs), построенный на picomcp. Он даёт агентам три инструмента, `list_docs`, `search_docs` и `read_doc`, и каждую страницу как ресурс. В Claude Code: ```sh claude mcp add picomcp-docs -- npx -y picomcp-docs ``` В Cursor, Windsurf, Claude Desktop и большинстве других клиентов добавьте его в файл конфигурации MCP (`.cursor/mcp.json` для Cursor): ```json { "mcpServers": { "picomcp-docs": { "command": "npx", "args": ["-y", "picomcp-docs"] } } } ``` Исходный код лежит в [`packages/docs`](https://github.com/picomcp/picomcp/tree/main/packages/docs), около 100 строк. ## В чём агенты часто ошибаются Вот ошибки, которые модели чаще всего допускают при написании MCP-серверов, и как всё устроено в picomcp: - **В режиме stdio stdout — это протокол.** `console.log` его испортит. picomcp перенаправляет вывод консоли в stderr при работе через stdio, но `process.stdout.write` по-прежнему опасен. - **Ошибки инструментов — это результаты, а не исключения.** Ошибка внутри `run` даёт `isError: true` с вашим сообщением. Модель видит его и может повторить попытку. Не перехватывайте ошибки только ради того, чтобы вернуть строку `"Error: ..."`. - **Аргументы промптов — это строки.** Для числовых аргументов используйте `z.coerce.number()`. - **JSON Schema берётся из библиотеки схем.** Zod 4 и ArkType ничего не требуют. Valibot требует `toStandardJsonSchema`. Остальные — `withJsonSchema`. - **`server.fetch` не смотрит на путь.** Направляйте к нему `/mcp` сами или используйте `serve({ http })`, который отвечает только на `/mcp`. - **Типизированный `server.call` требует цепочки.** `pico().tool().tool()` сохраняет типы. Отдельные инструкции их теряют. ## Участие в разработке с агентом В репозитории есть [`AGENTS.md`](https://github.com/picomcp/picomcp/blob/main/AGENTS.md) с правилами работы над самим picomcp: бюджет размера, команды для тестов и ограничения архитектуры.