How to Connect Hermes to Microsoft Teams?

A recipe-style guide to wiring NousResearch’s Hermes agent into Microsoft Teams — mentions, file cards, inline approvals, and meeting summaries, all from your own self-hosted gateway.

Setup time ~1 hour
Difficulty Moderate (Azure admin needed)
One-time cost Free (Azure Bot is free tier)
Going cost Your existing server + model bill

Ingredients

Why Teams is different from Slack or Telegram

On Slack, Hermes uses socket mode and holds a connection open. Teams does not work that way. Microsoft’s Bot Framework calls you: it makes an HTTPS POST to a public webhook whenever someone messages the bot, and Hermes answers back through the Bot Framework API. So the one hard requirement is a publicly reachable HTTPS endpoint — a dev tunnel for local work, a real domain with a valid certificate in production.

Hermes’s Teams adapter is not a thin bridge. It speaks the full Teams vocabulary: @-mentions, Adaptive Cards, FileConsent cards and file.download.info handling, polls, direct sends, DM file uploads, SharePoint channel uploads, inbound images and documents (PDF, DOCX, XLSX, PPTX, ZIP, TXT, MD), and message edits for streaming-style replies. When the agent wants to run something risky, it posts an Adaptive Card with approve/deny buttons instead of making you type /approve — one click resolves it inline.

1 Install the Teams extra and CLI

Teams support is an optional extra on Hermes. On a source install, sync it in; then install Microsoft’s Teams CLI, which you will use to create the bot and the app package:

# Hermes Teams extra (source installs)
uv sync --extra teams

# Microsoft Teams CLI
npm install -g @microsoft/teams.cli@preview
teams login

2 Expose a public HTTPS webhook

The gateway listens on port 3978 by default (override with TEAMS_PORT). Teams needs to reach it over HTTPS. For local development, open a tunnel to that port — pick whichever tool you have:

# devtunnel
devtunnel create hermes-bot --allow-anonymous
devtunnel port create hermes-bot -p 3978 --protocol https

# ── or ngrok ──
ngrok http 3978

# ── or cloudflared ──
cloudflared tunnel --url http://localhost:3978

Note the public URL it gives you. The endpoint Microsoft needs is that URL with /api/messages on the end.

3 Create the bot application

One command registers the Azure Bot, the app, and a secret, pointing it at your webhook:

teams app create --name "Hermes" \
    --endpoint "https://<your-tunnel-url>/api/messages"

It outputs CLIENT_ID, CLIENT_SECRET, and TENANT_ID. Save the secret immediately — it is shown only once.

4 Configure the environment

Add the credentials — and, critically, the allowlist — to ~/.hermes/.env:

TEAMS_CLIENT_ID=<your-client-id>
TEAMS_CLIENT_SECRET=<your-client-secret>
TEAMS_TENANT_ID=<your-tenant-id>
TEAMS_ALLOWED_USERS=<aad-object-id-1>,<aad-object-id-2>

# optional: change the listen port (default 3978)
# TEAMS_PORT=3978
Do not skip TEAMS_ALLOWED_USERS. Without it, anyone who can find or install your bot can drive your agent — an agent that runs shell commands on your server. Fill it with the AAD object IDs of authorized users. You can read your own with teams status --verbose.

5 Start the gateway

Bring up the gateway service (the Hermes compose stack passes your UID/GID through so files it writes are owned by you):

HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d gateway

Confirm it is healthy:

curl http://localhost:3978/health
# → ok

6 Install the bot into Teams

The Teams CLI builds the app package for you; get an install link and open it in the Teams client:

teams app get <teamsAppId> --install-link

Open the link, add Hermes to a team or start a DM with it, then send a test message. With TEAMS_ALLOWED_USERS set, only listed users get a reply — everyone else is ignored.

7 Optional: meeting summaries

If you enable the Teams meeting pipeline plugin, the same adapter handles outbound delivery of meeting summaries. After a meeting’s transcript is summarized, Hermes posts the summary into a Teams target you choose — a channel or a chat. It is a separate plugin from the chat channel, but it rides on the connection you just set up, so there is nothing new to authenticate.

Moving from tunnel to production

When you swap the throwaway tunnel for a real domain, repoint the bot’s endpoint so Azure delivers there:

teams app update --id <teamsAppId> \
    --endpoint "https://your-domain.com/api/messages"

Make sure the port behind your reverse proxy is internet-accessible and served with a valid TLS certificate — the Bot Framework refuses self-signed certs. Keep TEAMS_ALLOWED_USERS current as the team changes.

What you end up with

Hermes living inside Microsoft Teams: it answers DMs and @-mentions, accepts and returns files, streams replies by editing messages, and asks for dangerous-command approval with a card you tap instead of a slash command — all restricted to the AAD users you allowlisted. Turn on the meeting pipeline and it will drop meeting summaries into the channel of your choice as a bonus.

If messages do not arrive

Further reading