Feature: Add MCP remote_open/remote_close remote control sessions

Add M1 of MCP remote control (docs/Mcp_RemoteControl_Design.md): the
remote_open / remote_close tools plus the ScreenCtrlSession state machine.

remote_open establishes a hidden screen sub-connection by reusing
WebService::StartRemoteDesktop (COMMAND_SCREEN_SPY -> CScreenSpyDlg ->
RegisterScreenContext), polls for the sub-connection plus its physical
resolution (TOKEN_BITMAPINFO -> NotifyResolutionChange -> GetScreenSize),
then records the session (single device, single session, reverse-mapped
subCtx for OfflineProc cleanup) and returns {session_id, screen_w,
screen_h}. remote_close validates session_id and tears down the
sub-connection idempotently. A McpRemoteControl settings checkbox (default
off, requires McpReadonly=0) gates the tools; every open/close is audited
via WM_SHOWERRORMSG.

Gating: multi-monitor hosts are rejected with -32008 (phase 1 supports only
single monitor, where Observe=main screen and Act=virtual desktop coincide);
the monitor count comes from the client heartbeat RES_RESOLUTION ("N:W*H").

Known limitations (deferred to the injection milestones): mutual exclusion
with human remote-desktop viewing is one-directional in M1 (a human who
joins during an MCP session can tear it down on disconnect), and subCtx is
not yet dereferenced so no liveness re-check is needed until
remote_mouse/remote_keyboard.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxiang
2026-08-25 12:55:52 +02:00
parent 84e3565de1
commit 6045baadb8
7 changed files with 469 additions and 4 deletions

View File

@@ -2195,6 +2195,8 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
McpServer().SetCmdWhitelist(THIS_CFG.GetStr("settings", "McpCmdWhitelist", ""));
// 持久终端开关默认关要求只读关McpReadonly=0才生效工具列表/分派双重门控)。
McpServer().SetTerminalEnabled(THIS_CFG.GetInt("settings", "McpTerminal", 0) != 0);
// 远程控制开关默认关要求只读关McpReadonly=0才生效工具列表/分派双重门控)。
McpServer().SetRemoteControlEnabled(THIS_CFG.GetInt("settings", "McpRemoteControl", 0) != 0);
if (!McpServer().Start(mcpBind, mcpPort)) {
Mprintf("McpServer start failed on %s:%d\n", mcpBind.c_str(), mcpPort);
} else {
@@ -5166,6 +5168,11 @@ BOOL CALLBACK CMy2015RemoteDlg::OfflineProc(CONTEXT_OBJECT* ContextObject)
McpServer().OnTerminalClosed(ContextObject);
}
// MCP 远程控制的屏幕子连接断开:同步清理会话,避免悬空 subCtx 被连接池复用误路由。
if (McpServer().IsRunning() && McpServer().IsScreenCtrlContext(ContextObject)) {
McpServer().OnScreenControlClosed(ContextObject);
}
SOCKET nSocket = ContextObject->sClientSocket;
CDialogBase* p = (CDialogBase*)ContextObject->hDlg;