From 8b9a764c5f3cf71e904ee7ab08a8378613ee235b Mon Sep 17 00:00:00 2001 From: leemour Date: Sat, 17 Oct 2026 13:07:14 +0200 Subject: [PATCH] fix(bot): MCP bot calls run with the server's environment botMcpRun handed the command it runs the streams, tty and answer from the shared bot MCP server, but dropped env. A tool call therefore ran under process.env instead of the server's environment, so overrides such as MAX_READ_ONLY or a config directory did not apply. Pass the whole environment through, as tg does. The new test starts the bot MCP server with MAX_READ_ONLY=1 and checks that messages send is refused with no request to the Bot API. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 8 ++++++++ src/bot-mcp.test.ts | 13 +++++++++++-- src/commands/bot-messenger.ts | 4 ++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f6c846b..9da00b8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ номера версий по [семантике](https://semver.org/lang/ru/), то есть до `1.0.0` интерфейс команд ещё может меняться. +## Не выпущено + +### Исправлено + +- **Инструменты `max bot mcp` учитывают окружение сервера.** Раньше вызов шёл мимо переменных, с которыми + запущен сервер: например, при `MAX_READ_ONLY=1` бот всё равно отправлял сообщение. Теперь вызов через MCP + ведёт себя так же, как та же команда в терминале. + ## 0.58.0 — 17.10.2026 ### Безопасность diff --git a/src/bot-mcp.test.ts b/src/bot-mcp.test.ts index 99ba299a..133b416d 100644 --- a/src/bot-mcp.test.ts +++ b/src/bot-mcp.test.ts @@ -83,10 +83,11 @@ const owner = async (profile: string, argv: string[]) => { type Options = Partial> & { permissions?: Record readOtherBots?: boolean | string[] + env?: NodeJS.ProcessEnv } const connect = async ( - { permissions = {}, readOtherBots = false, ...options }: Options = {}, + { permissions = {}, readOtherBots = false, env = process.env, ...options }: Options = {}, { form }: { form?: (message: string) => ElicitResult } = {}, ) => { const profile = `bm-${++profiles}` @@ -97,7 +98,7 @@ const connect = async ( bot: { ...maxBot, mcp }, commandAt: commandLookup(group), settings: { profile, permissions, readOtherBots }, - env: process.env, + env, run: botMcpRun(run, testEnvironment()), tools: mcp.tools ?? [], ...(mcp.skill ? { skill: mcp.skill } : {}), @@ -230,6 +231,14 @@ describe("max bot mcp", () => { expect(deletes().at(-1)?.url).toBe("/chats/-100/members?user_id=42&block=true") }) + it("**runs each call under the server's environment**: MAX_READ_ONLY refuses a send there too", async () => { + const { client } = await connect({ env: { ...process.env, MAX_READ_ONLY: "1" } }) + const { isError, error } = await call(client, "messages send", { chat: "-100", text: "hi" }) + expect(isError).toBe(true) + expect(error.message).toContain("MAX_READ_ONLY") + expect(posts()).toHaveLength(0) + }) + it("meets the bot's recipient list, and the refusal is the command's", async () => { const { client, profile } = await connect() await owner(profile, ["recipients", "add", "-200"]) diff --git a/src/commands/bot-messenger.ts b/src/commands/bot-messenger.ts index 7412035f..a9f90e5e 100644 --- a/src/commands/bot-messenger.ts +++ b/src/commands/bot-messenger.ts @@ -22,8 +22,8 @@ import { environmentOf } from "./context.js" */ export const botMcpRun = (program: typeof run, extra: Parameters[1] = {}): RunBotCommand => - (argv, { streams, tty, answer }) => - program(argv, { ...extra, streams, tty, ...(answer ? { answer } : {}) }) + (argv, environment) => + program(argv, { ...extra, ...environment }) /** `bot mcp`. The program hands in its own `run`: importing it here would close a loop through the program. */ export const maxBotMcp = (program: typeof run): BotMcp => ({