Integration library for Microsoft Agents with Teams
Project description
microsoft-agents-hosting-msteams
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
Additionally we provide a Copilot Studio Client, to interact with Agents created in CopilotStudio:
| Package Name | PyPI Version | Description |
|---|---|---|
microsoft-agents-copilotstudio-client |
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.activity → TeamsActivity
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_client → ApiClient
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 Invokes —
config/fetchandconfig/submit - Message Updates — edit, delete, undelete, read receipts, actionable message execute
- TeamsAgentExtension routing — wraps any
AgentApplicationwith 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file microsoft_agents_hosting_msteams-1.2.0.tar.gz.
File metadata
- Download URL: microsoft_agents_hosting_msteams-1.2.0.tar.gz
- Upload date:
- Size: 36.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: RestSharp/106.13.0.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f33c3b7f26d5955c39aab6712b01c43a761c17c8b61718c760f0d1a8e7b48906
|
|
| MD5 |
3de6543d6cbc1eece3db2df28847367c
|
|
| BLAKE2b-256 |
d605efc04363b774ca518a928a1e56410616d7403b207143c5920c1260a87b0f
|
File details
Details for the file microsoft_agents_hosting_msteams-1.2.0-py3-none-any.whl.
File metadata
- Download URL: microsoft_agents_hosting_msteams-1.2.0-py3-none-any.whl
- Upload date:
- Size: 45.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: RestSharp/106.13.0.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c374c2b1e729d19f31b9818e1dc83a45ec4f6216e8d71426cc7616c2d8b7fe2
|
|
| MD5 |
89c003e5b20dd979cd18421729da9c3d
|
|
| BLAKE2b-256 |
39511e8a1a4f8d616b529a95d09fd687cd051a9fbcac7d154f41a8638ac21701
|