@mokoconsulting/mcp-windows 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/.gitattributes +94 -0
  2. package/.gitmessage +9 -0
  3. package/.mokogitea/ISSUE_TEMPLATE/adr.md +110 -0
  4. package/.mokogitea/ISSUE_TEMPLATE/bug_report.md +48 -0
  5. package/.mokogitea/ISSUE_TEMPLATE/config.yml +18 -0
  6. package/.mokogitea/ISSUE_TEMPLATE/documentation.md +52 -0
  7. package/.mokogitea/ISSUE_TEMPLATE/feature_request.md +51 -0
  8. package/.mokogitea/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
  9. package/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +67 -0
  10. package/.mokogitea/ISSUE_TEMPLATE/mcp_tool_request.md +49 -0
  11. package/.mokogitea/ISSUE_TEMPLATE/question.md +82 -0
  12. package/.mokogitea/ISSUE_TEMPLATE/rfc.md +126 -0
  13. package/.mokogitea/ISSUE_TEMPLATE/security.md +51 -0
  14. package/.mokogitea/ISSUE_TEMPLATE/version.md +24 -0
  15. package/.mokogitea/branch-protection.yml +251 -0
  16. package/.mokogitea/workflows/auto-assign.yml +76 -0
  17. package/.mokogitea/workflows/auto-bump.yml +66 -0
  18. package/.mokogitea/workflows/auto-dev-issue.yml +207 -0
  19. package/.mokogitea/workflows/auto-release.yml +421 -0
  20. package/.mokogitea/workflows/branch-cleanup.yml +48 -0
  21. package/.mokogitea/workflows/cascade-dev.yml +10 -0
  22. package/.mokogitea/workflows/changelog-validation.yml +101 -0
  23. package/.mokogitea/workflows/ci-generic.yml +191 -0
  24. package/.mokogitea/workflows/cleanup.yml +87 -0
  25. package/.mokogitea/workflows/codeql-analysis.yml +115 -0
  26. package/.mokogitea/workflows/copilot-agent.yml +44 -0
  27. package/.mokogitea/workflows/deploy-manual.yml +126 -0
  28. package/.mokogitea/workflows/enterprise-firewall-setup.yml +758 -0
  29. package/.mokogitea/workflows/gitleaks.yml +92 -0
  30. package/.mokogitea/workflows/issue-branch.yml +73 -0
  31. package/.mokogitea/workflows/mcp-auto-release.yml +278 -0
  32. package/.mokogitea/workflows/mcp-build-test.yml +65 -0
  33. package/.mokogitea/workflows/mcp-sdk-check.yml +109 -0
  34. package/.mokogitea/workflows/mcp-tool-inventory.yml +61 -0
  35. package/.mokogitea/workflows/notify.yml +70 -0
  36. package/.mokogitea/workflows/npm-publish.yml +113 -0
  37. package/.mokogitea/workflows/pr-check.yml +534 -0
  38. package/.mokogitea/workflows/pre-release.yml +252 -0
  39. package/.mokogitea/workflows/rc-revert.yml +66 -0
  40. package/.mokogitea/workflows/repo-health.yml +712 -0
  41. package/.mokogitea/workflows/repository-cleanup.yml +525 -0
  42. package/.mokogitea/workflows/security-audit.yml +82 -0
  43. package/.mokogitea/workflows/standards-compliance.yml +2614 -0
  44. package/.mokogitea/workflows/sync-version-on-merge.yml +133 -0
  45. package/.mokogitea/workflows/update-server.yml +312 -0
  46. package/.mokogitea/workflows/workflow-sync-trigger.yml +73 -0
  47. package/CHANGELOG.md +130 -0
  48. package/CLAUDE.md +49 -0
  49. package/CONTRIBUTING.md +161 -0
  50. package/ISSUES.md +601 -0
  51. package/Makefile +70 -0
  52. package/README.md +80 -0
  53. package/automation/ci-issue-reporter.sh +237 -0
  54. package/config.example.json +18 -0
  55. package/dist/index.d.ts +3 -0
  56. package/dist/index.js +111 -0
  57. package/dist/shell.d.ts +50 -0
  58. package/dist/shell.js +209 -0
  59. package/dist/tools/apps.d.ts +3 -0
  60. package/dist/tools/apps.js +63 -0
  61. package/dist/tools/audio.d.ts +3 -0
  62. package/dist/tools/audio.js +142 -0
  63. package/dist/tools/audio_apps.d.ts +3 -0
  64. package/dist/tools/audio_apps.js +86 -0
  65. package/dist/tools/automation.d.ts +3 -0
  66. package/dist/tools/automation.js +261 -0
  67. package/dist/tools/bluetooth.d.ts +3 -0
  68. package/dist/tools/bluetooth.js +96 -0
  69. package/dist/tools/clipboard.d.ts +3 -0
  70. package/dist/tools/clipboard.js +118 -0
  71. package/dist/tools/config.d.ts +3 -0
  72. package/dist/tools/config.js +85 -0
  73. package/dist/tools/dialog.d.ts +3 -0
  74. package/dist/tools/dialog.js +72 -0
  75. package/dist/tools/display.d.ts +3 -0
  76. package/dist/tools/display.js +256 -0
  77. package/dist/tools/drives.d.ts +3 -0
  78. package/dist/tools/drives.js +98 -0
  79. package/dist/tools/environment.d.ts +3 -0
  80. package/dist/tools/environment.js +129 -0
  81. package/dist/tools/execute.d.ts +3 -0
  82. package/dist/tools/execute.js +28 -0
  83. package/dist/tools/filesystem.d.ts +3 -0
  84. package/dist/tools/filesystem.js +230 -0
  85. package/dist/tools/firewall.d.ts +3 -0
  86. package/dist/tools/firewall.js +108 -0
  87. package/dist/tools/hosts.d.ts +3 -0
  88. package/dist/tools/hosts.js +119 -0
  89. package/dist/tools/maintenance.d.ts +3 -0
  90. package/dist/tools/maintenance.js +236 -0
  91. package/dist/tools/netstat.d.ts +3 -0
  92. package/dist/tools/netstat.js +56 -0
  93. package/dist/tools/network.d.ts +3 -0
  94. package/dist/tools/network.js +70 -0
  95. package/dist/tools/notification.d.ts +3 -0
  96. package/dist/tools/notification.js +41 -0
  97. package/dist/tools/power.d.ts +3 -0
  98. package/dist/tools/power.js +104 -0
  99. package/dist/tools/printer.d.ts +3 -0
  100. package/dist/tools/printer.js +97 -0
  101. package/dist/tools/process.d.ts +3 -0
  102. package/dist/tools/process.js +54 -0
  103. package/dist/tools/process_kill.d.ts +3 -0
  104. package/dist/tools/process_kill.js +48 -0
  105. package/dist/tools/recycle_bin.d.ts +3 -0
  106. package/dist/tools/recycle_bin.js +108 -0
  107. package/dist/tools/registry.d.ts +3 -0
  108. package/dist/tools/registry.js +136 -0
  109. package/dist/tools/scheduler.d.ts +3 -0
  110. package/dist/tools/scheduler.js +116 -0
  111. package/dist/tools/service.d.ts +3 -0
  112. package/dist/tools/service.js +79 -0
  113. package/dist/tools/startup.d.ts +3 -0
  114. package/dist/tools/startup.js +159 -0
  115. package/dist/tools/storage.d.ts +3 -0
  116. package/dist/tools/storage.js +129 -0
  117. package/dist/tools/system.d.ts +3 -0
  118. package/dist/tools/system.js +84 -0
  119. package/dist/tools/system_mgmt.d.ts +3 -0
  120. package/dist/tools/system_mgmt.js +174 -0
  121. package/dist/tools/terminal.d.ts +3 -0
  122. package/dist/tools/terminal.js +80 -0
  123. package/dist/tools/theme.d.ts +3 -0
  124. package/dist/tools/theme.js +165 -0
  125. package/dist/tools/usb.d.ts +3 -0
  126. package/dist/tools/usb.js +52 -0
  127. package/dist/tools/virtual_desktop.d.ts +3 -0
  128. package/dist/tools/virtual_desktop.js +112 -0
  129. package/dist/tools/wifi.d.ts +3 -0
  130. package/dist/tools/wifi.js +136 -0
  131. package/dist/tools/window.d.ts +3 -0
  132. package/dist/tools/window.js +189 -0
  133. package/dist/tools/winget.d.ts +3 -0
  134. package/dist/tools/winget.js +79 -0
  135. package/dist/tools/wsl.d.ts +3 -0
  136. package/dist/tools/wsl.js +99 -0
  137. package/docs/API.md +63 -0
  138. package/docs/ARCHITECTURE.md +73 -0
  139. package/docs/INSTALLATION.md +102 -0
  140. package/docs/index.md +12 -0
  141. package/package.json +35 -0
  142. package/scripts/setup.mjs +123 -0
  143. package/src/index.ts +125 -0
  144. package/src/shell.ts +253 -0
  145. package/src/tools/apps.ts +76 -0
  146. package/src/tools/audio.ts +161 -0
  147. package/src/tools/audio_apps.ts +98 -0
  148. package/src/tools/automation.ts +297 -0
  149. package/src/tools/bluetooth.ts +114 -0
  150. package/src/tools/clipboard.ts +138 -0
  151. package/src/tools/config.ts +105 -0
  152. package/src/tools/dialog.ts +87 -0
  153. package/src/tools/display.ts +285 -0
  154. package/src/tools/drives.ts +124 -0
  155. package/src/tools/environment.ts +146 -0
  156. package/src/tools/execute.ts +35 -0
  157. package/src/tools/filesystem.ts +273 -0
  158. package/src/tools/firewall.ts +125 -0
  159. package/src/tools/hosts.ts +135 -0
  160. package/src/tools/maintenance.ts +299 -0
  161. package/src/tools/netstat.ts +72 -0
  162. package/src/tools/network.ts +84 -0
  163. package/src/tools/notification.ts +50 -0
  164. package/src/tools/power.ts +123 -0
  165. package/src/tools/printer.ts +114 -0
  166. package/src/tools/process.ts +80 -0
  167. package/src/tools/process_kill.ts +57 -0
  168. package/src/tools/recycle_bin.ts +126 -0
  169. package/src/tools/registry.ts +165 -0
  170. package/src/tools/scheduler.ts +140 -0
  171. package/src/tools/service.ts +102 -0
  172. package/src/tools/startup.ts +180 -0
  173. package/src/tools/storage.ts +141 -0
  174. package/src/tools/system.ts +99 -0
  175. package/src/tools/system_mgmt.ts +190 -0
  176. package/src/tools/terminal.ts +117 -0
  177. package/src/tools/theme.ts +205 -0
  178. package/src/tools/usb.ts +65 -0
  179. package/src/tools/virtual_desktop.ts +122 -0
  180. package/src/tools/wifi.ts +157 -0
  181. package/src/tools/window.ts +211 -0
  182. package/src/tools/winget.ts +100 -0
  183. package/src/tools/wsl.ts +112 -0
  184. package/tsconfig.json +19 -0
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
3
+ *
4
+ * This file is part of a Moko Consulting project.
5
+ *
6
+ * SPDX-License-Identifier: GPL-3.0-or-later
7
+ *
8
+ * FILE INFORMATION
9
+ * DEFGROUP: mcp_windows.Scripts
10
+ * INGROUP: mcp_windows
11
+ * REPO: https://git.mokoconsulting.tech/MokoConsulting/mcp_windows
12
+ * PATH: /scripts/setup.mjs
13
+ * VERSION: 01.00.00
14
+ * BRIEF: Interactive setup — prompts for API connection details and writes config
15
+ */
16
+
17
+ import { createInterface } from 'node:readline/promises';
18
+ import { readFile, writeFile } from 'node:fs/promises';
19
+ import { resolve } from 'node:path';
20
+ import { homedir } from 'node:os';
21
+
22
+ // ── Customize these ─────────────────────────────────────────────────────
23
+ const PROJECT_NAME = 'mcp_windows';
24
+ const CONFIG_PATH = resolve(homedir(), `.${PROJECT_NAME}.json`);
25
+ const API_LABEL = 'Windows Desktop'; // e.g. "Dolibarr", "Joomla"
26
+ const AUTH_FIELD = 'apiKey'; // field name in config
27
+ const AUTH_PROMPT = 'API key'; // what to ask the user for
28
+ // ────────────────────────────────────────────────────────────────────────
29
+
30
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
31
+
32
+ async function prompt(question, defaultValue) {
33
+ const suffix = defaultValue ? ` [${defaultValue}]` : '';
34
+ const answer = await rl.question(`${question}${suffix}: `);
35
+ return answer.trim() || defaultValue || '';
36
+ }
37
+
38
+ async function promptRequired(question) {
39
+ let answer = '';
40
+ while (!answer) {
41
+ answer = (await rl.question(`${question}: `)).trim();
42
+ if (!answer) {
43
+ console.log(' This field is required.');
44
+ }
45
+ }
46
+ return answer;
47
+ }
48
+
49
+ async function main() {
50
+ console.log('');
51
+ console.log(`=== ${PROJECT_NAME} Setup ===`);
52
+ console.log('');
53
+ console.log('This will create your configuration file at:');
54
+ console.log(` ${CONFIG_PATH}`);
55
+ console.log('');
56
+
57
+ let existing = null;
58
+ try {
59
+ const raw = await readFile(CONFIG_PATH, 'utf-8');
60
+ existing = JSON.parse(raw);
61
+ console.log('Existing config found. You can add a new connection or overwrite.');
62
+ console.log(` Current connections: ${Object.keys(existing.connections).join(', ')}`);
63
+ console.log('');
64
+ } catch {
65
+ // No existing config
66
+ }
67
+
68
+ const connectionName = await prompt('Connection name', 'production');
69
+ const baseUrl = await promptRequired(`${API_LABEL} URL (e.g. https://api.example.com)`);
70
+ const authValue = await promptRequired(`${API_LABEL} ${AUTH_PROMPT}`);
71
+
72
+ const cleanUrl = baseUrl.replace(/\/+$/, '');
73
+
74
+ const insecureAnswer = await prompt('Skip TLS verification for self-signed certs? (y/N)', 'N');
75
+ const insecure = insecureAnswer.toLowerCase() === 'y';
76
+
77
+ const connection = { baseUrl: cleanUrl, [AUTH_FIELD]: authValue };
78
+ if (insecure) {
79
+ connection.insecure = true;
80
+ }
81
+
82
+ let config;
83
+ if (existing) {
84
+ config = existing;
85
+ config.connections[connectionName] = connection;
86
+ const setDefault = await prompt(`Set "${connectionName}" as default connection? (y/N)`, 'N');
87
+ if (setDefault.toLowerCase() === 'y') {
88
+ config.defaultConnection = connectionName;
89
+ }
90
+ } else {
91
+ config = {
92
+ defaultConnection: connectionName,
93
+ connections: {
94
+ [connectionName]: connection,
95
+ },
96
+ };
97
+ }
98
+
99
+ await writeFile(CONFIG_PATH, JSON.stringify(config, null, '\t') + '\n', 'utf-8');
100
+
101
+ console.log('');
102
+ console.log(`Config written to ${CONFIG_PATH}`);
103
+ console.log(` Connection "${connectionName}" configured for ${cleanUrl}`);
104
+ console.log('');
105
+
106
+ const addAnother = await prompt('Add another connection? (y/N)', 'N');
107
+ if (addAnother.toLowerCase() === 'y') {
108
+ rl.close();
109
+ const { execFileSync } = await import('node:child_process');
110
+ execFileSync('node', [new URL(import.meta.url).pathname], { stdio: 'inherit' });
111
+ return;
112
+ }
113
+
114
+ console.log('Setup complete. You can now use the MCP server.');
115
+ console.log('');
116
+ rl.close();
117
+ }
118
+
119
+ main().catch((err) => {
120
+ console.error(`Setup failed: ${err.message}`);
121
+ rl.close();
122
+ process.exit(1);
123
+ });
package/src/index.ts ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
3
+ * SPDX-License-Identifier: GPL-3.0-or-later
4
+ *
5
+ * mcp_windows — MCP server for Windows desktop system operations
6
+ */
7
+
8
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { registerExecuteTools } from './tools/execute.js';
11
+ import { registerProcessTools } from './tools/process.js';
12
+ import { registerAudioTools } from './tools/audio.js';
13
+ import { registerSystemTools } from './tools/system.js';
14
+ import { registerTerminalTools } from './tools/terminal.js';
15
+ import { registerFilesystemTools } from './tools/filesystem.js';
16
+ import { registerProcessKillTools } from './tools/process_kill.js';
17
+ import { registerServiceTools } from './tools/service.js';
18
+ import { registerAudioAppTools } from './tools/audio_apps.js';
19
+ import { registerPowerTools } from './tools/power.js';
20
+ import { registerNetworkTools } from './tools/network.js';
21
+ import { registerDriveTools } from './tools/drives.js';
22
+ import { registerDisplayTools } from './tools/display.js';
23
+ import { registerWindowTools } from './tools/window.js';
24
+ import { registerClipboardTools } from './tools/clipboard.js';
25
+ import { registerNotificationTools } from './tools/notification.js';
26
+ import { registerSchedulerTools } from './tools/scheduler.js';
27
+ import { registerRegistryTools } from './tools/registry.js';
28
+ import { registerEnvironmentTools } from './tools/environment.js';
29
+ import { registerStartupTools } from './tools/startup.js';
30
+ import { registerConfigTools } from './tools/config.js';
31
+ import { registerAppsTools } from './tools/apps.js';
32
+ import { registerDialogTools } from './tools/dialog.js';
33
+ import { registerNetstatTools } from './tools/netstat.js';
34
+ import { registerRecycleBinTools } from './tools/recycle_bin.js';
35
+ import { registerBluetoothTools } from './tools/bluetooth.js';
36
+ import { registerWifiTools } from './tools/wifi.js';
37
+ import { registerUsbTools } from './tools/usb.js';
38
+ import { registerPrinterTools } from './tools/printer.js';
39
+ import { registerHostsTools } from './tools/hosts.js';
40
+ import { registerThemeTools } from './tools/theme.js';
41
+ import { registerVirtualDesktopTools } from './tools/virtual_desktop.js';
42
+ import { registerFirewallTools } from './tools/firewall.js';
43
+ import { registerMaintenanceTools } from './tools/maintenance.js';
44
+ import { registerWslTools } from './tools/wsl.js';
45
+ import { registerWingetTools } from './tools/winget.js';
46
+ import { registerStorageTools } from './tools/storage.js';
47
+ import { registerSystemMgmtTools } from './tools/system_mgmt.js';
48
+ import { registerAutomationTools } from './tools/automation.js';
49
+
50
+ const server = new McpServer({
51
+ name: 'mcp_windows',
52
+ version: '3.0.0',
53
+ });
54
+
55
+ // v1.0 — Core
56
+ registerExecuteTools(server);
57
+ registerProcessTools(server);
58
+ registerAudioTools(server);
59
+ registerSystemTools(server);
60
+ registerTerminalTools(server);
61
+ registerFilesystemTools(server);
62
+
63
+ // v1.1 — System Control
64
+ registerProcessKillTools(server);
65
+ registerServiceTools(server);
66
+ registerAudioAppTools(server);
67
+ registerPowerTools(server);
68
+ registerNetworkTools(server);
69
+ registerDriveTools(server);
70
+
71
+ // v1.2 — Desktop Automation
72
+ registerDisplayTools(server);
73
+ registerWindowTools(server);
74
+ registerClipboardTools(server);
75
+ registerNotificationTools(server);
76
+
77
+ // v1.3 — Admin Tools
78
+ registerSchedulerTools(server);
79
+ registerRegistryTools(server);
80
+ registerEnvironmentTools(server);
81
+ registerStartupTools(server);
82
+ registerConfigTools(server);
83
+
84
+ // v1.4 — Advanced
85
+ registerAppsTools(server);
86
+ registerDialogTools(server);
87
+ registerNetstatTools(server);
88
+ registerRecycleBinTools(server);
89
+
90
+ // v2.0 — Connectivity & Hardware
91
+ registerBluetoothTools(server);
92
+ registerWifiTools(server);
93
+ registerUsbTools(server);
94
+ registerPrinterTools(server);
95
+ registerHostsTools(server);
96
+
97
+ // v2.1 — Appearance & Desktop
98
+ registerThemeTools(server);
99
+ registerVirtualDesktopTools(server);
100
+
101
+ // v2.2 — Security & Maintenance
102
+ registerFirewallTools(server);
103
+ registerMaintenanceTools(server);
104
+
105
+ // v3.0 — WSL & Package Management
106
+ registerWslTools(server);
107
+ registerWingetTools(server);
108
+
109
+ // v3.1 — Storage & System Management
110
+ registerStorageTools(server);
111
+ registerSystemMgmtTools(server);
112
+
113
+ // v3.2 — Automation & Advanced
114
+ registerAutomationTools(server);
115
+
116
+ async function main(): Promise<void> {
117
+ const transport = new StdioServerTransport();
118
+ await server.connect(transport);
119
+ process.stderr.write('mcp_windows: server started\n');
120
+ }
121
+
122
+ main().catch((err) => {
123
+ process.stderr.write(`mcp_windows: fatal error: ${err}\n`);
124
+ process.exit(1);
125
+ });
package/src/shell.ts ADDED
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
3
+ * SPDX-License-Identifier: GPL-3.0-or-later
4
+ */
5
+
6
+ import { execFile, spawn, ChildProcess } from 'node:child_process';
7
+ import { promisify } from 'node:util';
8
+
9
+ const execFileAsync = promisify(execFile);
10
+
11
+ export interface ShellResult {
12
+ stdout: string;
13
+ stderr: string;
14
+ exitCode: number;
15
+ }
16
+
17
+ const POWERSHELL = 'powershell.exe';
18
+ const DEFAULT_TIMEOUT = 30_000;
19
+
20
+ /**
21
+ * Run a PowerShell command and return stdout/stderr/exitCode.
22
+ * Commands are wrapped with -NoProfile -NonInteractive for speed and safety.
23
+ */
24
+ export async function runPowerShell(
25
+ command: string,
26
+ options: { timeout?: number; cwd?: string } = {},
27
+ ): Promise<ShellResult> {
28
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
29
+ try {
30
+ const { stdout, stderr } = await execFileAsync(POWERSHELL, [
31
+ '-NoProfile',
32
+ '-NonInteractive',
33
+ '-Command',
34
+ command,
35
+ ], {
36
+ timeout,
37
+ cwd: options.cwd,
38
+ maxBuffer: 10 * 1024 * 1024,
39
+ windowsHide: true,
40
+ });
41
+ return { stdout: stdout.trimEnd(), stderr: stderr.trimEnd(), exitCode: 0 };
42
+ } catch (err: unknown) {
43
+ const e = err as { stdout?: string; stderr?: string; code?: number | string; killed?: boolean };
44
+ if (e.killed) {
45
+ return {
46
+ stdout: e.stdout?.trimEnd() ?? '',
47
+ stderr: `Command timed out after ${timeout}ms`,
48
+ exitCode: -1,
49
+ };
50
+ }
51
+ return {
52
+ stdout: e.stdout?.trimEnd() ?? '',
53
+ stderr: e.stderr?.trimEnd() ?? String(err),
54
+ exitCode: typeof e.code === 'number' ? e.code : 1,
55
+ };
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Run a PowerShell command that returns JSON. Wraps output with ConvertTo-Json
61
+ * and parses the result.
62
+ */
63
+ export async function runPowerShellJson<T = unknown>(
64
+ command: string,
65
+ options: { timeout?: number; cwd?: string } = {},
66
+ ): Promise<T> {
67
+ const wrapped = `${command} | ConvertTo-Json -Depth 10 -Compress`;
68
+ const result = await runPowerShell(wrapped, options);
69
+ if (result.exitCode !== 0) {
70
+ throw new Error(result.stderr || `PowerShell exited with code ${result.exitCode}`);
71
+ }
72
+ if (!result.stdout) {
73
+ return [] as unknown as T;
74
+ }
75
+ return JSON.parse(result.stdout) as T;
76
+ }
77
+
78
+ /**
79
+ * Run a generic shell command (cmd, bash, pwsh).
80
+ */
81
+ export async function runShell(
82
+ command: string,
83
+ options: {
84
+ shell?: 'pwsh' | 'cmd' | 'bash';
85
+ timeout?: number;
86
+ cwd?: string;
87
+ } = {},
88
+ ): Promise<ShellResult> {
89
+ const shell = options.shell ?? 'pwsh';
90
+ const timeout = options.timeout ?? DEFAULT_TIMEOUT;
91
+
92
+ let executable: string;
93
+ let args: string[];
94
+
95
+ switch (shell) {
96
+ case 'cmd':
97
+ executable = 'cmd.exe';
98
+ args = ['/c', command];
99
+ break;
100
+ case 'bash':
101
+ executable = 'bash';
102
+ args = ['-c', command];
103
+ break;
104
+ case 'pwsh':
105
+ default:
106
+ executable = POWERSHELL;
107
+ args = ['-NoProfile', '-NonInteractive', '-Command', command];
108
+ break;
109
+ }
110
+
111
+ try {
112
+ const { stdout, stderr } = await execFileAsync(executable, args, {
113
+ timeout,
114
+ cwd: options.cwd,
115
+ maxBuffer: 10 * 1024 * 1024,
116
+ windowsHide: true,
117
+ });
118
+ return { stdout: stdout.trimEnd(), stderr: stderr.trimEnd(), exitCode: 0 };
119
+ } catch (err: unknown) {
120
+ const e = err as { stdout?: string; stderr?: string; code?: number | string; killed?: boolean };
121
+ if (e.killed) {
122
+ return {
123
+ stdout: e.stdout?.trimEnd() ?? '',
124
+ stderr: `Command timed out after ${timeout}ms`,
125
+ exitCode: -1,
126
+ };
127
+ }
128
+ return {
129
+ stdout: e.stdout?.trimEnd() ?? '',
130
+ stderr: e.stderr?.trimEnd() ?? String(err),
131
+ exitCode: typeof e.code === 'number' ? e.code : 1,
132
+ };
133
+ }
134
+ }
135
+
136
+
137
+ // ── Persistent Terminal Sessions ─────────────────────────────────────────
138
+
139
+ export interface TerminalSession {
140
+ pid: number;
141
+ shell: string;
142
+ process: ChildProcess;
143
+ output: string[];
144
+ startedAt: Date;
145
+ }
146
+
147
+ const sessions = new Map<number, TerminalSession>();
148
+ const MAX_OUTPUT_LINES = 5000;
149
+
150
+ export function startSession(shell: 'pwsh' | 'cmd' | 'bash' | 'python' | 'node' | 'wsl' = 'pwsh'): TerminalSession {
151
+ let executable: string;
152
+ let args: string[];
153
+
154
+ switch (shell) {
155
+ case 'cmd':
156
+ executable = 'cmd.exe';
157
+ args = [];
158
+ break;
159
+ case 'bash':
160
+ executable = 'bash';
161
+ args = [];
162
+ break;
163
+ case 'python':
164
+ executable = 'python';
165
+ args = ['-i'];
166
+ break;
167
+ case 'node':
168
+ executable = 'node';
169
+ args = [];
170
+ break;
171
+ case 'wsl':
172
+ executable = 'wsl.exe';
173
+ args = [];
174
+ break;
175
+ case 'pwsh':
176
+ default:
177
+ executable = POWERSHELL;
178
+ args = ['-NoProfile', '-NoLogo'];
179
+ break;
180
+ }
181
+
182
+ const proc = spawn(executable, args, {
183
+ stdio: ['pipe', 'pipe', 'pipe'],
184
+ windowsHide: true,
185
+ detached: false,
186
+ });
187
+ // Don't let terminal sessions prevent the MCP server from exiting
188
+ proc.unref();
189
+ (proc.stdout as any)?.unref?.();
190
+ (proc.stderr as any)?.unref?.();
191
+ (proc.stdin as any)?.unref?.();
192
+
193
+ const session: TerminalSession = {
194
+ pid: proc.pid!,
195
+ shell,
196
+ process: proc,
197
+ output: [],
198
+ startedAt: new Date(),
199
+ };
200
+
201
+ const pushLine = (line: string) => {
202
+ session.output.push(line);
203
+ if (session.output.length > MAX_OUTPUT_LINES) {
204
+ session.output.splice(0, session.output.length - MAX_OUTPUT_LINES);
205
+ }
206
+ };
207
+
208
+ proc.stdout?.on('data', (data: Buffer) => {
209
+ data.toString().split('\n').forEach(pushLine);
210
+ });
211
+ proc.stderr?.on('data', (data: Buffer) => {
212
+ data.toString().split('\n').forEach(l => pushLine(`[stderr] ${l}`));
213
+ });
214
+ proc.on('exit', () => {
215
+ pushLine(`[session ended]`);
216
+ });
217
+
218
+ sessions.set(proc.pid!, session);
219
+ return session;
220
+ }
221
+
222
+ export function sendToSession(pid: number, input: string): void {
223
+ const session = sessions.get(pid);
224
+ if (!session) throw new Error(`No session with PID ${pid}`);
225
+ if (session.process.exitCode !== null) throw new Error(`Session ${pid} has ended`);
226
+ session.process.stdin?.write(input + '\n');
227
+ }
228
+
229
+ export function readSessionOutput(pid: number, offset = 0, length?: number): string[] {
230
+ const session = sessions.get(pid);
231
+ if (!session) throw new Error(`No session with PID ${pid}`);
232
+ const start = offset < 0 ? Math.max(0, session.output.length + offset) : offset;
233
+ const end = length !== undefined ? start + length : undefined;
234
+ return session.output.slice(start, end);
235
+ }
236
+
237
+ export function listSessions(): Array<{ pid: number; shell: string; running: boolean; lines: number; startedAt: string }> {
238
+ return Array.from(sessions.values()).map(s => ({
239
+ pid: s.pid,
240
+ shell: s.shell,
241
+ running: s.process.exitCode === null,
242
+ lines: s.output.length,
243
+ startedAt: s.startedAt.toISOString(),
244
+ }));
245
+ }
246
+
247
+ export function terminateSession(pid: number): boolean {
248
+ const session = sessions.get(pid);
249
+ if (!session) return false;
250
+ session.process.kill();
251
+ sessions.delete(pid);
252
+ return true;
253
+ }
@@ -0,0 +1,76 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tool: windows_installed_apps (#19)
5
+ */
6
+
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { z } from 'zod';
9
+ import { runPowerShell } from '../shell.js';
10
+
11
+ export function registerAppsTools(server: McpServer): void {
12
+ server.tool(
13
+ 'windows_installed_apps',
14
+ 'List installed applications from registry and Microsoft Store.',
15
+ {
16
+ filter: z.string().optional().describe('Filter by app name (substring)'),
17
+ sort: z.enum(['name', 'date', 'size']).default('name').describe('Sort order'),
18
+ limit: z.number().default(50).describe('Max results'),
19
+ },
20
+ async ({ filter, sort, limit }) => {
21
+ const filterClause = filter
22
+ ? `| Where-Object { $_.DisplayName -like '*${filter.replace(/'/g, "''")}*' }`
23
+ : '';
24
+
25
+ const sortClause = sort === 'date'
26
+ ? '| Sort-Object InstallDate -Descending'
27
+ : sort === 'size'
28
+ ? '| Sort-Object { [int]$_.EstimatedSize } -Descending'
29
+ : '| Sort-Object DisplayName';
30
+
31
+ const ps = `
32
+ $regPaths = @(
33
+ 'HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*',
34
+ 'HKLM:\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*',
35
+ 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'
36
+ )
37
+
38
+ $apps = $regPaths | ForEach-Object {
39
+ Get-ItemProperty -Path $_ -ErrorAction SilentlyContinue
40
+ } | Where-Object { $_.DisplayName } ${filterClause} ${sortClause} |
41
+ Select-Object -First ${limit} |
42
+ ForEach-Object {
43
+ [PSCustomObject]@{
44
+ Name = $_.DisplayName
45
+ Version = $_.DisplayVersion
46
+ Publisher = $_.Publisher
47
+ InstallDate = $_.InstallDate
48
+ SizeMB = if ($_.EstimatedSize) { [math]::Round($_.EstimatedSize / 1024, 1) } else { $null }
49
+ }
50
+ }
51
+
52
+ $apps | ConvertTo-Json -Depth 3 -Compress`;
53
+
54
+ const result = await runPowerShell(ps, { timeout: 20000 });
55
+ if (result.exitCode !== 0) {
56
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
57
+ }
58
+
59
+ if (!result.stdout) {
60
+ return { content: [{ type: 'text', text: 'No apps found.' }] };
61
+ }
62
+
63
+ const apps = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
64
+ const lines = apps.map((a: { Name: string; Version: string; Publisher: string; InstallDate: string; SizeMB: number | null }) => {
65
+ const size = a.SizeMB ? `${String(a.SizeMB).padStart(8)} MB` : ' ';
66
+ const date = a.InstallDate || ' ';
67
+ return `${(a.Name || '').padEnd(45).slice(0, 45)} ${(a.Version || '').padEnd(15).slice(0, 15)} ${date.padEnd(10)} ${size} ${(a.Publisher || '').slice(0, 25)}`;
68
+ });
69
+
70
+ const header = `${'Name'.padEnd(45)} ${'Version'.padEnd(15)} ${'Installed'.padEnd(10)} ${'Size'.padStart(11)} Publisher`;
71
+ return {
72
+ content: [{ type: 'text', text: `${header}\n${'─'.repeat(115)}\n${lines.join('\n')}\n\n${apps.length} applications` }],
73
+ };
74
+ },
75
+ );
76
+ }