McpProxy.Samples.Teams.Mcp 1.19.0

dotnet tool install --global McpProxy.Samples.Teams.Mcp --version 1.19.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local McpProxy.Samples.Teams.Mcp --version 1.19.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=McpProxy.Samples.Teams.Mcp&version=1.19.0
                    
nuke :add-package McpProxy.Samples.Teams.Mcp --version 1.19.0
                    

Teams Integration Sample

This sample demonstrates how to use MCP-Proxy's SDK features (hooks, interceptors, and virtual tools) to create a rich Microsoft Teams integration layer.

Authentication Modes

This sample supports three authentication modes:

Mode Transport Auth Handling Best For
forward-auth (default) HTTP/SSE VS Code authenticates with Azure AD, proxy forwards auth header Interactive use, user-specific permissions
proxy-auth stdio Proxy authenticates with Azure AD using app credentials Automated agents, scripts, simplified client setup
user-auth stdio Proxy authenticates as the current user via interactive browser Non-OAuth clients (OpenCode, etc.), user-specific permissions without VS Code

Forward-Auth Mode (Default)

VS Code ──(HTTP + OAuth)──> Proxy ──(forwarded auth)──> Teams MCP Server
  • VS Code handles Azure AD authentication (browser popup)
  • Proxy forwards the Authorization header to Teams MCP Server
  • Each user authenticates with their own credentials
  • User-specific permissions and data access

Proxy-Auth Mode

Client ──(stdio, no auth)──> Proxy ──(Azure AD app auth)──> Teams MCP Server
  • Proxy authenticates using Azure AD Client Credentials flow
  • Clients connect via stdio without any authentication
  • Single app identity for all requests
  • Simpler client configuration, suitable for automation

Features

Automatic Caching

  • File-backed JSON persistence - Cache survives restarts
  • TTL-based freshness - 4-hour default, configurable
  • Tiered lookup - Recent contacts → Full cache → Built-in (self-chat)
  • Automatic population - Cache is updated from MCP responses

Cache Short-Circuiting

  • Interceptor-based - Returns cached data without MCP calls
  • Transparent to LLM - Results include fromCache: true flag
  • Respects TTL - Only short-circuits when cache is fresh

Security

  • Credential scanning - Blocks messages containing API keys, tokens, passwords
  • Configurable - Block or warn-only mode
  • Multiple patterns - AWS keys, JWT tokens, connection strings, etc.

Automatic Pagination

  • Enforced limits - ListChats/ListTeams limited to 20 items
  • Automatic injection - Adds top parameter if not specified
  • Capping - Reduces oversized requests to safe limits

Virtual Tools

  • teams_resolve - Unified resolver for people, chats, teams, channels
  • teams_lookup_* - Direct ID-based lookups
  • teams_cache_* - Cache management
  • teams_parse_url - Extract IDs from Teams URLs
  • teams_validate_message - Pre-send credential check

Running the Sample

Prerequisites

  1. Your Azure AD tenant ID
  2. For forward-auth mode: VS Code with the MCP extension
  3. For proxy-auth mode: An Azure AD app registration with client credentials

Forward-Auth Mode (Default)

This mode requires VS Code to handle OAuth authentication.

cd samples/15-teams-integration

# Using environment variables
$env:TENANT_ID = "your-tenant-id"
dotnet run

# Or using command line arguments
dotnet run -- --tenant-id=your-tenant-id

# Optional: customize the port (default: 5100)
dotnet run -- --tenant-id=your-tenant-id --port=5200
Discovering Token Claims (--log-token)

When building a user-auth integration (e.g., connecting from a client that does not support OAuth like OpenCode), you need to know VS Code's client ID, the audience, and the scopes that the Teams MCP Server requires. Pass --log-token to log the JWT claims from the first authenticated request VS Code makes:

dotnet run -- --tenant-id=your-tenant-id --log-token

Or set the environment variable:

$env:LOG_TOKEN = "true"
dotnet run -- --tenant-id=your-tenant-id

After VS Code connects and authenticates, the proxy logs the token claims to stderr:

═══════════════════════════════════════════════════════════════════════════
TOKEN CLAIMS (from VS Code's first authenticated request)
═══════════════════════════════════════════════════════════════════════════
  appid (Client ID):   aebc6443-996d-45c2-90f0-388ff96faa56
  aud   (Audience):    api://teams-mcp-server/...
  scp   (Scopes):      TeamsActivity.Send Chat.ReadWrite ...
  tid   (Tenant ID):   your-tenant-id
  upn   (User):        user@company.com
  iss   (Issuer):      https://sts.windows.net/your-tenant-id/

Use these values for user-auth mode:
  --auth-mode=user-auth --vscode-client-id=aebc6443-996d-45c2-90f0-388ff96faa56
  --scopes=api://teams-mcp-server/TeamsActivity.Send,...
═══════════════════════════════════════════════════════════════════════════

Use the logged appid value as the --vscode-client-id for user-auth mode.

VS Code Configuration (.vscode/mcp.json):

{
  "servers": {
    "teams-proxy": {
      "type": "http",
      "url": "http://localhost:5100/mcp"
    }
  }
}

Proxy-Auth Mode

This mode uses Azure AD Client Credentials flow - the proxy authenticates with an app identity.

Azure AD App Setup
  1. Register an app in Azure AD (Azure Portal)
  2. Create a client secret (Certificates & secrets → New client secret)
  3. Grant API permissions:
    • Microsoft Graph → Application permissions → relevant permissions for Teams
    • Admin consent may be required
  4. Note the Application (client) ID and tenant ID
Running
cd samples/15-teams-integration

# Using environment variables (recommended)
$env:TENANT_ID = "your-tenant-id"
$env:AUTH_MODE = "proxy-auth"
$env:AZURE_CLIENT_ID = "your-app-client-id"
$env:AZURE_CLIENT_SECRET = "your-client-secret"
dotnet run

# Or using command line arguments
dotnet run -- --auth-mode=proxy-auth --tenant-id=your-tenant-id --client-id=your-app-id --client-secret=your-secret

# Optional: customize scopes (default: https://graph.microsoft.com/.default)
$env:AZURE_SCOPES = "api://your-api/.default"
dotnet run

VS Code Configuration (.vscode/mcp.json):

{
  "servers": {
    "teams-proxy": {
      "type": "stdio",
      "command": "dotnet",
      "args": ["run", "--project", "path/to/samples/15-teams-integration"],
      "env": {
        "TENANT_ID": "your-tenant-id",
        "AUTH_MODE": "proxy-auth",
        "AZURE_CLIENT_ID": "your-app-client-id",
        "AZURE_CLIENT_SECRET": "your-client-secret"
      }
    }
  }
}

User-Auth Mode

This mode authenticates as the current user using a pre-authorized public client ID (e.g., VS Code's app registration) with interactive browser login. No app registration of your own is needed.

This uses the built-in InteractiveBrowser backend auth type from the SDK, which can also be configured via JSON for any MCP server (see below).

Client ──(stdio, no auth)──> Proxy ──(user-delegated Bearer token)──> Teams MCP Server
                              │
                              ├─ First run: opens browser for sign-in
                              └─ Subsequent runs: silent (cached refresh token)
  • Proxy authenticates as the current user via interactive browser
  • Uses a pre-authorized public client ID (discover via --log-token in forward-auth mode)
  • Clients connect via stdio without any authentication
  • Token is cached in OS credential store — only needs browser sign-in once (~90 day refresh token)
  • User-specific permissions and data access (same as forward-auth)
Prerequisites
  1. Discover the public client ID using --log-token in forward-auth mode (see above)
  2. Your Azure AD tenant ID
Running
cd samples/15-teams-integration

# Using environment variables (recommended)
$env:TENANT_ID = "your-tenant-id"
$env:AUTH_MODE = "user-auth"
$env:PUBLIC_CLIENT_ID = "your-public-client-id"
dotnet run

# Or using command line arguments
dotnet run -- --auth-mode=user-auth --tenant-id=your-tenant-id --public-client-id=your-public-client-id

# Optional: pre-authenticate with --login (caches token, then exits)
dotnet run -- --auth-mode=user-auth --tenant-id=your-tenant-id --public-client-id=your-public-client-id --login

# Optional: provide scopes explicitly (auto-discovered from RFC 9728 metadata if omitted)
$env:AZURE_SCOPES = "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default"
dotnet run

OpenCode Configuration (opencode.json):

{
  "mcp": {
    "teams-proxy": {
      "type": "local",
      "command": ["dotnet", "run", "--project", "path/to/samples/15-teams-integration"],
      "env": {
        "TENANT_ID": "your-tenant-id",
        "AUTH_MODE": "user-auth",
        "PUBLIC_CLIENT_ID": "your-public-client-id"
      },
      "enabled": true
    }
  }
}

VS Code Configuration (.vscode/mcp.json):

{
  "servers": {
    "teams-proxy": {
      "type": "stdio",
      "command": "dotnet",
      "args": ["run", "--project", "path/to/samples/15-teams-integration"],
      "env": {
        "TENANT_ID": "your-tenant-id",
        "AUTH_MODE": "user-auth",
        "PUBLIC_CLIENT_ID": "your-public-client-id"
      }
    }
  }
}
Using JSON Configuration (without custom code)

Since InteractiveBrowser is a built-in SDK auth type, you can also configure it directly in mcp-proxy.json and use the mcpproxy CLI tool. This works for any MCP server that uses the same OAuth pattern (Teams, Mail, Calendar, etc.):

{
  "mcp": {
    "teams": {
      "type": "http",
      "url": "https://agent365.svc.cloud.microsoft/agents/tenants/${TENANT_ID}/servers/mcp_TeamsServer",
      "auth": {
        "type": "InteractiveBrowser",
        "azureAd": {
          "clientId": "${PUBLIC_CLIENT_ID}",
          "tenantId": "${TENANT_ID}",
          "scopes": ["ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default"]
        }
      }
    },
    "mail": {
      "type": "http",
      "url": "https://agent365.svc.cloud.microsoft/agents/tenants/${TENANT_ID}/servers/mcp_MailTools",
      "auth": {
        "type": "InteractiveBrowser",
        "azureAd": {
          "clientId": "${PUBLIC_CLIENT_ID}",
          "tenantId": "${TENANT_ID}",
          "scopes": ["ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default"]
        }
      }
    },
    "calendar": {
      "type": "http",
      "url": "https://agent365.svc.cloud.microsoft/agents/tenants/${TENANT_ID}/servers/mcp_CalendarTools",
      "auth": {
        "type": "InteractiveBrowser",
        "azureAd": {
          "clientId": "${PUBLIC_CLIENT_ID}",
          "tenantId": "${TENANT_ID}",
          "scopes": ["ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default"]
        }
      }
    }
  }
}

Then run with the standard CLI:

mcpproxy -t stdio -c mcp-proxy.json

Use as a Backend of Another mcp-proxy

When packaged as a .NET tool (see TeamsIntegration.csproj), sample 15 can be consumed by another mcp-proxy instance as a plain stdio backend — exposing all its caching, hooks, virtual tools, and credential scanning to the parent proxy without the parent needing any custom code.

Sample 16 demonstrates this pattern: it aggregates four Microsoft 365 InteractiveBrowser HTTP backends plus this sample (Teams) as a stdio backend, so a single browser sign-in covers all five backends.

Tool package

Field Value
Package id McpProxy.Samples.Teams.Mcp
Tool command teams-mcp
Versions Tracks the repo Version in Directory.Build.props (currently 1.18.0)

Pack locally with ./pack.ps1 (produces artifacts/McpProxy.Samples.Teams.Mcp.<version>.nupkg) and install via dnx --add-source ./artifacts McpProxy.Samples.Teams.Mcp@<version> --yes -- --help.

Parent proxy configuration (mcp-proxy.json snippet)

"teams": {
  "type": "stdio",
  "title": "Microsoft 365 Teams",
  "description": "Teams MCP with caching, hooks, virtual tools, and credential scanning",
  "command": "dnx",
  "arguments": [
    "McpProxy.Samples.Teams.Mcp@1.18.0",
    "--yes",
    "--",
    "--auth-mode=user-auth",
    "--tenant-id=${TENANT_ID}",
    "--public-client-id=${PUBLIC_CLIENT_ID}",
    "--token-cache-name=mcp-proxy",
    "--defer-connection"
  ],
  "environment": {
    "AZURE_SCOPES": "${AZURE_AUDIENCE}/.default"
  },
  "enabled": true
}

Flags relevant to chained usage

Flag / env var Purpose
--auth-mode=user-auth / AUTH_MODE Required: stdio transport with InteractiveBrowser outbound auth
--token-cache-name <name> / AZURE_TOKEN_CACHE_NAME Persistent token-cache name used by InteractiveBrowserCredential. Set this to match the parent proxy's cache name (sample 16 defaults to mcp-proxy) so the cached refresh token is shared and only one browser sign-in is needed across all backends. Default when not set: mcp-proxy-teams (the original standalone value).
--defer-connection / DEFER_CONNECTION=true Delays the outbound Teams MCP connection until the first tool call from the parent proxy. Without this, sample 15 connects eagerly at child-process startup and may trigger a browser prompt before the user actually invokes a Teams tool.
AZURE_SCOPES Pass the same audience the parent proxy uses (e.g. ${AZURE_AUDIENCE}/.default) so the cached access token is reused across backends.

See sample 16's README for the full end-to-end configuration.

Environment Variables

Variable Required Mode Description
TENANT_ID Yes All Azure AD tenant ID
AUTH_MODE No All forward-auth (default), proxy-auth, or user-auth
PORT No forward-auth HTTP port (default: 5100)
LOG_TOKEN No forward-auth Set to true to log JWT claims from the first authenticated request
PUBLIC_CLIENT_ID Yes user-auth Pre-authorized public client ID (discover via --log-token). Also accepts VSCODE_CLIENT_ID for backward compatibility
AZURE_CLIENT_ID Yes proxy-auth App registration client ID
AZURE_CLIENT_SECRET Yes proxy-auth App registration client secret
AZURE_SCOPES No proxy-auth, user-auth Comma-separated scopes (auto-discovered from RFC 9728 metadata in user-auth mode)
AZURE_TOKEN_CACHE_NAME No user-auth Persistent token-cache name (default mcp-proxy-teams). Override to match a parent proxy's cache name (e.g. mcp-proxy) when used as a backend of another mcp-proxy.
DEFER_CONNECTION No user-auth When true, defers the outbound Teams MCP connection until the first tool call. Equivalent to --defer-connection.

Configuration Options

Option Default Description
CacheFilePath %LOCALAPPDATA%/mcp-proxy/teams-cache.json Cache file location
CacheTtl 4 hours Time-to-live for cache entries
MaxRecentContacts 50 Size of recent contacts list
DefaultPaginationLimit 20 Max items per list request
EnableCredentialScanning true Scan outbound messages
BlockCredentials true Block or just warn
EnableMessagePrefix false Add prefix to messages
MessagePrefix "[AI]" Prefix text
EnableCachePopulation true Auto-populate from responses
EnableCacheShortCircuit true Return cached data
EnableAutoPagination true Enforce pagination limits
RegisterVirtualTools true Add virtual tools

Virtual Tools Reference

teams_resolve

Unified resolver for all Teams entities.

{
  "query": "John Smith",
  "type": "person"  // optional: any, person, chat, team, channel
}

teams_parse_url

Extract IDs from Teams URLs.

{
  "url": "https://teams.microsoft.com/l/chat/19:abc...@thread.v2"
}

teams_validate_message

Pre-check message for credentials.

{
  "message": "Here's the API key: sk-abc123..."
}

Architecture

Forward-Auth Mode

┌─────────────────────────────────────────────────────────────┐
│                      VS Code / LLM                           │
│                                                              │
│  1. Fetches /.well-known/oauth-authorization-server         │
│  2. Receives OAuth metadata (proxied from Teams Server)     │
│  3. Authenticates with Azure AD (browser popup)             │
│  4. Sends requests with Authorization header                │
└───────────────────────────┬─────────────────────────────────┘
                            │ HTTP with Authorization header
┌───────────────────────────▼─────────────────────────────────┐
│              MCP Proxy (localhost:5100/mcp)                  │
│  ┌─────────────────────────────────────────────────────┐    │
│  │         OAuthMetadataProxyMiddleware                 │    │
│  │    (proxies /.well-known/* from Teams Server)        │    │
│  └─────────────────────────────────────────────────────┘    │
│  ┌─────────────────────────────────────────────────────┐    │
│  │         ForwardAuthorizationHandler                  │    │
│  │    (forwards incoming Authorization header)          │    │
│  └─────────────────────────────────────────────────────┘    │
│                        ... hooks, cache, etc ...             │
└───────────────────────────┬─────────────────────────────────┘
                            │ SSE with forwarded Authorization
┌───────────────────────────▼─────────────────────────────────┐
│                    Teams MCP Server                          │
└─────────────────────────────────────────────────────────────┘

Proxy-Auth Mode

┌─────────────────────────────────────────────────────────────┐
│                      VS Code / LLM                           │
│                                                              │
│  Connects via stdio - no authentication required             │
└───────────────────────────┬─────────────────────────────────┘
                            │ stdio (no auth)
┌───────────────────────────▼─────────────────────────────────┐
│                   MCP Proxy (stdio)                          │
│  ┌─────────────────────────────────────────────────────┐    │
│  │         AzureAdCredentialProvider                    │    │
│  │    (acquires tokens using client credentials)        │    │
│  └─────────────────────────────────────────────────────┘    │
│  ┌─────────────────────────────────────────────────────┐    │
│  │         AzureAdAuthorizationHandler                  │    │
│  │    (adds Bearer token to outbound requests)          │    │
│  └─────────────────────────────────────────────────────┘    │
│                        ... hooks, cache, etc ...             │
└───────────────────────────┬─────────────────────────────────┘
                            │ SSE with Bearer token
┌───────────────────────────▼─────────────────────────────────┐
│                    Teams MCP Server                          │
└─────────────────────────────────────────────────────────────┘

User-Auth Mode

┌─────────────────────────────────────────────────────────────┐
│                  OpenCode / Any MCP Client                    │
│                                                              │
│  Connects via stdio - no authentication required             │
└───────────────────────────┬─────────────────────────────────┘
                            │ stdio (no auth)
┌───────────────────────────▼─────────────────────────────────┐
│                   MCP Proxy (stdio)                          │
│  ┌─────────────────────────────────────────────────────┐    │
│  │         InteractiveBrowserCredential                 │    │
│  │    (VS Code's client ID, persistent token cache)     │    │
│  │    First run: opens browser for sign-in              │    │
│  │    Subsequent: silent (cached refresh token)         │    │
│  └─────────────────────────────────────────────────────┘    │
│  ┌─────────────────────────────────────────────────────┐    │
│  │         DefaultAzureCredentialAuthHandler             │    │
│  │    (adds user-delegated Bearer token to requests)    │    │
│  └─────────────────────────────────────────────────────┘    │
│                        ... hooks, cache, etc ...             │
└───────────────────────────┬─────────────────────────────────┘
                            │ HTTP with user-delegated Bearer token
┌───────────────────────────▼─────────────────────────────────┐
│                    Teams MCP Server                          │
│  Token has appid = VS Code's client ID (pre-authorized)      │
└─────────────────────────────────────────────────────────────┘

Project Structure

15-teams-integration/
├── Cache/
│   ├── Models/
│   │   └── CacheModels.cs      # Cache entity records
│   ├── ITeamsCacheService.cs   # Cache service interface
│   └── TeamsCacheService.cs    # Thread-safe implementation
├── Hooks/
│   ├── TeamsPaginationHook.cs      # Auto-pagination
│   ├── TeamsCredentialScanHook.cs  # Security scanning
│   ├── TeamsMessagePrefixHook.cs   # Message prefixing
│   └── TeamsCachePopulateHook.cs   # Auto-cache population
├── Interceptors/
│   └── TeamsCacheInterceptor.cs    # Cache short-circuiting
├── Utilities/
│   ├── CredentialScanner.cs    # Regex-based credential detection
│   └── TeamsUrlParser.cs       # Teams URL parsing
├── VirtualTools/
│   └── TeamsVirtualTools.cs    # All virtual tool definitions
├── TeamsIntegrationOptions.cs  # Configuration options
├── TeamsIntegrationExtensions.cs # SDK extension method
├── Program.cs                  # Application entry point
├── mcp-proxy.json             # Sample configuration
├── SKILL.md                   # LLM guidance document
└── README.md                  # This file

See Also

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
1.19.0 108 5/17/2026

Sample showing how to compose the Teams Integration with another mcp-proxy (e.g. sample 16) via dnx.