Recordings & Transcripts - Technical Manual
10 min read
This page documents how meeting transcripts and recordings get from Microsoft Teams into the Unique knowledge base, and how the Recordings area reads them back out. For the server that performs the capture — its OAuth model, token handling, chat tools, and deployment — see the Teams MCP - Technical Manual.
Architecture
Capture is webhook-driven and asynchronous. Presentation is a normal read path against the knowledge base.
RabbitMQ is not optional for this feature. Microsoft requires a webhook endpoint to respond within 10 seconds, while processing a transcript — database lookups, several Graph calls, participant resolution, and uploads — routinely takes 30 seconds or more. The queue decouples the two: the controller validates the notification, enqueues it, and returns 202 Accepted immediately.
Ingestion pipeline
When a meeting transcript becomes available, Microsoft Graph sends a change notification. The recording, if one exists, is located by correlating on contentCorrelationId.
Webhook validation: Microsoft Graph returns the clientState value supplied at subscription time with every notification. The server rejects any notification whose clientState does not match the configured MICROSOFT_WEBHOOK_SECRET.
Recording handling: the recording is located by contentCorrelationId. If it is unavailable the transcript is still ingested — recording failures are logged but never fail transcript processing. Recordings are uploaded with SKIP_INGESTION, so no chunking, embedding, or indexing is performed on the video file.
Exchanges and queues:
Exchange | Type | Purpose |
|---|---|---|
| topic | Primary message routing |
| topic | Failed message storage (DLX) |
Queue | Purpose |
|---|---|
| Transcript processing |
| Subscription renewal and removal |
| Dead letter collection |
Failure handling: a message that fails processing is nacked and routed to a Dead Letter Exchange, where it accumulates indefinitely — there is no automatic TTL or retry. Because no delta sync exists, a message in the DLQ is the only copy of that notification: discarding it without successful processing means the transcript is never ingested.
Knowledge base data model
Scope layout
Meetings are written beneath the configured root scope as a two-level structure:
<root scope>
└── <meeting subject> (<short hash of meeting id>)
├── 2026-07-20 14-03-11 ← one child scope per session
│ ├── transcript (VTT)
│ ├── recording (MP4)
│ └── reports
└── 2026-07-27 14-01-58The subject folder is keyed on the meeting id, so every occurrence of a recurring series collapses into one folder while two unrelated meetings that happen to share a title stay separate. Each session gets its own child scope named from the session start time, so same-day occurrences cannot collide.
Content attributes
Attribute | Value |
|---|---|
Source kind |
|
Source name |
|
Transcript MIME type |
|
Recording MIME type |
|
Recording ingestion mode |
|
Metadata stamped on the ingested content includes content_correlation_id (shared by the transcript, its recording, and its reports), subject, organizer_email, participant_emails, start_datetime, and end_datetime.
Access control
Access is applied at the scope level as part of ingestion:
The meeting organiser receives read and write access
Meeting participants receive read access
Participants are resolved to Unique accounts by email or username; those that do not resolve are skipped
Later sharing from the Recordings area adds access on top of this, mapped as Can view → read, Can edit → write, Can manage → manage.
How the Recordings area reads the data
The Recordings area queries the knowledge base rather than Teams MCP — it has no dependency on the server being reachable once content has been ingested:
The list query selects content with source kind
MICROSOFT_365_TEAMSwhose MIME type istext/vtt(the transcript is the primary record of a meeting)The video is resolved by looking up content that shares the transcript's
content_correlation_idReports are resolved the same way, filtered to report artifacts
Transcript, report, and video payloads are fetched as files; the video is streamed via a short-lived URL
Because every read is a normal knowledge-base read, standard access control applies: users only ever see the meetings they were granted access to.
Subscription lifecycle
A Microsoft Graph webhook subscription must be active for meetings to be captured automatically. The server manages the whole lifecycle and exposes it through the tools described in Ingestion tools.
Transcripts only
This subscription covers meeting transcript capture. Teams chat and channel messages are read live through Microsoft Graph and are never subscribed to or ingested. The ingest_meeting tool does not require a subscription — it pulls a single meeting's transcript on demand.
Creation
Ingestion is opt-in per user: a subscription is created when the user calls start_kb_integration, not automatically on connect — unless the operator sets UNIQUE_AUTO_START_INGESTION, which enqueues a subscription for every user at login.
start_kb_integrationinvokes the subscription create serviceA Graph subscription is created for the resource
users/{providerUserId}/onlineMeetings/getAllTranscriptswithchangeType: createdThe subscription registers both a
notificationUrl(transcript notifications) and alifecycleNotificationUrl(lifecycle events), authenticated with theclientStatewebhook secretThe subscription record is stored in the database with the Graph subscription id and its expiration time
The expiration time is set to the next occurrence of the configured off-peak UTC hour (MICROSOFT_SUBSCRIPTION_EXPIRATION_TIME_HOURS_UTC), which batches all renewals into one predictable window.
Renewal
Subscriptions are renewed, never recreated:
Microsoft sends a reauthorizationRequired lifecycle notification before expiry (timing is not guaranteed, but notifications arrive at least ~15 minutes ahead). The server PATCHes the subscription with a new expiration and updates its record.
Recreation is deliberately avoided because it loses transcripts: Microsoft Graph only notifies about transcripts created while a subscription is active, so the gap between a DELETE and the following POST is a permanent hole. Renewal keeps the subscription continuously active, preserves the subscription id, and costs fewer API calls.
The database is the source of truth: a lifecycle notification for a subscription id that has no matching record is ignored.
Status
Status | Meaning | Action |
|---|---|---|
| Subscription valid, more than 15 minutes until expiry | None required |
| 15 minutes or less until expiry | Renewal is automatic; no action needed |
| Subscription has lapsed | Call |
| No subscription exists | Call |
Subscription failure handling
Microsoft Graph sends lifecycle notifications when a subscription's state changes. The server acts on two of them automatically and discards the rest.
Condition | What happens | User action required? |
|---|---|---|
| Server PATCHes the subscription with a new expiration time | No |
| Server deletes the local record (Graph has already removed it) — capture stops | Yes — call |
Other lifecycle events (e.g. | Discarded and logged; no recovery action is taken | No, but missed transcripts are not recovered |
Subscription expired (missed renewal) | Status reports | Yes — call |
No subscription exists | Status reports | Yes — call |
No automatic gap recovery
There is no catch-up pass for transcripts missed while a subscription was lapsed, or after a missed lifecycle event. To capture a specific meeting whose transcript was not ingested, use ingest_meeting with the meeting's join URL.
Ingestion tools
These four MCP tools are registered only when UNIQUE_INTEGRATION=enabled. Chat-only deployments do not expose them. For the eight chat and messaging tools, see Teams MCP - Tools.
Tool | Mutating | Description |
|---|---|---|
Yes | Ingest a specific meeting's transcript on demand | |
No | Check the capture subscription status | |
Yes | Start automatic capture | |
Yes | Stop automatic capture |
ingest_meeting
Ingest a specific Teams meeting's transcript on demand, identified by its join URL. Use this to capture a meeting that predates the integration, or to re-pull a single occurrence. The caller must be the organiser or an invited attendee. Ingestion runs asynchronously; the tool returns once the transcript is queued.
Interactive transcript selection
When a recurring meeting has multiple transcripts and no date is given, the tool prompts the user to choose via MCP elicitation. If the client does not support elicitation, pass an explicit date (YYYY-MM-DD).
Input parameters:
Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| string (URL) | Yes | — | The Teams meeting join URL ( |
| string (ISO date) | No | — | Day ( |
Returns:success, a human-readable message, meeting (id, subject, joinUrl — or null if not found), and queued (array of { transcriptId, createdDate } for each transcript queued for ingestion).
Example:
{
"success": true,
"message": "Queued 1 transcript(s) for ingestion. They will appear in the knowledge base shortly.",
"meeting": { "id": "MSo...", "subject": "Q2 Planning", "joinUrl": "https://teams.microsoft.com/l/meetup-join/..." },
"queued": [{ "transcriptId": "MSMjMCMj...", "createdDate": "2024-06-01T10:05:00.000Z" }]
}verify_kb_integration_status
Check whether automatic capture is active, expiring soon, expired, or not configured.
Input parameters: None
Returns:status (active | expiring_soon | expired | not_configured), a message, and subscription (id, expiresAt, minutesUntilExpiration, createdAt, updatedAt — or null when not configured). See Status for the meaning of each value.
start_kb_integration
Start automatic capture of meeting transcripts. Safe to call at any time — it is idempotent and inspects the existing subscription before acting:
No subscription exists: creates a new subscription (
created)Valid subscription, more than 15 minutes until expiry: returns
already_active, no changes madeValid subscription, expiring within 15 minutes: returns
expiring_soon, no changes made — automatic renewal is either in progress or imminent, so a new subscription is deliberately not forced (avoids racing an in-flight renewal)Expired subscription: deletes the lapsed record and creates a fresh subscription (
created)
Input parameters: None
Returns:success, a message, and subscription (id, expiresAt, minutesUntilExpiration, status — one of created, already_active, expiring_soon).
stop_kb_integration
Stop automatic capture. Deletes the local subscription record (the source of truth) and then issues DELETE /subscriptions/{id} to Microsoft Graph.
Previously ingested transcripts and recordings remain in the Unique knowledge base — this tool does not delete content. To resume capture, call start_kb_integration again.
Input parameters: None
Returns:success, a message, and subscription (id, status — removed or not_found — or null when nothing was active).
Graph deletion is best-effort
Because the database is the source of truth, the local record is removed first. If the subsequent Graph DELETE fails, the orphaned Graph subscription is harmless: any later notification it produces is discarded because no matching record exists.
Microsoft Graph constraints
The following limits originate in the Microsoft Graph API and cannot be worked around while using delegated permissions. Delegated permissions are what allow a user to connect their own Microsoft account without IT involvement; switching to application permissions would lift these limits but would require tenant administrators to configure Application Access Policies via PowerShell for every individual user.
No delta sync
Microsoft Graph exposes delta APIs for transcripts and recordings:
GET /users/{userId}/onlineMeetings/getAllTranscripts(...)/delta
GET /users/{userId}/onlineMeetings/getAllRecordings(...)/deltaThey support both full initial synchronisation and incremental sync, but only with application permissions:
Permission type | Support |
|---|---|
Delegated (work or school account) | Not supported |
Delegated (personal Microsoft account) | Not supported |
Application |
|
Capture therefore relies entirely on real-time change notifications, which cover everything going forward but cannot recover anything missed during a subscription gap.
Source: callTranscript: delta · callRecording: delta
No historical or full sync
The only API that lists transcripts across all of a user's meetings without knowing meeting ids in advance is getAllTranscripts:
GET /users/{userId}/onlineMeetings/getAllTranscripts(meetingOrganizerUserId='{userId}',startDateTime=...)It, too, requires application permissions. With delegated permissions the only available path is GET /users/{userId}/onlineMeetings/{meetingId}/transcripts, which needs the meeting id up front — so bulk enumeration of past meetings is impossible. Meetings that took place before a user enabled capture can only be ingested one at a time with ingest_meeting.
Source: onlineMeeting: getAllTranscripts
Additional limits on historical data, even with application permissions:
Transcripts are only accessible for meetings that have not expired. One-time meetings expire 60 days after their scheduled time; recurring meetings with no end date expire one year after the last activity.
Recording and transcript files are subject to the tenant's admin-configured expiration policy (Microsoft's default: 120 days after creation).
Source: Limits and specifications for Microsoft Teams
Required Microsoft Graph permissions
Capture requires four delegated scopes in addition to the chat and messaging scopes every Teams MCP server requests. A chat-only deployment requests none of these four — they exist only when UNIQUE_INTEGRATION=enabled.
Permission | Type | ID | Admin consent | Why it is needed |
|---|---|---|---|---|
| Delegated | No | Resolve meeting details and participants for a transcript | |
| Delegated | No | Read online meeting metadata by id | |
| Delegated | Yes | Read transcript content | |
| Delegated | Yes | Read recording content |
Grant admin consent for the two privileged scopes with the URL in Grant admin consent.
Least-privilege justification
Calendars.Read
Aspect | Detail |
|---|---|
Purpose | Read the user's calendar events |
Used For | Determining if a meeting is recurring by querying the calendar event associated with an online meeting |
Why Not Less | No narrower permission exists for reading calendar events |
Why Not | We don't create or modify calendar events, only read them |
OnlineMeetings.Read
Aspect | Detail |
|---|---|
Purpose | Read meeting metadata (subject, start/end time, participants) |
Used For | Fetching meeting details when a transcript notification arrives |
Why Not Less | No narrower permission exists for reading meeting data |
Why Not | We don't create or modify meetings, only read them |
OnlineMeetingTranscript.Read.All
Aspect | Detail |
|---|---|
Purpose | Read transcripts from all meetings the user can access |
Used For | Downloading VTT transcript content for ingestion |
Why Not Less | No per-meeting transcript permission exists; |
Why Not Application Permission | Would require tenant admin to create Application Access Policies per-user; impractical for self-service MCP connections |
Admin Consent | Required because transcripts may contain sensitive meeting content |
OnlineMeetingRecording.Read.All
Aspect | Detail |
|---|---|
Purpose | Read recordings from all meetings the user can access |
Used For | Downloading MP4 recording files to store alongside transcripts |
Why Not Less | No per-meeting recording permission exists; |
Why Not Application Permission | Would require tenant admin to create Application Access Policies per-user; impractical for self-service MCP connections |
Admin Consent | Required because recordings contain audio/video of meetings |
The chat and messaging scopes, and the rationale for using delegated rather than application permissions throughout, are in Teams MCP - Permissions.
Related Documentation
Recordings & Transcripts — what the feature is and who it is for
Operator Manual — configuration and enablement
FAQ — frequently asked questions
Teams MCP - Technical Manual — the server's architecture, flows, and security model
Teams MCP - Flows — user connection, OAuth, and token refresh sequences
Teams MCP - Security — token encryption, webhook validation, and threat model
Standard References
Microsoft Graph Change Notifications - Subscription and notification model
Microsoft Graph Webhooks - Lifecycle Notifications - Renewal and lifecycle events
Microsoft Graph Permissions Reference - Permission details
OnlineMeetingTranscript.Read.All - Third-party permission explorer
OnlineMeetingRecording.Read.All - Third-party permission explorer
Calendars.Read - Third-party permission explorer