Skip to content

fix:mcp重载&消息限制#173

Merged
JohnRichard4096 merged 2 commits intomainfrom
fix/mcp-memory
Jan 29, 2026
Merged

fix:mcp重载&消息限制#173
JohnRichard4096 merged 2 commits intomainfrom
fix/mcp-memory

Conversation

@JohnRichard4096
Copy link
Member

@JohnRichard4096 JohnRichard4096 commented Jan 29, 2026

Summary by Sourcery

优化聊天消息丢弃和令牌限制行为、改进 MCP 客户端重连机制,并提升项目版本号。

Bug 修复:

  • 通过集中处理消息丢弃逻辑,确保被丢弃的上下文消息得到一致处理,并避免在总结中重复使用之前已被丢弃的消息。
  • 通过向加载器传递正确的客户端实例来修复 MCP 客户端的重新初始化问题,并增强连接失败时的错误日志记录。
  • 修正聊天任务运行状态的处理逻辑,确保仅在持有锁的情况下设置运行标志位。
  • 在执行内存和令牌限制时,防止工具角色(tool-role)的消息被错误地保留。

增强:

  • 改进 MCP 客户端的错误日志记录,通过结构化日志选项加入异常详情。

构建:

  • 将项目版本从 0.7.3.1 提升到 0.7.3.2。
Original summary in English

Summary by Sourcery

Refine chat message dropping and token-limiting behavior, improve MCP client reconnection, and bump the project version.

Bug Fixes:

  • Ensure dropped context messages are handled consistently by centralizing the drop logic and avoiding reusing previously dropped messages in summaries.
  • Fix MCP client reinitialization by passing the correct client instance to the loader and enhance error logging for connection failures.
  • Correct chat task running state handling by setting the running flag only while holding the lock.
  • Prevent tool-role messages from being incorrectly retained when enforcing memory and token limits.

Enhancements:

  • Improve MCP client error logging by including exception details with structured logging options.

Build:

  • Bump project version from 0.7.3.1 to 0.7.3.2.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 29, 2026

审查者指南(在小型 PR 上默认折叠)

审查者指南

将消息丢弃逻辑重构为可复用方法,以正确处理成对的工具消息,收紧基于内存与 Token 的截断行为,修复 MCP 客户端重新初始化时传递正确对象的问题,改进 MCP 连接错误日志记录,并调整聊天任务运行标记的设置时机,同时提升补丁版本号。

MCP 客户端重新初始化与改进错误日志的时序图

sequenceDiagram
    participant Manager as MCPClientManager
    participant Client as MCPClient
    participant Server as MCPServer
    participant Logger as Logger

    Manager->>Manager: reinitalize_all()
    activate Manager
    loop for each client in clients copy
        Manager->>Manager: unregister_client(client.server_script, False)
        Manager->>Manager: _load_this(client, fail_then_raise=False)
        activate Manager
        Manager->>Client: connect()
        alt connection fails
            Client-->>Manager: raise Exception e
            Manager->>Logger: opt(exception=e, colors=True).error(failed message)
        else connection succeeds
            Client-->>Manager: tools_remapping_tmp
            Manager->>Manager: tools_remapping.update(tools_remapping_tmp)
            Manager->>Logger: info(success message)
        end
        deactivate Manager
    end
    deactivate Manager
Loading

更新后 ChatManager 消息丢弃与限制行为的类图

classDiagram
    class ChatManager {
        List messages
        List _dropped_messages
        Config config
        datetime last_call
        bool _pending
        bool _is_running
        Memory memory

        void _make_abstract()
        void _drop_message()
        void run_enforce()
        void _limit_length()
        int get_token(memory)
        void __call__(event, matcher, bot)
    }

    class Memory {
        MessageList memory
    }

    class MessageList {
        List messages
    }

    class Message {
        string role
        string content
    }

    class Config {
        LLMConfig llm_config
        SessionConfig session
    }

    class LLMConfig {
        int memory_lenth_limit
    }

    class SessionConfig {
        int session_max_tokens
    }

    ChatManager --> Memory
    Memory --> MessageList
    MessageList --> Message
    ChatManager --> Config
    Config --> LLMConfig
    Config --> SessionConfig
Loading

更新后 MCPClientManager 加载行为的类图

classDiagram
    class MCPClientManager {
        List clients
        dict tools_remapping
        Lock _lock

        void _load_this(client, fail_then_raise)
        void reinitalize_all()
        void initialize_all(lock)
        void unregister_client(server_script, remove_from_list)
    }

    class MCPClient {
        string server_script
        void connect()
    }

    class Logger {
        void opt(exception, colors)
        void error(message)
        void info(message)
    }

    MCPClientManager --> MCPClient
    MCPClientManager --> Logger
    MCPClientManager --> Lock
Loading

文件级改动

变更 详情 文件
重构并集中消息丢弃行为,以便在执行内存和 Token 限制时,正确丢弃前导消息及其关联的工具响应。
  • 引入 _drop_message 辅助方法,从 memory 中弹出第一条消息,并在其后紧跟工具角色消息时一并弹出这条工具消息,将两者都追加到 _dropped_messages
  • 更新 _limit_length,在超过配置的内存长度限制时,仅直接丢弃工具角色消息,其他情况则委托给 _drop_message 来处理丢弃逻辑
  • get_token 的基于 Token 的截断逻辑中,用共享的 _drop_message 方法替换内联的 drop_message 闭包
amrita/plugins/chat/chatmanager.py
调整聊天执行的加锁流程,只有在获取异步锁之后才设置运行标记,以避免过早将任务标记为运行中。
  • 移除在获取锁之前提前赋值 _is_running 的逻辑
  • async with 锁块内部、开始处理之前,将 _is_running 设为 True
amrita/plugins/chat/chatmanager.py
改进 MCP 客户端重连与错误处理逻辑,以使用正确的客户端对象并提供更丰富的日志信息。
  • reinitalize_all 调整为向 _load_this 传递 MCPClient 对象本身,而不是其 server_script 字符串
  • 更新 MCP 连接失败的日志记录,使用 logger.opt 携带 exceptioncolors 参数,以获得更好的错误上下文,同时在配置为非抛出的情况下保持不抛异常
amrita/plugins/chat/utils/llm_tools/mcp_client.py
提升项目补丁版本号以反映此次修复发布,并相应更新锁文件。
  • pyproject.toml 中将项目版本从 0.7.3.1 增加到 0.7.3.2
  • 重新生成或更新 uv.lock 以匹配新的项目状态
pyproject.toml
uv.lock

提示与命令

与 Sourcery 交互

  • 触发新审查: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 在某条审查评论下回复,请求 Sourcery 从该评论创建一个 issue。你也可以直接回复该评论 @sourcery-ai issue 来从中创建 issue。
  • 生成 pull request 标题: 在 pull request 标题的任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文任意位置写上 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审查者指南: 在 pull request 中评论 @sourcery-ai guide,即可随时(重新)生成审查者指南。
  • 解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论且不想再看到它们,这会很有用。
  • 撤销所有 Sourcery 审查: 在 pull request 中评论 @sourcery-ai dismiss,以撤销所有现有的 Sourcery 审查。若你希望以全新的审查重新开始,这尤其有用——别忘了再评论 @sourcery-ai review 来触发新的审查!

自定义使用体验

访问你的 控制面板 以:

  • 启用或禁用审查功能,例如 Sourcery 自动生成的 pull request 摘要、审查者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查指令。
  • 调整其他审查设置。

获取帮助

Original review guide in English
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Refactors message-dropping logic into a reusable method that correctly handles paired tool messages, tightens memory and token-based truncation behavior, fixes MCP client reinitialization to pass the correct object, improves MCP connection error logging, and adjusts the chat task running flag timing, along with a patch version bump.

Sequence diagram for MCP client reinitialization and improved error logging

sequenceDiagram
    participant Manager as MCPClientManager
    participant Client as MCPClient
    participant Server as MCPServer
    participant Logger as Logger

    Manager->>Manager: reinitalize_all()
    activate Manager
    loop for each client in clients copy
        Manager->>Manager: unregister_client(client.server_script, False)
        Manager->>Manager: _load_this(client, fail_then_raise=False)
        activate Manager
        Manager->>Client: connect()
        alt connection fails
            Client-->>Manager: raise Exception e
            Manager->>Logger: opt(exception=e, colors=True).error(failed message)
        else connection succeeds
            Client-->>Manager: tools_remapping_tmp
            Manager->>Manager: tools_remapping.update(tools_remapping_tmp)
            Manager->>Logger: info(success message)
        end
        deactivate Manager
    end
    deactivate Manager
Loading

Class diagram for updated ChatManager message dropping and limits

classDiagram
    class ChatManager {
        List messages
        List _dropped_messages
        Config config
        datetime last_call
        bool _pending
        bool _is_running
        Memory memory

        void _make_abstract()
        void _drop_message()
        void run_enforce()
        void _limit_length()
        int get_token(memory)
        void __call__(event, matcher, bot)
    }

    class Memory {
        MessageList memory
    }

    class MessageList {
        List messages
    }

    class Message {
        string role
        string content
    }

    class Config {
        LLMConfig llm_config
        SessionConfig session
    }

    class LLMConfig {
        int memory_lenth_limit
    }

    class SessionConfig {
        int session_max_tokens
    }

    ChatManager --> Memory
    Memory --> MessageList
    MessageList --> Message
    ChatManager --> Config
    Config --> LLMConfig
    Config --> SessionConfig
Loading

Class diagram for updated MCPClientManager loading behavior

classDiagram
    class MCPClientManager {
        List clients
        dict tools_remapping
        Lock _lock

        void _load_this(client, fail_then_raise)
        void reinitalize_all()
        void initialize_all(lock)
        void unregister_client(server_script, remove_from_list)
    }

    class MCPClient {
        string server_script
        void connect()
    }

    class Logger {
        void opt(exception, colors)
        void error(message)
        void info(message)
    }

    MCPClientManager --> MCPClient
    MCPClientManager --> Logger
    MCPClientManager --> Lock
Loading

File-Level Changes

Change Details Files
Refactor and centralize message-dropping behavior to properly drop leading messages and associated tool responses when enforcing memory and token limits.
  • Introduce a _drop_message helper that pops the first message from memory and also pops a following tool-role message if present, appending both to _dropped_messages
  • Update _limit_length to drop only tool-role messages directly and otherwise delegate dropping to _drop_message when exceeding the configured memory length limit
  • Replace the inline drop_message closure in get_token with the shared _drop_message method for token-based truncation logic
amrita/plugins/chat/chatmanager.py
Adjust chat execution locking flow to set the running flag only after acquiring the async lock to avoid marking tasks as running too early.
  • Remove the early _is_running assignment before lock acquisition
  • Set _is_running to True inside the async with lock block just before processing begins
amrita/plugins/chat/chatmanager.py
Improve MCP client reconnection and error handling to use the correct client object and richer logging.
  • Change reinitalize_all to call _load_this with the MCPClient object instead of its server_script string
  • Update MCP connection failure logging to use logger.opt with exception and colors for better error context while remaining non-raising when configured
amrita/plugins/chat/utils/llm_tools/mcp_client.py
Bump project patch version to reflect the bugfix release and update lockfile accordingly.
  • Increment project version from 0.7.3.1 to 0.7.3.2 in pyproject.toml
  • Regenerate or update uv.lock to match the new project state
pyproject.toml
uv.lock

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并给出了一些整体反馈:

  • _drop_message 中,你在第一次 pop 之后假定 data.memory.messages[0] 一定存在;如果这个 helper 将来在其他地方复用,为了避免潜在的 IndexError,更安全的做法是在访问下标 0 前重新检查一次 len(data.memory.messages)
  • _make_abstract 中,将 self._dropped_messages + dropped_part 改为只使用 dropped_part,意味着之前被丢弃的消息将不再包含在摘要中;请再次确认这一行为变更是否是你在摘要逻辑中有意为之。
面向 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- In `_drop_message`, you assume `data.memory.messages[0]` exists after the first pop; if this helper is reused elsewhere in the future, it would be safer to re-check `len(data.memory.messages)` before accessing index 0 to avoid potential IndexError.
- The change in `_make_abstract` from `self._dropped_messages + dropped_part` to only `dropped_part` means previously dropped messages are no longer included in the summary; please double-check that this behavior change is intentional for your summarization logic.

## Individual Comments

### Comment 1
<location> `amrita/plugins/chat/chatmanager.py:249-254` </location>
<code_context>
         else:
             debug_log("未进行上下文摘要")

+    def _drop_message(self):
+        data = self.memory
+        if len(data.memory.messages) < 2:
+            return
+        self._dropped_messages.append(data.memory.messages.pop(0))
+        if data.memory.messages[0].role == "tool":
+            self._dropped_messages.append(data.memory.messages.pop(0))
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Potential infinite loop if `memory_lenth_limit` can be 0 and `_drop_message` is a no-op for a single message.

Because `_drop_message` returns early when `len(data.memory.messages) < 2`, it can be a no-op. In `_limit_length`, this is called inside `while len(data.memory.messages) > 0:` with only a `len(messages) > memory_lenth_limit` guard. If `memory_lenth_limit` can be 0, then with exactly one `assistant`/`user` message left, the condition stays true, `_drop_message()` does nothing, and the loop never makes progress. Consider either guaranteeing `_drop_message` always removes at least one message when invoked, or adding a guard (e.g. `len(messages) > 1` or an explicit `limit == 0` case) before calling it.
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据这些反馈改进后续的审查。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • In _drop_message, you assume data.memory.messages[0] exists after the first pop; if this helper is reused elsewhere in the future, it would be safer to re-check len(data.memory.messages) before accessing index 0 to avoid potential IndexError.
  • The change in _make_abstract from self._dropped_messages + dropped_part to only dropped_part means previously dropped messages are no longer included in the summary; please double-check that this behavior change is intentional for your summarization logic.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_drop_message`, you assume `data.memory.messages[0]` exists after the first pop; if this helper is reused elsewhere in the future, it would be safer to re-check `len(data.memory.messages)` before accessing index 0 to avoid potential IndexError.
- The change in `_make_abstract` from `self._dropped_messages + dropped_part` to only `dropped_part` means previously dropped messages are no longer included in the summary; please double-check that this behavior change is intentional for your summarization logic.

## Individual Comments

### Comment 1
<location> `amrita/plugins/chat/chatmanager.py:249-254` </location>
<code_context>
         else:
             debug_log("未进行上下文摘要")

+    def _drop_message(self):
+        data = self.memory
+        if len(data.memory.messages) < 2:
+            return
+        self._dropped_messages.append(data.memory.messages.pop(0))
+        if data.memory.messages[0].role == "tool":
+            self._dropped_messages.append(data.memory.messages.pop(0))
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Potential infinite loop if `memory_lenth_limit` can be 0 and `_drop_message` is a no-op for a single message.

Because `_drop_message` returns early when `len(data.memory.messages) < 2`, it can be a no-op. In `_limit_length`, this is called inside `while len(data.memory.messages) > 0:` with only a `len(messages) > memory_lenth_limit` guard. If `memory_lenth_limit` can be 0, then with exactly one `assistant`/`user` message left, the condition stays true, `_drop_message()` does nothing, and the loop never makes progress. Consider either guaranteeing `_drop_message` always removes at least one message when invoked, or adding a guard (e.g. `len(messages) > 1` or an explicit `limit == 0` case) before calling it.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@JohnRichard4096 JohnRichard4096 merged commit 178fa2d into main Jan 29, 2026
5 checks passed
@JohnRichard4096 JohnRichard4096 deleted the fix/mcp-memory branch January 29, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant