@hcu-lab.me/mcp-device 1.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 (261) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/bootstrap.d.ts +1 -0
  4. package/dist/bootstrap.js +22 -0
  5. package/dist/command-manager.d.ts +8 -0
  6. package/dist/command-manager.js +241 -0
  7. package/dist/config-field-definitions.d.ts +41 -0
  8. package/dist/config-field-definitions.js +37 -0
  9. package/dist/config-manager.d.ts +102 -0
  10. package/dist/config-manager.js +291 -0
  11. package/dist/config.d.ts +6 -0
  12. package/dist/config.js +10 -0
  13. package/dist/custom-stdio.d.ts +51 -0
  14. package/dist/custom-stdio.js +358 -0
  15. package/dist/data/official-app-ca.pem +12 -0
  16. package/dist/data/onboarding-prompts.json +70 -0
  17. package/dist/device/device-state.d.ts +13 -0
  18. package/dist/device/device-state.js +100 -0
  19. package/dist/device/device-status.d.ts +81 -0
  20. package/dist/device/device-status.js +156 -0
  21. package/dist/device/device.d.ts +16 -0
  22. package/dist/device/device.js +190 -0
  23. package/dist/device/execution-engine.d.ts +144 -0
  24. package/dist/device/execution-engine.js +195 -0
  25. package/dist/device/gateway-channel.d.ts +48 -0
  26. package/dist/device/gateway-channel.js +384 -0
  27. package/dist/device/gateway-config.d.ts +40 -0
  28. package/dist/device/gateway-config.js +207 -0
  29. package/dist/device/gateway-identity.d.ts +35 -0
  30. package/dist/device/gateway-identity.js +178 -0
  31. package/dist/device/gateway-pairing.d.ts +25 -0
  32. package/dist/device/gateway-pairing.js +105 -0
  33. package/dist/device/gateway-secure-transport.d.ts +25 -0
  34. package/dist/device/gateway-secure-transport.js +117 -0
  35. package/dist/device/gateway-tool-adapter.d.ts +17 -0
  36. package/dist/device/gateway-tool-adapter.js +284 -0
  37. package/dist/device/gateway-url-policy.d.ts +3 -0
  38. package/dist/device/gateway-url-policy.js +43 -0
  39. package/dist/device/linux-service.d.ts +70 -0
  40. package/dist/device/linux-service.js +268 -0
  41. package/dist/device/official-trust.d.ts +7 -0
  42. package/dist/device/official-trust.js +49 -0
  43. package/dist/device/project-inspection.d.ts +1 -0
  44. package/dist/device/project-inspection.js +180 -0
  45. package/dist/device/runtime-owner.d.ts +41 -0
  46. package/dist/device/runtime-owner.js +177 -0
  47. package/dist/device/windows-dpapi.d.ts +11 -0
  48. package/dist/device/windows-dpapi.js +67 -0
  49. package/dist/device/windows-service.d.ts +56 -0
  50. package/dist/device/windows-service.js +238 -0
  51. package/dist/error-handlers.d.ts +7 -0
  52. package/dist/error-handlers.js +15 -0
  53. package/dist/handlers/edit-search-handlers.d.ts +6 -0
  54. package/dist/handlers/edit-search-handlers.js +6 -0
  55. package/dist/handlers/filesystem-handlers.d.ts +38 -0
  56. package/dist/handlers/filesystem-handlers.js +440 -0
  57. package/dist/handlers/history-handlers.d.ts +12 -0
  58. package/dist/handlers/history-handlers.js +67 -0
  59. package/dist/handlers/index.d.ts +6 -0
  60. package/dist/handlers/index.js +7 -0
  61. package/dist/handlers/process-handlers.d.ts +9 -0
  62. package/dist/handlers/process-handlers.js +15 -0
  63. package/dist/handlers/search-handlers.d.ts +17 -0
  64. package/dist/handlers/search-handlers.js +224 -0
  65. package/dist/handlers/terminal-handlers.d.ts +21 -0
  66. package/dist/handlers/terminal-handlers.js +35 -0
  67. package/dist/index.d.ts +2 -0
  68. package/dist/index.js +138 -0
  69. package/dist/mcp-device.d.ts +2 -0
  70. package/dist/mcp-device.js +7 -0
  71. package/dist/npm-scripts/remote.d.ts +3 -0
  72. package/dist/npm-scripts/remote.js +314 -0
  73. package/dist/search-manager.d.ts +147 -0
  74. package/dist/search-manager.js +828 -0
  75. package/dist/server.d.ts +46 -0
  76. package/dist/server.js +1563 -0
  77. package/dist/terminal-manager.d.ts +104 -0
  78. package/dist/terminal-manager.js +635 -0
  79. package/dist/tools/config.d.ts +96 -0
  80. package/dist/tools/config.js +260 -0
  81. package/dist/tools/edit.d.ts +38 -0
  82. package/dist/tools/edit.js +435 -0
  83. package/dist/tools/feedback.d.ts +8 -0
  84. package/dist/tools/feedback.js +132 -0
  85. package/dist/tools/filesystem.d.ts +79 -0
  86. package/dist/tools/filesystem.js +947 -0
  87. package/dist/tools/fuzzySearch.d.ts +12 -0
  88. package/dist/tools/fuzzySearch.js +74 -0
  89. package/dist/tools/fuzzySearchCore.d.ts +52 -0
  90. package/dist/tools/fuzzySearchCore.js +125 -0
  91. package/dist/tools/improved-process-tools.d.ts +24 -0
  92. package/dist/tools/improved-process-tools.js +627 -0
  93. package/dist/tools/mime-types.d.ts +3 -0
  94. package/dist/tools/mime-types.js +28 -0
  95. package/dist/tools/pdf/extract-images.d.ts +34 -0
  96. package/dist/tools/pdf/extract-images.js +132 -0
  97. package/dist/tools/pdf/index.d.ts +6 -0
  98. package/dist/tools/pdf/index.js +3 -0
  99. package/dist/tools/pdf/lib/pdf2md.d.ts +36 -0
  100. package/dist/tools/pdf/lib/pdf2md.js +76 -0
  101. package/dist/tools/pdf/manipulations.d.ts +13 -0
  102. package/dist/tools/pdf/manipulations.js +99 -0
  103. package/dist/tools/pdf/markdown.d.ts +26 -0
  104. package/dist/tools/pdf/markdown.js +275 -0
  105. package/dist/tools/pdf/utils.d.ts +12 -0
  106. package/dist/tools/pdf/utils.js +34 -0
  107. package/dist/tools/process.d.ts +3 -0
  108. package/dist/tools/process.js +56 -0
  109. package/dist/tools/prompts.d.ts +28 -0
  110. package/dist/tools/prompts.js +259 -0
  111. package/dist/tools/schemas.d.ts +454 -0
  112. package/dist/tools/schemas.js +242 -0
  113. package/dist/tools/usage.d.ts +5 -0
  114. package/dist/tools/usage.js +24 -0
  115. package/dist/types.d.ts +83 -0
  116. package/dist/types.js +1 -0
  117. package/dist/ui/config-editor/config-editor-runtime.js +150 -0
  118. package/dist/ui/config-editor/index.html +13 -0
  119. package/dist/ui/config-editor/src/app.d.ts +43 -0
  120. package/dist/ui/config-editor/src/app.js +831 -0
  121. package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
  122. package/dist/ui/config-editor/src/array-modal.js +185 -0
  123. package/dist/ui/config-editor/src/main.d.ts +1 -0
  124. package/dist/ui/config-editor/src/main.js +2 -0
  125. package/dist/ui/config-editor/styles.css +587 -0
  126. package/dist/ui/contracts.d.ts +14 -0
  127. package/dist/ui/contracts.js +21 -0
  128. package/dist/ui/file-preview/index.html +16 -0
  129. package/dist/ui/file-preview/preview-runtime.js +469 -0
  130. package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
  131. package/dist/ui/file-preview/shared/preview-file-types.js +59 -0
  132. package/dist/ui/file-preview/src/app.d.ts +4 -0
  133. package/dist/ui/file-preview/src/app.js +680 -0
  134. package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
  135. package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
  136. package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
  137. package/dist/ui/file-preview/src/components/highlighting.js +54 -0
  138. package/dist/ui/file-preview/src/components/html-renderer.d.ts +5 -0
  139. package/dist/ui/file-preview/src/components/html-renderer.js +47 -0
  140. package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
  141. package/dist/ui/file-preview/src/components/markdown-renderer.js +59 -0
  142. package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
  143. package/dist/ui/file-preview/src/directory-controller.js +240 -0
  144. package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
  145. package/dist/ui/file-preview/src/document-layout.js +109 -0
  146. package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
  147. package/dist/ui/file-preview/src/document-outline.js +97 -0
  148. package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
  149. package/dist/ui/file-preview/src/document-workspace.js +33 -0
  150. package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
  151. package/dist/ui/file-preview/src/file-type-handlers.js +109 -0
  152. package/dist/ui/file-preview/src/host/external-actions.d.ts +8 -0
  153. package/dist/ui/file-preview/src/host/external-actions.js +55 -0
  154. package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
  155. package/dist/ui/file-preview/src/host/selection-context.js +106 -0
  156. package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
  157. package/dist/ui/file-preview/src/image-preview.js +21 -0
  158. package/dist/ui/file-preview/src/main.d.ts +1 -0
  159. package/dist/ui/file-preview/src/main.js +5 -0
  160. package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
  161. package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
  162. package/dist/ui/file-preview/src/markdown/controller.d.ts +44 -0
  163. package/dist/ui/file-preview/src/markdown/controller.js +1043 -0
  164. package/dist/ui/file-preview/src/markdown/editor.d.ts +131 -0
  165. package/dist/ui/file-preview/src/markdown/editor.js +1479 -0
  166. package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
  167. package/dist/ui/file-preview/src/markdown/linking.js +228 -0
  168. package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
  169. package/dist/ui/file-preview/src/markdown/outline.js +16 -0
  170. package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
  171. package/dist/ui/file-preview/src/markdown/parser.js +38 -0
  172. package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
  173. package/dist/ui/file-preview/src/markdown/preview.js +20 -0
  174. package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
  175. package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
  176. package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
  177. package/dist/ui/file-preview/src/markdown/utils.js +15 -0
  178. package/dist/ui/file-preview/src/model.d.ts +35 -0
  179. package/dist/ui/file-preview/src/model.js +1 -0
  180. package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
  181. package/dist/ui/file-preview/src/panel-actions.js +182 -0
  182. package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
  183. package/dist/ui/file-preview/src/path-utils.js +64 -0
  184. package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
  185. package/dist/ui/file-preview/src/payload-utils.js +119 -0
  186. package/dist/ui/file-preview/src/types.d.ts +1 -0
  187. package/dist/ui/file-preview/src/types.js +1 -0
  188. package/dist/ui/file-preview/styles.css +1631 -0
  189. package/dist/ui/resources.d.ts +28 -0
  190. package/dist/ui/resources.js +86 -0
  191. package/dist/ui/shared/compact-row.d.ts +11 -0
  192. package/dist/ui/shared/compact-row.js +18 -0
  193. package/dist/ui/shared/escape-html.d.ts +4 -0
  194. package/dist/ui/shared/escape-html.js +11 -0
  195. package/dist/ui/shared/host-context.d.ts +15 -0
  196. package/dist/ui/shared/host-context.js +51 -0
  197. package/dist/ui/shared/tool-bridge.d.ts +30 -0
  198. package/dist/ui/shared/tool-bridge.js +137 -0
  199. package/dist/ui/shared/tool-shell.d.ts +25 -0
  200. package/dist/ui/shared/tool-shell.js +107 -0
  201. package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
  202. package/dist/ui/shared/ui-event-tracker.js +27 -0
  203. package/dist/ui/shared/widget-state.d.ts +33 -0
  204. package/dist/ui/shared/widget-state.js +158 -0
  205. package/dist/utils/ab-test.d.ts +12 -0
  206. package/dist/utils/ab-test.js +104 -0
  207. package/dist/utils/capture.d.ts +35 -0
  208. package/dist/utils/capture.js +481 -0
  209. package/dist/utils/dockerPrompt.d.ts +17 -0
  210. package/dist/utils/dockerPrompt.js +53 -0
  211. package/dist/utils/feature-flags.d.ts +59 -0
  212. package/dist/utils/feature-flags.js +215 -0
  213. package/dist/utils/files/base.d.ts +173 -0
  214. package/dist/utils/files/base.js +5 -0
  215. package/dist/utils/files/binary.d.ts +21 -0
  216. package/dist/utils/files/binary.js +65 -0
  217. package/dist/utils/files/docx.d.ts +47 -0
  218. package/dist/utils/files/docx.js +679 -0
  219. package/dist/utils/files/excel.d.ts +24 -0
  220. package/dist/utils/files/excel.js +437 -0
  221. package/dist/utils/files/factory.d.ts +41 -0
  222. package/dist/utils/files/factory.js +113 -0
  223. package/dist/utils/files/image.d.ts +21 -0
  224. package/dist/utils/files/image.js +78 -0
  225. package/dist/utils/files/index.d.ts +10 -0
  226. package/dist/utils/files/index.js +13 -0
  227. package/dist/utils/files/pdf.d.ts +32 -0
  228. package/dist/utils/files/pdf.js +142 -0
  229. package/dist/utils/files/text.d.ts +63 -0
  230. package/dist/utils/files/text.js +370 -0
  231. package/dist/utils/fuzzySearchLogger.d.ts +30 -0
  232. package/dist/utils/fuzzySearchLogger.js +126 -0
  233. package/dist/utils/lineEndingHandler.d.ts +21 -0
  234. package/dist/utils/lineEndingHandler.js +77 -0
  235. package/dist/utils/logger.d.ts +32 -0
  236. package/dist/utils/logger.js +72 -0
  237. package/dist/utils/mcp-ui-ab-test.d.ts +13 -0
  238. package/dist/utils/mcp-ui-ab-test.js +62 -0
  239. package/dist/utils/open-browser.d.ts +9 -0
  240. package/dist/utils/open-browser.js +42 -0
  241. package/dist/utils/process-detection.d.ts +23 -0
  242. package/dist/utils/process-detection.js +150 -0
  243. package/dist/utils/ripgrep-resolver.d.ts +9 -0
  244. package/dist/utils/ripgrep-resolver.js +74 -0
  245. package/dist/utils/system-info.d.ts +71 -0
  246. package/dist/utils/system-info.js +712 -0
  247. package/dist/utils/toolHistory.d.ts +86 -0
  248. package/dist/utils/toolHistory.js +257 -0
  249. package/dist/utils/trackTools.d.ts +6 -0
  250. package/dist/utils/trackTools.js +54 -0
  251. package/dist/utils/unsupportedParams.d.ts +24 -0
  252. package/dist/utils/unsupportedParams.js +66 -0
  253. package/dist/utils/usageTracker.d.ts +133 -0
  254. package/dist/utils/usageTracker.js +477 -0
  255. package/dist/utils/welcome-onboarding.d.ts +11 -0
  256. package/dist/utils/welcome-onboarding.js +99 -0
  257. package/dist/utils/withTimeout.d.ts +28 -0
  258. package/dist/utils/withTimeout.js +85 -0
  259. package/dist/version.d.ts +1 -0
  260. package/dist/version.js +1 -0
  261. package/package.json +131 -0
@@ -0,0 +1,177 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs/promises';
3
+ import net from 'net';
4
+ import path from 'path';
5
+ import { deviceStatePaths, migrateLegacyDeviceState } from './device-state.js';
6
+ function endpointFor(paths, platform = process.platform) {
7
+ const digest = crypto.createHash('sha256').update(paths.root, 'utf8').digest('hex').slice(0, 24);
8
+ if (platform === 'win32')
9
+ return `\\\\.\\pipe\\mcp-device-${digest}`;
10
+ return path.join(paths.root, `runtime-${digest}.sock`);
11
+ }
12
+ function controlFileFor(paths) {
13
+ return path.join(paths.root, 'runtime-control.json');
14
+ }
15
+ async function request(endpoint, message, timeoutMs = 800) {
16
+ return await new Promise(resolve => {
17
+ const socket = net.createConnection(endpoint);
18
+ let data = '';
19
+ const finish = (value) => {
20
+ socket.destroy();
21
+ resolve(value);
22
+ };
23
+ socket.setEncoding('utf8');
24
+ socket.setTimeout(timeoutMs, () => finish(null));
25
+ socket.once('error', () => finish(null));
26
+ socket.on('data', chunk => { data += chunk; });
27
+ socket.once('end', () => {
28
+ try {
29
+ finish(JSON.parse(data));
30
+ }
31
+ catch {
32
+ finish(null);
33
+ }
34
+ });
35
+ socket.once('connect', () => socket.write(`${JSON.stringify(message)}\n`));
36
+ });
37
+ }
38
+ export async function probeRuntimeOwner(paths = deviceStatePaths(), platform = process.platform) {
39
+ const response = await request(endpointFor(paths, platform), { action: 'status' });
40
+ return response?.ok === true && response.status?.product === 'MCP Device' ? response.status : null;
41
+ }
42
+ export class RuntimeOwner {
43
+ constructor({ paths = deviceStatePaths(), platform = process.platform, mode, onStop }) {
44
+ this.paths = paths;
45
+ this.platform = platform;
46
+ this.endpoint = endpointFor(paths, platform);
47
+ this.mode = mode;
48
+ this.nonce = crypto.randomBytes(32).toString('base64url');
49
+ this.onStop = onStop;
50
+ this.status = {
51
+ product: 'MCP Device',
52
+ mode,
53
+ pid: process.pid,
54
+ startedAt: Date.now(),
55
+ executable: path.resolve(process.execPath),
56
+ entrypoint: path.resolve(process.argv[1] || process.execPath),
57
+ deviceId: null
58
+ };
59
+ }
60
+ async acquire() {
61
+ await fs.mkdir(this.paths.root, { recursive: true, mode: 0o700 });
62
+ const existing = await probeRuntimeOwner(this.paths, this.platform);
63
+ if (existing)
64
+ throw new Error(`MCP Device runtime is already owned by PID ${existing.pid} (${existing.mode}).`);
65
+ if (this.platform !== 'win32') {
66
+ try {
67
+ await fs.unlink(this.endpoint);
68
+ }
69
+ catch (error) {
70
+ if (error?.code !== 'ENOENT')
71
+ throw error;
72
+ }
73
+ }
74
+ this.server = net.createServer(socket => {
75
+ socket.setEncoding('utf8');
76
+ socket.on('error', () => { });
77
+ let data = '';
78
+ socket.on('data', async (chunk) => {
79
+ data += chunk;
80
+ const newline = data.indexOf('\n');
81
+ if (newline < 0)
82
+ return;
83
+ const frame = data.slice(0, newline);
84
+ data = data.slice(newline + 1);
85
+ let message = null;
86
+ try {
87
+ message = JSON.parse(frame);
88
+ }
89
+ catch { }
90
+ if (message?.action === 'status') {
91
+ socket.end(JSON.stringify({ ok: true, status: this.status }));
92
+ return;
93
+ }
94
+ if (message?.action === 'stop' && message?.nonce === this.nonce) {
95
+ socket.end(JSON.stringify({ ok: true }));
96
+ await this.onStop?.();
97
+ return;
98
+ }
99
+ socket.end(JSON.stringify({ ok: false }));
100
+ });
101
+ });
102
+ await new Promise((resolve, reject) => {
103
+ this.server.once('error', reject);
104
+ this.server.listen(this.endpoint, resolve);
105
+ });
106
+ await fs.writeFile(controlFileFor(this.paths), JSON.stringify({ endpoint: this.endpoint, nonce: this.nonce }, null, 2), { mode: 0o600 });
107
+ }
108
+ async bootstrap() {
109
+ await migrateLegacyDeviceState(this.paths);
110
+ }
111
+ setDeviceId(deviceId) {
112
+ this.status.deviceId = String(deviceId || '').trim() || null;
113
+ }
114
+ async release() {
115
+ if (this.server) {
116
+ const server = this.server;
117
+ this.server = undefined;
118
+ await new Promise(resolve => server.close(() => resolve()));
119
+ }
120
+ try {
121
+ await fs.unlink(controlFileFor(this.paths));
122
+ }
123
+ catch (error) {
124
+ if (error?.code !== 'ENOENT')
125
+ throw error;
126
+ }
127
+ if (this.platform !== 'win32') {
128
+ try {
129
+ await fs.unlink(this.endpoint);
130
+ }
131
+ catch (error) {
132
+ if (error?.code !== 'ENOENT')
133
+ throw error;
134
+ }
135
+ }
136
+ }
137
+ }
138
+ export async function stopRuntimeOwner(paths = deviceStatePaths(), platform = process.platform) {
139
+ let control;
140
+ try {
141
+ control = JSON.parse(await fs.readFile(controlFileFor(paths), 'utf8'));
142
+ }
143
+ catch (error) {
144
+ if (error?.code === 'ENOENT')
145
+ return false;
146
+ throw error;
147
+ }
148
+ if (String(control.endpoint || '') !== endpointFor(paths, platform) || !control.nonce)
149
+ return false;
150
+ const response = await request(String(control.endpoint), { action: 'stop', nonce: control.nonce }, 1500);
151
+ return response?.ok === true;
152
+ }
153
+ export async function acquireRuntimeOwner(owner, { confirmTakeover, waitMs = 5000, pollMs = 50 } = {}) {
154
+ const existing = await probeRuntimeOwner(owner.paths);
155
+ if (!existing) {
156
+ await owner.acquire();
157
+ return { takenOver: false, previous: null };
158
+ }
159
+ if (existing.mode === 'bootstrap') {
160
+ throw new Error(`MCP Device bootstrap is busy in PID ${existing.pid}; retry after it completes.`);
161
+ }
162
+ if (!confirmTakeover || !await confirmTakeover(existing)) {
163
+ throw new Error(`MCP Device is already running in ${existing.mode} mode; takeover declined.`);
164
+ }
165
+ if (!await stopRuntimeOwner(owner.paths)) {
166
+ throw new Error('MCP Device owner did not accept authenticated stop.');
167
+ }
168
+ const deadline = Date.now() + Math.max(1, waitMs);
169
+ while (Date.now() < deadline) {
170
+ if (!await probeRuntimeOwner(owner.paths)) {
171
+ await owner.acquire();
172
+ return { takenOver: true, previous: existing };
173
+ }
174
+ await new Promise(resolve => setTimeout(resolve, Math.max(1, pollMs)));
175
+ }
176
+ throw new Error(`MCP Device owner PID ${existing.pid} did not release runtime ownership.`);
177
+ }
@@ -0,0 +1,11 @@
1
+ type DpapiLabel = 'identity' | 'proxy';
2
+ type PowerShellRunner = (script: string, input: string) => Promise<string>;
3
+ export declare function protectWindowsSecret(secret: Buffer | Uint8Array | string, label: DpapiLabel, options?: {
4
+ platform?: NodeJS.Platform | string;
5
+ runner?: PowerShellRunner;
6
+ }): Promise<string>;
7
+ export declare function unprotectWindowsSecret(blob: string, label: DpapiLabel, options?: {
8
+ platform?: NodeJS.Platform | string;
9
+ runner?: PowerShellRunner;
10
+ }): Promise<Buffer>;
11
+ export {};
@@ -0,0 +1,67 @@
1
+ import { spawn } from 'child_process';
2
+ const ENTROPY = {
3
+ identity: 'hcu-device-identity-private-key-v1',
4
+ proxy: 'hcu-device-proxy-url-v1'
5
+ };
6
+ function assertLabel(label) {
7
+ if (!(label in ENTROPY))
8
+ throw new Error('Unsupported DPAPI secret label.');
9
+ }
10
+ async function runPowerShell(script, input) {
11
+ return await new Promise((resolve, reject) => {
12
+ const child = spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {
13
+ windowsHide: true,
14
+ stdio: ['pipe', 'pipe', 'pipe']
15
+ });
16
+ const stdout = [];
17
+ const stderr = [];
18
+ child.stdout.on('data', chunk => stdout.push(Buffer.from(chunk)));
19
+ child.stderr.on('data', chunk => stderr.push(Buffer.from(chunk)));
20
+ child.once('error', reject);
21
+ child.once('close', code => {
22
+ if (code !== 0) {
23
+ reject(new Error(Buffer.concat(stderr).toString('utf8').trim() || `DPAPI PowerShell failed with exit code ${code}`));
24
+ return;
25
+ }
26
+ resolve(Buffer.concat(stdout).toString('utf8').trim());
27
+ });
28
+ child.stdin.end(input, 'utf8');
29
+ });
30
+ }
31
+ function dpapiScript(action, label) {
32
+ const entropy = Buffer.from(ENTROPY[label], 'utf8').toString('base64');
33
+ return [
34
+ "$ErrorActionPreference = 'Stop'",
35
+ 'Add-Type -AssemblyName System.Security',
36
+ '$inputB64 = [Console]::In.ReadToEnd().Trim()',
37
+ '$data = [Convert]::FromBase64String($inputB64)',
38
+ `$entropy = [Convert]::FromBase64String('${entropy}')`,
39
+ '$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser',
40
+ `$output = [System.Security.Cryptography.ProtectedData]::${action}($data, $entropy, $scope)`,
41
+ '[Console]::Out.Write([Convert]::ToBase64String($output))'
42
+ ].join('; ');
43
+ }
44
+ export async function protectWindowsSecret(secret, label, options = {}) {
45
+ const platform = options.platform || process.platform;
46
+ if (platform !== 'win32')
47
+ throw new Error('Windows DPAPI is only available on Windows.');
48
+ assertLabel(label);
49
+ const bytes = typeof secret === 'string' ? Buffer.from(secret, 'utf8') : Buffer.from(secret);
50
+ const output = await (options.runner || runPowerShell)(dpapiScript('Protect', label), bytes.toString('base64'));
51
+ if (!output)
52
+ throw new Error('Windows DPAPI returned an empty protected value.');
53
+ return output;
54
+ }
55
+ export async function unprotectWindowsSecret(blob, label, options = {}) {
56
+ const platform = options.platform || process.platform;
57
+ if (platform !== 'win32')
58
+ throw new Error('Windows DPAPI is only available on Windows.');
59
+ assertLabel(label);
60
+ const input = String(blob || '').trim();
61
+ if (!input)
62
+ throw new Error('Windows DPAPI protected value is empty.');
63
+ const output = await (options.runner || runPowerShell)(dpapiScript('Unprotect', label), input);
64
+ if (!output)
65
+ throw new Error('Windows DPAPI returned an empty unprotected value.');
66
+ return Buffer.from(output, 'base64');
67
+ }
@@ -0,0 +1,56 @@
1
+ type ExecFileResult = {
2
+ stdout: string;
3
+ stderr: string;
4
+ };
5
+ type ExecFileFn = (file: string, args: string[]) => Promise<ExecFileResult>;
6
+ export declare function buildWindowsTaskName(deviceId: string): string;
7
+ export declare function buildWindowsRunnerScript(options: {
8
+ gatewayUrl?: string;
9
+ allowedRoots?: string;
10
+ nodePath: string;
11
+ entrypoint: string;
12
+ }): string;
13
+ export type WindowsDeviceServiceStatus = {
14
+ installed: boolean;
15
+ running: boolean;
16
+ autostart: boolean;
17
+ registration: 'task' | 'run' | 'manual' | 'stale';
18
+ taskName: string;
19
+ };
20
+ export declare class WindowsDeviceService {
21
+ private platform;
22
+ private execFile;
23
+ private runnerPath;
24
+ private legacyRunnerPath;
25
+ private nodePath;
26
+ private entrypoint;
27
+ constructor(options?: {
28
+ platform?: NodeJS.Platform | string;
29
+ execFile?: ExecFileFn;
30
+ runnerPath?: string;
31
+ legacyRunnerPath?: string;
32
+ nodePath?: string;
33
+ entrypoint?: string;
34
+ });
35
+ private assertWindows;
36
+ private runnerExists;
37
+ private hasUserRunEntry;
38
+ private scheduledTaskState;
39
+ private deleteRegistrations;
40
+ private commandReferencesLegacyRunner;
41
+ private deleteVerifiedLegacyRegistrations;
42
+ private createAutostartRegistration;
43
+ private startUserProcess;
44
+ private userProcessRunning;
45
+ install(options: {
46
+ deviceId: string;
47
+ gatewayUrl?: string;
48
+ allowedRoots?: string;
49
+ }): Promise<void>;
50
+ setAutostart(deviceId: string, enabled: boolean): Promise<void>;
51
+ start(deviceId: string): Promise<void>;
52
+ stop(deviceId: string): Promise<void>;
53
+ status(deviceId: string): Promise<WindowsDeviceServiceStatus>;
54
+ uninstall(deviceId: string): Promise<void>;
55
+ }
56
+ export {};
@@ -0,0 +1,238 @@
1
+ import { execFile as execFileCallback } from 'child_process';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import { promisify } from 'util';
5
+ import { deviceStatePaths } from './device-state.js';
6
+ const execFileAsync = promisify(execFileCallback);
7
+ const USER_RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
8
+ function psQuote(value) {
9
+ return `'${String(value).split("'").join("''")}'`;
10
+ }
11
+ function taskSafeDeviceId(deviceId) {
12
+ const value = String(deviceId || '').trim();
13
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(value))
14
+ throw new Error('Invalid device ID for Windows service.');
15
+ return value;
16
+ }
17
+ function isMissingRegistrationError(error) {
18
+ const message = String(error?.stderr || error?.message || error);
19
+ return /not running|cannot find|does not exist|not exist|cannot find the file/i.test(message);
20
+ }
21
+ export function buildWindowsTaskName(deviceId) {
22
+ return `MCP-Device-${taskSafeDeviceId(deviceId)}`;
23
+ }
24
+ function legacyWindowsTaskName(deviceId) {
25
+ return `HCU-Device-${taskSafeDeviceId(deviceId)}`;
26
+ }
27
+ export function buildWindowsRunnerScript(options) {
28
+ return [
29
+ "$ErrorActionPreference = 'Stop'",
30
+ `& ${psQuote(options.nodePath)} ${psQuote(options.entrypoint)} --service`,
31
+ 'exit $LASTEXITCODE',
32
+ ''
33
+ ].join('\r\n');
34
+ }
35
+ export class WindowsDeviceService {
36
+ constructor(options = {}) {
37
+ this.platform = options.platform || process.platform;
38
+ this.execFile = options.execFile || (async (file, args) => {
39
+ const result = await execFileAsync(file, args, { windowsHide: true, encoding: 'utf8' });
40
+ return { stdout: String(result.stdout || ''), stderr: String(result.stderr || '') };
41
+ });
42
+ this.runnerPath = path.resolve(options.runnerPath || path.join(deviceStatePaths().root, 'run-device.ps1'));
43
+ this.legacyRunnerPath = path.resolve(options.legacyRunnerPath || path.join(deviceStatePaths().legacyRoot, 'run-device.ps1'));
44
+ this.nodePath = path.resolve(options.nodePath || process.execPath);
45
+ this.entrypoint = path.resolve(options.entrypoint || process.argv[1]);
46
+ }
47
+ assertWindows() {
48
+ if (this.platform !== 'win32')
49
+ throw new Error('Background MCP Device lifecycle is currently supported on Windows only.');
50
+ }
51
+ async runnerExists() {
52
+ try {
53
+ const stat = await fs.stat(this.runnerPath);
54
+ return stat.isFile();
55
+ }
56
+ catch (error) {
57
+ if (error?.code === 'ENOENT')
58
+ return false;
59
+ throw error;
60
+ }
61
+ }
62
+ async hasUserRunEntry(taskName) {
63
+ try {
64
+ await this.execFile('reg.exe', ['query', USER_RUN_KEY, '/v', taskName]);
65
+ return true;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ async scheduledTaskState(taskName) {
72
+ try {
73
+ const command = `(Get-ScheduledTask -TaskName ${psQuote(taskName)} -ErrorAction Stop).State.ToString()`;
74
+ const result = await this.execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command]);
75
+ return String(result.stdout || '').trim();
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ async deleteRegistrations(taskName) {
82
+ try {
83
+ await this.execFile('schtasks.exe', ['/Delete', '/F', '/TN', taskName]);
84
+ }
85
+ catch (error) {
86
+ if (!isMissingRegistrationError(error))
87
+ throw error;
88
+ }
89
+ try {
90
+ await this.execFile('reg.exe', ['delete', USER_RUN_KEY, '/v', taskName, '/f']);
91
+ }
92
+ catch { /* absent is fine */ }
93
+ }
94
+ commandReferencesLegacyRunner(value) {
95
+ const normalize = (text) => String(text || '').replace(/\//g, '\\').toLowerCase();
96
+ return normalize(value).includes(normalize(this.legacyRunnerPath));
97
+ }
98
+ async deleteVerifiedLegacyRegistrations(taskName) {
99
+ let scheduledAction = null;
100
+ try {
101
+ const command = `(Get-ScheduledTask -TaskName ${psQuote(taskName)} -ErrorAction Stop).Actions | ForEach-Object { ($_.Execute + ' ' + $_.Arguments) }`;
102
+ scheduledAction = String((await this.execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command])).stdout || '');
103
+ }
104
+ catch { /* absent scheduled task is fine */ }
105
+ if (scheduledAction !== null && this.commandReferencesLegacyRunner(scheduledAction)) {
106
+ try {
107
+ await this.execFile('schtasks.exe', ['/Delete', '/F', '/TN', taskName]);
108
+ }
109
+ catch (error) {
110
+ if (!isMissingRegistrationError(error))
111
+ throw error;
112
+ }
113
+ }
114
+ let runValue = null;
115
+ try {
116
+ runValue = String((await this.execFile('reg.exe', ['query', USER_RUN_KEY, '/v', taskName])).stdout || '');
117
+ }
118
+ catch { /* absent Run value is fine */ }
119
+ if (runValue !== null && this.commandReferencesLegacyRunner(runValue)) {
120
+ try {
121
+ await this.execFile('reg.exe', ['delete', USER_RUN_KEY, '/v', taskName, '/f']);
122
+ }
123
+ catch (error) {
124
+ if (!isMissingRegistrationError(error))
125
+ throw error;
126
+ }
127
+ }
128
+ }
129
+ async createAutostartRegistration(taskName) {
130
+ const taskAction = `powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${this.runnerPath}"`;
131
+ try {
132
+ await this.execFile('schtasks.exe', ['/Create', '/F', '/SC', 'ONLOGON', '/TN', taskName, '/TR', taskAction]);
133
+ try {
134
+ await this.execFile('reg.exe', ['delete', USER_RUN_KEY, '/v', taskName, '/f']);
135
+ }
136
+ catch { /* no stale fallback */ }
137
+ return 'task';
138
+ }
139
+ catch (error) {
140
+ const message = String(error?.stderr || error?.message || error);
141
+ if (!/access is denied/i.test(message))
142
+ throw error;
143
+ const runCommand = `powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "${this.runnerPath}"`;
144
+ await this.execFile('reg.exe', ['add', USER_RUN_KEY, '/v', taskName, '/t', 'REG_SZ', '/d', runCommand, '/f']);
145
+ return 'run';
146
+ }
147
+ }
148
+ async startUserProcess() {
149
+ const command = `Start-Process -FilePath 'powershell.exe' -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-WindowStyle','Hidden','-File',${psQuote(this.runnerPath)}) -WindowStyle Hidden`;
150
+ await this.execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command]);
151
+ }
152
+ async userProcessRunning() {
153
+ const command = `$runner = ${psQuote(this.runnerPath)}; if (Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -ne $PID -and $_.CommandLine -like ('*' + $runner + '*') }) { 'Running' } else { 'Stopped' }`;
154
+ const result = await this.execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command]);
155
+ return String(result.stdout || '').trim().toLowerCase() === 'running';
156
+ }
157
+ async install(options) {
158
+ this.assertWindows();
159
+ const taskName = buildWindowsTaskName(options.deviceId);
160
+ const legacyTaskName = legacyWindowsTaskName(options.deviceId);
161
+ const existing = await this.status(options.deviceId).catch(() => null);
162
+ const preserveManual = existing?.installed === true && existing.autostart === false;
163
+ const runner = buildWindowsRunnerScript({ nodePath: this.nodePath, entrypoint: this.entrypoint });
164
+ await fs.mkdir(path.dirname(this.runnerPath), { recursive: true });
165
+ await fs.writeFile(this.runnerPath, runner, { mode: 0o600 });
166
+ await this.deleteVerifiedLegacyRegistrations(legacyTaskName);
167
+ if (preserveManual) {
168
+ await this.deleteRegistrations(taskName);
169
+ return;
170
+ }
171
+ await this.deleteRegistrations(taskName);
172
+ await this.createAutostartRegistration(taskName);
173
+ }
174
+ async setAutostart(deviceId, enabled) {
175
+ this.assertWindows();
176
+ const taskName = buildWindowsTaskName(deviceId);
177
+ if (!await this.runnerExists())
178
+ throw new Error('Background device is not installed.');
179
+ await this.deleteRegistrations(taskName);
180
+ if (enabled)
181
+ await this.createAutostartRegistration(taskName);
182
+ }
183
+ async start(deviceId) {
184
+ this.assertWindows();
185
+ const taskName = buildWindowsTaskName(deviceId);
186
+ if (!await this.runnerExists())
187
+ throw new Error('Background device is not installed.');
188
+ const taskState = await this.scheduledTaskState(taskName);
189
+ if (taskState !== null) {
190
+ await this.execFile('schtasks.exe', ['/Run', '/TN', taskName]);
191
+ return;
192
+ }
193
+ if (!await this.userProcessRunning())
194
+ await this.startUserProcess();
195
+ }
196
+ async stop(deviceId) {
197
+ this.assertWindows();
198
+ const taskName = buildWindowsTaskName(deviceId);
199
+ try {
200
+ await this.execFile('schtasks.exe', ['/End', '/TN', taskName]);
201
+ }
202
+ catch (error) {
203
+ if (!isMissingRegistrationError(error))
204
+ throw error;
205
+ }
206
+ if (await this.runnerExists() && await this.userProcessRunning()) {
207
+ throw new Error('MCP Device runtime did not accept authenticated stop; refusing PID-only termination.');
208
+ }
209
+ }
210
+ async status(deviceId) {
211
+ this.assertWindows();
212
+ const taskName = buildWindowsTaskName(deviceId);
213
+ const installed = await this.runnerExists();
214
+ const taskState = await this.scheduledTaskState(taskName);
215
+ const runEntry = await this.hasUserRunEntry(taskName);
216
+ const running = installed ? await this.userProcessRunning() : false;
217
+ if (!installed && (taskState !== null || runEntry)) {
218
+ return { installed: false, running: false, autostart: true, registration: 'stale', taskName };
219
+ }
220
+ if (taskState !== null)
221
+ return { installed, running, autostart: true, registration: 'task', taskName };
222
+ if (runEntry)
223
+ return { installed, running, autostart: true, registration: 'run', taskName };
224
+ return { installed, running, autostart: false, registration: 'manual', taskName };
225
+ }
226
+ async uninstall(deviceId) {
227
+ this.assertWindows();
228
+ const taskName = buildWindowsTaskName(deviceId);
229
+ const legacyTaskName = legacyWindowsTaskName(deviceId);
230
+ try {
231
+ await this.stop(deviceId);
232
+ }
233
+ catch { /* registration cleanup remains authoritative; runtime is never PID-killed. */ }
234
+ await this.deleteRegistrations(taskName);
235
+ await this.deleteVerifiedLegacyRegistrations(legacyTaskName);
236
+ await fs.rm(this.runnerPath, { force: true });
237
+ }
238
+ }
@@ -0,0 +1,7 @@
1
+ import { ServerResult } from './types.js';
2
+ /**
3
+ * Creates a standard error response for tools
4
+ * @param message The error message
5
+ * @returns A ServerResult with the error message
6
+ */
7
+ export declare function createErrorResponse(message: string): ServerResult;
@@ -0,0 +1,15 @@
1
+ import { capture } from "./utils/capture.js";
2
+ /**
3
+ * Creates a standard error response for tools
4
+ * @param message The error message
5
+ * @returns A ServerResult with the error message
6
+ */
7
+ export function createErrorResponse(message) {
8
+ capture('server_request_error', {
9
+ error: message
10
+ });
11
+ return {
12
+ content: [{ type: "text", text: `Error: ${message}` }],
13
+ isError: true,
14
+ };
15
+ }
@@ -0,0 +1,6 @@
1
+ import { handleEditBlock } from '../tools/edit.js';
2
+ /**
3
+ * Handle edit_block command
4
+ * Uses the enhanced implementation with multiple occurrence support and fuzzy matching
5
+ */
6
+ export { handleEditBlock };
@@ -0,0 +1,6 @@
1
+ import { handleEditBlock } from '../tools/edit.js';
2
+ /**
3
+ * Handle edit_block command
4
+ * Uses the enhanced implementation with multiple occurrence support and fuzzy matching
5
+ */
6
+ export { handleEditBlock };
@@ -0,0 +1,38 @@
1
+ import { ServerResult } from '../types.js';
2
+ /**
3
+ * Resolve a file path to an absolute path for use in structured content.
4
+ * This ensures "Open in folder" always has a valid absolute path.
5
+ */
6
+ export declare function resolveAbsolutePath(filePath: string): string;
7
+ /**
8
+ * Handle read_file command
9
+ */
10
+ export declare function handleReadFile(args: unknown): Promise<ServerResult>;
11
+ /**
12
+ * Handle read_multiple_files command
13
+ */
14
+ export declare function handleReadMultipleFiles(args: unknown): Promise<ServerResult>;
15
+ /**
16
+ * Handle write_file command
17
+ */
18
+ export declare function handleWriteFile(args: unknown): Promise<ServerResult>;
19
+ /**
20
+ * Handle create_directory command
21
+ */
22
+ export declare function handleCreateDirectory(args: unknown): Promise<ServerResult>;
23
+ /**
24
+ * Handle list_directory command
25
+ */
26
+ export declare function handleListDirectory(args: unknown): Promise<ServerResult>;
27
+ /**
28
+ * Handle move_file command
29
+ */
30
+ export declare function handleMoveFile(args: unknown): Promise<ServerResult>;
31
+ /**
32
+ * Handle get_file_info command
33
+ */
34
+ export declare function handleGetFileInfo(args: unknown): Promise<ServerResult>;
35
+ /**
36
+ * Handle write_pdf command
37
+ */
38
+ export declare function handleWritePdf(args: unknown): Promise<ServerResult>;