Проектируйте tools как безопасные операции
Инструмент в MCP - это model-controlled capability: клиент решает, когда предложить его модели, а модель может сама сформировать аргументы. Отсюда неочевидное следствие: schema, описание, annotations, authorization, идемпотентность и размер ответа - это не удобства, а часть границы безопасности. Плохо спроектированный инструмент даёт модели власть, которую потом нечем ограничить.
Хороший инструмент начинается со строгой схемы и честных annotations.
const searchOutputSchema = z.object({
hits: z.array(z.object({
id: z.string(),
title: z.string(),
excerpt: z.string(),
score: z.number()
}))
});
const reviewOutputSchema = z.object({
service: z.string(),
version: z.string(),
decision: z.enum(["ready", "blocked"]),
checks: z.array(z.object({
id: z.enum(["ci", "tests", "security", "migration", "rollback"]),
title: z.string(),
passed: z.boolean(),
evidence: z.string()
})),
summary: z.string()
});
server.registerTool(
"search_runbooks",
{
title: "Поиск по runbook",
description: "Ищет только в утверждённых инженерных runbook и возвращает короткие выдержки.",
inputSchema: z.object({ query: z.string().min(2).max(120) }),
outputSchema: searchOutputSchema,
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
}
},
async ({ query }) => {
const output = { hits: searchRunbooks(query) };
return {
content: [{ type: "text", text: JSON.stringify(output) }],
structuredContent: output
};
}
);Важно понимать, что именно каждое поле обещает - и, главное, чего оно не гарантирует.
Поле · Что сообщает · Чего не гарантирует
inputSchema- Допустимая wire-форма аргументов; Право пользователя и бизнес-инвариантыoutputSchema- Ожидаемая structure результата; Истинность данныхreadOnlyHint- Операция не должна менять состояние; Безопасность внешнего APIdestructiveHint- Подсказка о разрушительном эффекте; Human approvalidempotentHint- Повтор с теми же args не добавляет эффект; Корректную retry policyopenWorldHint- Есть ли взаимодействие с внешним миром; Изоляцию сети
Эта таблица - защита от опасной иллюзии, будто schema и annotations уже делают инструмент безопасным. inputSchema задаёт допустимую форму аргументов, но не право пользователя и не бизнес-инвариант. readOnlyHint говорит о намерении, но не о безопасности внешнего API. Annotations - подсказки для UI и approval-политики host, не защита сервера.
Чтобы почувствовать, какую поверхность выбрать под конкретное намерение, прогоните его через конструктор - он подскажет, что уместнее: инструмент, ресурс или prompt.
Выберите поверхность MCP
Tool: узкое имя, Zod input/output, authorization, timeout и annotations.
Разница между наивным и профессиональным инструментом почти всегда в ширине семантики. run_shell(command) или query_database(sql) дают модели язык с огромной властью и никакой allowlist. review_release(service, version) даёт узкую операцию с чёткой схемой, авторизацией и предсказуемым ответом. Второе скучнее, но именно оно доживает до продакшена.
Annotations являются hints. Клиент может использовать их для UI и approval-политики, но сервер не должен считать их защитой. Реальные ограничения живут в handler и в downstream-системе.
Инструменты выполняют операции - но часто модели нужен не эффект, а факт. Для этого есть отдельная поверхность: resources.
У tool стоит readOnlyHint: true. Что это гарантирует?