Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine

ENV MCP_CLIENT_DOCKER=true

# Create app directory
WORKDIR /usr/src/app

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"setup": "npm install && npm run build && node setup-claude-server.js",
"setup:debug": "npm install && npm run build && node setup-claude-server.js --debug",
"prepare": "npm run build",
"clean": "shx rm -rf dist",
"test": "node test/run-all-tests.js",
"test:debug": "node --inspect test/run-all-tests.js",
"link:local": "npm run build && npm link",
Expand Down
2 changes: 1 addition & 1 deletion src/config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class ConfigManager {
"cipher", // Encrypt/decrypt files or wipe data
"takeown" // Take ownership of files
],
defaultShell: os.platform() === 'win32' ? 'powershell.exe' : 'bash',
defaultShell: os.platform() === 'win32' ? 'powershell.exe' : '/bin/sh',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify impact of shell change on existing users.

Changing the default shell from bash to /bin/sh for non-Windows platforms could be a breaking change for users who rely on bash-specific features. While /bin/sh is more universally available, it has fewer features than bash.

Please verify:

  1. That existing user workflows won't break with this change
  2. Consider documenting this change in release notes
  3. Ensure the shell selection logic in other parts of the codebase (mentioned in AI summary) properly handles this change

🏁 Script executed:

#!/bin/bash
# Search for any bash-specific usage that might be affected
echo "Searching for potential bash-specific features in the codebase..."
rg -i "bash|#!/bin/bash" --type js --type ts -A 3 -B 1

Length of output: 7093


Document the change to /bin/sh as the new default shell

We’ve confirmed that the default in config-manager.ts is now /bin/sh on Unix, and test/test-default-shell.js has been updated to expect /bin/sh as the first‐choice shell. A ripgrep search for “bash”/“#!/bin/bash” across JS/TS files shows no remaining runtime dependencies on Bash-only features.

Please add the following before merging:

• Changelog/release notes entry: “Default shell on macOS/Linux changed from bash to /bin/sh.”
• Update user documentation:
– README (or any user-facing docs) section describing default shell.
– Code comments in src/server.ts that list “start_process('bash')”.
– Any mention in src/utils/system-info.ts of defaultShell = 'bash'.

🤖 Prompt for AI Agents
In src/config-manager.ts at line 125, the default shell was changed to '/bin/sh'
for Unix systems. To complete this update, add a changelog or release notes
entry stating "Default shell on macOS/Linux changed from `bash` to `/bin/sh`."
Update all user-facing documentation such as the README to reflect this new
default shell. Also, revise code comments in src/server.ts that mention
"start_process('bash')" and update any references to defaultShell = 'bash' in
src/utils/system-info.ts to align with the new default shell setting.

allowedDirectories: [],
telemetryEnabled: true, // Default to opt-out approach (telemetry on by default)
fileWriteLineLimit: 50, // Default line limit for file write operations (changed from 100)
Expand Down
12 changes: 12 additions & 0 deletions src/tools/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ export async function getConfig() {

// Add system information to the config response
const systemInfo = getSystemInfo();

// Determine host client
let hostClientIdentifier: string | undefined = 'unknown';
if (process.env.CURSOR_TRACE_ID) {
hostClientIdentifier = 'cursor';
} else if (process.env.CLAUDE_MCP_TOKEN) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain how its working? i tried it on macos, and CLAUDE_MCP_TOKEN is empty and i have unknown client most of the time. Where did you found this variable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also using macos and I didn't do anything special. I found they were adding that Env variable to my tool call. What version of Claude Desktop are you using? How do you have DC installed? Maybe that influences things?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i did npm run setup on this repository, also built conteiner from this repo.
in docker i saw "docker" as a client.
Claude desktop: Claude 0.11.6 (0aa9ce) 2025-06-29T21:59:05.000Z

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will make more sense to put docker as a separate flag: is_docker or something like this, this is more about env, than client

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@waroca what do you think if we remove this identification, and keep only is it from docker or not?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that that makes sense so to not confuse the underlying client vs the environment its running in. Since we also now have a better way to detect clients, how about we don't merge this at all and I create a separate one with the Dockerfile update and the new flag?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good

hostClientIdentifier = 'claude';
} else if (process.env.MCP_CLIENT_DOCKER === 'true') {
hostClientIdentifier = 'docker';
}

const configWithSystemInfo = {
...config,
systemInfo: {
Expand All @@ -22,6 +33,7 @@ export async function getConfig() {
isWindows: systemInfo.isWindows,
isMacOS: systemInfo.isMacOS,
isLinux: systemInfo.isLinux,
hostClient: hostClientIdentifier,
examplePaths: systemInfo.examplePaths
}
};
Expand Down
26 changes: 21 additions & 5 deletions src/tools/improved-process-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { capture } from "../utils/capture.js";
import { ServerResult } from '../types.js';
import { analyzeProcessState, cleanProcessOutput, formatProcessStateMessage, ProcessState } from '../utils/process-detection.js';
import { getSystemInfo } from '../utils/system-info.js';
import * as os from 'os';
import { configManager } from '../config-manager.js';

/**
* Start a new process (renamed from execute_command)
Expand Down Expand Up @@ -40,10 +42,28 @@ export async function startProcess(args: unknown): Promise<ServerResult> {
};
}

let shellUsed: string | undefined = parsed.data.shell;

if (!shellUsed) {
const config = await configManager.getConfig();
if (config.defaultShell) {
shellUsed = config.defaultShell;
} else {
const isWindows = os.platform() === 'win32';
if (isWindows && process.env.COMSPEC) {
shellUsed = process.env.COMSPEC;
} else if (!isWindows && process.env.SHELL) {
shellUsed = process.env.SHELL;
} else {
shellUsed = isWindows ? 'cmd.exe' : '/bin/sh';
}
}
}

const result = await terminalManager.executeCommand(
parsed.data.command,
parsed.data.timeout_ms,
parsed.data.shell
shellUsed
);

if (result.pid === -1) {
Expand All @@ -56,10 +76,6 @@ export async function startProcess(args: unknown): Promise<ServerResult> {
// Analyze the process state to detect if it's waiting for input
const processState = analyzeProcessState(result.output, result.pid);

// Get system info for shell information
const systemInfo = getSystemInfo();
const shellUsed = parsed.data.shell || systemInfo.defaultShell;

let statusMessage = '';
if (processState.isWaitingForInput) {
statusMessage = `\n🔄 ${formatProcessStateMessage(processState, result.pid)}`;
Expand Down
16 changes: 15 additions & 1 deletion src/utils/capture.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {platform} from 'os';

import {randomUUID} from 'crypto';
import * as https from 'https';
import {configManager} from '../config-manager.js';
Expand Down Expand Up @@ -124,13 +125,26 @@ export const captureBase = async (captureURL: string, event: string, properties?
}

// Prepare standard properties
const baseProperties = {
interface BaseProperties {
timestamp: string;
platform: NodeJS.Platform;
app_version: string;
engagement_time_msec: string;
host_client?: string;
}

const baseProperties: BaseProperties = {
timestamp: new Date().toISOString(),
platform: platform(),
app_version: VERSION,
engagement_time_msec: "100"
};

const hostClient = await configManager.getValue('hostClient');
if (hostClient) {
(baseProperties as any).host_client = hostClient;
}
Comment on lines +143 to +146

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve host_client assignment to avoid type casting.

The current implementation uses type casting to assign the host_client property, which bypasses TypeScript's type checking.

Consider this cleaner approach:

-        const hostClient = await configManager.getValue('hostClient');
-        if (hostClient) {
-            (baseProperties as any).host_client = hostClient;
-        }
+        const hostClient = await configManager.getValue('hostClient');
+        if (hostClient) {
+            baseProperties.host_client = hostClient;
+        }

Alternatively, if you want to always include the property, modify the interface and initialization:

         interface BaseProperties {
             timestamp: string;
             platform: NodeJS.Platform;
             app_version: string;
             engagement_time_msec: string;
-            host_client?: string;
+            host_client: string;
         }

         const baseProperties: BaseProperties = {
             timestamp: new Date().toISOString(),
             platform: platform(),
             app_version: VERSION,
-            engagement_time_msec: "100"
+            engagement_time_msec: "100",
+            host_client: await configManager.getValue('hostClient') || 'unknown'
         };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/utils/capture.ts around lines 143 to 146, avoid using type casting to
assign the host_client property on baseProperties as it bypasses TypeScript's
type checking. Instead, update the type definition of baseProperties to include
host_client or initialize baseProperties with host_client as an optional
property. Then assign hostClient directly without casting, ensuring type safety
and cleaner code.


// Combine with sanitized properties
const eventProperties = {
...baseProperties,
Expand Down
30 changes: 28 additions & 2 deletions test/test-enhanced-repl.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
import assert from 'assert';
import { execSync } from 'child_process';
import { startProcess, readProcessOutput, forceTerminate, interactWithProcess } from '../dist/tools/improved-process-tools.js';

/**
* Determines the correct python command to use
* @returns {string} 'python3' or 'python'
*/
function getPythonCommand() {
try {
// Prefer python3 if available
execSync('command -v python3', { stdio: 'ignore' });
return 'python3';
} catch (e) {
// Fallback to python
try {
execSync('command -v python', { stdio: 'ignore' });
return 'python';
} catch (error) {
throw new Error('Neither python3 nor python command is available in the PATH');
}
}
}


/**
* Test enhanced REPL functionality
*/
async function testEnhancedREPL() {
console.log('Testing enhanced REPL functionality...');

const pythonCommand = getPythonCommand();
console.log(`Using python command: ${pythonCommand}`);

// Start Python in interactive mode
console.log('Starting Python REPL...');
const result = await startProcess({
command: 'python -i',
timeout_ms: 10000
command: `${pythonCommand} -i`,
timeout_ms: 10000,
shell: '/bin/bash'
});

console.log('Result from start_process:', result);
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion test/test_improved_search_truncation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ async function testImprovedSearchTruncation() {

// Test search that will produce many results to trigger truncation
const searchArgs = {
path: '/Users/eduardruzga/work/ClaudeServerCommander',
path: '.',
pattern: '.', // Match almost every line - this should be a lot of results
maxResults: 50000, // Very high limit to get lots of results, but capped at 5000
ignoreCase: true
Expand Down
12 changes: 6 additions & 6 deletions test/test_output/node_repl_debug.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
Starting Node.js REPL...
Waiting for Node.js startup...
[STDOUT] Welcome to Node.js v23.8.0.
[STDOUT] Welcome to Node.js v22.15.0.
Type ".help" for more information.
[STDOUT] >
Initial output buffer: Welcome to Node.js v23.8.0.
Initial output buffer: Welcome to Node.js v22.15.0.
Type ".help" for more information.
> 
Sending simple command...
[STDOUT] Hello from Node.js!
[STDOUT] undefined
[STDOUT] >
Output after first command: Welcome to Node.js v23.8.0.
Output after first command: Welcome to Node.js v22.15.0.
Type ".help" for more information.
> Hello from Node.js!
undefined
Expand All @@ -35,12 +35,12 @@ for (let i = 0; i < 3; i++) {
[STDOUT] ...
[STDOUT] ...
[STDOUT] Hello, User 0!
[STDOUT] Hello, User 1!
Hello, User 2!
[STDOUT] Hello, User 1!
[STDOUT] Hello, User 2!
[STDOUT] undefined
[STDOUT] >
[STDOUT] >
Final output buffer: Welcome to Node.js v23.8.0.
Final output buffer: Welcome to Node.js v22.15.0.
Type ".help" for more information.
> Hello from Node.js!
undefined
Expand Down
8 changes: 4 additions & 4 deletions test/test_output/repl_test_output.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Python REPL output:
Python 3.11.0 (main, Jan 19 2024, 19:53:39) [Clang 15.0.0 (clang-1500.0.40.1)] on darwin
Python 3.13.3 (main, Apr 8 2025, 13:54:08) [Clang 16.0.0 (clang-1600.0.26.6)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> STARTING PYTHON TEST
>>> REPL_TEST_VALUE: 52
>>> REPL_TEST_VALUE: 44
>>>

Node.js REPL output:
Welcome to Node.js v23.8.0.
Welcome to Node.js v22.15.0.
Type ".help" for more information.
> STARTING NODE TEST
undefined
> NODE_REPL_TEST_VALUE: 81
> NODE_REPL_TEST_VALUE: 54
undefined
>
2 changes: 1 addition & 1 deletion test/test_search_truncation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ async function testSearchTruncation() {

// Test search that will produce many results
const searchArgs = {
path: '/Users/eduardruzga/work/ClaudeServerCommander',
path: '.',
pattern: 'function|const|let|var', // This should match many lines
maxResults: 50000, // Very high limit to get lots of results
ignoreCase: true
Expand Down