Guide · AWS Transcribe · Custom Vocabulary

AWS Transcribe Custom Vocabulary in MCP Servers — CreateVocabularyCommand, Vocabulary Filters, TSV Format

AWS Transcribe custom vocabularies let you teach the speech model domain-specific words — product names, technical jargon, rare proper nouns — that it would otherwise mishear or silently drop. A vocabulary is a list of words paired with pronunciation hints in IPA or phonetic spellings (SoundsLike), and an optional DisplayAs override that controls how the word appears in the final transcript. Separately, vocabulary filters mask, remove, or tag specific words in the output transcript — useful for profanity filtering or redacting branded competitor names. Both are created once, compile to a READY state (minutes), and are referenced by name in subsequent transcription jobs or streaming sessions. This guide covers the TSV file format, the vocabulary compile-wait pattern, filter methods, and combining both in an MCP tool.

TL;DR

Create a custom vocabulary with CreateVocabularyCommand using either a simple Phrases array for single-word entries or a VocabularyFileUri pointing to an S3 TSV file with four columns: Phrase, IPA (IPA pronunciation, can be empty), SoundsLike (phonetic spelling, can be empty), DisplayAs (output form, can be empty). Poll GetVocabularyCommand until VocabularyState === 'READY' before using it. Create a vocabulary filter with CreateVocabularyFilterCommand (a Words array or an S3 word-list file) and choose a method: mask (replace with ***), remove (omit the word), or tag (keep but mark VocabularyFilterMatch: true on the item). Reference both by name in Settings.VocabularyName and Settings.VocabularyFilterName when starting a transcription job.

Custom vocabulary — simple Phrases array

import {
  TranscribeClient,
  CreateVocabularyCommand,
  GetVocabularyCommand,
  VocabularyState,
  UpdateVocabularyCommand,
  DeleteVocabularyCommand,
} from '@aws-sdk/client-transcribe';

const transcribe = new TranscribeClient({ region: 'us-east-1' });

// Simple vocabulary — word list only, no pronunciation hints
// Good for product names and acronyms that differ only in capitalisation/spacing
await transcribe.send(new CreateVocabularyCommand({
  VocabularyName: 'mcp-tools-vocab',
  LanguageCode: 'en-US',
  Phrases: [
    'AliveMCP',
    'MCP',         // forces capitalised output even when spoken as a word
    'WebSocket',
    'OAuth',
    'k8s',         // Kubernetes shorthand
    'kubectl',
    'Zod',
    'Prisma',
    'Drizzle',
  ],
}));

// Wait for READY state before using in a job (usually 1–5 minutes)
await waitForVocabularyReady('mcp-tools-vocab');

async function waitForVocabularyReady(name: string, maxAttempts = 30): Promise<void> {
  for (let i = 0; i < maxAttempts; i++) {
    await new Promise(r => setTimeout(r, 10_000)); // 10-second poll
    const { VocabularyState: state } = await transcribe.send(
      new GetVocabularyCommand({ VocabularyName: name })
    );
    if (state === VocabularyState.READY) return;
    if (state === VocabularyState.FAILED) {
      const { FailureReason } = await transcribe.send(
        new GetVocabularyCommand({ VocabularyName: name })
      );
      throw new Error(`Vocabulary ${name} failed: ${FailureReason}`);
    }
  }
  throw new Error(`Vocabulary ${name} did not reach READY state within ${maxAttempts * 10}s`);
}

The Phrases array accepts single words and hyphenated multi-word phrases. Each entry is case-sensitive in the vocabulary file but Transcribe applies it case-insensitively when matching audio — it then outputs the word in the casing you specified. Limit the vocabulary to words the model genuinely fails on; adding thousands of common English words worsens accuracy rather than improving it.

TSV file format with pronunciation hints

For words with unusual pronunciation, upload a TSV file to S3 and pass the URI as VocabularyFileUri. The file must have a header row and exactly four tab-separated columns:

# vocabulary.tsv — tab-separated, UTF-8, header row required
Phrase	IPA	SoundsLike	DisplayAs
AliveMCP		uh-lyve-em-see-pee	AliveMCP
Kubernetes	kju.bɚˈneɪ.tɪs		Kubernetes
kubectl		kube-cuttle	kubectl
OAuth		oh-auth	OAuth
gRPC		gee-are-pee-see	gRPC
Zod		zahd	Zod
tsconfig		tee-es-config	tsconfig
Prisma		priz-muh	Prisma

Column rules:

// Upload vocabulary TSV to S3 and create vocabulary from it
import { PutObjectCommand } from '@aws-sdk/client-s3';

const tsvContent = [
  'Phrase\tIPA\tSoundsLike\tDisplayAs',
  'AliveMCP\t\tuh-lyve-em-see-pee\tAliveMCP',
  'kubectl\t\tkube-cuttle\tkubectl',
  'gRPC\t\tgee-are-pee-see\tgRPC',
].join('\n');

await s3.send(new PutObjectCommand({
  Bucket: 'my-transcribe-assets',
  Key: 'vocabularies/mcp-tools-vocab.tsv',
  Body: tsvContent,
  ContentType: 'text/plain',
}));

await transcribe.send(new CreateVocabularyCommand({
  VocabularyName: 'mcp-tools-vocab-detailed',
  LanguageCode: 'en-US',
  VocabularyFileUri: 's3://my-transcribe-assets/vocabularies/mcp-tools-vocab.tsv',
}));

await waitForVocabularyReady('mcp-tools-vocab-detailed');

Updating an existing vocabulary

// UpdateVocabularyCommand replaces the entire vocabulary — not a merge/append
// The vocabulary re-enters PENDING state and must be re-waited before use

await transcribe.send(new UpdateVocabularyCommand({
  VocabularyName: 'mcp-tools-vocab',
  LanguageCode: 'en-US',
  // Add new terms to the existing list — must include ALL desired phrases
  Phrases: [
    'AliveMCP', 'MCP', 'WebSocket', 'OAuth', 'k8s', 'kubectl', 'Zod',
    'Prisma', 'Drizzle',
    'Hono',          // ← newly added
    'Fastify',       // ← newly added
    'Vite',          // ← newly added
  ],
}));

// Must wait again after update
await waitForVocabularyReady('mcp-tools-vocab');

Vocabulary updates are destructive replacements, not patches. Maintain the canonical phrase list in your own configuration store (e.g., a JSON file in S3 or a database column) and always pass the full desired list when calling UpdateVocabularyCommand. Keep a versioned copy of your TSV in S3 — Transcribe deletes the previous file from its managed store when you update. The vocabulary enters PENDING state during the update and returns to READY once compiled. Any jobs started while the vocabulary is PENDING will fail if they reference this vocabulary by name — gate job submission on vocabulary state.

Vocabulary filters

import {
  CreateVocabularyFilterCommand,
  VocabularyFilterMethod,
} from '@aws-sdk/client-transcribe';

// Create a profanity filter using a simple word list
await transcribe.send(new CreateVocabularyFilterCommand({
  VocabularyFilterName: 'profanity-en-us',
  LanguageCode: 'en-US',
  Words: ['word1', 'word2', 'word3'], // replace with actual words to filter
}));

// Vocabulary filters do NOT have a compile phase — they're available immediately
// after creation (unlike vocabularies which require READY polling)

Vocabulary filters have three methods that control how matched words are handled in the transcript output:

VocabularyFilterMethodEffect on matched wordUse case
mask Replaces matched word with *** Visible profanity filter — users can see a word was filtered but not what it was
remove Omits the word entirely from the transcript Clean transcripts for downstream parsing; filler words (um, uh, like) for readability
tag Keeps the word but adds VocabularyFilterMatch: true on the item Audit logging — keep the full text for compliance but flag matched words for review

The mask and remove methods affect results.transcripts[0].transcript (the full text string). The tag method does not affect the text — only the results.items[N].VocabularyFilterMatch flag. Use tag when you need the original words in a secure audit log but want the downstream agent to see a sanitized version built by filtering tagged items client-side.

Combining vocabulary and filter in a transcription job

import { StartTranscriptionJobCommand } from '@aws-sdk/client-transcribe';

await transcribe.send(new StartTranscriptionJobCommand({
  TranscriptionJobName: `mcp-${Date.now()}`,
  Media: { MediaFileUri: 's3://recordings-bucket/meeting-2026-11-04.ogg' },
  LanguageCode: 'en-US',
  OutputBucketName: 'transcripts-bucket',
  OutputKey: `output/meeting-2026-11-04.json`,
  Settings: {
    // Custom vocabulary — improves recognition of your domain terms
    VocabularyName: 'mcp-tools-vocab',

    // Vocabulary filter — masks profanity in the output
    VocabularyFilterName: 'profanity-en-us',
    VocabularyFilterMethod: 'mask',

    // Speaker diarization (optional — combine with both above)
    ShowSpeakerLabels: true,
    MaxSpeakerLabels: 3,
  },
}));

Both a vocabulary and a vocabulary filter can be active on the same job. The vocabulary improves recognition first (before the transcript is written), and the filter is applied to the output. You cannot use two vocabularies or two filters simultaneously on a single job — if you need multiple domain sets, merge them into a single vocabulary file. For streaming transcription, the same field names apply directly on StartStreamingTranscriptionCommand (without the Settings wrapper — they're top-level parameters).

Vocabulary limits and naming rules

LimitValue
Maximum vocabularies per account100 (default; request increase via Service Quotas)
Maximum phrases per vocabulary256 KB TSV file size (not phrase count)
Maximum phrase length256 characters
Vocabulary compile timeTypically 1–5 minutes; large files may take 15–20 minutes
VocabularyName pattern^[0-9a-zA-Z._-]+$ — alphanumeric plus ., _, -
VocabularyFilterName patternSame as above
Vocabulary filters per account100 (same default limit)

Vocabulary names are global per account per region. If your MCP server supports multi-tenant workloads, prefix vocabulary names with the tenant or project identifier (e.g., acme-corp-vocab) to avoid name collisions. The vocabulary compile time means that vocabularies must be provisioned asynchronously — not on first request — in production systems. Build vocabulary management into your infrastructure provisioning flow, not into your MCP tool's hot path.