@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,236 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tools: windows_updates (#59), windows_event_log (#60),
5
+ * windows_restore_point (#61), windows_certificate_list (#62),
6
+ * windows_performance_monitor (#63)
7
+ */
8
+ import { z } from 'zod';
9
+ import { runPowerShell } from '../shell.js';
10
+ export function registerMaintenanceTools(server) {
11
+ server.tool('windows_updates', 'Check Windows Update status, pending updates, and recent history.', {
12
+ action: z.enum(['status', 'history']).default('status').describe('Check status or view history'),
13
+ limit: z.number().default(20).describe('Max history entries'),
14
+ }, async ({ action, limit }) => {
15
+ if (action === 'history') {
16
+ const ps = `
17
+ $session = New-Object -ComObject Microsoft.Update.Session
18
+ $searcher = $session.CreateUpdateSearcher()
19
+ $count = $searcher.GetTotalHistoryCount()
20
+ $history = $searcher.QueryHistory(0, [math]::Min($count, ${limit}))
21
+ $history | ForEach-Object {
22
+ [PSCustomObject]@{
23
+ Date = $_.Date.ToString('yyyy-MM-dd HH:mm')
24
+ Title = $_.Title
25
+ Result = switch ($_.ResultCode) { 0 {'Not Started'} 1 {'In Progress'} 2 {'Succeeded'} 3 {'Succeeded with Errors'} 4 {'Failed'} 5 {'Aborted'} default {'Unknown'} }
26
+ }
27
+ } | ConvertTo-Json -Depth 3 -Compress`;
28
+ const result = await runPowerShell(ps, { timeout: 30000 });
29
+ if (result.exitCode !== 0) {
30
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
31
+ }
32
+ if (!result.stdout) {
33
+ return { content: [{ type: 'text', text: 'No update history.' }] };
34
+ }
35
+ const entries = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
36
+ const lines = entries.map((e) => `${e.Result.padEnd(10)} ${e.Date} ${(e.Title || '').slice(0, 70)}`);
37
+ return { content: [{ type: 'text', text: `Recent updates:\n${lines.join('\n')}` }] };
38
+ }
39
+ // Status
40
+ const ps = `
41
+ $reboot = Test-Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired'
42
+ $lastCheck = (Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\Results\\Detect' -Name LastSuccessTime -ErrorAction SilentlyContinue).LastSuccessTime
43
+ [PSCustomObject]@{
44
+ RestartPending = $reboot
45
+ LastCheckTime = $lastCheck
46
+ } | ConvertTo-Json -Compress`;
47
+ const result = await runPowerShell(ps, { timeout: 15000 });
48
+ if (result.exitCode !== 0) {
49
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
50
+ }
51
+ const info = JSON.parse(result.stdout);
52
+ return {
53
+ content: [{
54
+ type: 'text',
55
+ text: `Windows Update:\n Restart pending: ${info.RestartPending}\n Last check: ${info.LastCheckTime || 'Unknown'}`,
56
+ }],
57
+ };
58
+ });
59
+ server.tool('windows_event_log', 'Read Windows Event Log entries. Filter by log, level, source, or time.', {
60
+ log: z.string().default('System').describe('Log name (System, Application, Security, etc.)'),
61
+ level: z.enum(['Critical', 'Error', 'Warning', 'Information', 'All']).default('All').describe('Event level'),
62
+ source: z.string().optional().describe('Event source filter'),
63
+ limit: z.number().default(20).describe('Max entries'),
64
+ hours: z.number().optional().describe('Only events from last N hours'),
65
+ }, async ({ log, level, source, limit, hours }) => {
66
+ const levelMap = {
67
+ Critical: '1', Error: '2', Warning: '3', Information: '4',
68
+ };
69
+ const levelFilter = level !== 'All' ? `-Level ${levelMap[level]}` : '';
70
+ const sourceFilter = source ? `-ProviderName '${source.replace(/'/g, "''")}'` : '';
71
+ const timeFilter = hours ? `-After (Get-Date).AddHours(-${hours})` : '';
72
+ const ps = `
73
+ Get-WinEvent -LogName '${log.replace(/'/g, "''")}' -MaxEvents ${limit} ${levelFilter ? `| Where-Object { $_.Level -eq ${levelMap[level]} }` : ''} -ErrorAction SilentlyContinue |
74
+ ${source ? `Where-Object { $_.ProviderName -like '*${source.replace(/'/g, "''")}*' } |` : ''}
75
+ ${hours ? `Where-Object { $_.TimeCreated -gt (Get-Date).AddHours(-${hours}) } |` : ''}
76
+ Select-Object -First ${limit} |
77
+ ForEach-Object {
78
+ [PSCustomObject]@{
79
+ Time = $_.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss')
80
+ Level = $_.LevelDisplayName
81
+ Source = $_.ProviderName
82
+ EventId = $_.Id
83
+ Message = ($_.Message -split [char]10)[0].Substring(0, [math]::Min(($_.Message -split [char]10)[0].Length, 100))
84
+ }
85
+ } | ConvertTo-Json -Depth 3 -Compress`;
86
+ const result = await runPowerShell(ps, { timeout: 20000 });
87
+ if (result.exitCode !== 0) {
88
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
89
+ }
90
+ if (!result.stdout) {
91
+ return { content: [{ type: 'text', text: 'No events found.' }] };
92
+ }
93
+ const events = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
94
+ const lines = events.map((e) => `${(e.Level || '').padEnd(12)} ${e.Time} ${String(e.EventId).padStart(5)} ${(e.Source || '').padEnd(25).slice(0, 25)} ${(e.Message || '').slice(0, 50)}`);
95
+ const header = `${'Level'.padEnd(12)} ${'Time'.padEnd(19)} ${'ID'.padStart(5)} ${'Source'.padEnd(25)} Message`;
96
+ return { content: [{ type: 'text', text: `${log} log:\n${header}\n${'─'.repeat(120)}\n${lines.join('\n')}` }] };
97
+ });
98
+ server.tool('windows_restore_point', 'List or create System Restore points.', {
99
+ action: z.enum(['list', 'create']).default('list').describe('Action'),
100
+ description: z.string().optional().describe('Description for new restore point'),
101
+ }, async ({ action, description }) => {
102
+ if (action === 'create') {
103
+ const desc = description || 'mcp_windows restore point';
104
+ const ps = `Checkpoint-Computer -Description '${desc.replace(/'/g, "''")}' -RestorePointType MODIFY_SETTINGS -ErrorAction Stop; "Restore point created: ${desc}"`;
105
+ const result = await runPowerShell(ps, { timeout: 60000 });
106
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }], isError: result.exitCode !== 0 };
107
+ }
108
+ const ps = `
109
+ try {
110
+ $points = Get-ComputerRestorePoint -ErrorAction Stop
111
+ if (-not $points) { Write-Output '[]'; return }
112
+ $points | ForEach-Object {
113
+ [PSCustomObject]@{
114
+ SequenceNumber = $_.SequenceNumber
115
+ Description = $_.Description
116
+ Type = switch ($_.RestorePointType) { 0 {'Application Install'} 1 {'Application Uninstall'} 10 {'Device Install'} 12 {'Modify Settings'} 13 {'Cancel'} default {'Other'} }
117
+ Date = $_.ConvertToDateTime($_.CreationTime).ToString('yyyy-MM-dd HH:mm')
118
+ }
119
+ } | ConvertTo-Json -Depth 3 -Compress
120
+ } catch {
121
+ Write-Output "RESTORE_ERROR:$($_.Exception.Message)"
122
+ }`;
123
+ const result = await runPowerShell(ps, { timeout: 15000 });
124
+ if (result.stdout?.startsWith('RESTORE_ERROR:')) {
125
+ const msg = result.stdout.replace('RESTORE_ERROR:', '');
126
+ return { content: [{ type: 'text', text: `System Restore: ${msg || 'Requires elevation or System Restore is disabled.'}` }] };
127
+ }
128
+ if (result.exitCode !== 0) {
129
+ return { content: [{ type: 'text', text: `Error: ${result.stderr || 'Requires elevation.'}` }], isError: true };
130
+ }
131
+ if (!result.stdout || result.stdout === '[]') {
132
+ return { content: [{ type: 'text', text: 'No restore points found (System Restore may be disabled or requires elevation).' }] };
133
+ }
134
+ const points = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
135
+ const lines = points.map((p) => ` #${p.SequenceNumber} ${p.Date} ${(p.Type || '').padEnd(20)} ${p.Description}`);
136
+ return { content: [{ type: 'text', text: `System Restore points:\n${lines.join('\n')}` }] };
137
+ });
138
+ server.tool('windows_certificate_list', 'List certificates in the Windows certificate store.', {
139
+ store: z.string().default('Cert:\\CurrentUser\\My').describe('Certificate store path'),
140
+ expiring_days: z.number().optional().describe('Only show certs expiring within N days'),
141
+ filter: z.string().optional().describe('Filter by subject (substring)'),
142
+ }, async ({ store, expiring_days, filter }) => {
143
+ const expiryFilter = expiring_days
144
+ ? `| Where-Object { $_.NotAfter -lt (Get-Date).AddDays(${expiring_days}) -and $_.NotAfter -gt (Get-Date) }`
145
+ : '';
146
+ const nameFilter = filter
147
+ ? `| Where-Object { $_.Subject -like '*${filter.replace(/'/g, "''")}*' }`
148
+ : '';
149
+ // Map store path to .NET enums
150
+ // Cert:\CurrentUser\My -> CurrentUser, My
151
+ const storeMatch = store.match(/Cert:\\\\?(CurrentUser|LocalMachine)\\\\?(\w+)/i);
152
+ const storeLocation = storeMatch ? storeMatch[1] : 'CurrentUser';
153
+ const storeName = storeMatch ? storeMatch[2] : 'My';
154
+ const ps = `
155
+ $store = New-Object System.Security.Cryptography.X509Certificates.X509Store('${storeName}', '${storeLocation}')
156
+ $store.Open('ReadOnly')
157
+ $certs = $store.Certificates
158
+ ${expiring_days ? `$certs = $certs | Where-Object { $_.NotAfter -lt (Get-Date).AddDays(${expiring_days}) -and $_.NotAfter -gt (Get-Date) }` : ''}
159
+ ${filter ? `$certs = $certs | Where-Object { $_.Subject -like '*${filter.replace(/'/g, "''")}*' }` : ''}
160
+ $certs | ForEach-Object {
161
+ [PSCustomObject]@{
162
+ Subject = $_.Subject
163
+ Issuer = $_.Issuer
164
+ Thumbprint = $_.Thumbprint
165
+ Expires = $_.NotAfter.ToString('yyyy-MM-dd')
166
+ KeyUsage = ($_.Extensions | Where-Object { $_ -is [System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension] } | ForEach-Object { ($_.EnhancedKeyUsages | ForEach-Object { $_.FriendlyName }) -join ', ' })
167
+ }
168
+ } | ConvertTo-Json -Depth 3 -Compress
169
+ $store.Close()`;
170
+ const result = await runPowerShell(ps, { timeout: 15000 });
171
+ if (result.exitCode !== 0) {
172
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
173
+ }
174
+ if (!result.stdout) {
175
+ return { content: [{ type: 'text', text: 'No certificates found.' }] };
176
+ }
177
+ const certs = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
178
+ const lines = certs.map((c) => ` ${c.Expires} ${(c.Thumbprint || '').slice(0, 16)}... ${(c.Subject || '').slice(0, 60)}`);
179
+ const header = ` ${'Expires'.padEnd(10)} ${'Thumbprint'.padEnd(19)} Subject`;
180
+ return { content: [{ type: 'text', text: `${store}\n${header}\n${'─'.repeat(90)}\n${lines.join('\n')}\n\n${certs.length} certificates` }] };
181
+ });
182
+ server.tool('windows_performance_monitor', 'Get real-time system performance: CPU per core, RAM, disk I/O, network throughput, top processes.', {}, async () => {
183
+ const ps = `
184
+ $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
185
+ $os = Get-CimInstance Win32_OperatingSystem
186
+ $cs = Get-CimInstance Win32_ComputerSystem
187
+ $perfDisk = Get-CimInstance Win32_PerfFormattedData_PerfDisk_LogicalDisk -Filter "Name='_Total'" -ErrorAction SilentlyContinue
188
+ $perfNet = Get-CimInstance Win32_PerfFormattedData_Tcpip_NetworkInterface -ErrorAction SilentlyContinue | Select-Object -First 1
189
+
190
+ $topCPU = Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 | ForEach-Object {
191
+ [PSCustomObject]@{ Name = $_.ProcessName; PID = $_.Id; CPU = [math]::Round($_.CPU, 1); MemMB = [math]::Round($_.WorkingSet64 / 1MB, 1) }
192
+ }
193
+ $topMem = Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 5 | ForEach-Object {
194
+ [PSCustomObject]@{ Name = $_.ProcessName; PID = $_.Id; MemMB = [math]::Round($_.WorkingSet64 / 1MB, 1) }
195
+ }
196
+
197
+ [PSCustomObject]@{
198
+ CPUUsage = "$([math]::Round($cpu.LoadPercentage, 0))%"
199
+ CPUCores = $cpu.NumberOfLogicalProcessors
200
+ RAMTotalGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 1)
201
+ RAMUsedGB = [math]::Round(($cs.TotalPhysicalMemory - $os.FreePhysicalMemory * 1KB) / 1GB, 1)
202
+ RAMUsedPct = [math]::Round(($cs.TotalPhysicalMemory - $os.FreePhysicalMemory * 1KB) / $cs.TotalPhysicalMemory * 100, 1)
203
+ DiskReadBps = if ($perfDisk) { "$([math]::Round($perfDisk.DiskReadBytesPersec / 1MB, 2)) MB/s" } else { 'N/A' }
204
+ DiskWriteBps = if ($perfDisk) { "$([math]::Round($perfDisk.DiskWriteBytesPersec / 1MB, 2)) MB/s" } else { 'N/A' }
205
+ NetSentBps = if ($perfNet) { "$([math]::Round($perfNet.BytesSentPersec / 1MB, 2)) MB/s" } else { 'N/A' }
206
+ NetRecvBps = if ($perfNet) { "$([math]::Round($perfNet.BytesReceivedPersec / 1MB, 2)) MB/s" } else { 'N/A' }
207
+ TopCPU = $topCPU
208
+ TopMem = $topMem
209
+ } | ConvertTo-Json -Depth 4 -Compress`;
210
+ const result = await runPowerShell(ps, { timeout: 30000 });
211
+ if (result.exitCode !== 0) {
212
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
213
+ }
214
+ const p = JSON.parse(result.stdout);
215
+ const topCpu = Array.isArray(p.TopCPU) ? p.TopCPU : [p.TopCPU].filter(Boolean);
216
+ const topMem = Array.isArray(p.TopMem) ? p.TopMem : [p.TopMem].filter(Boolean);
217
+ return {
218
+ content: [{
219
+ type: 'text',
220
+ text: [
221
+ `CPU: ${p.CPUUsage} (${p.CPUCores} threads)`,
222
+ `RAM: ${p.RAMUsedGB}/${p.RAMTotalGB} GB (${p.RAMUsedPct}%)`,
223
+ `Disk: Read ${p.DiskReadBps} / Write ${p.DiskWriteBps}`,
224
+ `Net: Send ${p.NetSentBps} / Recv ${p.NetRecvBps}`,
225
+ '',
226
+ 'Top by CPU:',
227
+ ...topCpu.map((t) => ` ${String(t.PID).padStart(6)} ${t.Name.padEnd(20)} ${String(t.CPU).padStart(8)}s ${String(t.MemMB).padStart(8)} MB`),
228
+ '',
229
+ 'Top by Memory:',
230
+ ...topMem.map((t) => ` ${String(t.PID).padStart(6)} ${t.Name.padEnd(20)} ${String(t.MemMB).padStart(8)} MB`),
231
+ ].join('\n'),
232
+ }],
233
+ };
234
+ });
235
+ }
236
+ //# sourceMappingURL=maintenance.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerNetstatTools(server: McpServer): void;
3
+ //# sourceMappingURL=netstat.d.ts.map
@@ -0,0 +1,56 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tool: windows_network_connections (#23)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerNetstatTools(server) {
9
+ server.tool('windows_network_connections', 'List active TCP/UDP network connections (like netstat). Shows local/remote address, state, and owning process.', {
10
+ state: z.enum(['all', 'listen', 'established', 'time_wait', 'close_wait']).default('all').describe('Filter by state'),
11
+ port: z.number().optional().describe('Filter by port number'),
12
+ process_name: z.string().optional().describe('Filter by process name'),
13
+ limit: z.number().default(50).describe('Max results'),
14
+ }, async ({ state, port, process_name, limit }) => {
15
+ const stateMap = {
16
+ listen: "| Where-Object { $_.State -eq 'Listen' }",
17
+ established: "| Where-Object { $_.State -eq 'Established' }",
18
+ time_wait: "| Where-Object { $_.State -eq 'TimeWait' }",
19
+ close_wait: "| Where-Object { $_.State -eq 'CloseWait' }",
20
+ all: '',
21
+ };
22
+ const portFilter = port
23
+ ? `| Where-Object { $_.LocalPort -eq ${port} -or $_.RemotePort -eq ${port} }`
24
+ : '';
25
+ const procFilter = process_name
26
+ ? `| Where-Object { $procName -like '*${process_name.replace(/'/g, "''")}*' }`
27
+ : '';
28
+ const ps = `
29
+ Get-NetTCPConnection -ErrorAction SilentlyContinue ${stateMap[state]} ${portFilter} | ForEach-Object {
30
+ $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue
31
+ $procName = if ($proc) { $proc.ProcessName } else { '?' }
32
+ [PSCustomObject]@{
33
+ Proto = 'TCP'
34
+ LocalAddr = "$($_.LocalAddress):$($_.LocalPort)"
35
+ RemoteAddr = "$($_.RemoteAddress):$($_.RemotePort)"
36
+ State = $_.State.ToString()
37
+ PID = $_.OwningProcess
38
+ Process = $procName
39
+ }
40
+ } ${procFilter} | Select-Object -First ${limit} | ConvertTo-Json -Depth 3 -Compress`;
41
+ const result = await runPowerShell(ps, { timeout: 15000 });
42
+ if (result.exitCode !== 0) {
43
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
44
+ }
45
+ if (!result.stdout) {
46
+ return { content: [{ type: 'text', text: 'No connections found.' }] };
47
+ }
48
+ const conns = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
49
+ const lines = conns.map((c) => `${c.Proto} ${c.LocalAddr.padEnd(22)} ${c.RemoteAddr.padEnd(22)} ${c.State.padEnd(12)} ${String(c.PID).padStart(6)} ${c.Process}`);
50
+ const header = `Proto ${'Local Address'.padEnd(22)} ${'Remote Address'.padEnd(22)} ${'State'.padEnd(12)} ${'PID'.padStart(6)} Process`;
51
+ return {
52
+ content: [{ type: 'text', text: `${header}\n${'─'.repeat(100)}\n${lines.join('\n')}\n\n${conns.length} connections` }],
53
+ };
54
+ });
55
+ }
56
+ //# sourceMappingURL=netstat.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerNetworkTools(server: McpServer): void;
3
+ //# sourceMappingURL=network.d.ts.map
@@ -0,0 +1,70 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tool: windows_network_info (#22)
5
+ */
6
+ import { runPowerShell } from '../shell.js';
7
+ export function registerNetworkTools(server) {
8
+ server.tool('windows_network_info', 'Get network configuration: adapters, IPs, DNS, gateway, Wi-Fi status, and connectivity.', {}, async () => {
9
+ const ps = `
10
+ $adapters = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } | ForEach-Object {
11
+ $ipInfo = Get-NetIPAddress -InterfaceIndex $_.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue
12
+ $dns = (Get-DnsClientServerAddress -InterfaceIndex $_.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue).ServerAddresses
13
+ $gw = (Get-NetRoute -InterfaceIndex $_.ifIndex -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue).NextHop
14
+ [PSCustomObject]@{
15
+ Name = $_.Name
16
+ Description = $_.InterfaceDescription
17
+ Type = $_.MediaType
18
+ Status = $_.Status
19
+ Speed = $_.LinkSpeed
20
+ MAC = $_.MacAddress
21
+ IP = if ($ipInfo) { $ipInfo.IPAddress -join ', ' } else { 'N/A' }
22
+ Subnet = if ($ipInfo) { $ipInfo.PrefixLength -join ', ' } else { 'N/A' }
23
+ Gateway = if ($gw) { $gw -join ', ' } else { 'N/A' }
24
+ DNS = if ($dns) { $dns -join ', ' } else { 'N/A' }
25
+ }
26
+ }
27
+
28
+ # Wi-Fi info
29
+ $wifi = $null
30
+ try {
31
+ $wifiProfile = netsh wlan show interfaces 2>$null
32
+ if ($wifiProfile) {
33
+ $ssid = ($wifiProfile | Select-String 'SSID\s+:' | Select-Object -First 1) -replace '.*:\s*',''
34
+ $signal = ($wifiProfile | Select-String 'Signal' | Select-Object -First 1) -replace '.*:\s*',''
35
+ $auth = ($wifiProfile | Select-String 'Authentication' | Select-Object -First 1) -replace '.*:\s*',''
36
+ $wifi = [PSCustomObject]@{ SSID = $ssid.Trim(); Signal = $signal.Trim(); Security = $auth.Trim() }
37
+ }
38
+ } catch {}
39
+
40
+ # Connectivity
41
+ $connected = Test-Connection -ComputerName 8.8.8.8 -Count 1 -Quiet -ErrorAction SilentlyContinue
42
+
43
+ [PSCustomObject]@{
44
+ Adapters = $adapters
45
+ WiFi = $wifi
46
+ InternetConnected = $connected
47
+ } | ConvertTo-Json -Depth 4 -Compress`;
48
+ const result = await runPowerShell(ps, { timeout: 15000 });
49
+ if (result.exitCode !== 0) {
50
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
51
+ }
52
+ const info = JSON.parse(result.stdout);
53
+ const lines = [];
54
+ lines.push(`Internet: ${info.InternetConnected ? 'Connected' : 'Disconnected'}`);
55
+ if (info.WiFi) {
56
+ lines.push(`Wi-Fi: ${info.WiFi.SSID} (${info.WiFi.Signal}, ${info.WiFi.Security})`);
57
+ }
58
+ lines.push('');
59
+ lines.push('Adapters:');
60
+ const adapters = Array.isArray(info.Adapters) ? info.Adapters : [info.Adapters];
61
+ for (const a of adapters.filter(Boolean)) {
62
+ lines.push(` ${a.Name} (${a.Description})`);
63
+ lines.push(` IP: ${a.IP}/${a.Subnet} Gateway: ${a.Gateway}`);
64
+ lines.push(` DNS: ${a.DNS}`);
65
+ lines.push(` MAC: ${a.MAC} Speed: ${a.Speed}`);
66
+ }
67
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
68
+ });
69
+ }
70
+ //# sourceMappingURL=network.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerNotificationTools(server: McpServer): void;
3
+ //# sourceMappingURL=notification.d.ts.map
@@ -0,0 +1,41 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tool: windows_notification_send (#20)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerNotificationTools(server) {
9
+ server.tool('windows_notification_send', 'Send a Windows toast notification with title, body, and optional icon.', {
10
+ title: z.string().describe('Notification title'),
11
+ body: z.string().describe('Notification body text'),
12
+ icon: z.string().optional().describe('Path to icon file (optional)'),
13
+ sound: z.boolean().default(true).describe('Play notification sound'),
14
+ }, async ({ title, body, icon, sound }) => {
15
+ const iconParam = icon
16
+ ? `$notify.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon('${icon.replace(/'/g, "''")}');`
17
+ : `$notify.Icon = [System.Drawing.SystemIcons]::Information;`;
18
+ const ps = `
19
+ Add-Type -AssemblyName System.Windows.Forms
20
+ Add-Type -AssemblyName System.Drawing
21
+
22
+ $notify = New-Object System.Windows.Forms.NotifyIcon
23
+ ${iconParam}
24
+ $notify.Visible = $true
25
+ $notify.BalloonTipTitle = '${title.replace(/'/g, "''")}'
26
+ $notify.BalloonTipText = '${body.replace(/'/g, "''")}'
27
+ $notify.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Info
28
+ $notify.ShowBalloonTip(5000)
29
+
30
+ # Keep alive briefly so notification displays
31
+ Start-Sleep -Milliseconds 100
32
+ $notify.Dispose()
33
+ "Notification sent: ${title.replace(/"/g, '\\"')}"`;
34
+ const result = await runPowerShell(ps, { timeout: 10000 });
35
+ return {
36
+ content: [{ type: 'text', text: result.stdout || result.stderr }],
37
+ isError: result.exitCode !== 0,
38
+ };
39
+ });
40
+ }
41
+ //# sourceMappingURL=notification.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerPowerTools(server: McpServer): void;
3
+ //# sourceMappingURL=power.d.ts.map
@@ -0,0 +1,104 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tools: windows_power_get (#12), windows_power_action (#13)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerPowerTools(server) {
9
+ server.tool('windows_power_get', 'Get power state: battery level, AC/battery, power plan, screen/sleep timeouts.', {}, async () => {
10
+ const ps = `
11
+ $battery = Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue
12
+ $plan = powercfg /getactivescheme 2>$null
13
+ $planName = if ($plan) { ($plan -replace '^.*\\((.*)\\).*$','$1').Trim() } else { 'Unknown' }
14
+
15
+ # Get timeout values
16
+ $acScreen = (powercfg /query SCHEME_CURRENT SUB_VIDEO VIDEOIDLE 2>$null | Select-String 'Current AC Power Setting Index' | ForEach-Object { ($_ -split '0x')[1] }) -as [int]
17
+ $dcScreen = (powercfg /query SCHEME_CURRENT SUB_VIDEO VIDEOIDLE 2>$null | Select-String 'Current DC Power Setting Index' | ForEach-Object { ($_ -split '0x')[1] }) -as [int]
18
+ $acSleep = (powercfg /query SCHEME_CURRENT SUB_SLEEP STANDBYIDLE 2>$null | Select-String 'Current AC Power Setting Index' | ForEach-Object { ($_ -split '0x')[1] }) -as [int]
19
+ $dcSleep = (powercfg /query SCHEME_CURRENT SUB_SLEEP STANDBYIDLE 2>$null | Select-String 'Current DC Power Setting Index' | ForEach-Object { ($_ -split '0x')[1] }) -as [int]
20
+
21
+ [PSCustomObject]@{
22
+ HasBattery = $null -ne $battery
23
+ BatteryPct = if ($battery) { $battery.EstimatedChargeRemaining } else { $null }
24
+ Charging = if ($battery) { $battery.BatteryStatus -eq 2 } else { $null }
25
+ ACPower = if ($battery) { $battery.BatteryStatus -eq 2 -or $battery.BatteryStatus -eq 6 } else { $true }
26
+ TimeRemaining = if ($battery -and $battery.EstimatedRunTime -and $battery.EstimatedRunTime -lt 71582788) { "$([math]::Floor($battery.EstimatedRunTime / 60))h $($battery.EstimatedRunTime % 60)m" } else { $null }
27
+ PowerPlan = $planName
28
+ ScreenTimeout_AC = if ($acScreen) { "$([math]::Floor($acScreen / 60))m" } else { 'Never' }
29
+ ScreenTimeout_DC = if ($dcScreen) { "$([math]::Floor($dcScreen / 60))m" } else { 'Never' }
30
+ SleepTimeout_AC = if ($acSleep) { "$([math]::Floor($acSleep / 60))m" } else { 'Never' }
31
+ SleepTimeout_DC = if ($dcSleep) { "$([math]::Floor($dcSleep / 60))m" } else { 'Never' }
32
+ } | ConvertTo-Json -Compress`;
33
+ const result = await runPowerShell(ps, { timeout: 30000 });
34
+ if (result.exitCode !== 0) {
35
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
36
+ }
37
+ const info = JSON.parse(result.stdout);
38
+ const lines = [];
39
+ if (info.HasBattery) {
40
+ const icon = info.Charging ? '🔌' : '🔋';
41
+ lines.push(`${icon} Battery: ${info.BatteryPct}%${info.Charging ? ' (charging)' : ''}`);
42
+ if (info.TimeRemaining)
43
+ lines.push(`Estimated remaining: ${info.TimeRemaining}`);
44
+ }
45
+ else {
46
+ lines.push(`AC Power (no battery)`);
47
+ }
48
+ lines.push(`Power Plan: ${info.PowerPlan}`);
49
+ lines.push(``);
50
+ lines.push(`Screen timeout: ${info.ScreenTimeout_AC} (AC) / ${info.ScreenTimeout_DC} (battery)`);
51
+ lines.push(`Sleep timeout: ${info.SleepTimeout_AC} (AC) / ${info.SleepTimeout_DC} (battery)`);
52
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
53
+ });
54
+ server.tool('windows_power_action', 'Execute power actions: sleep, hibernate, lock, shutdown, restart, or switch power plan.', {
55
+ action: z.enum(['sleep', 'hibernate', 'lock', 'shutdown', 'restart', 'logoff', 'plan']).describe('Power action'),
56
+ delay: z.number().optional().describe('Delay in seconds (for shutdown/restart)'),
57
+ cancel: z.boolean().optional().describe('Cancel a scheduled shutdown/restart'),
58
+ plan: z.enum(['balanced', 'performance', 'powersaver']).optional().describe('Power plan to switch to (when action=plan)'),
59
+ }, async ({ action, delay, cancel, plan }) => {
60
+ if (cancel) {
61
+ const result = await runPowerShell('shutdown /a 2>&1; "Scheduled shutdown cancelled"');
62
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
63
+ }
64
+ let ps;
65
+ const delayArg = delay ? `/t ${delay}` : '';
66
+ switch (action) {
67
+ case 'sleep':
68
+ ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Application]::SetSuspendState('Suspend', $false, $false); "Sleeping..."`;
69
+ break;
70
+ case 'hibernate':
71
+ ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Application]::SetSuspendState('Hibernate', $false, $false); "Hibernating..."`;
72
+ break;
73
+ case 'lock':
74
+ ps = `rundll32.exe user32.dll,LockWorkStation; "Workstation locked"`;
75
+ break;
76
+ case 'shutdown':
77
+ ps = `shutdown /s /f ${delayArg}; "Shutdown initiated${delay ? ` in ${delay}s` : ''}"`;
78
+ break;
79
+ case 'restart':
80
+ ps = `shutdown /r /f ${delayArg}; "Restart initiated${delay ? ` in ${delay}s` : ''}"`;
81
+ break;
82
+ case 'logoff':
83
+ ps = `shutdown /l; "Logging off..."`;
84
+ break;
85
+ case 'plan':
86
+ if (!plan) {
87
+ return { content: [{ type: 'text', text: 'Specify a plan: balanced, performance, or powersaver' }], isError: true };
88
+ }
89
+ const planGuids = {
90
+ balanced: '381b4222-f694-41f0-9685-ff5bb260df2e',
91
+ performance: '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c',
92
+ powersaver: 'a1841308-3541-4fab-bc81-f71556f20b4a',
93
+ };
94
+ ps = `powercfg /setactive ${planGuids[plan]}; $p = powercfg /getactivescheme; "Switched to: $($p -replace '^.*\\((.*)\\).*$','$1')"`;
95
+ break;
96
+ }
97
+ const result = await runPowerShell(ps, { timeout: 30000 });
98
+ return {
99
+ content: [{ type: 'text', text: result.stdout || result.stderr }],
100
+ isError: result.exitCode !== 0,
101
+ };
102
+ });
103
+ }
104
+ //# sourceMappingURL=power.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerPrinterTools(server: McpServer): void;
3
+ //# sourceMappingURL=printer.d.ts.map
@@ -0,0 +1,97 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tool: windows_printer_list (#50)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerPrinterTools(server) {
9
+ server.tool('windows_printer_list', 'List printers, show print queues, set default, or clear a queue.', {
10
+ action: z.enum(['list', 'queue', 'set_default', 'clear_queue']).default('list').describe('Action'),
11
+ printer: z.string().optional().describe('Printer name (for queue/set_default/clear_queue)'),
12
+ }, async ({ action, printer }) => {
13
+ switch (action) {
14
+ case 'list': {
15
+ const ps = `
16
+ Get-Printer -ErrorAction SilentlyContinue | ForEach-Object {
17
+ $default = if ((Get-CimInstance Win32_Printer -Filter "Name='$($_.Name.Replace("'","''"))'" -ErrorAction SilentlyContinue).Default) { $true } else { $false }
18
+ [PSCustomObject]@{
19
+ Name = $_.Name
20
+ Status = $_.PrinterStatus
21
+ Type = $_.Type
22
+ Port = $_.PortName
23
+ Driver = $_.DriverName
24
+ Default = $default
25
+ Shared = $_.Shared
26
+ }
27
+ } | ConvertTo-Json -Depth 3 -Compress`;
28
+ const result = await runPowerShell(ps, { timeout: 45000 });
29
+ if (result.exitCode !== 0) {
30
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
31
+ }
32
+ if (!result.stdout) {
33
+ return { content: [{ type: 'text', text: 'No printers found.' }] };
34
+ }
35
+ const printers = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
36
+ const lines = printers.map((p) => {
37
+ const def = p.Default ? ' *' : ' ';
38
+ return `${def} ${(p.Name || '').padEnd(35).slice(0, 35)} ${(p.Driver || '').padEnd(25).slice(0, 25)} ${p.Port || ''}`;
39
+ });
40
+ const header = ` ${'Name'.padEnd(35)} ${'Driver'.padEnd(25)} Port`;
41
+ return {
42
+ content: [{ type: 'text', text: `${header}\n${'─'.repeat(80)}\n${lines.join('\n')}\n\n${printers.length} printers (* = default)` }],
43
+ };
44
+ }
45
+ case 'queue': {
46
+ if (!printer) {
47
+ return { content: [{ type: 'text', text: 'Queue requires printer name.' }], isError: true };
48
+ }
49
+ const ps = `
50
+ Get-PrintJob -PrinterName '${printer.replace(/'/g, "''")}' -ErrorAction Stop | ForEach-Object {
51
+ [PSCustomObject]@{
52
+ Id = $_.Id
53
+ Document = $_.DocumentName
54
+ Status = $_.JobStatus
55
+ Pages = $_.TotalPages
56
+ Size = "$([math]::Round($_.Size / 1KB, 1)) KB"
57
+ Submitted = $_.SubmittedTime.ToString('yyyy-MM-dd HH:mm')
58
+ }
59
+ } | ConvertTo-Json -Depth 3 -Compress`;
60
+ const result = await runPowerShell(ps, { timeout: 10000 });
61
+ if (result.exitCode !== 0) {
62
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
63
+ }
64
+ if (!result.stdout) {
65
+ return { content: [{ type: 'text', text: `Print queue for "${printer}" is empty.` }] };
66
+ }
67
+ const jobs = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
68
+ const lines = jobs.map((j) => ` #${j.Id} ${(j.Document || '').padEnd(30).slice(0, 30)} ${(j.Status || '').padEnd(12)} ${j.Pages} pg ${j.Size}`);
69
+ return { content: [{ type: 'text', text: `Queue for "${printer}":\n${lines.join('\n')}` }] };
70
+ }
71
+ case 'set_default': {
72
+ if (!printer) {
73
+ return { content: [{ type: 'text', text: 'set_default requires printer name.' }], isError: true };
74
+ }
75
+ const ps = `
76
+ $p = Get-CimInstance Win32_Printer -Filter "Name='${printer.replace(/'/g, "''")}'" -ErrorAction Stop
77
+ Invoke-CimMethod -InputObject $p -MethodName SetDefaultPrinter | Out-Null
78
+ "Default printer set to: ${printer}"`;
79
+ const result = await runPowerShell(ps, { timeout: 10000 });
80
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
81
+ }
82
+ case 'clear_queue': {
83
+ if (!printer) {
84
+ return { content: [{ type: 'text', text: 'clear_queue requires printer name.' }], isError: true };
85
+ }
86
+ const ps = `
87
+ $jobs = Get-PrintJob -PrinterName '${printer.replace(/'/g, "''")}' -ErrorAction Stop
88
+ $count = @($jobs).Count
89
+ $jobs | Remove-PrintJob -ErrorAction SilentlyContinue
90
+ "Cleared $count job(s) from ${printer}"`;
91
+ const result = await runPowerShell(ps, { timeout: 10000 });
92
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
93
+ }
94
+ }
95
+ });
96
+ }
97
+ //# sourceMappingURL=printer.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerProcessTools(server: McpServer): void;
3
+ //# sourceMappingURL=process.d.ts.map