Egnyte Large File Manager
by hschickdevs
README.md
# Egnyte Large File Manager
A local [MCP](https://modelcontextprotocol.io) server that fills the **binary / large-file gap** in Egnyte's hosted MCP connector.
Egnyte's official connector only returns **extracted text** (`get_file_content`) into agent context — it never hands you the real file bytes. So you can't `pandas.read_excel()` / `openpyxl` a workbook, parse a native PDF, or re-package a `.msg`. This server adds the missing piece: **sign in with OAuth, then download the actual binary to local disk** (and chunk-upload large files back).
It is meant to run **alongside** the official Egnyte connector, not replace it:
| Use the official Egnyte MCP for | Use this server for |
| --- | --- |
| search, `list_filesystem`, text extraction, `ask_document`, metadata | downloading real file **binaries** to disk, chunked upload of large files |
> **Where it runs.** This is a **local stdio MCP server** (and `.mcpb` bundle). The download→disk→load flow only works where the MCP server shares a filesystem with code execution:
> - **Claude Code (CLI):** ✅ fully works — server and your code run on the same machine.
> - **Claude Cowork / sandboxes:** ⚠️ Cowork runs your code in an isolated VM whose filesystem is **separate** from the host where the MCP server runs, so a file the MCP downloads isn't visible to the VM's Python. Use the bundled **`python/egnyte_fetch.py`** *inside* the sandbox instead (see [Claude Cowork / cloud sandbox](#claude-cowork--cloud-sandbox)).
> - **claude.ai web chat:** ❌ remote connectors only; no local MCP, no Python filesystem.
## Tools
| Tool | Purpose | Returns |
| --- | --- | --- |
| `egnyte_download` | Download a file's real bytes to local disk by `path` or `group_id`. Streams — handles large files. | the local filesystem **path** (never the bytes) |
| `egnyte_upload` | Upload a local file to Egnyte. Automatically uses **chunked** upload above the size threshold. | entry/group id + checksum |
| `egnyte_stat` | Get file metadata (size, type, ids, checksum) — use to decide before downloading. | metadata object |
| `egnyte_login` | Force the OAuth browser sign-in (otherwise it happens lazily on first call). | sign-in status |
**The model gets a path, not the bytes** — so a 200 MB workbook never bloats the context. Your code then does `pd.read_excel("/path/from/tool")`.
## Auth — simple OAuth sign-in
Uses the **OAuth 2.0 authorization-code flow** over an **HTTPS localhost loopback redirect**. Egnyte does not support PKCE / public clients, so this is a confidential client: a `client_secret` is required and is stored as a sensitive config field (never logged).
1. On first tool call (or `egnyte_login`) the server opens your browser to Egnyte's sign-in page.
2. You log in **as yourself** — including via your company's SSO/SAML.
3. Egnyte redirects to `https://localhost:<port>/callback` (a self-signed loopback listener — accept the one-time browser cert warning); the server captures the code and exchanges it (with the `client_secret`) for a token.
4. The token is cached locally (`0600`-permission file in your config dir) and reused; it auto-refreshes, falling back to a browser re-auth only when the refresh token is gone.
Because you sign in as yourself, the server only sees what **your** Egnyte permissions allow — same access model as the official connector. No shared service account, no flattened ACLs.
**Headless / SSH:** set `EGNYTE_NO_BROWSER=1` — the server prints the authorize URL instead of launching a browser; open it on any machine that can reach your Egnyte domain, and it will redirect back to the loopback.
## Prerequisites
- **Node.js ≥ 18**
- An **Egnyte API key + secret**. Register an app at <https://developers.egnyte.com> → "Get an API Key". Register `https://localhost:53682/callback` as the allowed redirect (Egnyte requires HTTPS).
- Your **Egnyte domain** (e.g. `acme.egnyte.com`).
## Configure
**Local dev:** copy `.env.example` → `.env` and fill in `EGNYTE_DOMAIN`, `EGNYTE_CLIENT_ID`, and `EGNYTE_CLIENT_SECRET` (all required — Egnyte is a confidential client).
**Installed `.mcpb` bundle:** the host (Claude Desktop) prompts for these via the bundle's `user_config` — `client_secret` is stored as a sensitive field.
## Run / develop
```bash
npm install
npm run login # one-time browser sign-in, caches token
npm run dev # run the stdio server locally
npm run build # compile to dist/
npm run pack # build + package into egnyte-large-file-manager.mcpb
```
### Use with Claude Code
```bash
claude mcp add egnyte-large-file-manager --transport stdio -- node /abs/path/to/dist/server.js
```
(or install the packed `.mcpb` in Claude Desktop)
## Claude Cowork / cloud sandbox
In Cowork your code runs in an **isolated VM** whose filesystem is separate from the host where an MCP server runs — so the MCP's download wouldn't be visible to the VM's Python. For these environments use the bundled, dependency-free **`python/egnyte_fetch.py`**, which runs *inside* the sandbox and pulls bytes straight from the Egnyte API.
**1. Pre-seed a token** (interactive browser OAuth can't reach a headless VM):
- Run `npm run login` once on a machine with a browser, then inject the resulting token into the sandbox — either copy `~/.config/egnyte-mcp/tokens.json`, or set `EGNYTE_ACCESS_TOKEN` as a sandbox secret.
- Per-user ACLs are preserved **only if each user injects their own token** (not a shared one).
**2. Use it in the sandbox** (`EGNYTE_DOMAIN` + the token in env):
```bash
python egnyte_fetch.py download "/Shared/Docs/report.xlsx" # prints {"path": "...", ...}
```
```python
from egnyte_fetch import download
import pandas as pd
path = download("/Shared/Docs/report.xlsx")["path"]
df = pd.read_excel(path, sheet_name=None) # real binary, loaded in-VM
```
Same hardening as the MCP: `*.egnyte.com` host lock, `..` path rejection, download-dir confinement, SHA-512 verify, single + chunked upload.
## Security
- **Per-user OAuth** — no shared service token; Egnyte ACLs are enforced per signed-in user.
- **Client secret** — Egnyte requires a confidential client; the `client_secret` is stored as a sensitive config field and is never logged or returned to the model. OAuth `state` guards the callback against CSRF.
- **Token cache** is a `0600` file; it is git-ignored and must never be committed.
- **Least privilege** — request only the scopes you need (filesystem read; add write only if you upload).
- **Paths, not bytes** — file contents are written to disk and referenced by path, never echoed through the model.
- **Download confinement** — downloads are restricted to the configured download directory; a model-supplied `dest` cannot escape it (no `../` or absolute-path writes).
- **Host locked to `*.egnyte.com`** — the API host is validated, so a bad `EGNYTE_DOMAIN` can't exfiltrate the token/secret to another server. `..` segments in Egnyte paths are rejected.
## License
Apache-2.0. See [LICENSE](./LICENSE).
TDQS
A4.2/5.0
Scored across 5 tools
Disambiguation5/5
Each tool has a clearly distinct function: download, login, logout, stat, upload. No overlap in purpose.
Naming Consistency5/5
All tools follow a consistent 'egnyte_' prefix followed by a verb (download, login, logout, stat, upload), making them predictable.
Tool Count5/5
5 tools is well-scoped for a large file manager, covering authentication, metadata inspection, download, and upload without unnecessary extras.
Completeness4/5
Covers core operations for large files (login, stat, download, upload) but lacks delete, move, or list capabilities, which are minor gaps given the stated purpose.
Maintenance
ActivityInactive
ResponsivenessNo issues