Metadata-Version: 2.4
Name: qrfile-mcp
Version: 0.2.7
Summary: QRFile: single-use HTTPS downloads on a private LAN with QR codes for MCP clients
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: mcp==2.3.0
Requires-Dist: qrcode[pil]<9,>=8
Requires-Dist: psutil<8,>=7
Requires-Dist: cryptography<51,>=50
Provides-Extra: test
Requires-Dist: pytest<10,>=8; extra == "test"

# QRFile MCP

Ask an MCP-enabled assistant to send a local file to your phone or another device. The tool copies **only the explicitly selected files**, starts a temporary **HTTPS server** on the sender's private LAN address, and returns a **single-use download URL and QR code containing temporary credentials**. Transfers use TLS 1.2 or newer to encrypt the file and credentials in transit; certificate verification is needed to authenticate the sender.

Once the receiving device trusts the server certificate, scanning the QR code or opening the URL starts a browser download directly. There is no intermediate download page or button. One file keeps its original name; multiple files arrive in a single **shared-files.zip** archive. The link includes the temporary credentials: no manual login, receiving app, account or SFTP client is needed. Both devices must already be on the same Wi-Fi/LAN subnet. The default temporary self-signed certificate causes a browser trust warning and may require receiver certificate setup; see **HTTPS and certificate trust** below.

## Package name

- Application: **QRFile**
- PyPI distribution and command: **`qrfile-mcp`**
- Python import and module launcher: **`qrfile_mcp`**

Requires **Python 3.11 or newer**. Install with `pip install qrfile-mcp` (or `pipx install qrfile-mcp`) and configure your MCP client to launch the `qrfile-mcp` command. It runs the MCP server over stdio. `python -m qrfile_mcp` is an equivalent launcher.

The HTTPS implementation is version **0.3.0** in this source tree. Until that version is published, install it from this directory with `pip install .` or run `./run.sh`; installing an older release from PyPI does not add HTTPS.

The source-tree `run.sh` and `server.py` launchers remain available for existing local installations.

## Usage

With QRFile connected to your MCP-enabled assistant, ask:

> Send `/Users/XXX/Downloads/report.pdf` to my phone and show the QR code.

Replace the example with the absolute path to your file. To share several files, name each one in your request; QRFile bundles them into one ZIP download.

1. Keep the sender and receiving device on the same trusted Wi-Fi/LAN subnet.
2. The assistant calls `create_file_share` and shows the HTTPS URL, QR image, expiration time, certificate SHA-256 fingerprint and trust instructions.
3. Verify and establish certificate trust as described below, then scan the QR code or open the URL on the receiving device. The download starts directly; the browser may show its own save confirmation.
4. After a complete server-side transfer, the link becomes inactive and temporary copies are removed. Keep the assistant's MCP process running until the transfer finishes.

You can also ask the assistant to check download status with `get_file_share` or `list_file_shares`, or cancel an unfinished share with `revoke_file_share`.

## HTTPS and certificate trust

All download listeners require **HTTPS with TLS 1.2 or newer**. Plain HTTP is rejected; there is no HTTP fallback. HTTPS encrypts the transfer, while checking the certificate authenticates the server and protects against an active network impersonator. It does not establish the receiver's identity or protect a copied download link.

By default, each share generates a new ECDSA P-256 key and short-lived self-signed certificate covering the selected LAN IP. The response includes `tls.certificate_sha256`, the public `tls.certificate_pem`, and trust instructions. The private key stays in the share's private temporary directory and is removed with the staged files on completion, expiration, revocation or graceful shutdown.

Browsers do not automatically trust these self-signed certificates. Before trusting a certificate, compare its SHA-256 fingerprint with the output shown on the sender through a trusted channel. Certificate inspection and trust setup vary by device and browser; some browsers may block the download until trust is configured. A QR code cannot install certificate trust. Simply dismissing a warning without verification leaves the sender unauthenticated. A new default share uses a new certificate, so trust does not carry over between shares.

For downloads without certificate warnings, configure a certificate and matching PEM private key using the MCP process's environment:

```sh
export LOCAL_SHARE_TLS_CERT=/absolute/path/to/server-cert-chain.pem
export LOCAL_SHARE_TLS_KEY=/absolute/path/to/server-key.pem
./run.sh
```

The certificate must include the selected LAN IPv4 address as an **IP subject alternative name**, remain valid for the full share lifetime, and be issued by an authority the receiving device trusts. A private CA requires installing and trusting its CA certificate on the receiver beforehand. A certificate for a DNS hostname alone does not cover the IP-based download URL. The chain file should contain the server certificate first, followed by any intermediate certificates; the key must be unencrypted and protected by file permissions. Setting only one variable, supplying a mismatched key, or supplying a certificate with the wrong IP or validity prevents share creation. Configured certificate/key files are owned by the sender and are never deleted by QRFile.

## Tools

| Tool | Purpose |
| --- | --- |
| `create_file_share` | Share 1–20 explicitly selected regular files; return QR, HTTPS URL, credentials, expiration and certificate information. |
| `get_file_share` | Recover an active share's QR/link or inspect its status. |
| `list_file_shares` | Show file names, expiration, status and completed download counts; omit credentials. |
| `revoke_file_share` | Stop the listener, invalidate the URL, interrupt active transfers and remove copied files. |
| `list_local_networks` | Inspect private LAN interfaces if automatic selection needs correction. |

Example arguments:

```json
{
  "file_paths": ["/Users/XXX/Downloads/report.pdf"],
  "expires_in_seconds": 600
}
```

Expiration defaults to **10 minutes**, configurable from 1 to 60 minutes. The timer begins after copying files, so copying a large file does not consume its download window. Five shares may run simultaneously; each share supports eight simultaneous connections. Repeated invalid credential requests revoke that share after ten attempts. Each share permits one successful download. After the server finishes sending the entire payload, the URL and QR are immediately invalidated, the listener stops, and staged files are deleted. HEAD metadata probes do not consume the link. A simultaneous second download receives HTTP 409; an interrupted send releases the transfer slot for retry until the original expiration. ZIP entry names are flattened and duplicate names are renamed to preserve all selected files.

The completion signal is the server successfully writing and flushing the complete payload; it does not verify the receiver’s saved file or browser storage. A browser can still show its own Save/Download confirmation, and the MCP cannot bypass that prompt. Direct download means there is no extra application Download button.

Restart the MCP connection (or quit/reopen the LLM app) after updating so its running process loads the updated version. A graceful restart closes existing shares; create a new share afterward. Shares served by an older process that is still running retain that process's behavior until closed or expired.

## Scope and lifetime

- Every share gets its own private temporary directory and random port. Only the credential-bearing download route and its `/file/0` alias serve the selected payload; there is no directory listing or arbitrary filesystem path route.
- Shared files are snapshots. Modifying the originals does not change the downloadable copies. Original files are never moved or deleted.
- The listener binds to a single RFC1918 IPv4 interface, never `0.0.0.0`. Requests from other subnets, public addresses and loopback are rejected. Known VPN/virtual interfaces are excluded from automatic discovery. An optional `interface` argument chooses among the discovered interfaces.
- Credentials are randomly generated per share. The URL/QR embeds them to avoid a manual login screen. Anyone holding the link on the allowed LAN can download while it is active; there is no receiver identity verification. A GET preview that actually fetches the entire file can consume a single-use link.
- Download completion, expiration and revocation close the server and delete staged copies, including the QR image. The MCP process must remain running during transfers; graceful shutdown also removes shares. A forced process kill cannot guarantee deletion of temporary copies, although the stopped process can no longer serve them.
- The download transport is **HTTPS on the local network**. Verify certificate trust to authenticate the sender. Use trusted Wi-Fi/LAN; don't expose it through port forwarding, tunnels or public proxies. Source-address checks cannot distinguish a LAN relay from a local receiver.
- IPv6-only networks and routed devices on a different subnet are not supported. Wi-Fi guest/client isolation and sender firewalls can prevent a connection even when both devices use the same Wi-Fi name. A sleeping sender cannot serve downloads.
- Automatic interface discovery chooses the preferred active physical-looking private interface; it cannot universally classify custom VPN/virtual interface names. Use `list_local_networks` and specify the correct interface when necessary.

Optional sender restrictions:

- `LOCAL_SHARE_ALLOWED_ROOTS`: absolute directories separated by the OS path separator (`:` on macOS/Linux, `;` on Windows). If omitted, the trusted local MCP client can request any regular file readable by the sender's user account. The tool instructions require the exact user-requested paths and do not authorize inferred extra files.
- `LOCAL_SHARE_MAX_BYTES`: maximum total snapshot bytes per share, default 5 GiB.

mcp-name: io.github.hkturtco/qrfile
