showcase/shell-docs/src/content/docs/frontends/teams.mdx
import { Accordions, Accordion } from "fumadocs-ui/components/accordion"; import { Callout } from "fumadocs-ui/components/callout"; import { Step, Steps } from "fumadocs-ui/components/steps"; import { Tab, Tabs } from "fumadocs-ui/components/tabs";
By the end of this guide, you will have a small Node app that can answer Microsoft Teams messages through Copilot Runtime. You will test it locally first with Teams DevTools, then optionally expose the same bot through Azure Bot Service and sideload it into Microsoft Teams.
@copilotkit/bot-teams handles the Teams-specific work: receiving Teams activities, rendering
Adaptive Cards, streaming updates, and running the local DevTools bridge. Your app provides the
Copilot Runtime, agent configuration, Microsoft credentials, tunnel, and Teams manifest.
| Process | Port | Purpose |
|---|---|---|
| Copilot Runtime | 8200 | Runs a BuiltInAgent and exposes /api/copilotkit |
| Teams bot | 3978 | Receives Teams activities at POST /api/messages |
| Teams DevTools | 3979 | Local browser UI for testing Teams messages without a Teams account |
For local development:
For sideloading into Microsoft Teams:
devtunnel CLIStart with a new Node project and install Copilot Runtime, the core package, and the Teams bridge:
<Tabs items={["npm", "pnpm", "yarn"]}> <Tab value="npm">
```bash
mkdir copilotkit-teams-bot
cd copilotkit-teams-bot
npm init -y
npm install @copilotkit/[email protected] @copilotkit/[email protected] @copilotkit/runtime
npm install -D tsx typescript @types/node
```
```bash
mkdir copilotkit-teams-bot
cd copilotkit-teams-bot
pnpm init
pnpm add @copilotkit/[email protected] @copilotkit/[email protected] @copilotkit/runtime
pnpm add -D tsx typescript @types/node
```
```bash
mkdir copilotkit-teams-bot
cd copilotkit-teams-bot
yarn init -y
yarn config set nodeLinker node-modules
yarn add @copilotkit/[email protected] @copilotkit/[email protected] @copilotkit/runtime
yarn add -D tsx typescript @types/node
```
Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
Create bot.cts. This single file starts both Copilot Runtime and the Teams-facing bot:
import { createServer } from "node:http";
import { CopilotKitCore } from "@copilotkit/core";
import { createTeamsAgentBot } from "@copilotkit/bot-teams/bot";
import { BuiltInAgent, CopilotRuntime } from "@copilotkit/runtime/v2";
import { createCopilotNodeListener } from "@copilotkit/runtime/v2/node";
const agentId = "assistant";
const runtimePort = Number(process.env.RUNTIME_PORT ?? 8200);
const botPort = Number(process.env.PORT ?? 3978);
const runtimeUrl = `http://localhost:${runtimePort}/api/copilotkit`;
const runtime = new CopilotRuntime({
agents: {
[agentId]: new BuiltInAgent({
model: process.env.OPENAI_MODEL ?? "openai:gpt-5-mini",
prompt:
"You are a helpful Microsoft Teams assistant. Keep replies concise and useful.",
}),
},
});
createServer(
createCopilotNodeListener({
runtime,
basePath: "/api/copilotkit",
cors: true,
}),
).listen(runtimePort, () => {
console.log(`Copilot Runtime listening at ${runtimeUrl}`);
});
const core = new CopilotKitCore({ runtimeUrl });
core.setDefaultThrottleMs(1000);
core.registerProxiedAgent({
agentId,
runtimeAgentId: agentId,
});
async function main() {
const bot = createTeamsAgentBot({
core,
agentId,
approvalTimeoutMs: 5 * 60 * 1000,
reviewerName: "Reviewer",
});
await bot.start(botPort);
console.log(`Teams bot listening at http://localhost:${botPort}/api/messages`);
console.log(`Teams DevTools available at http://localhost:${botPort + 1}/devtools`);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
Your project should now look like this:
copilotkit-teams-bot/
package.json
tsconfig.json
bot.cts
Set your OpenAI key and start the bot:
<Tabs items={["macOS/Linux", "Windows PowerShell"]}> <Tab value="macOS/Linux">
```bash
export OPENAI_API_KEY=sk-...
npx tsx bot.cts
```
```powershell
$env:OPENAI_API_KEY="sk-..."
npx tsx bot.cts
```
Open http://localhost:3979/devtools and send:
Hello from Teams
The local DevTools path does not require Microsoft credentials. The real Teams client does. Keep the
same bot.cts; add a tunnel, a Microsoft app registration, an Azure Bot resource, and a Teams app
manifest.
In a separate terminal, expose the bot port:
devtunnel create copilotkit-teams-local -a
devtunnel port create copilotkit-teams-local -p 3978
devtunnel host copilotkit-teams-local
Copy the HTTPS tunnel URL. Your bot messaging endpoint will be:
https://<your-tunnel-host>/api/messages
In Microsoft Entra ID:
In Azure Bot Service:
https://<your-tunnel-host>/api/messages.Run the bot with those credentials:
<Tabs items={["macOS/Linux", "Windows PowerShell"]}> <Tab value="macOS/Linux">
```bash
export OPENAI_API_KEY=sk-...
export CLIENT_ID=<application-client-id>
export CLIENT_SECRET=<client-secret-value>
export TENANT_ID=<directory-tenant-id>
npx tsx bot.cts
```
```powershell
$env:OPENAI_API_KEY="sk-..."
$env:CLIENT_ID="<application-client-id>"
$env:CLIENT_SECRET="<client-secret-value>"
$env:TENANT_ID="<directory-tenant-id>"
npx tsx bot.cts
```
Create appPackage/manifest.json:
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.19/MicrosoftTeams.schema.json",
"manifestVersion": "1.19",
"version": "1.0.0",
"id": "<new-teams-app-uuid>",
"developer": {
"name": "Your company",
"websiteUrl": "https://example.com",
"privacyUrl": "https://example.com/privacy",
"termsOfUseUrl": "https://example.com/terms"
},
"name": {
"short": "CopilotKit Bot",
"full": "CopilotKit Teams Bot"
},
"description": {
"short": "A CopilotKit assistant for Microsoft Teams.",
"full": "A Microsoft Teams bot powered by CopilotKit."
},
"icons": {
"outline": "outline.png",
"color": "color.png"
},
"accentColor": "#5B5FC7",
"bots": [
{
"botId": "<application-client-id>",
"scopes": ["personal"],
"supportsFiles": false,
"isNotificationOnly": false
}
],
"validDomains": ["<your-tunnel-host>"]
}
Replace:
<new-teams-app-uuid> with a new UUID for this Teams app package<application-client-id> with the Entra application client ID<your-tunnel-host> with the tunnel host only, without https://Add the required Teams icons:
appPackage/color.png: 192 x 192appPackage/outline.png: 32 x 32, transparent backgroundappPackage/
manifest.json
color.png
outline.png
Package the manifest:
cd appPackage
zip -r appPackage.local.zip manifest.json color.png outline.png
In Microsoft Teams:
appPackage/appPackage.local.zip.Hello.You should receive the same response you saw in DevTools.
</Step> </Steps>