sommus-laptop
Provides email tools over IMAP/SMTP: search, read, send, reply, draft, and mark read on a Gmail account using an app password.
Sends iMessages from the Mac, always confirming before the message is sent.
Controls the Mac: sound, display brightness, apps, browser tabs, clipboard, reminders, notifications, Wi-Fi, sleep, screenshots, keyboard input, and more.
Manages an Obsidian vault: search, read, list, append notes, add to-dos, and commit the notes repo.
Controls Spotify playback — volume, mute, play/pause/skip, and now-playing info — through macOS media control.
Allows messaging the assistant through Telegram, with a bot token, allowed chat IDs, and commands like /new and /cost.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@sommus-laptopstudy mode: close Messages, open Obsidian, volume 10"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Sommus
A personal assistant that controls my devices. Named after Somnus, the Roman god of sleep.
Type, text it on Telegram, or just talk to it: Sommus controls my MacBook, answers questions, and reaches my mail, notes and calendar. Next up: an always-on brain in the cloud, then speakers and custom hardware.
What a session looks like (illustrative):
you › study mode: close messages, open obsidian, volume 10
→ quit_app(name="Messages")
✓ Asked Messages to quit.
→ open_app(name="Obsidian")
✓ Opened Obsidian.
→ set_volume(level=10)
✓ Volume 10%.
sommus › Study mode is on: Messages closed, Obsidian open, volume at 10.
4 steps · 9,812 in (7,904 cached) · 212 out · $0.0141Measured, not guessed
Everyday commands (volume, apps, music, weather, "where's my next class") | $0, ~0.1 s, no model call |
Commands that need the model | ~0.5–1¢, 3–5 s (Sonnet 5, low effort) |
Prompt tokens served from cache | 89% |
Class and room questions | 20–27 s → under 1 ms (schedule engine instead of two calendar lookups) |
Tool-call accuracy | 45/46 on the eval suite (Sonnet 5); Haiku 4.5 41/46 at 0.26¢ |
Full numbers, regenerated from the log with sommus stats: docs/MEASUREMENTS.md.
Why it's built this way: docs/decisions — MCP nodes, permission tiers and the PIN,
the model sweep, no local LLM, one model with a budget guard instead of per-request routing, and the
fast path.
Related MCP server: Apple MCP Server
What it can do
60+ tools across five nodes, plus live web search:
Area | Tools |
Sound | volume, mute, play, pause, skip, what's playing |
Display | brightness, screen off, lock |
Apps | list open apps, open, quit, focus, any keyboard shortcut |
Web & files | open a URL, Spotlight search, read a file or folder, append a line, open a file |
Clipboard | read, write |
Reminders | create (syncs to iPhone), list |
Notes vault | search, read, append, add to the to-do list, commit |
search, read, send, reply, draft over IMAP/SMTP | |
System | battery, Wi-Fi status and toggle, sleep, notifications |
Browser | list Chrome tabs, read a tab's text, list its links, click a link or button, switch tabs |
Seeing & typing | screenshot (an image the model looks at), click at its coordinates, type any text, any keystroke, wait |
Documents | read a PDF as text — scanned pages go through macOS Vision OCR |
Contacts | look up anyone's number or email from the Contacts app |
Downloads | fetch a file straight to disk, or save a logged-in page with Cmd+S |
Messaging | send an iMessage, send or draft a Gmail |
Escape hatch | run any of the user's macOS Shortcuts — Focus modes, Home devices, anything macOS won't script |
Knowledge | web search for weather, news, prices, anything after the model's cutoff |
It answers questions as readily as it acts, and when there's no exact tool it tries the nearest route (a Shortcut, a keystroke, opening the right settings pane) before saying it can't.
Without a model
Fast path (
brain/fastpath.py): an intent claims a request only when every word is in its vocabulary — "brightness down 12", "screen's too dim", "quit Messages", "what's playing", weather and time anywhere, holidays, the to-do list. "A bit more" continues whichever level was just changed. A miss just goes to the model; a wrong match would do the wrong thing, so it leans towards missing.Campus engine (
brain/campus.py): next class, room, what's left today and what's due this week, from a schedule file.sommus todayprints the day and writesdata/today.jsonfor other tools.Long-term memory (
brain/memory.py): "remember my gym days are Monday, Wednesday and Friday" is saved to a private note and loaded into the cached prompt. Anything that looks like a password or code is refused.Budget guard (
brain/budget.py): a heads-up at 80% of the monthly cap, a cheaper model past 90%.
Safety, in code
PIN gate (
brain/pin.py): email, messages, files, the shell and Claude Code wait for a PIN said or typed; it never reaches the model or the log, and a PIN typed in Telegram is deleted from the chat.Outside content can't act on its own (
brain/loop.py): once a request has read mail, a web page, a PDF or the screen, sending, replying, the shell and Claude Code run only if the request itself asked for that — so text planted for an AI can't make it send anything (decision 7).
Nodes
Node | Tools | Setup |
laptop | 46 — sound, display, apps, browser, screen, shell, PDFs, contacts, files, clipboard, reminders, shortcuts | macOS permissions (below) |
vault | 9 — search, read by section, list, append, add a to-do, commit, remember, forget | none |
gmail | 6 — search, read, send, reply, draft, mark read | app password in |
web | 1 — search through a cheap worker model, so results never bloat the main context | API key |
claude | 1 — hands a request to Claude Code for Google Calendar, Drive and Notion | Claude Code installed |
A node that isn't set up reports as unreachable; everything else keeps working.
Sommus on your phone (Telegram)
The brain doesn't change — Telegram is a second interface over the same event stream.
Message @BotFather on Telegram, send
/newbot, pick a name, copy the token.Put it in
.envasTELEGRAM_BOT_TOKEN=...and leaveTELEGRAM_ALLOWED_IDSempty for now.Start Sommus, message your bot once, and it prints your chat id:
sommusPut that id in
TELEGRAM_ALLOWED_IDSand restart. Anyone not on that list is ignored and logged — without it, whoever finds the bot could drive the laptop.
Plain sommus answers the terminal and Telegram together, sharing one conversation — a lock keeps a
phone command and a typed one from interleaving. /new starts fresh, /cost reports the day's spend.
Keeping it running
The terminal session stops when its window closes. For Telegram only, detached:
sommus start # background, survives closing the window
sommus status # is it alive, plus the last log lines
sommus stopNot a LaunchAgent on purpose: macOS ties Accessibility and Automation permissions to the responsible
app, which for a launchd job is the bare Python binary — every grant would have to be redone, and
prompts would appear with nobody there to click them. Started from Terminal, the process inherits
Terminal's grants for its whole life. After a reboot, run sommus start once.
The laptop still has to be awake: closing the lid pauses everything until it's opened.
Permissions, once
sommus permissionsTriggers every macOS prompt in one go — Accessibility, Screen Recording, Contacts, Reminders, Chrome, Chrome's JavaScript setting, Messages — while you're at the keyboard to approve them. Worth running before relying on the background service, since a prompt that appears while you're away silently blocks whatever it was doing.
Talking to Sommus (voice)
sommus voice # always listening: say "Hey Sommus", then just talk. Return wakes it; typing works.
sommus voices # hear the voices and pick oneEverything audio stays on the Mac, and none of it costs anything:
Step | How |
Waking | Silero VAD cuts the mic into utterances (~1% of a CPU core); Whisper reads each one, and only one that starts or ends with "Sommus", "Hey Sommus", "What's up Sommus" or "Yo Sommus" wakes it. Nothing is kept or sent before that |
End of turn | Smart Turn v3 (Pipecat, open source, ~55 ms on the CPU) judges from the tone of voice whether a sentence is finished: yes at a 0.4 s pause, and a pause to think gets up to 1.6 s |
Conversation | Awake, everything said is a request — no wake phrase — until 30 s pass after a reply (90 s after a question), or "that's all". A small model checks that speech without the name was meant for Sommus, not someone else in the room |
Interrupting | macOS echo cancellation keeps the mic open while Sommus talks; its name cuts it off ("Sommus, stop", or "Sommus, what about tomorrow?"), anything else — its own echo, the room — is ignored |
Understanding | Whisper small.en on Apple silicon ( |
Speaking | Kokoro-82M on Apple silicon ( |
Live mode (Gemini Live)
sommus live # say "Hey Sommus", then talk like on a call: interrupt it, change your mind mid-sentence
sommus voices --gemini # hear Gemini's voices; pick one in [live] voice, steer the accent with [live] styleGemini Live hears and speaks in one model, so replies start in under a second (0.75 s measured) and it stops the
moment you talk over it. It's free on an AI Studio key. Sommus still hears the wake word locally, runs every
tool under its own rules (quick ones directly, the rest through the Claude brain as ask_sommus), and takes
the PIN locally — the microphone stops going to Google until the digits are heard
(decision 10).
Every utterance is logged to data/voice.log (loudness, detector score, transcript, and what was done with it),
so when it misses something the log says which layer dropped it. Measurements behind these choices:
decision 9.
Only the transcribed text reaches the model, so voice works unchanged wherever the brain runs.
Replies play through the Mac's current output — AirPods included, even if they connect after Sommus starts.
Set the voice in config.toml ([voice] kokoro_voice, speed); engine = "say" switches to the built-in
macOS voice, which is also the automatic fallback if Kokoro can't load.
Connecting Gmail
Sommus talks to Gmail over IMAP and SMTP with an app password, not the Gmail API. Gmail's read scopes are "restricted", so a personal OAuth app can't leave Google's Testing mode without a security assessment — and tokens in Testing expire every 7 days. An app password never expires and needs no cloud project.
Turn on 2-Step Verification on the Google account (required for app passwords).
Go to myaccount.google.com/apppasswords, name it "Sommus", and copy the 16-character password.
Put both in
.env(gitignored):
GMAIL_ADDRESS=you@gmail.com
GMAIL_APP_PASSWORD=abcd efgh ijkl mnopsommus check verifies the login. Without it, email still works through the browser
(compose_email) and the Gmail tools just explain the setup.
Architecture
INTERFACES BRAIN NODES
┌──────────────┐ ┌──────────────────────┐ MCP ┌──────────────────┐
│ terminal │─ text ─► │ agent loop │ ───────► │ laptop (macOS) │
│ telegram │ ◄ events │ LLM API │ ───────► │ vault (notes) │
│ voice │ │ permission tiers │ ───────► │ gmail │
└──────────────┘ │ audit log + cost │ └──────────────────┘
└──────────────────────┘Interfaces only exchange text and events with the brain. Voice is a new interface, not a rewrite.
Nodes are MCP servers. Each device lists its tools; the brain routes calls. A new device is a new node.
The agent loop is hand-written on the Messages API (
src/sommus/brain/loop.py): streaming, adaptive thinking, prompt caching, refusal fallback, and a step limit.
Permission tiers
Sommus runs with full permission by default: it acts without asking. Every tool still declares a tier
through MCP annotations, and every call is logged with its tier, so the gate can be switched back on
([safety] ask_before_destructive = true) when riskier tools arrive — email, files, voice.
Tier | Full permission (default) |
| Examples |
read | runs | runs |
|
reversible | runs | runs |
|
destructive | runs | asks y/N first |
|
always_ask | asks every time | asks every time | unused today; for tools that should never run unattended |
blocked | never runs, hidden from the model | same | set per tool in |
A tool with no annotations counts as destructive.
run_shell exists after an experiment in going without it: with no shell tool, Sommus typed a command
into Terminal with type_text instead — same power, no output, no exit code, no log, and it reported
success for something that never ran. A real tool is the safer of the two. AppleScript still receives
user text through argv, never string formatting, and every call is logged.
Setup
Needs macOS, Python 3.12, and uv (brew install uv).
uv sync
cp .env.example .env # then paste your model API key into .env
uv run sommus check # verifies the key, nodes, and macOS permissions
uv run sommusTo run it from any folder, add a shell function (this is what sommus means elsewhere in this README).
--no-sync matters: without it uv re-checks dependencies over the network on every launch, and a
dropped connection stops Sommus from starting. Run uv sync yourself after changing dependencies.
echo 'sommus() { uv run --no-sync --quiet --project "'"$PWD"'" sommus "$@"; }' >> ~/.zshrcmacOS Accessibility permission (for media_control, press_keys and lock_screen): System Settings →
Privacy & Security → Accessibility → enable the terminal app you run Sommus from, then restart it.
Reminders, Messages and Spotify/Music prompt separately the first time they're used (Privacy & Security →
Automation). Screen Recording is needed for screenshot, and reading a Chrome tab's text needs
Chrome's View → Developer → Allow JavaScript from Apple Events (titles and URLs work without it).
In the chat: /tools, /cost, /new, /quit. Ctrl+C cancels a reply.
Test a tool directly, no AI or API key needed:
uv run sommus tool # list tools
uv run sommus tool set_volume level=20
uv run sommus tool notify title=Hi message="From Sommus"Use the laptop node from any MCP client
The node is a standard MCP server, so any MCP-capable app can use it without the brain. Point the client at:
command: .venv/bin/python
args: -m sommus.nodes.laptop.serverScoring it
evals/commands.toml holds the 20 commands Phase 1 has to handle. The runner replays each one in a
fresh conversation and checks which tools were called:
uv run sommus eval # read tools run for real, the rest are simulated
uv run sommus eval --only volume # just the commands mentioning "volume"
uv run sommus eval --live # really run every tool (it will sleep the laptop)Currently 46/46 on Sonnet 5 at low effort, ~$0.0095 per command. Target: never below 90%. Each run is saved to data/evals/ with the tools called, replies, latency and cost,
so model and effort changes can be compared.
Layout
src/sommus/
├── brain/ loop.py · nodes.py · permissions.py · prompt.py · store.py
├── interfaces/ cli.py
├── evals/ runner.py
└── nodes/laptop/ server.py (MCP tools) · macos.py (system) · apps.py (music, Shortcuts, Reminders) · files.py
evals/commands.toml the 20 commands Phase 1 must handle
tests/ agent loop + permission gate against a scripted fake model and a real in-process nodeDevelopment
uv run pytest
uv run ruff check src tests && uv run ruff format src testsEvery turn and tool call is logged to data/sommus.db (SQLite) with tokens and cost.
This server cannot be deployed
Maintenance
Related MCP Connectors
Use your own Mac from ChatGPT, Claude or Codex: files, commands, documents, and a browser.
Mac & Windows: let ChatGPT, Claude & Cursor use your email, calendar, iMessage, Teams, files. Free.
Search, read, and write your Apple Notes from ChatGPT/Claude via a local Mac agent + MCP relay.
Carbon Voice MCP serves as a bridge that connects AI assistants like ChatGPT, Claude, and Cursor to a user's Carbon Voice account, turning voice messages and conversations into a private, on-demand knowledge base. It provides 28 specialized tools for comprehensive voice messaging management, including creating and sending messages, accessing conversation history with instant transcription, running AI actions (summarization, TLDR generation, meeting notes), and managing workspace collaboration through folders, contacts, and team communications.
Related MCP Servers
- AlicenseBqualityBmaintenanceEnables AI assistants to automate macOS through AppleScript and JXA by providing 44 tools for application management, window control, and UI interaction. It allows for comprehensive system control including screen capture, keyboard and mouse simulation, and system information retrieval.4457 npm1MIT
- FlicenseBqualityDmaintenanceExposes macOS system controls and Apple apps as structured tools for Claude, enabling control of volume, music, files, browser tabs, and more via natural language with configurable permissions and rate limiting.311-
- FlicenseBqualityDmaintenanceEnables macOS automation through 45 AppleScript-powered tools for system control, app management, calendar, reminders, notifications, clipboard, and task scheduling.45-
- AlicenseNot gradedqualityCmaintenanceEnables Claude to automate macOS applications, manage files, control music, send messages, and more through AppleScript.27 npm1GPL 3.0