diff --git a/Directory.Packages.props b/Directory.Packages.props
index 649c927a64..e44d6e9c9e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -61,6 +61,7 @@
+
diff --git a/PowerToys.sln b/PowerToys.sln
index c2f96095ac..fa372d0495 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -805,6 +805,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WebSea
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Shell.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Shell.UnitTests\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj", "{E816D7B4-4688-4ECB-97CC-3D8E798F3833}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MCPServer", "MCPServer", "{B637E6DD-FB81-4595-BB9C-01168556EA9E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCPServer", "src\modules\MCPServer\MCPServer\MCPServer.csproj", "{20CBF173-9E8D-3236-6664-5B9C303794A3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -2923,6 +2927,14 @@ Global
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|ARM64.Build.0 = Release|ARM64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.ActiveCfg = Release|x64
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.Build.0 = Release|x64
+ {20CBF173-9E8D-3236-6664-5B9C303794A3}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {20CBF173-9E8D-3236-6664-5B9C303794A3}.Debug|ARM64.Build.0 = Debug|ARM64
+ {20CBF173-9E8D-3236-6664-5B9C303794A3}.Debug|x64.ActiveCfg = Debug|x64
+ {20CBF173-9E8D-3236-6664-5B9C303794A3}.Debug|x64.Build.0 = Debug|x64
+ {20CBF173-9E8D-3236-6664-5B9C303794A3}.Release|ARM64.ActiveCfg = Release|ARM64
+ {20CBF173-9E8D-3236-6664-5B9C303794A3}.Release|ARM64.Build.0 = Release|ARM64
+ {20CBF173-9E8D-3236-6664-5B9C303794A3}.Release|x64.ActiveCfg = Release|x64
+ {20CBF173-9E8D-3236-6664-5B9C303794A3}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3243,6 +3255,8 @@ Global
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{E816D7B4-4688-4ECB-97CC-3D8E798F3833} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
+ {B637E6DD-FB81-4595-BB9C-01168556EA9E} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
+ {20CBF173-9E8D-3236-6664-5B9C303794A3} = {B637E6DD-FB81-4595-BB9C-01168556EA9E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
diff --git a/src/modules/MCPServer/MCPServer/MCPServer.csproj b/src/modules/MCPServer/MCPServer/MCPServer.csproj
new file mode 100644
index 0000000000..7f9a808ec4
--- /dev/null
+++ b/src/modules/MCPServer/MCPServer/MCPServer.csproj
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+ WinExe
+ false
+ false
+ false
+ PowerToys.MCPServer
+ PowerToys MCP Server for Model Context Protocol
+ true
+ ..\..\..\..\$(Platform)\$(Configuration)
+ false
+ true
+ PowerToys.MCPServer
+ enable
+
+
+
+
+ PowerToys.GPOWrapper
+ $(OutDir)
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
\ No newline at end of file
diff --git a/src/modules/MCPServer/MCPServer/Program.cs b/src/modules/MCPServer/MCPServer/Program.cs
new file mode 100644
index 0000000000..6886c73fb2
--- /dev/null
+++ b/src/modules/MCPServer/MCPServer/Program.cs
@@ -0,0 +1,34 @@
+// 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.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using ModelContextProtocol.Server;
+using PowerToys.MCPServer.Tools;
+
+namespace MCPServer
+{
+ internal sealed class Program
+ {
+ private static async Task Main(string[] args)
+ {
+ var builder = Host.CreateApplicationBuilder(args);
+ builder.Logging.AddConsole(consoleLogOptions =>
+ {
+ // Configure all logs to go to stderr
+ consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
+ });
+ builder.Services
+ .AddMcpServer()
+ .WithStdioServerTransport()
+ .WithToolsFromAssembly();
+ await builder.Build().RunAsync();
+
+ return 0;
+ }
+ }
+}
diff --git a/src/modules/MCPServer/MCPServer/Tools/AwakeTools.cs b/src/modules/MCPServer/MCPServer/Tools/AwakeTools.cs
new file mode 100644
index 0000000000..b6bff69e4b
--- /dev/null
+++ b/src/modules/MCPServer/MCPServer/Tools/AwakeTools.cs
@@ -0,0 +1,17 @@
+// 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 ModelContextProtocol.Server;
+
+namespace PowerToys.MCPServer.Tools
+{
+ [McpServerToolType]
+ public static class AwakeTools
+ {
+ [McpServerTool]
+ [Description("Echoes the message back to the client.")]
+ public static string SetTimeTest(string message) => $"Hello {message}";
+ }
+}
diff --git a/src/modules/MCPServer/MCPServer/Tools/EchoTool.cs b/src/modules/MCPServer/MCPServer/Tools/EchoTool.cs
new file mode 100644
index 0000000000..f68a18e4ae
--- /dev/null
+++ b/src/modules/MCPServer/MCPServer/Tools/EchoTool.cs
@@ -0,0 +1,17 @@
+// 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 ModelContextProtocol.Server;
+
+namespace PowerToys.MCPServer.Tools
+{
+ [McpServerToolType]
+ public static class EchoTool
+ {
+ [McpServerTool]
+ [Description("Echoes the message back to the client.")]
+ public static string Echo(string message) => $"Hello {message}";
+ }
+}
diff --git a/src/modules/MCPServer/MCPServer/appsettings.json b/src/modules/MCPServer/MCPServer/appsettings.json
new file mode 100644
index 0000000000..9c71e05cb6
--- /dev/null
+++ b/src/modules/MCPServer/MCPServer/appsettings.json
@@ -0,0 +1,18 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information",
+ "PowerToys.MCPServer": "Debug"
+ }
+ },
+ "MCPServer": {
+ "Port": 8080,
+ "MaxConcurrentConnections": 100,
+ "RequestTimeoutSeconds": 30,
+ "EnableTools": true,
+ "EnableResources": true,
+ "Transport": "http"
+ }
+}
\ No newline at end of file
diff --git a/src/modules/MCPServer/MCPServerModuleInterface/MCPServerModuleInterface.def b/src/modules/MCPServer/MCPServerModuleInterface/MCPServerModuleInterface.def
new file mode 100644
index 0000000000..6421b71234
--- /dev/null
+++ b/src/modules/MCPServer/MCPServerModuleInterface/MCPServerModuleInterface.def
@@ -0,0 +1,2 @@
+EXPORTS
+powertoy_create
\ No newline at end of file
diff --git a/src/modules/MCPServer/MCPServerModuleInterface/MCPServerModuleInterface.vcxproj b/src/modules/MCPServer/MCPServerModuleInterface/MCPServerModuleInterface.vcxproj
new file mode 100644
index 0000000000..6318dc7fec
--- /dev/null
+++ b/src/modules/MCPServer/MCPServerModuleInterface/MCPServerModuleInterface.vcxproj
@@ -0,0 +1,42 @@
+
+
+
+
+ 16.0
+ {A8B8D654-8F2A-4E6C-9B4F-1234567890AB}
+ Win32Proj
+ MCPServerModuleInterface
+ 10.0
+
+
+ DynamicLibrary
+
+
+
+ Use
+ pch.h
+
+
+ Windows
+ MCPServerModuleInterface.def
+
+
+
+
+
+ Create
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/MCPServer/MCPServerModuleInterface/dllmain.cpp b/src/modules/MCPServer/MCPServerModuleInterface/dllmain.cpp
new file mode 100644
index 0000000000..fb9c1678e3
--- /dev/null
+++ b/src/modules/MCPServer/MCPServerModuleInterface/dllmain.cpp
@@ -0,0 +1,298 @@
+#include "pch.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace NonLocalizable
+{
+ const wchar_t ModulePath[] = L"PowerToys.MCPServer.exe";
+ const wchar_t ModuleKey[] = L"MCPServer";
+}
+
+BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
+{
+ switch (ul_reason_for_call)
+ {
+ case DLL_PROCESS_ATTACH:
+ break;
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ case DLL_PROCESS_DETACH:
+ break;
+ }
+ return TRUE;
+}
+
+class MCPServerModuleInterface : public PowertoyModuleIface
+{
+public:
+ virtual PCWSTR get_name() override
+ {
+ return app_name.c_str();
+ }
+
+ virtual const wchar_t* get_key() override
+ {
+ return app_key.c_str();
+ }
+
+ virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
+ {
+ return powertoys_gpo::gpo_rule_configured_t::gpo_rule_configured_not_configured;
+ }
+
+ 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(L"MCP Server provides Model Context Protocol access to PowerToys functionality for AI assistants and tools");
+ settings.set_icon_key(L"pt-mcp-server");
+
+ // Port configuration
+ settings.add_int_spinner(
+ L"port",
+ L"Server Port",
+ m_port,
+ 1024,
+ 65535,
+ 1);
+
+ // Auto start option
+ settings.add_bool_toggle(
+ L"auto_start",
+ L"Auto Start Server",
+ m_auto_start);
+
+ // Enable tools API
+ settings.add_bool_toggle(
+ L"enable_tools",
+ L"Enable Tools API",
+ m_enable_tools);
+
+ // Enable resources API
+ settings.add_bool_toggle(
+ L"enable_resources",
+ L"Enable Resources API",
+ m_enable_resources);
+
+ // Transport protocol
+ settings.add_dropdown(
+ L"transport",
+ L"Transport Protocol",
+ m_transport,
+ std::vector>{
+ { L"http", L"HTTP" },
+ { L"stdio", L"Standard I/O" },
+ { L"tcp", L"TCP Socket" }
+ });
+
+ return settings.serialize_to_buffer(buffer, buffer_size);
+ }
+
+ virtual void set_config(const wchar_t* config) override
+ {
+ try
+ {
+ PowerToysSettings::PowerToyValues values =
+ PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
+
+ if (auto port = values.get_int_value(L"port"))
+ {
+ m_port = port.value();
+ }
+
+ if (auto auto_start = values.get_bool_value(L"auto_start"))
+ {
+ m_auto_start = auto_start.value();
+ }
+
+ if (auto enable_tools = values.get_bool_value(L"enable_tools"))
+ {
+ m_enable_tools = enable_tools.value();
+ }
+
+ if (auto enable_resources = values.get_bool_value(L"enable_resources"))
+ {
+ m_enable_resources = enable_resources.value();
+ }
+
+ if (auto transport = values.get_string_value(L"transport"))
+ {
+ m_transport = transport.value();
+ }
+
+ values.save_to_settings_file();
+
+ // If service is running, restart to apply new configuration
+ if (m_enabled && is_process_running())
+ {
+ StopMCPServer();
+ StartMCPServer();
+ }
+ }
+ catch (std::exception& e)
+ {
+ Logger::error("MCPServer configuration parsing failed: {}", std::string{ e.what() });
+ }
+ }
+
+ virtual void enable() override
+ {
+ Logger::info("MCPServer enabling");
+ m_enabled = true;
+ if (m_auto_start)
+ {
+ StartMCPServer();
+ }
+ }
+
+ virtual void disable() override
+ {
+ Logger::info("MCPServer disabling");
+ m_enabled = false;
+ StopMCPServer();
+ }
+
+ virtual bool is_enabled() override
+ {
+ return m_enabled;
+ }
+
+ virtual void destroy() override
+ {
+ StopMCPServer();
+ delete this;
+ }
+
+ MCPServerModuleInterface()
+ {
+ app_name = L"MCP Server";
+ app_key = NonLocalizable::ModuleKey;
+ m_port = 8080;
+ m_auto_start = true;
+ m_enable_tools = true;
+ m_enable_resources = true;
+ m_transport = L"http";
+ init_settings();
+ }
+
+private:
+ void StartMCPServer()
+ {
+ if (m_hProcess && WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT)
+ {
+ return; // Already running
+ }
+
+ std::wstring executable_args = L"--port=" + std::to_wstring(m_port);
+
+ if (!m_enable_tools)
+ {
+ executable_args += L" --disable-tools";
+ }
+
+ if (!m_enable_resources)
+ {
+ executable_args += L" --disable-resources";
+ }
+
+ if (!m_transport.empty())
+ {
+ executable_args += L" --transport=" + m_transport;
+ }
+
+ SHELLEXECUTEINFOW sei{ sizeof(sei) };
+ sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
+ sei.lpFile = NonLocalizable::ModulePath;
+ sei.nShow = SW_HIDE;
+ sei.lpParameters = executable_args.data();
+
+ if (ShellExecuteExW(&sei))
+ {
+ m_hProcess = sei.hProcess;
+ Logger::info("MCPServer started successfully on port {} with transport {}", m_port, std::string(m_transport.begin(), m_transport.end()));
+ }
+ else
+ {
+ Logger::error("Failed to start MCPServer");
+ auto message = get_last_error_message(GetLastError());
+ if (message.has_value())
+ {
+ Logger::error(message.value());
+ }
+ }
+ }
+
+ void StopMCPServer()
+ {
+ if (m_hProcess)
+ {
+ TerminateProcess(m_hProcess, 0);
+ CloseHandle(m_hProcess);
+ m_hProcess = nullptr;
+ Logger::info("MCPServer stopped");
+ }
+ }
+
+ bool is_process_running()
+ {
+ return m_hProcess && WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
+ }
+
+ void init_settings()
+ {
+ try
+ {
+ PowerToysSettings::PowerToyValues settings =
+ PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
+
+ if (auto port = settings.get_int_value(L"port"))
+ {
+ m_port = port.value();
+ }
+
+ if (auto auto_start = settings.get_bool_value(L"auto_start"))
+ {
+ m_auto_start = auto_start.value();
+ }
+
+ if (auto enable_tools = settings.get_bool_value(L"enable_tools"))
+ {
+ m_enable_tools = enable_tools.value();
+ }
+
+ if (auto enable_resources = settings.get_bool_value(L"enable_resources"))
+ {
+ m_enable_resources = enable_resources.value();
+ }
+
+ if (auto transport = settings.get_string_value(L"transport"))
+ {
+ m_transport = transport.value();
+ }
+ }
+ catch (std::exception&)
+ {
+ Logger::warn(L"MCPServer settings file not found, using defaults");
+ }
+ }
+
+ std::wstring app_name;
+ std::wstring app_key;
+ bool m_enabled = false;
+ HANDLE m_hProcess = nullptr;
+ int m_port = 8080;
+ bool m_auto_start = true;
+ bool m_enable_tools = true;
+ bool m_enable_resources = true;
+ std::wstring m_transport = L"http";
+};
+
+extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
+{
+ return new MCPServerModuleInterface();
+}
\ No newline at end of file
diff --git a/src/modules/MCPServer/MCPServerModuleInterface/pch.cpp b/src/modules/MCPServer/MCPServerModuleInterface/pch.cpp
new file mode 100644
index 0000000000..17305716aa
--- /dev/null
+++ b/src/modules/MCPServer/MCPServerModuleInterface/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
\ No newline at end of file
diff --git a/src/modules/MCPServer/MCPServerModuleInterface/pch.h b/src/modules/MCPServer/MCPServerModuleInterface/pch.h
new file mode 100644
index 0000000000..09a424638d
--- /dev/null
+++ b/src/modules/MCPServer/MCPServerModuleInterface/pch.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "targetver.h"
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
\ No newline at end of file
diff --git a/src/modules/MCPServer/MCPServerModuleInterface/targetver.h b/src/modules/MCPServer/MCPServerModuleInterface/targetver.h
new file mode 100644
index 0000000000..5b1f29cad0
--- /dev/null
+++ b/src/modules/MCPServer/MCPServerModuleInterface/targetver.h
@@ -0,0 +1,8 @@
+#pragma once
+
+// Including SDKDDKVer.h defines the highest available Windows platform.
+
+// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
+// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
+
+#include
\ No newline at end of file