starmux 0.12.0 → 0.13.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.
- package/dist/src/cli/mac-mcp-gateway.js +4 -3
- package/dist/src/cli/mac-mcp-gateway.js.map +1 -1
- package/dist/src/cli/starmux.js +1 -1
- package/dist/src/cli/starmux.js.map +1 -1
- package/dist/src/runtime-paths.js +3 -3
- package/dist/src/runtime-paths.js.map +1 -1
- package/dist/vendor/desktop-commander/LICENSE +21 -0
- package/dist/vendor/desktop-commander/dist/bootstrap.js +22 -0
- package/dist/vendor/desktop-commander/dist/command-manager.js +246 -0
- package/dist/vendor/desktop-commander/dist/config-field-definitions.js +37 -0
- package/dist/vendor/desktop-commander/dist/config-manager.js +634 -0
- package/dist/vendor/desktop-commander/dist/config-recovery.js +81 -0
- package/dist/vendor/desktop-commander/dist/config.js +19 -0
- package/dist/vendor/desktop-commander/dist/custom-stdio.js +358 -0
- package/dist/vendor/desktop-commander/dist/error-handlers.js +15 -0
- package/dist/vendor/desktop-commander/dist/handlers/edit-search-handlers.js +6 -0
- package/dist/vendor/desktop-commander/dist/handlers/filesystem-handlers.js +450 -0
- package/dist/vendor/desktop-commander/dist/handlers/history-handlers.js +67 -0
- package/dist/vendor/desktop-commander/dist/handlers/index.js +7 -0
- package/dist/vendor/desktop-commander/dist/handlers/process-handlers.js +15 -0
- package/dist/vendor/desktop-commander/dist/handlers/search-answers.js +172 -0
- package/dist/vendor/desktop-commander/dist/handlers/search-handlers.js +136 -0
- package/dist/vendor/desktop-commander/dist/handlers/terminal-handlers.js +35 -0
- package/dist/vendor/desktop-commander/dist/index.js +88 -0
- package/dist/vendor/desktop-commander/dist/search-manager.js +1262 -0
- package/dist/vendor/desktop-commander/dist/server.js +1414 -0
- package/dist/vendor/desktop-commander/dist/terminal-manager.js +757 -0
- package/dist/vendor/desktop-commander/dist/tools/config.js +211 -0
- package/dist/vendor/desktop-commander/dist/tools/edit.js +455 -0
- package/dist/vendor/desktop-commander/dist/tools/feedback.js +10 -0
- package/dist/vendor/desktop-commander/dist/tools/filesystem.js +1047 -0
- package/dist/vendor/desktop-commander/dist/tools/fuzzySearch.js +74 -0
- package/dist/vendor/desktop-commander/dist/tools/fuzzySearchCore.js +125 -0
- package/dist/vendor/desktop-commander/dist/tools/improved-process-tools.js +726 -0
- package/dist/vendor/desktop-commander/dist/tools/mime-types.js +28 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/extract-images.js +131 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/index.js +4 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/lib/pdf2md.js +78 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/manipulations.js +112 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/markdown.js +30 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/utils.js +45 -0
- package/dist/vendor/desktop-commander/dist/tools/process.js +90 -0
- package/dist/vendor/desktop-commander/dist/tools/prompts.js +259 -0
- package/dist/vendor/desktop-commander/dist/tools/schemas.js +242 -0
- package/dist/vendor/desktop-commander/dist/tools/usage.js +24 -0
- package/dist/vendor/desktop-commander/dist/types.js +1 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/config-editor-runtime.js +150 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/index.html +13 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/styles.css +587 -0
- package/dist/vendor/desktop-commander/dist/ui/contracts.js +57 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/index.html +16 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/preview-runtime.js +471 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/shared/preview-file-types.js +59 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/styles.css +1631 -0
- package/dist/vendor/desktop-commander/dist/ui/resources.js +93 -0
- package/dist/vendor/desktop-commander/dist/utils/ab-test.js +122 -0
- package/dist/vendor/desktop-commander/dist/utils/atomic-write.js +80 -0
- package/dist/vendor/desktop-commander/dist/utils/capture.js +40 -0
- package/dist/vendor/desktop-commander/dist/utils/dockerPrompt.js +9 -0
- package/dist/vendor/desktop-commander/dist/utils/exit-process.js +31 -0
- package/dist/vendor/desktop-commander/dist/utils/feature-flags.js +18 -0
- package/dist/vendor/desktop-commander/dist/utils/files/base.js +5 -0
- package/dist/vendor/desktop-commander/dist/utils/files/binary.js +65 -0
- package/dist/vendor/desktop-commander/dist/utils/files/docx.js +694 -0
- package/dist/vendor/desktop-commander/dist/utils/files/excel.js +497 -0
- package/dist/vendor/desktop-commander/dist/utils/files/factory.js +168 -0
- package/dist/vendor/desktop-commander/dist/utils/files/image.js +89 -0
- package/dist/vendor/desktop-commander/dist/utils/files/index.js +14 -0
- package/dist/vendor/desktop-commander/dist/utils/files/pdf.js +124 -0
- package/dist/vendor/desktop-commander/dist/utils/files/text.js +423 -0
- package/dist/vendor/desktop-commander/dist/utils/fuzzySearchLogger.js +126 -0
- package/dist/vendor/desktop-commander/dist/utils/internal-facts.js +23 -0
- package/dist/vendor/desktop-commander/dist/utils/lazy-package.js +44 -0
- package/dist/vendor/desktop-commander/dist/utils/lineEndingHandler.js +77 -0
- package/dist/vendor/desktop-commander/dist/utils/logger.js +72 -0
- package/dist/vendor/desktop-commander/dist/utils/mcp-ui-ab-test.js +62 -0
- package/dist/vendor/desktop-commander/dist/utils/open-browser.js +2 -0
- package/dist/vendor/desktop-commander/dist/utils/process-detection.js +260 -0
- package/dist/vendor/desktop-commander/dist/utils/process-tree.js +223 -0
- package/dist/vendor/desktop-commander/dist/utils/rename.js +74 -0
- package/dist/vendor/desktop-commander/dist/utils/ripgrep-resolver.js +69 -0
- package/dist/vendor/desktop-commander/dist/utils/shell.js +187 -0
- package/dist/vendor/desktop-commander/dist/utils/system-info.js +727 -0
- package/dist/vendor/desktop-commander/dist/utils/terminal-timestamps.js +18 -0
- package/dist/vendor/desktop-commander/dist/utils/toolHistory.js +309 -0
- package/dist/vendor/desktop-commander/dist/utils/trackTools.js +55 -0
- package/dist/vendor/desktop-commander/dist/utils/unsupportedParams.js +66 -0
- package/dist/vendor/desktop-commander/dist/utils/usageTracker.js +225 -0
- package/dist/vendor/desktop-commander/dist/utils/welcome-onboarding.js +2 -0
- package/dist/vendor/desktop-commander/dist/utils/withTimeout.js +85 -0
- package/dist/vendor/desktop-commander/dist/version.js +1 -0
- package/dist/vendor/desktop-commander/package.json +15 -0
- package/package.json +14 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
/**
|
|
5
|
+
* The settings still readable in a corrupt config.json: the longest beginning
|
|
6
|
+
* of it that is a JSON object once closed, so every complete top-level setting
|
|
7
|
+
* before the first error. {} when there is none.
|
|
8
|
+
*/
|
|
9
|
+
export function salvageSettings(text) {
|
|
10
|
+
const body = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
11
|
+
// A readable beginning can end at each comma between top-level settings, or at
|
|
12
|
+
// the brace that closes the object (anything after it is ignored)
|
|
13
|
+
const ends = [];
|
|
14
|
+
let depth = 0;
|
|
15
|
+
let inString = false;
|
|
16
|
+
let escaped = false;
|
|
17
|
+
for (let i = 0; i < body.length; i++) {
|
|
18
|
+
const char = body[i];
|
|
19
|
+
if (inString) {
|
|
20
|
+
if (escaped)
|
|
21
|
+
escaped = false;
|
|
22
|
+
else if (char === '\\')
|
|
23
|
+
escaped = true;
|
|
24
|
+
else if (char === '"')
|
|
25
|
+
inString = false;
|
|
26
|
+
}
|
|
27
|
+
else if (char === '"') {
|
|
28
|
+
inString = true;
|
|
29
|
+
}
|
|
30
|
+
else if (char === '{' || char === '[') {
|
|
31
|
+
depth++;
|
|
32
|
+
}
|
|
33
|
+
else if (char === '}' || char === ']') {
|
|
34
|
+
if (--depth === 0) {
|
|
35
|
+
ends.push(i);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (char === ',' && depth === 1) {
|
|
40
|
+
ends.push(i);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const end of ends.reverse()) {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(body.slice(0, end) + '}');
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Not valid JSON up to here either, so try a shorter beginning
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* What a corrupt config.json is recovered as: the defaults, then the settings
|
|
55
|
+
* last read (while running), then the settings salvaged from it. The welcome
|
|
56
|
+
* page stays off, as for any existing install.
|
|
57
|
+
*/
|
|
58
|
+
export function buildRecoveredConfig(defaults, lastRead, text) {
|
|
59
|
+
const { version: _version, ...settings } = lastRead ?? {};
|
|
60
|
+
return { ...defaults, ...settings, ...salvageSettings(text), welcomeOnboardingEligible: false, pendingWelcomeOnboarding: false };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Backs up a corrupt config.json as config.json.corrupt.<ms>.<pid>, unless the
|
|
64
|
+
* newest backup already holds `bytes` (a recovery tried again after its write
|
|
65
|
+
* failed). Returns the backup's name, or null when there is no config.json.
|
|
66
|
+
*/
|
|
67
|
+
export async function backupCorruptConfig(configPath, bytes) {
|
|
68
|
+
if (!existsSync(configPath))
|
|
69
|
+
return null;
|
|
70
|
+
const folder = path.dirname(configPath);
|
|
71
|
+
const prefix = `${path.basename(configPath)}.corrupt.`;
|
|
72
|
+
const newest = (await fs.readdir(folder).catch(() => []))
|
|
73
|
+
.filter((name) => name.startsWith(prefix))
|
|
74
|
+
.sort((a, b) => parseInt(a.slice(prefix.length), 10) - parseInt(b.slice(prefix.length), 10))
|
|
75
|
+
.pop();
|
|
76
|
+
if (newest && (await fs.readFile(path.join(folder, newest)).catch(() => null))?.equals(bytes))
|
|
77
|
+
return newest;
|
|
78
|
+
const name = `${prefix}${Date.now()}.${process.pid}`;
|
|
79
|
+
await fs.copyFile(configPath, path.join(folder, name));
|
|
80
|
+
return name;
|
|
81
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
// Use user's home directory for configuration files
|
|
4
|
+
export const USER_HOME = os.homedir();
|
|
5
|
+
const CONFIG_DIR = path.join(USER_HOME, '.claude-server-commander');
|
|
6
|
+
// Paths relative to the config directory
|
|
7
|
+
export const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
8
|
+
export const TOOL_CALL_FILE = path.join(CONFIG_DIR, 'claude_tool_call.log');
|
|
9
|
+
export const TOOL_CALL_FILE_MAX_SIZE = 1024 * 1024 * 10; // 10 MB
|
|
10
|
+
export const DEFAULT_COMMAND_TIMEOUT = 1000; // milliseconds
|
|
11
|
+
// Longest one process call (start_process, interact_with_process,
|
|
12
|
+
// read_process_output) blocks, whatever timeout_ms asks for; the process keeps
|
|
13
|
+
// running and the caller reads the rest with read_process_output. It stays well
|
|
14
|
+
// under 60 s: that is the MCP SDK's default request timeout
|
|
15
|
+
// (DEFAULT_REQUEST_TIMEOUT_MSEC), which Claude Desktop and the remote device's
|
|
16
|
+
// own client use, and an answer capped at 60 s arrived just after the client had
|
|
17
|
+
// given up (-32001 "Request timed out", #447). The margin leaves time to build
|
|
18
|
+
// and deliver the answer.
|
|
19
|
+
export const MAX_PROCESS_WAIT_MS = 50000;
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
/**
|
|
4
|
+
* Enhanced StdioServerTransport that wraps console output in valid JSON-RPC structures
|
|
5
|
+
* instead of filtering them out. This prevents crashes while maintaining debug visibility.
|
|
6
|
+
*/
|
|
7
|
+
export class FilteredStdioServerTransport extends StdioServerTransport {
|
|
8
|
+
constructor() {
|
|
9
|
+
super();
|
|
10
|
+
this.isInitialized = false;
|
|
11
|
+
this.messageBuffer = [];
|
|
12
|
+
this.clientName = 'unknown';
|
|
13
|
+
this.disableNotifications = false;
|
|
14
|
+
// Store original methods
|
|
15
|
+
this.originalConsole = {
|
|
16
|
+
log: console.log,
|
|
17
|
+
warn: console.warn,
|
|
18
|
+
error: console.error,
|
|
19
|
+
debug: console.debug,
|
|
20
|
+
info: console.info,
|
|
21
|
+
};
|
|
22
|
+
this.originalStdoutWrite = process.stdout.write;
|
|
23
|
+
// Setup console redirection
|
|
24
|
+
this.setupConsoleRedirection();
|
|
25
|
+
// Setup stdout filtering for any other output
|
|
26
|
+
this.setupStdoutFiltering();
|
|
27
|
+
// Note: We defer the initialization notification until enableNotifications() is called
|
|
28
|
+
// to ensure MCP protocol compliance - notifications must not be sent before initialization
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Call this method after MCP initialization is complete to enable JSON-RPC notifications
|
|
32
|
+
*/
|
|
33
|
+
enableNotifications() {
|
|
34
|
+
this.isInitialized = true;
|
|
35
|
+
// Check if notifications should be disabled based on client
|
|
36
|
+
if (this.disableNotifications) {
|
|
37
|
+
// Clear buffer without sending - just log to stderr instead
|
|
38
|
+
if (this.messageBuffer.length > 0) {
|
|
39
|
+
process.stderr.write(`[INFO] ${this.messageBuffer.length} buffered messages suppressed for ${this.clientName}\n`);
|
|
40
|
+
}
|
|
41
|
+
this.messageBuffer = [];
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Send the deferred initialization notification first
|
|
45
|
+
this.sendLogNotification('info', ['Enhanced FilteredStdioServerTransport initialized']);
|
|
46
|
+
// Replay all buffered messages in chronological order
|
|
47
|
+
if (this.messageBuffer.length > 0) {
|
|
48
|
+
this.sendLogNotification('info', [`Replaying ${this.messageBuffer.length} buffered initialization messages`]);
|
|
49
|
+
this.messageBuffer
|
|
50
|
+
.sort((a, b) => a.timestamp - b.timestamp)
|
|
51
|
+
.forEach(msg => {
|
|
52
|
+
this.sendLogNotification(msg.level, msg.args);
|
|
53
|
+
});
|
|
54
|
+
// Clear the buffer
|
|
55
|
+
this.messageBuffer = [];
|
|
56
|
+
}
|
|
57
|
+
this.sendLogNotification('info', ['JSON-RPC notifications enabled']);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Configure client-specific behavior
|
|
61
|
+
* Call this BEFORE enableNotifications()
|
|
62
|
+
*/
|
|
63
|
+
configureForClient(clientName) {
|
|
64
|
+
this.clientName = clientName.toLowerCase();
|
|
65
|
+
// Detect Cline and disable notifications
|
|
66
|
+
if (this.clientName.includes('cline') ||
|
|
67
|
+
this.clientName.includes('vscode') ||
|
|
68
|
+
this.clientName === 'claude-dev') {
|
|
69
|
+
this.disableNotifications = true;
|
|
70
|
+
process.stderr.write(`[INFO] Desktop Commander: Notifications disabled for ${clientName}\n`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if notifications are enabled
|
|
75
|
+
*/
|
|
76
|
+
get isNotificationsEnabled() {
|
|
77
|
+
return this.isInitialized;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get the current count of buffered messages
|
|
81
|
+
*/
|
|
82
|
+
get bufferedMessageCount() {
|
|
83
|
+
return this.messageBuffer.length;
|
|
84
|
+
}
|
|
85
|
+
setupConsoleRedirection() {
|
|
86
|
+
console.log = (...args) => {
|
|
87
|
+
if (this.isInitialized) {
|
|
88
|
+
this.sendLogNotification("info", args);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Buffer for later replay to client
|
|
92
|
+
this.messageBuffer.push({
|
|
93
|
+
level: "info",
|
|
94
|
+
args,
|
|
95
|
+
timestamp: Date.now()
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
console.info = (...args) => {
|
|
100
|
+
if (this.isInitialized) {
|
|
101
|
+
this.sendLogNotification("info", args);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.messageBuffer.push({
|
|
105
|
+
level: "info",
|
|
106
|
+
args,
|
|
107
|
+
timestamp: Date.now()
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
console.warn = (...args) => {
|
|
112
|
+
if (this.isInitialized) {
|
|
113
|
+
this.sendLogNotification("warning", args);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.messageBuffer.push({
|
|
117
|
+
level: "warning",
|
|
118
|
+
args,
|
|
119
|
+
timestamp: Date.now()
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
console.error = (...args) => {
|
|
124
|
+
if (this.isInitialized) {
|
|
125
|
+
this.sendLogNotification("error", args);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
this.messageBuffer.push({
|
|
129
|
+
level: "error",
|
|
130
|
+
args,
|
|
131
|
+
timestamp: Date.now()
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
console.debug = (...args) => {
|
|
136
|
+
if (this.isInitialized) {
|
|
137
|
+
this.sendLogNotification("debug", args);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.messageBuffer.push({
|
|
141
|
+
level: "debug",
|
|
142
|
+
args,
|
|
143
|
+
timestamp: Date.now()
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
setupStdoutFiltering() {
|
|
149
|
+
process.stdout.write = (buffer, encoding, callback) => {
|
|
150
|
+
// Handle different call signatures
|
|
151
|
+
if (typeof buffer === 'string') {
|
|
152
|
+
const trimmed = buffer.trim();
|
|
153
|
+
// Check if this looks like a valid JSON-RPC message
|
|
154
|
+
if (trimmed.startsWith('{') && (trimmed.includes('"jsonrpc"') ||
|
|
155
|
+
trimmed.includes('"method"') ||
|
|
156
|
+
trimmed.includes('"id"'))) {
|
|
157
|
+
// This looks like a valid JSON-RPC message, allow it
|
|
158
|
+
return this.originalStdoutWrite.call(process.stdout, buffer, encoding, callback);
|
|
159
|
+
}
|
|
160
|
+
else if (trimmed.length > 0) {
|
|
161
|
+
// Non-JSON-RPC output, wrap it in a log notification
|
|
162
|
+
if (this.isInitialized) {
|
|
163
|
+
this.sendLogNotification("info", [buffer.replace(/\n$/, '')]);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// Buffer for later replay to client
|
|
167
|
+
this.messageBuffer.push({
|
|
168
|
+
level: "info",
|
|
169
|
+
args: [buffer.replace(/\n$/, '')],
|
|
170
|
+
timestamp: Date.now()
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (callback)
|
|
174
|
+
callback();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// For non-string buffers or empty strings, let them through
|
|
179
|
+
return this.originalStdoutWrite.call(process.stdout, buffer, encoding, callback);
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
sendLogNotification(level, args) {
|
|
183
|
+
// Skip if notifications are disabled (e.g., for Cline)
|
|
184
|
+
if (this.disableNotifications) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
// For data, we can send structured data or string according to MCP spec
|
|
189
|
+
let data;
|
|
190
|
+
if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null) {
|
|
191
|
+
// Single object - send as structured data
|
|
192
|
+
data = args[0];
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Multiple args or primitives - convert to string
|
|
196
|
+
data = args.map(arg => {
|
|
197
|
+
if (typeof arg === 'object') {
|
|
198
|
+
try {
|
|
199
|
+
return JSON.stringify(arg, null, 2);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return String(arg);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return String(arg);
|
|
206
|
+
}).join(' ');
|
|
207
|
+
}
|
|
208
|
+
const notification = {
|
|
209
|
+
jsonrpc: "2.0",
|
|
210
|
+
method: "notifications/message",
|
|
211
|
+
params: {
|
|
212
|
+
level: level,
|
|
213
|
+
logger: "desktop-commander",
|
|
214
|
+
data: data
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
// Send as valid JSON-RPC notification
|
|
218
|
+
this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n');
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
// Fallback to a simple JSON-RPC error notification if JSON serialization fails
|
|
222
|
+
const fallbackNotification = {
|
|
223
|
+
jsonrpc: "2.0",
|
|
224
|
+
method: "notifications/message",
|
|
225
|
+
params: {
|
|
226
|
+
level: "error",
|
|
227
|
+
logger: "desktop-commander",
|
|
228
|
+
data: `Log serialization failed: ${args.join(' ')}`
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Public method to send log notifications from anywhere in the application
|
|
236
|
+
* Now properly buffers messages before MCP initialization to avoid breaking stdio protocol
|
|
237
|
+
*/
|
|
238
|
+
sendLog(level, message, data) {
|
|
239
|
+
// Skip if notifications are disabled (e.g., for Cline)
|
|
240
|
+
if (this.disableNotifications) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// Buffer messages before initialization to avoid breaking MCP protocol
|
|
244
|
+
// MCP requires client to send first message - server cannot write to stdout before that
|
|
245
|
+
if (!this.isInitialized) {
|
|
246
|
+
this.messageBuffer.push({
|
|
247
|
+
level,
|
|
248
|
+
args: [data ? { message, ...data } : message],
|
|
249
|
+
timestamp: Date.now()
|
|
250
|
+
});
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const notification = {
|
|
255
|
+
jsonrpc: "2.0",
|
|
256
|
+
method: "notifications/message",
|
|
257
|
+
params: {
|
|
258
|
+
level: level,
|
|
259
|
+
logger: "desktop-commander",
|
|
260
|
+
data: data ? { message, ...data } : message
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n');
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
// Fallback to basic JSON-RPC notification
|
|
267
|
+
const fallbackNotification = {
|
|
268
|
+
jsonrpc: "2.0",
|
|
269
|
+
method: "notifications/message",
|
|
270
|
+
params: {
|
|
271
|
+
level: "error",
|
|
272
|
+
logger: "desktop-commander",
|
|
273
|
+
data: `sendLog failed: ${message}`
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Send a progress notification (useful for long-running operations)
|
|
281
|
+
*/
|
|
282
|
+
sendProgress(token, value, total) {
|
|
283
|
+
// Don't send progress before initialization - would break MCP protocol
|
|
284
|
+
if (!this.isInitialized) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
const notification = {
|
|
289
|
+
jsonrpc: "2.0",
|
|
290
|
+
method: "notifications/progress",
|
|
291
|
+
params: {
|
|
292
|
+
progressToken: token,
|
|
293
|
+
value: value,
|
|
294
|
+
...(total && { total })
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n');
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
// Fallback to basic JSON-RPC notification for progress
|
|
301
|
+
const fallbackNotification = {
|
|
302
|
+
jsonrpc: "2.0",
|
|
303
|
+
method: "notifications/message",
|
|
304
|
+
params: {
|
|
305
|
+
level: "info",
|
|
306
|
+
logger: "desktop-commander",
|
|
307
|
+
data: `Progress ${token}: ${value}${total ? `/${total}` : ''}`
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Send a custom notification with any method name
|
|
315
|
+
*/
|
|
316
|
+
sendCustomNotification(method, params) {
|
|
317
|
+
// Don't send custom notifications before initialization - would break MCP protocol
|
|
318
|
+
if (!this.isInitialized) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const notification = {
|
|
323
|
+
jsonrpc: "2.0",
|
|
324
|
+
method: method,
|
|
325
|
+
params: params
|
|
326
|
+
};
|
|
327
|
+
this.originalStdoutWrite.call(process.stdout, JSON.stringify(notification) + '\n');
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
// Fallback to basic JSON-RPC notification for custom notifications
|
|
331
|
+
const fallbackNotification = {
|
|
332
|
+
jsonrpc: "2.0",
|
|
333
|
+
method: "notifications/message",
|
|
334
|
+
params: {
|
|
335
|
+
level: "error",
|
|
336
|
+
logger: "desktop-commander",
|
|
337
|
+
data: `Custom notification failed: ${method}: ${JSON.stringify(params)}`
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
this.originalStdoutWrite.call(process.stdout, JSON.stringify(fallbackNotification) + '\n');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Cleanup method to restore original console methods if needed
|
|
345
|
+
*/
|
|
346
|
+
cleanup() {
|
|
347
|
+
if (this.originalConsole) {
|
|
348
|
+
console.log = this.originalConsole.log;
|
|
349
|
+
console.warn = this.originalConsole.warn;
|
|
350
|
+
console.error = this.originalConsole.error;
|
|
351
|
+
console.debug = this.originalConsole.debug;
|
|
352
|
+
console.info = this.originalConsole.info;
|
|
353
|
+
}
|
|
354
|
+
if (this.originalStdoutWrite) {
|
|
355
|
+
process.stdout.write = this.originalStdoutWrite;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -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
|
+
}
|