voice-ingest
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@voice-ingestTranscribe the file meeting.m4a and show me the markdown transcript."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Voice Ingest
Turn long recordings into transcripts you can read, search, and build with.
A self-hosted transcription workspace for people, developers, and AI agents. Upload in your browser, automate from your terminal, or let an MCP client continue the same durable job.
English · 简体中文
Try the UI · Quickstart · Web workspace · MCP · Python SDK · CLI · Deployment

Actual running Web UI with built-in synthetic sample content. No private recordings or real ASR results are shown. Reproduce these screenshots.
Voice Ingest handles the work around cloud ASR: resumable uploads, asynchronous jobs, restart recovery, and consistent exports. It is built for personal use and trusted teams sharing an API-key-protected workspace.
Why Voice Ingest?
Long recordings, bounded memory. Upload in 16 MiB parts with four concurrent parts per file; validate media with ffprobe before recognition.
Jobs survive client disconnects. PostgreSQL persists progress and provider task IDs. Workers use leases and execution generations to recover work safely.
One workflow, five interfaces. Web, HTTP, CLI, an async Python SDK, and MCP address the same jobs. Upload in the browser and pass the job ID to an agent.
Results you can reuse. Keep raw provider output and normalized JSON; export TXT, Markdown, SRT, and VTT. Missing timestamps stay missing.
Explicit retry and billing behavior. Idempotency keys prevent duplicate requests. Uncertain provider submissions require attention instead of automatic resubmission.
Develop without cloud credentials. The mock provider exercises the workflow without recognition charges or provider network calls.
What you want to do | Start here |
Review an interview or meeting, search its text, export subtitles | |
Transcribe a folder and resume interrupted uploads | |
Add transcription to a Python application | |
Let an agent upload, check progress, and read selected time ranges |
Related MCP server: EarSay MCP
Try the workspace
For phase-1 speech synthesis, connect the backend, open Speech synthesis, select a configured
model/deployment and voice, then explicitly submit text. Completed files can be played and downloaded.
Aliyun's first preset is qwen-audio-3.0-tts-flash / longanhuan_v3.6 / MP3; mock deployments expose
mock-tts / mock-voice / WAV. Mock audio is synthetic silence, not a quality demonstration.
Use voice-ingest syntheses --help for CLI operations, or AsyncVoiceClient.synthesize() followed by
get_synthesis(), synthesis_result() and synthesis_audio() in Python. Remote/local MCP include
submit_synthesis, list_voices and synthesis lifecycle/result tools. All application endpoints,
including remote MCP, health, metrics and OpenAPI, are under /v1.
Existing installations must run incremental migration 0002; do not recreate the database.
Development design and acceptance describe setup, tests,
deployment selection, recovery and the separate paid acceptance gate.
Explore the interface before setting up infrastructure. You need Node.js 24 LTS (minimum 22.12):
git clone https://github.com/yuehong136/voice-ingest.git
cd voice-ingest/web
npm ci
npm run devOpen the local workspace and click Explore sample transcript. Search for timestamps, switch export formats, or use 中文 in the sidebar. The sample needs no backend, API key, or cloud account; it uses illustrative text without source audio. To transcribe your own recordings, continue with the backend quickstart below.
Quickstart
From a local checkout, use Docker Compose for the backend and Python 3.12 + uv for CLI/SDK development. No local GPU is required.
Run these commands from the repository root (voice-ingest/). If the frontend is running in another terminal, leave it running and open a new terminal at the root.
1. Start the backend
cp .env.example .env
# Edit .env: replace API/database/S3 credentials.
docker compose --env-file .env -f deploy/compose.yaml up -d --build
curl --fail http://127.0.0.1:18080/v1/health/readyThis starts a separate API, worker, PostgreSQL, and MinIO stack. The default local ports are 18080 (API) and 19000 (S3); 80/443 are unused.
Object storage uses PGSTY Silo, a community-maintained MinIO fork, pinned to
RELEASE.2026-09-03T13-18-01Z. It requires no AIStor activation file.
See the deployment guide for release provenance and acceptance status.
The default provider is mock. Its output is labeled [MOCK] and does not transcribe the contents of your recording. The health request should return HTTP 200 once initialization completes.
2. Submit a recording
uv sync --all-extras --frozen
export VOICE_URL=http://localhost:18080
export VOICE_API_KEY='the-service-key-from-your-dotenv'
uv run voice-ingest transcribe meeting.m4a --wait --format markdownReplace meeting.m4a with your audio file. --wait polls the job and prints Markdown when it succeeds. Omit it to return the job ID immediately; interrupting a wait does not cancel server-side work.
Validation status: Phase-1 checks and real Aliyun STT/TTS acceptance passed on 2026-09-07, including worker restart recovery, cross-interface access, browser playback/download and user listening confirmation. See the current handoff for evidence and remaining quality/deployment limits. The historical acceptance record is preserved separately. Isolated acceptance does not establish production deployment readiness.
Providers
Provider / model | Available behavior | Verification |
Mock | Complete upload/job/export workflow; synthetic text | Offline and PostgreSQL/MinIO integration tests |
Aliyun | Default whole-file asynchronous ASR | Historical 87-minute recording; phase-1 short-sample restart/export acceptance passed |
Aliyun | Complete-text synthesis, | Real synthesis, browser playback/download and user listening confirmation passed |
Aliyun | Explicit model selection | Adapter contract tests; no live acceptance yet |
The current STT model checks allow files up to 12 hours / 2 GB. Speaker diarization is rejected above two hours. Language hints, diarization, and context support depend on the model; inspect voice-ingest models or /v1/models for capabilities. Files are sent whole, without automatic compression or VAD splitting.
To enable Aliyun, edit .env and recreate the API and worker:
VOICE_PROVIDER=aliyun
VOICE_ALIYUN_REGION=beijing
VOICE_ALIYUN_API_KEY=YOUR_REGIONAL_DASHSCOPE_KEY
VOICE_S3_PUBLIC_ENDPOINT=https://files.example.comIn the default signed_url mode, the file endpoint must actually route to your S3 service and be reachable by clients and Aliyun. localhost cannot serve cloud recognition. HTTP and HTTPS origins are supported; use HTTPS for public deployments. Internal storage access and public signing endpoints are configured separately.
For local real-ASR evaluation without public S3, set VOICE_ALIYUN_SOURCE_MODE=temporary_upload and restart the API and worker. The worker uses Aliyun’s official temporary file service; this mode is for local evaluation only. Production keeps the default signed_url mode. See local browser setup.
Billing: This adapter uses regular DashScope ASR. Token Plan / Coding Plan is not integrated, and there is no automatic fallback between billing channels. VOICE_API_KEY protects your backend; VOICE_ALIYUN_API_KEY authenticates the backend to Aliyun. See deployment and credential configuration.
Web workspace
The optional React frontend brings recordings and transcripts into one English/Chinese workspace. Start it with the preview commands above, then connect to your running backend with its service key.
Upload and review. Upload a file, then choose recognition settings. Uploading stops at a review step; Start transcription submits the job.
Follow and read. Filter jobs by status, review speaker labels and original timestamps, and search loaded transcript text.
Export or continue with an agent. Download Markdown, TXT, JSON, SRT, or VTT. Copy the job ID from a connected workspace for an MCP or SDK follow-up.
The same responsive workspace, scrolled to the reader. Sample text is synthetic; no source audio is included.
Running both services locally: Vite forwards /api to http://127.0.0.1:18080. Browser uploads also need a reachable S3 endpoint and storage CORS. For deployment, the optional web container uses port 18081. See the web guide for configuration, the upload review flow, deployment, and tests.
CLI
uv run voice-ingest transcribe meeting.m4a
uv run voice-ingest batch ./recordings --recursive --resume
uv run voice-ingest --json jobs list
uv run voice-ingest jobs get JOB_ID
uv run voice-ingest jobs cancel JOB_ID
uv run voice-ingest export JOB_ID --format srt --output meeting.srtUse --model fun-asr on transcribe or batch to select the other Aliyun model.
Single-file transcription resumes by default and reuses the local job record for the same file and options. Use --no-resume for a fresh submission. Batch resume is explicit; one file's failure does not stop the remaining files.
--json is a global option and goes before the subcommand. Progress goes to stderr. Resume state lives under ~/.local/state/voice-ingest/; credentials are not stored there.
Exit code | Meaning |
| Success |
| Network or service error |
| Invalid arguments or local file |
| Job failure or partial batch failure |
| User interruption |
Python SDK
For SDK-only use, install from the checkout with uv pip install . in an activated environment. The default package depends on HTTPX and Pydantic, without database, HTTP server, or MCP dependencies. Optional extras are cli, server, and mcp.
import asyncio
import os
from voice_ingest import AsyncVoiceClient, TranscriptionOptions
async def main():
async with AsyncVoiceClient("http://localhost:18080", os.environ["VOICE_API_KEY"]) as client:
asset = await client.upload("meeting.m4a")
job = await client.submit(
asset.id,
options=TranscriptionOptions(language_hints=["zh"], diarization=True),
idempotency_key="meeting-001-v1",
)
job = await client.wait(job.id)
if job.state == "succeeded":
transcript = await client.result(job.id)
print(transcript.text)
else:
print(job.model_dump_json())
asyncio.run(main())Reuse an idempotency key when retrying the same submission; use a new key for changed parameters. A job in needs_attention may already have been accepted by the provider. A cancellation with remote_may_run=true means the provider may still execute and charge for recognition.
MCP
Give your agent a recording and keep a job ID you can return to. The agent can poll progress, read a time range, and request exports; the backend keeps working between tool calls.
flowchart LR
File[Local recording] --> Upload[upload_local_audio]
Upload --> Asset[asset_id]
Asset --> Submit[submit_transcription]
Submit --> Job[job_id]
Job --> Read[read_transcript]
Job --> Export[export_transcript]The local bridge supplies upload_local_audio. Poll get_transcription until the job succeeds before reading or exporting; a remote-only client starts with an existing asset or job.
Recipe: cloud backend, local recording
Transcribe the meeting on my laptop. Prepare Markdown and SRT exports, and keep the job ID so I can return later.
Run a lightweight local MCP bridge to upload files to your cloud backend, or upload in the web workspace and let a remote agent continue with the existing job. The backend owns the long-running work.
Follow the recipe → — client configuration, upload flow, example tool calls, and authenticated downloads. Includes the boundaries for cloud-hosted chat attachments.
Connect a remote client
Use http://localhost:18080/v1/mcp/ with Authorization: Bearer YOUR_VOICE_INGEST_API_KEY. Configure the URL and header using your client's HTTP MCP settings.
Task | Tools |
Discover models |
|
Manage jobs |
|
Read and export |
|
Submissions return durable business job IDs immediately. MCP Tasks support and a persistent MCP connection are not required. Transcript reads support pagination and time ranges to keep long recordings out of a single tool response.
submit_transcription only requires asset_id. MCP automatically deduplicates the same asset and
normalized options across reconnections, including completed jobs. An optional explicit
idempotency_key supports intentional new recognition; reuse it for retries of that request.
Use retry_transcription to retry a failed job. HTTP/SDK idempotency contracts are unchanged.
Upload local files from an agent
After uv sync --all-extras --frozen, configure a local stdio bridge in a client that supports mcpServers configuration:
{
"mcpServers": {
"voice-ingest": {
"command": "uv",
"args": [
"run", "--directory", "/absolute/path/to/voice-ingest",
"voice-ingest-mcp", "--url", "http://localhost:18080",
"--allow-dir", "/absolute/path/to/recordings"
],
"env": {"VOICE_API_KEY": "YOUR_VOICE_INGEST_API_KEY"}
}
}
}Replace both absolute paths and the service key; uv must be on the client's PATH. The backend must already be running. Other clients may use a different configuration schema.
The bridge adds upload_local_audio and checks resolved paths against explicitly allowed directories, including symlink boundaries. It uploads the file to the backend and returns an asset ID for a subsequent transcription submission. Remote MCP does not accept paths on your computer.
HTTP API
All business routes use /v1 and Bearer authentication. Creation of a transcription requires Idempotency-Key and returns 202 Accepted.
Resource | Purpose |
| Create, inspect, sign parts, complete, or abort an upload |
| Inspect or delete source audio |
| Query model capabilities |
| Submit jobs and list them with cursor pagination |
| Inspect a job or delete its results |
| Cancel or retry explicitly |
| Read normalized results and exports |
Download the OpenAPI schema for exact methods and request bodies:
curl -H "Authorization: Bearer $VOICE_API_KEY" \
"$VOICE_URL/v1/openapi.json" -o openapi.json/v1/docs also requires authentication. /v1/health/live and /v1/health/ready are public process/readiness checks; /v1/metrics requires the service key.
How it works
flowchart LR
CLI[CLI / Python SDK] --> API[HTTP API]
Agent[Agent] --> MCP[Remote MCP]
Agent --> Bridge[Local MCP bridge]
Bridge --> API
MCP --> Service[Transcription service]
API --> Service
CLI -->|Presigned upload| S3[(Private S3)]
Service --> PG[(PostgreSQL jobs)]
PG --> Worker[Worker]
Worker --> Provider[Aliyun / Mock]
Worker --> S3
Provider -->|Signed audio URL| S3Uploading creates a stable asset_id; transcription creates a separate job_id. PostgreSQL owns job state, while private S3 stores audio, raw results, and exports. HTTP and remote MCP share business use cases; CLI and the local MCP bridge reuse the SDK.
Workers claim due jobs with SKIP LOCKED, run network operations outside transactions, and fence writes by lease ownership and generation. After a restart, a saved provider task ID is polled again. A lost submission response becomes needs_attention. Success requires checking file-level status, downloading the result, and normalizing it.
Development
uv sync --all-extras --frozen
make check
uv buildmake check runs Ruff, format checks, Pyright, and offline tests. Native backend development also requires ffprobe, PostgreSQL, and S3; run migrations before starting the API and worker. See the deployment guide.
For real PostgreSQL/MinIO tests, configure dedicated test resources and run make integration. These tests use the mock ASR provider. Cloud recognition is a separate, explicitly billable acceptance step.
Read AGENTS.md before coding. Modules are organized by capability (transcription, media, providers, jobs, exports) with thin interfaces and shared runtime wiring. Keep changes within these boundaries, add behavior tests for changed contracts or recovery semantics, and keep both READMEs in sync.
Scope and documentation
The v1 speech framework implements file STT and complete-file TTS for a shared, trusted workspace. Phase 1 includes Aliyun adapters and explicit mock deployments. Realtime sessions, voice cloning, automatic text splitting, other vendors, local model hosting, multi-tenancy and knowledge-base ingestion remain out of scope. See the phase record for current acceptance evidence and quality/deployment limits.
Document | Contents |
Browser setup, upload review, storage CORS, and frontend checks | |
A complete MCP workflow with client configuration and exports | |
Configuration, private storage, HTTP/HTTPS, credentials, operations | |
Module boundaries, persistence, recovery, and tradeoffs | |
Durable jobs and capability-oriented organization | |
Tested behavior and remaining validation work (Chinese) | |
Repository rules for contributors and coding agents |
License
MIT. You may use, modify, distribute, and use this project commercially, provided you retain the copyright and license notice. Third-party dependencies and cloud services remain subject to their own licenses and terms.
This server cannot be deployed
Maintenance
Related MCP Connectors
OCR, transcription, file extraction, and image generation for AI agents via MCP.
Transcribe audio & video to text for AI agents: 100+ languages, speaker labels, webhooks.
Audio for your agent: transcribe, speak, translate, summarise, plus sound effects and music.
Transcribe audio and video with Speechmatics speech-to-text from Claude and any MCP client.
Related MCP Servers
- AlicenseAqualityFmaintenanceEnables AI assistants to transcribe audio files from URLs or local paths using AssemblyAI's services, with support for speaker diarization, language detection, and asynchronous job management through a standardized MCP interface.45 npm2MIT
- AlicenseAqualityCmaintenanceProvides voice transcription control and polling for MCP-compatible agents, enabling start/stop/pause/resume and retrieval of new text via tools.8MIT
- AlicenseAqualityBmaintenanceEnables automated audio restoration, transcription, and speaker diarization via MCP tools for queuing files, monitoring progress, and retrieving speaker-labeled transcripts.9MIT
- FlicenseNot gradedqualityCmaintenanceEnables MCP-aware agents to queue and run faster-whisper transcription jobs on a shared GPU, with tools to submit audio, poll job status, cancel jobs, and inspect queue status.-