From 02c6752965f3ef04991db6d5bb6583d5d6406f33 Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 10 Jun 2025 21:16:27 -0700 Subject: [PATCH 1/3] Create wrapper shim scripts --- .../smithy/java/mcp/cli/ConfigUtils.java | 113 ++++++++++++++++++ .../java/mcp/cli/commands/InstallBundle.java | 15 ++- 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java index 61d162b700..df4324294a 100644 --- a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java @@ -53,8 +53,12 @@ private ConfigUtils() {} .serializeTypeInDocuments(false) .build(); + private static final String OS = System.getProperty("os.name").toLowerCase(); + private static final boolean WINDOWS = OS.contains("windows"); + private static final Path CONFIG_DIR = resolveFromHomeDir(".config", "smithy-mcp"); private static final Path BUNDLE_DIR = CONFIG_DIR.resolve("bundles"); + private static final Path SHIMS_DIR = CONFIG_DIR.resolve("mcp-servers"); private static final Path CONFIG_PATH = CONFIG_DIR.resolve("config.json"); private static final DefaultConfigProvider DEFAULT_CONFIG_PROVIDER; @@ -63,6 +67,7 @@ private ConfigUtils() {} try { ensureDirectoryExists(CONFIG_DIR); ensureDirectoryExists(BUNDLE_DIR); + ensureDirectoryExists(SHIMS_DIR); } catch (IOException e) { throw new RuntimeException(e); } @@ -169,6 +174,8 @@ public static void removeMcpBundle(Config currentConfig, String bundleName) thro updateConfig(newConfig); var bundleFile = getBundleFileLocation(bundleName); Files.deleteIfExists(bundleFile); + // Remove wrapper script if it exists + removeWrapperScript(bundleName); } public static void addToClientConfigs(Config config, String name, Set clients, McpServerConfig serverConfig) @@ -333,4 +340,110 @@ private static String captureProcessOutput(Process process) throws IOException { return in.lines().collect(Collectors.joining(System.lineSeparator())); } } + + public static void createWrapperScript(String bundleName) throws IOException { + Path scriptPath = SHIMS_DIR.resolve(bundleName); + String scriptContent = createScriptContent(bundleName); + + Files.writeString(scriptPath, scriptContent, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + scriptPath.toFile().setExecutable(true); + } + + private static String createScriptContent(String bundleName) { + if (WINDOWS) { + return "@echo off\nmcp-registry start-server " + bundleName + " %*\n"; + } else { + return "#!/bin/bash\nexec mcp-registry start-server " + bundleName + " \"$@\"\n"; + } + } + + public static void removeWrapperScript(String bundleName) throws IOException { + Path scriptPath = SHIMS_DIR.resolve(bundleName); + Files.deleteIfExists(scriptPath); + } + + public static boolean isMcpServersDirInPath() { + String pathEnv = System.getenv("PATH"); + if (pathEnv == null) { + return false; + } + + String shimsDirStr = SHIMS_DIR.toAbsolutePath().toString(); + return pathEnv.contains(shimsDirStr); + } + + public static void ensureMcpServersDirInPath() { + if (isMcpServersDirInPath()) { + return; + } + + String shimsDirStr = SHIMS_DIR.toAbsolutePath().toString(); + + if (WINDOWS) { + printWindowsPathInstructions(shimsDirStr); + } else { + if (!tryAddToShellConfigs(shimsDirStr)) { + printUnixPathInstructions(shimsDirStr); + } + } + } + + private static boolean tryAddToShellConfigs(String shimsDirStr) { + var pathExport = "export PATH=\"" + shimsDirStr + ":$PATH\""; + var comment = "# Added by smithy-mcp"; + var configFiles = List.of(".zshrc", ".bashrc", ".profile", ".bash_profile"); + + String addedTo = null; + + for (var configFile : configFiles) { + var configPath = resolveFromHomeDir(configFile); + + if (Files.exists(configPath)) { + try { + // Check if already present + var lines = Files.readAllLines(configPath, StandardCharsets.UTF_8); + boolean alreadyPresent = lines.stream() + .anyMatch(line -> line.contains(shimsDirStr) && line.contains("PATH")); + + if (!alreadyPresent) { + // Add the export statement + lines.add(""); + lines.add(comment); + lines.add(pathExport); + + Files.write(configPath, + lines, + StandardCharsets.UTF_8, + StandardOpenOption.WRITE, + StandardOpenOption.TRUNCATE_EXISTING); + + System.out.println("Added mcp-servers directory to PATH in " + configFile); + addedTo = configFile; + } + } catch (IOException e) { + // Continue to next config file if this one fails + } + } + } + + if (addedTo != null) { + System.out.println("Please restart your shell or run 'source ~/" + configFiles + "' to reload your PATH"); + return true; + } + return false; + } + + private static void printWindowsPathInstructions(String shimsDirStr) { + System.out.println("\nTo use the installed bundle as a command, add the mcp-servers directory to your PATH:"); + System.out.println(" set PATH=" + shimsDirStr + ";%PATH%"); + System.out.println("Or permanently add it through System Properties > Environment Variables"); + System.out.println(); + } + + private static void printUnixPathInstructions(String shimsDirStr) { + System.out.println("\nTo use the installed bundle as a command, add the mcp-servers directory to your PATH:"); + System.out.println(" export PATH=\"" + shimsDirStr + ":$PATH\""); + System.out.println("Add this line to your shell profile (~/.bashrc, ~/.zshrc, etc.) to make it permanent"); + System.out.println(); + } } diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/InstallBundle.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/InstallBundle.java index 7f2f6dd157..aba27f9bd8 100644 --- a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/InstallBundle.java +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/InstallBundle.java @@ -43,12 +43,22 @@ protected void execute(ExecutionContext context) throws IOException { var config = context.config(); var bundle = registry.getMcpBundle(name); ConfigUtils.addMcpBundle(config, name, bundle); - var command = "mcp-registry"; - var args = List.of("start-server", name); + + var command = name; + List args = null; + boolean shouldCreateWrapper = true; + if (bundle.getValue() instanceof GenericBundle genericBundle && genericBundle.isExecuteDirectly()) { command = genericBundle.getRun().getExecutable(); args = genericBundle.getRun().getArgs(); + shouldCreateWrapper = false; + } + + if (shouldCreateWrapper) { + ConfigUtils.createWrapperScript(name); + ConfigUtils.ensureMcpServersDirInPath(); } + var newClientConfig = McpServerConfig.builder() .command(command) .args(args) @@ -60,6 +70,7 @@ protected void execute(ExecutionContext context) throws IOException { ConfigUtils.addToClientConfigs(config, name, clients, newClientConfig); System.out.println("Successfully installed " + name); + if (print) { System.out.println("You can add the following to your MCP Servers config to use " + name); System.out.println(newClientConfig); From 775bf1e84a35843936d7d33165963ecfe66883cd Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Wed, 11 Jun 2025 13:41:03 -0700 Subject: [PATCH 2/3] Check for file write access and comments in shell script --- .../software/amazon/smithy/java/mcp/cli/ConfigUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java index df4324294a..da7ef90a6b 100644 --- a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java @@ -398,12 +398,14 @@ private static boolean tryAddToShellConfigs(String shimsDirStr) { for (var configFile : configFiles) { var configPath = resolveFromHomeDir(configFile); - if (Files.exists(configPath)) { + if (Files.exists(configPath) && Files.isWritable(configPath)) { try { // Check if already present var lines = Files.readAllLines(configPath, StandardCharsets.UTF_8); boolean alreadyPresent = lines.stream() - .anyMatch(line -> line.contains(shimsDirStr) && line.contains("PATH")); + .anyMatch(line -> line.contains(shimsDirStr) + && line.contains("PATH") + && !(line.trim().startsWith("#"))); if (!alreadyPresent) { // Add the export statement From ee4c011deea3679a0bd1f8cf3b9905a6e5d7ccf9 Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Wed, 11 Jun 2025 13:48:52 -0700 Subject: [PATCH 3/3] Make copy of readlines --- .../java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java index da7ef90a6b..48538fa659 100644 --- a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java @@ -15,6 +15,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; @@ -401,7 +402,7 @@ private static boolean tryAddToShellConfigs(String shimsDirStr) { if (Files.exists(configPath) && Files.isWritable(configPath)) { try { // Check if already present - var lines = Files.readAllLines(configPath, StandardCharsets.UTF_8); + var lines = new ArrayList<>(Files.readAllLines(configPath, StandardCharsets.UTF_8)); boolean alreadyPresent = lines.stream() .anyMatch(line -> line.contains(shimsDirStr) && line.contains("PATH")