Skip to main content
Glama
klapom

drive-relay-mcp

by klapom
README.md
# drive-relay-mcp

Uploads a file that already exists on a persona container's local disk to
Microsoft OneDrive/Graph — without round-tripping the bytes through an MCP
tool-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** the
JSON tool-call argument. That has a hard ceiling (~60KB) on the size an LLM
can practically emit as output tokens — well below what a scanned receipt or
generated PDF needs. `ms-mcp`'s `save_attachment_to_drive` solves this for
Microsoft Graph **mail attachments** (fetched server-side, never touching an
LLM 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 a
persona container's local disk (e.g. a receipt photo converted to PDF).

This service closes that gap: given a small filename reference (not file
content), it reads the file directly off a shared host directory and uploads
it via a Graph chunked-upload session — the same mechanism `save_attachment_to_drive`
uses, 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 as `ms-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_name` is a **filename only** — no path separators, no subdirectories.
  The server resolves it strictly to `<shared-root>/<persona_key-from-JWT>/<file_name>`,
  where `persona_key` comes from the authenticated gateway JWT, never a
  caller-supplied field. See `src/utils/safe-path.ts` for the symlink-escape
  hardening (a persona container runs as root and could otherwise plant a
  symlink pointing at an arbitrary host file).
- **Auth**: ported from `ms-mcp`'s proven gateway-JWT + persona-scoping stack
  (`src/auth/`), trimmed to a single `uploads: boolean` capability. Runs in
  `enforce` mode from day one (no off/shadow rollout ramp — this is a new
  service with no legacy unauthenticated traffic to protect).
- **Cleanup**: deletes the source file on successful upload by default
  (`keep_source: true` to opt out), plus an hourly TTL sweep of anything left
  behind by a failed/abandoned upload.

## Setup

```bash
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 absent
from this file gets `403 Forbidden` regardless of JWT validity.

## Development

```bash
pnpm test:coverage
pnpm lint
pnpm typecheck
pnpm build
```