Skip to main content
Glama
guvno
by guvno
README.md
<h1 align="center">transdex-mcp</h1>
<h3 align="center">Whisper-first transcription and translation tools over MCP</h3>

<p align="center">
  Transdex exposes local stdio and remote Streamable HTTP transports from one
  codebase. MCP clients can plan safe file workflows, run whisper.cpp
  transcription, preserve subtitle timing, and retrieve bounded result files.
</p>

<p align="center">
  <img alt="Node.js 20 or newer" src="https://img.shields.io/badge/Node.js-%3E%3D20-339933">
  <img alt="MCP stdio and Streamable HTTP" src="https://img.shields.io/badge/MCP-stdio_%2B_HTTP-5A67D8">
  <img alt="Project status pre-release" src="https://img.shields.io/badge/status-pre--release-orange">
</p>

## Contents

- [What Is This?](#what-is-this)
- [Client Modes](#client-modes)
- [Architecture](#architecture)
- [Remote Tool Contract](#remote-tool-contract)
- [Quick Start](#quick-start)
- [Connect an MCP Client](#connect-an-mcp-client)
- [Configuration](#configuration)
- [Security and Privacy](#security-and-privacy)
- [Local Workflow](#local-workflow)
- [Project Structure](#project-structure)
- [Testing](#testing)
- [Troubleshooting](#troubleshooting)
- [Roadmap](#roadmap)
- [License](#license)

## What Is This?

transdex-mcp is a standalone MCP codebase for transcription and translation
workflows. It has its own Git history, package identity, configuration path,
model cache, server entry points, and deployment policy.

The default transcription profile is:

| Layer | Default |
| --- | --- |
| ASR provider | whisper.cpp |
| Model | large-v3-turbo-q5_0 |
| Language | Automatic detection |
| Output | Transcript plus optional SRT |
| Execution | Bounded asynchronous queue |
| Local transport | MCP over stdio |
| Remote transport | MCP Streamable HTTP |

This repository is a pre-release implementation. It is not ready for public
directory submission or untrusted multi-instance deployment.

## Client Modes

MCP is the product boundary. Client-specific behavior is kept at the connection
edge.

| Client | Recommended connection | Primary input style |
| --- | --- | --- |
| Codex CLI, app, or IDE | Local stdio for workspace files; Streamable HTTP for a deployed service | MCP roots and local paths, or remote tool arguments |
| ChatGPT | Streamable HTTP | Top-level remote file references supplied by the host |
| Other MCP clients | stdio or Streamable HTTP | Depends on client capabilities |

Codex and ChatGPT both support MCP servers. The repository does not require a
custom widget; clients can call the tools directly. See OpenAI's
[MCP documentation](https://learn.chatgpt.com/docs/extend/mcp) for current
client configuration behavior.

## Architecture

~~~text
MCP client
  |
  +-- Local stdio
  |     +-- Workspace roots
  |     +-- Plan / preview / confirm
  |     +-- Translation and transcription jobs
  |
  +-- Remote Streamable HTTP
        +-- Validate and stage a remote media reference
        +-- Enforce byte, duration, queue, and session limits
        +-- Run whisper.cpp asynchronously
        +-- Return opaque signed result links
~~~

Both transports use the same validated `JobSpec`, safe sidecar writer,
transcription providers, subtitle preservation rules, and cancellation path.

## Remote Tool Contract

| Tool | Effect |
| --- | --- |
| start_transcription | Stages one remote file and enqueues Whisper inference |
| get_transcription_job | Reads status and bounded progress |
| get_transcription_result | Promotes completed outputs and returns resource links |
| cancel_transcription_job | Stops queued or running inference |

Compatible OpenAI hosts can inject a top-level file parameter declared through
`openai/fileParams`:

~~~json
{
  "download_url": "https://temporary.example/media",
  "file_id": "file_example",
  "mime_type": "video/mp4",
  "file_name": "recording.mp4"
}
~~~

The server downloads the bytes during `start_transcription`; it never stores a
temporary URL for a background worker. Nested file parameters are intentionally
unsupported.

## Quick Start

### 1. Install Node dependencies

~~~bash
npm ci
~~~

Node.js 20 or newer is required.

### 2. Build whisper.cpp

~~~bash
git clone https://github.com/ggml-org/whisper.cpp.git "$HOME/.local/share/whisper.cpp"
cmake -S "$HOME/.local/share/whisper.cpp" \
  -B "$HOME/.local/share/whisper.cpp/build" \
  -DCMAKE_BUILD_TYPE=Release
cmake --build "$HOME/.local/share/whisper.cpp/build" -j 4
~~~

Pin a reviewed whisper.cpp revision for deployment rather than building an
unpinned branch.

### 3. Provision models

~~~bash
cd "$HOME/.local/share/whisper.cpp"
bash ./models/download-ggml-model.sh large-v3-turbo-q5_0
bash ./models/download-vad-model.sh silero-v6.2.0
~~~

Model weights and the whisper.cpp binary are not bundled. Remote deployments
should provision them while building the service image and keep request-time
auto-download disabled.

### 4. Configure the runtime

~~~bash
export TRANSDEX_WHISPER_CPP_BIN="$HOME/.local/share/whisper.cpp/build/bin/whisper-cli"
export TRANSDEX_WHISPER_CPP_MODEL_DIR="$HOME/.local/share/whisper.cpp/models"
export TRANSDEX_WHISPER_CPP_MODEL=large-v3-turbo-q5_0
export TRANSDEX_WHISPER_CPP_VAD=1
export TRANSDEX_WHISPER_CPP_VAD_MODEL="$HOME/.local/share/whisper.cpp/models/ggml-silero-v6.2.0.bin"
export TRANSDEX_WHISPER_CPP_AUTO_DOWNLOAD=0
~~~

Use [.env.example](.env.example) as the remote deployment checklist. The
application does not load dotenv files automatically.

### 5. Start a transport

Local stdio:

~~~bash
npm run mcp
~~~

Local HTTP development server:

~~~bash
npm start
~~~

The default development endpoints are:

~~~text
http://127.0.0.1:8787/mcp
http://127.0.0.1:8787/healthz
~~~

## Connect an MCP Client

### Codex with local stdio

Use an absolute checkout path:

~~~bash
codex mcp add transdex -- node /absolute/path/to/transdex-mcp/src/mcp/main.js
codex mcp list
~~~

The checked-in [.mcp.json](.mcp.json) contains the equivalent repository-local
definition for clients that read project MCP configuration.

### Codex with Streamable HTTP

Add the deployed endpoint to `~/.codex/config.toml`:

~~~toml
[mcp_servers.transdex]
url = "https://transdex.example.com/mcp"
bearer_token_env_var = "TRANSDEX_REMOTE_TOKEN"
tool_timeout_sec = 1800
~~~

### Other remote MCP clients

Expose `/mcp` through HTTPS, set `TRANSDEX_PUBLIC_BASE_URL` and
`TRANSDEX_HTTP_ALLOWED_HOSTS`, and configure one authentication mode. A static
bearer token is only appropriate for one trusted tenant. Multi-user deployments
must validate OAuth at a trusted reverse proxy and forward a stable principal
header after stripping client-supplied copies.

## Configuration

### HTTP and session settings

| Variable | Default | Purpose |
| --- | --- | --- |
| TRANSDEX_HTTP_HOST | 127.0.0.1 | Bind address |
| TRANSDEX_HTTP_PORT | 8787 | HTTP port |
| TRANSDEX_PUBLIC_BASE_URL | Local URL after listen | Origin used for artifact links |
| TRANSDEX_HTTP_ALLOWED_HOSTS | Empty | Required accepted Host values for a public endpoint |
| TRANSDEX_HTTP_BEARER_TOKEN | Empty | Static single-tenant staging token |
| TRANSDEX_SINGLE_TENANT | 0 | Required acknowledgement for static bearer mode |
| TRANSDEX_TRUSTED_AUTH_PROXY | 0 | Enable trusted proxy principal binding |
| TRANSDEX_AUTH_PRINCIPAL_HEADER | Empty | Stable principal header set by the trusted proxy |
| TRANSDEX_ALLOW_UNAUTHENTICATED | 0 | Unsafe isolated-development override |
| TRANSDEX_PUBLIC_WORKSPACE_ROOT | Random private local root; required remotely | Real service-owned root with mode 0700 |
| TRANSDEX_MAX_UPLOAD_BYTES | 536870912 | Maximum staged upload size |
| TRANSDEX_MAX_SESSION_BYTES | 1073741824 | Aggregate staged bytes per session |
| TRANSDEX_MAX_SESSION_STAGING_QUEUE | 2 | Pending staging requests per session |
| TRANSDEX_FILE_DOWNLOAD_TIMEOUT_MS | 60000 | Whole remote download deadline |
| TRANSDEX_MAX_MEDIA_DURATION_SECONDS | 14400 | Maximum inspected media duration |
| TRANSDEX_MAX_DECODED_AUDIO_BYTES | Derived from duration | Maximum 16 kHz mono PCM expansion |
| TRANSDEX_MAX_ACTIVE_JOBS | 1 | Concurrent Whisper jobs per session |
| TRANSDEX_MAX_GLOBAL_JOBS | 1 | Concurrent Whisper jobs for the service |
| TRANSDEX_MAX_GLOBAL_QUEUED_JOBS | 8 | Jobs waiting for a global Whisper slot |
| TRANSDEX_MAX_GLOBAL_STAGING | 2 | Concurrent remote downloads |
| TRANSDEX_MAX_GLOBAL_STAGING_QUEUE | 8 | Downloads waiting for a staging slot |
| TRANSDEX_MAX_SESSIONS | 8 | Concurrent MCP session cap |
| TRANSDEX_MAX_SESSIONS_PER_PRINCIPAL | 2 | Session cap for one trusted principal |
| TRANSDEX_DOWNLOAD_TTL_MS | 900000 | Signed artifact URL lifetime |
| TRANSDEX_DOWNLOAD_SECRET | Random per process | HMAC secret; at least 32 bytes when configured |
| TRANSDEX_FILE_HOSTS | Empty | Required exact or wildcard remote file host allowlist |
| TRANSDEX_ALLOW_ANY_PUBLIC_FILE_HOST | 0 | Unsafe host-allowlist override |

### Whisper settings

| Variable | Default | Purpose |
| --- | --- | --- |
| TRANSDEX_WHISPER_CPP_MODEL | large-v3-turbo-q5_0 | Default model |
| TRANSDEX_PUBLIC_WHISPER_MODELS | large-v3-turbo-q5_0 | Models exposed to remote callers |
| TRANSDEX_WHISPER_CPP_MODEL_DIR | User cache | Pre-provisioned GGML directory |
| TRANSDEX_WHISPER_CPP_BIN | whisper-cli on PATH | Binary path |
| TRANSDEX_WHISPER_CPP_LANGUAGE | auto | Whisper language code |
| TRANSDEX_WHISPER_CPP_THREADS | Up to 4 | Worker threads |
| TRANSDEX_WHISPER_CPP_PROCESSORS | 1 | Parallel processors |
| TRANSDEX_WHISPER_CPP_ACCELERATION | auto | auto, cpu, or gpu |
| TRANSDEX_WHISPER_CPP_DEVICE | Empty | Optional GPU device number |
| TRANSDEX_WHISPER_CPP_VAD | Off unless model is set | Enable Silero VAD |
| TRANSDEX_WHISPER_CPP_VAD_MODEL | Empty | VAD GGML path |
| TRANSDEX_WHISPER_CPP_TIMEOUT_MS | 1800000 | Inference timeout |
| TRANSDEX_WHISPER_CPP_AUTO_DOWNLOAD | 0 remotely | Model auto-download switch |

Remote callers can select only models in `TRANSDEX_PUBLIC_WHISPER_MODELS` and
cannot provide arbitrary local paths. Media ceilings and the auto-download
switch are parsed strictly at startup.

## Security and Privacy

Implemented safeguards include:

- HTTPS-only remote media URLs with hostname allowlisting
- DNS rejection for private, loopback, link-local, and reserved destinations
- DNS-pinned HTTPS connections and redirect revalidation
- Whole-download deadlines, streamed byte limits, and atomic staging
- ffprobe duration checks plus ffmpeg decoded-PCM limits
- Local-only ffmpeg/ffprobe protocol and demuxer allowlists
- Minimal child-process environments that omit application secrets
- Private per-session workspaces with ownership and symlink checks
- Bounded per-session and service-wide queues
- Per-principal session and artifact quotas in trusted-proxy mode
- Stable public errors without server-local paths
- Opaque signed artifact links
- Process-group cancellation and graceful shutdown

Current limitations:

- Jobs and artifact metadata are in memory and local to one process.
- Static bearer authentication is single-tenant, not public user OAuth.
- Trusted principal mode depends on a correctly configured reverse proxy.
- The reverse proxy must enforce request and bandwidth rate limits.
- ffmpeg, ffprobe, and whisper.cpp still require a low-privilege, no-network
  worker boundary with OS or container resource limits.
- Remote translation is not yet adapted to the direct remote-file flow.

Do not expose this pre-release directly to untrusted users or deploy multiple
replicas until worker isolation, edge rate limiting, durable shared storage,
and authenticated tenant ownership are in place.

## Local Workflow

The local stdio server retains the plan-preview-confirm workflow for text,
Markdown, subtitle, directory, and media jobs.

~~~bash
npm run mcp
~~~

The compatibility CLI remains available for direct terminal use:

~~~bash
npm run cli -- --help
~~~

## Project Structure

~~~text
src/
|-- mcp-http/             # Remote Streamable HTTP tools and artifact service
|-- mcp/                  # Local stdio planning and job workflow
|-- providers/            # whisper.cpp and optional cloud providers
|-- jobs/                 # JobSpec runner and safe artifact writing
|-- media/                # ffmpeg preparation and segment merging
|-- safety/               # Path and exclusive-write guards
+-- workers/              # Translation and subtitle preservation

test/
|-- mcp-http.test.js
|-- mcp-http-server.test.js
|-- remote-file-staging.test.js
|-- mcp.test.js
+-- whisper-public-profile.test.js
~~~

## Testing

Run the complete test suite:

~~~bash
npm test
~~~

The suite mocks `whisper-cli`. Before deployment, run a real smoke test with
the exact pinned whisper.cpp binary, model, VAD file, ffmpeg version, and target
server architecture.

## Troubleshooting

### whisper.cpp model is missing

Pre-provision the selected model in `TRANSDEX_WHISPER_CPP_MODEL_DIR`. Keep
request-time auto-download disabled for remote deployments.

### VAD is enabled but no model is configured

Set `TRANSDEX_WHISPER_CPP_VAD_MODEL` to the downloaded Silero model or disable
VAD.

### A remote file is rejected

Check that `download_url` uses HTTPS, resolves only to public addresses, has a
supported media extension, and matches `TRANSDEX_FILE_HOSTS`.

### An MCP client cannot connect

For stdio, verify the absolute Node and repository paths. For HTTP, verify the
HTTPS `/mcp` URL, reverse proxy streaming, Host allowlist, and authentication
policy. In Codex, use `/mcp` or `codex mcp list` to inspect configured servers.

### A job remains queued

Inspect both the per-session queue and the service-wide gate. Increase global
concurrency only after benchmarking CPU, memory, disk expansion, and
cancellation behavior.

## Roadmap

- Adapt host-assisted subtitle translation to the remote file flow
- Add an optional MCP progress and download widget
- Replace in-memory jobs with a durable queue
- Move artifacts to tenant-scoped object storage
- Add native OAuth metadata and durable per-user accounting
- Add model and binary digest pinning
- Add a real whisper.cpp integration smoke test
- Add container and deployment manifests
- Complete privacy-policy and directory-submission review

## License

This repository currently has no chosen redistribution license. `package.json`
is marked private and UNLICENSED. Do not publish the npm package or redistribute
the repository until the owner chooses a license and reviews third-party
notices.

whisper.cpp, Whisper model weights, ffmpeg, and optional cloud providers have
their own licenses and distribution requirements.