@wonderwhy-er/desktop-commander 0.2.2 → 0.2.4

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 (51) hide show
  1. package/README.md +27 -4
  2. package/dist/config-manager.d.ts +10 -0
  3. package/dist/config-manager.js +7 -1
  4. package/dist/handlers/edit-search-handlers.js +25 -6
  5. package/dist/handlers/filesystem-handlers.js +2 -4
  6. package/dist/handlers/terminal-handlers.d.ts +8 -4
  7. package/dist/handlers/terminal-handlers.js +16 -10
  8. package/dist/index-dxt.d.ts +2 -0
  9. package/dist/index-dxt.js +76 -0
  10. package/dist/index-with-startup-detection.d.ts +5 -0
  11. package/dist/index-with-startup-detection.js +180 -0
  12. package/dist/server.d.ts +5 -0
  13. package/dist/server.js +381 -65
  14. package/dist/terminal-manager.d.ts +7 -0
  15. package/dist/terminal-manager.js +93 -18
  16. package/dist/tools/client.d.ts +10 -0
  17. package/dist/tools/client.js +13 -0
  18. package/dist/tools/config.d.ts +1 -1
  19. package/dist/tools/config.js +21 -3
  20. package/dist/tools/edit.js +4 -3
  21. package/dist/tools/environment.d.ts +55 -0
  22. package/dist/tools/environment.js +65 -0
  23. package/dist/tools/feedback.d.ts +8 -0
  24. package/dist/tools/feedback.js +132 -0
  25. package/dist/tools/filesystem.d.ts +10 -0
  26. package/dist/tools/filesystem.js +410 -60
  27. package/dist/tools/improved-process-tools.d.ts +24 -0
  28. package/dist/tools/improved-process-tools.js +453 -0
  29. package/dist/tools/schemas.d.ts +20 -2
  30. package/dist/tools/schemas.js +20 -3
  31. package/dist/tools/usage.d.ts +5 -0
  32. package/dist/tools/usage.js +24 -0
  33. package/dist/utils/capture.d.ts +2 -0
  34. package/dist/utils/capture.js +40 -9
  35. package/dist/utils/early-logger.d.ts +4 -0
  36. package/dist/utils/early-logger.js +35 -0
  37. package/dist/utils/mcp-logger.d.ts +30 -0
  38. package/dist/utils/mcp-logger.js +59 -0
  39. package/dist/utils/process-detection.d.ts +23 -0
  40. package/dist/utils/process-detection.js +150 -0
  41. package/dist/utils/smithery-detector.d.ts +94 -0
  42. package/dist/utils/smithery-detector.js +292 -0
  43. package/dist/utils/startup-detector.d.ts +65 -0
  44. package/dist/utils/startup-detector.js +390 -0
  45. package/dist/utils/system-info.d.ts +30 -0
  46. package/dist/utils/system-info.js +146 -0
  47. package/dist/utils/usageTracker.d.ts +85 -0
  48. package/dist/utils/usageTracker.js +280 -0
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. package/package.json +4 -1
@@ -0,0 +1,35 @@
1
+ // Early logger that can work before FilteredStdioServerTransport is ready
2
+ let transportInstance = null;
3
+ export function setTransportInstance(transport) {
4
+ transportInstance = transport;
5
+ }
6
+ export function logError(message, data) {
7
+ if (transportInstance && transportInstance.sendLog) {
8
+ // Use transport's structured logging
9
+ transportInstance.sendLog("error", message, data);
10
+ }
11
+ else {
12
+ // Fallback to console.error which will be intercepted later
13
+ console.error(message);
14
+ }
15
+ }
16
+ export function logInfo(message, data) {
17
+ if (transportInstance && transportInstance.sendLog) {
18
+ // Use transport's structured logging
19
+ transportInstance.sendLog("info", message, data);
20
+ }
21
+ else {
22
+ // Fallback to console.log which will be intercepted later
23
+ console.log(message);
24
+ }
25
+ }
26
+ export function logWarning(message, data) {
27
+ if (transportInstance && transportInstance.sendLog) {
28
+ // Use transport's structured logging
29
+ transportInstance.sendLog("warning", message, data);
30
+ }
31
+ else {
32
+ // Fallback to console.warn which will be intercepted later
33
+ console.warn(message);
34
+ }
35
+ }
@@ -0,0 +1,30 @@
1
+ import '../types.js';
2
+ export type LogLevel = "emergency" | "alert" | "critical" | "error" | "warning" | "notice" | "info" | "debug";
3
+ /**
4
+ * MCP-compatible logger that sends log messages via JSON-RPC notifications
5
+ * Falls back to stderr if transport is not available
6
+ */
7
+ export declare class McpLogger {
8
+ private component;
9
+ constructor(component?: string);
10
+ private logMessage;
11
+ emergency(message: string, data?: any): void;
12
+ alert(message: string, data?: any): void;
13
+ critical(message: string, data?: any): void;
14
+ error(message: string, data?: any): void;
15
+ warning(message: string, data?: any): void;
16
+ notice(message: string, data?: any): void;
17
+ info(message: string, data?: any): void;
18
+ debug(message: string, data?: any): void;
19
+ warn(message: string, data?: any): void;
20
+ logInfo(message: string, data?: any): void;
21
+ toolCall(toolName: string, args: any, result?: any, error?: Error): void;
22
+ }
23
+ export declare const logger: McpLogger;
24
+ export declare const mcpConsole: {
25
+ log: (message: string, data?: any) => void;
26
+ info: (message: string, data?: any) => void;
27
+ warn: (message: string, data?: any) => void;
28
+ error: (message: string, data?: any) => void;
29
+ debug: (message: string, data?: any) => void;
30
+ };
@@ -0,0 +1,59 @@
1
+ // src/utils/mcp-logger.ts
2
+ import '../types.js'; // Import for global types
3
+ /**
4
+ * MCP-compatible logger that sends log messages via JSON-RPC notifications
5
+ * Falls back to stderr if transport is not available
6
+ */
7
+ export class McpLogger {
8
+ constructor(component = 'desktop-commander') {
9
+ this.component = component;
10
+ }
11
+ logMessage(level, message, data) {
12
+ if (global.mcpTransport?.sendLog) {
13
+ // Send via MCP JSON-RPC notification
14
+ global.mcpTransport.sendLog(level, `[${this.component}] ${message}`, data);
15
+ }
16
+ else {
17
+ // Fallback to stderr
18
+ const timestamp = new Date().toISOString();
19
+ const dataStr = data ? ` - ${JSON.stringify(data)}` : '';
20
+ process.stderr.write(`${timestamp} [${level.toUpperCase()}] [${this.component}] ${message}${dataStr}\n`);
21
+ }
22
+ }
23
+ emergency(message, data) { this.logMessage('emergency', message, data); }
24
+ alert(message, data) { this.logMessage('alert', message, data); }
25
+ critical(message, data) { this.logMessage('critical', message, data); }
26
+ error(message, data) { this.logMessage('error', message, data); }
27
+ warning(message, data) { this.logMessage('warning', message, data); }
28
+ notice(message, data) { this.logMessage('notice', message, data); }
29
+ info(message, data) { this.logMessage('info', message, data); }
30
+ debug(message, data) { this.logMessage('debug', message, data); }
31
+ // Convenience methods
32
+ warn(message, data) { this.warning(message, data); }
33
+ logInfo(message, data) { this.info(message, data); }
34
+ // Tool execution logging
35
+ toolCall(toolName, args, result, error) {
36
+ const logData = {
37
+ tool: toolName,
38
+ args,
39
+ ...(result && { result }),
40
+ ...(error && { error: error.message, stack: error.stack })
41
+ };
42
+ if (error) {
43
+ this.error(`Tool ${toolName} failed`, logData);
44
+ }
45
+ else {
46
+ this.debug(`Tool ${toolName} executed`, logData);
47
+ }
48
+ }
49
+ }
50
+ // Create a global logger instance
51
+ export const logger = new McpLogger('desktop-commander');
52
+ // Create convenience functions that mimic console methods
53
+ export const mcpConsole = {
54
+ log: (message, data) => logger.info(message, data),
55
+ info: (message, data) => logger.info(message, data),
56
+ warn: (message, data) => logger.warning(message, data),
57
+ error: (message, data) => logger.error(message, data),
58
+ debug: (message, data) => logger.debug(message, data),
59
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * REPL and Process State Detection Utilities
3
+ * Detects when processes are waiting for input vs finished vs running
4
+ */
5
+ export interface ProcessState {
6
+ isWaitingForInput: boolean;
7
+ isFinished: boolean;
8
+ isRunning: boolean;
9
+ detectedPrompt?: string;
10
+ lastOutput: string;
11
+ }
12
+ /**
13
+ * Analyze process output to determine current state
14
+ */
15
+ export declare function analyzeProcessState(output: string, pid?: number): ProcessState;
16
+ /**
17
+ * Clean output by removing prompts and input echoes
18
+ */
19
+ export declare function cleanProcessOutput(output: string, inputSent?: string): string;
20
+ /**
21
+ * Format process state for user display
22
+ */
23
+ export declare function formatProcessStateMessage(state: ProcessState, pid: number): string;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * REPL and Process State Detection Utilities
3
+ * Detects when processes are waiting for input vs finished vs running
4
+ */
5
+ // Common REPL prompt patterns
6
+ const REPL_PROMPTS = {
7
+ python: ['>>> ', '... '],
8
+ node: ['> ', '... '],
9
+ r: ['> ', '+ '],
10
+ julia: ['julia> ', ' '], // julia continuation is spaces
11
+ shell: ['$ ', '# ', '% ', 'bash-', 'zsh-'],
12
+ mysql: ['mysql> ', ' -> '],
13
+ postgres: ['=# ', '-# '],
14
+ redis: ['redis> '],
15
+ mongo: ['> ', '... ']
16
+ };
17
+ // Error patterns that indicate completion (even with errors)
18
+ const ERROR_COMPLETION_PATTERNS = [
19
+ /Error:/i,
20
+ /Exception:/i,
21
+ /Traceback/i,
22
+ /SyntaxError/i,
23
+ /NameError/i,
24
+ /TypeError/i,
25
+ /ValueError/i,
26
+ /ReferenceError/i,
27
+ /Uncaught/i,
28
+ /at Object\./i, // Node.js stack traces
29
+ /^\s*\^/m // Syntax error indicators
30
+ ];
31
+ // Process completion indicators
32
+ const COMPLETION_INDICATORS = [
33
+ /Process finished/i,
34
+ /Command completed/i,
35
+ /\[Process completed\]/i,
36
+ /Program terminated/i,
37
+ /Exit code:/i
38
+ ];
39
+ /**
40
+ * Analyze process output to determine current state
41
+ */
42
+ export function analyzeProcessState(output, pid) {
43
+ if (!output || output.trim().length === 0) {
44
+ return {
45
+ isWaitingForInput: false,
46
+ isFinished: false,
47
+ isRunning: true,
48
+ lastOutput: output
49
+ };
50
+ }
51
+ const lines = output.split('\n');
52
+ const lastLine = lines[lines.length - 1] || '';
53
+ const lastFewLines = lines.slice(-3).join('\n');
54
+ // Check for REPL prompts (waiting for input)
55
+ const allPrompts = Object.values(REPL_PROMPTS).flat();
56
+ const detectedPrompt = allPrompts.find(prompt => lastLine.endsWith(prompt) || lastLine.includes(prompt));
57
+ if (detectedPrompt) {
58
+ return {
59
+ isWaitingForInput: true,
60
+ isFinished: false,
61
+ isRunning: true,
62
+ detectedPrompt,
63
+ lastOutput: output
64
+ };
65
+ }
66
+ // Check for completion indicators
67
+ const hasCompletionIndicator = COMPLETION_INDICATORS.some(pattern => pattern.test(output));
68
+ if (hasCompletionIndicator) {
69
+ return {
70
+ isWaitingForInput: false,
71
+ isFinished: true,
72
+ isRunning: false,
73
+ lastOutput: output
74
+ };
75
+ }
76
+ // Check for error completion (errors usually end with prompts, but let's be thorough)
77
+ const hasErrorCompletion = ERROR_COMPLETION_PATTERNS.some(pattern => pattern.test(lastFewLines));
78
+ if (hasErrorCompletion) {
79
+ // Errors can indicate completion, but check if followed by prompt
80
+ if (detectedPrompt) {
81
+ return {
82
+ isWaitingForInput: true,
83
+ isFinished: false,
84
+ isRunning: true,
85
+ detectedPrompt,
86
+ lastOutput: output
87
+ };
88
+ }
89
+ else {
90
+ return {
91
+ isWaitingForInput: false,
92
+ isFinished: true,
93
+ isRunning: false,
94
+ lastOutput: output
95
+ };
96
+ }
97
+ }
98
+ // Default: process is running, not clearly waiting or finished
99
+ return {
100
+ isWaitingForInput: false,
101
+ isFinished: false,
102
+ isRunning: true,
103
+ lastOutput: output
104
+ };
105
+ }
106
+ /**
107
+ * Clean output by removing prompts and input echoes
108
+ */
109
+ export function cleanProcessOutput(output, inputSent) {
110
+ let cleaned = output;
111
+ // Remove input echo if provided
112
+ if (inputSent) {
113
+ const inputLines = inputSent.split('\n');
114
+ inputLines.forEach(line => {
115
+ if (line.trim()) {
116
+ cleaned = cleaned.replace(new RegExp(`^${escapeRegExp(line.trim())}\\s*\n?`, 'm'), '');
117
+ }
118
+ });
119
+ }
120
+ // Remove common prompt patterns from output
121
+ cleaned = cleaned.replace(/^>>>\s*/gm, ''); // Python >>>
122
+ cleaned = cleaned.replace(/^>\s*/gm, ''); // Node.js/Shell >
123
+ cleaned = cleaned.replace(/^\.{3}\s*/gm, ''); // Python ...
124
+ cleaned = cleaned.replace(/^\+\s*/gm, ''); // R +
125
+ // Remove trailing prompts
126
+ cleaned = cleaned.replace(/\n>>>\s*$/, '');
127
+ cleaned = cleaned.replace(/\n>\s*$/, '');
128
+ cleaned = cleaned.replace(/\n\+\s*$/, '');
129
+ return cleaned.trim();
130
+ }
131
+ /**
132
+ * Escape special regex characters
133
+ */
134
+ function escapeRegExp(string) {
135
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
136
+ }
137
+ /**
138
+ * Format process state for user display
139
+ */
140
+ export function formatProcessStateMessage(state, pid) {
141
+ if (state.isWaitingForInput) {
142
+ return `Process ${pid} is waiting for input${state.detectedPrompt ? ` (detected: "${state.detectedPrompt.trim()}")` : ''}`;
143
+ }
144
+ else if (state.isFinished) {
145
+ return `Process ${pid} has finished execution`;
146
+ }
147
+ else {
148
+ return `Process ${pid} is running`;
149
+ }
150
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Smithery CLI Detection Module
3
+ *
4
+ * Detects when a Node.js MCP server is being run through Smithery CLI
5
+ * which acts as a proxy/wrapper around MCP servers.
6
+ */
7
+ export interface SmitheryDetectionInfo {
8
+ isSmithery: boolean;
9
+ confidence: number;
10
+ evidence: string[];
11
+ details: {
12
+ sessionId?: string;
13
+ analyticsEnabled?: boolean;
14
+ clientType?: string;
15
+ connectionType?: 'stdio' | 'http' | 'streamable-http';
16
+ qualifiedName?: string;
17
+ profile?: string;
18
+ runtimeEnvironment?: string;
19
+ };
20
+ }
21
+ export declare class SmitheryDetector {
22
+ private static instance;
23
+ private _detectionInfo;
24
+ private constructor();
25
+ static getInstance(): SmitheryDetector;
26
+ /**
27
+ * Get Smithery detection information (cached after first call)
28
+ */
29
+ getSmitheryInfo(): SmitheryDetectionInfo;
30
+ /**
31
+ * Force re-detection (useful for testing)
32
+ */
33
+ forceRedetect(): SmitheryDetectionInfo;
34
+ private detectSmithery;
35
+ /**
36
+ * Check for Smithery-specific environment variables
37
+ */
38
+ private checkSmitheryEnvironmentVars;
39
+ /**
40
+ * Check process arguments for Smithery patterns
41
+ */
42
+ private checkProcessArguments;
43
+ /**
44
+ * Check parent process for Smithery CLI
45
+ */
46
+ private checkParentProcess;
47
+ /**
48
+ * Check for Smithery runtime environment patterns
49
+ */
50
+ private checkRuntimeEnvironment;
51
+ /**
52
+ * Check for analytics and session indicators
53
+ */
54
+ private checkAnalyticsIndicators;
55
+ /**
56
+ * Check for MCP transport patterns that indicate Smithery
57
+ */
58
+ private checkTransportPatterns;
59
+ /**
60
+ * Check stdio patterns that suggest Smithery proxying
61
+ */
62
+ private checkStdioPatterns;
63
+ /**
64
+ * Check if running through Smithery (convenience method)
65
+ */
66
+ isSmithery(): boolean;
67
+ /**
68
+ * Get the client type if running through Smithery
69
+ */
70
+ getClientType(): string | undefined;
71
+ /**
72
+ * Get the connection type if running through Smithery
73
+ */
74
+ getConnectionType(): string | undefined;
75
+ /**
76
+ * Get analytics status if running through Smithery
77
+ */
78
+ isAnalyticsEnabled(): boolean | undefined;
79
+ /**
80
+ * Get session ID if running through Smithery
81
+ */
82
+ getSessionId(): string | undefined;
83
+ /**
84
+ * Get server qualified name if running through Smithery
85
+ */
86
+ getQualifiedName(): string | undefined;
87
+ }
88
+ export declare const smitheryDetector: SmitheryDetector;
89
+ export declare const getSmitheryInfo: () => SmitheryDetectionInfo;
90
+ export declare const isSmithery: () => boolean;
91
+ export declare const getSmitheryClientType: () => string | undefined;
92
+ export declare const getSmitheryConnectionType: () => string | undefined;
93
+ export declare const getSmitherySessionId: () => string | undefined;
94
+ export declare const isSmitheryAnalyticsEnabled: () => boolean | undefined;
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Smithery CLI Detection Module
3
+ *
4
+ * Detects when a Node.js MCP server is being run through Smithery CLI
5
+ * which acts as a proxy/wrapper around MCP servers.
6
+ */
7
+ import { platform } from 'os';
8
+ export class SmitheryDetector {
9
+ constructor() {
10
+ this._detectionInfo = null;
11
+ }
12
+ static getInstance() {
13
+ if (!SmitheryDetector.instance) {
14
+ SmitheryDetector.instance = new SmitheryDetector();
15
+ }
16
+ return SmitheryDetector.instance;
17
+ }
18
+ /**
19
+ * Get Smithery detection information (cached after first call)
20
+ */
21
+ getSmitheryInfo() {
22
+ if (!this._detectionInfo) {
23
+ this._detectionInfo = this.detectSmithery();
24
+ }
25
+ return this._detectionInfo;
26
+ }
27
+ /**
28
+ * Force re-detection (useful for testing)
29
+ */
30
+ forceRedetect() {
31
+ this._detectionInfo = this.detectSmithery();
32
+ return this._detectionInfo;
33
+ }
34
+ detectSmithery() {
35
+ const result = {
36
+ isSmithery: false,
37
+ confidence: 0,
38
+ evidence: [],
39
+ details: {}
40
+ };
41
+ // Method 1: Check for Smithery-specific environment variables
42
+ this.checkSmitheryEnvironmentVars(result);
43
+ // Method 2: Check process arguments for Smithery patterns
44
+ this.checkProcessArguments(result);
45
+ // Method 3: Check parent process for Smithery CLI
46
+ this.checkParentProcess(result);
47
+ // Method 4: Check for Smithery runtime environment patterns
48
+ this.checkRuntimeEnvironment(result);
49
+ // Method 5: Check for analytics/session indicators
50
+ this.checkAnalyticsIndicators(result);
51
+ // Method 6: Check for MCP SDK transport patterns
52
+ this.checkTransportPatterns(result);
53
+ // Method 7: Check stdio patterns
54
+ this.checkStdioPatterns(result);
55
+ // Set final determination
56
+ result.isSmithery = result.confidence >= 30;
57
+ return result;
58
+ }
59
+ /**
60
+ * Check for Smithery-specific environment variables
61
+ */
62
+ checkSmitheryEnvironmentVars(result) {
63
+ const smitheryEnvVars = [
64
+ 'SMITHERY_SESSION_ID',
65
+ 'SMITHERY_API_KEY',
66
+ 'SMITHERY_CLIENT',
67
+ 'SMITHERY_PROFILE',
68
+ 'SMITHERY_ANALYTICS',
69
+ 'SMITHERY_RUNTIME',
70
+ 'REGISTRY_ENDPOINT',
71
+ 'ANALYTICS_ENDPOINT'
72
+ ];
73
+ for (const envVar of smitheryEnvVars) {
74
+ if (process.env[envVar]) {
75
+ result.confidence += 40;
76
+ result.evidence.push(`Smithery environment variable: ${envVar}`);
77
+ // Extract specific details
78
+ switch (envVar) {
79
+ case 'SMITHERY_SESSION_ID':
80
+ result.details.sessionId = process.env[envVar];
81
+ break;
82
+ case 'SMITHERY_CLIENT':
83
+ result.details.clientType = process.env[envVar];
84
+ break;
85
+ case 'SMITHERY_PROFILE':
86
+ result.details.profile = process.env[envVar];
87
+ break;
88
+ case 'SMITHERY_ANALYTICS':
89
+ result.details.analyticsEnabled = process.env[envVar] === 'true';
90
+ break;
91
+ case 'SMITHERY_RUNTIME':
92
+ result.details.runtimeEnvironment = process.env[envVar];
93
+ break;
94
+ }
95
+ }
96
+ }
97
+ // Check for Smithery endpoints
98
+ if (process.env.REGISTRY_ENDPOINT?.includes('smithery')) {
99
+ result.confidence += 30;
100
+ result.evidence.push('Smithery registry endpoint detected');
101
+ }
102
+ if (process.env.ANALYTICS_ENDPOINT?.includes('smithery')) {
103
+ result.confidence += 25;
104
+ result.evidence.push('Smithery analytics endpoint detected');
105
+ }
106
+ }
107
+ /**
108
+ * Check process arguments for Smithery patterns
109
+ */
110
+ checkProcessArguments(result) {
111
+ const args = process.argv.join(' ');
112
+ // Check for common Smithery CLI patterns
113
+ const smitheryPatterns = [
114
+ /smithery[\s\/\\]cli/i,
115
+ /@smithery[\s\/\\]cli/i,
116
+ /npx.*@smithery/i,
117
+ /smithery.*run/i,
118
+ /smithery.*install/i
119
+ ];
120
+ for (const pattern of smitheryPatterns) {
121
+ if (pattern.test(args)) {
122
+ result.confidence += 35;
123
+ result.evidence.push(`Smithery CLI pattern in arguments: ${pattern.source}`);
124
+ }
125
+ }
126
+ // Check for typical Smithery command structure
127
+ if (args.includes('--config') && args.includes('--client')) {
128
+ result.confidence += 20;
129
+ result.evidence.push('Smithery-style CLI arguments detected');
130
+ }
131
+ }
132
+ /**
133
+ * Check parent process for Smithery CLI
134
+ */
135
+ checkParentProcess(result) {
136
+ try {
137
+ if (platform() === 'darwin' || platform() === 'linux') {
138
+ const { execSync } = require('child_process');
139
+ const ppid = process.ppid;
140
+ if (ppid) {
141
+ // Get parent process command line
142
+ const parentCmd = execSync(`ps -p ${ppid} -o command=`, {
143
+ encoding: 'utf8',
144
+ timeout: 1000
145
+ }).trim();
146
+ if (parentCmd.includes('smithery') || parentCmd.includes('@smithery/cli')) {
147
+ result.confidence += 50;
148
+ result.evidence.push(`Parent process is Smithery CLI: ${parentCmd}`);
149
+ }
150
+ if (parentCmd.includes('node') && parentCmd.includes('npx')) {
151
+ result.confidence += 15;
152
+ result.evidence.push('Parent process shows npx execution pattern');
153
+ }
154
+ }
155
+ }
156
+ }
157
+ catch (error) {
158
+ // Ignore errors, parent process detection is best-effort
159
+ }
160
+ }
161
+ /**
162
+ * Check for Smithery runtime environment patterns
163
+ */
164
+ checkRuntimeEnvironment(result) {
165
+ // Check working directory for Smithery artifacts
166
+ const cwd = process.cwd();
167
+ if (cwd.includes('.smithery') || cwd.includes('smithery-cli')) {
168
+ result.confidence += 25;
169
+ result.evidence.push('Working directory indicates Smithery environment');
170
+ }
171
+ // Check for typical Smithery PATH modifications
172
+ const path = process.env.PATH || '';
173
+ if (path.includes('smithery') || path.includes('.smithery')) {
174
+ result.confidence += 20;
175
+ result.evidence.push('PATH contains Smithery directories');
176
+ }
177
+ // Check for Smithery-injected NODE_OPTIONS or similar
178
+ if (process.env.NODE_OPTIONS?.includes('smithery')) {
179
+ result.confidence += 30;
180
+ result.evidence.push('NODE_OPTIONS indicates Smithery runtime');
181
+ }
182
+ }
183
+ /**
184
+ * Check for analytics and session indicators
185
+ */
186
+ checkAnalyticsIndicators(result) {
187
+ // Check for UUID v7 pattern in environment (Smithery uses uuidv7 for sessions)
188
+ const envVars = Object.keys(process.env);
189
+ for (const key of envVars) {
190
+ const value = process.env[key] || '';
191
+ // UUID v7 pattern: 8-4-4-4-12 hex characters starting with timestamp
192
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) {
193
+ result.confidence += 25;
194
+ result.evidence.push(`UUID v7 session ID pattern detected: ${key}`);
195
+ result.details.sessionId = value;
196
+ }
197
+ }
198
+ // Check for Smithery analytics patterns
199
+ if (process.env.SMITHERY_ANALYTICS_CONSENT) {
200
+ result.confidence += 20;
201
+ result.evidence.push('Smithery analytics consent setting detected');
202
+ }
203
+ }
204
+ /**
205
+ * Check for MCP transport patterns that indicate Smithery
206
+ */
207
+ checkTransportPatterns(result) {
208
+ // Check for environment variables that suggest MCP transport through Smithery
209
+ if (process.env.MCP_TRANSPORT_TYPE) {
210
+ result.confidence += 15;
211
+ result.evidence.push('MCP transport type specification detected');
212
+ result.details.connectionType = process.env.MCP_TRANSPORT_TYPE;
213
+ }
214
+ // Check for Smithery's specific transport configurations
215
+ if (process.env.SMITHERY_CONNECTION_TYPE) {
216
+ result.confidence += 35;
217
+ result.evidence.push('Smithery connection type detected');
218
+ result.details.connectionType = process.env.SMITHERY_CONNECTION_TYPE;
219
+ }
220
+ // Check for server qualification patterns
221
+ if (process.env.SMITHERY_QUALIFIED_NAME || process.env.MCP_SERVER_NAME) {
222
+ result.confidence += 30;
223
+ result.evidence.push('MCP server qualification detected');
224
+ result.details.qualifiedName = process.env.SMITHERY_QUALIFIED_NAME || process.env.MCP_SERVER_NAME;
225
+ }
226
+ }
227
+ /**
228
+ * Check stdio patterns that suggest Smithery proxying
229
+ */
230
+ checkStdioPatterns(result) {
231
+ // Check if stdin/stdout are being piped in a way consistent with Smithery
232
+ if (!process.stdin.isTTY && !process.stdout.isTTY) {
233
+ result.confidence += 10;
234
+ result.evidence.push('Non-TTY stdio suggests proxied execution');
235
+ }
236
+ // Check for JSON-RPC message patterns in environment
237
+ if (process.env.MCP_JSONRPC_VERSION) {
238
+ result.confidence += 15;
239
+ result.evidence.push('JSON-RPC version specification detected');
240
+ }
241
+ // Check for Smithery's specific stdio handling
242
+ if (process.env.SMITHERY_STDIO_BUFFER_SIZE || process.env.SMITHERY_MESSAGE_TIMEOUT) {
243
+ result.confidence += 25;
244
+ result.evidence.push('Smithery stdio configuration detected');
245
+ }
246
+ }
247
+ /**
248
+ * Check if running through Smithery (convenience method)
249
+ */
250
+ isSmithery() {
251
+ return this.getSmitheryInfo().isSmithery;
252
+ }
253
+ /**
254
+ * Get the client type if running through Smithery
255
+ */
256
+ getClientType() {
257
+ return this.getSmitheryInfo().details.clientType;
258
+ }
259
+ /**
260
+ * Get the connection type if running through Smithery
261
+ */
262
+ getConnectionType() {
263
+ return this.getSmitheryInfo().details.connectionType;
264
+ }
265
+ /**
266
+ * Get analytics status if running through Smithery
267
+ */
268
+ isAnalyticsEnabled() {
269
+ return this.getSmitheryInfo().details.analyticsEnabled;
270
+ }
271
+ /**
272
+ * Get session ID if running through Smithery
273
+ */
274
+ getSessionId() {
275
+ return this.getSmitheryInfo().details.sessionId;
276
+ }
277
+ /**
278
+ * Get server qualified name if running through Smithery
279
+ */
280
+ getQualifiedName() {
281
+ return this.getSmitheryInfo().details.qualifiedName;
282
+ }
283
+ }
284
+ // Singleton instance
285
+ export const smitheryDetector = SmitheryDetector.getInstance();
286
+ // Convenience functions
287
+ export const getSmitheryInfo = () => smitheryDetector.getSmitheryInfo();
288
+ export const isSmithery = () => smitheryDetector.isSmithery();
289
+ export const getSmitheryClientType = () => smitheryDetector.getClientType();
290
+ export const getSmitheryConnectionType = () => smitheryDetector.getConnectionType();
291
+ export const getSmitherySessionId = () => smitheryDetector.getSessionId();
292
+ export const isSmitheryAnalyticsEnabled = () => smitheryDetector.isAnalyticsEnabled();