From d55d40e7a21a10b6a36c9086cf453053bd6c6b73 Mon Sep 17 00:00:00 2001 From: yuanyuanxiang <962914132@qq.com> Date: Tue, 25 Aug 2026 14:14:26 +0200 Subject: [PATCH] Feature: Exclude human Web viewing from MCP remote control sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete the bidirectional mutual exclusion between MCP remote control and human Web viewing (§8.2). Direction 1 (remote_open rejected while a human session holds the screen sub-connection) already existed; this adds direction 2: a human Web viewer is now rejected while an MCP session owns the device. Add a m_McpTriggeredDevices marker (mirroring m_MfcTriggeredDevices) that is set when an MCP session is created (BeginScreenCtrlOpen) and cleared at every session-erasure site (CloseScreenCtrlSession, SweepIdleScreenCtrl, OnScreenControlClosed, EndScreenCtrlAction), so the marker cannot go stale and permanently block humans. HandleConnect checks IsMcpTriggered before mutating client state or starting the remote desktop. Co-Authored-By: deepseek-v4-pro --- server/2015Remote/McpServer.cpp | 12 +++++++++++- server/2015Remote/WebService.cpp | 24 ++++++++++++++++++++++++ server/2015Remote/WebService.h | 10 ++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/server/2015Remote/McpServer.cpp b/server/2015Remote/McpServer.cpp index 5b7a14b..3000277 100644 --- a/server/2015Remote/McpServer.cpp +++ b/server/2015Remote/McpServer.cpp @@ -3851,6 +3851,10 @@ bool CMcpServer::BeginScreenCtrlOpen(uint64_t device_id, const std::string& sess s.sessionId = sessionId; s.lastActiveAt = time(nullptr); m_ScreenCtrlSessions[device_id] = std::move(s); + // 双向互斥(方向二):标记该设备屏幕子连接归 MCP 会话独占,人类 Web 观看期间被拒绝 + // (HandleConnect 查 IsMcpTriggered)。会话擦除点(CloseScreenCtrlSession / + // SweepIdleScreenCtrl / OnScreenControlClosed / EndScreenCtrlAction)同步 ClearMcpTriggered。 + WebService().SetMcpTriggered(device_id); return true; } @@ -3881,6 +3885,7 @@ int CMcpServer::CloseScreenCtrlSession(uint64_t device_id, const std::string& se } if (it->second.subCtx) m_ScreenCtrlContextToDevice.erase(it->second.subCtx); m_ScreenCtrlSessions.erase(it); + WebService().ClearMcpTriggered(device_id); return 0; } @@ -3893,8 +3898,10 @@ int CMcpServer::SweepIdleScreenCtrl(time_t idleTimeoutSec) { ScreenCtrlSession& s = it->second; if (!s.busy && difftime(now, s.lastActiveAt) > (double)idleTimeoutSec) { if (s.subCtx) m_ScreenCtrlContextToDevice.erase(s.subCtx); - toClose.push_back(it->first); + uint64_t devId = it->first; + toClose.push_back(devId); it = m_ScreenCtrlSessions.erase(it); + WebService().ClearMcpTriggered(devId); // 锁内清除,避免与并发 remote_open 竞态 } else { ++it; } @@ -3921,8 +3928,10 @@ void CMcpServer::OnScreenControlClosed(context* subCtx) { // 注入在飞:不擦会话(注入线程仍持 subCtx),仅置 closed,由 EndScreenCtrlAction 收尾。 sit->second.closed = true; } else { + uint64_t devId = it->second; m_ScreenCtrlSessions.erase(sit); m_ScreenCtrlContextToDevice.erase(it); + WebService().ClearMcpTriggered(devId); } } @@ -3954,6 +3963,7 @@ void CMcpServer::EndScreenCtrlAction(uint64_t device_id, const std::string& sess if (it->second.closed) { // 注入期间子连接已断:擦会话+路由 if (it->second.subCtx) m_ScreenCtrlContextToDevice.erase(it->second.subCtx); m_ScreenCtrlSessions.erase(it); + WebService().ClearMcpTriggered(device_id); } } diff --git a/server/2015Remote/WebService.cpp b/server/2015Remote/WebService.cpp index 2b98056..bf2777c 100644 --- a/server/2015Remote/WebService.cpp +++ b/server/2015Remote/WebService.cpp @@ -643,6 +643,15 @@ void CWebService::HandleConnect(void* ws_ptr, const std::string& token, uint64_t } } + // Bidirectional mutex (direction 2, §8.2): reject human Web viewing while an MCP + // remote control session owns this host's screen sub-connection (SetMcpTriggered on + // remote_open, cleared on session teardown), so AI input cannot be injected into a + // picture a human is watching/controlling. + if (IsMcpTriggered(device_id)) { + SendText(ws_ptr, BuildJsonResponse("connect_result", false, "Device busy: remote control session active")); + return; + } + // Check max clients per device int current_count = GetWebClientCount(device_id); if (current_count >= m_nMaxClientsPerDevice) { @@ -2150,6 +2159,21 @@ void CWebService::ClearMfcTriggered(uint64_t device_id) { m_MfcTriggeredDevices.erase(device_id); } +void CWebService::SetMcpTriggered(uint64_t device_id) { + std::lock_guard lock(m_McpTriggeredMutex); + m_McpTriggeredDevices.insert(device_id); +} + +bool CWebService::IsMcpTriggered(uint64_t device_id) { + std::lock_guard lock(m_McpTriggeredMutex); + return m_McpTriggeredDevices.find(device_id) != m_McpTriggeredDevices.end(); +} + +void CWebService::ClearMcpTriggered(uint64_t device_id) { + std::lock_guard lock(m_McpTriggeredMutex); + m_McpTriggeredDevices.erase(device_id); +} + bool CWebService::HasActiveSession(uint64_t device_id) { std::lock_guard lock(m_ScreenContextsMutex); return m_ScreenContexts.find(device_id) != m_ScreenContexts.end(); diff --git a/server/2015Remote/WebService.h b/server/2015Remote/WebService.h index f312822..cb4c207 100644 --- a/server/2015Remote/WebService.h +++ b/server/2015Remote/WebService.h @@ -299,6 +299,12 @@ public: bool IsMfcTriggered(uint64_t device_id); void ClearMfcTriggered(uint64_t device_id); + // MCP trigger management - an MCP remote control session owns the screen + // sub-connection and blocks human Web viewing (bidirectional mutex, §8.2). + void SetMcpTriggered(uint64_t device_id); + bool IsMcpTriggered(uint64_t device_id); + void ClearMcpTriggered(uint64_t device_id); + // Check if a remote desktop session already exists for device bool HasActiveSession(uint64_t device_id); @@ -344,6 +350,10 @@ private: std::set m_MfcTriggeredDevices; std::mutex m_MfcTriggeredMutex; + // MCP triggered devices: screen sub-connection owned by an MCP remote control session + std::set m_McpTriggeredDevices; + std::mutex m_McpTriggeredMutex; + // Web 终端会话状态 struct WebTermSession { void* ws_ptr; // browser WebSocket