Skip to content

Commit 4b539ca

Browse files
committed
Add uninstall command
1 parent a1ff16e commit 4b539ca

File tree

5 files changed

+131
-42
lines changed

5 files changed

+131
-42
lines changed

mcp/mcp-cli-api/model/main.smithy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ namespace smithy.mcp.cli
44

55
structure Config {
66
toolBundles: McpBundleConfigs
7+
78
defaultRegistry: String
9+
810
registries: Registries
11+
12+
@default([])
913
clientConfigs: ClientConfigs
1014
}
1115

@@ -72,6 +76,7 @@ list ToolNames {
7276
member: ToolName
7377
}
7478

79+
@uniqueItems
7580
list ClientConfigs {
7681
member: ClientConfig
7782
}

mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,21 @@
1717
import java.nio.file.StandardOpenOption;
1818
import java.util.Comparator;
1919
import java.util.HashMap;
20+
import java.util.LinkedHashMap;
2021
import java.util.ServiceLoader;
22+
import java.util.Set;
2123
import java.util.concurrent.TimeUnit;
2224
import java.util.stream.Collectors;
2325
import software.amazon.smithy.java.core.schema.SerializableStruct;
26+
import software.amazon.smithy.java.io.ByteBufferUtils;
2427
import software.amazon.smithy.java.json.JsonCodec;
28+
import software.amazon.smithy.java.mcp.cli.model.ClientConfig;
2529
import software.amazon.smithy.java.mcp.cli.model.Config;
2630
import software.amazon.smithy.java.mcp.cli.model.GenericToolBundleConfig;
2731
import software.amazon.smithy.java.mcp.cli.model.Location;
2832
import software.amazon.smithy.java.mcp.cli.model.McpBundleConfig;
33+
import software.amazon.smithy.java.mcp.cli.model.McpServerConfig;
34+
import software.amazon.smithy.java.mcp.cli.model.McpServersClientConfig;
2935
import software.amazon.smithy.java.mcp.cli.model.SmithyModeledBundleConfig;
3036
import software.amazon.smithy.mcp.bundle.api.model.Bundle;
3137
import software.amazon.smithy.mcp.bundle.api.model.ExecSpec;
@@ -149,6 +155,79 @@ public static Bundle getMcpBundle(String bundleName) {
149155
}
150156
}
151157

158+
public static void removeMcpBundle(Config currentConfig, String bundleName) throws IOException {
159+
var builder = currentConfig.toBuilder();
160+
var newBundles = new LinkedHashMap<>(currentConfig.getToolBundles());
161+
newBundles.remove(bundleName);
162+
builder.toolBundles(newBundles);
163+
var newConfig = builder.build();
164+
updateConfig(newConfig);
165+
var bundleFile = getBundleFileLocation(bundleName);
166+
Files.deleteIfExists(bundleFile);
167+
}
168+
169+
public static void addToClientConfigs(Config config, String name, Set<String> clients, McpServerConfig serverConfig)
170+
throws IOException {
171+
updateClientConfigs(config, name, clients, serverConfig);
172+
}
173+
174+
public static void removeFromClientConfigs(Config config, String name, Set<String> clients) throws IOException {
175+
updateClientConfigs(config, name, clients, null);
176+
}
177+
178+
private static void updateClientConfigs(Config config, String name, Set<String> clients, McpServerConfig newConfig)
179+
throws IOException {
180+
var clientConfigsToUpdate = getClientConfigsToUpdate(config, clients);
181+
boolean isDelete = newConfig == null;
182+
for (var clientConfigs : clientConfigsToUpdate) {
183+
var filePath = Path.of(clientConfigs.getFilePath());
184+
if (Files.notExists(filePath)) {
185+
System.out.printf("Skipping updating Mcp config file for %s as the file path '%s' does not exist.",
186+
name,
187+
filePath);
188+
continue;
189+
}
190+
var currentConfig = Files.readAllBytes(filePath);
191+
McpServersClientConfig currentMcpConfig;
192+
if (currentConfig.length == 0) {
193+
if (isDelete) {
194+
continue;
195+
}
196+
currentMcpConfig = McpServersClientConfig.builder().build();
197+
} else {
198+
currentMcpConfig =
199+
McpServersClientConfig.builder()
200+
.deserialize(JSON_CODEC.createDeserializer(currentConfig))
201+
.build();
202+
}
203+
var map = new LinkedHashMap<>(currentMcpConfig.getMcpServers());
204+
if (isDelete) {
205+
if (map.remove(name) == null) {
206+
continue;
207+
}
208+
} else {
209+
map.put(name, newConfig);
210+
}
211+
var newMcpConfig = McpServersClientConfig.builder().mcpServers(map).build();
212+
Files.write(filePath,
213+
ByteBufferUtils.getBytes(JSON_CODEC.serialize(newMcpConfig)),
214+
StandardOpenOption.TRUNCATE_EXISTING);
215+
}
216+
}
217+
218+
private static Set<ClientConfig> getClientConfigsToUpdate(Config config, Set<String> clients) {
219+
Set<ClientConfig> clientConfigsToUpdate;
220+
if (!clients.isEmpty()) {
221+
clientConfigsToUpdate = config.getClientConfigs()
222+
.stream()
223+
.filter(c -> clients.contains(c.getName()))
224+
.collect(Collectors.toUnmodifiableSet());
225+
} else {
226+
clientConfigsToUpdate = Set.copyOf(config.getClientConfigs());
227+
}
228+
return clientConfigsToUpdate;
229+
}
230+
152231
private static void addMcpBundle(Config config, String toolBundleName, CliBundle mcpBundleConfig)
153232
throws IOException {
154233
var serializedBundle = toJson(mcpBundleConfig.mcpBundle());

mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/McpCli.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import software.amazon.smithy.java.mcp.cli.commands.InstallBundle;
1414
import software.amazon.smithy.java.mcp.cli.commands.ListBundles;
1515
import software.amazon.smithy.java.mcp.cli.commands.StartServer;
16+
import software.amazon.smithy.java.mcp.cli.commands.UninstallBundle;
1617

1718
/**
1819
* Main entry point for the Smithy MCP Command Line Interface.
@@ -32,6 +33,7 @@ public static void main(String[] args) {
3233
.addSubcommand(new StartServer())
3334
.addSubcommand(new ListBundles())
3435
.addSubcommand(new InstallBundle())
36+
.addSubcommand(new UninstallBundle())
3537
.addSubcommand(configureCommand);
3638
commandLine.execute(args);
3739
}

mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/InstallBundle.java

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,29 @@
88
import static picocli.CommandLine.Command;
99

1010
import java.io.IOException;
11-
import java.nio.file.Files;
12-
import java.nio.file.Path;
13-
import java.nio.file.StandardOpenOption;
14-
import java.util.LinkedHashMap;
1511
import java.util.List;
12+
import java.util.Set;
1613
import picocli.CommandLine.Option;
17-
import software.amazon.smithy.java.io.ByteBufferUtils;
18-
import software.amazon.smithy.java.json.JsonCodec;
19-
import software.amazon.smithy.java.json.JsonSettings;
14+
import picocli.CommandLine.Parameters;
2015
import software.amazon.smithy.java.mcp.cli.ConfigUtils;
2116
import software.amazon.smithy.java.mcp.cli.ExecutionContext;
2217
import software.amazon.smithy.java.mcp.cli.SmithyMcpCommand;
2318
import software.amazon.smithy.java.mcp.cli.model.Config;
2419
import software.amazon.smithy.java.mcp.cli.model.McpServerConfig;
25-
import software.amazon.smithy.java.mcp.cli.model.McpServersClientConfig;
2620

2721
@Command(name = "install", description = "Downloads and adds a bundle from the MCP registry.")
2822
public class InstallBundle extends SmithyMcpCommand {
2923

30-
private static final JsonCodec JSON_CODEC = JsonCodec.builder()
31-
.settings(JsonSettings.builder()
32-
.prettyPrint(true)
33-
.build())
34-
.build();
35-
3624
@Option(names = {"-r", "--registry"},
3725
description = "Name of the registry to list the bundles from. If not provided it will use the default registry.")
3826
String registryName;
3927

40-
@Option(names = {"-n", "--name"}, description = "Name of the MCP Bundle to install.")
28+
@Parameters(description = "Name of the MCP bundle to install.")
4129
String name;
4230

4331
@Option(names = {"--clients"},
4432
description = "Names of client configs to update. If not specified all client configs registered would be updated")
45-
List<String> clients;
33+
Set<String> clients = Set.of();
4634

4735
@Option(names = "--print-only",
4836
description = "If specified will not edit the client configs and only print to console.")
@@ -56,34 +44,11 @@ protected void execute(ExecutionContext context) throws IOException {
5644
ConfigUtils.addMcpBundle(config, name, bundle);
5745
var newConfig = McpServerConfig.builder().command("mcp-registry").args(List.of("start-server", name)).build();
5846
if (print == null) {
59-
//By default print the output if there are no configured client configs.
47+
//By default, print the output if there are no configured client configs.
6048
print = !config.hasClientConfigs();
6149
}
62-
for (var clientConfigs : config.getClientConfigs()) {
63-
var filePath = Path.of(clientConfigs.getFilePath());
64-
if (Files.notExists(filePath)) {
65-
System.out.printf("Skipping updating Mcp config file for %s as the file path '%s' does not exist.",
66-
name,
67-
filePath);
68-
continue;
69-
}
70-
var currentConfig = Files.readAllBytes(filePath);
71-
McpServersClientConfig currentMcpConfig;
72-
if (currentConfig.length == 0) {
73-
currentMcpConfig = McpServersClientConfig.builder().build();
74-
} else {
75-
currentMcpConfig =
76-
McpServersClientConfig.builder()
77-
.deserialize(JSON_CODEC.createDeserializer(currentConfig))
78-
.build();
79-
}
80-
var map = new LinkedHashMap<>(currentMcpConfig.getMcpServers());
81-
map.put(name, newConfig);
82-
var newMcpConfig = McpServersClientConfig.builder().mcpServers(map).build();
83-
Files.write(filePath,
84-
ByteBufferUtils.getBytes(JSON_CODEC.serialize(newMcpConfig)),
85-
StandardOpenOption.TRUNCATE_EXISTING);
86-
}
50+
ConfigUtils.addToClientConfigs(config, name, clients, newConfig);
51+
8752
System.out.println("Successfully installed " + name);
8853
if (print) {
8954
System.out.println("You can add the following to your MCP Servers config to use " + name);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.java.mcp.cli.commands;
7+
8+
import java.util.Set;
9+
import picocli.CommandLine;
10+
import picocli.CommandLine.Command;
11+
import picocli.CommandLine.Parameters;
12+
import software.amazon.smithy.java.mcp.cli.ConfigUtils;
13+
import software.amazon.smithy.java.mcp.cli.ExecutionContext;
14+
import software.amazon.smithy.java.mcp.cli.SmithyMcpCommand;
15+
16+
@Command(name = "uninstall", description = "Uninstall a MCP bundle.")
17+
public class UninstallBundle extends SmithyMcpCommand {
18+
19+
@Parameters(description = "Name of the MCP bundle to uninstall.")
20+
String name;
21+
22+
@CommandLine.Option(names = {"--clients"},
23+
description = "Names of client configs to update. If not specified all client configs registered would be updated.")
24+
Set<String> clients = Set.of();
25+
26+
@Override
27+
protected void execute(ExecutionContext context) throws Exception {
28+
var config = context.config();
29+
if (!config.getToolBundles().containsKey(name)) {
30+
System.out.println("No such MCP bundle exists. Nothing to do.");
31+
return;
32+
}
33+
ConfigUtils.removeMcpBundle(config, name);
34+
System.out.println("Uninstalled MCP bundle: " + name);
35+
ConfigUtils.removeFromClientConfigs(config, name, clients);
36+
System.out.println("Updated client configs");
37+
}
38+
}

0 commit comments

Comments
 (0)