@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,54 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tool: windows_process_list — List running processes (#2)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerProcessTools(server) {
9
+ server.tool('windows_process_list', 'List running processes with PID, name, CPU, memory, window title, and path.', {
10
+ filter: z.string().optional().describe('Filter by process name (substring match)'),
11
+ sort: z.enum(['cpu', 'memory', 'name']).default('memory').describe('Sort order'),
12
+ limit: z.number().default(50).describe('Max results to return'),
13
+ }, async ({ filter, sort, limit }) => {
14
+ const filterClause = filter
15
+ ? `| Where-Object { $_.Name -like '*${filter.replace(/'/g, "''")}*' }`
16
+ : '';
17
+ const sortClause = sort === 'cpu'
18
+ ? '| Sort-Object CPU -Descending'
19
+ : sort === 'name'
20
+ ? '| Sort-Object Name'
21
+ : '| Sort-Object WorkingSet64 -Descending';
22
+ const ps = `
23
+ Get-Process ${filterClause} ${sortClause} | Select-Object -First ${limit} |
24
+ ForEach-Object {
25
+ [PSCustomObject]@{
26
+ PID = $_.Id
27
+ Name = $_.ProcessName
28
+ CPU = [math]::Round($_.CPU, 1)
29
+ MemoryMB = [math]::Round($_.WorkingSet64 / 1MB, 1)
30
+ WindowTitle = $_.MainWindowTitle
31
+ Path = $_.Path
32
+ }
33
+ } | ConvertTo-Json -Depth 3 -Compress`;
34
+ const result = await runPowerShell(ps);
35
+ if (result.exitCode !== 0) {
36
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
37
+ }
38
+ let processes = [];
39
+ if (result.stdout) {
40
+ const parsed = JSON.parse(result.stdout);
41
+ processes = Array.isArray(parsed) ? parsed : [parsed];
42
+ }
43
+ const lines = processes.map(p => `${String(p.PID).padStart(7)} ${p.Name.padEnd(25).slice(0, 25)} ${String(p.CPU ?? 0).padStart(8)}s ${String(p.MemoryMB).padStart(8)} MB ${p.WindowTitle || ''}`);
44
+ const header = `${'PID'.padStart(7)} ${'Name'.padEnd(25)} ${'CPU'.padStart(8)} ${'Memory'.padStart(8)} Window Title`;
45
+ const separator = '-'.repeat(90);
46
+ return {
47
+ content: [{
48
+ type: 'text',
49
+ text: `${header}\n${separator}\n${lines.join('\n')}\n\n${processes.length} processes`,
50
+ }],
51
+ };
52
+ });
53
+ }
54
+ //# sourceMappingURL=process.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerProcessKillTools(server: McpServer): void;
3
+ //# sourceMappingURL=process_kill.d.ts.map
@@ -0,0 +1,48 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tool: windows_process_kill (#3)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerProcessKillTools(server) {
9
+ server.tool('windows_process_kill', 'Terminate running processes by PID or name.', {
10
+ pid: z.number().optional().describe('Process ID to kill'),
11
+ name: z.string().optional().describe('Process name to kill (all matching)'),
12
+ force: z.boolean().default(false).describe('Force termination'),
13
+ }, async ({ pid, name, force }) => {
14
+ if (!pid && !name) {
15
+ return { content: [{ type: 'text', text: 'Provide either pid or name.' }], isError: true };
16
+ }
17
+ const forceFlag = force ? ' -Force' : '';
18
+ let ps;
19
+ if (pid) {
20
+ ps = `
21
+ $p = Get-Process -Id ${pid} -ErrorAction SilentlyContinue
22
+ if ($p) {
23
+ $n = $p.ProcessName
24
+ Stop-Process -Id ${pid}${forceFlag} -ErrorAction Stop
25
+ "Killed PID ${pid} ($n)"
26
+ } else {
27
+ "No process with PID ${pid}"
28
+ }`;
29
+ }
30
+ else {
31
+ ps = `
32
+ $procs = Get-Process -Name '${name.replace(/'/g, "''")}' -ErrorAction SilentlyContinue
33
+ if ($procs) {
34
+ $count = @($procs).Count
35
+ $procs | Stop-Process${forceFlag} -ErrorAction Stop
36
+ "Killed $count process(es) named '${name}'"
37
+ } else {
38
+ "No processes named '${name}'"
39
+ }`;
40
+ }
41
+ const result = await runPowerShell(ps);
42
+ return {
43
+ content: [{ type: 'text', text: result.stdout || result.stderr }],
44
+ isError: result.exitCode !== 0,
45
+ };
46
+ });
47
+ }
48
+ //# sourceMappingURL=process_kill.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerRecycleBinTools(server: McpServer): void;
3
+ //# sourceMappingURL=recycle_bin.d.ts.map
@@ -0,0 +1,108 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tool: windows_recycle_bin (#26)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerRecycleBinTools(server) {
9
+ server.tool('windows_recycle_bin', 'Manage the Recycle Bin: list items, get size, restore items, or empty.', {
10
+ action: z.enum(['list', 'size', 'empty', 'restore']).default('list').describe('Action'),
11
+ filter: z.string().optional().describe('Filter by filename (for list/restore)'),
12
+ limit: z.number().default(30).describe('Max items (for list)'),
13
+ }, async ({ action, filter, limit }) => {
14
+ switch (action) {
15
+ case 'size': {
16
+ const ps = `
17
+ $shell = New-Object -ComObject Shell.Application
18
+ $bin = $shell.Namespace(10)
19
+ $items = $bin.Items()
20
+ $count = $items.Count
21
+ $totalSize = 0
22
+ for ($i = 0; $i -lt $count; $i++) {
23
+ $totalSize += $bin.GetDetailsOf($items.Item($i), 2) -replace '[^0-9]','' -as [long]
24
+ }
25
+ [PSCustomObject]@{
26
+ Count = $count
27
+ SizeMB = [math]::Round($totalSize / 1MB, 1)
28
+ } | ConvertTo-Json -Compress`;
29
+ const result = await runPowerShell(ps, { timeout: 15000 });
30
+ if (result.exitCode !== 0) {
31
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
32
+ }
33
+ const info = JSON.parse(result.stdout);
34
+ return { content: [{ type: 'text', text: `Recycle Bin: ${info.Count} items, ${info.SizeMB} MB` }] };
35
+ }
36
+ case 'list': {
37
+ const filterClause = filter
38
+ ? `| Where-Object { $_.Name -like '*${filter.replace(/'/g, "''")}*' }`
39
+ : '';
40
+ const ps = `
41
+ $shell = New-Object -ComObject Shell.Application
42
+ $bin = $shell.Namespace(10)
43
+ $items = @()
44
+ foreach ($item in $bin.Items()) {
45
+ $items += [PSCustomObject]@{
46
+ Name = $item.Name
47
+ OriginalPath = $bin.GetDetailsOf($item, 1)
48
+ Size = $bin.GetDetailsOf($item, 2)
49
+ DeletedDate = $bin.GetDetailsOf($item, 3)
50
+ Type = $bin.GetDetailsOf($item, 4)
51
+ }
52
+ }
53
+ $items ${filterClause} | Select-Object -First ${limit} | ConvertTo-Json -Depth 3 -Compress`;
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
+ if (!result.stdout) {
59
+ return { content: [{ type: 'text', text: 'Recycle Bin is empty.' }] };
60
+ }
61
+ const items = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
62
+ const lines = items.map((i) => `${(i.Name || '').padEnd(35).slice(0, 35)} ${(i.Size || '').padStart(10)} ${(i.DeletedDate || '').padEnd(20)} ${(i.OriginalPath || '').slice(0, 40)}`);
63
+ const header = `${'Name'.padEnd(35)} ${'Size'.padStart(10)} ${'Deleted'.padEnd(20)} Original Path`;
64
+ return {
65
+ content: [{ type: 'text', text: `${header}\n${'─'.repeat(110)}\n${lines.join('\n')}\n\n${items.length} items` }],
66
+ };
67
+ }
68
+ case 'empty': {
69
+ const ps = `
70
+ $shell = New-Object -ComObject Shell.Application
71
+ $count = $shell.Namespace(10).Items().Count
72
+ if ($count -eq 0) { "Recycle Bin is already empty." }
73
+ else {
74
+ Clear-RecycleBin -Force -ErrorAction Stop
75
+ "Emptied Recycle Bin ($count items removed)"
76
+ }`;
77
+ const result = await runPowerShell(ps, { timeout: 15000 });
78
+ return {
79
+ content: [{ type: 'text', text: result.stdout || result.stderr }],
80
+ isError: result.exitCode !== 0,
81
+ };
82
+ }
83
+ case 'restore': {
84
+ if (!filter) {
85
+ return { content: [{ type: 'text', text: 'Restore requires a filter to identify which item(s) to restore.' }], isError: true };
86
+ }
87
+ const ps = `
88
+ $shell = New-Object -ComObject Shell.Application
89
+ $bin = $shell.Namespace(10)
90
+ $restored = 0
91
+ foreach ($item in $bin.Items()) {
92
+ if ($item.Name -like '*${filter.replace(/'/g, "''")}*') {
93
+ $origPath = $bin.GetDetailsOf($item, 1)
94
+ $item.InvokeVerb('undelete')
95
+ $restored++
96
+ }
97
+ }
98
+ if ($restored -gt 0) { "Restored $restored item(s)" } else { "No matching items found" }`;
99
+ const result = await runPowerShell(ps, { timeout: 15000 });
100
+ return {
101
+ content: [{ type: 'text', text: result.stdout || result.stderr }],
102
+ isError: result.exitCode !== 0,
103
+ };
104
+ }
105
+ }
106
+ });
107
+ }
108
+ //# sourceMappingURL=recycle_bin.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerRegistryTools(server: McpServer): void;
3
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1,136 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tools: windows_registry_read (#29), windows_registry_write (#30)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ function expandHive(path) {
9
+ return path
10
+ .replace(/^HKLM[:\\\/]/i, 'HKLM:\\')
11
+ .replace(/^HKCU[:\\\/]/i, 'HKCU:\\')
12
+ .replace(/^HKCR[:\\\/]/i, 'HKCR:\\')
13
+ .replace(/^HKU[:\\\/]/i, 'HKU:\\')
14
+ .replace(/^HKCC[:\\\/]/i, 'HKCC:\\');
15
+ }
16
+ export function registerRegistryTools(server) {
17
+ server.tool('windows_registry_read', 'Read Windows Registry keys, subkeys, and values. Supports HKLM, HKCU, HKCR abbreviations.', {
18
+ path: z.string().describe('Registry path (e.g. "HKCU:\\Software\\Microsoft")'),
19
+ value: z.string().optional().describe('Specific value name to read (omit to list all values)'),
20
+ subkeys: z.boolean().default(false).describe('List subkeys instead of values'),
21
+ }, async ({ path, value, subkeys }) => {
22
+ const regPath = expandHive(path);
23
+ if (subkeys) {
24
+ const ps = `
25
+ Get-ChildItem -Path '${regPath.replace(/'/g, "''")}' -ErrorAction Stop | ForEach-Object {
26
+ [PSCustomObject]@{
27
+ Name = $_.PSChildName
28
+ SubKeyCount = $_.SubKeyCount
29
+ ValueCount = $_.ValueCount
30
+ }
31
+ } | ConvertTo-Json -Depth 3 -Compress`;
32
+ const result = await runPowerShell(ps, { timeout: 10000 });
33
+ if (result.exitCode !== 0) {
34
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
35
+ }
36
+ if (!result.stdout) {
37
+ return { content: [{ type: 'text', text: 'No subkeys found.' }] };
38
+ }
39
+ const keys = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
40
+ const lines = keys.map((k) => ` ${k.Name.padEnd(40)} ${k.SubKeyCount} subkeys, ${k.ValueCount} values`);
41
+ return { content: [{ type: 'text', text: `${regPath}\n${'─'.repeat(70)}\n${lines.join('\n')}` }] };
42
+ }
43
+ if (value) {
44
+ const ps = `
45
+ $v = Get-ItemProperty -Path '${regPath.replace(/'/g, "''")}' -Name '${value.replace(/'/g, "''")}' -ErrorAction Stop
46
+ $raw = $v.'${value.replace(/'/g, "''")}'
47
+ $kind = (Get-Item -Path '${regPath.replace(/'/g, "''")}' -ErrorAction Stop).GetValueKind('${value.replace(/'/g, "''")}')
48
+ [PSCustomObject]@{
49
+ Name = '${value.replace(/'/g, "''")}'
50
+ Type = $kind.ToString()
51
+ Value = $raw
52
+ } | ConvertTo-Json -Depth 3 -Compress`;
53
+ const result = await runPowerShell(ps, { timeout: 10000 });
54
+ if (result.exitCode !== 0) {
55
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
56
+ }
57
+ const v = JSON.parse(result.stdout);
58
+ return { content: [{ type: 'text', text: `${regPath}\\${v.Name}\nType: ${v.Type}\nValue: ${JSON.stringify(v.Value)}` }] };
59
+ }
60
+ // List all values
61
+ const ps = `
62
+ $key = Get-Item -Path '${regPath.replace(/'/g, "''")}' -ErrorAction Stop
63
+ $key.GetValueNames() | ForEach-Object {
64
+ $name = $_
65
+ $val = $key.GetValue($name)
66
+ $kind = $key.GetValueKind($name)
67
+ [PSCustomObject]@{
68
+ Name = if ($name) { $name } else { '(Default)' }
69
+ Type = $kind.ToString()
70
+ Value = $val
71
+ }
72
+ } | ConvertTo-Json -Depth 3 -Compress`;
73
+ const result = await runPowerShell(ps, { timeout: 10000 });
74
+ if (result.exitCode !== 0) {
75
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
76
+ }
77
+ if (!result.stdout) {
78
+ return { content: [{ type: 'text', text: 'No values found.' }] };
79
+ }
80
+ const values = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
81
+ const lines = values.map((v) => {
82
+ const valStr = typeof v.Value === 'string' ? v.Value : JSON.stringify(v.Value);
83
+ return ` ${v.Name.padEnd(30)} ${v.Type.padEnd(12)} ${valStr.slice(0, 60)}`;
84
+ });
85
+ const header = ` ${'Name'.padEnd(30)} ${'Type'.padEnd(12)} Value`;
86
+ return { content: [{ type: 'text', text: `${regPath}\n${header}\n${'─'.repeat(80)}\n${lines.join('\n')}` }] };
87
+ });
88
+ server.tool('windows_registry_write', 'Write Windows Registry values. Restricted to HKCU by default. Use hklm_override for HKLM writes.', {
89
+ path: z.string().describe('Registry path'),
90
+ name: z.string().describe('Value name'),
91
+ value: z.string().describe('Value data'),
92
+ type: z.enum(['String', 'DWord', 'QWord', 'Binary', 'ExpandString', 'MultiString']).default('String').describe('Value type'),
93
+ hklm_override: z.boolean().default(false).describe('Allow writing to HKLM (requires elevation)'),
94
+ action: z.enum(['set', 'delete', 'create_key', 'delete_key']).default('set').describe('Action'),
95
+ }, async ({ path, name, value, type, hklm_override, action }) => {
96
+ const regPath = expandHive(path);
97
+ // Safety check
98
+ if (regPath.startsWith('HKLM:') && !hklm_override) {
99
+ return {
100
+ content: [{ type: 'text', text: 'HKLM writes are restricted. Set hklm_override=true and ensure elevation.' }],
101
+ isError: true,
102
+ };
103
+ }
104
+ let ps;
105
+ switch (action) {
106
+ case 'set': {
107
+ const typeMap = {
108
+ String: 'String', DWord: 'DWord', QWord: 'QWord',
109
+ Binary: 'Binary', ExpandString: 'ExpandString', MultiString: 'MultiString',
110
+ };
111
+ ps = `
112
+ if (-not (Test-Path '${regPath.replace(/'/g, "''")}')) {
113
+ New-Item -Path '${regPath.replace(/'/g, "''")}' -Force | Out-Null
114
+ }
115
+ Set-ItemProperty -Path '${regPath.replace(/'/g, "''")}' -Name '${name.replace(/'/g, "''")}' -Value '${value.replace(/'/g, "''")}' -Type ${typeMap[type]} -ErrorAction Stop
116
+ "Set ${regPath}\\${name} = ${value} (${type})"`;
117
+ break;
118
+ }
119
+ case 'delete':
120
+ ps = `Remove-ItemProperty -Path '${regPath.replace(/'/g, "''")}' -Name '${name.replace(/'/g, "''")}' -ErrorAction Stop; "Deleted ${regPath}\\${name}"`;
121
+ break;
122
+ case 'create_key':
123
+ ps = `New-Item -Path '${regPath.replace(/'/g, "''")}' -Force -ErrorAction Stop | Out-Null; "Created key ${regPath}"`;
124
+ break;
125
+ case 'delete_key':
126
+ ps = `Remove-Item -Path '${regPath.replace(/'/g, "''")}' -Recurse -Force -ErrorAction Stop; "Deleted key ${regPath}"`;
127
+ break;
128
+ }
129
+ const result = await runPowerShell(ps, { timeout: 10000 });
130
+ return {
131
+ content: [{ type: 'text', text: result.stdout || result.stderr }],
132
+ isError: result.exitCode !== 0,
133
+ };
134
+ });
135
+ }
136
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerSchedulerTools(server: McpServer): void;
3
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1,116 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tools: windows_task_scheduler_list (#27), windows_task_scheduler_manage (#28)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerSchedulerTools(server) {
9
+ server.tool('windows_task_scheduler_list', 'List Windows Task Scheduler tasks with status, last/next run, and trigger type.', {
10
+ folder: z.string().default('\\').describe('Task folder path (e.g. "\\" for root, "\\Microsoft\\")'),
11
+ filter: z.string().optional().describe('Filter by task name (substring)'),
12
+ }, async ({ folder, filter }) => {
13
+ const filterClause = filter
14
+ ? `| Where-Object { $_.TaskName -like '*${filter.replace(/'/g, "''")}*' }`
15
+ : '';
16
+ const ps = `
17
+ Get-ScheduledTask -TaskPath '${folder.replace(/'/g, "''")}*' -ErrorAction SilentlyContinue ${filterClause} | ForEach-Object {
18
+ $info = $_ | Get-ScheduledTaskInfo -ErrorAction SilentlyContinue
19
+ [PSCustomObject]@{
20
+ Name = $_.TaskName
21
+ Path = $_.TaskPath
22
+ State = $_.State.ToString()
23
+ LastRun = if ($info.LastRunTime -and $info.LastRunTime.Year -gt 1999) { $info.LastRunTime.ToString('yyyy-MM-dd HH:mm') } else { 'Never' }
24
+ NextRun = if ($info.NextRunTime -and $info.NextRunTime.Year -gt 1999) { $info.NextRunTime.ToString('yyyy-MM-dd HH:mm') } else { 'N/A' }
25
+ LastResult = if ($info) { '0x{0:X}' -f $info.LastTaskResult } else { 'N/A' }
26
+ Triggers = ($_.Triggers | ForEach-Object { $_.CimClass.CimClassName -replace 'MSFT_Task',''-replace 'Trigger','' }) -join ', '
27
+ }
28
+ } | ConvertTo-Json -Depth 3 -Compress`;
29
+ const result = await runPowerShell(ps, { timeout: 30000 });
30
+ if (result.exitCode !== 0) {
31
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
32
+ }
33
+ if (!result.stdout) {
34
+ return { content: [{ type: 'text', text: 'No tasks found.' }] };
35
+ }
36
+ const tasks = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
37
+ const lines = tasks.map((t) => {
38
+ const state = t.State === 'Ready' ? '[RDY]' : t.State === 'Running' ? '[RUN]' : t.State === 'Disabled' ? '[OFF]' : `[${t.State.slice(0, 3).toUpperCase()}]`;
39
+ return `${state} ${t.Name.padEnd(40).slice(0, 40)} ${t.LastRun.padEnd(16)} ${t.NextRun.padEnd(16)} ${t.Triggers}`;
40
+ });
41
+ const header = `State ${'Name'.padEnd(40)} ${'Last Run'.padEnd(16)} ${'Next Run'.padEnd(16)} Triggers`;
42
+ return {
43
+ content: [{ type: 'text', text: `${header}\n${'─'.repeat(110)}\n${lines.join('\n')}\n\n${tasks.length} tasks` }],
44
+ };
45
+ });
46
+ server.tool('windows_task_scheduler_manage', 'Create, delete, enable, disable, or run a scheduled task.', {
47
+ action: z.enum(['create', 'delete', 'enable', 'disable', 'run']).describe('Action to perform'),
48
+ name: z.string().describe('Task name'),
49
+ command: z.string().optional().describe('Command to execute (for create)'),
50
+ arguments: z.string().optional().describe('Command arguments (for create)'),
51
+ trigger: z.enum(['once', 'daily', 'weekly', 'hourly', 'logon', 'startup']).optional().describe('Trigger type (for create)'),
52
+ time: z.string().optional().describe('Time for trigger as HH:mm (for create with once/daily/weekly)'),
53
+ interval: z.number().optional().describe('Repetition interval in minutes (for create with hourly)'),
54
+ }, async ({ action, name, command, arguments: args, trigger, time, interval }) => {
55
+ let ps;
56
+ switch (action) {
57
+ case 'run':
58
+ ps = `Start-ScheduledTask -TaskName '${name.replace(/'/g, "''")}' -ErrorAction Stop; "Task '${name}' started"`;
59
+ break;
60
+ case 'enable':
61
+ ps = `Enable-ScheduledTask -TaskName '${name.replace(/'/g, "''")}' -ErrorAction Stop | Select-Object TaskName,State | ConvertTo-Json -Compress`;
62
+ break;
63
+ case 'disable':
64
+ ps = `Disable-ScheduledTask -TaskName '${name.replace(/'/g, "''")}' -ErrorAction Stop | Select-Object TaskName,State | ConvertTo-Json -Compress`;
65
+ break;
66
+ case 'delete':
67
+ ps = `Unregister-ScheduledTask -TaskName '${name.replace(/'/g, "''")}' -Confirm:$false -ErrorAction Stop; "Task '${name}' deleted"`;
68
+ break;
69
+ case 'create': {
70
+ if (!command) {
71
+ return { content: [{ type: 'text', text: 'Create requires command.' }], isError: true };
72
+ }
73
+ if (!trigger) {
74
+ return { content: [{ type: 'text', text: 'Create requires trigger type.' }], isError: true };
75
+ }
76
+ const actionPart = args
77
+ ? `$action = New-ScheduledTaskAction -Execute '${command.replace(/'/g, "''")}' -Argument '${args.replace(/'/g, "''")}'`
78
+ : `$action = New-ScheduledTaskAction -Execute '${command.replace(/'/g, "''")}'`;
79
+ let triggerPart;
80
+ switch (trigger) {
81
+ case 'once':
82
+ triggerPart = `$trigger = New-ScheduledTaskTrigger -Once -At '${time || '00:00'}'`;
83
+ break;
84
+ case 'daily':
85
+ triggerPart = `$trigger = New-ScheduledTaskTrigger -Daily -At '${time || '00:00'}'`;
86
+ break;
87
+ case 'weekly':
88
+ triggerPart = `$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At '${time || '00:00'}'`;
89
+ break;
90
+ case 'hourly':
91
+ triggerPart = `$trigger = New-ScheduledTaskTrigger -Once -At '00:00' -RepetitionInterval (New-TimeSpan -Minutes ${interval || 60}) -RepetitionDuration (New-TimeSpan -Days 9999)`;
92
+ break;
93
+ case 'logon':
94
+ triggerPart = `$trigger = New-ScheduledTaskTrigger -AtLogOn`;
95
+ break;
96
+ case 'startup':
97
+ triggerPart = `$trigger = New-ScheduledTaskTrigger -AtStartup`;
98
+ break;
99
+ }
100
+ ps = `
101
+ ${actionPart}
102
+ ${triggerPart}
103
+ $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
104
+ Register-ScheduledTask -TaskName '${name.replace(/'/g, "''")}' -Action $action -Trigger $trigger -Settings $settings -Force -ErrorAction Stop |
105
+ Select-Object TaskName,State | ConvertTo-Json -Compress`;
106
+ break;
107
+ }
108
+ }
109
+ const result = await runPowerShell(ps, { timeout: 15000 });
110
+ if (result.exitCode !== 0) {
111
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
112
+ }
113
+ return { content: [{ type: 'text', text: result.stdout }] };
114
+ });
115
+ }
116
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerServiceTools(server: McpServer): void;
3
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1,79 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tools: windows_service_list (#4), windows_service_control (#5)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerServiceTools(server) {
9
+ server.tool('windows_service_list', 'List Windows services with status, startup type, and description.', {
10
+ filter: z.string().optional().describe('Filter by service name or display name (substring)'),
11
+ status: z.enum(['running', 'stopped', 'all']).default('all').describe('Filter by status'),
12
+ }, async ({ filter, status }) => {
13
+ const filterClause = filter
14
+ ? `| Where-Object { $_.Name -like '*${filter.replace(/'/g, "''")}*' -or $_.DisplayName -like '*${filter.replace(/'/g, "''")}*' }`
15
+ : '';
16
+ const statusClause = status === 'running'
17
+ ? `| Where-Object { $_.Status -eq 'Running' }`
18
+ : status === 'stopped'
19
+ ? `| Where-Object { $_.Status -eq 'Stopped' }`
20
+ : '';
21
+ const ps = `
22
+ $wmiCache = @{}
23
+ Get-CimInstance Win32_Service -ErrorAction SilentlyContinue | ForEach-Object { $wmiCache[$_.Name] = $_.Description }
24
+ Get-Service ${filterClause} ${statusClause} | Sort-Object DisplayName | ForEach-Object {
25
+ [PSCustomObject]@{
26
+ Name = $_.Name
27
+ DisplayName = $_.DisplayName
28
+ Status = $_.Status.ToString()
29
+ StartType = $_.StartType.ToString()
30
+ Description = if ($wmiCache[$_.Name]) { $wmiCache[$_.Name] } else { '' }
31
+ }
32
+ } | ConvertTo-Json -Depth 3 -Compress`;
33
+ const result = await runPowerShell(ps, { timeout: 45000 });
34
+ if (result.exitCode !== 0) {
35
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
36
+ }
37
+ if (!result.stdout) {
38
+ return { content: [{ type: 'text', text: 'No services found.' }] };
39
+ }
40
+ const services = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
41
+ const lines = services.map((s) => `${s.Status === 'Running' ? '[RUN]' : '[STP]'} ${s.StartType.padEnd(10)} ${s.Name.padEnd(35).slice(0, 35)} ${s.DisplayName}`);
42
+ const header = `${'State'.padEnd(5)} ${'Startup'.padEnd(10)} ${'Name'.padEnd(35)} Display Name`;
43
+ return {
44
+ content: [{ type: 'text', text: `${header}\n${'─'.repeat(100)}\n${lines.join('\n')}\n\n${services.length} services` }],
45
+ };
46
+ });
47
+ server.tool('windows_service_control', 'Start, stop, restart, or change startup type of a Windows service.', {
48
+ name: z.string().describe('Service name or display name'),
49
+ action: z.enum(['start', 'stop', 'restart', 'enable', 'disable']).describe('Action to perform'),
50
+ }, async ({ name, action }) => {
51
+ let ps;
52
+ switch (action) {
53
+ case 'start':
54
+ ps = `Start-Service -Name '${name.replace(/'/g, "''")}' -ErrorAction Stop; Get-Service -Name '${name.replace(/'/g, "''")}' | Select-Object Name,Status,StartType | ConvertTo-Json -Compress`;
55
+ break;
56
+ case 'stop':
57
+ ps = `Stop-Service -Name '${name.replace(/'/g, "''")}' -Force -ErrorAction Stop; Get-Service -Name '${name.replace(/'/g, "''")}' | Select-Object Name,Status,StartType | ConvertTo-Json -Compress`;
58
+ break;
59
+ case 'restart':
60
+ ps = `Restart-Service -Name '${name.replace(/'/g, "''")}' -Force -ErrorAction Stop; Get-Service -Name '${name.replace(/'/g, "''")}' | Select-Object Name,Status,StartType | ConvertTo-Json -Compress`;
61
+ break;
62
+ case 'enable':
63
+ ps = `Set-Service -Name '${name.replace(/'/g, "''")}' -StartupType Automatic -ErrorAction Stop; Get-Service -Name '${name.replace(/'/g, "''")}' | Select-Object Name,Status,StartType | ConvertTo-Json -Compress`;
64
+ break;
65
+ case 'disable':
66
+ ps = `Set-Service -Name '${name.replace(/'/g, "''")}' -StartupType Disabled -ErrorAction Stop; Get-Service -Name '${name.replace(/'/g, "''")}' | Select-Object Name,Status,StartType | ConvertTo-Json -Compress`;
67
+ break;
68
+ }
69
+ const result = await runPowerShell(ps, { timeout: 15000 });
70
+ if (result.exitCode !== 0) {
71
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
72
+ }
73
+ const svc = JSON.parse(result.stdout);
74
+ return {
75
+ content: [{ type: 'text', text: `Service "${svc.Name}": ${action} → Status: ${svc.Status}, StartType: ${svc.StartType}` }],
76
+ };
77
+ });
78
+ }
79
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerStartupTools(server: McpServer): void;
3
+ //# sourceMappingURL=startup.d.ts.map