diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json
index 404fee99df..a7d68f0523 100644
--- a/.pipelines/ESRPSigning_core.json
+++ b/.pipelines/ESRPSigning_core.json
@@ -54,7 +54,6 @@
"PowerToys.AwakeModuleInterface.dll",
"PowerToys.Awake.exe",
"PowerToys.Awake.dll",
- "PowerToys.McpServer.exe",
"PowerToys.FancyZonesEditor.exe",
"PowerToys.FancyZonesEditor.dll",
@@ -238,7 +237,9 @@
"PowerToys.DSC.dll",
"PowerToys.DSC.exe",
- "PowerToysSparse.msix"
+ "PowerToysSparse.msix",
+ "PowerToys.McpServer.dll",
+ "PowerToys.McpServer.exe"
],
"SigningInfo": {
"Operations": [
diff --git a/.vscode/mcp.json b/.vscode/mcp.json
deleted file mode 100644
index b51b87eef0..0000000000
--- a/.vscode/mcp.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "servers": {
- "powertoys-mcp": {
- "command": "C:/PowerToys/x64/Release/PowerToys.McpServer.exe",
- "args": [],
- "env": {
- "NODE_ENV": "production"
- }
- }
- }
-}
\ No newline at end of file
diff --git a/PowerToys.sln b/PowerToys.sln
index 881e4ff36c..7e4a4ce341 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -330,11 +330,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AwakeModuleInterface", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Awake", "src\modules\awake\Awake\Awake.csproj", "{D940E07F-532C-4FF3-883F-790DA014F19A}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mcp", "mcp", "{7A0ECF79-2E61-4EC8-9B79-3C438962C0F0}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "McpModuleInterface", "src\modules\mcp\McpModuleInterface\McpModuleInterface.vcxproj", "{8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.McpServer", "src\modules\mcp\McpServer\PowerToys.McpServer.csproj", "{8A6F5D3B-F59E-4F34-A1D3-3F69D3FDBD9D}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.McpServer", "src\McpServer\PowerToys.McpServer.csproj", "{8A6F5D3B-F59E-4F34-A1D3-3F69D3FDBD9D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.UnitConverter", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.csproj", "{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}"
EndProject
@@ -1452,14 +1448,6 @@ Global
{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|ARM64.Build.0 = Release|ARM64
{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|x64.ActiveCfg = Release|x64
{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}.Release|x64.Build.0 = Release|x64
- {8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A}.Debug|ARM64.ActiveCfg = Debug|ARM64
- {8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A}.Debug|ARM64.Build.0 = Debug|ARM64
- {8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A}.Debug|x64.ActiveCfg = Debug|x64
- {8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A}.Debug|x64.Build.0 = Debug|x64
- {8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A}.Release|ARM64.ActiveCfg = Release|ARM64
- {8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A}.Release|ARM64.Build.0 = Release|ARM64
- {8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A}.Release|x64.ActiveCfg = Release|x64
- {8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A}.Release|x64.Build.0 = Release|x64
{D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|ARM64.Build.0 = Debug|ARM64
{D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|x64.ActiveCfg = Debug|x64
@@ -3367,9 +3355,7 @@ Global
{F5333ED7-06D8-4AB3-953A-36D63F08CB6F} = {3DCCD936-D085-4869-A1DE-CA6A64152C94}
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
- {7A0ECF79-2E61-4EC8-9B79-3C438962C0F0} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
- {8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A} = {7A0ECF79-2E61-4EC8-9B79-3C438962C0F0}
- {8A6F5D3B-F59E-4F34-A1D3-3F69D3FDBD9D} = {7A0ECF79-2E61-4EC8-9B79-3C438962C0F0}
+
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
diff --git a/doc/devdocs/modules/readme.md b/doc/devdocs/modules/readme.md
index 79584de10b..0a2b48f098 100644
--- a/doc/devdocs/modules/readme.md
+++ b/doc/devdocs/modules/readme.md
@@ -9,7 +9,6 @@ This section contains documentation for individual PowerToys modules, including
| [Advanced Paste](advancedpaste.md) | Tool for enhanced clipboard pasting with formatting options |
| [Always on Top](alwaysontop.md) | Tool for pinning windows to stay on top of other windows |
| [Awake](awake.md) | Tool to keep your computer awake without modifying power settings |
-| [MCP Server](mcp.md) | Model Context Protocol bridge exposing PowerToys tools to AI agents |
| [Color Picker](colorpicker.md) | Tool for selecting and managing colors from the screen |
| [Command Not Found](commandnotfound.md) | Tool suggesting package installations for missing commands |
| [Crop and Lock](cropandlock.md) | Tool for cropping application windows into smaller windows or thumbnails |
diff --git a/src/modules/mcp/McpServer/PowerToys.McpServer.csproj b/src/McpServer/PowerToys.McpServer.csproj
similarity index 69%
rename from src/modules/mcp/McpServer/PowerToys.McpServer.csproj
rename to src/McpServer/PowerToys.McpServer.csproj
index 067e1b8873..ba37dadbe7 100644
--- a/src/modules/mcp/McpServer/PowerToys.McpServer.csproj
+++ b/src/McpServer/PowerToys.McpServer.csproj
@@ -1,13 +1,13 @@
-
-
+
+
Exe
PowerToys.McpServer
PowerToys.McpServer
enable
- ..\..\..\..\$(Platform)\$(Configuration)
+ ..\..\$(Platform)\$(Configuration)
false
false
@@ -26,7 +26,7 @@
-
-
+
+
diff --git a/src/modules/mcp/McpServer/PowerToys.McpServer.dev.manifest b/src/McpServer/PowerToys.McpServer.dev.manifest
similarity index 100%
rename from src/modules/mcp/McpServer/PowerToys.McpServer.dev.manifest
rename to src/McpServer/PowerToys.McpServer.dev.manifest
diff --git a/src/modules/mcp/McpServer/PowerToys.McpServer.prod.manifest b/src/McpServer/PowerToys.McpServer.prod.manifest
similarity index 100%
rename from src/modules/mcp/McpServer/PowerToys.McpServer.prod.manifest
rename to src/McpServer/PowerToys.McpServer.prod.manifest
diff --git a/src/modules/mcp/McpServer/Program.cs b/src/McpServer/Program.cs
similarity index 100%
rename from src/modules/mcp/McpServer/Program.cs
rename to src/McpServer/Program.cs
diff --git a/src/modules/mcp/McpServer/README.md b/src/McpServer/README.md
similarity index 100%
rename from src/modules/mcp/McpServer/README.md
rename to src/McpServer/README.md
diff --git a/src/McpServer/Tools/AwakeTools.cs b/src/McpServer/Tools/AwakeTools.cs
new file mode 100644
index 0000000000..0e1d32f4fa
--- /dev/null
+++ b/src/McpServer/Tools/AwakeTools.cs
@@ -0,0 +1,649 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Text.Json.Nodes;
+using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Library;
+using ModelContextProtocol.Server;
+using Lock = System.Threading.Lock;
+
+namespace PowerToys.McpServer.Tools
+{
+ ///
+ /// MCP tools for PowerToys Awake module.
+ ///
+ [McpServerToolType]
+ public static class AwakeTools
+ {
+ private static readonly SettingsUtils SettingsUtils = new SettingsUtils();
+ private const string PowerToysProcessName = "PowerToys";
+ private const string AwakeExecutableName = "PowerToys.Awake.exe";
+ private static readonly string[] AwakeRelativeSearchPaths =
+ [
+ AwakeExecutableName,
+ Path.Combine("modules", "Awake", AwakeExecutableName),
+ ];
+
+ ///
+ /// Gets the current Awake mode and configuration.
+ ///
+ /// JSON object with current Awake status.
+ [McpServerTool]
+ [Description("Get the current Awake mode and configuration from the PowerToys settings store.")]
+ public static JsonObject GetAwakeStatus()
+ {
+ try
+ {
+ (bool powerToysRunning, bool awakeModuleEnabled) = CheckPowerToysAndAwakeStatus();
+
+ if (!powerToysRunning || !awakeModuleEnabled)
+ {
+ if (IsAwakeProcessRunning())
+ {
+ string errorMessage = "Awake is already running via CLI. Use force=true to override.";
+
+ return AwakeStatusPayload.CreateError(
+ errorMessage,
+ powerToysRunning: powerToysRunning,
+ launchedViaCli: true).ToJsonObject();
+ }
+
+ return AwakeStatusPayload.CreateInactive().ToJsonObject();
+ }
+
+ // PowerToys is running and Awake module is enabled
+ bool awakeProcessRunning = IsAwakeProcessRunning();
+
+ AwakeSettings settings = SettingsUtils.GetSettingsOrDefault(AwakeSettings.ModuleName);
+ string summary = FormatAwakeDescription(settings);
+
+ if (awakeProcessRunning)
+ {
+ summary = $"{summary} An Awake process is already running with the current configuration. To override the active session and apply new settings, use force=true.";
+ }
+
+ AwakeStatusPayload payload = AwakeStatusPayload.FromSettings(settings, summary);
+ payload.PowerToysRunning = true;
+ payload.LaunchedViaCli = !awakeModuleEnabled && awakeProcessRunning;
+ payload.AwakeProcessActive = awakeProcessRunning;
+ Logger.LogInfo("[MCP] Retrieved Awake status via SDK tool.");
+ return payload.ToJsonObject();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("[MCP] Failed to read Awake status.", ex);
+ return AwakeStatusPayload.CreateError(ex.Message).ToJsonObject();
+ }
+ }
+
+ ///
+ /// Sets the Awake mode to passive (allow system sleep).
+ ///
+ /// JSON object with updated Awake status.
+ [McpServerTool]
+ [Description("Set Awake to passive mode (allow system to sleep normally).")]
+ public static JsonObject SetAwakePassive()
+ {
+ try
+ {
+ (bool powerToysRunning, bool awakeModuleEnabled) = CheckPowerToysAndAwakeStatus();
+
+ if (!powerToysRunning || !awakeModuleEnabled)
+ {
+ StopAwakeProcesses();
+ Logger.LogInfo("[MCP] Stopped all Awake processes because PowerToys is not running.");
+ return AwakeStatusPayload.CreateInactive().ToJsonObject();
+ }
+
+ AwakeSettings settings = SettingsUtils.GetSettingsOrDefault(AwakeSettings.ModuleName);
+ settings.Properties.Mode = AwakeMode.PASSIVE;
+ settings.Properties.ProcessId = 0;
+ settings.Properties.KeepDisplayOn = false;
+ settings.Properties.IntervalHours = 0;
+ settings.Properties.IntervalMinutes = 0;
+ SettingsUtils.SaveSettings(settings.ToJsonString(), AwakeSettings.ModuleName);
+
+ string confirmation = FormatAwakeDescription(settings);
+ Logger.LogInfo($"[MCP] {confirmation}");
+ AwakeStatusPayload payload = AwakeStatusPayload.FromSettings(settings, confirmation);
+ payload.PowerToysRunning = true;
+ payload.LaunchedViaCli = false;
+ payload.AwakeProcessActive = false;
+ return payload.ToJsonObject();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("[MCP] Failed to set Awake to passive.", ex);
+ return AwakeStatusPayload.CreateError(ex.Message).ToJsonObject();
+ }
+ }
+
+ ///
+ /// Sets the Awake mode to indefinite (keep system awake forever).
+ ///
+ /// Whether to keep the display on. Default is true.
+ /// JSON object with updated Awake status.
+ [McpServerTool]
+ [Description("Set Awake to indefinite mode (keep system awake until manually changed).")]
+ public static JsonObject SetAwakeIndefinite(
+ [Description("Whether to keep the display on")] bool keepDisplayOn = true,
+ [Description("Force the change even if Awake is already running")] bool force = false)
+ {
+ try
+ {
+ (bool powerToysRunning, bool awakeModuleEnabled) = CheckPowerToysAndAwakeStatus();
+
+ if (!powerToysRunning || !awakeModuleEnabled)
+ {
+ return HandleCliScenario(AwakeMode.INDEFINITE, keepDisplayOn, 0, force);
+ }
+
+ AwakeSettings settings = SettingsUtils.GetSettingsOrDefault(AwakeSettings.ModuleName);
+ if (!force && IsAwakeActive(settings))
+ {
+ return BuildActiveProcessResponse(settings, true, false);
+ }
+
+ settings.Properties.Mode = AwakeMode.INDEFINITE;
+ settings.Properties.ProcessId = 0;
+ settings.Properties.KeepDisplayOn = keepDisplayOn;
+ settings.Properties.IntervalHours = 0;
+ settings.Properties.IntervalMinutes = 0;
+ SettingsUtils.SaveSettings(settings.ToJsonString(), AwakeSettings.ModuleName);
+
+ string confirmation = FormatAwakeDescription(settings);
+ Logger.LogInfo($"[MCP] {confirmation}");
+ AwakeStatusPayload payload = AwakeStatusPayload.FromSettings(settings, confirmation);
+ payload.PowerToysRunning = true;
+ payload.LaunchedViaCli = false;
+ payload.AwakeProcessActive = true;
+ return payload.ToJsonObject();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("[MCP] Failed to set Awake to indefinite.", ex);
+ return AwakeStatusPayload.CreateError(ex.Message).ToJsonObject();
+ }
+ }
+
+ ///
+ /// Sets the Awake mode to timed (keep system awake for a specific duration).
+ ///
+ /// Duration in seconds (minimum 60).
+ /// Whether to keep the display on. Default is true.
+ /// JSON object with updated Awake status.
+ [McpServerTool]
+ [Description("Set Awake to timed mode (keep system awake for a specific duration).")]
+ public static JsonObject SetAwakeTimed(
+ [Description("Duration in seconds (minimum 60)")] int durationSeconds,
+ [Description("Whether to keep the display on")] bool keepDisplayOn = true,
+ [Description("Force the change even if Awake is already running")] bool force = false)
+ {
+ try
+ {
+ if (durationSeconds < 60)
+ {
+ durationSeconds = 60;
+ }
+
+ (bool powerToysRunning, bool awakeModuleEnabled) = CheckPowerToysAndAwakeStatus();
+
+ if (!powerToysRunning || !awakeModuleEnabled)
+ {
+ return HandleCliScenario(AwakeMode.TIMED, keepDisplayOn, (uint)durationSeconds, force);
+ }
+
+ TimeSpan timeSpan = TimeSpan.FromSeconds(durationSeconds);
+ uint hours = (uint)timeSpan.TotalHours;
+ uint minutes = (uint)Math.Ceiling(timeSpan.TotalMinutes % 60);
+ if (hours == 0 && minutes == 0)
+ {
+ minutes = 1;
+ }
+
+ AwakeSettings settings = SettingsUtils.GetSettingsOrDefault(AwakeSettings.ModuleName);
+ if (!force && IsAwakeActive(settings))
+ {
+ return BuildActiveProcessResponse(settings, true, false);
+ }
+
+ settings.Properties.Mode = AwakeMode.TIMED;
+ settings.Properties.ProcessId = 0;
+ settings.Properties.KeepDisplayOn = keepDisplayOn;
+ settings.Properties.IntervalHours = hours;
+ settings.Properties.IntervalMinutes = minutes;
+ settings.Properties.ExpirationDateTime = DateTimeOffset.Now.Add(timeSpan);
+ SettingsUtils.SaveSettings(settings.ToJsonString(), AwakeSettings.ModuleName);
+
+ string confirmation = FormatAwakeDescription(settings);
+ Logger.LogInfo($"[MCP] {confirmation}");
+ AwakeStatusPayload payload = AwakeStatusPayload.FromSettings(settings, confirmation);
+ payload.PowerToysRunning = true;
+ payload.LaunchedViaCli = false;
+ payload.AwakeProcessActive = true;
+ return payload.ToJsonObject();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("[MCP] Failed to set Awake to timed mode.", ex);
+ return AwakeStatusPayload.CreateError(ex.Message).ToJsonObject();
+ }
+ }
+
+ private static string FormatAwakeDescription(AwakeSettings settings)
+ {
+ var mode = settings.Properties.Mode.ToString().ToLowerInvariant();
+ var display = settings.Properties.KeepDisplayOn ? "display on" : "display off";
+
+ if (settings.Properties.ProcessId > 0)
+ {
+ return $"Awake mode: process-bound (PID={settings.Properties.ProcessId}), {display}";
+ }
+
+ return settings.Properties.Mode switch
+ {
+ AwakeMode.PASSIVE => "Awake mode: passive (system sleep allowed)",
+ AwakeMode.INDEFINITE => $"Awake mode: indefinite, {display}",
+ AwakeMode.TIMED => $"Awake mode: timed ({settings.Properties.IntervalHours}h {settings.Properties.IntervalMinutes}m), {display}",
+ AwakeMode.EXPIRABLE => $"Awake mode: expirable (until {settings.Properties.ExpirationDateTime:yyyy-MM-dd HH:mm}), {display}",
+ _ => $"Awake mode: {mode}",
+ };
+ }
+
+ private static JsonObject BuildActiveProcessResponse(AwakeSettings settings, bool powerToysRunning, bool launchedViaCli)
+ {
+ return AwakeStatusPayload.CreateError(
+ "Awake is already running. Use force=true to override.",
+ settings,
+ powerToysRunning,
+ launchedViaCli).ToJsonObject();
+ }
+
+ private static bool IsPowerToysRunning()
+ {
+ try
+ {
+ return Process.GetProcessesByName(PowerToysProcessName).Length > 0;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"[MCP] Unable to determine PowerToys runner status: {ex.Message}");
+ return true;
+ }
+ }
+
+ ///
+ /// Gets whether the Awake module is enabled in PowerToys settings.
+ ///
+ /// True if Awake module is enabled, false otherwise
+ private static bool IsAwakeModuleEnabled()
+ {
+ try
+ {
+ var generalSettings = SettingsUtils.GetSettings();
+ return generalSettings?.Enabled?.Awake == true;
+ }
+ catch
+ {
+ // If we can't read settings, assume disabled
+ return false;
+ }
+ }
+
+ ///
+ /// Checks PowerToys and Awake module status.
+ ///
+ /// Tuple containing (powerToysRunning, awakeModuleEnabled)
+ private static (bool PowerToysRunning, bool AwakeModuleEnabled) CheckPowerToysAndAwakeStatus()
+ {
+ bool powerToysRunning = IsPowerToysRunning();
+ bool awakeModuleEnabled = powerToysRunning && IsAwakeModuleEnabled();
+
+ return (powerToysRunning, awakeModuleEnabled);
+ }
+
+ ///
+ /// Handles CLI scenario when PowerToys is not running or Awake module is disabled.
+ ///
+ /// The Awake mode to set
+ /// Whether to keep display on
+ /// Duration in seconds (0 for indefinite)
+ /// Whether to force override existing process
+ /// JSON response for CLI scenario
+ private static JsonObject HandleCliScenario(AwakeMode mode, bool keepDisplayOn, uint durationSeconds, bool force)
+ {
+ if (!force && IsAwakeProcessRunning())
+ {
+ return AwakeStatusPayload.CreateError(
+ "Awake is already running and PowerToys is not active. Use force=true to override.",
+ powerToysRunning: false,
+ launchedViaCli: false).ToJsonObject();
+ }
+
+ if (IsAwakeProcessRunning())
+ {
+ StopAwakeProcesses();
+ }
+
+ JsonObject cliPayload = StartAwakeCliProcess(mode, keepDisplayOn, durationSeconds);
+ return cliPayload;
+ }
+
+ private static JsonObject StartAwakeCliProcess(AwakeMode mode, bool keepDisplayOn, uint durationSeconds)
+ {
+ try
+ {
+ if (!TryResolveAwakeExecutable(out string executablePath))
+ {
+ throw new FileNotFoundException("PowerToys.Awake.exe was not found near the MCP server executable.");
+ }
+
+ ProcessStartInfo startInfo = CreateSimpleStartInfo(executablePath, mode, keepDisplayOn, durationSeconds);
+ Process? launchedProcess = Process.Start(startInfo);
+ if (launchedProcess is null)
+ {
+ throw new InvalidOperationException("Failed to start PowerToys.Awake.exe.");
+ }
+
+ // No tracking, just launch and forget
+ launchedProcess.Dispose();
+
+ AwakeSettings snapshot = BuildAwakeSnapshot(mode, keepDisplayOn, durationSeconds);
+ string confirmation = FormatAwakeDescription(snapshot);
+ Logger.LogInfo($"[MCP] Launched Awake CLI for mode {mode} (PowerToys not running).");
+ AwakeStatusPayload payload = AwakeStatusPayload.FromSettings(snapshot, confirmation);
+ payload.AwakeProcessActive = true;
+ payload.LaunchedViaCli = true;
+ payload.PowerToysRunning = false;
+ return payload.ToJsonObject();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("[MCP] Failed to start Awake CLI.", ex);
+ return AwakeStatusPayload.CreateError(
+ ex.Message,
+ powerToysRunning: false,
+ launchedViaCli: false).ToJsonObject();
+ }
+ }
+
+ private static ProcessStartInfo CreateSimpleStartInfo(string executablePath, AwakeMode mode, bool keepDisplayOn, uint durationSeconds)
+ {
+ string workingDirectory = Path.GetDirectoryName(executablePath) ?? AppDomain.CurrentDomain.BaseDirectory;
+ var startInfo = new ProcessStartInfo(executablePath)
+ {
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WorkingDirectory = string.IsNullOrEmpty(workingDirectory) ? AppDomain.CurrentDomain.BaseDirectory : workingDirectory,
+ };
+
+ startInfo.ArgumentList.Add("--display-on");
+ startInfo.ArgumentList.Add(keepDisplayOn ? "true" : "false");
+
+ if (mode == AwakeMode.TIMED && durationSeconds > 0)
+ {
+ startInfo.ArgumentList.Add("--time-limit");
+ startInfo.ArgumentList.Add(durationSeconds.ToString(CultureInfo.InvariantCulture));
+ }
+
+ return startInfo;
+ }
+
+ private static void StopAwakeProcesses()
+ {
+ string processName = Path.GetFileNameWithoutExtension(AwakeExecutableName);
+ try
+ {
+ Process[] awakeProcesses = Process.GetProcessesByName(processName);
+ foreach (Process process in awakeProcesses)
+ {
+ try
+ {
+ if (!process.HasExited)
+ {
+ process.Kill(true);
+ process.WaitForExit(TimeSpan.FromSeconds(5));
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"[MCP] Failed to terminate Awake process {process.Id}: {ex.Message}");
+ }
+ finally
+ {
+ process.Dispose();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"[MCP] Failed to enumerate Awake processes: {ex.Message}");
+ }
+ }
+
+ private static AwakeSettings BuildAwakeSnapshot(AwakeMode mode, bool keepDisplayOn, uint durationSeconds)
+ {
+ var snapshot = new AwakeSettings();
+ snapshot.Properties.Mode = mode;
+ snapshot.Properties.KeepDisplayOn = keepDisplayOn;
+ snapshot.Properties.ProcessId = 0;
+
+ if (mode == AwakeMode.TIMED && durationSeconds > 0)
+ {
+ TimeSpan timeSpan = TimeSpan.FromSeconds(durationSeconds);
+ snapshot.Properties.IntervalHours = (uint)timeSpan.TotalHours;
+ snapshot.Properties.IntervalMinutes = (uint)Math.Ceiling(timeSpan.TotalMinutes % 60);
+ snapshot.Properties.ExpirationDateTime = DateTimeOffset.Now.AddSeconds(durationSeconds);
+ }
+ else
+ {
+ snapshot.Properties.IntervalHours = 0;
+ snapshot.Properties.IntervalMinutes = 0;
+ snapshot.Properties.ExpirationDateTime = DateTimeOffset.Now;
+ }
+
+ return snapshot;
+ }
+
+ private static bool TryResolveAwakeExecutable(out string executablePath)
+ {
+ string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
+ if (TryResolveAwakeExecutableFrom(baseDirectory, out executablePath))
+ {
+ return true;
+ }
+
+ string? parentDirectory = Directory.GetParent(baseDirectory)?.FullName;
+ if (!string.IsNullOrEmpty(parentDirectory) && TryResolveAwakeExecutableFrom(parentDirectory, out executablePath))
+ {
+ return true;
+ }
+
+ executablePath = string.Empty;
+ return false;
+ }
+
+ private static bool TryResolveAwakeExecutableFrom(string rootDirectory, out string executablePath)
+ {
+ foreach (string relativePath in AwakeRelativeSearchPaths)
+ {
+ string candidate = Path.Combine(rootDirectory, relativePath);
+ if (File.Exists(candidate))
+ {
+ executablePath = candidate;
+ return true;
+ }
+ }
+
+ executablePath = string.Empty;
+ return false;
+ }
+
+ private static bool IsAwakeProcessRunning()
+ {
+ try
+ {
+ string processName = Path.GetFileNameWithoutExtension(AwakeExecutableName);
+ return Process.GetProcessesByName(processName).Length > 0;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"[MCP] Unable to determine Awake process status: {ex.Message}");
+ return false;
+ }
+ }
+
+ private static bool IsAwakeActive(AwakeSettings settings)
+ {
+ // Only check if Awake module is enabled
+ return IsAwakeModuleEnabled();
+ }
+
+ private sealed class AwakeStatusPayload
+ {
+ internal string Mode { get; set; } = "unknown";
+
+ internal bool KeepDisplayOn { get; set; }
+
+ internal bool IsProcessBound { get; set; }
+
+ internal int ProcessId { get; set; }
+
+ internal uint IntervalHours { get; set; }
+
+ internal uint IntervalMinutes { get; set; }
+
+ internal string ExpirationDateTime { get; set; } = string.Empty;
+
+ internal string Summary { get; set; } = string.Empty;
+
+ internal bool PowerToysRunning { get; set; }
+
+ internal bool AwakeProcessActive { get; set; }
+
+ internal bool LaunchedViaCli { get; set; }
+
+ internal bool Success { get; set; } = true;
+
+ internal string? ErrorMessage { get; set; }
+
+ internal JsonObject ToJsonObject()
+ {
+ var result = new JsonObject
+ {
+ ["mode"] = Mode,
+ ["keepDisplayOn"] = KeepDisplayOn,
+ ["isProcessBound"] = IsProcessBound,
+ ["processId"] = ProcessId,
+ ["intervalHours"] = IntervalHours,
+ ["intervalMinutes"] = IntervalMinutes,
+ ["expirationDateTime"] = ExpirationDateTime,
+ ["summary"] = Summary,
+ ["powerToysRunning"] = PowerToysRunning,
+ ["awakeProcessActive"] = AwakeProcessActive,
+ ["launchedViaCli"] = LaunchedViaCli,
+ };
+
+ // Add error handling properties
+ if (!Success)
+ {
+ result["success"] = false;
+ if (!string.IsNullOrEmpty(ErrorMessage))
+ {
+ result["error"] = ErrorMessage;
+ }
+ }
+
+ return result;
+ }
+
+ internal static AwakeStatusPayload FromSettings(AwakeSettings settings, string summary)
+ {
+ return new AwakeStatusPayload
+ {
+ Mode = settings.Properties.Mode.ToString().ToLowerInvariant(),
+ KeepDisplayOn = settings.Properties.KeepDisplayOn,
+ IsProcessBound = settings.Properties.ProcessId > 0,
+ ProcessId = (int)settings.Properties.ProcessId,
+ IntervalHours = settings.Properties.IntervalHours,
+ IntervalMinutes = settings.Properties.IntervalMinutes,
+ ExpirationDateTime = settings.Properties.ExpirationDateTime.ToString("O"),
+ Summary = summary,
+ };
+ }
+
+ internal static AwakeStatusPayload CreateInactive()
+ {
+ return new AwakeStatusPayload
+ {
+ Mode = "inactive",
+ KeepDisplayOn = false,
+ IsProcessBound = false,
+ ProcessId = 0,
+ IntervalHours = 0,
+ IntervalMinutes = 0,
+ ExpirationDateTime = string.Empty,
+ Summary = "PowerToys Awake is not running because PowerToys is not active.",
+ PowerToysRunning = false,
+ AwakeProcessActive = false,
+ LaunchedViaCli = false,
+ };
+ }
+
+ internal static AwakeStatusPayload CreateUnknownActive(bool powerToysRunning, bool launchedViaCli)
+ {
+ return new AwakeStatusPayload
+ {
+ Mode = "unknown",
+ KeepDisplayOn = false,
+ IsProcessBound = false,
+ ProcessId = 0,
+ IntervalHours = 0,
+ IntervalMinutes = 0,
+ ExpirationDateTime = string.Empty,
+ Summary = "An Awake process is currently running, but its configuration cannot be determined. To terminate the existing process and start with new settings, use force=true.",
+ PowerToysRunning = powerToysRunning,
+ AwakeProcessActive = true,
+ LaunchedViaCli = launchedViaCli,
+ };
+ }
+
+ internal static AwakeStatusPayload CreateError(string errorMessage, AwakeSettings? settings = null, bool powerToysRunning = false, bool launchedViaCli = false)
+ {
+ var payload = new AwakeStatusPayload
+ {
+ Success = false,
+ ErrorMessage = errorMessage,
+ PowerToysRunning = powerToysRunning,
+ LaunchedViaCli = launchedViaCli,
+ AwakeProcessActive = true,
+ };
+
+ if (settings != null)
+ {
+ payload.Mode = settings.Properties.Mode.ToString().ToLowerInvariant();
+ payload.KeepDisplayOn = settings.Properties.KeepDisplayOn;
+ payload.IsProcessBound = settings.Properties.ProcessId > 0;
+ payload.ProcessId = (int)settings.Properties.ProcessId;
+ payload.IntervalHours = settings.Properties.IntervalHours;
+ payload.IntervalMinutes = settings.Properties.IntervalMinutes;
+ payload.ExpirationDateTime = settings.Properties.ExpirationDateTime.ToString("O");
+ payload.Summary = "An Awake session is already active with the current settings. To override and change the configuration, use force=true.";
+ }
+ else
+ {
+ payload.Mode = "unknown";
+ payload.Summary = "An Awake process is currently running. To terminate the existing process and start with new settings, use force=true.";
+ }
+
+ return payload;
+ }
+ }
+ }
+}
diff --git a/src/modules/mcp/McpModuleInterface/McpConstants.h b/src/modules/mcp/McpModuleInterface/McpConstants.h
deleted file mode 100644
index 3fec1635f9..0000000000
--- a/src/modules/mcp/McpModuleInterface/McpConstants.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#pragma once
-
-namespace McpConstants
-{
- inline const wchar_t* ModuleKey = L"MCP";
-}
diff --git a/src/modules/mcp/McpModuleInterface/McpModuleInterface.rc b/src/modules/mcp/McpModuleInterface/McpModuleInterface.rc
deleted file mode 100644
index 3f9bc0b70c..0000000000
--- a/src/modules/mcp/McpModuleInterface/McpModuleInterface.rc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Microsoft Visual C++ generated resource script.
-//
-#include "resource.h"
-
-#define APSTUDIO_READONLY_SYMBOLS
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 2 resource.
-//
-#include "winres.h"
-
-/////////////////////////////////////////////////////////////////////////////
-#undef APSTUDIO_READONLY_SYMBOLS
-
-/////////////////////////////////////////////////////////////////////////////
-// English (United States) resources
-
-#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
-LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
-#pragma code_page(1252)
-
-#ifdef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// TEXTINCLUDE
-//
-
-1 TEXTINCLUDE
-BEGIN
- "resource.h\0"
-END
-
-2 TEXTINCLUDE
-BEGIN
- "#include ""winres.h""\r\n"
- "\0"
-END
-
-3 TEXTINCLUDE
-BEGIN
- "\r\n"
- "\0"
-END
-
-#endif // APSTUDIO_INVOKED
-
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// String Table
-//
-
-STRINGTABLE
-BEGIN
- IDS_MCP_NAME "Model Context Protocol"
-END
-
-#endif // English (United States) resources
-/////////////////////////////////////////////////////////////////////////////
-
-
-
-#ifndef APSTUDIO_INVOKED
-/////////////////////////////////////////////////////////////////////////////
-//
-// Generated from the TEXTINCLUDE 3 resource.
-//
-
-
-/////////////////////////////////////////////////////////////////////////////
-#endif // not APSTUDIO_INVOKED
diff --git a/src/modules/mcp/McpModuleInterface/McpModuleInterface.vcxproj b/src/modules/mcp/McpModuleInterface/McpModuleInterface.vcxproj
deleted file mode 100644
index f860148c73..0000000000
--- a/src/modules/mcp/McpModuleInterface/McpModuleInterface.vcxproj
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
- 15.0
- {8E87A3A7-9B8C-4F7D-A3E1-5C4B8D9F6E2A}
- Win32Proj
- McpServer
- McpModuleInterface
- PowerToys.McpModuleInterface
- v143
-
-
-
- DynamicLibrary
-
-
-
-
-
-
-
-
-
-
-
- ..\..\..\..\$(Platform)\$(Configuration)\
-
-
-
- EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
- ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)
-
-
- $(OutDir)$(TargetName)$(TargetExt)
-
-
-
-
-
-
-
-
-
-
-
- Create
-
-
-
-
-
- {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}
-
-
- {6955446d-23f7-4023-9bb3-8657f904af99}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
-
-
-
-
-
diff --git a/src/modules/mcp/McpModuleInterface/McpModuleInterface.vcxproj.filters b/src/modules/mcp/McpModuleInterface/McpModuleInterface.vcxproj.filters
deleted file mode 100644
index 6a0e29db45..0000000000
--- a/src/modules/mcp/McpModuleInterface/McpModuleInterface.vcxproj.filters
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
- {93995380-89BD-4b04-88EB-625FBE52EBFB}
- h;hh;hpp;hxx;hm;inl;inc;xsd
-
-
- {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
- rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
-
-
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
-
-
- Resource Files
-
-
-
-
-
-
diff --git a/src/modules/mcp/McpModuleInterface/dllmain.cpp b/src/modules/mcp/McpModuleInterface/dllmain.cpp
deleted file mode 100644
index 1ee2cfb902..0000000000
--- a/src/modules/mcp/McpModuleInterface/dllmain.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-#include "pch.h"
-#include
-#include
-#include
-#include "trace.h"
-#include "resource.h"
-#include "McpConstants.h"
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-#include
-
-BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
-{
- switch (ul_reason_for_call)
- {
- case DLL_PROCESS_ATTACH:
- Trace::RegisterProvider();
- break;
- case DLL_THREAD_ATTACH:
- case DLL_THREAD_DETACH:
- break;
- case DLL_PROCESS_DETACH:
- Trace::UnregisterProvider();
- break;
- }
- return TRUE;
-}
-
-const static wchar_t* MODULE_NAME = L"Model Context Protocol";
-const static wchar_t* MODULE_DESC = L"Exposes PowerToys functionality via MCP protocol for AI assistants.";
-
-class McpServer : public PowertoyModuleIface
-{
- std::wstring app_name;
- std::wstring app_key;
-
-private:
- bool m_enabled = false;
- PROCESS_INFORMATION p_info = {};
-
- bool is_process_running()
- {
- return WaitForSingleObject(p_info.hProcess, 0) == WAIT_TIMEOUT;
- }
-
- void launch_process()
- {
- Logger::trace(L"Launching PowerToys MCP Server process");
- unsigned long powertoys_pid = GetCurrentProcessId();
-
- std::wstring executable_args = L"--pid " + std::to_wstring(powertoys_pid);
- std::wstring application_path = L"PowerToys.McpServer.exe";
- std::wstring full_command_path = application_path + L" " + executable_args.data();
- Logger::trace(L"PowerToys MCP Server launching with parameters: " + executable_args);
-
- STARTUPINFO info = { sizeof(info) };
-
- if (!CreateProcess(application_path.c_str(), full_command_path.data(), NULL, NULL, true, NULL, NULL, NULL, &info, &p_info))
- {
- DWORD error = GetLastError();
- std::wstring message = L"PowerToys MCP Server failed to start with error: ";
- message += std::to_wstring(error);
- Logger::error(message);
- }
- }
-
-public:
- McpServer()
- {
- app_name = GET_RESOURCE_STRING(IDS_MCP_NAME);
- app_key = McpConstants::ModuleKey;
- std::filesystem::path logFilePath(PTSettingsHelper::get_module_save_folder_location(this->app_key));
- logFilePath.append(L"Logs");
- Logger::init("McpModuleInterface", logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location());
- Logger::info("McpServer module interface is constructing");
- };
-
- // Return the configured status for the gpo policy for the module
- virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
- {
- return powertoys_gpo::gpo_rule_configured_not_configured;
- }
-
- virtual void destroy() override
- {
- delete this;
- }
-
- virtual const wchar_t* get_name() override
- {
- return MODULE_NAME;
- }
-
- virtual bool get_config(wchar_t* buffer, int* buffer_size) override
- {
- HINSTANCE hinstance = reinterpret_cast(&__ImageBase);
-
- PowerToysSettings::Settings settings(hinstance, get_name());
- settings.set_description(MODULE_DESC);
-
- return settings.serialize_to_buffer(buffer, buffer_size);
- }
-
- virtual const wchar_t* get_key() override
- {
- return app_key.c_str();
- }
-
- virtual void set_config(const wchar_t* config) override
- {
- try
- {
- // Parse the input JSON string.
- PowerToysSettings::PowerToyValues values =
- PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
-
- // Persist the values.
- values.save_to_settings_file();
- }
- catch (std::exception&)
- {
- // Improper JSON.
- }
- }
-
- virtual void enable()
- {
- Trace::EnableMCP(true);
- launch_process();
- m_enabled = true;
- };
-
- virtual void disable()
- {
- if (m_enabled)
- {
- Trace::EnableMCP(false);
- Logger::trace(L"Disabling MCP Server...");
-
- // Terminate the MCP server process
- if (p_info.hProcess)
- {
- TerminateProcess(p_info.hProcess, 0);
- CloseHandle(p_info.hProcess);
- CloseHandle(p_info.hThread);
- }
- }
-
- m_enabled = false;
- }
-
- virtual bool is_enabled() override
- {
- return m_enabled;
- }
-};
-
-extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
-{
- return new McpServer();
-}
diff --git a/src/modules/mcp/McpModuleInterface/packages.config b/src/modules/mcp/McpModuleInterface/packages.config
deleted file mode 100644
index 8946351a67..0000000000
--- a/src/modules/mcp/McpModuleInterface/packages.config
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/src/modules/mcp/McpModuleInterface/pch.cpp b/src/modules/mcp/McpModuleInterface/pch.cpp
deleted file mode 100644
index 1d9f38c57d..0000000000
--- a/src/modules/mcp/McpModuleInterface/pch.cpp
+++ /dev/null
@@ -1 +0,0 @@
-#include "pch.h"
diff --git a/src/modules/mcp/McpModuleInterface/pch.h b/src/modules/mcp/McpModuleInterface/pch.h
deleted file mode 100644
index 14e722860e..0000000000
--- a/src/modules/mcp/McpModuleInterface/pch.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-
-#define WIN32_LEAN_AND_MEAN
-#include
-#include
diff --git a/src/modules/mcp/McpModuleInterface/resource.h b/src/modules/mcp/McpModuleInterface/resource.h
deleted file mode 100644
index 4df4d15aa1..0000000000
--- a/src/modules/mcp/McpModuleInterface/resource.h
+++ /dev/null
@@ -1,16 +0,0 @@
-//{{NO_DEPENDENCIES}}
-// Microsoft Visual C++ generated include file.
-// Used by McpModuleInterface.rc
-//
-#define IDS_MCP_NAME 101
-
-// Next default values for new objects
-//
-#ifdef APSTUDIO_INVOKED
-#ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE 102
-#define _APS_NEXT_COMMAND_VALUE 40001
-#define _APS_NEXT_CONTROL_VALUE 1001
-#define _APS_NEXT_SYMED_VALUE 101
-#endif
-#endif
diff --git a/src/modules/mcp/McpModuleInterface/trace.cpp b/src/modules/mcp/McpModuleInterface/trace.cpp
deleted file mode 100644
index ee9a6a7b8a..0000000000
--- a/src/modules/mcp/McpModuleInterface/trace.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-#include "pch.h"
-#include "trace.h"
-#include
-#include
-
-TRACELOGGING_DEFINE_PROVIDER(
- g_hProvider,
- "Microsoft.PowerToys",
- (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x3b, 0x77),
- TraceLoggingOptionProjectTelemetry());
-
-void Trace::RegisterProvider() noexcept
-{
- TraceLoggingRegister(g_hProvider);
-}
-
-void Trace::UnregisterProvider() noexcept
-{
- TraceLoggingUnregister(g_hProvider);
-}
-
-void Trace::EnableMCP(bool enabled) noexcept
-{
- TraceLoggingWrite(
- g_hProvider,
- "MCP_EnableMCP",
- ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
- TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
- TraceLoggingBoolean(enabled, "Enabled"));
-}
diff --git a/src/modules/mcp/McpModuleInterface/trace.h b/src/modules/mcp/McpModuleInterface/trace.h
deleted file mode 100644
index b3ace77fd6..0000000000
--- a/src/modules/mcp/McpModuleInterface/trace.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#pragma once
-
-namespace Trace
-{
- void RegisterProvider() noexcept;
- void UnregisterProvider() noexcept;
- void EnableMCP(bool enabled) noexcept;
-}
diff --git a/src/modules/mcp/McpServer/Tools/AwakeTools.cs b/src/modules/mcp/McpServer/Tools/AwakeTools.cs
index 468c0f50d8..0e1d32f4fa 100644
--- a/src/modules/mcp/McpServer/Tools/AwakeTools.cs
+++ b/src/modules/mcp/McpServer/Tools/AwakeTools.cs
@@ -4,10 +4,14 @@
using System;
using System.ComponentModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
using System.Text.Json.Nodes;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using ModelContextProtocol.Server;
+using Lock = System.Threading.Lock;
namespace PowerToys.McpServer.Tools
{
@@ -18,27 +22,13 @@ namespace PowerToys.McpServer.Tools
public static class AwakeTools
{
private static readonly SettingsUtils SettingsUtils = new SettingsUtils();
-
- ///
- /// Check if Awake module is enabled in MCP settings.
- ///
- private static void CheckModuleEnabled()
- {
- try
- {
- McpSettings mcpSettings = SettingsUtils.GetSettingsOrDefault(McpSettings.ModuleName);
- bool isEnabled = mcpSettings.Properties.EnabledModules.TryGetValue("Awake", out bool enabled) ? enabled : true;
- if (!isEnabled)
- {
- throw new InvalidOperationException("Awake module is disabled in MCP settings. Enable it in PowerToys Settings > MCP > Module Toggles.");
- }
- }
- catch (Exception ex) when (ex is not InvalidOperationException)
- {
- // If we can't read MCP settings, assume enabled (backward compatibility)
- Logger.LogWarning($"[MCP] Could not check MCP module status, assuming enabled: {ex.Message}");
- }
- }
+ private const string PowerToysProcessName = "PowerToys";
+ private const string AwakeExecutableName = "PowerToys.Awake.exe";
+ private static readonly string[] AwakeRelativeSearchPaths =
+ [
+ AwakeExecutableName,
+ Path.Combine("modules", "Awake", AwakeExecutableName),
+ ];
///
/// Gets the current Awake mode and configuration.
@@ -50,21 +40,45 @@ namespace PowerToys.McpServer.Tools
{
try
{
- CheckModuleEnabled();
+ (bool powerToysRunning, bool awakeModuleEnabled) = CheckPowerToysAndAwakeStatus();
+
+ if (!powerToysRunning || !awakeModuleEnabled)
+ {
+ if (IsAwakeProcessRunning())
+ {
+ string errorMessage = "Awake is already running via CLI. Use force=true to override.";
+
+ return AwakeStatusPayload.CreateError(
+ errorMessage,
+ powerToysRunning: powerToysRunning,
+ launchedViaCli: true).ToJsonObject();
+ }
+
+ return AwakeStatusPayload.CreateInactive().ToJsonObject();
+ }
+
+ // PowerToys is running and Awake module is enabled
+ bool awakeProcessRunning = IsAwakeProcessRunning();
+
AwakeSettings settings = SettingsUtils.GetSettingsOrDefault(AwakeSettings.ModuleName);
string summary = FormatAwakeDescription(settings);
- JsonObject payload = FormatAwakeJson(settings, summary);
+
+ if (awakeProcessRunning)
+ {
+ summary = $"{summary} An Awake process is already running with the current configuration. To override the active session and apply new settings, use force=true.";
+ }
+
+ AwakeStatusPayload payload = AwakeStatusPayload.FromSettings(settings, summary);
+ payload.PowerToysRunning = true;
+ payload.LaunchedViaCli = !awakeModuleEnabled && awakeProcessRunning;
+ payload.AwakeProcessActive = awakeProcessRunning;
Logger.LogInfo("[MCP] Retrieved Awake status via SDK tool.");
- return payload;
+ return payload.ToJsonObject();
}
catch (Exception ex)
{
Logger.LogError("[MCP] Failed to read Awake status.", ex);
- return new JsonObject
- {
- ["error"] = ex.Message,
- ["mode"] = "unknown",
- };
+ return AwakeStatusPayload.CreateError(ex.Message).ToJsonObject();
}
}
@@ -78,7 +92,15 @@ namespace PowerToys.McpServer.Tools
{
try
{
- CheckModuleEnabled();
+ (bool powerToysRunning, bool awakeModuleEnabled) = CheckPowerToysAndAwakeStatus();
+
+ if (!powerToysRunning || !awakeModuleEnabled)
+ {
+ StopAwakeProcesses();
+ Logger.LogInfo("[MCP] Stopped all Awake processes because PowerToys is not running.");
+ return AwakeStatusPayload.CreateInactive().ToJsonObject();
+ }
+
AwakeSettings settings = SettingsUtils.GetSettingsOrDefault(AwakeSettings.ModuleName);
settings.Properties.Mode = AwakeMode.PASSIVE;
settings.Properties.ProcessId = 0;
@@ -89,16 +111,16 @@ namespace PowerToys.McpServer.Tools
string confirmation = FormatAwakeDescription(settings);
Logger.LogInfo($"[MCP] {confirmation}");
- return FormatAwakeJson(settings, confirmation);
+ AwakeStatusPayload payload = AwakeStatusPayload.FromSettings(settings, confirmation);
+ payload.PowerToysRunning = true;
+ payload.LaunchedViaCli = false;
+ payload.AwakeProcessActive = false;
+ return payload.ToJsonObject();
}
catch (Exception ex)
{
Logger.LogError("[MCP] Failed to set Awake to passive.", ex);
- return new JsonObject
- {
- ["error"] = ex.Message,
- ["success"] = false,
- };
+ return AwakeStatusPayload.CreateError(ex.Message).ToJsonObject();
}
}
@@ -110,12 +132,24 @@ namespace PowerToys.McpServer.Tools
[McpServerTool]
[Description("Set Awake to indefinite mode (keep system awake until manually changed).")]
public static JsonObject SetAwakeIndefinite(
- [Description("Whether to keep the display on")] bool keepDisplayOn = true)
+ [Description("Whether to keep the display on")] bool keepDisplayOn = true,
+ [Description("Force the change even if Awake is already running")] bool force = false)
{
try
{
- CheckModuleEnabled();
+ (bool powerToysRunning, bool awakeModuleEnabled) = CheckPowerToysAndAwakeStatus();
+
+ if (!powerToysRunning || !awakeModuleEnabled)
+ {
+ return HandleCliScenario(AwakeMode.INDEFINITE, keepDisplayOn, 0, force);
+ }
+
AwakeSettings settings = SettingsUtils.GetSettingsOrDefault(AwakeSettings.ModuleName);
+ if (!force && IsAwakeActive(settings))
+ {
+ return BuildActiveProcessResponse(settings, true, false);
+ }
+
settings.Properties.Mode = AwakeMode.INDEFINITE;
settings.Properties.ProcessId = 0;
settings.Properties.KeepDisplayOn = keepDisplayOn;
@@ -125,16 +159,16 @@ namespace PowerToys.McpServer.Tools
string confirmation = FormatAwakeDescription(settings);
Logger.LogInfo($"[MCP] {confirmation}");
- return FormatAwakeJson(settings, confirmation);
+ AwakeStatusPayload payload = AwakeStatusPayload.FromSettings(settings, confirmation);
+ payload.PowerToysRunning = true;
+ payload.LaunchedViaCli = false;
+ payload.AwakeProcessActive = true;
+ return payload.ToJsonObject();
}
catch (Exception ex)
{
Logger.LogError("[MCP] Failed to set Awake to indefinite.", ex);
- return new JsonObject
- {
- ["error"] = ex.Message,
- ["success"] = false,
- };
+ return AwakeStatusPayload.CreateError(ex.Message).ToJsonObject();
}
}
@@ -148,16 +182,23 @@ namespace PowerToys.McpServer.Tools
[Description("Set Awake to timed mode (keep system awake for a specific duration).")]
public static JsonObject SetAwakeTimed(
[Description("Duration in seconds (minimum 60)")] int durationSeconds,
- [Description("Whether to keep the display on")] bool keepDisplayOn = true)
+ [Description("Whether to keep the display on")] bool keepDisplayOn = true,
+ [Description("Force the change even if Awake is already running")] bool force = false)
{
try
{
- CheckModuleEnabled();
if (durationSeconds < 60)
{
durationSeconds = 60;
}
+ (bool powerToysRunning, bool awakeModuleEnabled) = CheckPowerToysAndAwakeStatus();
+
+ if (!powerToysRunning || !awakeModuleEnabled)
+ {
+ return HandleCliScenario(AwakeMode.TIMED, keepDisplayOn, (uint)durationSeconds, force);
+ }
+
TimeSpan timeSpan = TimeSpan.FromSeconds(durationSeconds);
uint hours = (uint)timeSpan.TotalHours;
uint minutes = (uint)Math.Ceiling(timeSpan.TotalMinutes % 60);
@@ -167,6 +208,11 @@ namespace PowerToys.McpServer.Tools
}
AwakeSettings settings = SettingsUtils.GetSettingsOrDefault(AwakeSettings.ModuleName);
+ if (!force && IsAwakeActive(settings))
+ {
+ return BuildActiveProcessResponse(settings, true, false);
+ }
+
settings.Properties.Mode = AwakeMode.TIMED;
settings.Properties.ProcessId = 0;
settings.Properties.KeepDisplayOn = keepDisplayOn;
@@ -177,16 +223,16 @@ namespace PowerToys.McpServer.Tools
string confirmation = FormatAwakeDescription(settings);
Logger.LogInfo($"[MCP] {confirmation}");
- return FormatAwakeJson(settings, confirmation);
+ AwakeStatusPayload payload = AwakeStatusPayload.FromSettings(settings, confirmation);
+ payload.PowerToysRunning = true;
+ payload.LaunchedViaCli = false;
+ payload.AwakeProcessActive = true;
+ return payload.ToJsonObject();
}
catch (Exception ex)
{
Logger.LogError("[MCP] Failed to set Awake to timed mode.", ex);
- return new JsonObject
- {
- ["error"] = ex.Message,
- ["success"] = false,
- };
+ return AwakeStatusPayload.CreateError(ex.Message).ToJsonObject();
}
}
@@ -210,19 +256,394 @@ namespace PowerToys.McpServer.Tools
};
}
- private static JsonObject FormatAwakeJson(AwakeSettings settings, string summary)
+ private static JsonObject BuildActiveProcessResponse(AwakeSettings settings, bool powerToysRunning, bool launchedViaCli)
{
- return new JsonObject
+ return AwakeStatusPayload.CreateError(
+ "Awake is already running. Use force=true to override.",
+ settings,
+ powerToysRunning,
+ launchedViaCli).ToJsonObject();
+ }
+
+ private static bool IsPowerToysRunning()
+ {
+ try
{
- ["mode"] = settings.Properties.Mode.ToString().ToLowerInvariant(),
- ["keepDisplayOn"] = settings.Properties.KeepDisplayOn,
- ["isProcessBound"] = settings.Properties.ProcessId > 0,
- ["processId"] = settings.Properties.ProcessId,
- ["intervalHours"] = settings.Properties.IntervalHours,
- ["intervalMinutes"] = settings.Properties.IntervalMinutes,
- ["expirationDateTime"] = settings.Properties.ExpirationDateTime.ToString("O"),
- ["summary"] = summary,
+ return Process.GetProcessesByName(PowerToysProcessName).Length > 0;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"[MCP] Unable to determine PowerToys runner status: {ex.Message}");
+ return true;
+ }
+ }
+
+ ///
+ /// Gets whether the Awake module is enabled in PowerToys settings.
+ ///
+ /// True if Awake module is enabled, false otherwise
+ private static bool IsAwakeModuleEnabled()
+ {
+ try
+ {
+ var generalSettings = SettingsUtils.GetSettings();
+ return generalSettings?.Enabled?.Awake == true;
+ }
+ catch
+ {
+ // If we can't read settings, assume disabled
+ return false;
+ }
+ }
+
+ ///
+ /// Checks PowerToys and Awake module status.
+ ///
+ /// Tuple containing (powerToysRunning, awakeModuleEnabled)
+ private static (bool PowerToysRunning, bool AwakeModuleEnabled) CheckPowerToysAndAwakeStatus()
+ {
+ bool powerToysRunning = IsPowerToysRunning();
+ bool awakeModuleEnabled = powerToysRunning && IsAwakeModuleEnabled();
+
+ return (powerToysRunning, awakeModuleEnabled);
+ }
+
+ ///
+ /// Handles CLI scenario when PowerToys is not running or Awake module is disabled.
+ ///
+ /// The Awake mode to set
+ /// Whether to keep display on
+ /// Duration in seconds (0 for indefinite)
+ /// Whether to force override existing process
+ /// JSON response for CLI scenario
+ private static JsonObject HandleCliScenario(AwakeMode mode, bool keepDisplayOn, uint durationSeconds, bool force)
+ {
+ if (!force && IsAwakeProcessRunning())
+ {
+ return AwakeStatusPayload.CreateError(
+ "Awake is already running and PowerToys is not active. Use force=true to override.",
+ powerToysRunning: false,
+ launchedViaCli: false).ToJsonObject();
+ }
+
+ if (IsAwakeProcessRunning())
+ {
+ StopAwakeProcesses();
+ }
+
+ JsonObject cliPayload = StartAwakeCliProcess(mode, keepDisplayOn, durationSeconds);
+ return cliPayload;
+ }
+
+ private static JsonObject StartAwakeCliProcess(AwakeMode mode, bool keepDisplayOn, uint durationSeconds)
+ {
+ try
+ {
+ if (!TryResolveAwakeExecutable(out string executablePath))
+ {
+ throw new FileNotFoundException("PowerToys.Awake.exe was not found near the MCP server executable.");
+ }
+
+ ProcessStartInfo startInfo = CreateSimpleStartInfo(executablePath, mode, keepDisplayOn, durationSeconds);
+ Process? launchedProcess = Process.Start(startInfo);
+ if (launchedProcess is null)
+ {
+ throw new InvalidOperationException("Failed to start PowerToys.Awake.exe.");
+ }
+
+ // No tracking, just launch and forget
+ launchedProcess.Dispose();
+
+ AwakeSettings snapshot = BuildAwakeSnapshot(mode, keepDisplayOn, durationSeconds);
+ string confirmation = FormatAwakeDescription(snapshot);
+ Logger.LogInfo($"[MCP] Launched Awake CLI for mode {mode} (PowerToys not running).");
+ AwakeStatusPayload payload = AwakeStatusPayload.FromSettings(snapshot, confirmation);
+ payload.AwakeProcessActive = true;
+ payload.LaunchedViaCli = true;
+ payload.PowerToysRunning = false;
+ return payload.ToJsonObject();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("[MCP] Failed to start Awake CLI.", ex);
+ return AwakeStatusPayload.CreateError(
+ ex.Message,
+ powerToysRunning: false,
+ launchedViaCli: false).ToJsonObject();
+ }
+ }
+
+ private static ProcessStartInfo CreateSimpleStartInfo(string executablePath, AwakeMode mode, bool keepDisplayOn, uint durationSeconds)
+ {
+ string workingDirectory = Path.GetDirectoryName(executablePath) ?? AppDomain.CurrentDomain.BaseDirectory;
+ var startInfo = new ProcessStartInfo(executablePath)
+ {
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WorkingDirectory = string.IsNullOrEmpty(workingDirectory) ? AppDomain.CurrentDomain.BaseDirectory : workingDirectory,
};
+
+ startInfo.ArgumentList.Add("--display-on");
+ startInfo.ArgumentList.Add(keepDisplayOn ? "true" : "false");
+
+ if (mode == AwakeMode.TIMED && durationSeconds > 0)
+ {
+ startInfo.ArgumentList.Add("--time-limit");
+ startInfo.ArgumentList.Add(durationSeconds.ToString(CultureInfo.InvariantCulture));
+ }
+
+ return startInfo;
+ }
+
+ private static void StopAwakeProcesses()
+ {
+ string processName = Path.GetFileNameWithoutExtension(AwakeExecutableName);
+ try
+ {
+ Process[] awakeProcesses = Process.GetProcessesByName(processName);
+ foreach (Process process in awakeProcesses)
+ {
+ try
+ {
+ if (!process.HasExited)
+ {
+ process.Kill(true);
+ process.WaitForExit(TimeSpan.FromSeconds(5));
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"[MCP] Failed to terminate Awake process {process.Id}: {ex.Message}");
+ }
+ finally
+ {
+ process.Dispose();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"[MCP] Failed to enumerate Awake processes: {ex.Message}");
+ }
+ }
+
+ private static AwakeSettings BuildAwakeSnapshot(AwakeMode mode, bool keepDisplayOn, uint durationSeconds)
+ {
+ var snapshot = new AwakeSettings();
+ snapshot.Properties.Mode = mode;
+ snapshot.Properties.KeepDisplayOn = keepDisplayOn;
+ snapshot.Properties.ProcessId = 0;
+
+ if (mode == AwakeMode.TIMED && durationSeconds > 0)
+ {
+ TimeSpan timeSpan = TimeSpan.FromSeconds(durationSeconds);
+ snapshot.Properties.IntervalHours = (uint)timeSpan.TotalHours;
+ snapshot.Properties.IntervalMinutes = (uint)Math.Ceiling(timeSpan.TotalMinutes % 60);
+ snapshot.Properties.ExpirationDateTime = DateTimeOffset.Now.AddSeconds(durationSeconds);
+ }
+ else
+ {
+ snapshot.Properties.IntervalHours = 0;
+ snapshot.Properties.IntervalMinutes = 0;
+ snapshot.Properties.ExpirationDateTime = DateTimeOffset.Now;
+ }
+
+ return snapshot;
+ }
+
+ private static bool TryResolveAwakeExecutable(out string executablePath)
+ {
+ string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
+ if (TryResolveAwakeExecutableFrom(baseDirectory, out executablePath))
+ {
+ return true;
+ }
+
+ string? parentDirectory = Directory.GetParent(baseDirectory)?.FullName;
+ if (!string.IsNullOrEmpty(parentDirectory) && TryResolveAwakeExecutableFrom(parentDirectory, out executablePath))
+ {
+ return true;
+ }
+
+ executablePath = string.Empty;
+ return false;
+ }
+
+ private static bool TryResolveAwakeExecutableFrom(string rootDirectory, out string executablePath)
+ {
+ foreach (string relativePath in AwakeRelativeSearchPaths)
+ {
+ string candidate = Path.Combine(rootDirectory, relativePath);
+ if (File.Exists(candidate))
+ {
+ executablePath = candidate;
+ return true;
+ }
+ }
+
+ executablePath = string.Empty;
+ return false;
+ }
+
+ private static bool IsAwakeProcessRunning()
+ {
+ try
+ {
+ string processName = Path.GetFileNameWithoutExtension(AwakeExecutableName);
+ return Process.GetProcessesByName(processName).Length > 0;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning($"[MCP] Unable to determine Awake process status: {ex.Message}");
+ return false;
+ }
+ }
+
+ private static bool IsAwakeActive(AwakeSettings settings)
+ {
+ // Only check if Awake module is enabled
+ return IsAwakeModuleEnabled();
+ }
+
+ private sealed class AwakeStatusPayload
+ {
+ internal string Mode { get; set; } = "unknown";
+
+ internal bool KeepDisplayOn { get; set; }
+
+ internal bool IsProcessBound { get; set; }
+
+ internal int ProcessId { get; set; }
+
+ internal uint IntervalHours { get; set; }
+
+ internal uint IntervalMinutes { get; set; }
+
+ internal string ExpirationDateTime { get; set; } = string.Empty;
+
+ internal string Summary { get; set; } = string.Empty;
+
+ internal bool PowerToysRunning { get; set; }
+
+ internal bool AwakeProcessActive { get; set; }
+
+ internal bool LaunchedViaCli { get; set; }
+
+ internal bool Success { get; set; } = true;
+
+ internal string? ErrorMessage { get; set; }
+
+ internal JsonObject ToJsonObject()
+ {
+ var result = new JsonObject
+ {
+ ["mode"] = Mode,
+ ["keepDisplayOn"] = KeepDisplayOn,
+ ["isProcessBound"] = IsProcessBound,
+ ["processId"] = ProcessId,
+ ["intervalHours"] = IntervalHours,
+ ["intervalMinutes"] = IntervalMinutes,
+ ["expirationDateTime"] = ExpirationDateTime,
+ ["summary"] = Summary,
+ ["powerToysRunning"] = PowerToysRunning,
+ ["awakeProcessActive"] = AwakeProcessActive,
+ ["launchedViaCli"] = LaunchedViaCli,
+ };
+
+ // Add error handling properties
+ if (!Success)
+ {
+ result["success"] = false;
+ if (!string.IsNullOrEmpty(ErrorMessage))
+ {
+ result["error"] = ErrorMessage;
+ }
+ }
+
+ return result;
+ }
+
+ internal static AwakeStatusPayload FromSettings(AwakeSettings settings, string summary)
+ {
+ return new AwakeStatusPayload
+ {
+ Mode = settings.Properties.Mode.ToString().ToLowerInvariant(),
+ KeepDisplayOn = settings.Properties.KeepDisplayOn,
+ IsProcessBound = settings.Properties.ProcessId > 0,
+ ProcessId = (int)settings.Properties.ProcessId,
+ IntervalHours = settings.Properties.IntervalHours,
+ IntervalMinutes = settings.Properties.IntervalMinutes,
+ ExpirationDateTime = settings.Properties.ExpirationDateTime.ToString("O"),
+ Summary = summary,
+ };
+ }
+
+ internal static AwakeStatusPayload CreateInactive()
+ {
+ return new AwakeStatusPayload
+ {
+ Mode = "inactive",
+ KeepDisplayOn = false,
+ IsProcessBound = false,
+ ProcessId = 0,
+ IntervalHours = 0,
+ IntervalMinutes = 0,
+ ExpirationDateTime = string.Empty,
+ Summary = "PowerToys Awake is not running because PowerToys is not active.",
+ PowerToysRunning = false,
+ AwakeProcessActive = false,
+ LaunchedViaCli = false,
+ };
+ }
+
+ internal static AwakeStatusPayload CreateUnknownActive(bool powerToysRunning, bool launchedViaCli)
+ {
+ return new AwakeStatusPayload
+ {
+ Mode = "unknown",
+ KeepDisplayOn = false,
+ IsProcessBound = false,
+ ProcessId = 0,
+ IntervalHours = 0,
+ IntervalMinutes = 0,
+ ExpirationDateTime = string.Empty,
+ Summary = "An Awake process is currently running, but its configuration cannot be determined. To terminate the existing process and start with new settings, use force=true.",
+ PowerToysRunning = powerToysRunning,
+ AwakeProcessActive = true,
+ LaunchedViaCli = launchedViaCli,
+ };
+ }
+
+ internal static AwakeStatusPayload CreateError(string errorMessage, AwakeSettings? settings = null, bool powerToysRunning = false, bool launchedViaCli = false)
+ {
+ var payload = new AwakeStatusPayload
+ {
+ Success = false,
+ ErrorMessage = errorMessage,
+ PowerToysRunning = powerToysRunning,
+ LaunchedViaCli = launchedViaCli,
+ AwakeProcessActive = true,
+ };
+
+ if (settings != null)
+ {
+ payload.Mode = settings.Properties.Mode.ToString().ToLowerInvariant();
+ payload.KeepDisplayOn = settings.Properties.KeepDisplayOn;
+ payload.IsProcessBound = settings.Properties.ProcessId > 0;
+ payload.ProcessId = (int)settings.Properties.ProcessId;
+ payload.IntervalHours = settings.Properties.IntervalHours;
+ payload.IntervalMinutes = settings.Properties.IntervalMinutes;
+ payload.ExpirationDateTime = settings.Properties.ExpirationDateTime.ToString("O");
+ payload.Summary = "An Awake session is already active with the current settings. To override and change the configuration, use force=true.";
+ }
+ else
+ {
+ payload.Mode = "unknown";
+ payload.Summary = "An Awake process is currently running. To terminate the existing process and start with new settings, use force=true.";
+ }
+
+ return payload;
+ }
}
}
}
diff --git a/src/runner/main.cpp b/src/runner/main.cpp
index 85021a71b7..4b29149f78 100644
--- a/src/runner/main.cpp
+++ b/src/runner/main.cpp
@@ -156,7 +156,6 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
L"PowerToys.ShortcutGuideModuleInterface.dll",
L"PowerToys.ColorPicker.dll",
L"PowerToys.AwakeModuleInterface.dll",
- L"PowerToys.McpModuleInterface.dll",
L"PowerToys.FindMyMouse.dll",
L"PowerToys.MouseHighlighter.dll",
L"PowerToys.MouseJump.dll",
diff --git a/src/settings-ui/Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Settings.UI.Library/EnabledModules.cs
index 040ca01c08..977c03b839 100644
--- a/src/settings-ui/Settings.UI.Library/EnabledModules.cs
+++ b/src/settings-ui/Settings.UI.Library/EnabledModules.cs
@@ -530,23 +530,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
}
}
- private bool mcp = true;
-
- [JsonPropertyName("MCP")]
- public bool MCP
- {
- get => mcp;
- set
- {
- if (mcp != value)
- {
- LogTelemetryEvent(value);
- mcp = value;
- NotifyChange();
- }
- }
- }
-
private void NotifyChange()
{
notifyEnabledChangedAction?.Invoke();
diff --git a/src/settings-ui/Settings.UI.Library/McpProperties.cs b/src/settings-ui/Settings.UI.Library/McpProperties.cs
deleted file mode 100644
index 774486c99c..0000000000
--- a/src/settings-ui/Settings.UI.Library/McpProperties.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-
-namespace Microsoft.PowerToys.Settings.UI.Library
-{
- public class McpProperties
- {
- public McpProperties()
- {
- RegisterToVSCode = false;
- RegisterToWindowsCopilot = false;
- EnabledModules = new Dictionary
- {
- { "Awake", true },
- };
- }
-
- [JsonPropertyName("registerToVSCode")]
- public bool RegisterToVSCode { get; set; }
-
- [JsonPropertyName("registerToWindowsCopilot")]
- public bool RegisterToWindowsCopilot { get; set; }
-
- [JsonPropertyName("enabledModules")]
- public Dictionary EnabledModules { get; set; }
- }
-}
diff --git a/src/settings-ui/Settings.UI.Library/McpSettings.cs b/src/settings-ui/Settings.UI.Library/McpSettings.cs
deleted file mode 100644
index 310a0ed660..0000000000
--- a/src/settings-ui/Settings.UI.Library/McpSettings.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Text.Json.Serialization;
-
-using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
-
-namespace Microsoft.PowerToys.Settings.UI.Library
-{
- public class McpSettings : BasePTModuleSettings, ISettingsConfig, ICloneable
- {
- public const string ModuleName = "MCP";
-
- public McpSettings()
- {
- Name = ModuleName;
- Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
- Properties = new McpProperties();
- }
-
- [JsonPropertyName("properties")]
- public McpProperties Properties { get; set; }
-
- public object Clone()
- {
- return new McpSettings()
- {
- Name = Name,
- Version = Version,
- Properties = new McpProperties()
- {
- RegisterToVSCode = Properties.RegisterToVSCode,
- RegisterToWindowsCopilot = Properties.RegisterToWindowsCopilot,
- EnabledModules = Properties.EnabledModules.ToDictionary(entry => entry.Key, entry => entry.Value),
- },
- };
- }
-
- public string GetModuleName()
- {
- return Name;
- }
-
- public bool UpgradeSettingsConfiguration()
- {
- return false;
- }
- }
-}
diff --git a/src/settings-ui/Settings.UI.Library/Utilities/McpRegistrationHelper.cs b/src/settings-ui/Settings.UI.Library/Utilities/McpRegistrationHelper.cs
deleted file mode 100644
index 61e2bfecc0..0000000000
--- a/src/settings-ui/Settings.UI.Library/Utilities/McpRegistrationHelper.cs
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.IO;
-using System.Text.Json;
-using System.Text.Json.Nodes;
-
-namespace Microsoft.PowerToys.Settings.UI.Library.Utilities
-{
- ///
- /// Helper class for registering/unregistering MCP server to VS Code and Windows Copilot.
- ///
- public static class McpRegistrationHelper
- {
- private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
- {
- WriteIndented = true,
- Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
- };
-
- ///
- /// Register or unregister MCP server to VS Code settings.json.
- ///
- /// True to register, false to unregister.
- /// True if successful, false otherwise.
- public static bool UpdateVSCodeRegistration(bool register)
- {
- try
- {
- var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
- var settingsPath = Path.Combine(appData, "Code", "User", "settings.json");
-
- if (!File.Exists(settingsPath))
- {
- // Try VS Code Insiders
- settingsPath = Path.Combine(appData, "Code - Insiders", "User", "settings.json");
- if (!File.Exists(settingsPath))
- {
- // VS Code settings.json not found
- return false;
- }
- }
-
- return UpdateVSCodeSettingsFile(settingsPath, register);
- }
- catch
- {
- return false;
- }
- }
-
- ///
- /// Register or unregister MCP server to Windows Copilot.
- ///
- /// True to register, false to unregister.
- /// True if successful, false otherwise.
- public static bool UpdateWindowsCopilotRegistration(bool register)
- {
- // TODO: Implement Windows Copilot registration when API is available
- _ = register; // Suppress unused parameter warning
- return false;
- }
-
- private static bool UpdateVSCodeSettingsFile(string settingsPath, bool register)
- {
- try
- {
- // Backup original file
- var backupPath = settingsPath + ".bak";
- File.Copy(settingsPath, backupPath, true);
-
- // Read existing settings
- var settingsJson = File.ReadAllText(settingsPath);
- JsonNode rootNode = JsonNode.Parse(settingsJson);
-
- if (rootNode == null || rootNode is not JsonObject rootObject)
- {
- return false;
- }
-
- // Get or create mcp.servers object
- if (!rootObject.ContainsKey("mcp.servers"))
- {
- if (register)
- {
- rootObject["mcp.servers"] = new JsonObject();
- }
- else
- {
- // Nothing to unregister
- return true;
- }
- }
-
- var mcpServers = rootObject["mcp.servers"] as JsonObject;
- if (mcpServers == null)
- {
- return false;
- }
-
- if (register)
- {
- // Get PowerToys installation path
- var exePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PowerToys.McpServer.exe");
- if (!File.Exists(exePath))
- {
- return false;
- }
-
- // Add powertoys server entry
- var serverConfig = new JsonObject
- {
- ["command"] = exePath.Replace("\\", "/"),
- };
- mcpServers["powertoys"] = serverConfig;
- }
- else
- {
- // Remove powertoys server entry
- mcpServers.Remove("powertoys");
-
- // Remove mcp.servers if empty
- if (mcpServers.Count == 0)
- {
- rootObject.Remove("mcp.servers");
- }
- }
-
- // Write updated settings with proper formatting
- var updatedJson = JsonSerializer.Serialize(rootNode, JsonOptions);
- File.WriteAllText(settingsPath, updatedJson);
-
- return true;
- }
- catch
- {
- // Try to restore from backup
- var backupPath = settingsPath + ".bak";
- if (File.Exists(backupPath))
- {
- try
- {
- File.Copy(backupPath, settingsPath, true);
- }
- catch
- {
- // Ignore backup restore errors
- }
- }
-
- return false;
- }
- }
- }
-}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
index 45dace9823..19cd75b022 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml.cs
@@ -415,7 +415,6 @@ namespace Microsoft.PowerToys.Settings.UI
case "AdvancedPaste": return typeof(AdvancedPastePage);
case "AlwaysOnTop": return typeof(AlwaysOnTopPage);
case "Awake": return typeof(AwakePage);
- case "MCP": return typeof(McpPage);
case "CmdNotFound": return typeof(CmdNotFoundPage);
case "ColorPicker": return typeof(ColorPickerPage);
case "LightSwitch": return typeof(LightSwitchPage);
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/McpPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/McpPage.xaml
deleted file mode 100644
index ce2c16189e..0000000000
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/McpPage.xaml
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/McpPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/McpPage.xaml.cs
deleted file mode 100644
index dd3b28cff1..0000000000
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/McpPage.xaml.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using ManagedCommon;
-using Microsoft.PowerToys.Settings.UI.Helpers;
-using Microsoft.PowerToys.Settings.UI.Library;
-using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
-using Microsoft.PowerToys.Settings.UI.Library.Utilities;
-using Microsoft.PowerToys.Settings.UI.ViewModels;
-using Microsoft.UI.Dispatching;
-using PowerToys.GPOWrapper;
-
-namespace Microsoft.PowerToys.Settings.UI.Views
-{
- public sealed partial class McpPage : NavigablePage, IRefreshablePage
- {
- private readonly SettingsUtils _settingsUtils;
-
- private readonly SettingsRepository _generalSettingsRepository;
- private readonly SettingsRepository _moduleSettingsRepository;
-
- private readonly DispatcherQueue _dispatcherQueue;
-
- private readonly Func _sendConfigMsg;
-
- private McpViewModel ViewModel { get; set; }
-
- public McpPage()
- {
- _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
- _settingsUtils = new SettingsUtils();
- _sendConfigMsg = ShellPage.SendDefaultIPCMessage;
-
- ViewModel = new McpViewModel();
- ViewModel.PropertyChanged += ViewModel_PropertyChanged;
-
- _generalSettingsRepository = SettingsRepository.GetInstance(_settingsUtils);
- _moduleSettingsRepository = SettingsRepository.GetInstance(_settingsUtils);
-
- // We load the view model settings first.
- LoadSettings(_generalSettingsRepository, _moduleSettingsRepository);
-
- this.InitializeComponent();
- }
-
- public void RefreshEnabledState()
- {
- ViewModel.RefreshEnabledState();
- }
-
- private void LoadSettings(
- SettingsRepository generalSettingsRepository,
- SettingsRepository moduleSettingsRepository)
- {
- var generalSettings = generalSettingsRepository.SettingsConfig;
- var moduleSettings = moduleSettingsRepository.SettingsConfig;
-
- ViewModel.IsEnabled = generalSettings.Enabled.MCP;
- ViewModel.RegisterToVSCode = moduleSettings.Properties.RegisterToVSCode;
- ViewModel.RegisterToWindowsCopilot = moduleSettings.Properties.RegisterToWindowsCopilot;
- ViewModel.AwakeModuleEnabled = moduleSettings.Properties.EnabledModules.TryGetValue("Awake", out bool awakeEnabled) ? awakeEnabled : true;
-
- // TODO: Uncomment when GPO support is implemented
- // ViewModel.IsEnabledGpoConfigured = GPOWrapper.GetConfiguredMcpEnabledValue() == GpoRuleConfigured.Enabled || GPOWrapper.GetConfiguredMcpEnabledValue() == GpoRuleConfigured.Disabled;
- ViewModel.IsEnabledGpoConfigured = false;
- }
-
- private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
- {
- switch (e.PropertyName)
- {
- case nameof(ViewModel.IsEnabled):
- {
- var generalSettings = _generalSettingsRepository.SettingsConfig;
- generalSettings.Enabled.MCP = ViewModel.IsEnabled;
- OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(generalSettings);
- _sendConfigMsg(outgoing.ToString());
- }
-
- break;
- case nameof(ViewModel.RegisterToVSCode):
- {
- // Update VS Code registration
- bool success = McpRegistrationHelper.UpdateVSCodeRegistration(ViewModel.RegisterToVSCode);
- if (!success && ViewModel.RegisterToVSCode)
- {
- // If registration failed, revert the toggle
- ViewModel.RegisterToVSCode = false;
- }
-
- var moduleSettings = _moduleSettingsRepository.SettingsConfig;
- moduleSettings.Properties.RegisterToVSCode = ViewModel.RegisterToVSCode;
- _settingsUtils.SaveSettings(moduleSettings.ToJsonString(), McpSettings.ModuleName);
- }
-
- break;
- case nameof(ViewModel.RegisterToWindowsCopilot):
- {
- // Update Windows Copilot registration
- bool success = McpRegistrationHelper.UpdateWindowsCopilotRegistration(ViewModel.RegisterToWindowsCopilot);
- if (!success && ViewModel.RegisterToWindowsCopilot)
- {
- // If registration failed, revert the toggle
- ViewModel.RegisterToWindowsCopilot = false;
- }
-
- var moduleSettings = _moduleSettingsRepository.SettingsConfig;
- moduleSettings.Properties.RegisterToWindowsCopilot = ViewModel.RegisterToWindowsCopilot;
- _settingsUtils.SaveSettings(moduleSettings.ToJsonString(), McpSettings.ModuleName);
- }
-
- break;
- case nameof(ViewModel.AwakeModuleEnabled):
- {
- var moduleSettings = _moduleSettingsRepository.SettingsConfig;
- moduleSettings.Properties.EnabledModules["Awake"] = ViewModel.AwakeModuleEnabled;
- _settingsUtils.SaveSettings(moduleSettings.ToJsonString(), McpSettings.ModuleName);
- }
-
- break;
- }
- }
- }
-}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
index d222a1751d..c2de045ed2 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml
@@ -394,12 +394,6 @@
helpers:NavHelper.NavigateTo="views:HostsPage"
AutomationProperties.AutomationId="HostsNavItem"
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Hosts.png}" />
-
Register to VS Code
- Automatically register MCP server to VS Code settings.json
+ Add the PowerToys MCP server to VS Code. Remove it manually from VS Code when you no longer need it.
+
+
+ PowerToys couldn't update VS Code
+
+
+ PowerToys couldn't register the MCP server with VS Code automatically. Run {0} in a PowerShell window or add the server manually from VS Code's Model Context Protocol settings.
+ {0} is a command that can be pasted into a terminal.
+
+
+ Got it
+
+
+ Registration added
+
+
+ PowerToys added the MCP server to VS Code. To remove it later, open VS Code, run "Copilot: Manage MCP Servers", and delete "powertoys-mcp".
+
+
+ VS Code's command-line interface isn't available. Install or update VS Code, enable the "code" command, and try again.
+
+
+ Add to VS Code
Register to Windows Copilot
diff --git a/src/settings-ui/Settings.UI/ViewModels/McpViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/McpViewModel.cs
deleted file mode 100644
index 5ee07320ce..0000000000
--- a/src/settings-ui/Settings.UI/ViewModels/McpViewModel.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.ComponentModel;
-using System.Runtime.CompilerServices;
-using ManagedCommon;
-using Microsoft.PowerToys.Settings.UI.Library;
-using Microsoft.PowerToys.Settings.UI.Library.Helpers;
-
-namespace Microsoft.PowerToys.Settings.UI.ViewModels
-{
- public class McpViewModel : Observable
- {
- private bool _isEnabled;
- private bool _registerToVSCode;
- private bool _registerToWindowsCopilot;
- private bool _awakeModuleEnabled;
- private bool _enabledStateIsGPOConfigured;
- private bool _enabledGPOConfiguration;
-
- public McpViewModel()
- {
- _isEnabled = false;
- _registerToVSCode = false;
- _registerToWindowsCopilot = false;
- _awakeModuleEnabled = true;
- }
-
- public bool IsEnabled
- {
- get
- {
- if (_enabledStateIsGPOConfigured)
- {
- return _enabledGPOConfiguration;
- }
- else
- {
- return _isEnabled;
- }
- }
-
- set
- {
- if (_isEnabled != value)
- {
- if (_enabledStateIsGPOConfigured)
- {
- // If it's GPO configured, shouldn't be able to change this state.
- return;
- }
-
- _isEnabled = value;
- RefreshEnabledState();
- NotifyPropertyChanged();
- }
- }
- }
-
- public bool IsEnabledGpoConfigured
- {
- get => _enabledStateIsGPOConfigured;
- set
- {
- if (_enabledStateIsGPOConfigured != value)
- {
- _enabledStateIsGPOConfigured = value;
- NotifyPropertyChanged();
- }
- }
- }
-
- public bool EnabledGPOConfiguration
- {
- get => _enabledGPOConfiguration;
- set
- {
- if (_enabledGPOConfiguration != value)
- {
- _enabledGPOConfiguration = value;
- NotifyPropertyChanged();
- }
- }
- }
-
- public bool RegisterToVSCode
- {
- get => _registerToVSCode;
- set
- {
- if (_registerToVSCode != value)
- {
- _registerToVSCode = value;
- NotifyPropertyChanged();
- }
- }
- }
-
- public bool RegisterToWindowsCopilot
- {
- get => _registerToWindowsCopilot;
- set
- {
- if (_registerToWindowsCopilot != value)
- {
- _registerToWindowsCopilot = value;
- NotifyPropertyChanged();
- }
- }
- }
-
- public bool AwakeModuleEnabled
- {
- get => _awakeModuleEnabled;
- set
- {
- if (_awakeModuleEnabled != value)
- {
- _awakeModuleEnabled = value;
- NotifyPropertyChanged();
- }
- }
- }
-
- public void RefreshEnabledState()
- {
- OnPropertyChanged(nameof(IsEnabled));
- }
-
- protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
- {
- OnPropertyChanged(propertyName);
- }
- }
-}