Transcripts MCP Server
Click on "Install 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., "@Transcripts MCP ServerGet the transcript for my 'Project Kickoff' meeting from earlier today."
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.
Transcripts MCP Server
A remote Model Context Protocol (MCP) server that retrieves Microsoft Teams meeting transcripts via the Microsoft Graph API, using delegated OAuth 2.0 On-Behalf-Of (OBO) authentication.
Hosted on Azure Container Apps and designed for integration with Microsoft Copilot Studio (via the MCP Wizard), though any MCP-compatible client can connect.
Table of Contents
Use Cases
Once an AI agent can access the full text of a meeting, the transcript becomes a launchpad for downstream automation:
Use Case | Description |
Sentiment analysis | Gauge how a customer call actually went — detect frustration, satisfaction, or escalation patterns across every interaction, not just the ones a manager happened to attend. |
Follow-on automation | Extract action items, decisions, and deadlines, then push them into Power Automate flows — create Planner tasks, send follow-up emails, or update CRM records automatically. |
Customer service reviews | Audit support calls at scale without replaying hours of recordings. Search across transcripts for specific topics, complaints, or compliance language. |
Deal intelligence | Surface objections, competitor mentions, pricing commitments, and next steps from sales calls — feed them into your pipeline reporting. |
Training & coaching | Identify coaching moments by analysing how reps handle objections, discovery questions, or product demos. Compare top performers against the team. |
Compliance & audit | Verify that required disclosures, disclaimers, or consent language were delivered during regulated conversations. |
Meeting summaries on demand | Let users ask an agent "What did we decide in the design review?" and get a structured answer — without anyone having to write meeting notes. |
The server returns clean, speaker-attributed text — ready for any LLM to analyse, summarise, or act on.
Features
Three MCP tools: List meetings, retrieve transcripts, and save transcripts to SharePoint for RAG / archival
Delegated-only permissions: The server never has its own access — every Graph call runs in the signed-in user's context via OBO
Calendar-based discovery: Uses
/me/calendarViewto find meetings, then resolves each to an online meeting ID — works around severe/me/onlineMeetingsAPI limitationsOptimised name search: Filters calendar events by subject before resolving to online meetings (avoids unnecessary API calls)
VTT cleaning: Strips all WebVTT metadata (timestamps, cue IDs, NOTE blocks, HTML tags) and merges consecutive same-speaker lines into readable paragraphs
Comprehensive logging: All Graph API calls are traced with
[graph]prefixes for debuggingStateless container: Scales to zero when idle, ~250ms cold start on Alpine Node.js 20
Built for Copilot Studio: Drop-in MCP server with OAuth 2.0 wizard support
Combining with Other MCP Servers
MCP is designed to be composable — a single Copilot Studio agent can connect to multiple MCP servers simultaneously, each providing different tools. This Transcripts MCP server becomes significantly more powerful when paired with other Microsoft 365 MCP servers.
With the Office 365 Outlook / Meeting Management MCP
Copilot Studio includes a built-in Office 365 Outlook MCP connector (Meeting Management MCP Server) that provides tools for listing, creating, and managing calendar events. When both servers are connected to the same agent:
Agent capability | How it works |
"What meetings do I have today?" | The Outlook MCP lists today's calendar events (including non-Teams meetings). |
"Get the transcript from the Design Review" | The Transcripts MCP finds the meeting and returns the cleaned transcript. |
"Summarise my Monday standup and create follow-up tasks" | The agent chains both servers — retrieves the transcript, then uses the Outlook MCP to schedule follow-up meetings or send recap emails. |
The LLM in Copilot Studio automatically decides which MCP server's tools to call based on the user's prompt. No manual routing is needed — the agent sees all available tools from all connected servers and plans accordingly.
Multi-Tenant Agents (Preview)
Copilot Studio now supports multi-tenant agents as a preview feature, allowing you to deploy a single agent across multiple Entra ID tenants. Combined with a remote MCP server like this one (hosted on Azure Container Apps with OBO auth), you can offer transcript-powered AI agents as a managed service to multiple organisations — each user authenticates with their own tenant and only sees their own meetings.
Example: Multi-Server Agent Architecture
┌─────────────────────────────────────────────────────────────┐
│ Copilot Studio Agent │
│ │
│ Connected MCP Servers: │
│ ┌─────────────────────────┐ ┌──────────────────────────┐ │
│ │ Office 365 Outlook MCP │ │ Transcripts MCP Server │ │
│ │ (Built-in connector) │ │ (This repo) │ │
│ │ │ │ │ │
│ │ • List meetings │ │ • list_recent_meetings │ │
│ │ • Create events │ │ • get_meeting_transcript │ │
│ │ • Send emails │ │ • save_transcript │ │
│ │ • Manage calendar │ │ │ │
│ └─────────────────────────┘ └──────────────────────────┘ │
│ │
│ User: "What did we agree in the TredStone meeting? │
│ Schedule a follow-up for next Tuesday." │
│ │
│ Agent plan: │
│ 1. get_meeting_transcript("TredStone") → Transcripts MCP │
│ 2. Summarise action items from transcript │
│ 3. Create calendar event → Outlook MCP │
└─────────────────────────────────────────────────────────────┘Architecture
┌──────────────────┐ HTTPS + Bearer Token ┌─────────────────────────┐
│ │ ──────────────────────────► │ Azure Container Apps │
│ Copilot Studio │ │ (MCP Server) │
│ (MCP Client) │ ◄────────────────────────── │ │
│ │ JSON-RPC (MCP Protocol) │ Express + Streamable │
└──────────────────┘ │ HTTP Transport │
└────────────┬────────────┘
│
OBO Token │ Graph Token
Exchange │
▼
┌─────────────────────────┐
│ Microsoft Graph API │
│ │
│ /me/calendarView │
│ /me/onlineMeetings │
│ ?$filter=JoinWebUrl │
│ /{id}/transcripts │
│ /{tid}/content │
└─────────────────────────┘Auth Flow
User in Copilot Studio
│
├─1─► Sign in via OAuth 2.0 → gets token scoped to api://<client-id>/access_as_user
│
├─2─► Copilot sends MCP request with Authorization: Bearer <user-token>
│
├─3─► MCP Server extracts bearer token from request header
│
├─4─► MSAL OBO flow exchanges user token → Microsoft Graph token (delegated)
│
└─5─► Graph API calls execute as the signed-in user (never app-level)Key design decision: All Graph API permissions are delegated — the server only accesses meetings and transcripts the signed-in user has permission to see. There is no application-level access.
How It Works — Internals
This section explains the internal workings of the server for contributors and anyone wanting to understand the design decisions.
End-to-End Pipeline
When a user asks "Get the transcript for the Design Review meeting", the following chain executes:
User prompt
│
▼
Copilot Studio (LLM) decides to call get_meeting_transcript(meetingName="Design Review")
│
▼
MCP JSON-RPC POST to /mcp with Bearer token
│
▼
┌─ server.ts ──────────────────────────────────────────────────────────────────┐
│ 1. Extract bearer token from Authorization header │
│ 2. MSAL OBO exchange → Microsoft Graph delegated token │
│ 3. Create stateless MCP Server instance, wire tool handlers │
│ 4. Route to handleGetMeetingTranscript() │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─ graph.ts ───────────────────────────────────────────────────────────────────┐
│ findMeetingsByName("Design Review") │
│ │
│ 5. GET /me/calendarView?startDateTime=...&endDateTime=... │
│ → Returns all calendar events in the date range (30 days back, │
│ 7 days forward by default) │
│ │
│ 6. Filter by subject name first (case-insensitive partial match) │
│ → "Design Review" matches "Weekly Design Review" ✓ │
│ │
│ 7. Filter for events with a Teams join URL (onlineMeeting.joinUrl) │
│ │
│ 8. For ONLY matching events, resolve via: │
│ GET /me/onlineMeetings?$filter=JoinWebUrl eq '<joinUrl>' │
│ → Returns the onlineMeeting object with the meeting ID │
│ → Falls back to decoded URL if exact match fails │
│ │
│ 9. GET /me/onlineMeetings/{meetingId}/transcripts │
│ → List available transcripts │
│ │
│ 10. GET /me/onlineMeetings/{meetingId}/transcripts/{tid}/content?$format= │
│ text/vtt → Download the raw WebVTT transcript │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─ vtt-parser.ts ──────────────────────────────────────────────────────────────┐
│ 11. Strip WEBVTT header, timestamps, cue IDs, NOTE blocks, HTML tags │
│ 12. Convert <v Speaker Name>text</v> → "Speaker Name: text" │
│ 13. Merge consecutive same-speaker lines into paragraphs │
└──────────────────────────────────────────────────────────────────────────────┘
│
▼
Return to Copilot Studio as plain-text speaker-attributed dialogue:
Meeting: Weekly Design Review
Date: 2026-02-18T15:00:00Z
---
Alice Smith: We need to finalise the mockups by Friday.
Bob Jones: I've updated the Figma file. The navigation flow is ready for review.
Alice Smith: Great, let's walk through it now...This is 5 separate Graph API calls per transcript retrieval (calendarView → onlineMeetings → transcripts → content), but by filtering by name before resolving meetings, the server avoids unnecessary API calls for events the user didn't ask about.
Meeting Discovery (Calendar API)
The server uses /me/calendarView instead of /me/onlineMeetings for meeting discovery. This was a deliberate architectural decision driven by severe undocumented limitations in the /me/onlineMeetings endpoint:
What you'd expect to work | What actually happens |
| 400 — endpoint requires |
| 400 — |
| 400 — |
| 400 — |
The only supported filter on /me/onlineMeetings is JoinWebUrl eq '...' — which requires you to already know the join URL.
Solution: Use the Calendar API (/me/calendarView) which supports date ranges natively, include onlineMeeting in $select, then filter client-side for events with a join URL. Resolve each join URL via /me/onlineMeetings?$filter=JoinWebUrl eq '...' to get the meeting ID needed for transcript access.
Meeting Resolution (OnlineMeetings API)
Each calendar event with a Teams join URL must be resolved to an onlineMeeting object. This is handled by resolveOnlineMeeting():
Try exact match:
$filter=JoinWebUrl eq '<joinUrl>'Try decoded URL: Some Graph tenants store the decoded form —
decodeURIComponent(joinUrl)is tried if the exact match failsNon-throwing: Resolution failures are logged (
[graph] GET failed:) rather than silently swallowed, so cross-tenant meetings (403) or network issues are visible in container logs
Cross-tenant meetings: If the user's calendar contains meetings organised in a different Entra ID tenant, the /me/onlineMeetings endpoint returns 403. This is expected — the meeting object belongs to the organiser's tenant. The server logs these and continues to the next event.
Transcript Download & Cleaning
Raw Teams transcripts are in WebVTT format and contain significant metadata:
WEBVTT
617c22e3-ccc5-445a-b806-be21f6abb3be
00:00:00.000 --> 00:00:05.840
<v Graham Hosking>We need to discuss the Q4 roadmap.</v>
617c22e3-ccc5-445a-b806-be21f6abb3be
00:00:05.840 --> 00:00:08.120
<v Graham Hosking>First item is the timeline.</v>
a1b2c3d4-e5f6-7890-abcd-ef1234567890
00:00:08.120 --> 00:00:12.000
<v Sarah Chen>I've prepared the Gantt chart.</v>The cleanVttTranscript() function in vtt-parser.ts:
Strips: WEBVTT header, all timestamp lines (
00:00:00.000 --> ...), cue IDs (numeric and UUID), NOTE blocks,<v>and</v>HTML voice tags (converting toSpeaker: textformat), any remaining HTML tagsMerges: Consecutive lines from the same speaker into single paragraphs
Output:
Graham Hosking: We need to discuss the Q4 roadmap. First item is the timeline.
Sarah Chen: I've prepared the Gantt chart.MCP Transport
The server uses Streamable HTTP transport in stateless mode:
One
Serverinstance per request: A fresh MCPServeris created for every incomingPOST /mcp, wired with the user's Graph token, and disposed after the response. No sessions are maintained.sessionIdGenerator: undefined: Disables MCP session management — each request is independent.Why stateless: Container Apps scales to zero when idle. Stateful sessions would break across cold starts and replica restarts. Copilot Studio sends every tool call as an independent HTTP request with its own bearer token, so session state is unnecessary.
Implementation note: The server uses the low-level
Serverclass from@modelcontextprotocol/sdk, not the higher-levelMcpServerclass. This avoids a TypeScriptTS2589(deep type instantiation) error triggered by Zod's optional schemas in the SDK's type inference. The low-level API works identically but requires manualsetRequestHandler()wiring.
Authentication Chain
Copilot Studio user signs in
│
▼ (OAuth 2.0 Authorization Code flow)
Entra ID issues token scoped to: api://<client-id>/access_as_user
│
▼ (Copilot Studio sends to MCP server)
server.ts extracts Bearer token from Authorization header
│
▼
auth.ts: MSAL ConfidentialClientApplication.acquireTokenOnBehalfOf()
│
▼ (OBO flow — exchanges user token for Graph token)
Entra ID issues delegated Graph token with scopes:
- User.Read
- Calendars.Read
- OnlineMeetings.Read
- OnlineMeetingTranscript.Read.All
│
▼
graph.ts uses delegated token for all API calls → runs as the signed-in userSecurity properties:
The server's client secret authenticates the app to Entra ID, but the access is always the user's
If a user doesn't have access to a meeting or transcript, Graph will deny the request
Tokens are never stored — each request does a fresh OBO exchange
No application-level permissions are used
Tools
list_recent_meetings
Lists recent Microsoft Teams online meetings for the signed-in user.
Parameter | Type | Required | Description |
| string | No | Filter meetings to this date (YYYY-MM-DD) |
| number | No | Maximum results to return (default: 10, max: 50) |
Returns: Meeting subject, start/end times, meeting ID, and whether a transcript is available.
get_meeting_transcript
Retrieves and cleans the transcript for a specific Teams meeting.
Parameter | Type | Required | Description |
| string | Yes | Meeting subject to search for (partial match, case-insensitive) |
| string | No | Date filter (YYYY-MM-DD) to narrow results |
Returns: Clean speaker-attributed text with all VTT metadata stripped. The output is ready for AI summarisation, action item extraction, or semantic search.
save_transcript
Retrieves a meeting transcript and saves it to a SharePoint document library as a Markdown file. The file includes speaker attribution and is formatted for RAG indexing (e.g. by Microsoft 365 Copilot or Azure AI Search). Also returns the transcript text in the response for immediate use.
Parameter | Type | Required | Description |
| string | Yes | Meeting subject to search for (partial match, case-insensitive) |
| string | No | Date filter (YYYY-MM-DD) to narrow results |
| string | No | SharePoint site URL (e.g. |
| string | No | Folder path in the document library (e.g. |
Returns: The cleaned transcript text plus a confirmation with the SharePoint web URL of the uploaded file.
File naming: {Subject}_{YYYY-MM-DD}.md — e.g. Design_Review_2026-02-18.md
RAG integration: Files saved to SharePoint are automatically indexed by Microsoft 365 Copilot (no extra setup). For custom RAG, use the Azure AI Search SharePoint indexer to pull content into your own search index.
Prerequisites
Azure Subscription with Container Apps support
Azure Container Registry (Basic SKU is sufficient)
Microsoft Entra ID — ability to create App Registrations and grant admin consent
Node.js ≥ 20 (for local development only)
Docker (optional — ACR can build images remotely via
az acr build)Azure CLI (
az) installed and logged inA Microsoft 365 licence with Teams meetings and transcription enabled
Quick Start
# 1. Clone and install
git clone https://github.com/<your-org>/TranscriptsMCP.git
cd TranscriptsMCP
npm install
# 2. Configure environment
cp .env.example .env
# Edit .env with your Azure App Registration credentials:
# AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID
# 3. Build and run
npm run build
npm start
# → Server running on http://localhost:8080
# 4. Health check
curl http://localhost:8080/health
# → {"status":"ok","service":"transcripts-mcp-server"}For production deployment to Azure, see Deploy to Azure Container Apps.
Azure App Registration Setup
1. Register the Application
Click New registration
Name:
Transcripts MCP ServerSupported account types: Single tenant (your org only)
Redirect URI: Leave blank (added in step 5)
Note the Application (client) ID and Directory (tenant) ID
2. Create a Client Secret
Go to Certificates & secrets → New client secret
Description:
mcp-server-secret, Expiry: 24 monthsCopy the secret value immediately — it won't be shown again
3. Expose an API (Required for OBO)
This is the critical step that enables the On-Behalf-Of flow. Without it, the OBO token exchange will fail.
Go to Expose an API
Click Set next to "Application ID URI" → accept the default
api://<client-id>Click Add a scope:
Scope name:
access_as_userWho can consent: Admins and users
Admin consent display name:
Access Transcripts MCP as userAdmin consent description:
Allows the app to access meeting transcripts on behalf of the signed-in userUser consent display name:
Access your meeting transcriptsUser consent description:
Allows this app to read your Teams meeting transcriptsState: Enabled
The full scope URI will be:
api://<client-id>/access_as_user
4. Configure API Permissions
Go to API permissions → Add a permission → Microsoft Graph → Delegated permissions
Add these four permissions:
Permission
Purpose
User.ReadSign in and read user profile; enables
/meendpointsCalendars.ReadRead calendar events via
/me/calendarViewto discover Teams meetingsOnlineMeetings.ReadLook up online meeting details via
/me/onlineMeetings?$filter=JoinWebUrl eq '...'OnlineMeetingTranscript.Read.AllRead transcript metadata and content
Sites.ReadWrite.AllUpload transcript files to SharePoint (for
save_transcripttool)Click Grant admin consent for [your tenant]
Important: After granting admin consent, verify the consent grant includes all five scopes. If the grant was created before all permissions were added, you may need to update it. See Troubleshooting → Verifying Admin Consent Grants.
5. Configure Authentication (Redirect URIs)
Go to Authentication → Add a platform → Web
Add the following redirect URIs:
URI
Purpose
https://token.botframework.com/.auth/web/redirectBot Framework / Power Platform auth
https://copilotstudio.microsoft.com/auth/callbackCopilot Studio web callback
https://global.consent.azure-apim.net/redirect/<your-connector-id>Copilot Studio MCP connector (provided in the MCP wizard)
Click Save
Note: The third URI is specific to your Copilot Studio connector. When you set up the MCP connection in Copilot Studio, it will display the exact redirect URI you need to register. You must add it or you will get
AADSTS500113.
6. Authorise Client Applications (Optional)
If Copilot Studio provides a client application ID:
Go to Expose an API → Authorised client applications
Add the Copilot Studio client application ID
Check the
access_as_userscope
Environment Variables
Variable | Required | Default | Description |
| Yes | — | Application (client) ID from App Registration |
| Yes | — | Client secret value from App Registration |
| Yes | — | Directory (tenant) ID |
| No |
| HTTP server port |
| No | — | Default SharePoint site for |
| No |
| Default folder path in the document library |
Local Development
# Clone and install
git clone https://github.com/<your-org>/TranscriptsMCP.git
cd TranscriptsMCP
npm install
# Configure environment
cp .env.example .env
# Edit .env with your Azure App Registration credentials
# Build TypeScript
npm run build
# Start server
npm startThe server will start on http://localhost:8080 (or the port specified in .env).
Test Endpoints
# Health check (no auth required)
curl http://localhost:8080/health
# → {"status":"ok","service":"transcripts-mcp-server"}
# MCP endpoint without auth (should return 401)
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
# → 401 Unauthorized (expected — auth is enforced)Docker Build & Run
# Build locally
docker build -t transcripts-mcp-server .
# Run locally
docker run -p 8080:8080 \
-e AZURE_CLIENT_ID=<your-client-id> \
-e AZURE_CLIENT_SECRET=<your-client-secret> \
-e AZURE_TENANT_ID=<your-tenant-id> \
transcripts-mcp-serverThe Dockerfile uses a multi-stage build (node:20-alpine) with a non-root user for security:
Builder stage: Installs all dependencies, compiles TypeScript
Production stage: Copies only compiled JS + production dependencies, runs as
mcpuser(non-root)
Deploy to Azure Container Apps
Deploy from Scratch
# 1. Login and set subscription
az login
az account set --subscription <subscription-id>
# 2. Create resource group
az group create --name rg-transcripts-mcp --location <region>
# 3. Create Azure Container Registry
az acr create --resource-group rg-transcripts-mcp \
--name <your-acr-name> --sku Basic --admin-enabled true --location <region>
# 4. Build image in ACR (no local Docker needed)
az acr build --registry <your-acr-name> \
--image transcripts-mcp-server:latest --file Dockerfile .
# 5. Create Container Apps Environment
az containerapp env create \
--resource-group rg-transcripts-mcp \
--name cae-transcripts-mcp \
--location <region>
# 6. Get ACR credentials
ACR_PWD=$(az acr credential show --name <your-acr-name> \
--query 'passwords[0].value' -o tsv)
# 7. Create Container App
az containerapp create \
--resource-group rg-transcripts-mcp \
--name transcripts-mcp-server \
--environment cae-transcripts-mcp \
--image <your-acr-name>.azurecr.io/transcripts-mcp-server:latest \
--registry-server <your-acr-name>.azurecr.io \
--registry-username <your-acr-name> \
--registry-password "$ACR_PWD" \
--target-port 8080 \
--ingress external \
--min-replicas 0 --max-replicas 1 \
--cpu 0.25 --memory 0.5Gi \
--env-vars \
AZURE_CLIENT_ID=<client-id> \
AZURE_CLIENT_SECRET=<client-secret> \
AZURE_TENANT_ID=<tenant-id> \
PORT=8080
# 8. Get the FQDN
az containerapp show \
--resource-group rg-transcripts-mcp \
--name transcripts-mcp-server \
--query 'properties.configuration.ingress.fqdn' -o tsv
# 9. Verify
curl https://<your-app-fqdn>/healthRedeploy After Code Changes
# 1. Rebuild image in ACR
az acr build --registry <your-acr-name> \
--image transcripts-mcp-server:<version-tag> \
--file Dockerfile .
# 2. Update the container app
az containerapp update \
--resource-group rg-transcripts-mcp \
--name transcripts-mcp-server \
--image <your-acr-name>.azurecr.io/transcripts-mcp-server:<version-tag>
# 3. Verify
curl https://<your-app-fqdn>/healthView Container Logs
az containerapp logs show \
--resource-group rg-transcripts-mcp \
--name transcripts-mcp-server \
--type console --tail 50All Graph API operations are logged with [graph] prefixes, making it straightforward to trace the pipeline:
[graph] calendarView request: 2026-01-19T06:35:38Z → 2026-02-25T06:35:38Z
[graph] calendarView returned 8 events
[graph] 4 of 8 events have a Teams join URL
[graph] Resolved 3 online meetings
[graph] findMeetingsByName("Design Review"): 1 name matches out of 8 events
[graph] findMeetingsByName resolved 1 meetingsUpdate Environment Variables
az containerapp update \
--resource-group rg-transcripts-mcp \
--name transcripts-mcp-server \
--set-env-vars "AZURE_CLIENT_SECRET=<new-secret>"Copilot Studio Integration
MCP Wizard Configuration
In Copilot Studio, open your agent → Tools → Add a tool
Select MCP Server
Enter the MCP server URL:
URL:
https://<your-app-fqdn>/mcp
Select Authentication: OAuth 2.0
Fill in the OAuth 2.0 settings:
Field
Value
Client ID
Your Application (client) ID
Client Secret
Your client secret value
Authorization URL
https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorizeToken URL
https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/tokenRefresh URL
https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token(same as Token URL)Scope
api://<client-id>/access_as_userCopy the redirect URI shown by the wizard (e.g.,
https://global.consent.azure-apim.net/redirect/...)Register the redirect URI in the App Registration (see Step 5)
Test the connection — you should see both tools discovered:
list_recent_meetingsget_meeting_transcript
Example Prompts
Once connected, users can ask the Copilot:
"What meetings do I have today?"
"Show me my recent meetings"
"Get the transcript from the Design Review meeting"
"What did Sarah say in yesterday's standup?"
"Summarise the TredStone meeting from Tuesday"
Example Output
User: "Get the transcript for the TredStone meeting"
Copilot Studio calls get_meeting_transcript(meetingName="TredStone"), which returns clean speaker-attributed text with meeting metadata:
Meeting: TredStone - Meetings
Date: 2026-02-18T19:00:00Z
Meeting link: https://teams.microsoft.com/l/meetup-join/19%3ameeting_OGY0...
Transcript ID: ktVizInGAAAAi_B6lATZRTE5...
Transcript created: 2026-02-18T06:41:57Z
---
Graham Hosking: on optimising our Microsoft solutions to address some key
pain points and unlock new possibilities. We all know that navigating the
vast landscape of Microsoft can be challenging, from licencing to integration.
One significant pain point is ensuring seamless collaboration across different
departments...The agent then analyses the transcript and presents structured insights to the user.
Using the Meeting Link for Downstream Actions
Every transcript response includes a Meeting link — the Teams join URL for that meeting. This link points back to the original Teams meeting where the full recording, attendance report, and rich transcript (with timestamps and speaker timeline) are stored.
This is important for the use cases described earlier:
Use Case | How the Meeting Link Helps |
Compliance & audit | The link provides a verifiable reference back to the original meeting record. Auditors can click through to Teams to access the full recording and attendance list. |
Customer service reviews | Managers can share the meeting link with coaches or reviewers so they can listen to specific sections of the recording alongside the transcript. |
Training & coaching | The link allows trainers to jump directly into the Teams meeting to replay key moments — the AI-cleaned transcript identifies what was said, the recording shows how it was said. |
Deal intelligence | Sales leaders can follow the link to review the full meeting context when the transcript flags an objection or competitor mention. |
Follow-on automation | Power Automate flows can use the meeting link as a reference URL when creating Planner tasks or CRM entries from action items. |
The agent can also hand the meeting link to other MCP tools (e.g., the Office 365 Outlook MCP) to schedule follow-up meetings that reference the original discussion.
Agent Instructions
To get the best results from a Copilot Studio agent connected to this MCP server, use structured agent instructions that tell the agent how to find information (via MCP tools) rather than relying on knowledge or memory.
Below is a recommended set of agent instructions. Paste this into your Copilot Studio agent's Instructions field:
You are a digital employee named "Meeting Agent" who serves as a persistent organizational actor for client meetings.
Your Role
You can be invited to client meetings and your core responsibility is to:
Retrieve meeting transcripts from past meetings
Capture rich, structured notes from those transcripts
Take autonomous action after explicit confirmation
How You Find Information
You MUST use MCP tools to find meetings and transcripts. Never attempt to answer from knowledge or memory.
Step 1 — Find the meeting
Use the Meeting Management MCP (Office 365 Outlook) tool list_meetings or similar calendar tools to find the meeting the user is asking about. This returns the meeting subject, date/time, and attendees.
Step 2 — Get the transcript Use the Transcripts MCP Server tools:
list_recent_meetings— to browse recent Teams meetings and check transcript availabilityget_meeting_transcript— to retrieve the full cleaned transcript by meeting name (and optionally date). Pass the meeting subject from Step 1 as themeetingNameparameter.
Step 3 — Analyse and respond Once you have the transcript text, analyse it to answer the user's question or produce the structured output described below.
If the user asks about a meeting and you cannot find it via the MCP tools, tell them — do not fabricate or guess content. If a transcript is not available, explain that transcription may not have been enabled for that meeting.
Handling Meeting Metadata
When the transcript response includes a header section (before the --- separator), extract and use:
Meeting link: The Teams join URL — present this when users ask for the meeting link
Date: The meeting date/time
Transcript ID: Reference for the specific transcript
Only show the full transcript when the user explicitly asks for it. For questions about meeting details, extract the relevant metadata from the header.
What You Capture
When analysing a transcript, extract the following structured information:
Sentiment: Overall tone and sentiment of the meeting (positive, neutral, negative, concerned, enthusiastic)
Timeline: Key dates, deadlines, and milestones mentioned
Products/Funds Mentioned: List all products, funds, or services discussed
Sentiment per Product: For each product/fund, note the client's sentiment and interest level
Action Items: Tasks, follow-ups, and commitments made — include who owns each item and any stated deadline
Decisions Made: Explicit decisions or agreements reached during the meeting
Risks & Concerns: Any risks, blockers, or concerns raised by participants
Next Best Actions: Recommended follow-up actions based on the meeting content
Saving Transcripts
If the user asks to save or archive a transcript to SharePoint, use the save_transcript tool from the Transcripts MCP Server. You can specify a SharePoint site and folder path, or use the server defaults.
Response Format
When summarising a meeting, use this structure:
## Meeting Summary: [Subject]
**Date:** [Date] | **Sentiment:** [Overall sentiment]
**Meeting link:** [Teams join URL]
### Key Discussion Points
- [Bullet-point summary of main topics]
### Products/Funds Discussed
| Product/Fund | Sentiment | Notes |
|---|---|---|
| ... | ... | ... |
### Timeline & Milestones
- [Date] — [Milestone/deadline]
### Decisions Made
- [Decision]
### Action Items
| Action | Owner | Deadline |
|---|---|---|
| ... | ... | ... |
### Risks & Concerns
- [Risk/concern]
### Recommended Next Best Actions
1. [Action]Rules
Always retrieve real data via MCP tools — never guess or use training knowledge for meeting content
If multiple meetings match a search, present the options and ask the user to clarify
Keep summaries factual — attribute statements to speakers where possible
Ask for confirmation before taking any action (sending emails, creating tasks, saving files)
Never show raw JSON — always format responses for readability
Tip: These instructions work with the Meeting Management MCP (Office 365 Outlook) and Transcripts MCP Server connected as tools on the same agent. The agent uses the Outlook MCP for calendar discovery and this server for transcript retrieval — the LLM automatically routes to the right tool based on what the user asks.
Copilot Studio then automatically summarises the raw transcript into structured insights, action items, and highlights for the user.
API Reference
GET /health
Returns server health status. No authentication required.
Response (200):
{ "status": "ok", "service": "transcripts-mcp-server" }POST /mcp
MCP protocol endpoint. Requires Authorization: Bearer <token> header.
Request headers:
Content-Type: application/jsonAccept: application/json, text/event-streamAuthorization: Bearer <user-access-token>
Supported MCP methods:
Method | Description |
| MCP protocol handshake. Returns server capabilities and protocol version. |
| Returns the list of available tools with their input schemas. |
| Executes a tool and returns results. |
Error responses:
Status | Meaning |
401 | Missing or invalid Authorization header |
403 | OBO token exchange failed (bad credentials or consent) |
405 | Wrong HTTP method (GET or DELETE to /mcp) |
500 | Internal server error |
GET /mcp | DELETE /mcp
Returns 405 Method Not Allowed. The MCP transport is Streamable HTTP (POST only, stateless).
Project Structure
TranscriptsMCP/
├── src/
│ ├── server.ts # Express app, MCP server setup, tool routing
│ ├── auth.ts # MSAL ConfidentialClientApplication, OBO token exchange
│ ├── graph.ts # Microsoft Graph API client (meetings, transcripts)
│ └── vtt-parser.ts # WebVTT → clean text parser with speaker merge
├── dist/ # Compiled JavaScript output (generated by tsc)
├── Dockerfile # Multi-stage Docker build (node:20-alpine, non-root)
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript config: ES2022 target, CommonJS modules, strict
├── .env.example # Template for environment variables
├── .gitignore # Ignores node_modules, dist, .env, *.log
├── .dockerignore # Excludes node_modules, dist, .env, .git from Docker context
└── README.md # This fileModule Details
Module | Lines | Purpose | Key Exports |
| ~300 | Express HTTP server + MCP protocol wiring. Creates a new | Express app, |
| ~80 | MSAL OBO token exchange. Creates |
|
| ~290 | Microsoft Graph REST client. Uses Calendar API for meeting discovery, resolves join URLs to online meeting IDs, fetches transcripts. Includes |
|
| ~130 | Strips VTT metadata (headers, timestamps, cue IDs, NOTEs, HTML tags). Converts |
|
Dependencies
Package | Version | Purpose |
| ^1.12.1 | MCP server and Streamable HTTP transport |
| ^2.16.2 | MSAL ConfidentialClientApplication for OBO flow |
| ^4.21.2 | HTTP framework |
| ^3.24.2 | Schema validation (MCP SDK dependency) |
Permissions Deep Dive
Delegated Permissions (Microsoft Graph)
Permission | Type | API Endpoint | Why Needed |
| Delegated |
| Required for sign-in; enables all |
| Delegated |
| Discover Teams meetings from the user's calendar |
| Delegated |
| Resolve calendar events to online meeting IDs |
| Delegated |
| List and download transcript content (VTT) |
| Delegated |
| Upload transcript files to SharePoint document libraries |
Custom Scope
Scope | URI | Purpose |
|
| Exposed by the App Registration to enable the OBO flow. Copilot Studio requests this scope when authenticating the user. |
Security note: The server never accesses meetings with its own application identity. Every Graph API call uses a delegated token obtained via OBO, meaning it runs in the context of the signed-in user. If the user doesn't have access to a meeting or transcript, the Graph API will deny the request.
Teams Admin Requirements
For transcripts to be available, the following must be true:
Transcription must be enabled in the Teams admin centre (or via policy)
A meeting organiser or participant must start transcription during the meeting
The signed-in user must be an organiser or participant of the meeting
Troubleshooting
Authentication Errors
Error | Cause | Solution |
| No bearer token in request | Ensure Copilot Studio is configured with OAuth 2.0 and sends the |
| OBO token exchange failed | Check |
| Missing redirect URI | Add the redirect URI from the Copilot Studio MCP wizard to Authentication → Web → Redirect URIs. |
| Admin consent not granted/incomplete | Click Grant admin consent in API permissions. Verify the grant includes all five scopes (see below). Common pitfall: If you added permissions after the initial consent grant, the grant is NOT automatically updated — you must re-grant or patch it. |
| Wrong client secret or tenant | Regenerate the client secret and update the env var. |
| Redirect URI mismatch | Check for trailing slashes and case sensitivity. |
Token Expiry / Session Errors
Error | Cause | Solution |
| Copilot Studio cached an expired token | Fully refresh the browser, or disconnect and reconnect the MCP connection. Starting a "New conversation" alone is not sufficient. |
| Calendar event is for a meeting in a different Entra ID tenant | Expected behaviour. Server logs these as |
Graph API Errors
Error | Cause | Solution |
| No Teams meetings in calendar within date range | Try without a date filter (shows last 30 days + 7 days forward). User must be organiser or invitee. |
| No transcription was started during the meeting | Transcription must be started during the meeting by a participant. Check the transcription policy. |
| Insufficient permissions | Verify all five scopes are in the admin consent grant (see Verifying Admin Consent Grants). |
| Meeting or transcript ID invalid | Meeting may have been deleted. Try |
Graph API Gotchas Discovered During Development:
The
/me/onlineMeetingsendpoint has severe limitations not obvious from the documentation:
Cannot list all meetings — requires
$filter, only supportsJoinWebUrl,joinMeetingIdSettings/joinMeetingId, orVideoTeleconferenceIdNo
$topor$orderbysupport — rejected with400
isOnlineMeetingnot filterable — on/me/calendarView, you must includeonlineMeetingin$selectand filter client-sideDateTimeOffset values must be unquoted — single quotes cause
400 BadRequest
Container / Deployment Errors
Error | Cause | Solution |
Health endpoint times out | Container cold-starting (min replicas = 0) | Wait ~10 seconds and retry. Set |
Container fails to start | Missing env vars or build error | Check logs: |
| Port conflict | Ensure |
Image pull fails | ACR credentials expired |
|
Copilot Studio Errors
Error | Cause | Solution |
"Unable to connect to MCP server" | Wrong URL or server down | Verify URL ends in |
Tools not discovered | MCP handshake fails | Verify |
"Authentication failed" after signing in | OAuth misconfiguration | Check all OAuth fields. The Refresh URL is the same as the Token URL for Entra ID v2.0. |
Verifying Admin Consent Grants
If you suspect the admin consent grant is incomplete:
# Get your app's service principal ID
SP_ID=$(az ad sp show --id <client-id> --query id -o tsv)
# List all permission grants
az rest --method GET \
--uri "https://graph.microsoft.com/v1.0/servicePrincipals/$SP_ID/oauth2PermissionGrants" \
--query "value[].scope" -o tsvExpected output: User.Read Calendars.Read OnlineMeetings.Read OnlineMeetingTranscript.Read.All Sites.ReadWrite.All
To fix a grant with missing scopes:
# Option 1: Using az ad app permission grant (simplest)
# Get the service principal object ID first
SP_OBJECT_ID=$(az ad sp show --id <client-id> --query id -o tsv)
# Re-grant with ALL required scopes (this replaces the existing grant)
az ad app permission grant \
--id $SP_OBJECT_ID \
--api 00000003-0000-0000-c000-000000000000 \
--scope "User.Read Calendars.Read OnlineMeetings.Read OnlineMeetingTranscript.Read.All Sites.ReadWrite.All"
# Option 2: Using az rest to PATCH the existing grant
GRANT_ID=$(az rest --method GET \
--uri "https://graph.microsoft.com/v1.0/servicePrincipals/$SP_ID/oauth2PermissionGrants" \
--query "value[0].id" -o tsv)
az rest --method PATCH \
--uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants/$GRANT_ID" \
--headers "Content-Type=application/json" \
--body '{"scope":"User.Read Calendars.Read OnlineMeetings.Read OnlineMeetingTranscript.Read.All Sites.ReadWrite.All"}'Why does this happen? When you click "Grant admin consent" in the Azure Portal, it creates or updates an
oauth2PermissionGrantobject. However, if permissions were added to the App Registration after the initial grant was created, the portal may not update the existing grant to include the new scopes. The OBO flow then fails withAADSTS65001because the grant doesn't cover all the scopes the server is requesting. The fix is to explicitly re-grant with all scopes using the CLI commands above.
Development History
This project went through 9 iterations to arrive at a working architecture, primarily due to undocumented limitations in the Microsoft Graph /me/onlineMeetings API.
Version | Changes |
v1–v2 | Initial implementation using |
v3 | Removed all OData query options. Failed — endpoint requires |
v4 | Added |
v5 | Removed single-quotes around |
v6 | Architecture change: Switched to Calendar API ( |
v7 | First working version: Removed |
v8 | Extended date range to "30 days back → 7 days forward" to include upcoming meetings. |
v9 | Current version: Comprehensive |
License
MIT
Disclaimer
Authentication & Liability: This project is an open-source bridge and is not an official Microsoft product. It uses your own Azure AD App Registration and operates under the context of the signed-in user. You are responsible for managing the security of your client secrets and tokens. The maintainers of this repository accept no liability for any data loss, security breaches, or unexpected charges incurred by using this software. Use at your own risk.
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ITSpecialist111/MicrosoftGraph_Transcript_MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server