thermo-control-mcp
This server lets you monitor and control your Mac's thermal management (fan speeds and temperatures) through an MCP-compatible AI client.
Monitor thermal status: Read real-time per-sensor temperatures (CPU, GPU, memory), fan RPM (actual/target/min/max), fan mode, power draw in watts, current fan control state (manual vs. auto), and remaining TTL on any active manual setting. Thermal status can be read even without the root daemon installed.
Diagnose heat sources: Identify top CPU-consuming processes to pinpoint sources of heat.
Set fan speed manually: Put fans into manual mode using either absolute RPM or a percentage (0–100%) of each fan's min-to-max hardware range. Target all fans or a specific fan by index, with a configurable TTL (10s–7200s) after which fans automatically revert to system control.
Boost fans to maximum: Instantly ramp all fans to 100% for a configurable duration (default 600s, max 7200s) — useful for pre-cooling before intensive workloads.
Return fans to system control: Immediately release manual fan control and hand thermal management back to macOS.
Built-in safety guarantees (enforced by the root daemon):
All manual settings expire via a dead-man TTL switch (max 2 hours).
A hardcoded 102°C thermal failsafe automatically reverts fans to system control if any sensor overheats.
Fan RPM is always clamped to the hardware's safe min/max range.
Fans revert to system control if the daemon stops or the machine reboots.
Designed for Apple Silicon Macs (M1–M5), with dynamic sensor discovery that adapts to different Mac models.
Provides tools for monitoring Mac thermal status and controlling fan speed, including setting manual fan speeds with safety limits and automatic reversion.
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., "@thermo-control-mcpI'm about to run a 20-minute build. Keep the machine cool."
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.
thermo-control-mcp
English | 한국어
An MCP server that lets Claude Code (or any MCP client) monitor your Mac's thermals and control fan speed — with a root daemon that enforces safety limits no matter what the LLM asks for.

A true story: the battery menu blamed "Terminal", Claude found a runaway language server, boosted the fans, and handed control back. (Rendered with Remotion — source in demo/.)
You: "I'm about to run a 20-minute Rust build. Keep the machine cool."
Claude: [get_thermal_status] → CPU 58°C, fans on system control
[set_fan_speed percent=80 ttl_seconds=1500] → fans spin up before the heat arrives
... build runs without thermal throttling ...
[set_fan_auto] → back to macOS controlWorks on Apple Silicon (M1–M5, including the M3/M4 firmware lock — see How it works). Reading thermals requires no privileges; fan control uses a small root LaunchDaemon.
Why
macOS ramps fans reactively — by the time they spin up, the CPU has already been throttling. An LLM agent knows in advance when it's about to start a heavy build, test suite, or local inference job. This server lets it pre-cool the machine, hold higher fan speeds through sustained load, and hand control back when done.
Related MCP server: Mac MCP
Architecture
Claude Code ── stdio ──> MCP server (Node, unprivileged)
│
├─ reads (temps/RPM/power) ─── works even without the daemon
│
└─ unix socket /var/run/thermod.sock (root:admin 0660)
│
thermod daemon (root, launchd)
├─ SMC access via IOKit (AppleSMC)
├─ M3/M4 Ftst unlock sequence
└─ SAFETY POLICY (hardcoded):
· TTL dead-man switch
· 102°C thermal failsafe
· RPM clamped to hardware range
· revert-to-auto on daemon exitThe safety policy lives in the root daemon, not in the MCP layer the LLM talks to. The LLM can ask; the daemon decides.
Safeguard | Behavior |
Dead-man switch | Every manual setting carries a TTL (default 15 min, max 2 h). When it expires — or the daemon stops, or the machine reboots — fans revert to macOS control. Agents must re-request to keep control. |
Thermal failsafe | If any die sensor reaches 102°C while under manual control, the daemon abandons manual mode and returns fans to the system immediately. Not configurable over the socket. |
RPM clamping | Requested speeds are clamped to the fan's hardware-reported |
Sleep/wake handling | Firmware drops manual control across sleep; the daemon re-asserts it only if a valid, unexpired request is still active. |
Local-admin only | The control socket is |
Requirements
Apple Silicon Mac with fans (MacBook Air has none). Intel Macs may work (the legacy
fpe2format is implemented) but are untested.macOS 13+
Xcode Command Line Tools (
xcode-select --install) — for building the Swift daemonNode.js 18+
Install
There are two components: the MCP server (npm) and the thermod daemon (built from this repo — it's a root LaunchDaemon, so it intentionally never ships as a prebuilt binary; you build what you run).
git clone https://github.com/june4432/thermo-control-mcp.git
cd thermo-control-mcp
# 1. Build + register the MCP server
npm install && npm run build
# 2. Build + install the root daemon (asks for your password)
sudo ./scripts/install.sh
# 3. Register with Claude Code
claude mcp add thermo-control -- node "$(pwd)/dist/index.js"If you prefer the MCP server from npm (still needs step 2 above for fan control):
npm install -g thermo-control-mcp
claude mcp add thermo-control -- thermo-control-mcpWithout step 2, get_thermal_status still works (SMC reads are unprivileged); the control tools return an explanatory error.
Uninstall with sudo ./scripts/uninstall.sh — fans revert to system control.
MCP tools
Tool | What it does |
| Per-sensor die temperatures (CPU/GPU/memory), fan RPM/mode/range, power draw (W), current control state and remaining TTL. |
| Diagnose why the machine is hot: temperature summary plus the top CPU-consuming processes (with cumulative CPU time vs uptime, so runaways stand out). Breaks down what macOS's battery menu lumps together as "Terminal". |
| Manual mode at |
| All fans to 100% for |
| Release manual control back to macOS immediately. |
You can also poke the daemon directly:
echo '{"cmd":"status"}' | nc -U /var/run/thermod.sock | python3 -m json.tool
echo '{"cmd":"set","percent":70,"ttl_seconds":300}' | nc -U /var/run/thermod.sock
echo '{"cmd":"auto"}' | nc -U /var/run/thermod.sockAnd read thermals with no daemon at all: daemon/.build/release/thermod status.
How it works
Fan state lives in SMC keys (FNum, F0Ac actual RPM, F0Tg target, F0Mn/F0Mx range, F0Md mode, all floats little-endian on Apple Silicon), accessed from userspace through the AppleSMC IOKit service. Reads are allowed for any process; writes require root — enforced per-key by the SMC firmware itself.
On M3/M4 machines there is an extra gate: thermalmonitord holds the fans in "system mode" (mode 3) and the firmware rejects manual-mode writes with error 0x82. The daemon uses the community-documented unlock: try the direct write first (sufficient on M1/M2/M5 and Intel); on rejection, write the Ftst (force-test) diagnostic flag to 1, which suppresses thermalmonitord's reclaim logic, then retry the mode write until it lands (typically 3–6 s). Ftst must stay set while manual control is held — one of the reasons this is a persistent daemon rather than a one-shot CLI. The firmware clears Ftst across sleep/wake; the daemon detects and re-asserts.
Mode-key casing changed on M5 (F0md), and Ftst no longer exists there — both are probed at runtime. Pre-T2 Intel Macs have no mode key at all; there the legacy FS! force-bitmask is used instead.
Temperature sensors are discovered dynamically: the daemon enumerates the SMC's full key list (#KEY + read-by-index) and keeps every T… key that decodes as a plausible temperature — 291 sensors on an M4 Pro versus ~20 in a hand-curated list. This covers every chip variant (base/Pro/Max/Ultra) and future generations without per-model tables; curated catalogs only contribute friendly names where known. Values decode by declared SMC type (flt, sp78, fpe2, ui8/16/32), which also makes Intel's signed 7.8 fixed-point temperatures read correctly.
Credit where due: the unlock mechanism and much of the protocol behavior were documented by agoodkind/macos-smc-fan (via decompilation of thermalmonitord and AppleSMC.kext), with additional reference from raminsharifi/MacFanControl, the VirtualSMC SDK, and the Asahi Linux SMC docs. This project implements the protocol independently in Swift (MIT-licensed references only).
Compatibility
Hardware | Status |
M4 Pro ( | Verified end-to-end — 291 sensors discovered, manual fan control (2.3k→6.2k RPM), TTL dead-man revert observed live, via both the socket and MCP tools |
M1 / M2 / M3 / M5 | Implemented per documented behavior (direct mode write on M1/M2/M5, |
Intel (T2) | Direct mode write + |
Intel (pre-T2) | Legacy |
MacBook Air | No fans — status works, control does not apply |
Caveats
Don't run this alongside Macs Fan Control, TG Pro, or similar — two controllers will fight over the same SMC keys.
The
Ftstunlock is an undocumented Apple mechanism. A macOS update could change or remove it. Re-verify after major OS updates.Setting fans low under load is protected by the failsafe, but the failsafe is a backstop, not a thermostat. The intended use is raising fan speed, not silencing a hot machine.
macOS firmware retains its own independent thermal protection (throttling, emergency shutdown) regardless of this tool.
Disclaimer
This software manipulates system thermal management using undocumented interfaces. It ships with safety mechanisms, but you use it at your own risk. The authors are not responsible for hardware damage.
License
Available Tools
4 toolsboost_fansBoost fans to maximumA
Convenience wrapper: run all fans at 100% for a limited time (default 600 s). Good for pre-cooling right before a compile, video export, or LLM inference burst. Reverts automatically.
| Name | Required | Description | Default |
|---|---|---|---|
| ttl_seconds | No | Seconds until automatic revert (default 600) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses that it runs fans at 100%, is temporary with a default duration, and reverts automatically. No annotations provided, so description carries burden. Could mention what 'reverts' means (e.g., to previous state?), but it's clear enough.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no fluff. First sentence defines action and default, second gives use cases and behavior. Every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers purpose, usage, duration, revert behavior. With one optional parameter and no output schema, the description is sufficient for an agent to select and invoke correctly. Minor gap: revert behavior could be more precise.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema covers parameter fully with description. Description adds context about limited time and default, but does not add new semantic meaning beyond schema. Baseline score applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states it runs all fans at 100% for a limited time, a specific verb-resource-action with distinct behavior. Differentiated from siblings which query status or set auto/specific speeds.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly mentions ideal use cases (pre-cooling before compile, video export, LLM inference burst). Does not explicitly state when not to use, but context of siblings implies alternatives for granular control.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_thermal_statusGet thermal statusA
Read the Mac's current thermal state: per-sensor die temperatures (CPU/GPU/memory), fan RPM (actual/target/min/max) and mode, power draw in watts, and the fan-control state (manual targets, remaining TTL, last automatic revert). Use this before and after changing fan speeds, or to decide whether pre-cooling is worthwhile.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Description fully discloses the tool is a read operation and details the output data. No annotations provided, so description carries full burden; it is transparent about behavior and does not contradict any implicit assumptions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two well-structured sentences, front-loaded with purpose and output details. Every sentence adds value; no fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema, but description comprehensively lists return fields. Parameter count is zero, so complexity is low. Description covers all necessary context for a read-only monitoring tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
No parameters in the input schema, so baseline score of 4 applies. Description doesn't need to add parameter information; it is appropriately concise on this dimension.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Title and description clearly state it reads the thermal state, listing specific data points (sensor temps, fan RPM, power draw, fan-control state). Distinguishes from sibling tools like boost_fans and set_fan_auto by being a read-only status tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use the tool: 'before and after changing fan speeds, or to decide whether pre-cooling is worthwhile.' Provides clear context but lacks explicit when-not-to-use scenarios.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
set_fan_autoReturn fans to system controlA
Release manual fan control immediately and hand thermal management back to macOS (thermalmonitord). Use when the heavy workload is done.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries full burden. It discloses immediate release and handoff to macOS (thermalmonitord), which implies safe behavior, but lacks details on potential side effects or prerequisites.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with key action and purpose, every sentence adds value with no waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter tool with no output schema, the description fully covers what the tool does and when to use it, making it complete for an AI agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters (100% schema coverage), and the description adds no parameter info, which is appropriate. Baseline for 0 parameters is 4.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb 'release' and resource 'manual fan control', and clearly distinguishes from siblings like set_fan_speed and boost_fans by indicating it returns control to macOS.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description states 'Use when the heavy workload is done,' providing implicit context for when to use, but does not explicitly list alternatives or when not to use, though siblings make this inferable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
set_fan_speedSet fan speedA
Put the Mac's fans into manual mode at a given speed. Provide either 'rpm' (absolute) or 'percent' (0-100, mapped onto each fan's min-max range). Values are clamped to the hardware's reported safe range. The setting expires after ttl_seconds (default 900, max 7200) and fans return to system control — call again to renew. A root-owned failsafe overrides manual control if any die sensor reaches 102°C. Typical use: raise fans BEFORE starting a heavy build/inference job so the machine stays out of thermal throttling.
| Name | Required | Description | Default |
|---|---|---|---|
| fan | No | Fan index to control; omit to apply to all fans | |
| rpm | No | Absolute target RPM (mutually exclusive with percent) | |
| percent | No | Speed as % of each fan's min→max range | |
| ttl_seconds | No | Seconds until automatic revert to system control (default 900) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully discloses behavioral traits: manual mode, clamping to safe range, TTL expiration, and a failsafe at 102°C. This covers safety and auto-revert behaviors comprehensively.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and well-structured. The first sentence states the core purpose, followed by parameter explanations, expiration behavior, failsafe, and a typical use case. No redundant sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 4 parameters, no output schema, and no annotations, the description covers all essential aspects: manual mode setup, parameter meaning, clamping, TTL, failsafe, and typical usage. It is complete enough for an AI agent to select and invoke correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but the description adds meaningful context: the relationship between rpm and percent (percent mapped to min-max range), the mutual exclusivity, and that values are clamped to safe range. It also clarifies that omitting fan applies to all fans.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'Put the Mac's fans into manual mode at a given speed.' It specifies the resource ('Mac's fans') and the action ('manual mode'), and distinguishes from siblings like boost_fans, get_thermal_status, and set_fan_auto.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides a typical use case ('raise fans BEFORE starting a heavy build/inference job') and explains the automatic revert after ttl_seconds. However, it does not explicitly state when not to use the tool or compare it to alternatives like boost_fans or set_fan_auto.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.2.0- First observed
boost_fans - First observed
get_thermal_status - First observed
set_fan_auto - First observed
set_fan_speed
TDQS
Scored across 4 tools
Each tool has a clearly distinct purpose: boosting fans, reading status, setting auto mode, and setting manual speed. No overlap.
All tool names follow the consistent snake_case pattern with a verb_noun structure: boost_fans, get_thermal_status, set_fan_auto, set_fan_speed.
4 tools are well-scoped for a thermal control server, covering status, manual control, auto release, and a convenience function.
The tool set covers the essential operations for fan control: reading status, setting manual speed, reverting to auto, and a burst mode.
Maintenance
Related MCP Connectors
Let ChatGPT, Claude & Cursor use your Mac: email, calendar, iMessage, Teams, files. Local, free.
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Connects ChatGPT to your Apple Calendar via a local Mac agent + Vercel relay
MCP connector for iMessage & Contacts via a local Mac agent + Vercel relay
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceA local daemon for macOS that gives any MCP-capable AI client eyes, hands, and a face — screen capture, accessibility tree, mouse/keyboard actions, and an overlay — with a built-in security ceiling.2AGPL 3.0
- AlicenseNot gradedqualityAmaintenanceA local macOS MCP server for AI Agents that exposes safe endpoints for shell commands, files, processes, macOS automation, browser control, and more.44MIT
- FlicenseNot gradedqualityBmaintenanceEnterprise-grade macOS MCP server that gives AI agents eyes, hands, and a terminal, enabling screen capture, mouse/keyboard control, shell commands, and file operations.-
- AlicenseBqualityAmaintenanceEnables AI agents to directly access native macOS services, media, system health, and administration tools through a local MCP server.4013 npmMIT