Skip to main content
Glama

filebridge

Upload any-size files into the browser you're already signed in to, from an AI agent. An MCP server and CLI for Claude Code, Codex and friends that also moves files anywhere else: cloud storage through rclone, a headless upload browser, drives and the clipboard.

License: MIT Python 3.12+ MCP

Why

"Upload this video to my Drive" fails for agents in three boring ways:

  • Native file pickers cannot be driven. There is nothing to click, and dragging from Finder is impossible.

  • Browser-extension uploads are capped at about 10 MB. The file is pushed through the extension, not read from disk.

  • Headless browsers have no logins. A fresh automation browser is signed out of everything.

filebridge gives the agent four paths around that. Each one hands real disk paths to something that reads the file itself, so there is no size cap.

flowchart LR
    A[Agent<br/>Claude Code / Codex] -->|MCP tools or CLI| F[filebridge]
    F --> S["1. browser_upload_own<br/>one-time localhost links + JS snippet<br/>into YOUR signed-in browser"]
    F --> H["2. CDP browser<br/>set files / drop files / click<br/>(dedicated profile, or attach to yours)"]
    F --> R["3. rclone<br/>S3, R2, SFTP/NAS, Drive,<br/>Dropbox, OneDrive, ..."]
    F --> L["4. Local helpers<br/>rsync copy, clipboard files"]
    S --> W[Website upload form]
    H --> W
    R --> C[(Cloud / NAS)]
    L --> D[Drives, iCloud Drive folder,<br/>Slack / Finder / Mail via Cmd+V]

Which path to use:

You want to...

Use

Upload to a site where you are already signed in, in your everyday browser

browser_upload_own (CLI: filebridge serve)

Upload to a site with no login, or one you signed into in the automation profile

browser_snapshot, then browser_set_files / browser_drop_files / browser_click(files=...)

Drive your own running Chrome/Brave over DevTools

browser_launch(mode="attach"), then the same tools

Put files on a store that has an rclone backend (S3/R2, SFTP/NAS, Dropbox, ...)

transfer (needs a configured remote)

Copy to a drive, NAS mount or iCloud Drive folder

local_copy

Paste files into a desktop app (Slack, Telegram, Mail, Finder)

clipboard_files, then Cmd+V

Related MCP server: AKIMCP

Install

Requires Python 3.12+ (uv installs one if needed). Optional: rclone for transfer, Brave or Chrome for the browser tools.

Nothing to install up front: the MCP config below runs it with uvx. To try the CLI the same way:

uvx --from git+https://github.com/vincesector/filebridge filebridge --help

Or install it for good:

uv tool install git+https://github.com/vincesector/filebridge     # or: pip install git+https://github.com/vincesector/filebridge
filebridge --help

Platform note: developed and tested on macOS. Linux and Windows support (browser discovery on the PATH and in the standard install folders, xclip / wl-copy and PowerShell Set-Clipboard -Path for the clipboard, xdg-open and explorer /select, for reveal) is best effort and untested. rclone and browser_upload_own have no platform-specific code.

Quickstart

Claude Code

claude mcp add filebridge --scope user -- uvx --from git+https://github.com/vincesector/filebridge filebridge mcp

Only need uploads? Add --slim after mcp to expose just the upload tools (browser_upload_own, serve_status, serve_stop, browser_set_files, browser_drop_files, browser_snapshot, browser_tabs, browser_navigate, browser_launch) and save context. With uv tool install, the command is just filebridge mcp.

Codex

In ~/.codex/config.toml:

[mcp_servers.filebridge]
command = "uvx"
args = ["--from", "git+https://github.com/vincesector/filebridge", "filebridge", "mcp"]

First upload: your own signed-in browser

Ask the agent "upload ~/Movies/final.mp4 to my Drive". It calls browser_upload_own(paths=[...]), which starts a server on 127.0.0.1 in the background and returns:

{"ok": true, "server_id": "3f9c1a2b7d4e", "urls": ["http://127.0.0.1:53817/<token>/0/final.mp4"],
 "snippet": "var __fbUrls=[...]; ... JSON.stringify(__fbRes);", "expires_in_s": 1800}

The agent runs snippet in the signed-in tab with any tool that executes JavaScript there (for example the Claude in Chrome extension's JavaScript tool). The page fetches each file from localhost, then fills the <input type=file> matching selector, or drops the files on the element at the page centre when there is no selector (how Drive, X and Typefully listen). The snippet's result is a JSON string: {"ok": true, "action": "dropped", "files": 1, "bytes": ...} or {"ok": false, "code": "FetchBlocked", "message": ...}. serve_status(server_id) shows what was fetched.

Pass verify_text (for example the file name or "Upload complete") or verify_selector and the result also carries a verify_snippet: run it after the upload snippet and it waits until the page shows that, returning {verified, waited_s}.

The same from the CLI (prints the URLs and snippet, exits once every file was fetched):

filebridge serve ~/Movies/final.mp4 [--selector 'input[type=file]'] [--ttl 1800]

First upload: the automation browser

filebridge browser launch --url https://example.com/upload     # headless, dedicated profile
filebridge browser snapshot --tab example.com                  # lists e1, e2, ... with roles and names
filebridge set-files --tab example.com --ref e3 ~/big.zip --verify-text "Upload complete"

For a site that needs a login, sign in once in the automation profile (a visible window, you do the login), then go back to headless:

filebridge browser launch --visible --url https://the-site.example
# log in, then:
filebridge browser launch            # headless again, same saved session

Attach to your browser

browser_launch(mode="attach", cdp_url="http://127.0.0.1:9222") (CLI: filebridge browser attach --cdp-url ...) connects to a Chrome or Brave you started with remote debugging. filebridge never launches or closes a browser in this mode; browser_close only detaches. Endpoints must be loopback (127.0.0.1, localhost, ::1). Without cdp_url it tries http://127.0.0.1:9222, then the DevToolsActivePort file of the default Chrome and Brave profiles.

The honest trade-off: since Chrome 136, --remote-debugging-port is ignored on your default profile (a hardening against cookie theft). So there are two ways in:

  1. A separate profile you sign into once. Quit nothing; start a second instance with its own folder:

    # macOS (Brave: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser")
    "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222 --user-data-dir="$HOME/.chrome-agent"
    # Linux: google-chrome --remote-debugging-port=9222 --user-data-dir=$HOME/.chrome-agent
    # Windows: "%ProgramFiles%\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir=%USERPROFILE%\chrome-agent

    Sign in to the sites you need in that window. Logins persist in that folder. This is close to the built-in dedicated profile, with a visible window you keep using.

  2. Chrome's own toggle, where your Chrome version has it: open chrome://inspect/#remote-debugging and enable it. Your normal profile then exposes a websocket (no HTTP /json endpoints) and Chrome asks you to allow each new DevTools connection. filebridge opens one connection per tool call, so expect a prompt per call. This path is untested.

Anything that can reach that port can drive the browser and read its cookies, so only bind it to 127.0.0.1 (the default), and close the debugging instance when you are done.

How the browser paths work

  • browser_snapshot returns a compact list of interactive elements (role, name, text) with refs like e12: buttons, links, fields, file inputs (also hidden ones), and drop zones (found through their drop/dragover listeners or upload-style text). browser_set_files, browser_drop_files, browser_click and browser_type accept ref instead of selector. Refs are valid until the page navigates or reloads; a stale ref is an error, never a wrong element. Cross-site iframes and closed shadow roots are not in the snapshot (set_files without a selector still finds their file inputs).

  • browser_set_files uses CDP DOM.setFileInputFiles, so the browser reads the file from disk. It searches the page, open and closed shadow roots, and same-origin and cross-site iframes. Hidden inputs work. With no selector or ref it acts only if exactly one file input exists; otherwise it returns the candidates.

  • browser_drop_files sends real Input.dispatchDragEvent events carrying disk paths. If the page receives no files it falls back to an in-page DataTransfer built from disk-backed File objects (still no size cap, but the events are synthetic, isTrusted=false). The result says which method ran.

  • browser_click(..., files=[...]) always intercepts file choosers, so a native picker never appears. Use it for "Upload file" menu items that create the input on click.

  • verify_text / verify_selector on set_files and drop wait (up to verify_timeout) for the page to show that text or element afterwards and return verified: true/false.

  • When an upload target is missing and the page looks like a sign-in page (a password field, or a login/auth URL), the error code is NotSignedIn instead of SelectorNotFound. browser_navigate also warns about it.

  • tab is a tab id or a unique part of its URL or title.

  • In headless mode browser_text and browser_screenshot are how you see the page.

CLI reference

Add --json to any command for machine-readable output, and --allow-path DIR (repeatable) to allow reading files under DIR for that run. Exit code 1 on failure, with the error code printed (error [PathBlocked]: ...). Long jobs return a job id; add --wait to block until done.

Command

What it does

filebridge mcp [--slim]

Run the MCP server on stdio (what Claude Code / Codex launch); --slim = upload tools only

filebridge serve [--selector S] [--port P] [--ttl SECONDS] PATH...

Serve files to your own signed-in browser with one-time links and print the upload snippet

filebridge remotes

List configured rclone remotes

filebridge transfer SRC DEST [--mode copy|move|sync] [--dry-run] [--overwrite] [--confirm] [--bwlimit 10M] [--wait]

Copy/move/sync between local paths and rclone remotes as a background job

filebridge status JOB

Progress of a background job

filebridge jobs [--limit N]

Recent background jobs

filebridge cancel JOB

Stop a running job

filebridge ls PATH [-r] [--max N]

List a remote or local path

filebridge link PATH

Shareable link for a remote file or folder

filebridge mkdir PATH

Create a remote folder

filebridge stat PATH

Does a remote path exist; size and type

filebridge setup [BACKEND]

Exact commands to add a remote (drive, dropbox, onedrive, icloud, s3, r2, sftp)

filebridge copy SRC DEST [--overwrite] [--dry-run] [--wait]

Local rsync copy as a background job

filebridge clipboard PATH...

Put files on the clipboard as file references

filebridge reveal PATH

Show a path in Finder / the file manager (opens a window)

filebridge set-files --tab T [--selector S | --ref R] [--verify-text X] [--verify-selector S] [--verify-timeout N] PATH...

Attach local files to a file input

filebridge drop --tab T [--selector S | --ref R | --x X --y Y] [--method auto|cdp|synthetic] [--verify-...] PATH...

Drag-and-drop local files onto a drop zone

filebridge browser launch [--url U] [--browser auto|brave|chrome] [--visible]

Start or reuse the automation browser (headless unless --visible)

filebridge browser attach [--cdp-url URL] [--url U]

Use your running Chrome/Brave over DevTools (never launched or closed by filebridge)

filebridge browser close

Close the automation browser (profile kept); in attach mode, detach

filebridge browser tabs

List tabs

filebridge browser snapshot --tab T [--max N]

Interactive elements with refs

filebridge browser navigate --tab T URL [--timeout S]

Point a tab at a URL

filebridge browser text --tab T [--selector S] [--max-chars N]

Read the visible text of a page or element

filebridge browser screenshot --tab T [--path P] [--full-page]

Save a PNG of a tab

filebridge browser click --tab T (--selector S | --ref R) [--file F ...]

Click an element; --file fills a file chooser it opens

filebridge browser type --tab T (--selector S | --ref R) TEXT [--clear] [--enter]

Type into a field

filebridge browser wait --tab T WHAT [--timeout S]

Wait for a selector or text

Paths must be absolute or start with ~. See Security for which files can be read.

MCP tools

The MCP server and the CLI call the same code (filebridge/core.py).

Group

Tools

Your own browser

browser_upload_own, serve_status, serve_stop

CDP browser

browser_launch (headless / visible / attach), browser_close, browser_tabs, browser_snapshot, browser_set_files, browser_drop_files, browser_click, browser_type, browser_wait, browser_navigate, browser_text, browser_screenshot

rclone

remotes, transfer, job_status, jobs, cancel, ls, link, mkdir, stat, setup_instructions

Local

local_copy, clipboard_files, reveal

Nothing here deletes anything. move removes the source only after rclone verified the copy. sync can delete on the destination and requires a prior dry_run (or confirm=true). Copies skip existing files unless overwrite=true.

Errors

Failures come back as {"ok": false, "code": "...", "message": "..."} (plus details such as candidates or path), with the MCP error flag set when the call itself failed.

Code

Meaning

PathBlocked

The file matches the hard denylist (keys, .env, browser cookies...). Never allowed.

PathNotAllowed

Outside the allowed folders and the user said no, or could not be asked.

FileNotFound

The local path does not exist.

FileTooLarge

Over max_bytes in the config.

SelectorNotFound

Selector or ref matched nothing (or the ref is stale).

NotSignedIn

The page looks like a sign-in page.

TabNotFound

No tab, or several, match tab.

Timeout

A wait, load or command ran out of time.

FetchBlocked

(snippet) The browser could not reach localhost: it blocked the request, or the server already stopped.

LinkUsed

(snippet) That one-time link was already used.

RemoteNotConfigured

Unknown rclone remote.

BrowserNotRunning, NotInstalled, InvalidArgument, ConfigError, Failed

What they say.

Security

Which local files can be read

Every tool that reads local files (browser_set_files, browser_drop_files, browser_click(files=), browser_upload_own / serve, clipboard_files, local_copy, and transfer from a local source) resolves the real path (following symlinks) and checks it against ~/.filebridge/config.toml, created with these defaults on first run:

allowed_roots = ["~/Projects", "~/Movies", "~/Downloads", "~/Documents", "<cwd>"]
blocked_globs = ["**/.ssh/**", "**/.gnupg/**", "**/*.pem", "**/*.key", "**/id_rsa*", "**/.env", "**/.env.*",
                 "**/.aws/**", "**/.config/gcloud/**", "**/Library/Keychains/**", "**/.netrc", "**/.npmrc",
                 "**/.pypirc", "**/*secrets*.json", "**/Cookies", "**/Login Data", "**/.config/rclone/**"]
max_bytes = 0   # largest single file, 0 = no limit
  • Blocked (the requested path or the resolved one matches a blocked glob, case-insensitively): refused with PathBlocked, always. Approval cannot lift it, FILEBRIDGE_UNRESTRICTED cannot lift it, and the built-in patterns apply even if you delete them from the file (entries you add extend the list). A folder is scanned and refused if anything inside matches.

  • Inside an allowed root: proceeds. <cwd> is the folder the MCP client (or the CLI) was started in; it is ignored when that is / or your home folder.

  • Anywhere else: filebridge asks you through MCP elicitation: "filebridge wants to upload /path/file.mov (1.2 GiB) to the automation browser (tab 'drive'). Allow?" Yes allows that one call. No, or a client that does not support elicitation, returns PathNotAllowed naming the allowed_roots key to add. The CLI asks y/N on a terminal and refuses when stdin is not a TTY; --allow-path DIR adds a root for one run.

  • FILEBRIDGE_UNRESTRICTED=1 in the server environment turns the allowlist off (never the denylist).

The client's own permission mode can auto-accept elicitations. An agent client running in a "bypass permissions" or auto-approve mode may answer "yes" without showing you anything, so treat the allowlist as a convenience and the denylist as the hard floor. Hard links cannot be told apart from the original file, so a hard link to a secret inside an allowed folder is not caught by path rules.

The server binds 127.0.0.1 only. Every URL carries a random token, each file URL works exactly once (a second request gets 410 Gone), and the server stops and closes its socket once every file was fetched or the TTL (default 1800 s) runs out. It sends permissive CORS and Private Network Access headers so a page on another origin can fetch from it, which is why the token matters: do not paste the URLs or snippet anywhere you do not trust. A file over 1 GiB gets a warning, because the tab holds the whole file in memory while uploading.

Browsers

  • The automation browser uses a dedicated profile (~/.filebridge/browser-profile) with DevTools on 127.0.0.1:9229 only, and filebridge refuses that port if the listener was not started with that profile or binds beyond loopback. Sign in to only the sites you need.

  • Attach mode talks only to loopback endpoints, refuses a debugging port bound beyond loopback, and never launches or closes your browser. Anything with access to that port controls the browser, so keep it on 127.0.0.1.

  • rclone remotes are configured by a human. setup_instructions prints the command; filebridge never configures real accounts. Secrets stay in rclone's own config and are never printed.

  • No shell=True anywhere; subprocesses take argument lists.

  • Job state is stored in ~/.filebridge/jobs/<id>.{json,log} and ~/.filebridge/jobs.jsonl.

Limitations

  • macOS is the tested platform; Linux and Windows are best effort and untested.

  • browser_click and browser_drop_files act on the main page and same-origin iframes, not cross-site iframes.

  • Sites with bot detection may block headless Chrome-family browsers. CAPTCHAs and 2FA are the human's job (use the visible sign-in, or attach to your own browser).

  • browser_upload_own fetches through the page, so the page's Content Security Policy or the browser's local-network protection can block it (FetchBlocked), and the snippet has to be run in the tab by something that can execute JavaScript there.

  • The iclouddrive rclone backend is fragile (Apple blocks some accounts; Advanced Data Protection must be off). To reach iCloud Drive, local_copy into its local folder is more reliable.

  • No delete tool, by design.

FAQ

Why not just use the browser extension's upload tool? It caps files at about 10 MB. browser_upload_own exists for exactly that case: your signed-in tab pulls the file from localhost, no extension cap involved.

Does set_files really have no size limit? The browser reads the file from disk itself. A 300 MB file went through every browser path with a matching page-side SHA-256.

Will a window pop up on my screen? Not unless you launch with --visible, attach to your own browser, or call reveal. The automation browser runs --headless=new.

Can I use it without MCP? Yes, every action is a CLI command, so scripts and cron/launchd jobs can use it.

Do I need rclone? Only for transfer, remotes, ls, link, mkdir, stat and setup_instructions.

Why does it ask me before uploading a file? The file is outside your allowed folders. Add the folder to allowed_roots in ~/.filebridge/config.toml to stop the questions for it.

Development

uv sync
.venv/bin/python tests/test7_policy_offline.py  # path policy, elicitation, single-use links, error codes, --slim, config
.venv/bin/python tests/test4_mcp_listing.py     # tool list over stdio
.venv/bin/python tests/test_local.py            # rsync job, reveal validation
.venv/bin/python tests/test3_clipboard.py       # macOS pasteboard

Tests that need Brave/Chrome (all headless): test8_browser_v02.py (snapshot refs, verification, NotSignedIn, the upload snippet in a real page, attach mode), test2_browser.py (300 MB through every browser path), test5_cli_headless.py, test6_browser_extras.py. Needs rclone: test1_transfer.py (2 GiB through a local alias remote). Manual checks against real sites are in docs/LIVE_TESTS.md; design notes in docs/DESIGN.md; changes in CHANGELOG.md.

License

MIT

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that enables AI tools to control local browser sessions for ChatGPT, Claude, and other AI services, supporting querying, navigation, file uploads, and artifact management.
    21 npm
    567
    Mozilla Public 2.0
  • F
    license
    Not graded
    quality
    A
    maintenance
    Connects web-based AI chat clients such as Claude, ChatGPT, and Grok to a local machine through a single OAuth-gated MCP endpoint so they can browse, read, write, and edit files, run whitelisted shell commands, search content, use Git, query SQLite, drive browsers and DevTools, and manage background tasks, ports, and clipboard. It also exposes localhost and LAN services via a protected HTTP client, letting remote or mobile clients debug and operate local projects without granting unrestricted shell access.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables web-based AI clients such as Claude, ChatGPT, Grok, and Postman to securely operate a local machine through a single OAuth-gated MCP endpoint exposing 38 governed tools. Covers file read/write/edit, whitelisted shell commands, search, Git, SQLite, browser automation, DevTools, background tasks, localhost fetching, clipboard, notifications, port management, and Postman control.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables web-based AI clients like Claude, ChatGPT, and Grok to securely operate a local machine through OAuth-gated MCP tools covering files, shell, Git, databases, browser automation, and background tasks.
    MIT