A fast, native Microsoft Teams chat client. Written in Rust, rendered on the GPU. No admin approval needed.
Features · Quick start · How it works · Roadmap
Note
Unofficial. Not affiliated with or endorsed by Microsoft. It uses the same web APIs as the Teams web app, which can change at any time.
| Rusty Teams | |
|---|---|
| 🏢 Works in company tenants | No app registration, no admin consent: it runs on the Teams web app your account is already allowed to use |
| ⚡ Starts instantly | Renders from a local SQLite cache before the network answers |
| 🔒 No tokens stored | Signs in through a real Teams web page; tokens are never written to disk |
| 📡 Live, no polling | Realtime socket pushes every change |
| 🔎 Offline search | Full-text index over your chats, Ctrl+K switcher |
Chats and channels
- Teams pin order, chat folders, hidden teams, Muted and Meeting chats sections
- Channel posts like Teams: one card per post, newest activity on top, last 3 replies, inline reply, new post with a subject, full conversation view
- Channel tabs and the Shared tab (library and files from posts), per-channel notifications
- Chat menu: pin, move to folder, mark as unread, mute, hide, leave
- Read receipts, typing indicator with faces, presence, unread jump
- Own status, status message and work location from the avatar menu; profile cards with org chart
- Activity bell: new messages, @mentions and reactions, kept for 14 days
Messages
- Send, edit, delete, react, quote-reply, @mentions
- Forward, copy link, save (Saved panel), pin to the chat banner, mark unread from here
- Inline images, file cards, link previews
- Adaptive Cards like Teams: tables, code blocks, badges, charts, date and time pickers, live refresh, task dialogs
- GIFs and stickers in messages
- Select and copy message text
Composer
- Rich text: format bar on selection,
Ctrl+B/Ctrl+I/Ctrl+U, lists, quotes, code - Markdown converts as you type or paste
- Picker with Emoji, GIF and Sticker tabs
:thumbsup:and:)convert as you type, German aliases included- Schedule send: right-click send or
Ctrl+Shift+Enter - Drafts and unsent messages survive a restart
- Keyboard like Teams:
Ctrl+1..4panels and tabs,Alt+Up/Alt+Downnext chat, call keys onCtrl+Shift+A/D/H/O/E/M/J,Ctrl+.lists them all
Calls
- Test call to the Teams Echo bot from the avatar menu: native WebRTC audio, mute (
Ctrl+Shift+M), device switching, mini window while you read other chats - Phone button in 1:1 and group chat headers: callee tiles show Calling and Ringing, Cancel while ringing, notices like "Bea declined" or "No answer"
- Video and screen sharing: H264 tiles, shared screen on a stage (double click for full window, Esc back), camera and screen share buttons with a "You are sharing" banner. Linux needs glib at run time
- Incoming calls: topmost ring toast with Decline and Accept, ring tone, missed calls in Activity
- Join a running meeting from its chat (audio): roster tiles, speaking ring, lobby state, Leave or End meeting for the organizer
- After every call a pseudonymized test report (JSON and Markdown, newest 30) is saved to the app data folder
call-reports; toast "Test report saved" with Open folder, Settings > Devices switch - Organizer controls: lobby banner with Admit and Deny, tile menu (pin, spotlight, mute, remove), Mute all, live captions overlay, camera background blur
Agents (MCP)
- Local MCP server at
http://127.0.0.1:40615/mcp, off by default: Settings > MCP - Agents read allowed chats, channels, meetings and transcripts (images, reactions, files and cards included, newest messages across chats), and propose replies with @mentions and images, or an emoji reaction
- A reply shows as a card above the composer: Send, Edit or Discard. A reaction shows as a note under the message: Accept or Discard. Agents never send
- One-click install for Claude Code, Codex CLI, Copilot CLI, VS Code and Cursor, Windows and WSL. WSL needs
networkingMode=mirrored - Delegate a message to a coding-agent CLI (Claude Code, Copilot, any CLI) from the hover toolbar: progress chip, Agents page, toast when it finishes, Open session
App
- Dark theme, opens instantly from the cache
- Self-update from an update folder
- Single instance: a second start brings the window to the front
| Platform | State |
|---|---|
| 🪟 Windows | Main target. Login through an embedded WebView2 |
| 🐧 Linux | Connects to a Chrome started with --remote-debugging-port=9222 and signed in to Teams on the web. The app opens an Outlook on the web tab next to it when missing (some tenants grant chat scopes only to Outlook). Built-in login is planned |
| 🍎 macOS | Experimental. Universal arm64 + x86_64 binary, connects to a Chrome like Linux |
The remaining gaps are planned, see ROADMAP.md.
Try it with demo data, no account needed:
cargo run -p app -- --demo| Task | Command |
|---|---|
Linux build needs clang 21+ (libwebrtc's hermetic libc++) + mold (set in .cargo/config.toml); sccache shares compiled deps across worktrees |
sudo apt install clang mold sccache, clang from apt.llvm.org if the distro one is older than 21 |
| Against Chrome on a debug port | cargo run -p app -- --endpoint http://127.0.0.1:9222 |
| Windows exe from WSL/Linux | scripts/build-windows.sh crates/app teams |
| macOS .app (universal arm64 + x86_64), Xcode CLT only | scripts/build-macos.sh crates/app teams |
| Tests | cargo test |
| Headless screenshot (Xvfb, xdotool, xclip, mesa-vulkan-drivers) | cargo build -p app && scripts/headless-shot.sh shot.png -- "mousemove 450 740" "click 1" "type hello" |
Layout sweep before every UI deploy: 8 screens at 640x480 and 1920x1080, PNGs in target/layout-sweep (look at each) |
cargo build -p app && scripts/layout-sweep.sh |
Layout guard: no overlapping or clipped elements, no negative offsets outside widgets::corner_badge |
cargo test -p app layout_guard |
Rusty Teams never sees your password and never stores a token. It signs you in through a real Teams web page and lets that page make the API calls.
+--------------------------- Rusty Teams (Rust) ---------------------------+
| app GPUI window: sidebar, chat view, composer, Ctrl+K |
| core sync engine, actions, mapping to view models |
| store SQLite cache + FTS5 search index |
| graph Microsoft Graph: chats, channels, messages, people, sending |
| chatsvc Teams chat service: realtime, pins, folders, read receipts |
| session runs requests inside the Teams page, one Transport trait |
| mcp local MCP server for agents, permissions, config install |
+------------------------------------|-------------------------------------+
| DevTools protocol (CDP)
+------------------------------------v-------------------------------------+
| Hidden browser page on teams.cloud.microsoft, own profile |
| webview: WebView2 (Windows) or browser: Chrome with a debug port |
+------------------------------------|-------------------------------------+
| HTTPS with the page's own MSAL tokens
Graph | chat service (ic3) | presence | Trouter socket
Sign-in and tokens
- A hidden browser page loads Teams web with its own profile. On Windows this is WebView2, so single sign-on with the Windows account usually just works.
- If Teams asks for a login, the login window shows itself. After that the page stays parked in the background.
- Every API call is sent to that page over CDP. A small script (
crates/session/assets/fetch.js) picks the matching token from the page's MSAL cache, refreshes it when needed, and runs thefetch. - The page only hands out access tokens. Rust sends the requests itself and keeps each token in memory until shortly before it expires, never stored or logged. Websocket exchanges and calls that need the app's cookies still run inside the page.
TEAMS_PAGE_FETCH=1sends everything through the page again. - The Trouter socket is opened from Rust too, so the page can park even during a call.
Data flow
| Step | What happens |
|---|---|
| Start | The UI renders from the SQLite cache right away. A chat still syncing shows a progress line in its header. Skeleton bubbles only when the cache has no messages yet |
| Sync | core fetches chats and channels from Graph with delta queries, pins and folders from the chat service |
| Live | chatsvc keeps the Trouter WebSocket open. An event only says "something changed", the content is then fetched fresh |
| Write | Sends, edits and reactions go out through Graph or the chat service, then the cache updates |
| Search | Local FTS5 index over cached messages, no server round trip |
Crates
| Crate | Job |
|---|---|
app |
GPUI desktop app, binary teams |
core |
Sync engine, actions, people ranking, markdown and card rendering |
store |
SQLite cache, migrations, search, image file cache |
graph |
Typed Microsoft Graph client with batching and paging |
chatsvc |
Teams chat service: Trouter realtime, pins, folders, receipts |
calling |
Native calls: Teams signaling, SDP translation, libwebrtc audio and video |
session |
Transport trait, CDP session, in-page fetch |
webview |
Hidden WebView2 host (Windows only) |
browser |
Chrome lifecycle and watchdog for the CDP transport |
cli |
teams-probe, a read-only end-to-end check |
Open and finished features by priority: ROADMAP.md