Microsoft Teams
Teams needs a Teams app, and the app needs a URL to deliver to. Slack holds a socket open, but Teams POSTs every message to an HTTPS endpoint Bishop serves. Nothing binds a port unless the Teams credentials are set.
Setup
Install the Teams Developer CLI with these flags, then log in:
npm install -g --allow-scripts=@azure/msal-node-extensions,keytar @microsoft/teams.cli
teams loginThe CLI keeps its login in a native module that npm refuses to build by default, and falls back to holding the token in memory rather than reporting it. Installed without the flags it logs in, says it worked, and starts every later command logged out. Bishop says so when it sees that happen.
Run this on a workstation. Over SSH the CLI has no browser to open and falls back to the device code flow, which Microsoft blocks by default in every tenant, so no login can succeed there. Set up on a laptop and copy the BISHOP_TEAMS_* lines to the server, or use the administrator path, which needs no CLI at all.
Then create the app:
bishop teams setup --endpoint https://your-host.example/api/messagesIt creates the app, writes the three BISHOP_TEAMS_* credentials to .env, records the app in .bishop/config.json, and prints a link to install the app in Teams. You can leave --endpoint off and set it later.
Setup also asks the app for these, each as a further write, since teams app create takes no flags for them:
ChannelMessage.Read.GroupandChatMessage.Read.Chat, which let the agent hear a reply that doesn't @-mention it.ChannelSettings.Read.GroupandTeamSettings.Read.Group, which tell the agent who can read a channel.supportsFiles, which lets anyone attach a file in a chat with it.
A Developer Portal that refuses one costs that setting rather than the setup: it says what is missing and what to run.
One grant isn't in the Teams manifest, so setup makes it with the Azure CLI instead. Files.ReadWrite.All on the app registration lets the agent read a file attached in a channel and send one there. Consenting it takes an administrator, so setup tries and prints the commands when it can't. Install az and run az login --allow-no-subscriptions before setup if you can make the grant yourself. Nothing else needs az, and an agent that only works in direct chats never needs the grant.
Run setup again and it reports what's configured, including where the app currently points and whatever is still missing, and changes nothing. If only half of setup is on disk, an app with no credentials or credentials with no app, it says which half and stops rather than create a second app.
Adding or changing permissions on an app that's already installed means removing it and installing it again. Teams asks for them at install time and at no other time, so an app updated in place keeps the consent it was installed with, whatever its manifest now says. Remove it from every team and chat it's in, then add it back. This catches out anyone upgrading an agent that was already running in Teams.
The endpoint
Bishop serves /api/messages on port 3978 by default, on loopback, so something has to carry public HTTPS to it. A tunnel does that without opening a port on the host:
cloudflared tunnel --url http://localhost:3978That gives you the URL to hand bishop teams setup --endpoint. An ephemeral tunnel hostname changes on every restart, and a Teams app pointed at the old one goes quiet with nothing in the log to say why, so use a stable hostname for anything but a trial.
To terminate TLS in Bishop itself instead, give it a certificate:
{
"teams": {
"port": 443,
"host": "0.0.0.0",
"tls": { "cert": "/etc/bishop/fullchain.pem", "key": "/etc/bishop/privkey.pem" }
}
}When the URL changes:
bishop teams endpoint https://new-host.example/api/messagesThat touches the app and nothing else. When the change rewrites the app's manifest, which moving to a different domain does, it says so: the app has to be reinstalled in Teams before it receives anything again.
When somebody else owns the app
In many tenants only an administrator can create it, and installing custom apps can be turned off tenant-wide. bishop teams instructions prints what to send them. When they send back a client ID, a client secret, and a tenant ID:
bishop teams setup --manualIt checks the three against Microsoft before saving them, using the same request Bishop makes on every turn, so a bad secret fails here instead of silently on the first message.
Installing the app
Last, install the app in Teams by opening the link setup printed. When this is blocked the link does nothing at all: no activity is delivered, so Bishop's log stays silent and a working endpoint looks broken.
Sideloading has to be allowed in two places, and either one blocks it alone. bishop teams setup names whichever is in the way. Both live in the Teams admin center at admin.teams.microsoft.com:
- "Let users interact with custom apps" under Teams apps, Manage apps, Org-wide app settings.
- "Upload custom apps" in the app setup policy assigned to the account, under Teams apps, Setup policies.
An administrator can skip the per-user policy by uploading the app package under Teams apps, Manage apps, which publishes it to the whole org and takes a few hours to appear.
Using it
Message the app directly, or @-mention it in a channel. A channel reply starts a thread and the agent follows that thread. A chat has no threads, so the whole chat is one session.
- A turn shows itself while it runs. What the agent writes lands as it writes it, and a run of tool calls shares one message that is rewritten as the work moves. A turn ends with a line saying what it cost.
! verbosityturns this down. There's no typing indicator: Teams hides it while a stream is open, and outside one it lapses in seconds. - Commands work as they do in Slack. Type the command in backticks, like
`! model opus`, and Teams formats it as code. The whole message has to be the code. See Commands. - Tables don't render. Bold, italics, links, code and lists work, and the agent is told to write a list where it would otherwise reach for a table.
- @-mention it once per thread. Teams delivers a channel or group-chat message to an app only when the app is @-mentioned. The resource-specific consent permissions setup asks for lift that, so once the agent has answered in a thread it hears replies without being tagged. Without them it still works, and has to be tagged every time.
- It can stay out of a conversation. In a thread with several people talking, a message that didn't name the agent is one it may leave alone. A message that names it is always answered, as is anything in a one-to-one chat.
- It catches up when it's pulled in partway. @-mention it halfway down a channel thread or a group chat and its first answer has read what was said before. Only messages from people on the allow list are quoted to it. This uses the same two permissions, so an app installed without them answers from the mention alone.
- Stop a turn with
! stop. Teams has no stop button for Bishop to draw.
Files
- In a direct chat, attach a file and the agent gets the path. An image pasted into a message reaches it in any scope.
- In a channel, Teams keeps the file off the message it delivers, so Bishop reads it back through Microsoft Graph. That needs
Files.ReadWrite.All. Without it the agent is told a file was attached and says it couldn't read it. An app that already holdsFiles.Read.Allcan read channel files but can't send them. - In a group chat, an attached file may not come through. That case is untested.
Files the agent sends are covered in Files.
Access
The allow list takes user principal names ([email protected]), @example.com for everyone at a domain, or an Entra object ID:
{ "teams": { "allow": ["[email protected]", "@example.com"] } }Leave it out and anyone in the tenant who can reach the app can use the agent. Somebody not on the list is told so, rather than left with silence that looks like the agent being broken.