Download Microsoft Teams transcripts, chat, and meeting metadata from the macOS desktop app
Project description
Teams Transcript Downloader
A command-line tool that downloads Microsoft Teams meeting transcripts, chat
messages, and participant metadata as WebVTT (.vtt) or structured JSON. It
connects to the Teams desktop app via the Chrome DevTools Protocol (CDP) and
navigates the UI programmatically. An LLM agent (Claude on Vertex AI) handles
bounded non-deterministic steps such as selecting ambiguous search results,
selecting recurring-meeting occurrences, and disambiguating iframe targets.
Run with uvx
On macOS with uv installed, run the app directly from PyPI without a persistent installation:
uvx teams-transcripts --help
uvx teams-transcripts \
--meeting "Weekly Standup" \
--datetime 2026-07-11 \
--gcp-project your-project
The MCP server has a different executable name from the PyPI package, so use
--from to tell uvx which package provides it:
uvx --from 'teams-transcripts==0.5.1' teams-transcripts-mcp
uvx --from teams-transcripts transcript-download --help
Use teams-transcripts==0.5.1 for a reproducible MCP configuration, or replace
it with teams-transcripts to follow the latest release. Do not run
uvx teams-transcripts-mcp: that asks PyPI for a package named
teams-transcripts-mcp, rather than running the executable supplied by this
package.
uvx creates an isolated temporary environment. The Teams desktop app, CDP,
and authentication prerequisites below still apply.
How it works
The script executes an 8-step pipeline:
| Step | Description |
|---|---|
| 1 | Verify Teams is running with CDP enabled; relaunch if needed |
| 2 | Confirm CDP is responding on the configured port |
| 3 | Discover Teams page targets via CDP |
| 3b | (when --tenant specified) Verify tenant; switch if needed |
| 3c | (when --meeting-id used) Resolve UUID to meeting subject and date via MCPS API |
| 4 | Type the meeting name into the Teams search bar and navigate to the chat |
| 5 | Open Recap when needed, select the correct date occurrence for recurring meetings, extract speaker names from the Speakers sub-tab, then switch to the Transcript tab only when transcript output is requested |
| 6 | Locate the transcript iframe target when transcript output is requested |
| 7 | Download the transcript when requested, extract roster members and output chat messages when requested, read Event/Call messages and RSVP tracking when needed for metadata, build categorised participant lists, then build VTT or structured JSON |
| 8 | Validate generated transcript content when transcript output is requested |
Download approaches
When transcript output is requested, the script detects which approach to use based on the Download button state:
Approach A -- Native blob interception is used when you have download permission. The script clicks the Download button and intercepts the blob URL to capture the VTT content byte-for-byte.
Approach B -- React fibre tree extraction is used when download is blocked (the button shows "You don't have permission"). The script walks the React component tree to extract all transcript entries directly from the in-memory data structure, then generates a well-formed VTT file.
Speaker name resolution
Speaker names are resolved using the Recap Speakers tab. The script extracts a GUID-to-display-name mapping and applies it to clean up:
- Vendor-prefixed names:
v-John Doe (Metosin)becomesJohn Doe - Suffixed names:
Jane Smith GRMbecomesJane Smith - Synthetic placeholders:
@1becomesSpeaker 1
Names that are already clean (e.g. internal staff) are left unchanged.
Meeting participant data
The tool extracts categorised participant information from four sources:
| Category | Source | Description |
|---|---|---|
| Organiser | Roster popover (fibre tree) | The meeting creator, identified by organizerId from the chat topic menu |
| Invited | Roster popover (fibre tree) | All members of the meeting chat thread, with names, emails, and Azure AD object IDs |
| Speakers | Recap Speakers tab | Members who spoke during the meeting, cross-referenced with the roster by display name |
| Attendees | Event/Call <partlist> XML in chat messages |
Members who joined the call, cross-referenced with the roster by Azure AD object ID |
For the invited category, RSVP tracking data can optionally be enriched from the meeting Details tab. The tool navigates to Details, opens the Tracking panel, clicks "Show more attendees" if present, and parses the aria-labels to extract each invitee's calendar response status (accepted, tentative, declined, or no response). This enrichment is soft-failure: if the Tracking panel is unavailable, the pipeline continues without RSVP data.
The roster popover is accessed by clicking the participant count button in
the chat header. This retrieves the full member list (names, emails,
object IDs) for all members, regardless of meeting size. The legacy
membersLimited property in the fibre tree is intentionally limited to
~3 members by Teams and is only used as a fallback.
Prerequisites
1. Microsoft Teams desktop app (macOS)
Teams must be running with CDP remote debugging enabled. If Teams is already running without CDP, the script will kill and relaunch it automatically with the full set of required flags.
To launch manually, only the CDP port is strictly required:
export WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS='--remote-debugging-port=9333'
open -a 'Microsoft Teams'
When the script relaunches Teams itself, it sets additional Chromium flags for reliable automation:
| Flag | Purpose |
|---|---|
--disable-backgrounding-occluded-windows |
Keep rendering when Teams is behind other windows |
--disable-renderer-backgrounding |
Keep renderer at full priority when unfocused |
--disable-hang-monitor |
Suppress "page unresponsive" dialogs |
--disable-ipc-flooding-protection |
Prevent throttling of rapid CDP commands |
--disable-smooth-scrolling |
Remove animation delays |
--run-all-compositor-stages-before-draw |
Ensure fully composited frames for screenshots |
--hide-scrollbars |
Cleaner screenshots |
--mute-audio |
Suppress notification and meeting sounds |
--silent-debugger-extension-api |
Hide "debugging" infobars |
--force-device-scale-factor=2 |
Retina-quality screenshots for the LLM agent |
2. Google Cloud credentials
The LLM agent uses Claude via Vertex AI, so you need Application Default Credentials (ADC) for a GCP project with the Vertex AI API enabled:
gcloud auth application-default login
Or point the --adc flag (or GOOGLE_ADC env var) at a service account key
file.
3. Python dependencies
The package requires Python 3.10+ and is managed with
uv. When using uvx, dependencies are installed
automatically in an isolated environment; no project checkout or uv sync is
needed.
For development from a source checkout, install the locked dependencies:
uv sync --locked
This creates a virtual environment in .venv/ and installs the locked
dependencies from uv.lock.
Usage
Download a transcript
teams-transcripts (--meeting NAME | --meeting-id UUID) [--datetime YYYY-MM-DD[THH:MM]] [--output PATH] [options]
Or with uv run (no wrapper script needed):
uv run teams-transcripts (--meeting NAME | --meeting-id UUID) [--datetime YYYY-MM-DD[THH:MM]] [--output PATH] [options]
Or via the Python module:
uv run python -m teams_transcripts (--meeting NAME | --meeting-id UUID) [--datetime YYYY-MM-DD[THH:MM]] [--output PATH] [options]
List available tenants
teams-transcripts --list-tenants [--format json] [--output PATH]
Lists all Azure AD tenants the signed-in user can access, marks the current
tenant with *, then exits. Use the name or domain value from the output
with --tenant in a subsequent download invocation.
List meetings with transcripts
teams-transcripts --list-transcripts [DAYS] [--format json] [--output PATH] [--tenant DOMAIN]
Lists all meetings with transcripts that the signed-in user attended. DAYS
must be 1 or greater. This
queries the Teams MCPS (Meeting Content and Presence Service) API via CDP
worker injection -- no Graph API permissions or app registrations required.
The optional DAYS argument controls how many days back to search (default:
1 = today only).
Multi-tenant behaviour: when --tenant is specified, only the matching
tenant is queried. Without --tenant, all signed-in tenants are queried
and results aggregated into a single output.
Examples:
# Today's meetings with transcripts (all tenants)
teams-transcripts --list-transcripts
# Last 7 days
teams-transcripts --list-transcripts 7
# Last 14 days, specific tenant, JSON output to file
teams-transcripts --list-transcripts 14 --tenant choreograph.com --format json --output meetings.json
# Last 30 days, table output
teams-transcripts --list-transcripts 30
Text output:
Date Subject T R ID Call ID
---------- ------------------------------------------------ - - ------------------------------------ ------------------------------------
2026-04-29 Campaign Connect Leads Y Y f0a4b1c3-228f-4f24-8efb-6736215a3072 aae0280e-5b7e-473d-8a4b-99de3cf74a08
2026-04-29 OMS Forum Y Y 3f9c903d-339a-4be7-a796-c13001c6af01 b12f445a-6c1e-4e2d-9f01-4e8a6b7c9d0e
2026-04-29 Data connectors development Y Y 6d5d5a9f-357a-4f3c-a2be-d489454f6762 c23e556b-7d2f-5f3e-a012-5f9b7c8d0e1f
2026-04-29 UAP mid-Weekly Y Y 39158372-e829-454e-ad92-3d8812527aa9 d34f667c-8e3g-6g4f-b123-6g0c8d9e1f2g
2026-04-29 Dive into data for Data Connectors Y Y 67c13927-9b23-4f30-ae67-a489b3842994 e45g778d-9f4h-7h5g-c234-7h1d9e0f2g3h
2026-04-29 UAP / Trafficking / architecture Y Y 589a1e41-3361-4231-8fc0-582e42fe87c3 f56h889e-0g5i-8i6h-d345-8i2e0f1g3h4i
2026-04-28 Optimization Tech leads Fortnightly Sync session Y Y 2b4c1f5c-636d-4d78-a087-c2cbf3887fd8 g67i990f-1h6j-9j7i-e456-9j3f1g2h4i5j
2026-04-27 Campaign Connect Leads Y Y f0a4b1c3-228f-4f24-8efb-6736215a3072 h78j001g-2i7k-0k8j-f567-0k4g2h3i5j6k
8 meeting(s) found.
Columns: Date (ISO), Subject (meeting title, max 48 chars),
T (has transcript: Y/N), R (has recording: Y/N),
ID (meeting UUID -- use with --meeting-id to download),
Call ID (per-occurrence identifier -- use with --call-id for
unambiguous targeting of recurring meetings; shown only when MCPS returns
call IDs).
Note: for recurring meetings (e.g. "Campaign Connect Leads" above), the ID column shows the same UUID for all occurrences (they share a single chat thread). The Call ID column uniquely identifies each occurrence.
JSON output (--format json):
[
{
"subject": "Campaign Connect Leads",
"date": "2026-04-29",
"thread_id": "19:meeting_ZjBhNGIxYzMtMjI4Zi00ZjI0LThlZmItNjczNjIxNWEzMDcy@thread.v2",
"call_id": null,
"ical_uid": "040000008200E00074C5B7101A82E008...",
"has_transcript": true,
"has_recording": true,
"transcript_url": "https://tenant-my.sharepoint.com/personal/...transcripts/.../content",
"recording_url": "https://tenant-my.sharepoint.com/personal/.../Meeting%20Recording.mp4"
}
]
The JSON output includes SharePoint URLs for transcripts/recordings when MCPS returns them. These URLs can be used for direct download with appropriate authentication.
The legacy entry point transcript-download (backed by the original monolith
in legacy/transcript_download.py) is still registered but is no longer the
primary interface.
Required arguments (download mode)
One of the following is required (mutually exclusive):
| Argument | Description |
|---|---|
--meeting NAME |
Meeting title as it appears in Teams search results |
--meeting-id UUID |
Meeting UUID from --list-transcripts ID column. Identifies a meeting thread. For non-recurring meetings this is unique; for recurring meetings all occurrences share the same UUID. Use --call-id instead for unambiguous recurring-meeting targeting. Incompatible with --meeting, --call-id, and --datetime. |
--call-id CALL_ID |
Call identifier from --list-transcripts Call ID column. Uniquely identifies a specific occurrence of a meeting, including recurring meetings. The tool resolves the call ID to a meeting subject, date, and thread via the MCPS API. Incompatible with --meeting, --meeting-id, and --datetime. |
Optional arguments
| Argument | Default | Description |
|---|---|---|
--datetime YYYY-MM-DD[THH:MM] |
today | Target meeting date, optionally with time. Defaults to today's date when omitted. Date only selects the first occurrence on that date. Adding a time (e.g. 2026-04-22T14:00) disambiguates when there are multiple meetings with the same name on the same day. Incompatible with --meeting-id and --call-id (date is resolved automatically). |
--output PATH |
stdout | Output file path. When omitted, content is written to stdout. File format is determined by --format. |
--format {vtt,json} |
vtt |
Output format. vtt writes WebVTT content. json writes structured JSON with a transcript array of cue objects. Both write to --output (file) or stdout. |
--dry-run |
off | Find the meeting, load Recap only when transcript or metadata was requested, detect parts, extract safe roster metadata when metadata or chat was requested, then exit without downloading transcripts, extracting chat, or opening Details tracking. Incompatible with --output. |
--quiet / -q |
off | Suppress all diagnostic output on stderr. Errors and --format json output are still emitted. |
--verbose / -v |
off | Emit additional diagnostic detail on stderr. Mutually exclusive with --quiet. |
--gcp-project PROJECT_ID |
$GOOGLE_CLOUD_PROJECT |
GCP project for Vertex AI |
--port PORT |
9333 |
CDP remote debugging port. Must be between 1 and 65535. |
--force-restart |
off | Override the live-call safeguard for any restart required by this invocation. May interrupt an active call; use only when interruption is explicitly intended. |
--gcp-region REGION |
$CLOUD_ML_REGION or global |
Vertex AI region |
--model MODEL_ID |
$MODEL_ID or claude-haiku-4-5 |
Anthropic model for the agent |
--adc PATH |
$GOOGLE_ADC or ~/.config/gcloud/... |
Path to ADC JSON file |
--tenant DOMAIN |
$TEAMS_TENANT |
Azure AD tenant domain to verify (e.g. abccorp.com). When specified, the tool checks that Teams is on the correct tenant and automatically switches if needed. Exits with code 6 if the tenant is not in the available list. |
--list-tenants |
off | List all available Azure AD tenants and exit. Incompatible with --meeting, --meeting-id, --call-id, and --datetime. See Tenant selection below. |
--list-transcripts [DAYS] |
off | List meetings with transcripts for the last N days (default: 1 = today, must be >= 1) and exit. Incompatible with --meeting, --meeting-id, --call-id, and --datetime. Without --tenant, aggregates across all signed-in tenants. See List meetings with transcripts below. |
--transcript {yes,no} |
yes |
Include transcript cues in output. When no, the transcript download is skipped and output is forced to JSON. |
--chat {yes,no} |
yes |
Include chat messages in output. When no, chat messages are omitted. If --metadata yes, the tool may still read Event/Call messages internally to build attendee metadata. |
--metadata {yes,no} |
yes |
Include meeting participant metadata in output. When no, participant data (organiser, invited, speakers, attendees, RSVP) is omitted from the output. Roster extraction still runs if --chat yes (for author name resolution). |
Environment variables
All optional arguments can be set via environment variables as fallbacks:
| Variable | Used by |
|---|---|
GOOGLE_CLOUD_PROJECT |
--gcp-project (required if flag not provided) |
CLOUD_ML_REGION |
--gcp-region |
MODEL_ID |
--model |
GOOGLE_ADC |
--adc |
TEAMS_TENANT |
--tenant |
Examples
Download a single-occurrence meeting transcript:
uv run teams-transcripts \
--meeting 'Project Kickoff' \
--datetime 2026-04-20 \
--output ./transcripts/kickoff.vtt \
--gcp-project my-vertex-project
Download a recurring meeting (the script selects the correct occurrence) based upon the datetime:
uv run teams-transcripts \
--meeting 'Weekly Standup' \
--datetime 2026-04-14 \
--output ./transcripts/standup-2026-04-14.vtt
Disambiguate two meetings with the same name on the same day by adding a time:
uv run teams-transcripts \
--meeting 'Weekly Standup' \
--datetime 2026-04-14T14:00 \
--output ./transcripts/standup-afternoon.vtt
Pipe VTT to stdout (no file written):
uv run teams-transcripts \
--meeting 'Project Kickoff' \
--datetime 2026-04-20 | less
Download by meeting ID (from --list-transcripts output):
uv run teams-transcripts \
--meeting-id 2b4c1f5c-636d-4d78-a087-c2cbf3887fd8 \
--output ./transcripts/meeting.vtt
This eliminates ambiguity when multiple meetings share the same name. The UUID
is shown in the ID column of --list-transcripts table output. The meeting
date is resolved automatically from the UUID via the MCPS API.
Download a specific occurrence of a recurring meeting by call ID:
uv run teams-transcripts \
--call-id aae0280e-5b7e-473d-8a4b-99de3cf74a08 \
--output ./transcripts/standup-2026-04-29.vtt
The call ID is shown in the Call ID column of --list-transcripts table
output. Unlike the meeting UUID (which is shared across all occurrences of a
recurring series), the call ID uniquely identifies a specific occurrence. This
is the preferred method for recurring meetings.
Get structured JSON output to a file (nothing on stdout):
uv run teams-transcripts \
--meeting 'Project Kickoff' \
--datetime 2026-04-20 \
--output ./kickoff.json --format json
The JSON file includes the full structured transcript as a "transcript" array
of cue objects:
{
"status": "ok",
"meeting": "Project Kickoff",
"start": "2026-04-20T10:00+01:00",
"end": "2026-04-20T11:00+01:00",
"organiser": {"name": "Alice", "email": "alice@example.com"},
"invited": [
{"name": "Alice", "email": "alice@example.com", "rsvp_status": "organizer"},
{"name": "Bob", "email": "bob@example.com", "rsvp_status": "accepted"},
{"name": "Charlie", "email": "charlie@example.com", "rsvp_status": "tentative"}
],
"speakers_list": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
],
"attendees": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"},
{"name": "Charlie", "email": "charlie@example.com"}
],
"transcript": [
{
"id": 1, "start": "00:00:03.000", "end": "00:00:25.000",
"start_sec": 3.0, "end_sec": 25.0,
"speaker": "Alice",
"text": "So I do have this concept of account sets."
}
],
"chat": [
{
"author": "Bob",
"text": "Here is the link: https://example.com/doc",
"reactions": [{"type": "like", "count": 2, "users": ["Alice", "Charlie"]}],
"offset": "00:05:12.000"
}
],
"parts": 1,
"cues": 342,
"speakers": {"Alice": 180, "Bob": 162},
"file_size": 48210,
"chat_messages": 2
}
Get structured JSON on stdout (agent-friendly, no file):
uv run teams-transcripts \
--meeting 'Project Kickoff' \
--datetime 2026-04-20 --format json
The same schema is written to stdout. With --output, the JSON is written to
the file and stdout is empty.
Dry-run to check a meeting exists without downloading:
uv run teams-transcripts \
--meeting 'Weekly Standup' \
--datetime 2026-04-14 --dry-run --format json
Quiet mode for cron or agent use (suppress diagnostic logs):
uv run teams-transcripts \
--meeting 'Weekly Standup' \
--datetime 2026-04-14 \
--output ./standup.vtt -q
Use a specific model and region:
uv run teams-transcripts \
--meeting 'Quarterly Review' \
--datetime 2026-03-31 \
--output ./review.vtt \
--model claude-sonnet-4-20250514 \
--gcp-region us-east5
Tenant selection
When your Microsoft account has access to multiple Azure AD tenants (organisations), you can list them and target a specific one:
# List all available tenants
uv run teams-transcripts --list-tenants
Text output:
Available tenants (* = current):
NAME DOMAIN TENANT ID
* ABC Corp abccorp.onmicrosoft.com 86cbe1bb-213f-4271-b174-bd59d03c87a3
Contoso Ltd contoso.onmicrosoft.com 2b755fa1-23d1-48f3-98fc-6fdc1dc48d69
Use --tenant NAME_OR_DOMAIN to select a tenant for download.
JSON output:
uv run teams-transcripts --list-tenants --format json
[
{
"tenantId": "86cbe1bb-213f-4271-b174-bd59d03c87a3",
"tenantName": "ABC Corp",
"domain": "abccorp.onmicrosoft.com",
"current": true
},
{
"tenantId": "2b755fa1-23d1-48f3-98fc-6fdc1dc48d69",
"tenantName": "Contoso Ltd",
"domain": "contoso.onmicrosoft.com",
"current": false
}
]
Write the tenant list to a file:
uv run teams-transcripts --list-tenants --output tenants.json --format json
Download a transcript from a specific tenant (auto-switches if needed):
uv run teams-transcripts \
--meeting 'Weekly Standup' \
--datetime 2026-04-14 \
--output ./standup.vtt \
--tenant contoso.onmicrosoft.com
When --tenant is specified, the tool:
- Detects which tenant Teams is currently signed into
- Lists all available tenants for the signed-in user
- If the current tenant matches, proceeds normally
- If the current tenant does not match but the requested tenant is in the list, kills Teams and relaunches it on the correct tenant automatically
- If the requested tenant is not in the available list at all, exits with
code 6 (
TENANT_NOT_FOUND)
The --tenant value is matched case-insensitively against both the tenant
name and the primary domain name from the tenant list.
Component selection
By default all three components are extracted: transcript, chat, and metadata.
Use the --transcript, --chat, and --metadata flags to select which
components to include. At least one must be yes, except with --dry-run,
where all three may be no for a pure meeting-exists check.
Extract only chat messages (no transcript download, faster):
uv run teams-transcripts \
--meeting 'Weekly Standup' \
--datetime 2026-04-14 \
--transcript no --metadata no --format json
Extract only participant metadata:
uv run teams-transcripts \
--meeting 'Weekly Standup' \
--datetime 2026-04-14 \
--transcript no --chat no --format json
Extract chat and metadata without the transcript:
uv run teams-transcripts \
--meeting 'Weekly Standup' \
--datetime 2026-04-14 \
--transcript no --format json
When --transcript no is set, the output format is forced to JSON (VTT without
transcript cues is meaningless). Explicitly passing --format vtt alongside
--transcript no is an error.
When --chat no --metadata yes is set, chat messages are omitted from output,
but Event/Call messages may still be read internally to build attendee metadata.
The JSON output is composable: each component adds only its own fields. When
all three are yes (the default), the output is identical to the standard
full-extraction JSON schema.
MCP server
The package includes a Model Context Protocol (MCP) server that exposes the extraction tools over STDIO. This allows any MCP-compatible client (Claude Desktop, Cursor, OpenCode, etc.) to call the tools directly.
Running the MCP server
From PyPI, with no repository checkout or persistent installation:
# Pinned release (recommended for MCP client configuration)
uvx --from 'teams-transcripts==0.5.1' teams-transcripts-mcp
# Latest release
uvx --from teams-transcripts teams-transcripts-mcp
STDIO is the default transport. When run manually, the command waits quietly for MCP messages on standard input; normally an MCP client starts and manages this process for you.
If the package is already installed, run the entry point directly:
# STDIO transport (default, for subprocess spawning by MCP clients)
teams-transcripts-mcp
# SSE transport on a specific port (for network MCP clients)
teams-transcripts-mcp --port 8080
Or with uv run:
uv run teams-transcripts-mcp
uv run teams-transcripts-mcp --port 8080
Or via the Python module:
uv run python -m teams_transcripts.mcp_server
uv run python -m teams_transcripts.mcp_server --port 8080
When --port is omitted, the server communicates over STDIO (the standard
MCP transport for local subprocess spawning). When --port is specified,
the server runs as an SSE HTTP server on that port, accessible over the
network.
The server inherits environment variables for GCP configuration, including
GOOGLE_CLOUD_PROJECT and the optional CLOUD_ML_REGION, MODEL_ID,
GOOGLE_ADC, and TEAMS_TENANT settings (see
Environment variables).
Available tools
| Tool | Description |
|---|---|
restart_teams |
Safely relaunch Teams with CDP flags and wait for readiness |
list_tenants |
List available Azure AD tenants |
list_transcripts |
Discover meetings with transcripts (last N days) |
download |
Unified entry point -- accepts UUID or name+date, validates args |
download_by_meeting_id |
Download transcript + chat + metadata by UUID |
download_transcript_by_meeting_id |
Download only transcript by UUID |
download_chat_by_meeting_id |
Download only chat messages by UUID |
download_metadata_by_meeting_id |
Download only participant metadata by UUID |
download_all |
Download transcript + chat + metadata by name + date |
download_transcript |
Download only the transcript (WebVTT cues) by name + date |
download_chat |
Download only chat messages by name + date |
download_meeting_metadata |
Download only participant metadata by name + date |
Discovery tools (list_tenants, list_transcripts) accept an optional
port parameter and return JSON arrays.
Unified download tool (download) is the recommended entry point for
AI agents. It accepts either meeting_id (preferred) or meeting + date,
validates argument combinations before launching the subprocess, and supports
dry_run=True to verify a meeting can be found without transcript download,
output chat extraction, Event/Call attendance extraction, or Details tracking
extraction. In dry-run mode, all three component flags may be False for a
pure meeting-exists check.
UUID-based download tools (download_by_meeting_id, etc.) accept a
meeting_id (UUID from list_transcripts output), optional tenant,
port, and dry_run. The meeting subject and date are resolved
automatically from the UUID.
Name-based download tools (download_all, etc.) accept meeting
(required), date (required), and optional time, tenant, port, and
dry_run parameters.
Recommended workflow
1. list_transcripts(days=7) → discover meetings, get UUIDs
2. download(meeting_id=uuid) → extract transcript by UUID (preferred)
OR download(meeting_id=uuid, dry_run=True) → verify meeting exists first
Or for a known meeting:
1. download(meeting="Weekly Standup", date="2026-04-29")
MCP client configuration with uvx
Use uvx as the MCP command and pass the package plus its MCP executable as
arguments. This works with clients that accept the standard JSON MCP server
configuration format, including Claude Desktop:
{
"mcpServers": {
"teams-transcripts": {
"command": "uvx",
"args": [
"--from",
"teams-transcripts==0.5.1",
"teams-transcripts-mcp"
],
"env": {
"GOOGLE_CLOUD_PROJECT": "your-gcp-project"
}
}
}
}
For Claude Desktop, add that entry to claude_desktop_config.json and restart
Claude Desktop. Other MCP clients use the same command and arguments in their
local-server configuration. Add any optional environment variables your setup
needs to the env object. The MCP process must run as the same macOS user as
Teams so it can connect to the desktop app and use that user's credentials.
To track the newest PyPI release instead of pinning one, change the package
argument from teams-transcripts==0.5.1 to teams-transcripts.
Configuration from an installed checkout
If the project is already installed in a virtual environment, point the MCP client at its entry point:
{
"mcpServers": {
"teams-transcripts": {
"command": "/path/to/teams-transcripts/bin/teams-transcripts-mcp",
"env": {
"GOOGLE_CLOUD_PROJECT": "your-gcp-project"
}
}
}
}
For a source checkout managed by uv, configure the client to run in the
repository directory:
{
"mcpServers": {
"teams-transcripts": {
"command": "uv",
"args": ["run", "--directory", "/path/to/teams-transcripts", "teams-transcripts-mcp"],
"env": {
"GOOGLE_CLOUD_PROJECT": "your-gcp-project"
}
}
}
}
Architecture
Each MCP tool shells out to the teams-transcripts CLI with --format json --quiet and returns the parsed JSON response. The subprocess approach provides:
- Process isolation (Teams CDP locks are per-process)
- Clean signal handling (the child manages its own SIGINT/SIGTERM)
- No import-time side effects in the MCP server process
Discovery tools (list_tenants, list_transcripts) return JSON arrays.
Download tools return a JSON object with the extraction results. UUID-based
tools use --meeting-id internally; name-based tools use --meeting +
--datetime.
Safe Teams restarts
Every automatic Teams restart checks for a possible live call immediately
before killing the app. The check samples CoreAudio activity for the Teams app
and its helper processes three times, then checks the macOS Accessibility tree
for a visible Leave or Hang-up control. The result is active, inactive, or
unknown; both active and unknown block automatic CDP recovery and tenant
switching.
Blocked CLI flows exit with code 2 and the machine-stable RESTART_BLOCKED
error. MCP callers receive the same error through download/listing tools. The
restart_teams MCP tool instead returns a structured result containing
status, restarted, cdp_ready, port, call_state, and forced. A block
also includes reason: active_call or reason: call_state_unknown.
The CLI flag --force-restart overrides the safeguard for any automatic CDP
recovery or tenant switch required by that invocation. The MCP equivalent is
restart_teams(force=True). Both may interrupt a live call and must be used
only when interruption is explicitly intended. Without an override, downloads,
listing, tenant switching, and recovery continue to fail closed.
Output format
VTT file structure
The output is a standard WebVTT file containing three sections:
1. Meeting metadata -- a NOTE Meeting Metadata comment block at the top:
WEBVTT
NOTE Meeting Metadata
Meeting: Weekly Standup
Organiser: Alice <alice@example.com>
Invited: Alice, Bob, Charlie
Speakers: Alice, Bob
Attendees: Alice, Bob, Charlie
Start: 2026-04-14T10:00+01:00
End: 2026-04-14T10:30+01:00
For larger meetings (more than 10 invited or attendees), counts are shown instead of names, with an RSVP breakdown when tracking data is available:
NOTE Meeting Metadata
Meeting: Q1 2026 All Hands
Organiser: Suman Landa <suman.landa@example.com>
Invited: 90 members (50 accepted, 5 tentative, 2 declined, 33 no response)
Speakers: Alice, Bob, Charlie, Dave, Eve, Fran
Attendees: 77 participants
Start: 2026-04-01T09:00+01:00
End: 2026-04-01T10:30+01:00
Fields:
- Meeting: meeting title.
- Organiser: meeting creator with email in
Name <email>format. - Invited: all members on the meeting chat thread. Listed by name when 10 or fewer; shown as a count with RSVP summary when more than 10. RSVP statuses are: accepted, tentative, declined, no response.
- Speakers: members who spoke during the meeting (from Recap).
- Attendees: members who joined the call (from Event/Call partlist). May be incomplete for large meetings due to server-side truncation.
- Start / End: ISO 8601 timestamps with timezone offset.
2. Chat messages -- a NOTE Chat comment block containing a JSON array of
messages sent during the meeting:
NOTE Chat
[
{
"author": "Bob",
"text": "Link to the design doc: https://example.com/doc",
"reactions": [
{"type": "like", "count": 2, "users": ["Alice", "Charlie"]}
],
"offset": "00:12:34.567"
}
]
Each message includes: author name, plain text (HTML stripped, links
preserved), reactions (with per-user attribution where resolvable), and an
offset from meeting start in HH:MM:SS.mmm format where computable. The
offset field is null when it cannot be computed. The reactions field is
null when there are no reactions.
3. Transcript cues -- standard WebVTT cues with speaker identification:
1
00:00:03.000 --> 00:00:25.000
<v Alice>So I do have this concept of account sets. It's called
platform account sets in the code base, which basically...</v>
2
00:00:27.000 --> 00:00:48.000
<v Bob>Looks something like this. For example, this is our dev
accounts.</v>
Each cue contains a sequential number, start and end timestamps in
HH:MM:SS.mmm format, the speaker name in a <v Name> voice span, and the
spoken text.
For multi-part meetings (where Teams splits the recording), cue timestamps are offset so that Part 2 continues from where Part 1 ended, producing a single continuous timeline.
JSON output schemas
When --format json is used, a single JSON object is written to stdout (when
--output is omitted) or to the specified file (when --output is given).
In file mode, nothing is written to stdout.
Success (all components):
{
"status": "ok",
"meeting": "Weekly Standup",
"start": "2026-04-14T10:00+01:00",
"end": "2026-04-14T10:30+01:00",
"organiser": {"name": "Alice", "email": "alice@example.com"},
"invited": [
{"name": "Alice", "email": "alice@example.com", "rsvp_status": "organizer"},
{"name": "Bob", "email": "bob@example.com", "rsvp_status": "accepted"},
{"name": "Charlie", "email": "charlie@example.com", "rsvp_status": "declined"}
],
"speakers_list": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
],
"attendees": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"},
{"name": "Charlie", "email": "charlie@example.com"}
],
"chat": [
{
"author": "Bob",
"text": "Here is the link: https://example.com/doc",
"reactions": [{"type": "like", "count": 2, "users": ["Alice", "Charlie"]}],
"offset": "00:05:12.000"
},
{
"author": "Alice",
"text": "Thanks Bob",
"reactions": null,
"offset": "00:05:45.000"
}
],
"transcript": [
{
"id": 1, "start": "00:00:03.000", "end": "00:00:25.000",
"start_sec": 3.0, "end_sec": 25.0,
"speaker": "Alice",
"text": "So I do have this concept of account sets."
},
{
"id": 2, "start": "00:00:27.000", "end": "00:00:48.000",
"start_sec": 27.0, "end_sec": 48.0,
"speaker": "Bob",
"text": "Looks something like this."
}
],
"parts": 1,
"cues": 342,
"speakers": {"Alice": 180, "Bob": 162},
"file_size": 48210,
"chat_messages": 2
}
Dry-run (--dry-run --format json):
{
"status": "ok",
"dry_run": true,
"meeting": "Weekly Standup",
"start": "2026-04-14T10:00+01:00",
"end": "2026-04-14T10:30+01:00",
"organiser": {"name": "Alice", "email": "alice@example.com"},
"invited": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"},
{"name": "Charlie", "email": "charlie@example.com"}
],
"speakers_list": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
],
"parts": 1,
"speakers": {"Alice": 0, "Bob": 0}
}
Speaker counts are zero in dry-run mode (no transcript downloaded). The
"cues", "file_size", "transcript", and "chat_messages" keys are
absent. "attendees" is absent in dry-run because Event/Call partlists are
only available after chat extraction, which dry-run skips.
Error (any non-zero exit):
{
"status": "error",
"code": "MEETING_NOT_FOUND",
"message": "Could not find the target 2026-04-14 in any of the first 4 search results.",
"step": "step5"
}
The "code" field is a machine-stable error code (see --help for the full
list). The "step" field indicates which pipeline step failed (absent for
argument validation errors).
Conditional keys: "end" is absent if the meeting time text could not be
parsed. "organiser" is absent if the organiser could not be identified.
"invited" is absent if roster extraction failed entirely (popover
unavailable). "speakers_list" is absent if no speakers were found.
"attendees" is absent if no Event/Call partlist was available (e.g.
dry-run mode, or the meeting had no call join/leave events).
"chat_messages" is absent (not zero) when there are no chat messages.
"rsvp_status" is included on each invited member only when RSVP tracking
data was available; it is omitted (not empty) when the Details tab Tracking
panel could not be read.
Component-specific JSON schemas
The JSON output is composable. Each component (--transcript, --chat,
--metadata) contributes only its own fields. Base fields (status,
meeting, start, end) are always present regardless of which components
are selected.
Transcript only (--transcript yes --chat no --metadata no):
{
"status": "ok",
"meeting": "Weekly Standup",
"start": "2026-04-14T10:00+01:00",
"end": "2026-04-14T10:30+01:00",
"parts": 1,
"cues": 342,
"speakers": {"Alice": 180, "Bob": 162},
"file_size": 48210,
"transcript": [
{
"id": 1, "start": "00:00:03.000", "end": "00:00:25.000",
"start_sec": 3.0, "end_sec": 25.0,
"speaker": "Alice",
"text": "So I do have this concept of account sets."
}
]
}
Transcript-specific fields: parts, cues, speakers (name-to-cue-count
map), file_size, and transcript (array of cue objects).
Chat only (--transcript no --chat yes --metadata no):
{
"status": "ok",
"meeting": "Weekly Standup",
"start": "2026-04-14T10:00+01:00",
"end": "2026-04-14T10:30+01:00",
"chat": [
{
"author": "Bob",
"text": "Here is the link: https://example.com/doc",
"reactions": [{"type": "like", "count": 2, "users": ["Alice", "Charlie"]}],
"offset": "00:05:12.000"
},
{
"author": "Alice",
"text": "Thanks Bob",
"reactions": null,
"offset": "00:05:45.000"
}
]
}
Chat-specific fields: chat (array of message objects). Each message has:
author(string): display name of the sendertext(string): plain text content (HTML stripped, links preserved)reactions(array or null): list of reaction objects, each withtype,count, andusers(array of display names)offset(string or null): time offset from meeting start inHH:MM:SS.mmmformat when computable
Metadata only (--transcript no --chat no --metadata yes):
{
"status": "ok",
"meeting": "Weekly Standup",
"start": "2026-04-14T10:00+01:00",
"end": "2026-04-14T10:30+01:00",
"organiser": {"name": "Alice", "email": "alice@example.com"},
"invited": [
{"name": "Alice", "email": "alice@example.com", "rsvp_status": "organizer"},
{"name": "Bob", "email": "bob@example.com", "rsvp_status": "accepted"},
{"name": "Charlie", "email": "charlie@example.com", "rsvp_status": "tentative"}
],
"speakers_list": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
],
"attendees": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"},
{"name": "Charlie", "email": "charlie@example.com"}
]
}
Metadata-specific fields:
organiser(object):{name, email}of the meeting creatorinvited(array): members on the chat thread, each withname,email, and optionallyrsvp_status(one of:organizer,accepted,tentative,declined,none)speakers_list(array): members who spoke, each withnameandemailattendees(array): members who joined the call, each withnameandemail
Chat + metadata (--transcript no --chat yes --metadata yes):
{
"status": "ok",
"meeting": "Weekly Standup",
"start": "2026-04-14T10:00+01:00",
"end": "2026-04-14T10:30+01:00",
"organiser": {"name": "Alice", "email": "alice@example.com"},
"invited": [
{"name": "Alice", "email": "alice@example.com", "rsvp_status": "accepted"},
{"name": "Bob", "email": "bob@example.com", "rsvp_status": "accepted"}
],
"speakers_list": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
],
"attendees": [
{"name": "Alice", "email": "alice@example.com"},
{"name": "Bob", "email": "bob@example.com"}
],
"chat": [
{
"author": "Bob",
"text": "Starting now",
"reactions": null,
"offset": "00:00:05.000"
}
]
}
List tenants (--list-tenants --format json):
[
{
"tenantId": "86cbe1bb-213f-4271-b174-bd59d03c87a3",
"tenantName": "ABC Corp",
"domain": "abccorp.onmicrosoft.com",
"current": true
},
{
"tenantId": "2b755fa1-23d1-48f3-98fc-6fdc1dc48d69",
"tenantName": "Contoso Ltd",
"domain": "contoso.onmicrosoft.com",
"current": false
}
]
JSON field reference
| Field | Type | Present when | Description |
|---|---|---|---|
status |
string | always | "ok" on success, "error" on failure |
meeting |
string | always (success) | Meeting title |
start |
string | always (success) | ISO 8601 meeting start time |
end |
string | when parseable | ISO 8601 meeting end time |
dry_run |
boolean | --dry-run mode |
Always true when present |
organiser |
object | --metadata yes |
{name, email} |
invited |
array | --metadata yes |
[{name, email, rsvp_status?}] |
speakers_list |
array | --metadata yes |
[{name, email}] |
attendees |
array | --metadata yes + attendance data available |
[{name, email}] |
chat |
array | --chat yes + messages exist |
[{author, text, reactions, offset}] |
chat_messages |
integer | all-components mode | Count of chat messages |
parts |
integer | --transcript yes |
Number of transcript parts |
cues |
integer | --transcript yes |
Total cue count |
speakers |
object | --transcript yes |
{name: cue_count} map |
file_size |
integer | --transcript yes |
VTT content size in bytes |
transcript |
array | --transcript yes |
[{id, start, end, start_sec, end_sec, speaker, text}] |
code |
string | error | Machine-stable error code |
message |
string | error | Human-readable error description |
step |
string | error (runtime) | Pipeline step that failed |
Recurring meetings
For recurring meetings, the script:
- Navigates to the meeting chat group
- Opens the Recap tab and reads the occurrence dropdown
- If the default occurrence does not match the target date, opens the dropdown and scrolls through available dates
- Uses the LLM agent to identify and select the correct occurrence
Teams typically retains the most recent 5 or so occurrences in the dropdown. Older occurrences may no longer be available.
If the first search result leads to a chat group that does not contain the target date, the script automatically retries with the next search result (up to 4 attempts).
Search modes
Teams uses two different search UI patterns depending on the context:
- Panel mode: results appear in a side panel with text-based matching. The script scores results by word overlap with the meeting name.
- Popup mode: results appear in a dropdown overlay. The script uses the LLM agent to select the correct result from a screenshot.
The mode is auto-detected; no configuration is needed.
Limitations
- macOS only: the script uses macOS-specific commands (
open -a,pkill) to manage the Teams process. - Occurrence dropdown depth: Teams only retains recent occurrences (roughly the last 5). Older recurring meeting dates cannot be retrieved.
- Attendee list truncation: the Event/Call
<partlist>XML that Teams stores in chat messages is truncated server-side for large meetings. For example, a meeting declaringcount="77"may only include 25<part>elements. The attendee list in the output may therefore be incomplete. This is a Teams limitation with no client-side workaround. - RSVP data is calendar responses, not actual attendance: the tracking
panel shows who accepted/declined/tentatively accepted the calendar
invitation. It does not reflect who actually joined the call. Actual
attendance is determined by the
<partlist>(see above). - RSVP extraction is soft-failure: if the Details tab or Tracking panel
is unavailable (e.g. the meeting type does not support tracking, or the
UI layout changes), the pipeline continues without RSVP data. The
rsvp_statusfield will be absent from JSON output in this case. - Conference room accounts: room/resource accounts appear with their
system names (e.g.
UK-LON-1SB-05S19@oursystems.com) as that is what Teams stores as the display name.
Exit codes
| Code | Name | Meaning |
|---|---|---|
| 0 | EXIT_OK |
Success |
| 1 | EXIT_BAD_ARGS |
Invalid arguments or missing configuration |
| 2 | EXIT_CDP_UNAVAILABLE |
Teams/CDP not running or not responding |
| 3 | EXIT_MEETING_NOT_FOUND |
Meeting not found after all retries |
| 4 | EXIT_DOWNLOAD_FAILED |
Transcript download or permission error |
| 5 | EXIT_VERIFICATION_FAILED |
Output file is invalid |
| 6 | EXIT_TENANT_NOT_FOUND |
Requested tenant not in available tenants list |
| 130 | EXIT_INTERRUPTED |
Interrupted by SIGINT (Ctrl+C) |
| 143 | EXIT_TERMINATED |
Terminated by SIGTERM |
Error codes
Each error includes a machine-stable code string used as a [PREFIX] on stderr
and as the "code" field in JSON error output. These are stable identifiers
suitable for programmatic matching.
| Code | Meaning |
|---|---|
BAD_ARGS |
Invalid CLI arguments or missing configuration |
CDP_UNAVAILABLE |
Teams/CDP not running or not responding |
RESTART_BLOCKED |
Teams restart blocked by an active or unknown call state |
NO_TARGETS |
No Teams page targets found via CDP |
SEARCH_FAILED |
Search results did not appear in the UI |
MEETING_NOT_FOUND |
Meeting not found in search results |
RECAP_NOT_FOUND |
Recap tab not found after navigation |
TRANSCRIPT_NOT_FOUND |
Transcript tab not found |
IFRAME_NOT_FOUND |
No transcript iframe target among CDP targets |
DOWNLOAD_FAILED |
VTT download failed |
VERIFICATION_FAILED |
Output file content is invalid |
TENANT_NOT_FOUND |
Requested tenant not in available tenants list |
INTERRUPTED |
Process interrupted by SIGINT |
TERMINATED |
Process terminated by SIGTERM |
Agent and automation usage
The CLI is designed for use by both humans and automated agents. Key features for machine consumption:
- stdout/stderr separation: all diagnostic output goes to stderr. stdout
carries only data -- VTT content in text mode, or a JSON object in JSON mode.
When
--outputis given, nothing is written to stdout (both formats). Use--quietto suppress diagnostics entirely. --format json: emits a single JSON object. On success, the object contains meeting metadata, speaker counts, cue count, and a"transcript"array of structured cue objects. On failure, the object contains"status": "error", a machine-stable"code"string, and a human message.- Semantic exit codes: agents can branch on the exit code without parsing error messages. See the table above.
- Machine-stable error codes: each error includes a short code prefix in
text mode (e.g.
[MEETING_NOT_FOUND]) and a"code"field in JSON mode. Codes are listed in--helpunder "error codes". --dry-run: finds the meeting, loads Recap only when needed, extracts safe roster metadata when requested, and detects parts without downloading the transcript, extracting chat/attendance data, or opening Details tracking. Useful for validation before committing to a full download.- Signal handling: SIGINT and SIGTERM are handled cleanly. The first
signal sets a shutdown flag and the process exits at the next safe point
(within 500 ms). A second signal force-exits immediately. JSON error
output is emitted on signal exit when
--format jsonis active.
Output modes
--output |
--format |
--dry-run |
stdout | file |
|---|---|---|---|---|
| PATH | vtt | no | (empty) | VTT |
| PATH | json | no | (empty) | JSON |
| omitted | vtt | no | raw VTT content | (none) |
| omitted | json | no | structured JSON | (none) |
| omitted | vtt | yes | (empty) | (none) |
| omitted | json | yes | JSON metadata (no transcript) | (none) |
| PATH | any | yes | error (incompatible) | -- |
--list-tenants |
vtt | -- | tenant table | (none) |
--list-tenants |
json | -- | tenant JSON array | (none) |
--list-tenants + PATH |
vtt | -- | (empty) | tenant table |
--list-tenants + PATH |
json | -- | (empty) | tenant JSON |
--list-transcripts |
table | -- | meeting table | (none) |
--list-transcripts |
json | -- | meeting JSON array | (none) |
--list-transcripts |
vtt | -- | error (incompatible) | -- |
--list-transcripts + PATH |
table | -- | (empty) | meeting table |
--list-transcripts + PATH |
json | -- | (empty) | meeting JSON |
Development
Setup
git clone <repo-url> && cd teams-transcripts
uv sync --all-groups --locked
lefthook install
uv sync creates the virtual environment in .venv/ and installs all
runtime and development dependencies from the lockfile. Lefthook is an
external development prerequisite; install it with brew install lefthook
on macOS before running lefthook install to set up the pre-commit hooks.
To regenerate the live regression fixtures:
uv run pytest tests/regression/test_regression.py -m live --update-fixtures
See Generating regression tests for details.
After setup, the wrapper scripts in bin/ are ready to use:
./bin/teams-transcripts --help
./bin/teams-transcripts-mcp --help
These delegate to the venv entry points without requiring uv run.
Pre-commit hooks
This project uses Lefthook for pre-commit checks. The following checks run in parallel on every commit:
| Check | Tool | Description |
|---|---|---|
ruff-check |
Ruff | Lint with an extended rule set (Pyflakes, pycodestyle, Bugbear, Bandit, and more) |
ruff-format |
Ruff | Verify formatting matches the canonical style |
ty-check |
ty | Type checking with --error-on-warning |
All three checks must pass before a commit is accepted. Configuration is in
pyproject.toml.
Running checks manually
uv run ruff check teams_transcripts/ legacy/ tests/ scripts/
uv run ruff format --check teams_transcripts/ legacy/ tests/ scripts/
uv run ty check --error-on-warning teams_transcripts/ legacy/ tests/ scripts/
These checks exclude tests/regression/, which is a local, generated test tree
containing private meeting configuration and fixtures.
Adding a dependency
uv add <package>
This updates both pyproject.toml and uv.lock.
Running tests
# Safe local suite (live, destructive, and fixture-data tests are deselected)
uv run pytest
# Tracked tests only (for fresh clones without regression/ directory)
uv run pytest tests/ --ignore=tests/regression
# Verbose output with test names
uv run pytest -v
# Specific test file
uv run pytest tests/test_domain_models.py
# Live regression tests (requires Teams running with CDP on port 9333)
uv run pytest -m live
# Override the safe default and run every collected test (may relaunch Teams)
uv run pytest -m ""
The test suite uses four custom pytest markers. The default marker expression
excludes live, destructive, and fixtures; passing -m explicitly
overrides that default.
| Marker | Requires | Description |
|---|---|---|
live |
Running Teams + CDP | Regression tests against real meetings |
offline |
Generated local configuration | Offline integration accuracy tests |
destructive |
Running Teams | Tests that kill and relaunch the Teams process |
fixtures |
Generated fixture files | Offline tests that validate fixture VTT content |
Generating regression tests
The
tests/regression/fixtures/directory and local meeting config may contain real meeting data. Treat regenerated fixtures as sensitive and do not publish them outside the repository context.
The regression test suite compares tool output byte-for-byte against reference
VTT files in tests/regression/fixtures/. These must be generated from a live
Teams session:
uv run pytest tests/regression/test_regression.py -m live --update-fixtures
Requirements:
- Microsoft Teams running with CDP enabled on port 9333
- Valid GCP credentials for Vertex AI (the LLM agent)
- The 9 meetings defined in
tests/regression/test_meetings.yamlaccessible in Teams
This downloads each meeting's transcript and saves it as
tests/regression/fixtures/regression-{A-I}.vtt. Once generated, the fixture
validation tests run without Teams access.
To verify fixture-dependent tests pass after generation:
uv run pytest tests/regression/ -m "not live and not destructive"
The regression suite validates the 9 reference meetings in three modes: download (byte-for-byte comparison against fixtures), dry-run (JSON metadata schema), and format-json (output schema validation).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file teams_transcripts-0.5.1.tar.gz.
File metadata
- Download URL: teams_transcripts-0.5.1.tar.gz
- Upload date:
- Size: 363.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ceb487f6c4de9bf742e86fbb1c654d9fd30ffb9ef487a543e34b5e9c4e063f3
|
|
| MD5 |
a152fae3d374854f6700b9acf11f119f
|
|
| BLAKE2b-256 |
975463c4bf98500175683a4aa8a99188192f14aeb9cf516ee1906ffc2bf3257c
|
File details
Details for the file teams_transcripts-0.5.1-py3-none-any.whl.
File metadata
- Download URL: teams_transcripts-0.5.1-py3-none-any.whl
- Upload date:
- Size: 185.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3541e604ed47b52b3e5fb6393bd4751ccd44d30e16153442b05c23773a175b69
|
|
| MD5 |
e029b7f39defbed1666b753965a247ce
|
|
| BLAKE2b-256 |
3c67c7e7c78b7788df0eee0feb910087bad03408a04d9c1dc9c11b2d1d851c9b
|