Teams MCP - Architecture

6 min read

The Teams MCP Server is a NestJS-based microservice that integrates Microsoft Teams with the Unique platform through the Model Context Protocol (MCP). It captures meeting transcripts and recordings from Microsoft Teams, ingests them into Unique with proper access controls, and exposes synchronous MCP tools for reading and sending messages across chats and channels.

Core Capabilities:

  • Captures Microsoft Teams meeting transcripts and recordings in real-time

  • Manages webhook subscriptions to Microsoft Graph API for notifications

  • Handles OAuth2 authentication with Microsoft Entra ID

  • Ingests content into the Unique platform with participant-based access controls

  • Manages subscription lifecycle (create, renew, remove) with scheduled synchronization

  • Reads messages from personal chats and team channels via synchronous MCP tools

  • Searches across chats and channels using the Microsoft Search API

  • Sends messages to chats and channels via synchronous MCP tools

High-Level Architecture

embedded_30a9635f6f9cbf453057e6c4c87a708e.png

Components

embedded_02333aed04adc7ea64eaface0728e64d.png

Component Descriptions

Component

Purpose

Microsoft OAuth Provider

Handles OAuth2 flow with Microsoft Entra ID

MCP OAuth Store

Stores encrypted JWT tokens in PostgreSQL

Token Provider

Manages access/refresh tokens with automatic refresh

Graph Client Factory

Creates authenticated Microsoft Graph API clients

Webhook Controller

Receives notifications from Microsoft Graph

Subscription Services

Manages Graph API subscription lifecycle

Transcript Created Service

Processes new transcripts and fetches recordings

Unique Service

Interfaces with Unique Public API for content ingestion

AMQP Module

RabbitMQ integration for async message processing

Chat Module

The Chat Module (src/chat/) exposes a synchronous request/response tool surface over MCP, distinct from the asynchronous webhook/transcript ingestion path. Tool calls are handled inline — there is no queue or background worker.

Chat and channel messages are accessible through these tools but are never ingested into Unique. The Unique AI can read, search, and send them on demand, but each call is served live from the Microsoft Graph API — Unique keeps no knowledge-base copy, and the messages exist only in Microsoft. This is the opposite of the Transcript Module, whose output is ingested into Unique and later queried from that stored copy. "Not ingested" refers to storage, not accessibility. See README — Where the Data Lives.

Services:

Service

File

Responsibility

ChatService

chat.service.ts

Lists personal chats; fetches and sends chat messages via /me/chats and /chats/{id}/messages

ChannelService

channel.service.ts

Lists joined teams and their channels; fetches and sends channel messages via /me/joinedTeams, /teams/{id}/channels, and /teams/{id}/channels/{id}/messages

SearchService

search.service.ts

Cross-container message search via the Microsoft Search API (POST /search/query on Graph v1.0); delegates per-hit hydration to ChatService

Tool layer (src/chat/tools/):

Tool

What it does

list_chats

Lists recent personal chats with member and preview metadata

get_chat_messages

Fetches messages from a personal chat by ID

send_chat_message

Posts a plain-text message to a personal chat by ID

list_teams

Lists all Teams the user has joined

list_channels

Lists channels in a given team by ID

get_channel_messages

Fetches messages from a team channel by ID

send_channel_message

Posts a plain-text message to a team channel by ID

search_messages

Searches messages across chats, channels, or both

Targeting by id:list_* tools return identifiers (chat id, team id, channel id) that are passed directly to the get_*_messages and send_*_message tools. See Chat Flows for sequence diagrams.

Search specifics (SearchService): The Microsoft Search API does not use @odata.nextLink; pagination is driven by offset/size on the request body and moreResultsAvailable on the response. When detail=full, each matching hit is hydrated with its full message body via an additional Graph call (N+1). Hydration runs with a concurrency cap of 5 (via pLimit). A hit that returns 403 or 404 during hydration falls back to its summary-only row rather than failing the entire page.

Infrastructure

PostgreSQL

Stores persistent data with the following schema:

embedded_69ebd0d03881e676b24f309cc2679623.png

Table

Purpose

user_profiles

User identity and encrypted Microsoft tokens

subscriptions

Active Graph API webhook subscriptions

oauth_clients

Registered MCP OAuth clients

oauth_sessions

Active OAuth sessions with token family tracking

tokens

MCP access and refresh tokens (hashed)

authorization_codes

Temporary OAuth authorization codes with PKCE

Key Design Decisions:

  • Token Family Tracking: Each session has a token_family ID. If a refresh token is reused (indicating possible theft), the entire family is revoked.

  • Encrypted Microsoft Tokens: Access and refresh tokens from Microsoft are encrypted at rest using AES-GCM.

  • Hashed MCP Tokens: MCP tokens are stored as hashes, not plaintext, for cache-based validation.

RabbitMQ

Enables asynchronous processing of webhook notifications. See FAQ - Why use RabbitMQ for webhook processing? for details.

Exchange

Type

Purpose

unique.teams-mcp.main

topic

Primary message routing

unique.teams-mcp.dead

topic

Failed message storage (DLX)

Queue

Purpose

unique.teams-mcp.transcript.change-notifications

Transcript processing

unique.teams-mcp.transcript.lifecycle-notifications

Subscription management

unique.teams-mcp.dead

Dead letter collection

Authentication Architecture

The Teams MCP service handles two layers of authentication:

  1. MCP OAuth - Authentication between MCP clients and this server

  2. Microsoft OAuth - Authentication with Microsoft Entra ID for Graph API access

embedded_cf46f886d97ae4348d3f1b744373992e.png

Token Isolation

Critical Security Design: Microsoft OAuth tokens (access and refresh) are never exposed to clients. The OAuth flow happens entirely on the server:

  1. Microsoft OAuth Flow: User authenticates with Microsoft Entra ID

  2. Token Exchange: Server exchanges authorization code for Microsoft tokens (using CLIENT_SECRET)

  3. Token Storage: Microsoft tokens are encrypted and stored on the server only

  4. Client Authentication: Server issues separate opaque JWT tokens to the client for MCP API access

This design ensures that: - Microsoft tokens never leave the server - Clients cannot access Microsoft Graph API directly - All Microsoft API calls are made by the server on behalf of authenticated users - Client tokens are opaque JWTs that only authenticate with the MCP server

Token Storage

Token Type

Source

Storage Location

Client Access

Access Token

Microsoft Entra ID

Encrypted in user_profiles table

Never

Refresh Token

Microsoft Entra ID

Encrypted in user_profiles table

Never

Required Scopes: See Microsoft Graph Permissions for the complete list with least-privilege justification.

Token Encryption

All Microsoft tokens are encrypted at rest using AES-GCM (authenticated encryption) with a 256-bit key stored in environment variables.

Single App Registration Architecture

Each MCP server deployment uses one Microsoft Entra ID app registration:

  • Single App Registration: One CLIENT_ID/CLIENT_SECRET pair per deployment

  • Multi-Tenant Capable: The app registration can be configured to accept users from multiple Microsoft tenants

  • Cross-Tenant Authentication: Users from different organizations authenticate via Enterprise Applications in their tenant that reference the original app registration

  • Enterprise Application Creation: When tenant admin grants consent, Microsoft creates an Enterprise Application in their tenant as a proxy to the original app registration

This design uses a single OAuth application that can serve users across multiple tenants, rather than requiring separate app registrations per organization.

For detailed explanation, see Permissions - Why Delegated (Not Application).

Required App Registration Components

Component

Purpose

Security Function

CLIENT_ID

Application identifier

Identifies which app is requesting access

CLIENT_SECRET

Application credential

Proves the server is the legitimate app (not an imposter)

Redirect URI

OAuth callback endpoint

Prevents authorization code interception

API Permissions

Graph scopes

Limits what data the app can access

Admin Consent

Privileged scopes

Required for transcript and recording access

Without proper app registration, Microsoft Graph API will reject all authentication attempts with invalid_client errors.

Unsupported Authentication Methods

Method

Supported

Reason

Client Secret + Delegated

Yes

Standard OAuth2 flow for user-specific access

Client Credentials (OIDC)

No

No user context; requires admin policy setup

Certificate Authentication

No

Only works with Client Credentials flow

Federated Identity

No

Only works with Client Credentials flow

Multiple App Registrations

No

Each MCP server deployment uses one Entra ID app registration

The Teams MCP service requires delegated permissions to access user-specific resources. Client Credentials flow only supports application permissions, which would require tenant admins to create Application Access Policies via PowerShell—impractical for self-service MCP connections.

See also:

MCP OAuth (Internal)

The MCP OAuth layer implements the MCP Authorization specification:

  • OAuth 2.1 Authorization Code + PKCE flow

  • Refresh token rotation with family-based revocation for theft detection

  • Cache-first token validation (no introspection endpoint)

  • Token cleanup for expired tokens

Token Type

Default TTL

Purpose

Access Token

60 seconds

Short-lived API access

Refresh Token

30 days

Obtain new access tokens

Configuration Reference

Variable

Default

Description

AUTH_ACCESS_TOKEN_EXPIRES_IN_SECONDS

60

MCP access token TTL

AUTH_REFRESH_TOKEN_EXPIRES_IN_SECONDS

2592000

MCP refresh token TTL (30 days)

AUTH_HMAC_SECRET

(required)

64-char hex for JWT signing

ENCRYPTION_KEY

(required)

64-char hex for AES-GCM encryption

  • Flows - User connection, subscription lifecycle, transcript processing, chat read/search/send

  • Security - Encryption, authentication, and threat model

  • Microsoft Graph Permissions - Required scopes and least-privilege justification

Standard References

Last updated