Teams MCP - Architecture — Unique AI Documentation

Teams MCP - Architecture

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:

High-Level Architecture

Components

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.

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.

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.

Infrastructure

PostgreSQL

Stores persistent data with the following schema:

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:

RabbitMQ

Enables asynchronous processing of webhook notifications.

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

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:

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:

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:

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

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

MCP OAuth (Internal)

The MCP OAuth layer implements the MCP Authorization specification:

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