Skip to main content
Glama
README.md
# mcp-file-bridge

A ChatGPT/Codex App file bridge designed to move artifacts **without putting file bytes or Base64 in model context**.

The repository contains:

- an MCP/App server (`/mcp`)
- a host-native file input tool (`ingest_file`)
- a direct HTTP streaming fallback for ordinary networked clients
- a reusable Agent Skill under `skills/file-transfer/`
- Docker deployment files

## Why this exists

A remote MCP server cannot open `/mnt/data/foo.zip` from a ChatGPT/Codex sandbox. Passing Base64 through `tools/call.arguments` is also unsuitable for large files because the bytes would travel through model/tool JSON.

Compatible OpenAI hosts support an Apps-style file parameter bridge. `ingest_file` advertises:

```json
{
  "_meta": {
    "openai/fileParams": ["file"]
  }
}
```

The model-facing call can remain tiny:

```text
ingest_file(file=/mnt/data/build.apk, path="builds/build.apk")
```

At execution time a compatible host handles the local file out of band and the remote app receives a temporary provided-file object such as:

```json
{
  "download_url": "https://...temporary-signed-url...",
  "file_id": "file_...",
  "mime_type": "application/octet-stream",
  "file_name": "build.apk"
}
```

The bridge server then streams that URL directly to persistent storage while calculating SHA-256. The binary payload is never generated by the model and never Base64-encoded into ordinary MCP arguments.

## Architecture

```text
ChatGPT / Codex host
        |
        | local artifact / attachment
        | (bytes stay outside model context)
        v
host file-parameter bridge
        |
        | temporary provided-file object
        | {download_url,file_id,...}
        v
mcp-file-bridge App
        |
        | server-side HTTPS stream
        v
/data/files/<destination>
```

### Important size note

OpenAI Codex currently has an explicit 512 MiB limit in its `openai/fileParams` local-file upload bridge. That limit is host-side, not this server's storage limit.

`MAX_FILE_BYTES` defaults to 10 GiB so the server itself can accept larger transfers from future/other host file-transfer mechanisms. A 1 GiB file therefore requires a host path whose file-transfer layer supports that size; do not assume the legacy OpenAI file-parameter bridge will accept it.

MCP's newer file-transfer work (`x-mcp-file` / File Objects and Transfer) is intended to keep large file bytes out of JSON-RPC. This project keeps the storage/data plane compatible with that direction, but does not claim a host supports a particular large-file size until tested on that host.

## MCP tools

### `ingest_file` — preferred for ChatGPT/Codex

Inputs:

- `file`: host-native file parameter
- `path` (optional): destination under `/data/files`
- `sha256` (optional): expected SHA-256

The tool descriptor declares `_meta["openai/fileParams"] = ["file"]`.

The server validates the temporary download URL, streams it without buffering the full file, applies `MAX_FILE_BYTES`, prevents path traversal, and returns the stored size and SHA-256.

### `create_upload` — fallback for networked clients

Creates a short-lived `PUT /upload/<id>` session. This is useful for MCP clients that can access the public network themselves.

**Do not use this path from a sandbox with no public network egress.**

### Other tools

- `get_upload`
- `cancel_upload`
- `list_files`
- `stat_file`
- `delete_file`

## Plugin shape

As of the current ChatGPT plugin model, a plugin can package workflow Skills together with an App. This repository is structured for that split:

```text
skills/file-transfer/SKILL.md   workflow guidance
src/server.mjs                  MCP-backed App
```

The Plugin Directory / workspace publishing layer is managed by ChatGPT, not by a magic repository manifest. Deploy the App first, connect/test it as a custom App where your plan/workspace supports that, then package the App together with the included Skill when creating/submitting the plugin.

The skill explicitly instructs compatible agents to use `ingest_file` and never Base64 a binary artifact into model context.

## Deploy

```bash
cp .env.example .env
# Set a strong MCP_API_TOKEN and your public HTTPS base URL.
docker compose up -d --build
```

Expected endpoints:

```text
GET  /healthz
POST /mcp
PUT  /upload/<id>      # fallback data plane
```

For ChatGPT, the MCP endpoint must be reachable through HTTPS (or through a supported secure tunnel during development).

## Configuration

```dotenv
MCP_API_TOKEN=replace-with-a-long-random-secret
PUBLIC_BASE_URL=https://mcp-files.example.com
MAX_FILE_BYTES=10737418240
PROVIDED_FILE_HOST_SUFFIXES=openai.com,oaiusercontent.com,blob.core.windows.net
```

`PROVIDED_FILE_HOST_SUFFIXES` restricts hosts from which `ingest_file` is willing to retrieve temporary provided files. Redirect targets are revalidated.

## Security

- optional Bearer auth for `/mcp`
- destination path traversal prevention
- streaming transfer (no whole-file RAM buffering)
- SHA-256 calculation on every completed file
- optional expected SHA-256 validation
- maximum file size enforcement while streaming
- HTTPS-only host-provided file downloads
- host suffix allowlist and redirect revalidation
- destinations are never silently overwritten

## Test

```bash
npm test
```

Current storage tests cover path traversal, direct streaming with SHA-256, bad upload tokens, and host-provided streaming.

## Compatibility strategy

1. **Preferred:** host-native App file parameter (`openai/fileParams`)
2. **Future/when supported by host:** standard MCP out-of-band file transfer (`x-mcp-file` / File Objects and Transfer)
3. **Fallback:** direct HTTP `PUT` for clients with their own network egress
4. **Never:** Base64/chunking file bytes through model-generated MCP arguments

## License

MIT