@hcu-lab.me/mcp-device 1.0.5 → 1.0.7

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 (57) hide show
  1. package/README.md +7 -71
  2. package/dist/bootstrap.js +1 -1
  3. package/dist/config-manager.js +56 -19
  4. package/dist/config.js +1 -1
  5. package/dist/custom-stdio.js +1 -2
  6. package/dist/data/onboarding-prompts.json +22 -11
  7. package/dist/device/device-state.d.ts +2 -5
  8. package/dist/device/device-state.js +3 -85
  9. package/dist/device/device.d.ts +1 -1
  10. package/dist/device/device.js +69 -9
  11. package/dist/device/execution-engine.d.ts +11 -1
  12. package/dist/device/execution-engine.js +85 -126
  13. package/dist/device/gateway-channel.d.ts +19 -1
  14. package/dist/device/gateway-channel.js +107 -21
  15. package/dist/device/gateway-identity.d.ts +1 -0
  16. package/dist/device/gateway-identity.js +4 -0
  17. package/dist/device/gateway-tool-adapter.d.ts +3 -3
  18. package/dist/device/gateway-tool-adapter.js +12 -11
  19. package/dist/device/linux-service.d.ts +6 -0
  20. package/dist/device/linux-service.js +12 -5
  21. package/dist/device/runtime-owner.d.ts +1 -0
  22. package/dist/device/runtime-owner.js +6 -6
  23. package/dist/device/self-update.d.ts +107 -12
  24. package/dist/device/self-update.js +660 -62
  25. package/dist/device/update-helper.cjs +343 -0
  26. package/dist/device/windows-service.d.ts +3 -4
  27. package/dist/device/windows-service.js +5 -44
  28. package/dist/handlers/filesystem-handlers.js +3 -0
  29. package/dist/index.js +7 -3
  30. package/dist/npm-scripts/remote.d.ts +5 -0
  31. package/dist/npm-scripts/remote.js +50 -3
  32. package/dist/server.js +83 -167
  33. package/dist/terminal-manager.js +41 -1
  34. package/dist/tools/config.d.ts +1 -1
  35. package/dist/tools/filesystem.js +3 -3
  36. package/dist/tools/pdf/markdown.js +1 -1
  37. package/dist/tools/schemas.d.ts +2 -3
  38. package/dist/tools/schemas.js +0 -10
  39. package/dist/ui/config-editor/index.html +1 -1
  40. package/dist/ui/file-preview/index.html +1 -1
  41. package/dist/ui/file-preview/styles.css +2 -2
  42. package/dist/utils/capture.d.ts +5 -28
  43. package/dist/utils/capture.js +11 -455
  44. package/dist/utils/feature-flags.d.ts +0 -47
  45. package/dist/utils/feature-flags.js +5 -199
  46. package/dist/utils/files/docx.d.ts +2 -2
  47. package/dist/utils/files/docx.js +2 -2
  48. package/dist/utils/files/factory.js +20 -0
  49. package/dist/utils/files/text.d.ts +1 -0
  50. package/dist/utils/files/text.js +78 -34
  51. package/dist/utils/trackTools.js +23 -2
  52. package/dist/utils/usageTracker.js +2 -24
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/package.json +6 -7
  56. package/dist/tools/feedback.d.ts +0 -8
  57. package/dist/tools/feedback.js +0 -132
package/dist/server.js CHANGED
@@ -1,4 +1,3 @@
1
- import path from 'path';
2
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
2
  import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, ListPromptsRequestSchema, InitializeRequestSchema, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, } from "@modelcontextprotocol/sdk/types.js";
4
3
  import { zodToJsonSchema } from "zod-to-json-schema";
@@ -9,11 +8,10 @@ const OS_GUIDANCE = getOSSpecificGuidance(SYSTEM_INFO);
9
8
  const DEV_TOOL_GUIDANCE = getDevelopmentToolGuidance(SYSTEM_INFO);
10
9
  const PATH_GUIDANCE = `IMPORTANT: ${getPathGuidance(SYSTEM_INFO)} Relative paths may fail as they depend on the current working directory. Tilde paths (~/...) might not work in all contexts. Unless the user explicitly asks for relative paths, use absolute paths.`;
11
10
  const CMD_PREFIX_DESCRIPTION = `This command can be referenced as "MCP Device: ..." or "use MCP Device to ..." in your instructions.`;
12
- import { StartProcessArgsSchema, ReadProcessOutputArgsSchema, InteractWithProcessArgsSchema, ForceTerminateArgsSchema, ListSessionsArgsSchema, KillProcessArgsSchema, ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, GetFileInfoArgsSchema, GetConfigArgsSchema, SetConfigValueArgsSchema, ListProcessesArgsSchema, EditBlockArgsSchema, GetUsageStatsArgsSchema, GiveFeedbackArgsSchema, StartSearchArgsSchema, GetMoreSearchResultsArgsSchema, StopSearchArgsSchema, ListSearchesArgsSchema, GetPromptsArgsSchema, GetRecentToolCallsArgsSchema, WritePdfArgsSchema, toolArgSchemas, } from './tools/schemas.js';
11
+ import { StartProcessArgsSchema, ReadProcessOutputArgsSchema, InteractWithProcessArgsSchema, ForceTerminateArgsSchema, ListSessionsArgsSchema, KillProcessArgsSchema, ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, GetFileInfoArgsSchema, GetConfigArgsSchema, SetConfigValueArgsSchema, ListProcessesArgsSchema, EditBlockArgsSchema, GetUsageStatsArgsSchema, StartSearchArgsSchema, GetMoreSearchResultsArgsSchema, StopSearchArgsSchema, ListSearchesArgsSchema, GetPromptsArgsSchema, GetRecentToolCallsArgsSchema, WritePdfArgsSchema, toolArgSchemas, } from './tools/schemas.js';
13
12
  import { detectUnsupportedParams, getSupportedParams, buildUnsupportedParamsWarning, } from './utils/unsupportedParams.js';
14
13
  import { getConfig, setConfigValue } from './tools/config.js';
15
14
  import { getUsageStats } from './tools/usage.js';
16
- import { giveFeedbackToDesktopCommander } from './tools/feedback.js';
17
15
  import { getPrompts } from './tools/prompts.js';
18
16
  import { trackToolCall } from './utils/trackTools.js';
19
17
  import { usageTracker } from './utils/usageTracker.js';
@@ -89,7 +87,7 @@ function setCurrentCallIsRemote(isRemote) {
89
87
  currentCallIsRemote = isRemote;
90
88
  }
91
89
  // The remote caller's client for the in-flight tool call (e.g. openai-mcp,
92
- // claude-ai). Set per CallTool when the call is remote; null for local calls.
90
+ // remote client). Set per CallTool when the call is remote; null for local calls.
93
91
  // Mirrors currentCallIsRemote so telemetry attributes remote events to the
94
92
  // actual remote client instead of the device's own currentClient (which stays
95
93
  // LOCAL and must not be polluted by remote callers).
@@ -104,11 +102,10 @@ function setCurrentRemoteClient(clientInfo) {
104
102
  /**
105
103
  * True when this server instance is serving remote services rather than a
106
104
  * local MCP client. The device wrapper marks the server it spawns with
107
- * MCP_DEVICE_REMOTE=true (see device/execution-engine.ts);
108
- * the client-name check covers older wrappers that predate the env marker.
105
+ * MCP_DEVICE_REMOTE=true (see device/execution-engine.ts).
109
106
  */
110
107
  function isRemoteClientContext(clientName) {
111
- return process.env.MCP_DEVICE_REMOTE === 'true' || clientName === 'desktop-commander-client';
108
+ return process.env.MCP_DEVICE_REMOTE === 'true';
112
109
  }
113
110
  /**
114
111
  * Unified way to update client information
@@ -138,40 +135,19 @@ server.setRequestHandler(InitializeRequestSchema, async (request) => {
138
135
  const clientInfo = request.params?.clientInfo;
139
136
  if (clientInfo) {
140
137
  await updateCurrentClient(clientInfo);
141
- // Welcome page for new users (A/B test controlled) — all clients except
142
- // the Desktop Commander app and remote contexts. Further exclusions are
143
- // flag-served via welcome_page_excluded_clients (e.g. claude-code, which
144
- // covers Claude Code and Cowork plugin sessions — both identify as
145
- // `claude-code` and provide their own onboarding surface).
146
- const isWelcomePageEligibleClient = currentClient.name !== 'desktop-commander-app'
147
- && currentClient.name !== 'desktop-commander'
148
- && !isRemoteClientContext(currentClient.name)
138
+ const isWelcomePageEligibleClient = !isRemoteClientContext(currentClient.name)
149
139
  && !global.disableOnboarding;
150
- if (isWelcomePageEligibleClient) {
140
+ if (isRemoteClientContext(currentClient.name)) {
141
+ // Remote child engines do not mutate local onboarding state.
142
+ }
143
+ else if (isWelcomePageEligibleClient) {
151
144
  await handleWelcomePageOnboarding(currentClient.name);
152
145
  }
153
146
  else {
154
- // Do not carry a first-run page over to a client that is made
155
- // eligible in a later release.
156
147
  await skipWelcomePageOnboarding();
157
148
  }
149
+ capture('run_server_mcp_initialized');
158
150
  }
159
- // Raw host environment signals (no PII, undefined when absent). Some
160
- // hosts share a clientInfo name — Claude Code CLI, Claude Code inside
161
- // the Claude Desktop app, and Cowork all report 'claude-code' — and
162
- // these let analytics tell them apart without client-specific
163
- // branching in code. Verified signatures: CLI → entrypoint 'cli';
164
- // CC-in-desktop → entrypoint 'claude-desktop'; Cowork → no
165
- // entrypoint/agent, plugin id 'desktop-commander-inline'.
166
- // Values truncated to GA4's 100-char param limit (same convention as
167
- // containerName/containerImage) so an oversized value can never get
168
- // the whole event rejected.
169
- capture('run_server_mcp_initialized', {
170
- host_entrypoint: process.env.CLAUDE_CODE_ENTRYPOINT?.substring(0, 100),
171
- host_agent: process.env.AI_AGENT?.substring(0, 100),
172
- host_plugin_id: process.env.CLAUDE_PLUGIN_DATA
173
- ? path.basename(process.env.CLAUDE_PLUGIN_DATA).substring(0, 100) : undefined
174
- });
175
151
  // Negotiate protocol version with client
176
152
  const requestedVersion = request.params?.protocolVersion;
177
153
  const protocolVersion = (requestedVersion && SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion))
@@ -203,15 +179,7 @@ deferLog('info', 'Setting up request handlers...');
203
179
  /**
204
180
  * Check if a tool should be included based on current client
205
181
  */
206
- function shouldIncludeTool(toolName) {
207
- // Exclude these tools for desktop-commander client (DC-specific meta-tools not useful when DC itself is the client)
208
- if (currentClient?.name === 'desktop-commander-app') {
209
- if (toolName === 'give_feedback_to_desktop_commander' || toolName === 'get_prompts') {
210
- return false;
211
- }
212
- }
213
- // Add more conditional tool logic here as needed
214
- // Example: if (toolName === 'some_tool' && currentClient?.name === 'some_client') return false;
182
+ function shouldIncludeTool(_toolName) {
215
183
  return true;
216
184
  }
217
185
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -1069,48 +1037,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1069
1037
  readOnlyHint: true,
1070
1038
  },
1071
1039
  },
1072
- {
1073
- name: "give_feedback_to_desktop_commander",
1074
- description: `
1075
- Open feedback form in browser to provide feedback about MCP Device.
1076
-
1077
- IMPORTANT: This tool simply opens the feedback form - no pre-filling available.
1078
- The user will fill out the form manually in their browser.
1079
-
1080
- WORKFLOW:
1081
- 1. When user agrees to give feedback, just call this tool immediately
1082
- 2. No need to ask questions or collect information
1083
- 3. Tool opens form with only usage statistics pre-filled automatically:
1084
- - tool_call_count: Number of commands they've made
1085
- - days_using: How many days they've used MCP Device
1086
- - platform: Their operating system (Mac/Windows/Linux)
1087
- - client_id: Analytics identifier
1088
-
1089
- All survey questions will be answered directly in the form:
1090
- - Job title and technical comfort level
1091
- - Company URL for industry context
1092
- - Other AI tools they use
1093
- - MCP Device's biggest advantage
1094
- - How they typically use it
1095
- - Recommendation likelihood (0-10)
1096
- - User study participation interest
1097
- - Email and any additional feedback
1098
-
1099
- EXAMPLE INTERACTION:
1100
- User: "sure, I'll give feedback"
1101
- Claude: "Perfect! Let me open the feedback form for you."
1102
- [calls tool immediately]
1103
-
1104
- No parameters are needed - just call the tool to open the form.
1105
-
1106
- ${CMD_PREFIX_DESCRIPTION}`,
1107
- inputSchema: zodToJsonSchema(GiveFeedbackArgsSchema),
1108
- annotations: {
1109
- title: "Give Feedback",
1110
- readOnlyHint: false,
1111
- openWorldHint: true,
1112
- },
1113
- },
1114
1040
  {
1115
1041
  name: "get_prompts",
1116
1042
  description: `
@@ -1190,7 +1116,7 @@ async function handleCallToolRequest(request) {
1190
1116
  // add remote flag (convert to string for telemetry)
1191
1117
  telemetryData.remote = String(metadata.remote);
1192
1118
  // Remote calls carry the originating MCP client (e.g. openai-mcp,
1193
- // claude-ai) in _meta.clientInfo. Attribute this call to that remote
1119
+ // remote client) in _meta.clientInfo. Attribute this call to that remote
1194
1120
  // client — NOT the device's own currentClient. Fall back to a sentinel
1195
1121
  // when it's absent so the call is visibly remote-but-unattributed
1196
1122
  // rather than masquerading as the local device client. We deliberately
@@ -1332,18 +1258,6 @@ async function handleCallToolRequest(request) {
1332
1258
  };
1333
1259
  }
1334
1260
  break;
1335
- case "give_feedback_to_desktop_commander":
1336
- try {
1337
- result = await giveFeedbackToDesktopCommander(args);
1338
- }
1339
- catch (error) {
1340
- capture('server_request_error', { message: `Error in give_feedback_to_desktop_commander handler: ${error}` });
1341
- result = {
1342
- content: [{ type: "text", text: `Error: Failed to open feedback form` }],
1343
- isError: true,
1344
- };
1345
- }
1346
- break;
1347
1261
  // Terminal tools
1348
1262
  case "start_process":
1349
1263
  result = await handlers.handleStartProcess(args);
@@ -1422,7 +1336,7 @@ async function handleCallToolRequest(request) {
1422
1336
  'get_recent_tool_calls',
1423
1337
  'track_ui_event'
1424
1338
  ];
1425
- if (!EXCLUDED_TOOLS.includes(name)) {
1339
+ if (!currentCallIsRemote && process.env.MCP_DEVICE_REMOTE !== 'true' && !EXCLUDED_TOOLS.includes(name)) {
1426
1340
  toolHistory.addCall(name, args, result, duration);
1427
1341
  }
1428
1342
  // Track success or failure based on result
@@ -1436,77 +1350,79 @@ async function handleCallToolRequest(request) {
1436
1350
  else {
1437
1351
  await usageTracker.trackSuccess(name);
1438
1352
  console.log(`[FEEDBACK DEBUG] Tool ${name} succeeded, checking feedback...`);
1439
- // Check if should show onboarding (before feedback - first-time users are priority)
1440
- const shouldShowOnboarding = await usageTracker.shouldShowOnboarding();
1441
- console.log(`[ONBOARDING DEBUG] Should show onboarding: ${shouldShowOnboarding}`);
1442
- if (shouldShowOnboarding) {
1443
- console.log(`[ONBOARDING DEBUG] Generating onboarding message...`);
1444
- const onboardingResult = await usageTracker.getOnboardingMessage();
1445
- console.log(`[ONBOARDING DEBUG] Generated variant: ${onboardingResult.variant}`);
1446
- // Capture onboarding prompt injection event
1447
- const stats = await usageTracker.getStats();
1448
- await capture('server_onboarding_shown', {
1449
- trigger_tool: name,
1450
- total_calls: stats.totalToolCalls,
1451
- successful_calls: stats.successfulCalls,
1452
- days_since_first_use: Math.floor((Date.now() - stats.firstUsed) / (1000 * 60 * 60 * 24)),
1453
- total_sessions: stats.totalSessions,
1454
- message_variant: onboardingResult.variant
1455
- });
1456
- // Inject onboarding message for the LLM
1457
- if (result.content && result.content.length > 0 && result.content[0].type === "text") {
1458
- const currentContent = result.content[0].text || '';
1459
- result.content[0].text = `${currentContent}${onboardingResult.message}`;
1460
- }
1461
- else {
1462
- result.content = [
1463
- ...(result.content || []),
1464
- {
1465
- type: "text",
1466
- text: onboardingResult.message
1467
- }
1468
- ];
1469
- }
1470
- // Mark that we've shown onboarding (to prevent spam)
1471
- await usageTracker.markOnboardingShown(onboardingResult.variant);
1472
- }
1473
- // Check if should prompt for feedback (only on successful operations)
1474
- const shouldPrompt = await usageTracker.shouldPromptForFeedback();
1475
- console.log(`[FEEDBACK DEBUG] Should prompt for feedback: ${shouldPrompt}`);
1476
- if (shouldPrompt) {
1477
- console.log(`[FEEDBACK DEBUG] Generating feedback message...`);
1478
- const feedbackResult = await usageTracker.getFeedbackPromptMessage();
1479
- console.log(`[FEEDBACK DEBUG] Generated variant: ${feedbackResult.variant}`);
1480
- // Capture feedback prompt injection event
1481
- const stats = await usageTracker.getStats();
1482
- await capture('feedback_prompt_injected', {
1483
- trigger_tool: name,
1484
- total_calls: stats.totalToolCalls,
1485
- successful_calls: stats.successfulCalls,
1486
- failed_calls: stats.failedCalls,
1487
- days_since_first_use: Math.floor((Date.now() - stats.firstUsed) / (1000 * 60 * 60 * 24)),
1488
- total_sessions: stats.totalSessions,
1489
- message_variant: feedbackResult.variant
1490
- });
1491
- // Inject feedback instruction for the LLM
1492
- if (result.content && result.content.length > 0 && result.content[0].type === "text") {
1493
- const currentContent = result.content[0].text || '';
1494
- result.content[0].text = `${currentContent}${feedbackResult.message}`;
1353
+ if (!currentCallIsRemote && !isRemoteClientContext(currentClient.name)) {
1354
+ // Check if should show onboarding (before feedback - first-time users are priority)
1355
+ const shouldShowOnboarding = await usageTracker.shouldShowOnboarding();
1356
+ console.log(`[ONBOARDING DEBUG] Should show onboarding: ${shouldShowOnboarding}`);
1357
+ if (shouldShowOnboarding) {
1358
+ console.log(`[ONBOARDING DEBUG] Generating onboarding message...`);
1359
+ const onboardingResult = await usageTracker.getOnboardingMessage();
1360
+ console.log(`[ONBOARDING DEBUG] Generated variant: ${onboardingResult.variant}`);
1361
+ // Capture onboarding prompt injection event
1362
+ const stats = await usageTracker.getStats();
1363
+ await capture('server_onboarding_shown', {
1364
+ trigger_tool: name,
1365
+ total_calls: stats.totalToolCalls,
1366
+ successful_calls: stats.successfulCalls,
1367
+ days_since_first_use: Math.floor((Date.now() - stats.firstUsed) / (1000 * 60 * 60 * 24)),
1368
+ total_sessions: stats.totalSessions,
1369
+ message_variant: onboardingResult.variant
1370
+ });
1371
+ // Inject onboarding message for the LLM
1372
+ if (result.content && result.content.length > 0 && result.content[0].type === "text") {
1373
+ const currentContent = result.content[0].text || '';
1374
+ result.content[0].text = `${currentContent}${onboardingResult.message}`;
1375
+ }
1376
+ else {
1377
+ result.content = [
1378
+ ...(result.content || []),
1379
+ {
1380
+ type: "text",
1381
+ text: onboardingResult.message
1382
+ }
1383
+ ];
1384
+ }
1385
+ // Mark that we've shown onboarding (to prevent spam)
1386
+ await usageTracker.markOnboardingShown(onboardingResult.variant);
1495
1387
  }
1496
- else {
1497
- result.content = [
1498
- ...(result.content || []),
1499
- {
1500
- type: "text",
1501
- text: feedbackResult.message
1502
- }
1503
- ];
1388
+ // Check if should prompt for feedback (only on successful operations)
1389
+ const shouldPrompt = await usageTracker.shouldPromptForFeedback();
1390
+ console.log(`[FEEDBACK DEBUG] Should prompt for feedback: ${shouldPrompt}`);
1391
+ if (shouldPrompt) {
1392
+ console.log(`[FEEDBACK DEBUG] Generating feedback message...`);
1393
+ const feedbackResult = await usageTracker.getFeedbackPromptMessage();
1394
+ console.log(`[FEEDBACK DEBUG] Generated variant: ${feedbackResult.variant}`);
1395
+ // Capture feedback prompt injection event
1396
+ const stats = await usageTracker.getStats();
1397
+ await capture('feedback_prompt_injected', {
1398
+ trigger_tool: name,
1399
+ total_calls: stats.totalToolCalls,
1400
+ successful_calls: stats.successfulCalls,
1401
+ failed_calls: stats.failedCalls,
1402
+ days_since_first_use: Math.floor((Date.now() - stats.firstUsed) / (1000 * 60 * 60 * 24)),
1403
+ total_sessions: stats.totalSessions,
1404
+ message_variant: feedbackResult.variant
1405
+ });
1406
+ // Inject feedback instruction for the LLM
1407
+ if (result.content && result.content.length > 0 && result.content[0].type === "text") {
1408
+ const currentContent = result.content[0].text || '';
1409
+ result.content[0].text = `${currentContent}${feedbackResult.message}`;
1410
+ }
1411
+ else {
1412
+ result.content = [
1413
+ ...(result.content || []),
1414
+ {
1415
+ type: "text",
1416
+ text: feedbackResult.message
1417
+ }
1418
+ ];
1419
+ }
1420
+ // Mark that we've prompted (to prevent spam)
1421
+ await usageTracker.markFeedbackPrompted();
1504
1422
  }
1505
- // Mark that we've prompted (to prevent spam)
1506
- await usageTracker.markFeedbackPrompted();
1423
+ // Check if should prompt about Docker environment
1424
+ result = await processDockerPrompt(result, name);
1507
1425
  }
1508
- // Check if should prompt about Docker environment
1509
- result = await processDockerPrompt(result, name);
1510
1426
  }
1511
1427
  // If the caller sent parameters this tool does not support, Zod silently
1512
1428
  // strips them. Prepend a corrective warning so the model knows they were
@@ -8,7 +8,7 @@ import { analyzeProcessState } from './utils/process-detection.js';
8
8
  * Standard Windows PATHEXT value, used to repair a corrupted PATHEXT before
9
9
  * spawning child shells.
10
10
  *
11
- * On some Windows Claude Desktop / DXT launches the server process inherits a
11
+ * On some Windows host launches the server process inherits a
12
12
  * broken PATHEXT (observed as ".CPL" only). Because we build the child env from
13
13
  * { ...process.env }, that broken value would propagate into every spawned
14
14
  * shell, stripping ".EXE" and breaking resolution of git / node / python / rg /
@@ -205,6 +205,21 @@ export class TerminalManager {
205
205
  // Spawn the process with appropriate arguments
206
206
  const childProcess = spawn(spawnConfig.executable, spawnConfig.args, spawnOptions);
207
207
  let output = '';
208
+ // spawn() reports resolution/launch failures asynchronously via 'error'.
209
+ // A listener-less ChildProcess error becomes an uncaughtException and kills
210
+ // the inherited MCP server. Attach before any early-return path and keep
211
+ // the listener for the process lifetime.
212
+ let forwardProcessError = null;
213
+ let pendingProcessError = null;
214
+ childProcess.on('error', (err) => {
215
+ if (forwardProcessError) {
216
+ forwardProcessError(err);
217
+ }
218
+ else {
219
+ pendingProcessError = err;
220
+ console.error(`Process error for "${command}": ${err.message}`);
221
+ }
222
+ });
208
223
  // Ensure childProcess.pid is defined before proceeding
209
224
  if (!childProcess.pid) {
210
225
  // Return a consistent error object instead of throwing
@@ -259,6 +274,31 @@ export class TerminalManager {
259
274
  }
260
275
  resolve(result);
261
276
  };
277
+ // Once resolveOnce exists, route runtime/spawn errors into the normal
278
+ // command result path so callers do not hang and the session is retired.
279
+ forwardProcessError = (err) => {
280
+ // A late ChildProcess 'error' can arrive after the caller has already
281
+ // received a live/background session. Do not silently retire that
282
+ // session; the normal 'exit' event remains authoritative for cleanup.
283
+ if (resolved) {
284
+ this.appendToLineBuffer(session, `\nProcess error: ${err.message}\n`);
285
+ console.error(`Process error for "${command}": ${err.message}`);
286
+ return;
287
+ }
288
+ if (childProcess.pid)
289
+ this.sessions.delete(childProcess.pid);
290
+ exitReason = 'process_exit';
291
+ resolveOnce({
292
+ pid: childProcess.pid ?? -1,
293
+ output: output + `\nProcess error: ${err.message}`,
294
+ isBlocked: false
295
+ });
296
+ };
297
+ // Spawn failures normally arrive on the next tick, but replay any error
298
+ // emitted between spawn() and resolver initialization.
299
+ if (pendingProcessError) {
300
+ forwardProcessError(pendingProcessError);
301
+ }
262
302
  childProcess.stdout.on('data', (data) => {
263
303
  const text = data.toString();
264
304
  const now = Date.now();
@@ -65,7 +65,7 @@ export declare function getConfig(): Promise<{
65
65
  availableShells: string[];
66
66
  };
67
67
  entries: {
68
- key: "defaultShell" | "blockedCommands" | "allowedDirectories" | "telemetryEnabled" | "fileReadLineLimit" | "fileWriteLineLimit";
68
+ key: "blockedCommands" | "defaultShell" | "allowedDirectories" | "telemetryEnabled" | "fileWriteLineLimit" | "fileReadLineLimit";
69
69
  value: unknown;
70
70
  valueType: "string" | "number" | "boolean" | "array";
71
71
  editable: boolean;
@@ -84,10 +84,10 @@ function buildPermissionError(filePath, errCode) {
84
84
  ` 5. The app does not have permission to access this location (macOS Full Disk Access).`,
85
85
  ];
86
86
  if (isMac) {
87
- lines.push(` → Go to System Settings → Privacy & Security → Full Disk Access and enable Claude.`);
87
+ lines.push(` → Go to System Settings → Privacy & Security → Full Disk Access and enable the terminal or runtime hosting MCP Device.`);
88
88
  lines.push(` → To open that pane directly, run in terminal:`);
89
89
  lines.push(` open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"`);
90
- lines.push(` Then find "Claude" in the list and enable the toggle next to it.`);
90
+ lines.push(` Then enable the toggle for the application that launches MCP Device.`);
91
91
  }
92
92
  else {
93
93
  lines.push(` → Check that the app has permission to access this file location.`);
@@ -265,7 +265,7 @@ export async function validatePath(requestedPath) {
265
265
  const pathForNextCheck = resolvedRealPath ?? absoluteOriginal;
266
266
  // Direct Gateway device workers are already authenticated and policy-gated by
267
267
  // the central gateway. In that dedicated remote context, do not inherit a
268
- // workstation's historical Desktop Commander directory allowlist. An optional
268
+ // workstation's historical directory allowlist. An optional
269
269
  // MCP_GATEWAY_ALLOWED_ROOTS restriction is enforced by GatewayToolAdapter before
270
270
  // calls reach this filesystem layer.
271
271
  const remoteGatewayContext = process.env.MCP_DEVICE_REMOTE === 'true';
@@ -9,7 +9,7 @@ const isUrl = (source) => source.startsWith('http://') || source.startsWith('htt
9
9
  let cachedChromePath = null; // null = not checked yet
10
10
  let chromeCheckPromise = null;
11
11
  /**
12
- * Get Desktop Commander's private Puppeteer cache directory.
12
+ * Get the local engine's private Puppeteer cache directory.
13
13
  */
14
14
  function getPuppeteerCacheDir() {
15
15
  return join(dirname(CONFIG_FILE), 'puppeteer-cache');
@@ -117,15 +117,15 @@ export declare const WriteFileArgsSchema: z.ZodObject<{
117
117
  mode: z.ZodDefault<z.ZodEnum<["rewrite", "append"]>>;
118
118
  origin: z.ZodOptional<z.ZodEnum<["ui", "llm"]>>;
119
119
  }, "strip", z.ZodTypeAny, {
120
+ mode: "rewrite" | "append";
120
121
  path: string;
121
122
  content: string;
122
- mode: "rewrite" | "append";
123
123
  origin?: "ui" | "llm" | undefined;
124
124
  }, {
125
125
  path: string;
126
126
  content: string;
127
- origin?: "ui" | "llm" | undefined;
128
127
  mode?: "rewrite" | "append" | undefined;
128
+ origin?: "ui" | "llm" | undefined;
129
129
  }>;
130
130
  export declare const PdfInsertOperationSchema: z.ZodObject<{
131
131
  type: z.ZodLiteral<"insert">;
@@ -348,7 +348,6 @@ export declare const InteractWithProcessArgsSchema: z.ZodObject<{
348
348
  wait_for_prompt?: boolean | undefined;
349
349
  }>;
350
350
  export declare const GetUsageStatsArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
351
- export declare const GiveFeedbackArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
352
351
  export declare const StartSearchArgsSchema: z.ZodObject<{
353
352
  path: z.ZodString;
354
353
  pattern: z.ZodString;
@@ -153,15 +153,6 @@ export const InteractWithProcessArgsSchema = z.object({
153
153
  });
154
154
  // Usage stats schema
155
155
  export const GetUsageStatsArgsSchema = z.object({});
156
- // Feedback tool schema - no pre-filled parameters, all user input
157
- export const GiveFeedbackArgsSchema = z.object({
158
- // No parameters needed - form will be filled manually by user
159
- // Only auto-filled hidden fields remain:
160
- // - tool_call_count (auto)
161
- // - days_using (auto)
162
- // - platform (auto)
163
- // - client_id (auto)
164
- });
165
156
  // Search schemas (renamed for natural language)
166
157
  export const StartSearchArgsSchema = z.object({
167
158
  path: z.string(),
@@ -236,7 +227,6 @@ export const toolArgSchemas = {
236
227
  kill_process: KillProcessArgsSchema,
237
228
  get_usage_stats: GetUsageStatsArgsSchema,
238
229
  get_recent_tool_calls: GetRecentToolCallsArgsSchema,
239
- give_feedback_to_desktop_commander: GiveFeedbackArgsSchema,
240
230
  get_prompts: GetPromptsArgsSchema,
241
231
  track_ui_event: TrackUiEventArgsSchema,
242
232
  };
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>Desktop Commander Config Editor</title>
6
+ <title>MCP Device Config Editor</title>
7
7
  <link rel="stylesheet" href="./styles.css" />
8
8
  </head>
9
9
  <body>
@@ -6,7 +6,7 @@
6
6
  <head>
7
7
  <meta charset="utf-8" />
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1" />
9
- <title>Desktop Commander File Preview</title>
9
+ <title>MCP Device File Preview</title>
10
10
  <link rel="stylesheet" href="./styles.css" />
11
11
  </head>
12
12
  <body>
@@ -141,7 +141,7 @@ body.dc-ready {
141
141
 
142
142
 
143
143
  /*
144
- * App-specific styling for File Preview — tuned to blend with Claude's UI.
144
+ * App-specific styling for File Preview — tuned for the MCP Device UI.
145
145
  */
146
146
  :root {
147
147
  --code-bg: var(--panel);
@@ -166,7 +166,7 @@ body.dc-ready {
166
166
  --toc-rail-width: 320px;
167
167
  }
168
168
 
169
- /* ── Panel (Claude-style card) ── */
169
+ /* ── Panel (compact card) ── */
170
170
 
171
171
  .panel {
172
172
  margin-top: 6px;
@@ -1,35 +1,12 @@
1
1
  export declare function runInUiOriginCallContext<T>(fn: () => T): T;
2
2
  export declare function isInsideUiOriginCall(): boolean;
3
- /**
4
- * Hard kill-switch for telemetry via environment variable.
5
- *
6
- * Independent of the persisted `telemetryEnabled` config so that tests, CI and
7
- * one-off runs can suppress all analytics without mutating the user's config.
8
- * Set DESKTOP_COMMANDER_DISABLE_TELEMETRY to 1/true/yes/on to disable.
9
- */
10
3
  export declare function isTelemetryDisabledByEnv(): boolean;
11
- /**
12
- * Sanitizes error objects to remove potentially sensitive information like file paths
13
- * @param error Error object or string to sanitize
14
- * @returns An object with sanitized message and optional error code
15
- */
16
4
  export declare function sanitizeError(error: any): {
17
5
  message: string;
18
6
  code?: string;
19
7
  };
20
- /**
21
- * Send an event to telemetry
22
- * @param event Event name
23
- * @param properties Optional event properties
24
- */
25
- export declare const captureBase: (captureURL: string, event: string, properties?: any) => Promise<void>;
26
- export declare const capture: (event: string, properties?: any) => Promise<void>;
27
- export declare const capture_call_tool: (event: string, properties?: any) => Promise<void>;
28
- export declare const capture_ui_event: (event: string, properties?: any) => Promise<void>;
29
- /**
30
- * Wrapper for capture() that automatically adds remote flag for device telemetry
31
- * Also adds additional privacy filtering to remove sensitive identity information
32
- * @param event Event name
33
- * @param properties Optional event properties
34
- */
35
- export declare const captureRemote: (event: string, properties?: any) => Promise<void>;
8
+ export declare const captureBase: (_captureURL: string, _event: string, _properties?: any) => Promise<void>;
9
+ export declare const capture: (_event: string, _properties?: any) => Promise<void>;
10
+ export declare const capture_call_tool: (_event: string, _properties?: any) => Promise<void>;
11
+ export declare const capture_ui_event: (_event: string, _properties?: any) => Promise<void>;
12
+ export declare const captureRemote: (_event: string, _properties?: any) => Promise<void>;