How to Integrate AI Agents with Microsoft Teams

Step-by-step guide to building AI agents that work within Microsoft Teams. Covers Azure App Registration, Microsoft Graph API for Teams, LangChain tools, Microsoft Copilot Studio, and enterprise compliance considerations with working Python examples.

Before You Start

Prerequisites: Microsoft 365 account (Business Basic or higher), Azure account with permission to register applications, Azure App Registration with Teams and Microsoft Graph permissions, Basic Python knowledge (for LangChain option)

Works with: LangChain, Microsoft Copilot Studio, n8n

Microsoft Teams is where enterprise work happens — meetings are scheduled there, questions are asked there, and decisions get made there. An AI agent embedded in Teams can answer HR policy questions instantly, summarize meetings, route support requests, and trigger workflows directly from the conversations where they originate.

This guide covers the full integration path: Azure App Registration, Microsoft Graph API tools for LangChain, the no-code Microsoft Copilot Studio option, and enterprise compliance considerations.

What AI Agents Can Do in Microsoft Teams#

Mapping out the use cases before building prevents scope creep:

Communication and messaging

  • Send messages to channels and chats (announcements, alerts, summaries)
  • Read channel messages for context or monitoring
  • Respond to mentions and direct messages
  • Post cards (Adaptive Cards) with structured information and action buttons

Meeting lifecycle

  • Create and schedule Teams meetings via Microsoft Graph
  • Retrieve meeting transcripts and recordings (with appropriate permissions)
  • Generate meeting summaries and distribute them to channels
  • Add participants to existing meetings

File and content access

  • Read files shared in Teams channels (SharePoint-backed)
  • Search Teams conversations for specific information
  • Retrieve tab content and pinned resources

Administrative workflows

  • Create new teams and channels programmatically
  • Manage team membership
  • Retrieve organization chart information via Microsoft Graph

Step 1: Azure App Registration#

The Microsoft Graph API (which powers Teams programmatic access) requires an Azure Active Directory (now Entra ID) application registration. This is the authentication foundation for all custom agents.

Create the App Registration#

  1. Go to the Azure Portal and navigate to Azure Active DirectoryApp registrations
  2. Click New registration
  3. Name: e.g., "AI Agent - Teams Integration"
  4. Supported account types: Accounts in this organizational directory only (single tenant, recommended for enterprise)
  5. Redirect URI: Leave blank for now (not needed for application permissions)
  6. Click Register

Copy the following values — you will need them for your agent:

  • Application (client) ID
  • Directory (tenant) ID

Add API Permissions#

  1. In your app registration, go to API permissionsAdd a permissionMicrosoft Graph
  2. Choose Application permissions (your agent runs as itself, not on behalf of a user)
  3. Add the following permissions:

| Permission | Purpose | |-----------|---------| | ChannelMessage.Read.All | Read messages in channels | | ChannelMessage.Send | Send messages to channels | | Chat.ReadWrite.All | Read and write direct messages | | OnlineMeetings.ReadWrite.All | Create and manage meetings | | Files.Read.All | Read files in Teams/SharePoint | | Team.ReadBasic.All | List teams and channels |

  1. Click Grant admin consent for [your organization] — this requires a Global Administrator or Privileged Role Administrator to approve

Create a Client Secret#

  1. Go to Certificates & secretsNew client secret
  2. Set an expiration (12 or 24 months recommended)
  3. Copy the Value immediately — it is only shown once

Store your credentials securely:

export AZURE_TENANT_ID="your-tenant-id"
export AZURE_CLIENT_ID="your-client-id"
export AZURE_CLIENT_SECRET="your-client-secret"
export TEAMS_TARGET_TEAM_ID="your-team-id"
export TEAMS_TARGET_CHANNEL_ID="your-channel-id"

Step 2: Build Teams Tools for LangChain#

Install the required packages:

pip install langchain langchain-openai msal requests python-dotenv
import requests
import msal
from langchain.tools import tool
from dotenv import load_dotenv
import os
import json

load_dotenv()

# Microsoft Graph API base URL
GRAPH_BASE = "https://graph.microsoft.com/v1.0"

# Authentication: get an access token using client credentials flow
def get_graph_token() -> str:
    """Acquire a Microsoft Graph API access token using client credentials."""
    tenant_id = os.getenv("AZURE_TENANT_ID")
    client_id = os.getenv("AZURE_CLIENT_ID")
    client_secret = os.getenv("AZURE_CLIENT_SECRET")

    authority = f"https://login.microsoftonline.com/{tenant_id}"
    app = msal.ConfidentialClientApplication(
        client_id=client_id,
        authority=authority,
        client_credential=client_secret,
    )

    result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])

    if "access_token" not in result:
        raise Exception(f"Authentication failed: {result.get('error_description', 'Unknown error')}")

    return result["access_token"]


def graph_get(endpoint: str) -> dict:
    """Make an authenticated GET request to Microsoft Graph."""
    token = get_graph_token()
    response = requests.get(
        f"{GRAPH_BASE}{endpoint}",
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
        timeout=15
    )
    response.raise_for_status()
    return response.json()


def graph_post(endpoint: str, payload: dict) -> dict:
    """Make an authenticated POST request to Microsoft Graph."""
    token = get_graph_token()
    response = requests.post(
        f"{GRAPH_BASE}{endpoint}",
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
        json=payload,
        timeout=15
    )
    response.raise_for_status()
    return response.json()


@tool
def send_teams_channel_message(team_id: str, channel_id: str, message: str) -> str:
    """
    Send a message to a Microsoft Teams channel.
    team_id: The Teams group ID.
    channel_id: The channel ID within the team.
    message: The message text to send (supports basic Markdown).
    """
    payload = {
        "body": {
            "contentType": "html",
            "content": message.replace("\n", "<br>")
        }
    }

    result = graph_post(
        f"/teams/{team_id}/channels/{channel_id}/messages",
        payload
    )
    return f"Message sent to Teams channel. Message ID: {result.get('id', 'unknown')}"


@tool
def get_channel_messages(team_id: str, channel_id: str, limit: int = 10) -> str:
    """
    Retrieve recent messages from a Teams channel.
    Returns the last N messages with sender name and content.
    """
    result = graph_get(f"/teams/{team_id}/channels/{channel_id}/messages?$top={limit}")
    messages = result.get("value", [])

    if not messages:
        return "No messages found in this channel."

    formatted = []
    for msg in messages:
        sender = msg.get("from", {}).get("user", {}).get("displayName", "Unknown")
        body = msg.get("body", {}).get("content", "")
        created = msg.get("createdDateTime", "")[:10]
        # Strip HTML tags for plain text
        import re
        body_text = re.sub(r"<[^>]+>", "", body).strip()
        formatted.append(f"[{created}] {sender}: {body_text[:300]}")

    return "\n".join(formatted)


@tool
def create_teams_meeting(
    subject: str,
    start_datetime: str,
    end_datetime: str,
    attendee_emails: str
) -> str:
    """
    Create a Microsoft Teams online meeting.
    start_datetime and end_datetime: ISO 8601 format, e.g., '2026-03-01T14:00:00'
    attendee_emails: comma-separated email addresses
    Returns the Teams meeting join URL.
    """
    attendees = [
        {"emailAddress": {"address": email.strip()}, "type": "required"}
        for email in attendee_emails.split(",")
    ]

    payload = {
        "subject": subject,
        "startDateTime": f"{start_datetime}Z",
        "endDateTime": f"{end_datetime}Z",
        "attendees": attendees,
        "isOnlineMeeting": True,
        "onlineMeetingProvider": "teamsForBusiness"
    }

    # Note: Creating calendar events requires a user context (delegated permissions)
    # For application-only meetings, use the /communications/onlineMeetings endpoint
    result = graph_post("/me/events", payload)
    join_url = result.get("onlineMeeting", {}).get("joinUrl", "No join URL available")
    return f"Teams meeting created: '{subject}'. Join URL: {join_url}"


@tool
def search_teams_messages(team_id: str, search_query: str) -> str:
    """
    Search for messages in Teams channels matching a query.
    Returns relevant messages with context.
    """
    # Use Microsoft Search API for Teams content
    payload = {
        "requests": [
            {
                "entityTypes": ["chatMessage"],
                "query": {"queryString": search_query},
                "from": 0,
                "size": 5
            }
        ]
    }

    result = graph_post("/search/query", payload)
    hits = result.get("value", [{}])[0].get("hitsContainers", [{}])[0].get("hits", [])

    if not hits:
        return f"No Teams messages found for query: {search_query}"

    formatted = []
    for hit in hits:
        resource = hit.get("resource", {})
        sender = resource.get("from", {}).get("user", {}).get("displayName", "Unknown")
        summary = hit.get("summary", "No preview available")
        formatted.append(f"{sender}: {summary}")

    return f"Search results for '{search_query}':\n" + "\n---\n".join(formatted)

Step 3: Build the Teams Agent#

from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [send_teams_channel_message, get_channel_messages, create_teams_meeting, search_teams_messages]

TEAM_ID = os.getenv("TEAMS_TARGET_TEAM_ID")
CHANNEL_ID = os.getenv("TEAMS_TARGET_CHANNEL_ID")

prompt = ChatPromptTemplate.from_messages([
    ("system", f"""You are an enterprise AI assistant for Microsoft Teams.
    Default team ID: {TEAM_ID}
    Default channel ID: {CHANNEL_ID}

    You can:
    - Read and send messages to Teams channels
    - Search Teams conversations for information
    - Create Teams meetings and send invites
    - Answer questions by searching for relevant conversations

    Always be concise in Teams messages — people read them on mobile.
    Use line breaks and bullet points for clarity.
    Never share personal information or confidential data in public channels."""),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

Use Case 1: HR Policy Question-Answering Agent#

Deploy an agent in the #hr-questions channel that reads your HR documentation (from SharePoint or Notion) and answers policy questions instantly.

# Example invocation
result = executor.invoke({
    "input": "An employee asked in #hr-questions: 'How many days of parental leave do we get and when does it start?' Please search our HR documentation and post the answer to the channel."
})

The agent searches Teams messages for existing answers, retrieves HR documentation via its knowledge base tools, and posts a clear, formatted response to the channel — citing the policy source.


Use Case 2: Automated Meeting Scheduler and Summarizer#

This workflow combines two agent runs: one to schedule the meeting (triggered by a channel request) and one to summarize it afterward (triggered by a webhook from your meeting transcription service).

# Scheduling request
result = executor.invoke({
    "input": """A user in Teams requested: 'Can you schedule a 30-min sprint planning meeting for the engineering team
    next Monday at 2pm EST? Attendees: alice@company.com, bob@company.com, carlos@company.com'
    Create the Teams meeting and post the join link in the #engineering channel."""
})

For post-meeting summarization, connect your transcription service's webhook to your agent endpoint, then have the agent format the transcript and post a structured summary (decisions, action items, next steps) to the relevant channel.


Option 2: Microsoft Copilot Studio (No-Code)#

Best for: IT, HR, and operations teams who want a working Teams agent without infrastructure management.

Microsoft Copilot Studio (formerly Power Virtual Agents) is Microsoft's native no-code agent builder, deeply integrated with Teams.

Key capabilities:#

  • Visual conversation designer with built-in LLM reasoning
  • Native Teams deployment (no Azure app registration required)
  • Connects to SharePoint, Dataverse, Power Automate, and Microsoft 365 data
  • Pre-built templates for HR FAQ, IT helpdesk, and customer service
  • Built-in authentication using your existing Microsoft 365 SSO

Setup overview:#

  1. Go to copilotstudio.microsoft.com
  2. Create a new copilot, select Teams as the channel
  3. Add knowledge sources: SharePoint sites, uploaded files, or public URLs
  4. Configure actions: Power Automate flows, direct API connections
  5. Publish to Teams — it appears as a bot your users can chat with

When to choose Copilot Studio over a custom agent:#

  • You need deployment in under a day
  • Your data lives in SharePoint, Microsoft 365, or Dataverse
  • You do not have Python/LangChain engineering resources
  • Your use case fits FAQ, HR, or IT helpdesk patterns
  • Enterprise compliance (data residency, audit logs) is paramount

Enterprise Considerations#

Data Residency#

Teams message content stays within your Microsoft 365 tenant's geographic boundary. When your custom agent calls the Graph API, the content briefly passes through your agent's hosting environment. If your agent uses an external LLM API (OpenAI, Anthropic), message content leaves your Microsoft environment. Review your DPA (Data Processing Agreement) with the LLM provider before sending Teams content externally.

Mitigation: Use Azure OpenAI Service instead of the public OpenAI API. Azure OpenAI processes data within Azure's infrastructure and can be configured to stay within your tenant's region, with no data used for model training.

Azure AD Permission Scoping#

Grant only the permissions your agent actually uses. Each permission is a potential attack surface if credentials are compromised. Use Azure AD Conditional Access to restrict the app registration to specific IP ranges (your agent's hosting environment).

Audit Logging#

All Microsoft Graph API calls are logged in the Microsoft 365 Unified Audit Log. Review these logs regularly to confirm your agent is only accessing resources it should. Set up Azure Monitor alerts for unusual patterns (e.g., bulk message reads, off-hours activity).

Token Management#

Application access tokens expire after one hour. Implement a token cache (MSAL includes one) to avoid re-authenticating on every API call. Never log tokens or include them in error messages.


Comparison: Custom Teams Bot vs. Microsoft Copilot Studio#

| Factor | Custom Agent (LangChain) | Microsoft Copilot Studio | |--------|--------------------------|--------------------------| | Build time | 1-3 days | 4-8 hours | | Technical requirement | Python + Azure experience | None | | LLM choice | Any (OpenAI, Anthropic, local) | Microsoft models only | | Integration depth | Any API you can code | Microsoft ecosystem + connectors | | Cost model | LLM API + hosting | Per-session Copilot Studio licensing | | Data control | Full (your infrastructure) | Microsoft tenant | | Customization | Unlimited | Limited to platform features | | Enterprise compliance | You manage it | Built-in Microsoft compliance |


Next Steps#

With your Teams agent operational, you can extend it into a broader enterprise automation system: