drive-relay-mcp
Uploads a file that already exists on a persona container's local disk toMicrosoft OneDrive/Graph — without round-tripping the bytes through an MCPtool-call argument.
Why this exists
Every other Graph upload path in this fleet (ms-mcp's upload_file /upload_large_file) requires the full file content as base64 inside theJSON tool-call argument. That has a hard ceiling (~60KB) on the size an LLMcan practically emit as output tokens — well below what a scanned receipt orgenerated PDF needs. ms-mcp's save_attachment_to_drive solves this forMicrosoft Graph mail attachments (fetched server-side, never touching anLLM argument) — but that only works when the source is a Graph attachment(message_id + attachment_id). It cannot help a file that only exists on apersona container's local disk (e.g. a receipt photo converted to PDF).
This service closes that gap: given a small filename reference (not filecontent), it reads the file directly off a shared host directory and uploadsit via a Graph chunked-upload session — the same mechanism save_attachment_to_driveuses, just sourced from local disk instead of a Graph attachment fetch.
Architecture
- Shared host directory:
/home/admin/hermes-shared-uploads/<persona_key>/is bind-mounted into each persona's Docker container at/data/shared-uploads.This service (running natively on the same host asms-mcp, via systemd)reads the same host path directly — no extra Docker networking needed. - Tool surface: one tool,
upload_local_file(file_name, folder_path, target_file_name?, confirm, idempotency_key?, keep_source?).file_nameis a filename only — no path separators, no subdirectories.The server resolves it strictly to<shared-root>/<persona_key-from-JWT>/<file_name>,wherepersona_keycomes from the authenticated gateway JWT, never acaller-supplied field. Seesrc/utils/safe-path.tsfor the symlink-escapehardening (a persona container runs as root and could otherwise plant asymlink pointing at an arbitrary host file). - Auth: ported from
ms-mcp's proven gateway-JWT + persona-scoping stack(src/auth/), trimmed to a singleuploads: booleancapability. Runs inenforcemode from day one (no off/shadow rollout ramp — this is a newservice with no legacy unauthenticated traffic to protect). - Cleanup: deletes the source file on successful upload by default(
keep_source: trueto opt out), plus an hourly TTL sweep of anything leftbehind by a failed/abandoned upload.
Setup
pnpm install
cp .env.example .env # fill in AZURE_TENANT_ID, AZURE_CLIENT_ID, GATEWAY_ISSUER, AUTH_TOKEN, BOOT_AUTH_TOKEN
pnpm build
pnpm auth # one-time interactive device-code sign-in (per deployment identity)
pnpm start # or: systemd unit, see systemd/drive-relay-mcp.service
config/persona-scopes.json is the fail-closed allowlist — a persona absentfrom this file gets 403 Forbidden regardless of JWT validity.
Development
pnpm test:coverage
pnpm lint
pnpm typecheck
pnpm build