Skip to content

About

A fast, native Microsoft Teams chat client.

Topics

Resources

Stars

27 stars

Watchers

0 watching

Forks

Repository files navigation

Rusty Teams logo

Rusty Teams

A fast, native Microsoft Teams chat client. Written in Rust, rendered on the GPU. No admin approval needed.

Rust GPUI Platform License: MIT

Features · Quick start · How it works · Roadmap

Rusty Teams with demo data

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.

Why

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

Features

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..4 panels and tabs, Alt+Up / Alt+Down next chat, call keys on Ctrl+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

Status

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.

Quick start

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

How it works

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
  1. 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.
  2. If Teams asks for a login, the login window shows itself. After that the page stays parked in the background.
  3. 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 the fetch.
  4. 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=1 sends everything through the page again.
  5. 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

Roadmap

Open and finished features by priority: ROADMAP.md

License

MIT

About

A fast, native Microsoft Teams chat client.

Topics

Resources

Stars

27 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages