Skip to main content
Glama

teams-mcp-delegated

A Model Context Protocol server for Microsoft Teams that can act as the signed-in user — reading their chats and calendar, scheduling meetings, pulling meeting transcripts, and resolving colleagues by partial or misspelled name.

This is a fork of InditexTech/mcp-teams-server (Apache-2.0, © 2025 Industria de Diseño Textil S.A.). Upstream is a Teams bot that posts as itself. This fork keeps that mode byte-for-byte and adds a second one: delegated per-user auth over Microsoft Graph, plus 17 new tools and a Windows installer. Nothing upstream was removed.

Portfolio copy. The Entra app ID and tenant ID this was originally installed against have been removed — the installer now takes them as parameters. Example names in tests and docstrings are fictional.

What this fork adds

1. Delegated (per-user) authentication — the architectural change

Upstream only supports app / service-principal auth: a registered bot posting under its own identity via ClientSecretCredential + CloudAdapter. That identity cannot read your chats, your calendar, or a meeting transcript.

src/mcp_teams_server/auth.py (new, 186 lines) adds a credential factory selected by TEAMS_AUTH_MODE:

  • app (default) — ClientSecretCredential, exactly as upstream. Untouched.

  • delegated — DeviceCodeCredential with TokenCachePersistenceOptions, persisting an AuthenticationRecord to %LOCALAPPDATA%\.IdentityService\mcp-teams-server-auth-record.json. The interactive device-code flow therefore runs once per machine, not once per invocation.

In delegated mode app_lifespan builds no bot adapter at all — it is pure Microsoft Graph with the user's token, and resolves /me for message attribution.

Two details that were not obvious going in:

  • The device-code prompt cannot go to stdout. stdout is the JSON-RPC channel; writing a prompt there corrupts the protocol. It goes to stderr — and because Claude Code hides stderr, the prompt additionally auto-opens the browser and raises a native dialog with the code (a detached MessageBox on Windows, osascript on macOS, notify-send on Linux).

  • Explicit cache warm-up. authenticate() followed by get_token(..., enable_cae=True) runs at startup, so the Graph SDK's first CAE-enabled call does not re-prompt the user.

2. Tools: 6 → 23

Upstream's six (start_thread, update_thread, read_thread, list_threads, get_member_by_name, list_members) plus seventeen:

Area

Tools

Discovery

list_teams, list_channels, resolve_teams_link, create_channel, add_channel_members

Calendar

find_available_times, create_meeting, cancel_meeting, list_events, update_event

People

resolve_user, send_direct_message

Transcripts

list_meeting_transcripts, get_meeting_transcript, get_transcript_content

Chats

list_chats, read_chat_messages

Each has dual internals (_start_thread_graph vs the bot path, _send_dm_delegated vs _send_dm_app, …) so every tool works in both auth modes.

3. Three pieces of engineering worth the read

Transcript access without OnlineMeetings.Read. Fetching a transcript normally requires a permission the app registration does not hold, and normally only the organizer can do it. _build_meeting_id_from_join_url() reconstructs the Graph online-meeting ID locally — base64 of the thread ID plus the organizer OID parsed out of the join URL — and _resolve_meeting_id() tries that first, falling back to a Graph filter query. The result: any attendee can pull the transcript. The content fetch sets an explicit Accept: text/vtt header, because the SDK's default is wrong for this endpoint.

Local fuzzy people-resolution. _build_directory_cache() builds a directory from the caller's own Teams memberships; _fuzzy_score() ranks candidates locally. So "Alex", "Alex Rivera", "alx rivra" and "alex.rivera" all resolve to the same person, ranked by relevance to the signed-in user — no directory-wide search permission needed.

Outbound rendering and attribution. Teams renders a markdown pipe table as literal | --- | text, so convert_markdown_tables_to_html() turns it into real <table> markup and a text message containing a table is auto-upgraded to HTML. Separately, _attribution_footer() appends "Posted via Teams MCP on behalf of &lt;user&gt;" to every outbound message, in both the HTML and plaintext bodies. An agent posting as a human should say so.

Inbound card extraction. CI and alerting bots post Adaptive Cards and legacy O365 Connector Cards, which carry no plain-text body — so the model sees an empty message. The extraction layer pulls readable text out of both.

4. Windows installer

install-teams-mcp.ps1 (397 lines) is one command: detect or winget-install Python 3.10-3.12, create an isolated venv under %LOCALAPPDATA%, find or build the wheel, install it, verify the import, register the server with claude mcp add --scope user, and run the one-time device-code auth.

Two real Windows problems it solves:

  • A LongPathsEnabled registry check with self-elevation guidance, because the msgraph SDK's paths exceed MAX_PATH.

  • Remove-LongPathDir, which purges a >260-character venv via robocopy /purge against an empty directory — Remove-Item -Recurse simply cannot delete it.

Related MCP server: Teams MCP

Install

.\install-teams-mcp.ps1 -TeamsAppId <app-guid> -TeamsTenantId <tenant-guid>

Or set TEAMS_APP_ID / TEAMS_APP_TENANT_ID in the environment and run it with no arguments. You need an Entra app registration with the delegated Graph scopes — see doc/MS-Teams-setup.md for the app registration, and doc/delegated-auth.md for what delegated mode needs specifically.

Manual install:

uv sync --frozen
cp sample.env .env      # fill in your own IDs
uv run mcp-teams-server

Configuration

Variable

Mode

Meaning

TEAMS_AUTH_MODE

both

app (default) or delegated

TEAMS_APP_ID

both

Entra app registration (client) ID

TEAMS_APP_TENANT_ID

both

Entra tenant ID

TEAMS_APP_TYPE

both

SingleTenant or MultiTenant

TEAMS_APP_PASSWORD

app only

Client secret. Not required in delegated mode.

TEAM_ID

both

Default Teams group ID

TEAMS_CHANNEL_ID

both

Default channel ID

TEAMS_TOKEN_CACHE_NAME

delegated

Token cache filename. Default mcp-teams-server.cache.

Verifying

python bin/e2e.py

Runs every check that works without a Teams tenant — imports, the 23-tool registry, both credential factory branches, card extraction against captured payloads, markdown-table conversion — and prints an explicit SKIP for each path that needs live Graph access, rather than omitting it. Exit code is non-zero on any failure.

uv run pytest runs the same offline unit tests; the integration suite is excluded by default (-m "not integration") because it needs a real tenant.

Known limitations

Stated rather than hidden:

  • teams.py hardcodes service_url = "https://smba.trafficmanager.net/emea/", inherited unchanged from upstream. It affects app (bot) mode only; a non-EMEA tenant needs this parameterised.

  • Default timezone for calendar operations is Central Standard Time, not the caller's. Worth deriving from /me/mailboxSettings.

  • Upstream's doc/MS-Teams-setup.md still describes the bot-mode setup. That is still accurate for app mode; delegated mode is documented in doc/delegated-auth.md.

  • The CI workflows inherited from upstream that depended on upstream's org (the SonarCloud gate, the ghcr release job, OSSF Scorecard on master) are removed — they could never run here. .github/workflows/PR-verify.yml (ruff, pyright, pytest, repolinter, REUSE) is kept and does run.

License and attribution

Apache-2.0, inherited from upstream. LICENSE.txt, LICENSES/, NOTICE and the per-file SPDX headers are retained as Apache-2.0 §4 requires. Files substantially modified here carry an additional SPDX-FileCopyrightText: 2026 Jason Hastain (modifications) line; files written here carry only that.

© 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.) — original work © 2026 Jason Hastain — modifications

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to interact with Microsoft Teams, users, chats, files, and organizational data via Microsoft Graph APIs, with added support for creating, updating, listing, and inspecting Teams meetings and calendar events.
    11 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to interact with Microsoft Teams and Microsoft Graph APIs for user and team management, channel and chat messaging, message editing and deletion, file upload/download, and advanced message search.
    11 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to interact with Microsoft Teams and Microsoft Graph APIs, covering chat and channel messaging, message search, file uploads and downloads, and user and organizational directory lookups. It supports OAuth device-code authentication, rich Markdown formatting, HTML-to-Markdown conversion for LLM-friendly output, and a read-only mode.
    11 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to interact with Microsoft Teams and Microsoft Graph APIs for user, team, channel, chat, message search, file, and attachment management. Supports authentication, rich Markdown formatting, and LLM-friendly content conversion.
    11 npm
    MIT