Skip to main content

Integration library for Microsoft Agents with Teams

Project description

microsoft-agents-hosting-msteams

PyPI version

Teams-specific extension for the Microsoft 365 Agents SDK for Python. Provides the TeamsAgentExtension and a full set of typed, decorator-based route registrars for every Teams invoke and event activity — messaging extensions, task modules, meeting lifecycle events, channel and team management, file consent, config invokes, and more.

All handlers receive a TeamsTurnContext, a Teams-aware wrapper around the base TurnContext that surfaces the Teams API client and typed activity helpers.

What is this?

This library is part of the Microsoft 365 Agents SDK for Python — a comprehensive framework for building enterprise-grade conversational AI agents. The SDK enables developers to create intelligent agents that work across Microsoft Teams, M365 Copilot, Copilot Studio, and web chat.

Release Notes

Version Date Release Notes
1.2.0 2026-07-17 1.2.0 Release Notes
1.1.0 2026-06-19 1.1.0 Release Notes
1.0.0 2026-05-22 1.0.0 Release Notes
0.9.1 2026-05-04 0.9.1 Release Notes
0.9.0 2026-04-15 0.9.0 Release Notes
0.8.0 2026-02-23 0.8.0 Release Notes
0.7.0 2026-01-21 0.7.0 Release Notes
0.6.1 2025-12-01 0.6.1 Release Notes
0.6.0 2025-11-18 0.6.0 Release Notes
0.5.0 2025-10-22 0.5.0 Release Notes

Packages Overview

Package Name PyPI Version Description
microsoft-agents-activity PyPI Types and validators implementing the Activity protocol spec.
microsoft-agents-hosting-core PyPI Core library for Microsoft Agents hosting.
microsoft-agents-hosting-aiohttp PyPI Configures aiohttp to run the Agent.
microsoft-agents-hosting-msteams PyPI Provides classes to host an Agent for Teams.
microsoft-agents-hosting-dialogs PyPI Dialog system with waterfall dialogs, prompts, and multi-turn conversation management.
microsoft-agents-storage-blob PyPI Extension to use Azure Blob as storage.
microsoft-agents-storage-cosmos PyPI Extension to use CosmosDB as storage.
microsoft-agents-authentication-msal PyPI MSAL-based authentication for Microsoft Agents.

Additionally we provide a Copilot Studio Client, to interact with Agents created in CopilotStudio:

Package Name PyPI Version Description
microsoft-agents-copilotstudio-client PyPI Direct to Engine client to interact with Agents created in CopilotStudio

Installation

pip install microsoft-agents-hosting-msteams

Key Classes

Class Description
TeamsAgentExtension Attaches to an AgentApplication and exposes all Teams route registrars as typed properties.
TeamsTurnContext Teams-aware context passed to every handler. Provides the Teams API client and typed TeamsActivity helpers.
MessageExtension Route registrar for all composeExtension/* invokes (query, fetch action, submit, link unfurling, settings, etc.).
TaskModule Route registrar for task/fetch and task/submit invokes.
Meeting Route registrar for meeting start/end and participant join/leave events.
Channel Route registrar for channel created/deleted/renamed/shared events.
Team Route registrar for team archived/renamed/restored/deleted events.
FileConsent Route registrar for file consent accept/decline invokes.
Config Route registrar for config/fetch and config/submit invokes.
Message Route registrar for message edit, delete, undelete, read receipts, and actionable message execute.

Usage

Setup

Wrap your AgentApplication with TeamsAgentExtension to access all Teams-specific route registrars:

from microsoft_agents.hosting.core import AgentApplication, TurnState
from microsoft_agents.hosting.msteams import TeamsAgentExtension

app = AgentApplication[TurnState]()
teams = TeamsAgentExtension(app)

TeamsAgentExtension registers a before_turn hook that deserializes activity.channel_data into a typed ChannelData object and creates a pre-authenticated Teams API client on the turn state for every Teams activity.

The Teams context

Every handler registered through TeamsAgentExtension receives a TeamsTurnContext instead of a plain TurnContext.

context.activityTeamsActivity

The activity is upgraded to TeamsActivity, which adds helper methods on top of the standard Activity:

# Get the Teams channel the activity came from
channel_id = context.activity.get_channel_id()

# Get the team info embedded in channel_data
team = context.activity.get_team_info()

# Get the meeting the activity was sent in
meeting = context.activity.get_meeting_info()

# Mark the outgoing reply to target a specific user in a meeting
context.activity.notify_user(alert_in_meeting=True)

# Attach a feedback loop to a message being sent (Copilot scenarios)
context.activity.enable_feedback_loop()

context.api_clientApiClient

Direct access to the Teams REST API client, pre-authenticated for the current turn. See Teams API client below.

Sending targeted activities

TeamsTurnContext adds two methods for sending activities that target a specific user in a meeting:

await context.send_targeted_activity(activity)
await context.send_targeted_activities([activity1, activity2])

Messaging Extensions

from microsoft_teams.api.models import (
    MessagingExtensionQuery,
    MessagingExtensionResponse,
    MessagingExtensionAction,
    MessagingExtensionActionResponse,
    AppBasedLinkQuery,
)

# Search-based extension
@teams.message_extensions.query("searchCmd")
async def on_search(context: TeamsTurnContext, state, query: MessagingExtensionQuery):
    return MessagingExtensionResponse(...)

# Action-based extension — open a task module to collect input
@teams.message_extensions.fetch_action("myCmd")
async def on_fetch_action(context: TeamsTurnContext, state, action: MessagingExtensionAction):
    return MessagingExtensionActionResponse(...)

# Handle submission from the task module
@teams.message_extensions.submit_action("myCmd")
async def on_submit(context: TeamsTurnContext, state, action: MessagingExtensionAction):
    return MessagingExtensionResponse(...)

# Link unfurling
@teams.message_extensions.query_link
async def on_query_link(context: TeamsTurnContext, state, query: AppBasedLinkQuery):
    return MessagingExtensionResponse(...)

# Anonymous link unfurling (before the user has authenticated)
@teams.message_extensions.anonymous_query_link
async def on_anon_link(context: TeamsTurnContext, state, query: AppBasedLinkQuery):
    return MessagingExtensionResponse(...)

# Bot message preview — edit stage
@teams.message_extensions.message_preview_edit("myCmd")
async def on_preview_edit(context: TeamsTurnContext, state, preview: Activity):
    return MessagingExtensionResponse(...)

# Bot message preview — send stage
@teams.message_extensions.message_preview_send("myCmd")
async def on_preview_send(context: TeamsTurnContext, state, preview: Activity):
    return MessagingExtensionResponse(...)

# Select item from search results
@teams.message_extensions.select_item
async def on_select_item(context: TeamsTurnContext, state, item):
    return MessagingExtensionResponse(...)

# Card button clicked
@teams.message_extensions.card_button_clicked
async def on_card_click(context: TeamsTurnContext, state, card):
    ...

All command_id arguments accept a plain string, a compiled re.Pattern, or None to match all commands.

Settings page

@teams.message_extensions.query_setting_url
async def on_settings_url(context: TeamsTurnContext, state, query: MessagingExtensionQuery):
    return MessagingExtensionResponse(
        compose_extension=MessagingExtensionResult(type="config", suggested_actions=...)
    )

@teams.message_extensions.setting
async def on_settings_submit(context: TeamsTurnContext, state, query: MessagingExtensionQuery):
    await save_settings(context, query.state)

Task Modules

from microsoft_teams.api.models import TaskModuleRequest, TaskModuleResponse

@teams.task_modules.fetch("myVerb")
async def on_task_fetch(context: TeamsTurnContext, state, request: TaskModuleRequest):
    return TaskModuleResponse(...)

@teams.task_modules.submit("myVerb")
async def on_task_submit(context: TeamsTurnContext, state, request: TaskModuleRequest):
    return TaskModuleResponse(...)

The first argument is a verb — a string or regex matched against activity.value.data.verb. Omit it to match all task/fetch or task/submit invokes.

Meeting Events

from microsoft_teams.api.models import MeetingDetails
from microsoft_agents.activity.teams import MeetingParticipantsEventDetails

@teams.meetings.start
async def on_meeting_start(context: TeamsTurnContext, state, meeting: MeetingDetails):
    ...

@teams.meetings.end
async def on_meeting_end(context: TeamsTurnContext, state, meeting: MeetingDetails):
    ...

@teams.meetings.participants_join
async def on_participants_join(context: TeamsTurnContext, state, details: MeetingParticipantsEventDetails):
    ...

@teams.meetings.participants_leave
async def on_participants_leave(context: TeamsTurnContext, state, details: MeetingParticipantsEventDetails):
    ...

Channel Events

from microsoft_teams.api.models import ChannelInfo

@teams.channels.created
async def on_channel_created(context: TeamsTurnContext, state, channel: ChannelInfo):
    print(f"New channel: {channel.name}")

@teams.channels.renamed
async def on_channel_renamed(context: TeamsTurnContext, state, channel: ChannelInfo):
    ...

# Also available: deleted, shared, unshared, restored, members_added, members_removed

teams.channels.event() is a catch-all that matches any channel.* event type via regex.

Team Events

from microsoft_teams.api.models import TeamInfo

@teams.teams.renamed
async def on_team_renamed(context: TeamsTurnContext, state, team: TeamInfo): ...

# Also available: archived, unarchived, deleted, hard_deleted, restored

teams.teams.event() is a catch-all matching any team.* event type.

File Consent

from microsoft_teams.api.models import FileConsentCardResponse

@teams.file_consent.accept
async def on_file_accept(context: TeamsTurnContext, state, consent: FileConsentCardResponse):
    await upload_file(consent.upload_info)

@teams.file_consent.decline
async def on_file_decline(context: TeamsTurnContext, state, consent: FileConsentCardResponse):
    ...

Teams expects a 200 OK with no body for both; the routing layer sends that automatically.

Config Invokes

from microsoft_teams.api.models import ConfigResponse

@teams.config.fetch
async def on_config_fetch(context: TeamsTurnContext, state, config_data):
    return ConfigResponse(...)

@teams.config.submit
async def on_config_submit(context: TeamsTurnContext, state, config_data):
    return ConfigResponse(...)

config_data is the raw activity.value payload.

Message Updates

from microsoft_teams.api.models import O365ConnectorCardActionQuery

@teams.messages.edit
async def on_message_edit(context: TeamsTurnContext, state):
    ...

@teams.messages.delete
async def on_message_delete(context: TeamsTurnContext, state):
    ...

@teams.messages.undelete
async def on_message_undelete(context: TeamsTurnContext, state):
    ...

@teams.messages.read_receipt
async def on_read_receipt(context: TeamsTurnContext, state, data: dict):
    last_read_id = data.get("lastReadMessageId")
    ...

@teams.messages.execute_action
async def on_execute_action(context: TeamsTurnContext, state, query: O365ConnectorCardActionQuery):
    ...

Generic Teams routes

TeamsAgentExtension also wraps the underlying AgentApplication route methods so all handlers automatically receive a TeamsTurnContext:

from microsoft_agents.activity import ActivityTypes, ConversationUpdateTypes

@teams.activity(ActivityTypes.message)
async def on_any_message(context: TeamsTurnContext, state):
    await context.send_activity(f"Echo: {context.activity.text}")

@teams.message(r"^hello")
async def on_hello(context: TeamsTurnContext, state):
    await context.send_activity("Hi there!")

@teams.conversation_update(ConversationUpdateTypes.members_added)
async def on_members_added(context: TeamsTurnContext, state):
    for member in context.activity.members_added:
        if member.id != context.activity.recipient.id:
            await context.send_activity(f"Welcome, {member.name}!")

Teams API client

context.api_client is a pre-authenticated ApiClient pointing at the Teams connector service for the current turn. It wraps the Teams REST API and is the right tool for operations on channels, conversations, and members.

# Get the list of members in the current conversation
members = await context.api_client.conversations.get_conversation_members(
    context.activity.conversation.id
)

# Send a proactive message to a specific conversation
await context.api_client.conversations.send_to_conversation(
    context.activity.conversation.id,
    activity,
)

You can also get the client directly from the extension if you have a context but aren't inside a Teams handler:

client = teams.get_teams_api_client(context)

Microsoft Graph client

For operations that go beyond the Teams connector service — user profiles, SharePoint, calendar, etc. — use the Graph client:

@teams.message(r"^profile")
async def on_profile(context: TeamsTurnContext, state):
    graph = teams.get_graph_client(context, handler_name="UserAuth")
    user = await graph.users.by_user_id(context.activity.from_.aad_object_id).get()
    await context.send_activity(f"Display name: {user.display_name}")

The handler_name argument names an OAuth connection configured in the app's auth settings. If omitted, the app's default auth handler is used.

Invoke response conventions

Teams expects a synchronous HTTP response for all invoke activities. The routing layer handles this automatically — return a value from your handler and the framework serializes and sends it.

Namespace Sends response? Response type
task_modules.fetch/submit If handler returns non-None TaskModuleResponse
message_extensions.query/select_item/fetch_action/submit_action/message_preview_* If handler returns non-None MessagingExtensionResponse / MessagingExtensionActionResponse
message_extensions.query_link/anonymous_query_link/query_setting_url If handler returns non-None MessagingExtensionResponse
message_extensions.setting Always MessagingExtensionResponse (or 200 with no body if handler returns None)
message_extensions.card_button_clicked Always empty 200
config.fetch/submit If handler returns non-None ConfigResponse
file_consent.accept/decline Always empty 200
messages.execute_action Always empty 200
meetings, channels, teams, messages.edit/undelete/delete Never — not invokes

Features Supported

  • Messaging Extensions — query, fetch action, submit action, link unfurling, anonymous link unfurling, settings URL, configure settings, card button clicked, bot message preview (edit/send)
  • Task Modules — fetch and submit with optional verb matching
  • Meeting Lifecycle — start, end, participant join/leave
  • Channel Management — created, deleted, renamed, shared, unshared, restored, members added/removed
  • Team Management — archived, unarchived, deleted, hard deleted, renamed, restored
  • File Consent — accept and decline flows
  • Config Invokesconfig/fetch and config/submit
  • Message Updates — edit, delete, undelete, read receipts, actionable message execute
  • TeamsAgentExtension routing — wraps any AgentApplication with zero configuration

Quick Links

Sample Applications

Name Description README
Quickstart Simplest agent Quickstart
Auto Sign In Simple OAuth agent using Graph and GitHub auto-signin
OBO Authorization OBO flow to access a Copilot Studio Agent obo-authorization
Semantic Kernel Integration A weather agent built with Semantic Kernel semantic-kernel-multiturn
Streaming Agent Streams OpenAI responses azure-ai-streaming
Copilot Studio Client Console app to consume a Copilot Studio Agent copilotstudio-client
Cards Agent Agent that uses rich cards to enhance conversation design cards

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

microsoft_agents_hosting_msteams-1.2.0.tar.gz (36.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file microsoft_agents_hosting_msteams-1.2.0.tar.gz.

File metadata

File hashes

Hashes for microsoft_agents_hosting_msteams-1.2.0.tar.gz
Algorithm Hash digest
SHA256 f33c3b7f26d5955c39aab6712b01c43a761c17c8b61718c760f0d1a8e7b48906
MD5 3de6543d6cbc1eece3db2df28847367c
BLAKE2b-256 d605efc04363b774ca518a928a1e56410616d7403b207143c5920c1260a87b0f

See more details on using hashes here.

File details

Details for the file microsoft_agents_hosting_msteams-1.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for microsoft_agents_hosting_msteams-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3c374c2b1e729d19f31b9818e1dc83a45ec4f6216e8d71426cc7616c2d8b7fe2
MD5 89c003e5b20dd979cd18421729da9c3d
BLAKE2b-256 39511e8a1a4f8d616b529a95d09fd687cd051a9fbcac7d154f41a8638ac21701

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page