You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On Telegram Web (https://web.telegram.org/a/), the MCP upload tool cannot upload files through BrowserOS neo — because the tool's ref-based injection contract does not match SPA sites with transient file inputs:
The upload MCP tool requires a ref of an <input type="file"> element from the last snapshot, then calls DOM.setFileInputFiles (per the official implementation: packages/browseros-agent/packages/browser-mcp/src/tools/upload.ts and crates/browseros-mcp/src/tools/upload.rs). But Telegram Web creates the file input transiently: a MutationObserver attached to document.body (subtree) recorded zero input[type=file] nodes during the click, document.querySelectorAll('input[type=file]') stays empty immediately after the File menu item is clicked, and window.showOpenFilePicker was not invoked. So there is never a persistent DOM node to hand to upload as a ref — the input either never becomes a persistent node or is created and destroyed within the same microtask.
Net effect: on this site the MCP upload tool has no working path to inject a file.
Open a new tab via MCP: tabs action="new" url="https://web.telegram.org/a/".
Open any chat, click the paperclip (attachment) button, then click File in the menu.
Observe: the menu closes, and document.querySelectorAll('input[type=file]') returns 0 — no file input exists in the DOM to target.
Try upload(page, ref, files) — there is no ref available in the snapshot to pass (upload fails with "Node is not a file input element" or cannot resolve a ref).
The native file chooser does open on agent clicks. When the File menu item is clicked via MCP (act), a Windows-native "Open" dialog does appear (verified by enumerating top-level windows — it shows up as a separate window with the title "打开"/Open at the top z-order). It is not intercepted or suppressed by BrowserOS: source search confirms there is noPage.setInterceptFileChooserDialog call anywhere in the codebase, and the FILE_CHOOSER timeout constant (packages/browseros-agent/packages/shared/src/constants/timeouts.ts) is defined but never referenced. The only reason this was initially missed is that CDP page screenshots cannot capture OS-level dialogs.
Manual (human) clicking works normally: a real user clicking the paperclip → File gets the native Windows file picker every time. The limitation is specific to the MCP upload tool's ref-based injection.
Workaround that works today: since the native chooser opens, a desktop-level automation layer (e.g. UIA) can drive the dialog (select the file, click Open) and the upload completes. This proves the browser/transport has no upload blockage — it is purely a missing agent-native injection path.
The upload tool's own description says "Set local file path(s) on a file input using a ref from the last snapshot" — that contract breaks on sites that mount the file input only for the duration of the click, or that prefer the File System Access API (showOpenFilePicker, which is available in Chromium 148 and does not create a DOM input at all).
Suggestion for a fix (Playwright/Puppeteer pattern): implement Page.setInterceptFileChooserDialog({enabled: true}) before triggering the click, listen for Page.fileChooserOpened (whose payload carries the transient input's backendNodeId), and let upload accept either a ref or the chooser's backendNodeId — i.e. support a "no-ref upload" mode that injects via DOM.setFileInputFiles(files, backendNodeId). This is exactly how Playwright's setInputFiles and Puppeteer's waitForFileChooser handle SPA uploads, and it works regardless of whether the input ever persists in the DOM or AX tree.
Issue Type
Operating System
Description of the bug
On Telegram Web (https://web.telegram.org/a/), the MCP
uploadtool cannot upload files through BrowserOS neo — because the tool's ref-based injection contract does not match SPA sites with transient file inputs:The
uploadMCP tool requires arefof an<input type="file">element from the last snapshot, then callsDOM.setFileInputFiles(per the official implementation:packages/browseros-agent/packages/browser-mcp/src/tools/upload.tsandcrates/browseros-mcp/src/tools/upload.rs). But Telegram Web creates the file input transiently: a MutationObserver attached todocument.body(subtree) recorded zeroinput[type=file]nodes during the click,document.querySelectorAll('input[type=file]')stays empty immediately after the File menu item is clicked, andwindow.showOpenFilePickerwas not invoked. So there is never a persistent DOM node to hand touploadas a ref — the input either never becomes a persistent node or is created and destroyed within the same microtask.Net effect: on this site the MCP
uploadtool has no working path to inject a file.Steps to Reproduce
tabs action="new" url="https://web.telegram.org/a/".document.querySelectorAll('input[type=file]')returns 0 — no file input exists in the DOM to target.upload(page, ref, files)— there is no ref available in the snapshot to pass (uploadfails with "Node is not a file input element" or cannot resolve a ref).Screenshots / Videos
N/A (the DOM evidence is described above).
BrowserOS Version
Chromium 148.0.7988.97 (BrowserOS neo / BrowserClaw build on Windows), MCP endpoint http://127.0.0.1:9010/mcp
Additional Context
act), a Windows-native "Open" dialog does appear (verified by enumerating top-level windows — it shows up as a separate window with the title "打开"/Open at the top z-order). It is not intercepted or suppressed by BrowserOS: source search confirms there is noPage.setInterceptFileChooserDialogcall anywhere in the codebase, and theFILE_CHOOSERtimeout constant (packages/browseros-agent/packages/shared/src/constants/timeouts.ts) is defined but never referenced. The only reason this was initially missed is that CDP page screenshots cannot capture OS-level dialogs.uploadtool's ref-based injection.showOpenFilePicker, which is available in Chromium 148 and does not create a DOM input at all).Page.setInterceptFileChooserDialog({enabled: true})before triggering the click, listen forPage.fileChooserOpened(whose payload carries the transient input'sbackendNodeId), and letuploadaccept either a ref or the chooser'sbackendNodeId— i.e. support a "no-ref upload" mode that injects viaDOM.setFileInputFiles(files, backendNodeId). This is exactly how Playwright'ssetInputFilesand Puppeteer'swaitForFileChooserhandle SPA uploads, and it works regardless of whether the input ever persists in the DOM or AX tree.