officedev/microsoft-365-agents-toolkit-samples/proxy-agent-dev
- community
Checking that the SKILL.md file is still in the repository.
Develop, debug, and extend a proxy agent that connects any external AI agent to Microsoft 365 Copilot and Teams via direct SDK integration. Use when: understanding the project, explaining the architecture, onboarding to the codebase, building or modifying a Teams proxy agent with M365 Agents SDK, wiring a new backend SDK into agent.ts, configuring SSO, updating Bicep infrastructure, troubleshooting bot messaging, streaming responses, or managing environment config. Stop if: backend has no Node/TS SDK or cannot be called from a Node process.
Install
npx vskill@latest install officedev/microsoft-365-agents-toolkit-samples/proxy-agent-devSKILL.md
Proxy Agent Skill — Connect Any External Agent to M365 Copilot (SDK Pattern)
Wire any backend AI SDK — whether a direct LLM provider or an orchestration framework — into a stable M365 Copilot/Teams integration layer. The proxy handles Teams/M365 Copilot transport, SSO, and streaming — you replace only the backend call.
Use When / Stop When
Use this skill when:
- Backend has a TypeScript/Node SDK (LLM provider or orchestrator)
- Backend supports streaming or can map to chunked responses
- Starting from the ProxyAgent-NodeJS sample
Stop and reassess if:
- Backend has no Node SDK (consider an adapter service instead)
- Backend is synchronous-only with no streaming path and cannot be adapted
Principles
These are non-negotiable. Every code change must preserve them:
- No secrets in source — never commit API keys, passwords, or tokens to the repository
- Secure secret storage — use
${{SECRET_*}}for local dev (ATK encrypts to.env.local.user), Key Vault references for production - Prefer managed identity — when the backend supports Entra ID /
TokenCredential, use federated credentials and managed identities (zero-secret). This is the ideal. - User identity where possible — the user's Entra ID identity flows from M365 Copilot through SSO. Propagate it to the backend when supported; at minimum, use it for audit/logging even when the backend uses a separate auth mechanism.
- Bot Service is control plane only — discovery and auth setup; no user messages flow through it
- Backend is replaceable — swap the SDK without changing the M365 integration shell
- Streaming-first — real-time token streaming from backend to Teams/Copilot UI
Backend Auth Tiers
| Tier | When | How | Example | |
|---|---|---|---|---|
| TokenCredential (ideal) | Backend accepts Entra ID tokens | UserAuthorizationTokenWrapper → SDK credential; zero secrets | Azure AI Foundry, Azure OpenAI | |
| API key + user context (practical) | Backend requires an API key but you still want per-user context | Key Vault for key; SSO token for user identity/logging | OpenAI, Anthropic with user ID pass-through | |
| API key only (acceptable) | Backend has no user-identity concept | Key Vault for key; no per-user context | Simple LLM endpoints | |
| Hardcoded secrets (forbidden) | Never | ❌ | — |
Forbidden Patterns
- ❌ Hardcoding secrets in source — use
${{SECRET_*}}for local dev, Key Vault for production - ❌ Committing
.env.local.useror any file containing plaintext secrets - ❌ Collapsing all users into one backend identity/session when the backend supports per-user identity
- ❌ Routing user messages through Bot Service
- ❌ Storing API keys in plain-text app settings in production (use Key Vault references)
Architecture
Control Plane (setup-time only)
Data Plane (every message)
All conversation traffic flows directly from Teams infrastructure to the App Service. Bot Service handles only auth flows.
Network Security
The proxy agent endpoint (/api/messages) must be publicly accessible. Private Endpoints are not supported when the agent is exposed to public channels like Teams or M365 Copilot — Teams backend services need to reach the endpoint directly over the internet.
Securing the public endpoint:
| Layer | How | Notes | |
|---|---|---|---|
| Channel auth (built-in) | SDK validates JWT bearer token on every inbound request | Automatic via startServer() — proves request is from Microsoft Teams/Copilot | |
| IP-based ACL (recommended) | Restrict inbound traffic to Teams backend IPs listed in Microsoft 365 URLs and IP ranges | App Service Access Restrictions or NSG rules | |
| API Gateway / WAF / Front Door / Azure Firewall | Advanced filtering, rate limiting, DDoS protection | Optional — for additional defense-in-depth |
Note: TheAzureBotServiceservice tag is not relevant here — messages flow from Teams infrastructure, not through Bot Service.
SSO with Federated Credentials
Reference: Add user authorization using Federated Identity Credential — covers app registration setup, redirect URIs, pre-authorized client IDs, federated credential configuration, and OAuth connection creation on the Azure Bot.
- Entra ID App Registration with
access_as_userscope - Federated Credentials link App Registration to Bot's Managed Identity — no client secret
- Bot OAuth Connection uses AADv2 with Federated Credentials for token exchange
- Pre-authorized client IDs: Teams desktop, web, mobile — full list
- Redirect URI: depends on data residency and cloud — see table below
| Data Residency | Cloud | OAuth URL | OAuth Redirect URL | |
|---|---|---|---|---|
| None | Public | https://token.botframework.com | https://token.botframework.com/.auth/web/redirect | |
| Europe | Public | https://europe.token.botframework.com | https://europe.token.botframework.com/.auth/web/redirect | |
| United States | Public | https://unitedstates.token.botframework.com | https://unitedstates.token.botframework.com/.auth/web/redirect | |
| India | Public | https://india.token.botframework.com | https://india.token.botframework.com/.auth/web/redirect | |
| None | Azure Government | https://token.botframework.azure.us | https://token.botframework.azure.us/.auth/web/redirect | |
| None | Azure operated by 21Vianet | https://token.botframework.azure.cn | https://token.botframework.azure.cn/.auth/web/redirect |
- OAuth connection on Azure Bot: use AAD v2 with Federated Credentials service provider — setup steps
- Local dev: Single Tenant + Client Secret (auto-provisioned by toolkit)
- Production: User Assigned Managed Identity — mutual authentication between Bot Service, Teams/Copilot services, and the App Service, ensuring all parties are who they claim to be
Multi-OAuth
If the backend uses a non-Entra IDP, configure a second OAuth connection in Bot Service. The SDK supports multiple OAuth configs declaratively — no proxy code changes needed.
Conversation History
M365 Copilot provides a Conversation ID (context.activity.conversation.id) each time a new conversation is created. The proxy receives this ID with every incoming message.
Key facts:
- M365 Copilot shows chat history to the user — the user sees their past messages in the Copilot UI. This history is managed by M365 Copilot itself and is not accessible to the developer. The proxy agent never reads or controls it.
- The backend owns conversation memory — the proxy should not accumulate or manage message history. If the backend is stateful (Foundry, Assistants API), use the Conversation ID to map to a backend thread/session. If the backend manages its own threads (like Foundry's
AgentThread), store the backend thread ID inturnState.conversationand reuse it on subsequent messages. - Stateless backends create a UX mismatch — the user sees chat history in M365 Copilot and assumes the agent remembers prior context. If the backend is stateless (e.g., a raw chat completion API with no thread management), each message is processed independently and the agent has no memory of the conversation. This will confuse users. If you use a stateless backend, you must either:
- Add a conversation memory layer in the backend (recommended), or
- Make it clear to the user that each message is independent (e.g., in the agent's system prompt or welcome message)
| Backend type | Who owns history | Proxy responsibility | |
|---|---|---|---|
| Stateful (Foundry, Assistants API) | Backend | Map Conversation ID → backend thread ID; store thread ID in turnState.conversation | |
| Stateful with external session | Backend | Pass Conversation ID as session key to the backend | |
| Stateless (raw chat completion) | Nobody — UX gap | Consider adding a memory layer or clearly communicate the limitation |
In the current Foundry sample, the proxy creates a Foundry AgentThread on first message and stores its ID in turnState.conversation.threadInfo. Foundry keeps the full message history server-side — the proxy only holds the thread reference.
Tech Stack
- Runtime: Node.js 22/24, TypeScript 5.9+
Core Dependencies (invariant)
| Package | Purpose | |
|---|---|---|
@microsoft/agents-hosting-express | Express server + channel auth | |
@microsoft/agents-hosting | AgentApplication, TurnContext, TurnState, storage, SSO pipeline | |
@microsoft/agents-activity | Activity type definitions (ActivityTypes.Message) | |
@azure/identity | TokenCredential for zero-secret auth | |
@azure/core-auth | Shared auth interfaces | |
jsonwebtoken | JWT decode/verify for SSO token exchange |
Backend-Specific (you replace these)
LLM providers:
| Example | Package | |
|---|---|---|
| Groq | groq-sdk | |
| OpenAI | openai | |
| Anthropic | @anthropic-ai/sdk | |
| Foundry | @azure/ai-agents |
Orchestrators:
| Example | Package | |
|---|---|---|
| Agent Framework | @microsoft/agents-framework | |
| LangChain / LangGraph | langchain, @langchain/core | |
| CrewAI | Custom HTTP client (Python backend via REST) |
Project Structure
| Path | Purpose | Modify? | |
|---|---|---|---|
src/index.ts | Express server entry — startServer(agentApp) | No | |
src/agent.ts | ProxyAgent class — backend client, message handling, streaming | Yes — backend seam | |
src/config.ts | Environment-based config | Yes — backend vars | |
src/logger.ts | Logging utility | No | |
src/userAuthTokenWrapper.ts | Bridges Agent SDK SSO with AI Foundry auth (TokenCredential). Foundry-specific — not needed for backends that use API keys or other auth. | Only if using Foundry | |
appPackage/manifest.json | Teams app manifest | Display strings only | |
m365agents.yml / .local.yml | ATK project config | Only env var additions | |
infra/ | Bicep modules for Azure deployment | Backend-specific params | |
env/ | Environment variable templates | Yes |
Edit Boundaries in agent.ts
This is the critical file. The backend seam is clearly defined:
REPLACE (the backend seam)
| What | Example | |
|---|---|---|
| Backend SDK import | import MySDK from "my-backend-sdk" | |
| Client initialization | const client = new MySDK({ /* auth from config.ts */ }) | |
| Session/thread mapping | Map Conversation ID → backend thread/session (stateful) or manage history (if needed) | |
| Backend invoke + stream | client.chat.completions.create({ stream: true, ... }) | |
| Response assembly | for await (const chunk of stream) { ... } |
DO NOT MODIFY
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingProxyAgent-NodeJS/.agents/skills/proxy-agent-dev/SKILL.md
Popularity
- Repository stars
- 493
- Repository forks
- 319
- 7-day trend score
- 31
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 2 Aug 2026, 2 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/officedev/microsoft-365-agents-toolkit-samples/proxy-agent-dev)