@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,159 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tools: windows_startup_list (#33), windows_startup_manage (#34)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerStartupTools(server) {
9
+ server.tool('windows_startup_list', 'List all startup items from registry (Run/RunOnce), startup folder, and scheduled logon tasks.', {}, async () => {
10
+ const ps = `
11
+ $items = [System.Collections.Generic.List[PSObject]]::new()
12
+
13
+ # Registry: HKCU Run
14
+ $regPaths = @(
15
+ @{ Path = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'; Scope = 'User'; Source = 'Registry Run' }
16
+ @{ Path = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce'; Scope = 'User'; Source = 'Registry RunOnce' }
17
+ @{ Path = 'HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'; Scope = 'System'; Source = 'Registry Run' }
18
+ @{ Path = 'HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce'; Scope = 'System'; Source = 'Registry RunOnce' }
19
+ )
20
+
21
+ foreach ($rp in $regPaths) {
22
+ if (Test-Path $rp.Path) {
23
+ $props = Get-ItemProperty -Path $rp.Path -ErrorAction SilentlyContinue
24
+ if ($props) {
25
+ $props.PSObject.Properties | Where-Object { $_.Name -notlike 'PS*' } | ForEach-Object {
26
+ $items.Add([PSCustomObject]@{
27
+ Name = $_.Name
28
+ Command = $_.Value
29
+ Source = $rp.Source
30
+ Scope = $rp.Scope
31
+ Enabled = $true
32
+ })
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ # Startup folder
39
+ $userStartup = [Environment]::GetFolderPath('Startup')
40
+ $commonStartup = [Environment]::GetFolderPath('CommonStartup')
41
+ foreach ($folder in @(@{Path=$userStartup;Scope='User'}, @{Path=$commonStartup;Scope='System'})) {
42
+ if (Test-Path $folder.Path) {
43
+ Get-ChildItem -Path $folder.Path -File -ErrorAction SilentlyContinue | ForEach-Object {
44
+ $items.Add([PSCustomObject]@{
45
+ Name = $_.BaseName
46
+ Command = $_.FullName
47
+ Source = 'Startup Folder'
48
+ Scope = $folder.Scope
49
+ Enabled = $true
50
+ })
51
+ }
52
+ }
53
+ }
54
+
55
+ # Scheduled tasks at logon
56
+ Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object {
57
+ $_.Triggers | Where-Object { $_ -is [CimInstance] -and $_.CimClass.CimClassName -eq 'MSFT_TaskLogonTrigger' }
58
+ } | ForEach-Object {
59
+ $items.Add([PSCustomObject]@{
60
+ Name = $_.TaskName
61
+ Command = ($_.Actions | ForEach-Object { $_.Execute }) -join ' '
62
+ Source = 'Task Scheduler (Logon)'
63
+ Scope = 'System'
64
+ Enabled = $_.State -eq 'Ready'
65
+ })
66
+ }
67
+
68
+ $items | ConvertTo-Json -Depth 3 -Compress`;
69
+ const result = await runPowerShell(ps, { timeout: 20000 });
70
+ if (result.exitCode !== 0) {
71
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
72
+ }
73
+ if (!result.stdout) {
74
+ return { content: [{ type: 'text', text: 'No startup items found.' }] };
75
+ }
76
+ const items = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
77
+ const lines = items.map((i) => {
78
+ const status = i.Enabled ? '[ON] ' : '[OFF]';
79
+ return `${status} ${i.Scope.padEnd(6)} ${i.Source.padEnd(24)} ${i.Name.padEnd(30).slice(0, 30)} ${(i.Command || '').slice(0, 50)}`;
80
+ });
81
+ const header = `State ${'Scope'.padEnd(6)} ${'Source'.padEnd(24)} ${'Name'.padEnd(30)} Command`;
82
+ return {
83
+ content: [{ type: 'text', text: `${header}\n${'─'.repeat(110)}\n${lines.join('\n')}\n\n${items.length} startup items` }],
84
+ };
85
+ });
86
+ server.tool('windows_startup_manage', 'Add, remove, enable, or disable a startup item.', {
87
+ action: z.enum(['add', 'remove', 'enable', 'disable']).describe('Action'),
88
+ name: z.string().describe('Startup item name'),
89
+ command: z.string().optional().describe('Command to run at startup (for add)'),
90
+ location: z.enum(['registry', 'startup_folder']).default('registry').describe('Where to add (for add)'),
91
+ }, async ({ action, name, command, location }) => {
92
+ let ps;
93
+ switch (action) {
94
+ case 'add':
95
+ if (!command) {
96
+ return { content: [{ type: 'text', text: 'Add requires a command.' }], isError: true };
97
+ }
98
+ if (location === 'startup_folder') {
99
+ ps = `
100
+ $startupPath = [Environment]::GetFolderPath('Startup')
101
+ $shortcutPath = Join-Path $startupPath '${name.replace(/'/g, "''")}.lnk'
102
+ $ws = New-Object -ComObject WScript.Shell
103
+ $sc = $ws.CreateShortcut($shortcutPath)
104
+ $sc.TargetPath = '${command.replace(/'/g, "''")}'
105
+ $sc.Save()
106
+ "Added startup shortcut: $shortcutPath"`;
107
+ }
108
+ else {
109
+ ps = `
110
+ Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name '${name.replace(/'/g, "''")}' -Value '${command.replace(/'/g, "''")}' -ErrorAction Stop
111
+ "Added to HKCU Run: ${name}"`;
112
+ }
113
+ break;
114
+ case 'remove':
115
+ ps = `
116
+ $removed = $false
117
+ # Try registry
118
+ $regPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
119
+ if ((Get-ItemProperty -Path $regPath -Name '${name.replace(/'/g, "''")}' -ErrorAction SilentlyContinue)) {
120
+ Remove-ItemProperty -Path $regPath -Name '${name.replace(/'/g, "''")}' -ErrorAction Stop
121
+ $removed = $true
122
+ }
123
+ # Try startup folder
124
+ $startupPath = [Environment]::GetFolderPath('Startup')
125
+ $lnk = Join-Path $startupPath '${name.replace(/'/g, "''")}.lnk'
126
+ if (Test-Path $lnk) { Remove-Item $lnk -Force; $removed = $true }
127
+ if ($removed) { "Removed: ${name}" } else { "Not found: ${name}" }`;
128
+ break;
129
+ case 'disable':
130
+ ps = `
131
+ $regPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
132
+ $val = (Get-ItemProperty -Path $regPath -Name '${name.replace(/'/g, "''")}' -ErrorAction SilentlyContinue).'${name.replace(/'/g, "''")}'
133
+ if ($val) {
134
+ Remove-ItemProperty -Path $regPath -Name '${name.replace(/'/g, "''")}' -ErrorAction Stop
135
+ $disabledPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run_Disabled'
136
+ if (-not (Test-Path $disabledPath)) { New-Item -Path $disabledPath -Force | Out-Null }
137
+ Set-ItemProperty -Path $disabledPath -Name '${name.replace(/'/g, "''")}' -Value $val
138
+ "Disabled: ${name}"
139
+ } else { "Not found in registry Run: ${name}" }`;
140
+ break;
141
+ case 'enable':
142
+ ps = `
143
+ $disabledPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run_Disabled'
144
+ $val = (Get-ItemProperty -Path $disabledPath -Name '${name.replace(/'/g, "''")}' -ErrorAction SilentlyContinue).'${name.replace(/'/g, "''")}'
145
+ if ($val) {
146
+ Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name '${name.replace(/'/g, "''")}' -Value $val
147
+ Remove-ItemProperty -Path $disabledPath -Name '${name.replace(/'/g, "''")}' -ErrorAction SilentlyContinue
148
+ "Enabled: ${name}"
149
+ } else { "Not found in disabled items: ${name}" }`;
150
+ break;
151
+ }
152
+ const result = await runPowerShell(ps, { timeout: 10000 });
153
+ return {
154
+ content: [{ type: 'text', text: result.stdout || result.stderr }],
155
+ isError: result.exitCode !== 0,
156
+ };
157
+ });
158
+ }
159
+ //# sourceMappingURL=startup.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerStorageTools(server: McpServer): void;
3
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1,129 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tools: windows_disk_cleanup (#72), windows_symlink (#73)
5
+ */
6
+ import { z } from 'zod';
7
+ import { runPowerShell } from '../shell.js';
8
+ export function registerStorageTools(server) {
9
+ server.tool('windows_disk_cleanup', 'Analyze disk usage and clean temp files, caches, and logs.', {
10
+ action: z.enum(['analyze', 'clean']).default('analyze').describe('Analyze or clean'),
11
+ drive: z.string().default('C:').describe('Drive letter'),
12
+ }, async ({ action, drive }) => {
13
+ if (action === 'analyze') {
14
+ const ps = `
15
+ $tempUser = [IO.Path]::GetTempPath()
16
+ $tempWin = "$env:WINDIR\\Temp"
17
+ $downloads = [Environment]::GetFolderPath('UserProfile') + '\\Downloads'
18
+
19
+ function Get-FolderSize($path) {
20
+ if (Test-Path $path) {
21
+ $size = (Get-ChildItem $path -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
22
+ [math]::Round($size / 1MB, 1)
23
+ } else { 0 }
24
+ }
25
+
26
+ $disk = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='${drive.replace(/'/g, "''")}'"
27
+
28
+ [PSCustomObject]@{
29
+ Drive = '${drive}'
30
+ TotalGB = [math]::Round($disk.Size / 1GB, 1)
31
+ FreeGB = [math]::Round($disk.FreeSpace / 1GB, 1)
32
+ UsedPct = [math]::Round(($disk.Size - $disk.FreeSpace) / $disk.Size * 100, 1)
33
+ TempUserMB = Get-FolderSize $tempUser
34
+ TempWindowsMB = Get-FolderSize $tempWin
35
+ DownloadsMB = Get-FolderSize $downloads
36
+ RecycleBinItems = (New-Object -ComObject Shell.Application).Namespace(10).Items().Count
37
+ } | ConvertTo-Json -Compress`;
38
+ const result = await runPowerShell(ps, { timeout: 30000 });
39
+ if (result.exitCode !== 0) {
40
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
41
+ }
42
+ const d = JSON.parse(result.stdout);
43
+ return {
44
+ content: [{
45
+ type: 'text',
46
+ text: [
47
+ `${d.Drive} — ${d.FreeGB}/${d.TotalGB} GB free (${d.UsedPct}% used)`,
48
+ ``,
49
+ `Cleanable:`,
50
+ ` User temp: ${d.TempUserMB} MB`,
51
+ ` Windows temp: ${d.TempWindowsMB} MB`,
52
+ ` Downloads: ${d.DownloadsMB} MB`,
53
+ ` Recycle Bin: ${d.RecycleBinItems} items`,
54
+ ].join('\n'),
55
+ }],
56
+ };
57
+ }
58
+ const ps = `
59
+ $before = (Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='${drive.replace(/'/g, "''")}'").FreeSpace
60
+ Remove-Item "$env:TEMP\\*" -Recurse -Force -ErrorAction SilentlyContinue
61
+ Remove-Item "$env:WINDIR\\Temp\\*" -Recurse -Force -ErrorAction SilentlyContinue
62
+ $after = (Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='${drive.replace(/'/g, "''")}'").FreeSpace
63
+ $freed = [math]::Round(($after - $before) / 1MB, 1)
64
+ "Cleaned temp files. Freed: $freed MB"`;
65
+ const result = await runPowerShell(ps, { timeout: 30000 });
66
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
67
+ });
68
+ server.tool('windows_symlink', 'Create and manage symbolic links, hard links, and directory junctions.', {
69
+ action: z.enum(['create', 'list', 'resolve', 'remove']).default('list').describe('Action'),
70
+ target: z.string().optional().describe('Target path (what the link points to)'),
71
+ link: z.string().optional().describe('Link path (the symlink itself)'),
72
+ type: z.enum(['symlink', 'junction', 'hardlink']).default('symlink').describe('Link type (for create)'),
73
+ path: z.string().optional().describe('Directory to list symlinks in (for list)'),
74
+ }, async ({ action, target, link, type, path }) => {
75
+ switch (action) {
76
+ case 'create': {
77
+ if (!target || !link) {
78
+ return { content: [{ type: 'text', text: 'create requires target and link.' }], isError: true };
79
+ }
80
+ let cmd;
81
+ if (type === 'junction') {
82
+ cmd = `cmd /c mklink /J "${link}" "${target}"`;
83
+ }
84
+ else if (type === 'hardlink') {
85
+ cmd = `cmd /c mklink /H "${link}" "${target}"`;
86
+ }
87
+ else {
88
+ cmd = `cmd /c mklink ${target.includes('.') ? '' : '/D'} "${link}" "${target}"`;
89
+ }
90
+ const ps = `& { ${cmd} } 2>&1`;
91
+ const result = await runPowerShell(ps, { timeout: 10000 });
92
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
93
+ }
94
+ case 'list': {
95
+ const dir = path || '.';
96
+ const ps = `
97
+ Get-ChildItem -Path '${dir.replace(/'/g, "''")}' -Force -ErrorAction Stop | Where-Object { $_.Attributes -band [IO.FileAttributes]::ReparsePoint } | ForEach-Object {
98
+ [PSCustomObject]@{
99
+ Name = $_.Name
100
+ Target = $_.Target
101
+ Type = if ($_.PSIsContainer) { 'Directory' } else { 'File' }
102
+ LinkType = $_.LinkType
103
+ }
104
+ } | ConvertTo-Json -Depth 3 -Compress`;
105
+ const result = await runPowerShell(ps, { timeout: 10000 });
106
+ if (!result.stdout)
107
+ return { content: [{ type: 'text', text: 'No symlinks found.' }] };
108
+ const links = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
109
+ const lines = links.map((l) => ` ${(l.LinkType || 'Link').padEnd(12)} ${l.Name.padEnd(30)} -> ${l.Target}`);
110
+ return { content: [{ type: 'text', text: `Symlinks in ${dir}:\n${lines.join('\n')}` }] };
111
+ }
112
+ case 'resolve': {
113
+ if (!link)
114
+ return { content: [{ type: 'text', text: 'resolve requires link path.' }], isError: true };
115
+ const ps = `(Get-Item '${link.replace(/'/g, "''")}' -Force).Target`;
116
+ const result = await runPowerShell(ps, { timeout: 5000 });
117
+ return { content: [{ type: 'text', text: `${link} -> ${result.stdout || '(not a symlink)'}` }] };
118
+ }
119
+ case 'remove': {
120
+ if (!link)
121
+ return { content: [{ type: 'text', text: 'remove requires link path.' }], isError: true };
122
+ const ps = `Remove-Item '${link.replace(/'/g, "''")}' -Force -ErrorAction Stop; "Removed: ${link}"`;
123
+ const result = await runPowerShell(ps, { timeout: 5000 });
124
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
125
+ }
126
+ }
127
+ });
128
+ }
129
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerSystemTools(server: McpServer): void;
3
+ //# sourceMappingURL=system.d.ts.map
@@ -0,0 +1,84 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tool: windows_system_info (#18)
5
+ */
6
+ import { runPowerShell } from '../shell.js';
7
+ export function registerSystemTools(server) {
8
+ server.tool('windows_system_info', 'Get comprehensive system information: OS, CPU, RAM, disk, network, uptime.', {}, async () => {
9
+ const ps = `
10
+ $os = Get-CimInstance Win32_OperatingSystem
11
+ $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
12
+ $cs = Get-CimInstance Win32_ComputerSystem
13
+ $disks = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
14
+ [PSCustomObject]@{
15
+ Drive = $_.DeviceID
16
+ Label = $_.VolumeName
17
+ FileSystem = $_.FileSystem
18
+ TotalGB = [math]::Round($_.Size / 1GB, 1)
19
+ FreeGB = [math]::Round($_.FreeSpace / 1GB, 1)
20
+ UsedPct = if ($_.Size -gt 0) { [math]::Round(($_.Size - $_.FreeSpace) / $_.Size * 100, 1) } else { 0 }
21
+ }
22
+ }
23
+ $adapters = Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq 'Up' } | ForEach-Object {
24
+ $ip = (Get-NetIPAddress -InterfaceIndex $_.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue).IPAddress
25
+ [PSCustomObject]@{
26
+ Name = $_.Name
27
+ Speed = $_.LinkSpeed
28
+ IP = $ip
29
+ MAC = $_.MacAddress
30
+ }
31
+ }
32
+ $uptime = (Get-Date) - $os.LastBootUpTime
33
+
34
+ [PSCustomObject]@{
35
+ OS = "$($os.Caption) $($os.Version) Build $($os.BuildNumber)"
36
+ Edition = $os.OperatingSystemSKU
37
+ Architecture = $os.OSArchitecture
38
+ Hostname = $env:COMPUTERNAME
39
+ Username = $env:USERNAME
40
+ Domain = $cs.Domain
41
+ CPU = "$($cpu.Name)"
42
+ CPUCores = "$($cpu.NumberOfCores) cores / $($cpu.NumberOfLogicalProcessors) threads"
43
+ CPUUsage = "$([math]::Round($cpu.LoadPercentage, 0))%"
44
+ RAMTotalGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 1)
45
+ RAMAvailGB = [math]::Round($os.FreePhysicalMemory / 1MB, 1)
46
+ RAMUsedPct = [math]::Round(($cs.TotalPhysicalMemory - $os.FreePhysicalMemory * 1KB) / $cs.TotalPhysicalMemory * 100, 1)
47
+ Disks = $disks
48
+ Network = $adapters
49
+ Uptime = "$($uptime.Days)d $($uptime.Hours)h $($uptime.Minutes)m"
50
+ } | ConvertTo-Json -Depth 4 -Compress`;
51
+ const result = await runPowerShell(ps, { timeout: 45000 });
52
+ if (result.exitCode !== 0) {
53
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
54
+ }
55
+ const info = JSON.parse(result.stdout);
56
+ const diskLines = (Array.isArray(info.Disks) ? info.Disks : [info.Disks])
57
+ .filter(Boolean)
58
+ .map((d) => ` ${d.Drive} ${d.Label || ''} — ${d.FreeGB}/${d.TotalGB} GB free (${d.UsedPct}% used)`);
59
+ const netLines = (Array.isArray(info.Network) ? info.Network : [info.Network])
60
+ .filter(Boolean)
61
+ .map((n) => ` ${n.Name} — ${n.IP || 'no IP'} (${n.Speed})`);
62
+ const text = [
63
+ `OS: ${info.OS}`,
64
+ `Architecture: ${info.Architecture}`,
65
+ `Host: ${info.Hostname} (${info.Domain})`,
66
+ `User: ${info.Username}`,
67
+ `Uptime: ${info.Uptime}`,
68
+ ``,
69
+ `CPU: ${info.CPU}`,
70
+ `Cores: ${info.CPUCores}`,
71
+ `CPU Usage: ${info.CPUUsage}`,
72
+ ``,
73
+ `RAM: ${info.RAMAvailGB}/${info.RAMTotalGB} GB available (${info.RAMUsedPct}% used)`,
74
+ ``,
75
+ `Disks:`,
76
+ ...diskLines,
77
+ ``,
78
+ `Network:`,
79
+ ...netLines,
80
+ ].join('\n');
81
+ return { content: [{ type: 'text', text }] };
82
+ });
83
+ }
84
+ //# sourceMappingURL=system.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerSystemMgmtTools(server: McpServer): void;
3
+ //# sourceMappingURL=system_mgmt.d.ts.map
@@ -0,0 +1,174 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tools: windows_timezone (#74), windows_features (#75),
5
+ * windows_smb_shares (#76), windows_dns_cache (#77)
6
+ */
7
+ import { z } from 'zod';
8
+ import { runPowerShell } from '../shell.js';
9
+ export function registerSystemMgmtTools(server) {
10
+ server.tool('windows_timezone', 'Get or set system timezone. List available timezones or sync time.', {
11
+ action: z.enum(['get', 'list', 'set', 'sync']).default('get').describe('Action'),
12
+ timezone: z.string().optional().describe('Timezone ID (for set, e.g. "Eastern Standard Time")'),
13
+ filter: z.string().optional().describe('Filter timezone list'),
14
+ }, async ({ action, timezone, filter }) => {
15
+ switch (action) {
16
+ case 'get': {
17
+ const ps = `
18
+ $tz = Get-TimeZone
19
+ [PSCustomObject]@{
20
+ Id = $tz.Id
21
+ DisplayName = $tz.DisplayName
22
+ UTCOffset = $tz.BaseUtcOffset.ToString()
23
+ DST = $tz.SupportsDaylightSavingTime
24
+ DSTActive = (Get-Date).IsDaylightSavingTime()
25
+ CurrentTime = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')
26
+ UTCTime = (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss')
27
+ } | ConvertTo-Json -Compress`;
28
+ const result = await runPowerShell(ps, { timeout: 10000 });
29
+ if (result.exitCode !== 0)
30
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
31
+ const tz = JSON.parse(result.stdout);
32
+ return {
33
+ content: [{
34
+ type: 'text',
35
+ text: `Timezone: ${tz.Id}\n${tz.DisplayName}\nUTC Offset: ${tz.UTCOffset}${tz.DSTActive ? ' (DST active)' : ''}\nLocal: ${tz.CurrentTime}\nUTC: ${tz.UTCTime}`,
36
+ }],
37
+ };
38
+ }
39
+ case 'list': {
40
+ const filterClause = filter ? `| Where-Object { $_.Id -like '*${filter.replace(/'/g, "''")}*' -or $_.DisplayName -like '*${filter.replace(/'/g, "''")}*' }` : '';
41
+ const ps = `Get-TimeZone -ListAvailable ${filterClause} | ForEach-Object { "$($_.BaseUtcOffset.ToString().PadRight(9)) $($_.Id)" }`;
42
+ const result = await runPowerShell(ps, { timeout: 10000 });
43
+ return { content: [{ type: 'text', text: result.stdout || 'No timezones found.' }] };
44
+ }
45
+ case 'set': {
46
+ if (!timezone)
47
+ return { content: [{ type: 'text', text: 'set requires timezone.' }], isError: true };
48
+ const ps = `Set-TimeZone -Id '${timezone.replace(/'/g, "''")}' -ErrorAction Stop; "Timezone set to: ${timezone}"`;
49
+ const result = await runPowerShell(ps, { timeout: 10000 });
50
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }], isError: result.exitCode !== 0 };
51
+ }
52
+ case 'sync': {
53
+ const ps = `w32tm /resync /force 2>&1; "Time sync requested"`;
54
+ const result = await runPowerShell(ps, { timeout: 15000 });
55
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
56
+ }
57
+ }
58
+ });
59
+ server.tool('windows_features', 'List, enable, or disable Windows optional features (WSL, Hyper-V, Sandbox, etc.).', {
60
+ action: z.enum(['list', 'enable', 'disable']).default('list').describe('Action'),
61
+ name: z.string().optional().describe('Feature name (for enable/disable)'),
62
+ filter: z.string().optional().describe('Filter by name (for list)'),
63
+ }, async ({ action, name, filter }) => {
64
+ if (action === 'list') {
65
+ const filterClause = filter ? `| Where-Object { $_.FeatureName -like '*${filter.replace(/'/g, "''")}*' }` : '';
66
+ const ps = `
67
+ try {
68
+ Get-WindowsOptionalFeature -Online -ErrorAction Stop ${filterClause} | Select-Object FeatureName,State | Sort-Object FeatureName | ConvertTo-Json -Depth 3 -Compress
69
+ } catch {
70
+ if ($_.Exception.Message -match 'elevation') { Write-Output 'NEEDS_ELEVATION' }
71
+ else { throw }
72
+ }`;
73
+ const result = await runPowerShell(ps, { timeout: 30000 });
74
+ if (result.stdout?.trim() === 'NEEDS_ELEVATION') {
75
+ return { content: [{ type: 'text', text: 'Windows Optional Features requires elevation (run as Administrator).' }] };
76
+ }
77
+ if (result.exitCode !== 0)
78
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
79
+ if (!result.stdout)
80
+ return { content: [{ type: 'text', text: 'No features found.' }] };
81
+ const features = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
82
+ const lines = features.map((f) => {
83
+ const state = f.State === 2 ? '[ON] ' : '[OFF]';
84
+ return `${state} ${f.FeatureName}`;
85
+ });
86
+ return { content: [{ type: 'text', text: `${lines.join('\n')}\n\n${features.length} features` }] };
87
+ }
88
+ if (!name)
89
+ return { content: [{ type: 'text', text: `${action} requires name.` }], isError: true };
90
+ const cmd = action === 'enable'
91
+ ? `Enable-WindowsOptionalFeature -Online -FeatureName '${name.replace(/'/g, "''")}' -NoRestart -ErrorAction Stop`
92
+ : `Disable-WindowsOptionalFeature -Online -FeatureName '${name.replace(/'/g, "''")}' -NoRestart -ErrorAction Stop`;
93
+ const ps = `${cmd} | Select-Object RestartNeeded | ConvertTo-Json -Compress`;
94
+ const result = await runPowerShell(ps, { timeout: 60000 });
95
+ if (result.exitCode !== 0)
96
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
97
+ const info = JSON.parse(result.stdout);
98
+ return { content: [{ type: 'text', text: `${name}: ${action}d${info.RestartNeeded ? ' (restart required)' : ''}` }] };
99
+ });
100
+ server.tool('windows_smb_shares', 'List local shares, mapped drives, or map/unmap network drives.', {
101
+ action: z.enum(['list_shares', 'list_mapped', 'map', 'unmap']).default('list_shares').describe('Action'),
102
+ letter: z.string().optional().describe('Drive letter (for map/unmap, e.g. "Z:")'),
103
+ unc_path: z.string().optional().describe('UNC path (for map, e.g. "\\\\\\\\server\\\\share")'),
104
+ }, async ({ action, letter, unc_path }) => {
105
+ switch (action) {
106
+ case 'list_shares': {
107
+ const ps = `Get-SmbShare -ErrorAction Stop | Select-Object Name,Path,Description | ConvertTo-Json -Depth 3 -Compress`;
108
+ const result = await runPowerShell(ps, { timeout: 10000 });
109
+ if (result.exitCode !== 0)
110
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
111
+ if (!result.stdout)
112
+ return { content: [{ type: 'text', text: 'No shares.' }] };
113
+ const shares = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
114
+ const lines = shares.map((s) => ` ${s.Name.padEnd(20)} ${(s.Path || '').padEnd(30)} ${s.Description || ''}`);
115
+ return { content: [{ type: 'text', text: `Local shares:\n${lines.join('\n')}` }] };
116
+ }
117
+ case 'list_mapped': {
118
+ const ps = `Get-PSDrive -PSProvider FileSystem | Where-Object { $_.DisplayRoot } | ForEach-Object { "$($_.Name): $($_.DisplayRoot)" }`;
119
+ const result = await runPowerShell(ps, { timeout: 10000 });
120
+ return { content: [{ type: 'text', text: result.stdout || 'No mapped drives.' }] };
121
+ }
122
+ case 'map': {
123
+ if (!letter || !unc_path)
124
+ return { content: [{ type: 'text', text: 'map requires letter and unc_path.' }], isError: true };
125
+ const ps = `net use ${letter} "${unc_path}" /persistent:yes 2>&1`;
126
+ const result = await runPowerShell(ps, { timeout: 15000 });
127
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
128
+ }
129
+ case 'unmap': {
130
+ if (!letter)
131
+ return { content: [{ type: 'text', text: 'unmap requires letter.' }], isError: true };
132
+ const ps = `net use ${letter} /delete /yes 2>&1`;
133
+ const result = await runPowerShell(ps, { timeout: 10000 });
134
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
135
+ }
136
+ }
137
+ });
138
+ server.tool('windows_dns_cache', 'View, filter, or clear the DNS resolver cache. Also resolves domain names.', {
139
+ action: z.enum(['list', 'clear', 'resolve']).default('list').describe('Action'),
140
+ filter: z.string().optional().describe('Filter by domain (for list)'),
141
+ domain: z.string().optional().describe('Domain to resolve (for resolve)'),
142
+ limit: z.number().default(30).describe('Max entries (for list)'),
143
+ }, async ({ action, filter, domain, limit }) => {
144
+ switch (action) {
145
+ case 'list': {
146
+ const filterClause = filter ? `| Where-Object { $_.Entry -like '*${filter.replace(/'/g, "''")}*' }` : '';
147
+ const ps = `Get-DnsClientCache ${filterClause} -ErrorAction SilentlyContinue | Select-Object -First ${limit} Entry,RecordName,RecordType,Data,TimeToLive | ConvertTo-Json -Depth 3 -Compress`;
148
+ const result = await runPowerShell(ps, { timeout: 10000 });
149
+ if (!result.stdout)
150
+ return { content: [{ type: 'text', text: 'DNS cache is empty.' }] };
151
+ const entries = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
152
+ const lines = entries.map((e) => ` ${(e.Entry || '').padEnd(40).slice(0, 40)} ${String(e.RecordType).padEnd(5)} ${String(e.TimeToLive).padStart(6)}s ${e.Data || ''}`);
153
+ return { content: [{ type: 'text', text: `DNS Cache (${entries.length} entries):\n${lines.join('\n')}` }] };
154
+ }
155
+ case 'clear': {
156
+ const ps = `Clear-DnsClientCache; "DNS cache cleared"`;
157
+ const result = await runPowerShell(ps, { timeout: 5000 });
158
+ return { content: [{ type: 'text', text: result.stdout || result.stderr }] };
159
+ }
160
+ case 'resolve': {
161
+ if (!domain)
162
+ return { content: [{ type: 'text', text: 'resolve requires domain.' }], isError: true };
163
+ const ps = `Resolve-DnsName '${domain.replace(/'/g, "''")}' -ErrorAction Stop | Select-Object Name,Type,IPAddress,NameHost | ConvertTo-Json -Depth 3 -Compress`;
164
+ const result = await runPowerShell(ps, { timeout: 10000 });
165
+ if (result.exitCode !== 0)
166
+ return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
167
+ const records = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
168
+ const lines = records.map((r) => ` ${r.Name} ${r.IPAddress || r.NameHost || ''}`);
169
+ return { content: [{ type: 'text', text: `${domain}:\n${lines.join('\n')}` }] };
170
+ }
171
+ }
172
+ });
173
+ }
174
+ //# sourceMappingURL=system_mgmt.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerTerminalTools(server: McpServer): void;
3
+ //# sourceMappingURL=terminal.d.ts.map
@@ -0,0 +1,80 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ * SPDX-License-Identifier: GPL-3.0-or-later
3
+ *
4
+ * Tool: windows_terminal_session — Persistent interactive terminals (#35)
5
+ */
6
+ import { z } from 'zod';
7
+ import { startSession, sendToSession, readSessionOutput, listSessions, terminateSession, } from '../shell.js';
8
+ export function registerTerminalTools(server) {
9
+ server.tool('windows_terminal_start', 'Start a persistent interactive terminal session (PowerShell, cmd, bash, python, node, wsl).', {
10
+ shell: z.enum(['pwsh', 'cmd', 'bash', 'python', 'node', 'wsl']).default('pwsh').describe('Shell type'),
11
+ }, async ({ shell }) => {
12
+ const session = startSession(shell);
13
+ // Give the shell a moment to start and produce initial output
14
+ await new Promise(resolve => setTimeout(resolve, 500));
15
+ return {
16
+ content: [{
17
+ type: 'text',
18
+ text: `Session started: PID ${session.pid} (${session.shell})\nUse windows_terminal_send to send commands, windows_terminal_read to read output.`,
19
+ }],
20
+ };
21
+ });
22
+ server.tool('windows_terminal_send', 'Send input to a running interactive terminal session.', {
23
+ pid: z.number().describe('Session PID'),
24
+ input: z.string().describe('Text to send to the session'),
25
+ }, async ({ pid, input }) => {
26
+ try {
27
+ sendToSession(pid, input);
28
+ // Wait for output to arrive
29
+ await new Promise(resolve => setTimeout(resolve, 1000));
30
+ const lines = readSessionOutput(pid, -30);
31
+ return {
32
+ content: [{
33
+ type: 'text',
34
+ text: lines.join('\n') || '(no output yet)',
35
+ }],
36
+ };
37
+ }
38
+ catch (err) {
39
+ return { content: [{ type: 'text', text: `Error: ${err}` }], isError: true };
40
+ }
41
+ });
42
+ server.tool('windows_terminal_read', 'Read output from a terminal session with pagination. Use negative offset to read from the end.', {
43
+ pid: z.number().describe('Session PID'),
44
+ offset: z.number().default(0).describe('Line offset (negative = from end)'),
45
+ length: z.number().optional().describe('Number of lines to return'),
46
+ }, async ({ pid, offset, length }) => {
47
+ try {
48
+ const lines = readSessionOutput(pid, offset, length);
49
+ return {
50
+ content: [{
51
+ type: 'text',
52
+ text: lines.join('\n') || '(no output)',
53
+ }],
54
+ };
55
+ }
56
+ catch (err) {
57
+ return { content: [{ type: 'text', text: `Error: ${err}` }], isError: true };
58
+ }
59
+ });
60
+ server.tool('windows_terminal_list', 'List all active terminal sessions.', {}, async () => {
61
+ const sessions = listSessions();
62
+ if (sessions.length === 0) {
63
+ return { content: [{ type: 'text', text: 'No active sessions.' }] };
64
+ }
65
+ const lines = sessions.map(s => `PID ${s.pid} — ${s.shell} — ${s.running ? 'running' : 'ended'} — ${s.lines} lines — started ${s.startedAt}`);
66
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
67
+ });
68
+ server.tool('windows_terminal_kill', 'Terminate a terminal session by PID.', {
69
+ pid: z.number().describe('Session PID to terminate'),
70
+ }, async ({ pid }) => {
71
+ const killed = terminateSession(pid);
72
+ return {
73
+ content: [{
74
+ type: 'text',
75
+ text: killed ? `Session ${pid} terminated.` : `No session with PID ${pid}.`,
76
+ }],
77
+ };
78
+ });
79
+ }
80
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerThemeTools(server: McpServer): void;
3
+ //# sourceMappingURL=theme.d.ts.map