From b190a74b0b2ef43fb85e78dac425418633d399f0 Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 29 Apr 2025 19:54:31 -0700 Subject: [PATCH 01/13] First cut of the smithy-mcp-cli --- aws/aws-mcp-cli-commands/build.gradle.kts | 21 +++ .../cli/commands/AddAwsServiceToolBundle.java | 87 ++++++++++++ ...n.smithy.java.mcp.cli.ConfigurationCommand | 1 + .../bundler/AwsServiceBundler.java | 11 +- .../codegen/types/JavaTypeCodegenPlugin.java | 2 + .../server/mcp/BundleMCPServerExample.java | 4 +- .../example/server/mcp/MCPServerExample.java | 4 +- .../example/server/mcp/ProxyMCPExample.java | 5 +- gradle/libs.versions.toml | 1 + .../smithy/java/io/ByteBufferUtils.java | 11 ++ mcp/mcp-cli-api/build.gradle.kts | 56 ++++++++ mcp/mcp-cli-api/license.txt | 4 + mcp/mcp-cli-api/model/main.smithy | 96 +++++++++++++ mcp/mcp-cli-api/smithy-build.json | 10 ++ .../java/mcp/cli/AbstractAddToolBundle.java | 57 ++++++++ .../smithy/java/mcp/cli/ConfigUtils.java | 127 ++++++++++++++++++ .../java/mcp/cli/ConfigurationCommand.java | 16 +++ .../smithy/java/mcp/cli/SmithyMcpCommand.java | 49 +++++++ mcp/mcp-cli/build.gradle.kts | 62 +++++++++ .../amazon/smithy/java/mcp/cli/McpCli.java | 46 +++++++ .../smithy/java/mcp/cli/VersionProvider.java | 23 ++++ .../mcp/cli/commands/AddSmithyToolBundle.java | 56 ++++++++ .../java/mcp/cli/commands/Configure.java | 21 +++ .../java/mcp/cli/commands/StartServer.java | 111 +++++++++++++++ mcp/mcp-schemas/build.gradle.kts | 6 +- .../server/McpServer.java} | 12 +- .../server/McpServerBuilder.java} | 25 ++-- .../smithy/java/server/OperationFilters.java | 11 +- .../smithy/java/server/ProxyService.java | 2 +- settings.gradle.kts | 5 +- 30 files changed, 906 insertions(+), 36 deletions(-) create mode 100644 aws/aws-mcp-cli-commands/build.gradle.kts create mode 100644 aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java create mode 100644 aws/aws-mcp-cli-commands/src/main/resources/META-INF/services/software.amazon.smithy.java.mcp.cli.ConfigurationCommand create mode 100644 mcp/mcp-cli-api/build.gradle.kts create mode 100644 mcp/mcp-cli-api/license.txt create mode 100644 mcp/mcp-cli-api/model/main.smithy create mode 100644 mcp/mcp-cli-api/smithy-build.json create mode 100644 mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddToolBundle.java create mode 100644 mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java create mode 100644 mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigurationCommand.java create mode 100644 mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/SmithyMcpCommand.java create mode 100644 mcp/mcp-cli/build.gradle.kts create mode 100644 mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/McpCli.java create mode 100644 mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/VersionProvider.java create mode 100644 mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddSmithyToolBundle.java create mode 100644 mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/Configure.java create mode 100644 mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java rename mcp/mcp-server/src/main/java/software/amazon/smithy/java/{server/mcp/MCPServer.java => mcp/server/McpServer.java} (97%) rename mcp/mcp-server/src/main/java/software/amazon/smithy/java/{server/mcp/MCPServerBuilder.java => mcp/server/McpServerBuilder.java} (66%) diff --git a/aws/aws-mcp-cli-commands/build.gradle.kts b/aws/aws-mcp-cli-commands/build.gradle.kts new file mode 100644 index 000000000..4b20cb98a --- /dev/null +++ b/aws/aws-mcp-cli-commands/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + id("smithy-java.module-conventions") +} + +description = "This module produces service bundles for AWS services" + +extra["displayName"] = "Smithy :: Java :: AWS :: Service Bundler" +extra["moduleName"] = "software.amazon.smithy.java.aws.mcp.cli.commands" + +dependencies { + implementation(project(":model-bundler:bundle-api")) + implementation(libs.smithy.model) + implementation(libs.picocli) + implementation(project(":aws:aws-mcp-types")) + // we need to be able to resolve the sigv4 and protocol traits + implementation(libs.smithy.aws.traits) + implementation(project(":mcp:mcp-cli-api")) + implementation(project(":aws:aws-service-bundler")) + + testImplementation(libs.aws.sdk.auth) +} diff --git a/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java new file mode 100644 index 000000000..a23e6d0a6 --- /dev/null +++ b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java @@ -0,0 +1,87 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.aws.mcp.cli.commands; + +import java.util.Set; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import software.amazon.smithy.java.aws.servicebundle.bundler.AwsServiceBundler; +import software.amazon.smithy.java.mcp.cli.AbstractAddToolBundle; +import software.amazon.smithy.java.mcp.cli.model.Bundle; +import software.amazon.smithy.java.mcp.cli.model.GenericArguments; +import software.amazon.smithy.java.mcp.cli.model.Model; +import software.amazon.smithy.java.mcp.cli.model.SmithyModeledToolBundleConfig; +import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig.SmithyModeledMember; + +@Command(name = "add-aws-service-tool-bundle") +public class AddAwsServiceToolBundle extends AbstractAddToolBundle { + + @Option(names = "--overwrite", + description = "Overwrite existing config", + defaultValue = "false") + protected boolean overwrite; + + @Option(names = {"-n", "--name"}, description = "Name of the AWS Service.", required = true) + protected String awsServiceName; + + @Option(names = {"-a", "--allowed-apis"}, description = "List of APIs to expose in the MCP server") + protected Set allowedApis; + + @Option(names = {"-b", "--blocked-apis"}, description = "List of APIs to hide in the MCP server") + protected Set blockedApis; + + @Override + protected SmithyModeledMember getNewToolConfig() { + var bundle = new AwsServiceBundler(awsServiceName).bundle(); + return new SmithyModeledMember(SmithyModeledToolBundleConfig.builder() + .name(awsServiceName) + .serviceDescriptor(convert(bundle)) + .build()); + } + + private static Bundle convert(software.amazon.smithy.modelbundle.api.model.Bundle bundle) { + return Bundle.builder() + .config(bundle.getConfig()) + .configType(bundle.getConfigType()) + .serviceName(bundle.getServiceName()) + .model(convert(bundle.getModel())) + .requestArguments(convert(bundle.getRequestArguments())) + .build(); + } + + private static GenericArguments convert( + software.amazon.smithy.modelbundle.api.model.GenericArguments genericArguments + ) { + return GenericArguments.builder() + .model(convert(genericArguments.getModel())) + .identifier(genericArguments.getIdentifier()) + .build(); + } + + private static Model convert(software.amazon.smithy.modelbundle.api.model.Model model) { + return Model.builder().smithyModel(model.getValue()).build(); + } + + @Override + protected String getToolBundleName() { + return awsServiceName; + } + + @Override + protected boolean canOverwrite() { + return overwrite; + } + + @Override + protected Set allowedTools() { + return allowedApis; + } + + @Override + protected Set blockedTools() { + return blockedApis; + } +} diff --git a/aws/aws-mcp-cli-commands/src/main/resources/META-INF/services/software.amazon.smithy.java.mcp.cli.ConfigurationCommand b/aws/aws-mcp-cli-commands/src/main/resources/META-INF/services/software.amazon.smithy.java.mcp.cli.ConfigurationCommand new file mode 100644 index 000000000..1986e7c55 --- /dev/null +++ b/aws/aws-mcp-cli-commands/src/main/resources/META-INF/services/software.amazon.smithy.java.mcp.cli.ConfigurationCommand @@ -0,0 +1 @@ +software.amazon.smithy.java.aws.mcp.cli.commands.AddAwsServiceToolBundle \ No newline at end of file diff --git a/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java b/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java index 7135ca49a..548a634eb 100644 --- a/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java +++ b/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java @@ -34,7 +34,7 @@ import software.amazon.smithy.modelbundle.api.model.GenericArguments; import software.amazon.smithy.modelbundle.api.model.Model; -final class AwsServiceBundler implements Bundler { +public final class AwsServiceBundler implements Bundler { private static final ShapeId ENDPOINT_TESTS = ShapeId.from("smithy.rules#endpointTests"); // visible for testing @@ -64,6 +64,10 @@ final class AwsServiceBundler implements Bundler { this.resolver = resolver; } + public AwsServiceBundler(String serviceName) { + this(serviceName, GithubModelResolver.INSTANCE); + } + @Override public Bundle bundle() { try { @@ -112,16 +116,17 @@ public Bundle bundle() { } private static String serializeModel(software.amazon.smithy.model.Model model) { - return ObjectNode.printJson(ModelSerializer.builder() + var output = ObjectNode.printJson(ModelSerializer.builder() .build() .serialize(model)); + return output; } private static String loadModel(String path) { try (var reader = new BufferedReader(new InputStreamReader( Objects.requireNonNull(AwsServiceBundler.class.getResourceAsStream(path)), StandardCharsets.UTF_8))) { - return reader.lines().collect(Collectors.joining()); + return reader.lines().collect(Collectors.joining("\n")); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/codegen/plugins/types-codegen/src/main/java/software/amazon/smithy/java/codegen/types/JavaTypeCodegenPlugin.java b/codegen/plugins/types-codegen/src/main/java/software/amazon/smithy/java/codegen/types/JavaTypeCodegenPlugin.java index c012ce6cb..44a15bf8b 100644 --- a/codegen/plugins/types-codegen/src/main/java/software/amazon/smithy/java/codegen/types/JavaTypeCodegenPlugin.java +++ b/codegen/plugins/types-codegen/src/main/java/software/amazon/smithy/java/codegen/types/JavaTypeCodegenPlugin.java @@ -85,7 +85,9 @@ private static Set getClosure(Model model, TypeCodegenSettings settings) .filter(nested::contains) .collect(Collectors.toSet())); } + System.out.println(nested); closure.removeAll(nested); + System.out.println(closure); if (closure.isEmpty()) { throw new CodegenException("Could not generate types. No shapes found in closure"); } diff --git a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java index 0c60fbd28..b403d9c03 100644 --- a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java +++ b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java @@ -2,7 +2,7 @@ import software.amazon.smithy.java.json.JsonCodec; import software.amazon.smithy.java.server.ProxyService; -import software.amazon.smithy.java.server.mcp.MCPServer; +import software.amazon.smithy.java.mcp.server.McpServer; import software.amazon.smithy.modelbundle.api.model.Bundle; import java.util.Objects; @@ -10,7 +10,7 @@ public final class BundleMCPServerExample { public static void main(String[] args) throws Exception { try { - var mcpServer = MCPServer.builder() + var mcpServer = McpServer.builder() .stdio() .name("smithy-mcp-server") .addService( diff --git a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/MCPServerExample.java b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/MCPServerExample.java index 710a1281a..f29a29bdc 100644 --- a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/MCPServerExample.java +++ b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/MCPServerExample.java @@ -3,7 +3,7 @@ import software.amazon.smithy.java.example.server.mcp.operations.GetCodingStatistics; import software.amazon.smithy.java.example.server.mcp.operations.GetEmployeeDetails; import software.amazon.smithy.java.example.server.mcp.service.EmployeeService; -import software.amazon.smithy.java.server.mcp.MCPServer; +import software.amazon.smithy.java.mcp.server.McpServer; public class MCPServerExample { @@ -13,7 +13,7 @@ public static void main(String[] args) { .addGetEmployeeDetailsOperation(new GetEmployeeDetails()) .build(); - var mcpServer = MCPServer.builder() + var mcpServer = McpServer.builder() .stdio() .name("smithy-mcp-server") .addService(service) diff --git a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/ProxyMCPExample.java b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/ProxyMCPExample.java index 9e4676041..771fd5d76 100644 --- a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/ProxyMCPExample.java +++ b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/ProxyMCPExample.java @@ -1,13 +1,12 @@ package software.amazon.smithy.java.example.server.mcp; -import software.amazon.smithy.java.client.core.auth.scheme.AuthScheme; import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolver; import software.amazon.smithy.java.example.server.mcp.operations.GetCodingStatistics; import software.amazon.smithy.java.example.server.mcp.operations.GetEmployeeDetails; import software.amazon.smithy.java.example.server.mcp.service.EmployeeService; +import software.amazon.smithy.java.mcp.server.McpServer; import software.amazon.smithy.java.server.ProxyService; import software.amazon.smithy.java.server.Server; -import software.amazon.smithy.java.server.mcp.MCPServer; import software.amazon.smithy.model.Model; public class ProxyMCPExample { @@ -39,7 +38,7 @@ public static void main(String[] args) { .proxyEndpoint("http://localhost:8080") .build(); - var mcpServer = MCPServer.builder() + var mcpServer = McpServer.builder() .stdio() .name("smithy-mcp-server") .addService(mcpService) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 33fa5c628..2593f89fa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,6 +30,7 @@ smithy-protocol-tests = { module = "software.amazon.smithy:smithy-protocol-tests smithy-validation-model = { module = "software.amazon.smithy:smithy-validation-model", version.ref = "smithy" } smithy-jmespath = { module = "software.amazon.smithy:smithy-jmespath", version.ref = "smithy" } smithy-waiters = { module = "software.amazon.smithy:smithy-waiters", version.ref = "smithy" } +smithy-utils = { module = "software.amazon.smithy:smithy-utils", version.ref = "smithy" } # AWS SDK for Java V2 adapters. aws-sdk-retries-spi = {module = "software.amazon.awssdk:retries-spi", version.ref = "aws-sdk"} diff --git a/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java b/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java index edbaf4efe..653c26d14 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java +++ b/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java @@ -7,6 +7,8 @@ import java.io.InputStream; import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Base64; public final class ByteBufferUtils { @@ -33,6 +35,15 @@ public static byte[] getBytes(ByteBuffer buffer) { return bytes; } + public static String asString(ByteBuffer buffer) { + return asString(buffer, StandardCharsets.UTF_8); + } + + public static String asString(ByteBuffer buffer, Charset charset) { + byte[] bytes = getBytes(buffer); + return new String(bytes, charset); + } + public static InputStream byteBufferInputStream(ByteBuffer buffer) { return new ByteBufferBackedInputStream(buffer); } diff --git a/mcp/mcp-cli-api/build.gradle.kts b/mcp/mcp-cli-api/build.gradle.kts new file mode 100644 index 000000000..8f96b87fc --- /dev/null +++ b/mcp/mcp-cli-api/build.gradle.kts @@ -0,0 +1,56 @@ +plugins { + id("smithy-java.module-conventions") + id("software.amazon.smithy.gradle.smithy-base") +} + +description = "This module provides a apis for MCP CLI" + +extra["displayName"] = "Smithy :: Java :: MCP CLI API" +extra["moduleName"] = "software.amazon.smithy.mcp.cli.api" + +dependencies { + api(project(":core")) + api(libs.smithy.model) + api(libs.picocli) + api(project(":model-bundler:bundle-api")) + implementation(project(":codecs:json-codec")) + implementation(project(":logging")) + smithyBuild(project(":codegen:plugins:types-codegen")) +} + +sourceSets { + main { + java { + srcDir("model") + } + } +} + +afterEvaluate { + val typePath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-type-codegen") + sourceSets { + main { + java { + srcDir(typePath) + include("software/**") + } + resources { + srcDir(typePath) + include("META-INF/**") + } + } + } +} + +tasks.named("compileJava") { + dependsOn("smithyBuild") +} + +// Needed because sources-jar needs to run after smithy-build is done +tasks.sourcesJar { + mustRunAfter(tasks.compileJava) +} + +tasks.processResources { + dependsOn(tasks.compileJava) +} diff --git a/mcp/mcp-cli-api/license.txt b/mcp/mcp-cli-api/license.txt new file mode 100644 index 000000000..5f97ab495 --- /dev/null +++ b/mcp/mcp-cli-api/license.txt @@ -0,0 +1,4 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ \ No newline at end of file diff --git a/mcp/mcp-cli-api/model/main.smithy b/mcp/mcp-cli-api/model/main.smithy new file mode 100644 index 000000000..38a221716 --- /dev/null +++ b/mcp/mcp-cli-api/model/main.smithy @@ -0,0 +1,96 @@ +$version: "2" + +namespace smithy.mcp.cli + +structure Config { + toolBundles: ToolBundleConfigs +} + +map ToolBundleConfigs { + key: String + value: ToolBundleConfig +} + +union ToolBundleConfig { + smithyModeled: SmithyModeledToolBundleConfig + genericConfig: GenericToolBundleConfig +} + +@mixin +structure CommonToolConfig { + name: String + allowListedTools: ToolNames + blockListedTools: ToolNames +} + +structure SmithyModeledToolBundleConfig with [CommonToolConfig] { + bundlePlugins: BundlePlugins + + allowListedTools: ToolNames + + blockListedTools: ToolNames + + serviceDescriptor: Bundle + + // TODO separate this into another location and just reference it here. +} + +structure GenericToolBundleConfig with [CommonToolConfig] { + config: Document +} + +list BundlePlugins { + member: BundlePlugin +} + +structure BundlePlugin { + name: String + jars: FilePaths +} + +list ToolNames { + member: ToolName +} + +list FilePaths { + member: FilePath +} + +string FilePath + +string ToolName + +// TODO fix this once external type Schemas are fixed +structure Bundle { + /// unique identifier for the configuration type. used to resolve the appropriate Bundler. + @required + configType: String + + /// fully-qualified ShapeId of the service + @required + serviceName: String + + /// Bundle-specific configuration. If this bundle does not require configuration, this + /// field may be omitted. + config: Document + + /// model that describes the service. The service given in `serviceName` must be present. + @required + model: Model + + /// model describing the generic arguments that must be present in every request. If this + /// bundle does not require generic arguments, this field may be omitted. + requestArguments: GenericArguments +} + +union Model { + smithyModel: String +} + +structure GenericArguments { + @required + identifier: String + + @required + model: Model +} diff --git a/mcp/mcp-cli-api/smithy-build.json b/mcp/mcp-cli-api/smithy-build.json new file mode 100644 index 000000000..7c24bf49a --- /dev/null +++ b/mcp/mcp-cli-api/smithy-build.json @@ -0,0 +1,10 @@ +{ + "version": "1.0", + "plugins": { + "java-type-codegen": { + "namespace": "software.amazon.smithy.java.mcp.cli", + "headerFile": "license.txt", + "useExternalTypes": true + } + } +} diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddToolBundle.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddToolBundle.java new file mode 100644 index 000000000..745be2a30 --- /dev/null +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddToolBundle.java @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +import java.util.Set; +import software.amazon.smithy.java.mcp.cli.model.Config; +import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig; + +/** + * Abstract base class for CLI commands that add tool bundles to the Smithy MCP configuration. + *

+ * Subclasses must implement methods to provide tool bundle configuration details and specify + * whether existing configurations can be overwritten. + * + * @param The specific type of ToolBundleConfig to be added + */ +public abstract class AbstractAddToolBundle extends ConfigurationCommand { + + @Override + public final void execute(Config config) throws Exception { + if (!canOverwrite() && config.getToolBundles().containsKey(getToolBundleName())) { + throw new IllegalArgumentException("Tool bundle " + getToolBundleName() + + " already exists. Either choose a new name or pass --overwrite to overwrite the existing tool bundle"); + } + var newConfig = getNewToolConfig(); + ConfigUtils.addToolConfig(config, getToolBundleName(), newConfig); + System.out.println("Added tool bundle " + getToolBundleName()); + } + + /** + * Returns a new tool configuration instance to be added to the Smithy MCP config. + * + * @return A new tool bundle configuration + */ + protected abstract T getNewToolConfig(); + + /** + * Returns the name under which this tool bundle will be registered. + * + * @return The tool bundle name + */ + protected abstract String getToolBundleName(); + + /** + * Determines whether an existing tool bundle with the same name can be overwritten. + * + * @return true if existing tool bundle can be overwritten, false otherwise + */ + protected abstract boolean canOverwrite(); + + protected abstract Set allowedTools(); + + protected abstract Set blockedTools(); +} 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 new file mode 100644 index 000000000..c3a631d08 --- /dev/null +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java @@ -0,0 +1,127 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import software.amazon.smithy.java.io.ByteBufferUtils; +import software.amazon.smithy.java.json.JsonCodec; +import software.amazon.smithy.java.mcp.cli.model.Config; +import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig; + +/** + * Utility class for managing Smithy MCP configuration files. + *

+ * This class provides methods for loading, creating, updating, and serializing MCP configurations. + */ +public class ConfigUtils { + + private ConfigUtils() {} + + private static final JsonCodec JSON_CODEC = JsonCodec.builder().build(); + + /** + * Gets the path to the config file. + * + * @return The path to the config file + */ + private static Path getConfigPath() { + String userHome = System.getProperty("user.home"); + Path configDir = Paths.get(userHome, ".config", "smithy-mcp"); + return configDir.resolve("config.json"); + } + + /** + * Ensures the config directory exists. + * + * @throws IOException If there's an error creating directories + */ + private static void ensureConfigDirExists() throws IOException { + Path configDir = getConfigPath(); + if (!Files.exists(configDir)) { + Files.createDirectories(configDir); + } + } + + /** + * Loads an existing config file or creates one if it doesn't exist. + * + * @return The config file + * @throws IOException If there's an error creating directories or the file + */ + public static Config loadOrCreateConfig() throws IOException { + Path configFile = getConfigPath(); + ensureConfigDirExists(); + + // Check if the config file exists, create it if it doesn't + var file = configFile.toFile(); + if (!file.exists()) { + // Create an empty JSON object as the default config + try (var writer = new FileWriter(file, StandardCharsets.UTF_8)) { + writer.write("{}"); + } + } + + return fromJson(Files.readAllBytes(configFile)); + } + + /** + * Updates the MCP configuration file with the provided configuration. + * + * @param config The configuration to write to the file + * @throws IOException If there's an error writing to the file + */ + public static void updateConfig(Config config) throws IOException { + Path configFile = getConfigPath(); + ensureConfigDirExists(); + + var file = configFile.toFile(); + try (var writer = new FileWriter(file, StandardCharsets.UTF_8)) { + writer.write(ByteBufferUtils.asString(JSON_CODEC.serialize(config))); + } + } + + /** + * Deserializes a Config object from JSON bytes. + * + * @param json The JSON data as a byte array + * @return The deserialized Config object with defaults applied + */ + public static Config fromJson(byte[] json) { + return adjustDefaults(Config.builder().deserialize(JSON_CODEC.createDeserializer(json)).build()); + } + + /** + * Applies default values to a configuration if needed. + * + * @param config The configuration to adjust + * @return The configuration with defaults applied + */ + private static Config adjustDefaults(Config config) { + return config; + } + + /** + * Adds a new tool bundle configuration to an existing configuration and saves it. + * + * @param existingConfig The existing configuration to update + * @param name The name under which to register the tool bundle + * @param toolBundleConfig The tool bundle configuration to add + * @throws IOException If there's an error writing the updated configuration + */ + public static void addToolConfig(Config existingConfig, String name, ToolBundleConfig toolBundleConfig) + throws IOException { + var existingToolBundles = new HashMap<>(existingConfig.getToolBundles()); + existingToolBundles.put(name, toolBundleConfig); + var newConfig = existingConfig.toBuilder().toolBundles(existingToolBundles).build(); + updateConfig(newConfig); + } +} diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigurationCommand.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigurationCommand.java new file mode 100644 index 000000000..6fccac730 --- /dev/null +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigurationCommand.java @@ -0,0 +1,16 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +/** + * Base class for all Smithy MCP CLI configuration commands. + *

+ * This class extends SmithyMcpCommand to provide a common base for all commands + * that modify the MCP configuration. Subclasses should implement the execute method. + */ +public abstract class ConfigurationCommand extends SmithyMcpCommand { + +} diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/SmithyMcpCommand.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/SmithyMcpCommand.java new file mode 100644 index 000000000..403475d08 --- /dev/null +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/SmithyMcpCommand.java @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +import static software.amazon.smithy.java.mcp.cli.ConfigUtils.loadOrCreateConfig; + +import java.util.concurrent.Callable; +import software.amazon.smithy.java.logging.InternalLogger; +import software.amazon.smithy.java.mcp.cli.model.Config; + +/** + * Base class for all Smithy MCP CLI commands. + *

+ * This class implements the Callable interface to provide a consistent execution pattern + * for all MCP CLI commands. It handles loading the configuration, executing the command, + * and providing appropriate error handling. + */ +public abstract class SmithyMcpCommand implements Callable { + + InternalLogger LOG = InternalLogger.getLogger(SmithyMcpCommand.class); + + @Override + public final Integer call() throws Exception { + try { + var config = loadOrCreateConfig(); + execute(config); + return 0; + } catch (IllegalArgumentException e) { + System.out.println("Invalid input : [" + e.getMessage() + "]"); + return 2; + } catch (Exception e) { + LOG.error("Unexpected error", e); + return 1; + } + } + + /** + * Execute the command with the provided configuration. + *

+ * Subclasses must implement this method to provide command-specific functionality. + * + * @param config The MCP configuration + * @throws Exception If an error occurs during execution + */ + protected abstract void execute(Config config) throws Exception; +} diff --git a/mcp/mcp-cli/build.gradle.kts b/mcp/mcp-cli/build.gradle.kts new file mode 100644 index 000000000..218e8827c --- /dev/null +++ b/mcp/mcp-cli/build.gradle.kts @@ -0,0 +1,62 @@ +import com.github.jengelman.gradle.plugins.shadow.transformers.AppendingTransformer +import kotlin.jvm.java + +plugins { + id("smithy-java.module-conventions") + alias(libs.plugins.shadow) + application +} + +description = + "MCP Server support" + +extra["displayName"] = "Smithy :: Java :: MCP CLI" +extra["moduleName"] = "software.amazon.smithy.java.mcp.cli" + +dependencies { + implementation(project(":logging")) + implementation(project(":mcp:mcp-server")) + implementation(project(":server:server-proxy")) + implementation(project(":codecs:json-codec")) + implementation(libs.picocli) + api(project(":mcp:mcp-cli-api")) + implementation(libs.smithy.utils) + + // TODO these need to be dynamically loaded + implementation(project(":aws:aws-mcp-cli-commands")) + implementation(project(":aws:aws-service-bundle")) + implementation(project(":aws:client:aws-client-restjson")) + implementation(project(":aws:client:aws-client-awsjson")) +} + +application { + mainClass = "software.amazon.smithy.java.mcp.cli.McpCli" + applicationDefaultJvmArgs = listOf("-Dorg.slf4j.simpleLogger.defaultLogLevel=off") + applicationName = "smithy-mcp" +} + +val generateVersionFile = + tasks.register("generateVersionFile") { + val versionFile = + sourceSets.main + .get() + .output.resourcesDir + ?.resolve("software/amazon/smithy/java/mcp/cli/VERSION")!! + + outputs.file(versionFile) + + doLast { + versionFile.writeText(project.version.toString()) + } + } + +tasks.processResources { + dependsOn(generateVersionFile) +} + +tasks.shadowJar { + mergeServiceFiles() + transform(AppendingTransformer::class.java) { + resource = "META-INF/smithy/manifest" + } +} diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/McpCli.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/McpCli.java new file mode 100644 index 000000000..a18cbe441 --- /dev/null +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/McpCli.java @@ -0,0 +1,46 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +import java.util.List; +import java.util.ServiceLoader; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import software.amazon.smithy.java.mcp.cli.commands.Configure; +import software.amazon.smithy.java.mcp.cli.commands.StartServer; + +/** + * Main entry point for the Smithy MCP Command Line Interface. + *

+ * This class configures and launches the CLI application using Picocli. + * It discovers and registers all available configuration commands and + * sets up the command hierarchy. + */ +@Command(name = "smithy-mcp", versionProvider = VersionProvider.class, mixinStandardHelpOptions = true, + scope = CommandLine.ScopeType.INHERIT) +public class McpCli { + + public static void main(String[] args) { + var configureCommand = new CommandLine(new Configure()); + discoverConfigurationCommands().forEach(configureCommand::addSubcommand); + var commandLine = new CommandLine(new McpCli()) + .addSubcommand(new StartServer()) + .addSubcommand(configureCommand); + commandLine.execute(args); + } + + /** + * Discovers and loads all ConfigurationCommand implementations using the Java ServiceLoader. + * + * @return A list of discovered configuration commands + */ + private static List discoverConfigurationCommands() { + return ServiceLoader.load(ConfigurationCommand.class) + .stream() + .map(ServiceLoader.Provider::get) + .toList(); + } +} diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/VersionProvider.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/VersionProvider.java new file mode 100644 index 000000000..7c3ccdc76 --- /dev/null +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/VersionProvider.java @@ -0,0 +1,23 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +import picocli.CommandLine; +import software.amazon.smithy.utils.IoUtils; + +/** + * Provides version information for the Smithy MCP CLI. + *

+ * This class implements Picocli's IVersionProvider interface to provide + * version information from a resource file. + */ +public class VersionProvider implements CommandLine.IVersionProvider { + + @Override + public String[] getVersion() throws Exception { + return new String[] {IoUtils.readUtf8Resource(VersionProvider.class, "VERSION").trim()}; + } +} diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddSmithyToolBundle.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddSmithyToolBundle.java new file mode 100644 index 000000000..bb85c4ef9 --- /dev/null +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddSmithyToolBundle.java @@ -0,0 +1,56 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli.commands; + +import java.util.Set; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import software.amazon.smithy.java.mcp.cli.AbstractAddToolBundle; +import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig.SmithyModeledMember; + +/** + * Command to add a Smithy tool bundle to the MCP configuration. + *

+ * This command allows users to add a new Smithy tool bundle to their MCP configuration. + * Currently under development and hidden from the CLI help. + */ +@Command(name = "add-smithy-tool-bundle", description = "Add a smithy tool bundle.", hidden = true) +//TODO implement and unhide +public class AddSmithyToolBundle extends AbstractAddToolBundle { + + @CommandLine.Option(names = "--overwrite", + description = "Overwrite existing config", + defaultValue = "false") + protected boolean overwrite; + + @CommandLine.Option(names = {"-n", "--name"}, description = "Name of the tool bundle.", required = true) + protected String name; + + @Override + protected SmithyModeledMember getNewToolConfig() { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + protected String getToolBundleName() { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + protected boolean canOverwrite() { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + protected Set allowedTools() { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + protected Set blockedTools() { + throw new UnsupportedOperationException("Not implemented yet."); + } +} diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/Configure.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/Configure.java new file mode 100644 index 000000000..34afa1e48 --- /dev/null +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/Configure.java @@ -0,0 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli.commands; + +import picocli.CommandLine.Command; + +/** + * Command group for configuration-related commands in the Smithy MCP CLI. + *

+ * This class serves as a container for all subcommands related to configuring + * the MCP CLI, such as adding tool bundles. + */ +@Command(name = "configure", description = "Configure the Smithy MCP CLI", subcommands = { + AddSmithyToolBundle.class +}) +public final class Configure { + +} diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java new file mode 100644 index 000000000..ddadae6cd --- /dev/null +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java @@ -0,0 +1,111 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli.commands; + +import java.util.ArrayList; +import java.util.List; +import picocli.CommandLine.Command; +import picocli.CommandLine.Parameters; +import software.amazon.smithy.java.mcp.cli.SmithyMcpCommand; +import software.amazon.smithy.java.mcp.cli.model.Bundle; +import software.amazon.smithy.java.mcp.cli.model.Config; +import software.amazon.smithy.java.mcp.cli.model.GenericArguments; +import software.amazon.smithy.java.mcp.cli.model.Model; +import software.amazon.smithy.java.mcp.cli.model.SmithyModeledToolBundleConfig; +import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig; +import software.amazon.smithy.java.mcp.server.McpServer; +import software.amazon.smithy.java.server.FilteredService; +import software.amazon.smithy.java.server.OperationFilters; +import software.amazon.smithy.java.server.ProxyService; +import software.amazon.smithy.java.server.Service; + +/** + * Command to start a Smithy MCP server exposing specified tool bundles. + *

+ * This command loads configured tool bundles and starts an MCP server that + * exposes the operations provided by those bundles. The server runs until + * interrupted or terminated. + */ +@Command(name = "start-server", description = "Starts an MCP server.") +public final class StartServer extends SmithyMcpCommand { + + @Parameters(paramLabel = "TOOL_BUNDLES", description = "Name(s) of the Tool Bundles to expose in this MCP Server.") + List toolBundles; + + /** + * Executes the start-server command. + *

+ * Loads the requested tool bundles from configuration, creates appropriate services, + * and starts the MCP server. + * + * @param config The MCP configuration + * @throws IllegalArgumentException If no tool bundles are configured or requested bundles not found + */ + @Override + public void execute(Config config) { + if (!config.hasToolBundles()) { + throw new IllegalArgumentException( + "No Tool Bundles have been configured. Configure one using the configure-tool-bundle command."); + } + List toolBundleConfigs = new ArrayList<>(toolBundles.size()); + for (var toolBundle : toolBundles) { + var toolBundleConfig = config.getToolBundles().get(toolBundle); + if (toolBundleConfig == null) { + throw new IllegalArgumentException("Can't find a configured tool bundle for '" + toolBundle + "'."); + } + toolBundleConfigs.add(toolBundleConfig); + } + var services = new ArrayList(); + for (var toolBundleConfig : toolBundleConfigs) { + switch (toolBundleConfig.type()) { + case smithyModeled -> { + SmithyModeledToolBundleConfig bundleConfig = toolBundleConfig.getValue(); + Service service = + ProxyService.builder().bundle(convert(bundleConfig.getServiceDescriptor())).build(); + if (bundleConfig.hasAllowListedTools() || bundleConfig.hasBlockListedTools()) { + var filter = OperationFilters.allowList(bundleConfig.getAllowListedTools()) + .and(OperationFilters.blockList(bundleConfig.getBlockListedTools())); + service = new FilteredService(service, filter); + } + services.add(service); + } + default -> + throw new IllegalArgumentException("Unknown tool bundle type '" + toolBundleConfig.type() + "'."); + } + } + var mcpServer = McpServer.builder().stdio().addServices(services).name("smithy-mcp-server").build(); + mcpServer.start(); + try { + Thread.currentThread().join(); + } catch (InterruptedException e) { + mcpServer.shutdown().join(); + } + } + + //TODO remove these after external types Schema if fixed. + private static software.amazon.smithy.modelbundle.api.model.Bundle convert(Bundle bundle) { + return software.amazon.smithy.modelbundle.api.model.Bundle.builder() + .config(bundle.getConfig()) + .configType(bundle.getConfigType()) + .serviceName(bundle.getServiceName()) + .model(convert(bundle.getModel())) + .requestArguments(convert(bundle.getRequestArguments())) + .build(); + } + + private static software.amazon.smithy.modelbundle.api.model.GenericArguments convert( + GenericArguments genericArguments + ) { + return software.amazon.smithy.modelbundle.api.model.GenericArguments.builder() + .model(convert(genericArguments.getModel())) + .identifier(genericArguments.getIdentifier()) + .build(); + } + + private static software.amazon.smithy.modelbundle.api.model.Model convert(Model model) { + return software.amazon.smithy.modelbundle.api.model.Model.builder().smithyModel(model.getValue()).build(); + } +} diff --git a/mcp/mcp-schemas/build.gradle.kts b/mcp/mcp-schemas/build.gradle.kts index 2720b9f44..c6a517b67 100644 --- a/mcp/mcp-schemas/build.gradle.kts +++ b/mcp/mcp-schemas/build.gradle.kts @@ -9,9 +9,9 @@ extra["displayName"] = "Smithy :: Java :: MCP Schemas" extra["moduleName"] = "software.amazon.smithy.mcp.schemas" dependencies { - smithyBuild(project(":codegen:plugins:types-codegen")) api(project(":core")) api(libs.smithy.model) + smithyBuild(project(":codegen:plugins:types-codegen")) } sourceSets { @@ -44,9 +44,9 @@ tasks.named("compileJava") { // Needed because sources-jar needs to run after smithy-build is done tasks.sourcesJar { - mustRunAfter("compileJava") + mustRunAfter(tasks.compileJava) } tasks.processResources { - dependsOn("compileJava") + dependsOn(tasks.compileJava) } diff --git a/mcp/mcp-server/src/main/java/software/amazon/smithy/java/server/mcp/MCPServer.java b/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpServer.java similarity index 97% rename from mcp/mcp-server/src/main/java/software/amazon/smithy/java/server/mcp/MCPServer.java rename to mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpServer.java index 995e61384..797713a5b 100644 --- a/mcp/mcp-server/src/main/java/software/amazon/smithy/java/server/mcp/MCPServer.java +++ b/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpServer.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.server.mcp; +package software.amazon.smithy.java.mcp.server; import java.io.InputStream; import java.io.OutputStream; @@ -44,9 +44,9 @@ import software.amazon.smithy.utils.SmithyUnstableApi; @SmithyUnstableApi -public final class MCPServer implements Server { +public final class McpServer implements Server { - private static final InternalLogger LOG = InternalLogger.getLogger(MCPServer.class); + private static final InternalLogger LOG = InternalLogger.getLogger(McpServer.class); private static final JsonCodec CODEC = JsonCodec.builder() .settings(JsonSettings.builder() @@ -60,7 +60,7 @@ public final class MCPServer implements Server { private final OutputStream os; private final String name; - MCPServer(MCPServerBuilder builder) { + McpServer(McpServerBuilder builder) { this.tools = createTools(builder.serviceList); this.is = builder.is; this.os = builder.os; @@ -237,7 +237,7 @@ private record Tool(ToolInfo toolInfo, Operation operation) { } - public static MCPServerBuilder builder() { - return new MCPServerBuilder(); + public static McpServerBuilder builder() { + return new McpServerBuilder(); } } diff --git a/mcp/mcp-server/src/main/java/software/amazon/smithy/java/server/mcp/MCPServerBuilder.java b/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpServerBuilder.java similarity index 66% rename from mcp/mcp-server/src/main/java/software/amazon/smithy/java/server/mcp/MCPServerBuilder.java rename to mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpServerBuilder.java index 9c649a2f4..6d64774ba 100644 --- a/mcp/mcp-server/src/main/java/software/amazon/smithy/java/server/mcp/MCPServerBuilder.java +++ b/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpServerBuilder.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.java.server.mcp; +package software.amazon.smithy.java.mcp.server; import java.io.InputStream; import java.io.OutputStream; @@ -16,49 +16,54 @@ import software.amazon.smithy.utils.SmithyUnstableApi; @SmithyUnstableApi -public final class MCPServerBuilder { +public final class McpServerBuilder { InputStream is; OutputStream os; List serviceList = new ArrayList<>(); String name; - MCPServerBuilder() {} + McpServerBuilder() {} - public MCPServerBuilder stdio() { + public McpServerBuilder stdio() { this.is = System.in; this.os = System.out; return this; } - public MCPServerBuilder input(InputStream is) { + public McpServerBuilder input(InputStream is) { this.is = is; return this; } - public MCPServerBuilder output(OutputStream os) { + public McpServerBuilder output(OutputStream os) { this.os = os; return this; } - public MCPServerBuilder name(String name) { + public McpServerBuilder name(String name) { this.name = name; return this; } public Server build() { validate(); - return new MCPServer(this); + return new McpServer(this); } - public MCPServerBuilder addService(Service... service) { + public McpServerBuilder addService(Service... service) { serviceList.addAll(Arrays.asList(service)); return this; } + public McpServerBuilder addServices(List services) { + serviceList.addAll(services); + return this; + } + private void validate() { Objects.requireNonNull(is, "MCP server input stream is required"); - Objects.requireNonNull(is, "MCP server output stream is required"); + Objects.requireNonNull(os, "MCP server output stream is required"); if (serviceList.isEmpty()) { throw new IllegalArgumentException("MCP server requires at least one service"); } diff --git a/server/server-api/src/main/java/software/amazon/smithy/java/server/OperationFilters.java b/server/server-api/src/main/java/software/amazon/smithy/java/server/OperationFilters.java index e1e4b3112..df2ca708f 100644 --- a/server/server-api/src/main/java/software/amazon/smithy/java/server/OperationFilters.java +++ b/server/server-api/src/main/java/software/amazon/smithy/java/server/OperationFilters.java @@ -5,6 +5,7 @@ package software.amazon.smithy.java.server; +import java.util.Collection; import java.util.Collections; import java.util.Set; import java.util.function.Predicate; @@ -22,9 +23,9 @@ private OperationFilters() {} * @return A {@link Predicate} that only includes the specified operations */ public static Predicate> allowList( - Set operationNames + Collection operationNames ) { - return new OperationFilters.OperationNameFilter(operationNames, null); + return new OperationFilters.OperationNameFilter(Set.copyOf(operationNames), null); } /** @@ -33,10 +34,10 @@ private OperationFilters() {} * @param operationNames Set of operation names to exclude * @return A {@link Predicate} that excludes the specified operations */ - static Predicate> blockList( - Set operationNames + public static Predicate> blockList( + Collection operationNames ) { - return new OperationNameFilter(null, operationNames); + return new OperationNameFilter(null, Set.copyOf(operationNames)); } static final class OperationNameFilter implements Predicate> { diff --git a/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java b/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java index 5c2a52014..b88f1658e 100644 --- a/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java +++ b/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java @@ -62,7 +62,7 @@ private static software.amazon.smithy.model.Model adapt(Builder builder) { var args = builder.bundle.getRequestArguments(); var model = new ModelAssembler() .addModel(builder.model) - .addModel(args.getModel().getValue()) + .addUnparsedModel("args.smithy", args.getModel().getValue()) .assemble() .unwrap(); var template = model.expectShape(ShapeId.from(args.getIdentifier())) diff --git a/settings.gradle.kts b/settings.gradle.kts index 216b63e33..c40367c29 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -70,11 +70,12 @@ include(":aws:aws-service-bundle") include(":aws:aws-service-bundler") include(":aws:aws-mcp-provider") include(":aws:aws-mcp-types") +include(":aws:aws-mcp-cli-commands") // AWS SDK V2 shims include(":aws:sdkv2:aws-sdkv2-retries") include(":aws:sdkv2:aws-sdkv2-shapes") -include("aws:sdkv2:aws-sdkv2-auth") +include(":aws:sdkv2:aws-sdkv2-auth") // Examples include(":examples") @@ -91,6 +92,8 @@ include(":examples:mcp-server") include(":mcp") include(":mcp:mcp-schemas") include(":mcp:mcp-server") +include(":mcp:mcp-cli") +include(":mcp:mcp-cli-api") include(":model-bundler:bundle-api") include(":model-bundler:bundle-cli") From 477184257bdb7a3f330ab1c51030ff8f1a465547 Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 6 May 2025 11:34:56 -0700 Subject: [PATCH 02/13] wip --- .../provider/AwsServiceBundle.java | 44 +++-- .../bundler/AwsServiceBundler.java | 4 - .../bundler/AwsServiceBundlerFactory.java | 21 --- examples/mcp-server/build.gradle.kts | 1 + .../server/mcp/BundleMCPServerExample.java | 7 +- mcp/mcp-bundle-api/build.gradle.kts | 19 ++ .../java/mcp/cli/commands/StartServer.java | 5 +- model-bundler/bundle-api/build.gradle.kts | 1 + .../smithy/modelbundle/api/BundlePlugin.java | 6 +- .../smithy/modelbundle/api/Bundles.java | 65 +++++++ .../modelbundle/api/PluginProviders.java | 2 +- server/server-proxy/build.gradle.kts | 3 +- .../smithy/java/server/ProxyService.java | 170 +++++++++--------- settings.gradle.kts | 3 + 14 files changed, 212 insertions(+), 139 deletions(-) delete mode 100644 aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerFactory.java create mode 100644 mcp/mcp-bundle-api/build.gradle.kts create mode 100644 model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java diff --git a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java index b0150cacb..891f83057 100644 --- a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java +++ b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java @@ -13,9 +13,13 @@ import software.amazon.smithy.java.aws.client.auth.scheme.sigv4.SigV4AuthScheme; import software.amazon.smithy.java.aws.client.core.settings.RegionSetting; import software.amazon.smithy.java.aws.sdkv2.auth.SdkCredentialsResolver; +import software.amazon.smithy.java.client.core.Client; +import software.amazon.smithy.java.client.core.ClientConfig; import software.amazon.smithy.java.client.core.RequestOverrideConfig; import software.amazon.smithy.java.client.core.auth.scheme.AuthScheme; import software.amazon.smithy.java.client.core.endpoint.EndpointResolver; +import software.amazon.smithy.java.client.core.interceptors.CallHook; +import software.amazon.smithy.java.client.core.interceptors.ClientInterceptor; import software.amazon.smithy.java.core.serde.document.Document; import software.amazon.smithy.modelbundle.api.BundlePlugin; import software.amazon.smithy.modelbundle.api.StaticAuthSchemeResolver; @@ -30,16 +34,34 @@ final class AwsServiceBundle implements BundlePlugin { } @Override - public RequestOverrideConfig.Builder buildOverride(Document args) { - var input = args.asShape(PreRequest.builder()); - var endpoint = URI.create(Objects.requireNonNull(serviceMetadata.getEndpoints().get(input.getAwsRegion()), - "no endpoint for region " + input.getAwsRegion())); - var identityResolver = new SdkCredentialsResolver(ProfileCredentialsProvider.create(input.getAwsProfileName())); - return RequestOverrideConfig.builder() - .putConfig(RegionSetting.REGION, input.getAwsRegion()) - .endpointResolver(EndpointResolver.staticEndpoint(endpoint)) - .addIdentityResolver(identityResolver) - .authSchemeResolver(StaticAuthSchemeResolver.getInstance()) - .putSupportedAuthSchemes(StaticAuthSchemeResolver.staticScheme(authScheme)); + public > B configureClient(B clientBuilder) { + clientBuilder.addInterceptor(new AwsServiceClientInterceptor(serviceMetadata)); + return clientBuilder; + } + + private record AwsServiceClientInterceptor(AwsServiceMetadata serviceMetadata) implements ClientInterceptor { + + @Override + public ClientConfig modifyBeforeCall(CallHook hook) { + if (!(hook.input() instanceof Document d)) { + throw new IllegalArgumentException("Input must be a Document"); + } + var input = d.asShape(PreRequest.builder()); + + var endpoint = URI.create(Objects.requireNonNull(serviceMetadata.getEndpoints().get(input.getAwsRegion()), + "no endpoint for region " + input.getAwsRegion())); + + try (var sdkCredentialsProvider = ProfileCredentialsProvider.create(input.getAwsProfileName())) { + var identityResolver = new SdkCredentialsResolver(sdkCredentialsProvider); + + return hook.config() + .withRequestOverride(RequestOverrideConfig.builder() + .putConfig(RegionSetting.REGION, input.getAwsRegion()) + .endpointResolver(EndpointResolver.staticEndpoint(endpoint)) + .addIdentityResolver(identityResolver) + .authSchemeResolver(StaticAuthSchemeResolver.getInstance()) + .build()); + } + } } } diff --git a/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java b/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java index 548a634eb..1f8c2c212 100644 --- a/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java +++ b/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java @@ -55,10 +55,6 @@ public final class AwsServiceBundler implements Bundler { private final ModelResolver resolver; private final String serviceName; - AwsServiceBundler(String... args) { - this(args[0].toLowerCase(Locale.ROOT), GithubModelResolver.INSTANCE); - } - AwsServiceBundler(String serviceName, ModelResolver resolver) { this.serviceName = serviceName; this.resolver = resolver; diff --git a/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerFactory.java b/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerFactory.java deleted file mode 100644 index 00558c73c..000000000 --- a/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerFactory.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.aws.servicebundle.bundler; - -import software.amazon.smithy.modelbundle.api.Bundler; -import software.amazon.smithy.modelbundle.api.BundlerFactory; - -public final class AwsServiceBundlerFactory implements BundlerFactory { - @Override - public String identifier() { - return "aws"; - } - - @Override - public Bundler newBundler(String... args) { - return new AwsServiceBundler(args); - } -} diff --git a/examples/mcp-server/build.gradle.kts b/examples/mcp-server/build.gradle.kts index 7362f2423..ec791d414 100644 --- a/examples/mcp-server/build.gradle.kts +++ b/examples/mcp-server/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { implementation("software.amazon.smithy.java:aws-client-restjson:$smithyJavaVersion") implementation("software.amazon.smithy.java:aws-client-awsjson:$smithyJavaVersion") implementation("software.amazon.smithy.java:aws-service-bundle:$smithyJavaVersion") + implementation("software.amazon.smithy.java:bundle-api:$smithyJavaVersion") } // Add generated Java files to the main sourceSet diff --git a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java index b403d9c03..4b6bd0da2 100644 --- a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java +++ b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java @@ -3,6 +3,7 @@ import software.amazon.smithy.java.json.JsonCodec; import software.amazon.smithy.java.server.ProxyService; import software.amazon.smithy.java.mcp.server.McpServer; +import software.amazon.smithy.modelbundle.api.Bundles; import software.amazon.smithy.modelbundle.api.model.Bundle; import java.util.Objects; @@ -10,13 +11,11 @@ public final class BundleMCPServerExample { public static void main(String[] args) throws Exception { try { + var bundle = loadBundle("dynamodb.json"); var mcpServer = McpServer.builder() .stdio() .name("smithy-mcp-server") - .addService( - ProxyService.builder() - .bundle(loadBundle("dynamodb.json")) - .build()) + .addService(Bundles.getProxyService(bundle)) .build(); mcpServer.start(); diff --git a/mcp/mcp-bundle-api/build.gradle.kts b/mcp/mcp-bundle-api/build.gradle.kts new file mode 100644 index 000000000..042bb9659 --- /dev/null +++ b/mcp/mcp-bundle-api/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("java") +} + +group = "software.amazon.smithy.java" +version = "0.0.2" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java index ddadae6cd..6ee99cf9b 100644 --- a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java @@ -19,8 +19,8 @@ import software.amazon.smithy.java.mcp.server.McpServer; import software.amazon.smithy.java.server.FilteredService; import software.amazon.smithy.java.server.OperationFilters; -import software.amazon.smithy.java.server.ProxyService; import software.amazon.smithy.java.server.Service; +import software.amazon.smithy.modelbundle.api.Bundles; /** * Command to start a Smithy MCP server exposing specified tool bundles. @@ -63,8 +63,7 @@ public void execute(Config config) { switch (toolBundleConfig.type()) { case smithyModeled -> { SmithyModeledToolBundleConfig bundleConfig = toolBundleConfig.getValue(); - Service service = - ProxyService.builder().bundle(convert(bundleConfig.getServiceDescriptor())).build(); + Service service = Bundles.getProxyService(convert(bundleConfig.getServiceDescriptor())); if (bundleConfig.hasAllowListedTools() || bundleConfig.hasBlockListedTools()) { var filter = OperationFilters.allowList(bundleConfig.getAllowListedTools()) .and(OperationFilters.blockList(bundleConfig.getBlockListedTools())); diff --git a/model-bundler/bundle-api/build.gradle.kts b/model-bundler/bundle-api/build.gradle.kts index 033a40e99..b070c9af6 100644 --- a/model-bundler/bundle-api/build.gradle.kts +++ b/model-bundler/bundle-api/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { api(project(":client:client-auth-api")) api(project(":client:client-core")) api(project(":dynamic-schemas")) + api(project(":server:server-proxy")) } afterEvaluate { diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java index cdc113d1b..67a8a9f5f 100644 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java +++ b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java @@ -5,8 +5,8 @@ package software.amazon.smithy.modelbundle.api; +import software.amazon.smithy.java.client.core.Client; import software.amazon.smithy.java.client.core.RequestOverrideConfig; -import software.amazon.smithy.java.core.serde.document.Document; /** * A BundlePlugin applies the settings specified in a {@link software.amazon.smithy.modelbundle.api.model.Bundle} @@ -15,9 +15,7 @@ public interface BundlePlugin { /** * Applies the bundle-specific settings to a client call. - * - * @param args per-request args specified by the bundle, or null if the bundle takes no per-request args * @return a {@link RequestOverrideConfig.Builder} with the settings from the bundle applied */ - RequestOverrideConfig.Builder buildOverride(Document args); + > B configureClient(B clientBuilder); } diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java new file mode 100644 index 000000000..90fcf4c3c --- /dev/null +++ b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java @@ -0,0 +1,65 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.modelbundle.api; + +import software.amazon.smithy.java.server.ProxyService; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.loader.ModelAssembler; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.modelbundle.api.model.Bundle; + +public class Bundles { + + private static final PluginProviders PLUGIN_PROVIDERS = PluginProviders.builder().build(); + + private Bundles() {} + + public static ProxyService getProxyService(Bundle bundle) { + var model = getModel(bundle); + var plugin = PLUGIN_PROVIDERS.getPlugin(bundle.getConfigType(), bundle.getConfig()); + return ProxyService.builder() + .model(model) + .clientConfigurator(plugin::configureClient) + .build(); + } + + private static Model getModel(Bundle bundle) { + var modelAssemble = new ModelAssembler().putProperty(ModelAssembler.ALLOW_UNKNOWN_TRAITS, true) + .addUnparsedModel("bundle.json", bundle.getModel().getValue()); + var args = bundle.getRequestArguments(); + if (args != null) { + modelAssemble.addUnparsedModel("args.smithy", bundle.getRequestArguments().getModel().getValue()); + var model = modelAssemble.assemble().unwrap(); + var template = model.expectShape(ShapeId.from(args.getIdentifier())).asStructureShape().get(); + var b = model.toBuilder(); + // mix in the generic arg members + for (var op : model.getOperationShapes()) { + var input = model.expectShape(op.getInput().get(), StructureShape.class).toBuilder(); + for (var member : template.members()) { + input.addMember(member.toBuilder() + .id(ShapeId.from(input.getId().toString() + "$" + member.getMemberName())) + .build()); + } + b.addShape(input.build()); + } + + for (var service : model.getServiceShapes()) { + b.addShape(service.toBuilder() + // trim the endpoint rules because they're huge and we don't need them + .removeTrait(ShapeId.from("smithy.rules#endpointRuleSet")) + .removeTrait(ShapeId.from("smithy.rules#endpointTests")) + .build()); + } + return b.build(); + } + return modelAssemble.assemble().unwrap(); + } + + public static BundlePlugin getBundlePlugin(Bundle bundle) { + return PLUGIN_PROVIDERS.getPlugin(bundle.getConfigType(), bundle.getConfig()); + } +} diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java index e3909d29f..35337c8a8 100644 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java +++ b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java @@ -16,7 +16,7 @@ private PluginProviders(Builder builder) { this.providers = builder.providers; } - public BundlePlugin getProvider(String identifier, Document input) { + public BundlePlugin getPlugin(String identifier, Document input) { var provider = providers.get(identifier); if (provider == null) { throw new NullPointerException("no auth provider named " + identifier); diff --git a/server/server-proxy/build.gradle.kts b/server/server-proxy/build.gradle.kts index 3fae7783d..6ff103163 100644 --- a/server/server-proxy/build.gradle.kts +++ b/server/server-proxy/build.gradle.kts @@ -13,12 +13,11 @@ dependencies { api(project(":core")) api(project(":context")) api(project(":framework-errors")) - api(project(":model-bundler:bundle-api")) + api(project(":client:dynamic-client")) implementation(libs.smithy.model) implementation(project(":io")) implementation(project(":logging")) implementation(project(":dynamic-schemas")) - implementation(project(":client:dynamic-client")) implementation(project(":client:client-core")) implementation(project(":aws:client:aws-client-http")) } diff --git a/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java b/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java index b88f1658e..0527b3764 100644 --- a/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java +++ b/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java @@ -5,21 +5,29 @@ package software.amazon.smithy.java.server; +import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.function.BiFunction; +import java.util.function.UnaryOperator; import software.amazon.smithy.java.auth.api.identity.Identity; import software.amazon.smithy.java.auth.api.identity.IdentityResolver; import software.amazon.smithy.java.aws.client.core.settings.RegionSetting; -import software.amazon.smithy.java.client.core.RequestOverrideConfig; +import software.amazon.smithy.java.client.core.ClientProtocol; +import software.amazon.smithy.java.client.core.MessageExchange; import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolver; +import software.amazon.smithy.java.client.core.endpoint.Endpoint; import software.amazon.smithy.java.client.core.endpoint.EndpointResolver; +import software.amazon.smithy.java.context.Context; import software.amazon.smithy.java.core.error.ModeledException; +import software.amazon.smithy.java.core.schema.ApiOperation; import software.amazon.smithy.java.core.schema.Schema; import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.serde.Codec; import software.amazon.smithy.java.core.serde.TypeRegistry; import software.amazon.smithy.java.core.serde.document.Document; import software.amazon.smithy.java.dynamicclient.DocumentException; @@ -29,90 +37,40 @@ import software.amazon.smithy.java.dynamicschemas.StructDocument; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.TopDownIndex; -import software.amazon.smithy.model.loader.ModelAssembler; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.ShapeType; -import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.ToShapeId; -import software.amazon.smithy.modelbundle.api.BundlePlugin; -import software.amazon.smithy.modelbundle.api.PluginProviders; -import software.amazon.smithy.modelbundle.api.model.Bundle; import software.amazon.smithy.utils.SmithyUnstableApi; @SmithyUnstableApi public final class ProxyService implements Service { - private static final PluginProviders PLUGIN_PROVIDERS = PluginProviders.builder().build(); private final DynamicClient dynamicClient; private final SchemaConverter schemaConverter; private final Map> operations; private final TypeRegistry serviceErrorRegistry; - private final Model model; private final List> allOperations; private final Schema schema; - private final BundlePlugin plugin; - - private static software.amazon.smithy.model.Model adapt(Builder builder) { - if (builder.bundle == null || builder.bundle.getRequestArguments() == null) { - return builder.model; - } - - var args = builder.bundle.getRequestArguments(); - var model = new ModelAssembler() - .addModel(builder.model) - .addUnparsedModel("args.smithy", args.getModel().getValue()) - .assemble() - .unwrap(); - var template = model.expectShape(ShapeId.from(args.getIdentifier())) - .asStructureShape() - .get(); - var b = model.toBuilder(); - - // mix in the generic arg members - for (var op : model.getOperationShapes()) { - var input = model.expectShape(op.getInput().get(), StructureShape.class).toBuilder(); - for (var member : template.members()) { - input.addMember(member.toBuilder() - .id(ShapeId.from(input.getId().toString() + "$" + member.getMemberName())) - .build()); - } - b.addShape(input.build()); - } - - for (var service : model.getServiceShapes()) { - b.addShape(service.toBuilder() - // trim the endpoint rules because they're huge and we don't need them - .removeTrait(ShapeId.from("smithy.rules#endpointRuleSet")) - .removeTrait(ShapeId.from("smithy.rules#endpointTests")) - .build()); - } - - return b.build(); - } private ProxyService(Builder builder) { - this.model = adapt(builder); + var model = builder.model; DynamicClient.Builder clientBuilder = DynamicClient.builder() .service(builder.service) - .model(model); - if (builder.bundle != null) { - this.plugin = PLUGIN_PROVIDERS.getProvider(builder.bundle.getConfigType(), builder.bundle.getConfig()); - clientBuilder.endpointResolver(EndpointResolver.staticEndpoint("http://placeholder")); - } else { - this.plugin = null; - // TODO: render this as a bundle - clientBuilder.endpointResolver(EndpointResolver.staticEndpoint(builder.proxyEndpoint)); - if (builder.identityResolver != null) { - clientBuilder.addIdentityResolver(builder.identityResolver); - } - if (builder.authScheme != null) { - clientBuilder.authSchemeResolver(builder.authScheme); - } - if (builder.region != null) { - clientBuilder.putConfig(RegionSetting.REGION, builder.region); - } + .model(builder.model); + clientBuilder.endpointResolver(EndpointResolver.staticEndpoint(builder.proxyEndpoint)); + if (builder.identityResolver != null) { + clientBuilder.addIdentityResolver(builder.identityResolver); + } + if (builder.authScheme != null) { + clientBuilder.authSchemeResolver(builder.authScheme); + } + if (builder.region != null) { + clientBuilder.putConfig(RegionSetting.REGION, builder.region); + } + if (builder.clientConfigurator != null) { + clientBuilder = (DynamicClient.Builder) builder.clientConfigurator.apply(clientBuilder); } this.dynamicClient = clientBuilder.build(); this.schemaConverter = new SchemaConverter(model); @@ -120,7 +78,7 @@ private ProxyService(Builder builder) { var registryBuilder = TypeRegistry.builder(); var service = model.expectShape(builder.service, ServiceShape.class); for (var e : service.getErrors()) { - registerError(e, builder.service, registryBuilder); + registerError(e, builder.service, registryBuilder, model); } this.serviceErrorRegistry = registryBuilder.build(); this.allOperations = new ArrayList<>(); @@ -132,8 +90,7 @@ private ProxyService(Builder builder) { schemaConverter, model, operation, - service, - plugin); + service); Operation serverOperation = Operation.of(operationName, function, @@ -142,7 +99,7 @@ private ProxyService(Builder builder) { model, service, serviceErrorRegistry, - (e, rb) -> registerError(e, builder.service, rb)), + (e, rb) -> registerError(e, builder.service, rb, model)), this); allOperations.add(serverOperation); operations.put(operationName, serverOperation); @@ -150,7 +107,7 @@ private ProxyService(Builder builder) { this.schema = schemaConverter.getSchema(service); } - private void registerError(ShapeId e, ShapeId serviceId, TypeRegistry.Builder registryBuilder) { + private void registerError(ShapeId e, ShapeId serviceId, TypeRegistry.Builder registryBuilder, Model model) { var error = model.expectShape(e); var errorSchema = schemaConverter.getSchema(error); registryBuilder.putType(e, @@ -191,7 +148,7 @@ public static final class Builder { private IdentityResolver identityResolver; private String proxyEndpoint; private AuthSchemeResolver authScheme; - private Bundle bundle; + private UnaryOperator clientConfigurator; private Builder() {} @@ -253,16 +210,8 @@ public Builder region(String region) { return this; } - public Builder bundle(Bundle bundle) { - this.bundle = bundle; - this.model = new ModelAssembler() - .putProperty(ModelAssembler.ALLOW_UNKNOWN_TRAITS, true) - .addUnparsedModel("bundle.json", bundle.getModel().getValue()) - // synthetic members may cause certain validations to fail, but it's ok for this application - .disableValidation() - .assemble() - .unwrap(); - this.service = ShapeId.from(bundle.getServiceName()); + public Builder clientConfigurator(UnaryOperator clientConfigurator) { + this.clientConfigurator = clientConfigurator; return this; } } @@ -273,17 +222,11 @@ private record DynamicFunction( SchemaConverter schemaConverter, Model model, OperationShape operationShape, - ServiceShape serviceShape, - BundlePlugin plugin) implements BiFunction { + ServiceShape serviceShape) implements BiFunction { @Override public StructDocument apply(StructDocument input, RequestContext requestContext) { - RequestOverrideConfig bundleSettings = null; - if (plugin != null) { - bundleSettings = plugin.buildOverride(input).build(); - } - return createStructDocument(operationShape.getOutput().get(), - dynamicClient.call(operation, input, bundleSettings)); + return createStructDocument(operationShape.getOutput().get(), dynamicClient.call(operation, input)); } private StructDocument createStructDocument(ToShapeId shape, Document value) { @@ -295,4 +238,53 @@ private StructDocument createStructDocument(ToShapeId shape, Document value) { } } + private static class NoOpClientProtocol implements ClientProtocol { + + private static final NoOpClientProtocol INSTANCE = new NoOpClientProtocol(); + + private NoOpClientProtocol() { + + } + + @Override + public ShapeId id() { + return null; + } + + @Override + public Codec payloadCodec() { + return null; + } + + @Override + public MessageExchange messageExchange() { + return null; + } + + @Override + public I createRequest( + ApiOperation operation, + I1 input, + Context context, + URI endpoint + ) { + return null; + } + + @Override + public I setServiceEndpoint(I request, Endpoint endpoint) { + return null; + } + + @Override + public CompletableFuture deserializeResponse( + ApiOperation operation, + Context context, + TypeRegistry errorRegistry, + I request, + O response + ) { + return null; + } + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index c40367c29..1cf1dda51 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -97,3 +97,6 @@ include(":mcp:mcp-cli-api") include(":model-bundler:bundle-api") include(":model-bundler:bundle-cli") + +include("coral-bundle-types") +include("mcp:mcp-bundle-api") \ No newline at end of file From c9b7f7613cb18a5e12bd116546cd507568655e88 Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 6 May 2025 14:22:50 -0700 Subject: [PATCH 03/13] Everything working --- .../cli/commands/AddAwsServiceToolBundle.java | 49 +++++++++---------- .../provider/AwsServiceBundle.java | 7 ++- mcp/mcp-cli-api/model/main.smithy | 37 +------------- .../smithy/java/mcp/cli/ConfigUtils.java | 4 ++ .../java/mcp/cli/commands/StartServer.java | 49 +++++++++---------- .../smithy/modelbundle/api/Bundles.java | 1 + .../smithy/java/server/ProxyService.java | 5 +- 7 files changed, 61 insertions(+), 91 deletions(-) diff --git a/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java index a23e6d0a6..9b90d6290 100644 --- a/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java +++ b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java @@ -10,9 +10,6 @@ import picocli.CommandLine.Option; import software.amazon.smithy.java.aws.servicebundle.bundler.AwsServiceBundler; import software.amazon.smithy.java.mcp.cli.AbstractAddToolBundle; -import software.amazon.smithy.java.mcp.cli.model.Bundle; -import software.amazon.smithy.java.mcp.cli.model.GenericArguments; -import software.amazon.smithy.java.mcp.cli.model.Model; import software.amazon.smithy.java.mcp.cli.model.SmithyModeledToolBundleConfig; import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig.SmithyModeledMember; @@ -38,32 +35,32 @@ protected SmithyModeledMember getNewToolConfig() { var bundle = new AwsServiceBundler(awsServiceName).bundle(); return new SmithyModeledMember(SmithyModeledToolBundleConfig.builder() .name(awsServiceName) - .serviceDescriptor(convert(bundle)) + .serviceDescriptor(bundle) .build()); } - private static Bundle convert(software.amazon.smithy.modelbundle.api.model.Bundle bundle) { - return Bundle.builder() - .config(bundle.getConfig()) - .configType(bundle.getConfigType()) - .serviceName(bundle.getServiceName()) - .model(convert(bundle.getModel())) - .requestArguments(convert(bundle.getRequestArguments())) - .build(); - } - - private static GenericArguments convert( - software.amazon.smithy.modelbundle.api.model.GenericArguments genericArguments - ) { - return GenericArguments.builder() - .model(convert(genericArguments.getModel())) - .identifier(genericArguments.getIdentifier()) - .build(); - } - - private static Model convert(software.amazon.smithy.modelbundle.api.model.Model model) { - return Model.builder().smithyModel(model.getValue()).build(); - } + // private static Bundle convert(software.amazon.smithy.modelbundle.api.model.Bundle bundle) { + // return Bundle.builder() + // .config(bundle.getConfig()) + // .configType(bundle.getConfigType()) + // .serviceName(bundle.getServiceName()) + // .model(convert(bundle.getModel())) + // .requestArguments(convert(bundle.getRequestArguments())) + // .build(); + // } + // + // private static GenericArguments convert( + // software.amazon.smithy.modelbundle.api.model.GenericArguments genericArguments + // ) { + // return GenericArguments.builder() + // .model(convert(genericArguments.getModel())) + // .identifier(genericArguments.getIdentifier()) + // .build(); + // } + // + // private static Model convert(software.amazon.smithy.modelbundle.api.model.Model model) { + // return Model.builder().smithyModel(model.getValue()).build(); + // } @Override protected String getToolBundleName() { diff --git a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java index 891f83057..f34b232d3 100644 --- a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java +++ b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java @@ -35,11 +35,13 @@ final class AwsServiceBundle implements BundlePlugin { @Override public > B configureClient(B clientBuilder) { - clientBuilder.addInterceptor(new AwsServiceClientInterceptor(serviceMetadata)); + clientBuilder.addInterceptor(new AwsServiceClientInterceptor(serviceMetadata, authScheme)); + clientBuilder.endpointResolver(EndpointResolver.staticEndpoint("http://dummyurl.com")); return clientBuilder; } - private record AwsServiceClientInterceptor(AwsServiceMetadata serviceMetadata) implements ClientInterceptor { + private record AwsServiceClientInterceptor(AwsServiceMetadata serviceMetadata, AuthScheme authScheme) + implements ClientInterceptor { @Override public ClientConfig modifyBeforeCall(CallHook hook) { @@ -60,6 +62,7 @@ public ClientConfig modifyBeforeCall(CallHook hook) { .endpointResolver(EndpointResolver.staticEndpoint(endpoint)) .addIdentityResolver(identityResolver) .authSchemeResolver(StaticAuthSchemeResolver.getInstance()) + .putSupportedAuthSchemes(StaticAuthSchemeResolver.staticScheme(authScheme)) .build()); } } diff --git a/mcp/mcp-cli-api/model/main.smithy b/mcp/mcp-cli-api/model/main.smithy index 38a221716..563f5c046 100644 --- a/mcp/mcp-cli-api/model/main.smithy +++ b/mcp/mcp-cli-api/model/main.smithy @@ -2,6 +2,8 @@ $version: "2" namespace smithy.mcp.cli +use software.amazon.smithy.modelbundle.api#Bundle + structure Config { toolBundles: ToolBundleConfigs } @@ -59,38 +61,3 @@ list FilePaths { string FilePath string ToolName - -// TODO fix this once external type Schemas are fixed -structure Bundle { - /// unique identifier for the configuration type. used to resolve the appropriate Bundler. - @required - configType: String - - /// fully-qualified ShapeId of the service - @required - serviceName: String - - /// Bundle-specific configuration. If this bundle does not require configuration, this - /// field may be omitted. - config: Document - - /// model that describes the service. The service given in `serviceName` must be present. - @required - model: Model - - /// model describing the generic arguments that must be present in every request. If this - /// bundle does not require generic arguments, this field may be omitted. - requestArguments: GenericArguments -} - -union Model { - smithyModel: String -} - -structure GenericArguments { - @required - identifier: String - - @required - model: Model -} 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 c3a631d08..79be7e735 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 @@ -124,4 +124,8 @@ public static void addToolConfig(Config existingConfig, String name, ToolBundleC var newConfig = existingConfig.toBuilder().toolBundles(existingToolBundles).build(); updateConfig(newConfig); } + + public static void main(String[] args) throws IOException { + System.out.println(loadOrCreateConfig()); + } } diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java index 6ee99cf9b..bed6014bf 100644 --- a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java @@ -10,10 +10,7 @@ import picocli.CommandLine.Command; import picocli.CommandLine.Parameters; import software.amazon.smithy.java.mcp.cli.SmithyMcpCommand; -import software.amazon.smithy.java.mcp.cli.model.Bundle; import software.amazon.smithy.java.mcp.cli.model.Config; -import software.amazon.smithy.java.mcp.cli.model.GenericArguments; -import software.amazon.smithy.java.mcp.cli.model.Model; import software.amazon.smithy.java.mcp.cli.model.SmithyModeledToolBundleConfig; import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig; import software.amazon.smithy.java.mcp.server.McpServer; @@ -63,7 +60,7 @@ public void execute(Config config) { switch (toolBundleConfig.type()) { case smithyModeled -> { SmithyModeledToolBundleConfig bundleConfig = toolBundleConfig.getValue(); - Service service = Bundles.getProxyService(convert(bundleConfig.getServiceDescriptor())); + Service service = Bundles.getProxyService(bundleConfig.getServiceDescriptor()); if (bundleConfig.hasAllowListedTools() || bundleConfig.hasBlockListedTools()) { var filter = OperationFilters.allowList(bundleConfig.getAllowListedTools()) .and(OperationFilters.blockList(bundleConfig.getBlockListedTools())); @@ -85,26 +82,26 @@ public void execute(Config config) { } //TODO remove these after external types Schema if fixed. - private static software.amazon.smithy.modelbundle.api.model.Bundle convert(Bundle bundle) { - return software.amazon.smithy.modelbundle.api.model.Bundle.builder() - .config(bundle.getConfig()) - .configType(bundle.getConfigType()) - .serviceName(bundle.getServiceName()) - .model(convert(bundle.getModel())) - .requestArguments(convert(bundle.getRequestArguments())) - .build(); - } - - private static software.amazon.smithy.modelbundle.api.model.GenericArguments convert( - GenericArguments genericArguments - ) { - return software.amazon.smithy.modelbundle.api.model.GenericArguments.builder() - .model(convert(genericArguments.getModel())) - .identifier(genericArguments.getIdentifier()) - .build(); - } - - private static software.amazon.smithy.modelbundle.api.model.Model convert(Model model) { - return software.amazon.smithy.modelbundle.api.model.Model.builder().smithyModel(model.getValue()).build(); - } + // private static software.amazon.smithy.modelbundle.api.model.Bundle convert(Bundle bundle) { + // return software.amazon.smithy.modelbundle.api.model.Bundle.builder() + // .config(bundle.getConfig()) + // .configType(bundle.getConfigType()) + // .serviceName(bundle.getServiceName()) + // .model(convert(bundle.getModel())) + // .requestArguments(convert(bundle.getRequestArguments())) + // .build(); + // } + // + // private static software.amazon.smithy.modelbundle.api.model.GenericArguments convert( + // GenericArguments genericArguments + // ) { + // return software.amazon.smithy.modelbundle.api.model.GenericArguments.builder() + // .model(convert(genericArguments.getModel())) + // .identifier(genericArguments.getIdentifier()) + // .build(); + // } + // + // private static software.amazon.smithy.modelbundle.api.model.Model convert(Model model) { + // return software.amazon.smithy.modelbundle.api.model.Model.builder().smithyModel(model.getValue()).build(); + // } } diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java index 90fcf4c3c..f7bdc05bb 100644 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java +++ b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java @@ -24,6 +24,7 @@ public static ProxyService getProxyService(Bundle bundle) { return ProxyService.builder() .model(model) .clientConfigurator(plugin::configureClient) + .service(ShapeId.from(bundle.getServiceName())) .build(); } diff --git a/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java b/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java index 0527b3764..5e218a46f 100644 --- a/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java +++ b/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java @@ -59,7 +59,6 @@ private ProxyService(Builder builder) { DynamicClient.Builder clientBuilder = DynamicClient.builder() .service(builder.service) .model(builder.model); - clientBuilder.endpointResolver(EndpointResolver.staticEndpoint(builder.proxyEndpoint)); if (builder.identityResolver != null) { clientBuilder.addIdentityResolver(builder.identityResolver); } @@ -70,7 +69,9 @@ private ProxyService(Builder builder) { clientBuilder.putConfig(RegionSetting.REGION, builder.region); } if (builder.clientConfigurator != null) { - clientBuilder = (DynamicClient.Builder) builder.clientConfigurator.apply(clientBuilder); + clientBuilder = builder.clientConfigurator.apply(clientBuilder); + } else { + clientBuilder.endpointResolver(EndpointResolver.staticEndpoint(builder.proxyEndpoint)); } this.dynamicClient = clientBuilder.build(); this.schemaConverter = new SchemaConverter(model); From 2970e563360f67befb3bfc74d1c9abfebbf4fb26 Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 6 May 2025 17:48:07 -0700 Subject: [PATCH 04/13] Adding Registry and list and add CLIs --- aws/aws-mcp-cli-commands/build.gradle.kts | 2 +- .../mcp/cli/commands/AddAwsServiceBundle.java | 67 +++++++++++ .../cli/commands/AddAwsServiceToolBundle.java | 84 -------------- ...n.smithy.java.mcp.cli.ConfigurationCommand | 2 +- aws/aws-service-bundle/build.gradle.kts | 2 +- .../provider/AwsServiceBundle.java | 4 +- .../AwsServiceBundlePluginFactory.java | 4 +- ...smithy.mcp.bundle.api.BundlePluginFactory} | 0 aws/aws-service-bundler/build.gradle.kts | 2 +- .../bundler/AwsServiceBundler.java | 43 +++---- ...azon.smithy.mcp.bundle.api.BundlerFactory} | 0 .../bundler/AwsServiceBundlerTest.java | 10 +- .../codegen/types/JavaTypeCodegenPlugin.java | 2 - examples/mcp-server/build.gradle.kts | 2 +- .../server/mcp/BundleMCPServerExample.java | 7 +- mcp/mcp-bundle-api/build.gradle.kts | 54 +++++++-- mcp/mcp-bundle-api/license.txt | 4 + mcp/mcp-bundle-api/smithy-build.json | 9 ++ .../smithy/mcp/bundle/api/BundlePlugin.java | 21 ++++ .../mcp/bundle/api/BundlePluginFactory.java | 14 +++ .../amazon/smithy/mcp/bundle/api/Bundler.java | 32 ++++++ .../amazon/smithy/mcp/bundle/api/Bundles.java | 68 +++++++++++ .../mcp/bundle/api/PluginProviders.java | 59 ++++++++++ .../mcp/bundle/api/ServiceLoaderLoader.java | 21 ++++ .../bundle/api/StaticAuthSchemeResolver.java | 78 +++++++++++++ .../resources/META-INF/smithy/bundle.smithy | 49 ++++++++ .../main/resources/META-INF/smithy/manifest | 1 + mcp/mcp-cli-api/build.gradle.kts | 2 +- mcp/mcp-cli-api/model/main.smithy | 55 ++++----- ...ToolBundle.java => AbstractAddBundle.java} | 13 ++- .../amazon/smithy/java/mcp/cli/CliBundle.java | 14 +++ .../smithy/java/mcp/cli/ConfigUtils.java | 108 +++++++++++------- .../java/mcp/cli/ConfigurationCommand.java | 2 +- .../java/mcp/cli/DefaultConfigProvider.java | 14 +++ .../mcp/cli/EmptyDefaultConfigProvider.java | 21 ++++ .../amazon/smithy/java/mcp/cli/Registry.java | 20 ++++ .../smithy/java/mcp/cli/RegistryUtils.java | 31 +++++ .../smithy/java/mcp/cli/SmithyMcpCommand.java | 2 +- .../java/mcp/cli/commands/AddBundle.java | 32 ++++++ ...hyToolBundle.java => AddSmithyBundle.java} | 10 +- .../java/mcp/cli/commands/Configure.java | 4 +- .../java/mcp/cli/commands/ListBundles.java | 34 ++++++ .../java/mcp/cli/commands/StartServer.java | 14 ++- mcp/mcp-server/build.gradle.kts | 2 +- .../smithy/modelbundle/api/Bundles.java | 6 +- settings.gradle.kts | 4 +- 46 files changed, 795 insertions(+), 234 deletions(-) create mode 100644 aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java delete mode 100644 aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java rename aws/aws-service-bundle/src/main/resources/META-INF/services/{software.amazon.smithy.modelbundle.api.BundlePluginFactory => software.amazon.smithy.mcp.bundle.api.BundlePluginFactory} (100%) rename aws/aws-service-bundler/src/main/resources/META-INF/services/{software.amazon.smithy.modelbundle.api.BundlerFactory => software.amazon.smithy.mcp.bundle.api.BundlerFactory} (100%) create mode 100644 mcp/mcp-bundle-api/license.txt create mode 100644 mcp/mcp-bundle-api/smithy-build.json create mode 100644 mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePlugin.java create mode 100644 mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePluginFactory.java create mode 100644 mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundler.java create mode 100644 mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java create mode 100644 mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/PluginProviders.java create mode 100644 mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/ServiceLoaderLoader.java create mode 100644 mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/StaticAuthSchemeResolver.java create mode 100644 mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/bundle.smithy create mode 100644 mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/manifest rename mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/{AbstractAddToolBundle.java => AbstractAddBundle.java} (81%) create mode 100644 mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/CliBundle.java create mode 100644 mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/DefaultConfigProvider.java create mode 100644 mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/EmptyDefaultConfigProvider.java create mode 100644 mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/Registry.java create mode 100644 mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/RegistryUtils.java create mode 100644 mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddBundle.java rename mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/{AddSmithyToolBundle.java => AddSmithyBundle.java} (78%) create mode 100644 mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/ListBundles.java diff --git a/aws/aws-mcp-cli-commands/build.gradle.kts b/aws/aws-mcp-cli-commands/build.gradle.kts index 4b20cb98a..265d78a59 100644 --- a/aws/aws-mcp-cli-commands/build.gradle.kts +++ b/aws/aws-mcp-cli-commands/build.gradle.kts @@ -8,7 +8,7 @@ extra["displayName"] = "Smithy :: Java :: AWS :: Service Bundler" extra["moduleName"] = "software.amazon.smithy.java.aws.mcp.cli.commands" dependencies { - implementation(project(":model-bundler:bundle-api")) + implementation(project(":mcp:mcp-bundle-api")) implementation(libs.smithy.model) implementation(libs.picocli) implementation(project(":aws:aws-mcp-types")) diff --git a/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java new file mode 100644 index 000000000..9940ce555 --- /dev/null +++ b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java @@ -0,0 +1,67 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.aws.mcp.cli.commands; + +import java.util.Set; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import software.amazon.smithy.java.aws.servicebundle.bundler.AwsServiceBundler; +import software.amazon.smithy.java.mcp.cli.AbstractAddBundle; +import software.amazon.smithy.java.mcp.cli.CliBundle; +import software.amazon.smithy.java.mcp.cli.model.Location; +import software.amazon.smithy.java.mcp.cli.model.McpBundleConfig; +import software.amazon.smithy.java.mcp.cli.model.SmithyModeledBundleConfig; + +@Command(name = "add-aws-bundle") +public class AddAwsServiceBundle extends AbstractAddBundle { + + @Option(names = "--overwrite", + description = "Overwrite existing config", + defaultValue = "false") + protected boolean overwrite; + + @Option(names = {"-n", "--name"}, description = "Name of the AWS Service.", required = true) + protected String awsServiceName; + + @Option(names = {"-a", "--allowed-apis"}, description = "List of APIs to expose in the MCP server") + protected Set allowedApis; + + @Option(names = {"-b", "--blocked-apis"}, description = "List of APIs to hide in the MCP server") + protected Set blockedApis; + + @Override + protected CliBundle getNewToolConfig() { + var bundle = new AwsServiceBundler(awsServiceName).bundle(); + + var bundleConfig = McpBundleConfig.builder() + .smithyModeled(SmithyModeledBundleConfig.builder() + .name(awsServiceName) + .bundleLocation(Location.builder().fileLocation(getBundleFileLocation().toString()).build()) + .build()) + .build(); + return new CliBundle(bundle, bundleConfig); + } + + @Override + protected String getToolBundleName() { + return awsServiceName; + } + + @Override + protected boolean canOverwrite() { + return overwrite; + } + + @Override + protected Set allowedTools() { + return allowedApis; + } + + @Override + protected Set blockedTools() { + return blockedApis; + } +} diff --git a/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java deleted file mode 100644 index 9b90d6290..000000000 --- a/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceToolBundle.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.aws.mcp.cli.commands; - -import java.util.Set; -import picocli.CommandLine.Command; -import picocli.CommandLine.Option; -import software.amazon.smithy.java.aws.servicebundle.bundler.AwsServiceBundler; -import software.amazon.smithy.java.mcp.cli.AbstractAddToolBundle; -import software.amazon.smithy.java.mcp.cli.model.SmithyModeledToolBundleConfig; -import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig.SmithyModeledMember; - -@Command(name = "add-aws-service-tool-bundle") -public class AddAwsServiceToolBundle extends AbstractAddToolBundle { - - @Option(names = "--overwrite", - description = "Overwrite existing config", - defaultValue = "false") - protected boolean overwrite; - - @Option(names = {"-n", "--name"}, description = "Name of the AWS Service.", required = true) - protected String awsServiceName; - - @Option(names = {"-a", "--allowed-apis"}, description = "List of APIs to expose in the MCP server") - protected Set allowedApis; - - @Option(names = {"-b", "--blocked-apis"}, description = "List of APIs to hide in the MCP server") - protected Set blockedApis; - - @Override - protected SmithyModeledMember getNewToolConfig() { - var bundle = new AwsServiceBundler(awsServiceName).bundle(); - return new SmithyModeledMember(SmithyModeledToolBundleConfig.builder() - .name(awsServiceName) - .serviceDescriptor(bundle) - .build()); - } - - // private static Bundle convert(software.amazon.smithy.modelbundle.api.model.Bundle bundle) { - // return Bundle.builder() - // .config(bundle.getConfig()) - // .configType(bundle.getConfigType()) - // .serviceName(bundle.getServiceName()) - // .model(convert(bundle.getModel())) - // .requestArguments(convert(bundle.getRequestArguments())) - // .build(); - // } - // - // private static GenericArguments convert( - // software.amazon.smithy.modelbundle.api.model.GenericArguments genericArguments - // ) { - // return GenericArguments.builder() - // .model(convert(genericArguments.getModel())) - // .identifier(genericArguments.getIdentifier()) - // .build(); - // } - // - // private static Model convert(software.amazon.smithy.modelbundle.api.model.Model model) { - // return Model.builder().smithyModel(model.getValue()).build(); - // } - - @Override - protected String getToolBundleName() { - return awsServiceName; - } - - @Override - protected boolean canOverwrite() { - return overwrite; - } - - @Override - protected Set allowedTools() { - return allowedApis; - } - - @Override - protected Set blockedTools() { - return blockedApis; - } -} diff --git a/aws/aws-mcp-cli-commands/src/main/resources/META-INF/services/software.amazon.smithy.java.mcp.cli.ConfigurationCommand b/aws/aws-mcp-cli-commands/src/main/resources/META-INF/services/software.amazon.smithy.java.mcp.cli.ConfigurationCommand index 1986e7c55..6f9e77d75 100644 --- a/aws/aws-mcp-cli-commands/src/main/resources/META-INF/services/software.amazon.smithy.java.mcp.cli.ConfigurationCommand +++ b/aws/aws-mcp-cli-commands/src/main/resources/META-INF/services/software.amazon.smithy.java.mcp.cli.ConfigurationCommand @@ -1 +1 @@ -software.amazon.smithy.java.aws.mcp.cli.commands.AddAwsServiceToolBundle \ No newline at end of file +software.amazon.smithy.java.aws.mcp.cli.commands.AddAwsServiceBundle \ No newline at end of file diff --git a/aws/aws-service-bundle/build.gradle.kts b/aws/aws-service-bundle/build.gradle.kts index d812c8be8..16fc4a7dd 100644 --- a/aws/aws-service-bundle/build.gradle.kts +++ b/aws/aws-service-bundle/build.gradle.kts @@ -13,7 +13,7 @@ dependencies { api(project(":auth-api")) implementation(project(":aws:aws-sigv4")) implementation(project(":aws:client:aws-client-core")) - implementation(project(":model-bundler:bundle-api")) + implementation(project(":mcp:mcp-bundle-api")) implementation(project(":aws:sdkv2:aws-sdkv2-auth")) implementation(libs.aws.sdk.auth) } diff --git a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java index f34b232d3..a590cc49c 100644 --- a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java +++ b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java @@ -21,8 +21,8 @@ import software.amazon.smithy.java.client.core.interceptors.CallHook; import software.amazon.smithy.java.client.core.interceptors.ClientInterceptor; import software.amazon.smithy.java.core.serde.document.Document; -import software.amazon.smithy.modelbundle.api.BundlePlugin; -import software.amazon.smithy.modelbundle.api.StaticAuthSchemeResolver; +import software.amazon.smithy.mcp.bundle.api.BundlePlugin; +import software.amazon.smithy.mcp.bundle.api.StaticAuthSchemeResolver; final class AwsServiceBundle implements BundlePlugin { private final AwsServiceMetadata serviceMetadata; diff --git a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundlePluginFactory.java b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundlePluginFactory.java index dce5113c8..706f3461f 100644 --- a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundlePluginFactory.java +++ b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundlePluginFactory.java @@ -7,8 +7,8 @@ import software.amazon.smithy.awsmcp.model.AwsServiceMetadata; import software.amazon.smithy.java.core.serde.document.Document; -import software.amazon.smithy.modelbundle.api.BundlePlugin; -import software.amazon.smithy.modelbundle.api.BundlePluginFactory; +import software.amazon.smithy.mcp.bundle.api.BundlePlugin; +import software.amazon.smithy.mcp.bundle.api.BundlePluginFactory; public final class AwsServiceBundlePluginFactory implements BundlePluginFactory { public AwsServiceBundlePluginFactory() { diff --git a/aws/aws-service-bundle/src/main/resources/META-INF/services/software.amazon.smithy.modelbundle.api.BundlePluginFactory b/aws/aws-service-bundle/src/main/resources/META-INF/services/software.amazon.smithy.mcp.bundle.api.BundlePluginFactory similarity index 100% rename from aws/aws-service-bundle/src/main/resources/META-INF/services/software.amazon.smithy.modelbundle.api.BundlePluginFactory rename to aws/aws-service-bundle/src/main/resources/META-INF/services/software.amazon.smithy.mcp.bundle.api.BundlePluginFactory diff --git a/aws/aws-service-bundler/build.gradle.kts b/aws/aws-service-bundler/build.gradle.kts index 031156a34..86337d0f2 100644 --- a/aws/aws-service-bundler/build.gradle.kts +++ b/aws/aws-service-bundler/build.gradle.kts @@ -8,7 +8,7 @@ extra["displayName"] = "Smithy :: Java :: AWS :: Service Bundler" extra["moduleName"] = "software.amazon.smithy.java.aws.servicebundle.bundler" dependencies { - implementation(project(":model-bundler:bundle-api")) + implementation(project(":mcp:mcp-bundle-api")) implementation(libs.smithy.model) implementation(project(":aws:aws-mcp-types")) // we need to be able to resolve the sigv4 and protocol traits diff --git a/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java b/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java index 1f8c2c212..f506d2e1e 100644 --- a/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java +++ b/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java @@ -17,11 +17,14 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; -import java.util.stream.Collectors; import software.amazon.smithy.aws.traits.auth.SigV4Trait; import software.amazon.smithy.awsmcp.model.AwsServiceMetadata; import software.amazon.smithy.awsmcp.model.PreRequest; import software.amazon.smithy.java.core.serde.document.Document; +import software.amazon.smithy.mcp.bundle.api.Bundler; +import software.amazon.smithy.mcp.bundle.api.model.AdditionalInput; +import software.amazon.smithy.mcp.bundle.api.model.Bundle; +import software.amazon.smithy.mcp.bundle.api.model.SmithyBundle; import software.amazon.smithy.model.loader.ModelAssembler; import software.amazon.smithy.model.node.BooleanNode; import software.amazon.smithy.model.node.Node; @@ -29,16 +32,13 @@ import software.amazon.smithy.model.shapes.ModelSerializer; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.EndpointTrait; -import software.amazon.smithy.modelbundle.api.Bundler; -import software.amazon.smithy.modelbundle.api.model.Bundle; -import software.amazon.smithy.modelbundle.api.model.GenericArguments; -import software.amazon.smithy.modelbundle.api.model.Model; -public final class AwsServiceBundler implements Bundler { +public final class AwsServiceBundler extends Bundler { private static final ShapeId ENDPOINT_TESTS = ShapeId.from("smithy.rules#endpointTests"); // visible for testing static final Map GH_URIS_BY_SERVICE = new HashMap<>(); + static { // line is in the form fooService/service/version/fooService.json try (var models = new BufferedReader(new InputStreamReader( @@ -93,16 +93,14 @@ public Bundle bundle() { } } return Bundle.builder() - .config(Document.of(bundle.build())) - .configType("aws") - .serviceName(model.getServiceShapes().iterator().next().getId().toString()) - .model(Model.builder() - .smithyModel(serializeModel(model)) - .build()) - .requestArguments(GenericArguments.builder() - .identifier(PreRequest.$ID.toString()) - .model(Model.builder() - .smithyModel(loadModel("/META-INF/smithy/bundle.smithy")) + .smithyBundle(SmithyBundle.builder() + .config(Document.of(bundle.build())) + .configType("aws") + .serviceName(model.getServiceShapes().iterator().next().getId().toString()) + .model(serializeModel(model)) + .additionalInput(AdditionalInput.builder() + .identifier(PreRequest.$ID.toString()) + .model(loadModel("/META-INF/smithy/bundle.smithy")) .build()) .build()) .build(); @@ -112,20 +110,9 @@ public Bundle bundle() { } private static String serializeModel(software.amazon.smithy.model.Model model) { - var output = ObjectNode.printJson(ModelSerializer.builder() + return ObjectNode.printJson(ModelSerializer.builder() .build() .serialize(model)); - return output; - } - - private static String loadModel(String path) { - try (var reader = new BufferedReader(new InputStreamReader( - Objects.requireNonNull(AwsServiceBundler.class.getResourceAsStream(path)), - StandardCharsets.UTF_8))) { - return reader.lines().collect(Collectors.joining("\n")); - } catch (Exception e) { - throw new RuntimeException(e); - } } Map parseEndpoints(ObjectNode endpointTests) { diff --git a/aws/aws-service-bundler/src/main/resources/META-INF/services/software.amazon.smithy.modelbundle.api.BundlerFactory b/aws/aws-service-bundler/src/main/resources/META-INF/services/software.amazon.smithy.mcp.bundle.api.BundlerFactory similarity index 100% rename from aws/aws-service-bundler/src/main/resources/META-INF/services/software.amazon.smithy.modelbundle.api.BundlerFactory rename to aws/aws-service-bundler/src/main/resources/META-INF/services/software.amazon.smithy.mcp.bundle.api.BundlerFactory diff --git a/aws/aws-service-bundler/src/test/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerTest.java b/aws/aws-service-bundler/src/test/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerTest.java index 5ed64aee1..da39fb7bd 100644 --- a/aws/aws-service-bundler/src/test/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerTest.java +++ b/aws/aws-service-bundler/src/test/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerTest.java @@ -12,17 +12,19 @@ import java.util.Objects; import org.junit.jupiter.api.Test; import software.amazon.smithy.awsmcp.model.AwsServiceMetadata; +import software.amazon.smithy.mcp.bundle.api.model.SmithyBundle; public class AwsServiceBundlerTest { @Test public void accessAnalyzer() { var bundler = new AwsServiceBundler("accessanalyzer-2019-11-01.json", AwsServiceBundlerTest::getModel); - var bundle = bundler.bundle().getConfig().asShape(AwsServiceMetadata.builder()); + SmithyBundle bundle = bundler.bundle().getValue(); + var config = bundle.getConfig().asShape(AwsServiceMetadata.builder()); - assertEquals("access-analyzer", bundle.getSigv4SigningName()); - assertEquals("AccessAnalyzer", bundle.getServiceName()); + assertEquals("access-analyzer", config.getSigv4SigningName()); + assertEquals("AccessAnalyzer", config.getServiceName()); - assertNotEquals(0, bundle.getEndpoints().size()); + assertNotEquals(0, config.getEndpoints().size()); } private static String getModel(String path) { diff --git a/codegen/plugins/types-codegen/src/main/java/software/amazon/smithy/java/codegen/types/JavaTypeCodegenPlugin.java b/codegen/plugins/types-codegen/src/main/java/software/amazon/smithy/java/codegen/types/JavaTypeCodegenPlugin.java index 44a15bf8b..c012ce6cb 100644 --- a/codegen/plugins/types-codegen/src/main/java/software/amazon/smithy/java/codegen/types/JavaTypeCodegenPlugin.java +++ b/codegen/plugins/types-codegen/src/main/java/software/amazon/smithy/java/codegen/types/JavaTypeCodegenPlugin.java @@ -85,9 +85,7 @@ private static Set getClosure(Model model, TypeCodegenSettings settings) .filter(nested::contains) .collect(Collectors.toSet())); } - System.out.println(nested); closure.removeAll(nested); - System.out.println(closure); if (closure.isEmpty()) { throw new CodegenException("Could not generate types. No shapes found in closure"); } diff --git a/examples/mcp-server/build.gradle.kts b/examples/mcp-server/build.gradle.kts index ec791d414..6c883cbda 100644 --- a/examples/mcp-server/build.gradle.kts +++ b/examples/mcp-server/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { implementation("software.amazon.smithy.java:aws-client-restjson:$smithyJavaVersion") implementation("software.amazon.smithy.java:aws-client-awsjson:$smithyJavaVersion") implementation("software.amazon.smithy.java:aws-service-bundle:$smithyJavaVersion") - implementation("software.amazon.smithy.java:bundle-api:$smithyJavaVersion") + implementation("software.amazon.smithy.java:mcp-bundle-api:$smithyJavaVersion") } // Add generated Java files to the main sourceSet diff --git a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java index 4b6bd0da2..2fd6bf690 100644 --- a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java +++ b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java @@ -1,10 +1,9 @@ package software.amazon.smithy.java.example.server.mcp; import software.amazon.smithy.java.json.JsonCodec; -import software.amazon.smithy.java.server.ProxyService; import software.amazon.smithy.java.mcp.server.McpServer; -import software.amazon.smithy.modelbundle.api.Bundles; -import software.amazon.smithy.modelbundle.api.model.Bundle; +import software.amazon.smithy.mcp.bundle.api.model.Bundle; +import software.amazon.smithy.mcp.bundle.api.Bundles; import java.util.Objects; @@ -15,7 +14,7 @@ public static void main(String[] args) throws Exception { var mcpServer = McpServer.builder() .stdio() .name("smithy-mcp-server") - .addService(Bundles.getProxyService(bundle)) + .addService(Bundles.getService(bundle.getValue())) .build(); mcpServer.start(); diff --git a/mcp/mcp-bundle-api/build.gradle.kts b/mcp/mcp-bundle-api/build.gradle.kts index 042bb9659..bee4546de 100644 --- a/mcp/mcp-bundle-api/build.gradle.kts +++ b/mcp/mcp-bundle-api/build.gradle.kts @@ -1,19 +1,51 @@ plugins { - id("java") + application + id("smithy-java.module-conventions") + id("software.amazon.smithy.gradle.smithy-base") } -group = "software.amazon.smithy.java" -version = "0.0.2" +description = "This module implements the mcp-bundler utility" -repositories { - mavenCentral() -} +extra["displayName"] = "Smithy :: Java :: MCP Bundler" +extra["moduleName"] = "software.amazon.smithy.java.mcp.bundle.api" dependencies { - testImplementation(platform("org.junit:junit-bom:5.10.0")) - testImplementation("org.junit.jupiter:junit-jupiter") + smithyBuild(project(":codegen:plugins:types-codegen")) + + implementation(project(":core")) + implementation(libs.smithy.model) + api(project(":client:client-auth-api")) + api(project(":server:server-api")) + api(project(":client:client-core")) + api(project(":dynamic-schemas")) + implementation(project(":server:server-proxy")) } -tasks.test { - useJUnitPlatform() -} \ No newline at end of file +afterEvaluate { + val typePath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-type-codegen") + sourceSets { + main { + java { + srcDir(typePath) + include("software/**") + } + resources { + srcDir(typePath) + include("META-INF/**") + } + } + } +} + +tasks.named("compileJava") { + dependsOn("smithyBuild") +} + +// Needed because sources-jar needs to run after smithy-build is done +tasks.sourcesJar { + mustRunAfter("compileJava") +} + +tasks.processResources { + dependsOn("compileJava") +} diff --git a/mcp/mcp-bundle-api/license.txt b/mcp/mcp-bundle-api/license.txt new file mode 100644 index 000000000..5f97ab495 --- /dev/null +++ b/mcp/mcp-bundle-api/license.txt @@ -0,0 +1,4 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ \ No newline at end of file diff --git a/mcp/mcp-bundle-api/smithy-build.json b/mcp/mcp-bundle-api/smithy-build.json new file mode 100644 index 000000000..357bce589 --- /dev/null +++ b/mcp/mcp-bundle-api/smithy-build.json @@ -0,0 +1,9 @@ +{ + "version": "1.0", + "plugins": { + "java-type-codegen": { + "namespace": "software.amazon.smithy.mcp.bundle.api", + "headerFile": "license.txt" + } + } +} diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePlugin.java b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePlugin.java new file mode 100644 index 000000000..b310103b6 --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePlugin.java @@ -0,0 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.mcp.bundle.api; + +import software.amazon.smithy.java.client.core.Client; +import software.amazon.smithy.java.client.core.RequestOverrideConfig; + +/** + * A BundlePlugin applies the settings specified in a {@link software.amazon.smithy.modelbundle.api.model.Bundle} + * on a per-call basis. + */ +public interface BundlePlugin { + /** + * Applies the bundle-specific settings to a client call. + * @return a {@link RequestOverrideConfig.Builder} with the settings from the bundle applied + */ + > B configureClient(B clientBuilder); +} diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePluginFactory.java b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePluginFactory.java new file mode 100644 index 000000000..dde289d3c --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePluginFactory.java @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.mcp.bundle.api; + +import software.amazon.smithy.java.core.serde.document.Document; + +public interface BundlePluginFactory { + String identifier(); + + BundlePlugin createBundlePlugin(Document input); +} diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundler.java b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundler.java new file mode 100644 index 000000000..aab6de56c --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundler.java @@ -0,0 +1,32 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.mcp.bundle.api; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.stream.Collectors; +import software.amazon.smithy.mcp.bundle.api.model.Bundle; +import software.amazon.smithy.utils.SmithyInternalApi; +import software.amazon.smithy.utils.SmithyUnstableApi; + +@SmithyInternalApi +@SmithyUnstableApi +public abstract class Bundler { + + public abstract Bundle bundle(); + + protected String loadModel(String path) { + try (var reader = new BufferedReader(new InputStreamReader( + Objects.requireNonNull(Bundler.class.getResourceAsStream(path)), + StandardCharsets.UTF_8))) { + return reader.lines().collect(Collectors.joining("\n")); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java new file mode 100644 index 000000000..ce1c29019 --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java @@ -0,0 +1,68 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.mcp.bundle.api; + +import software.amazon.smithy.java.server.ProxyService; +import software.amazon.smithy.java.server.Service; +import software.amazon.smithy.mcp.bundle.api.model.Bundle; +import software.amazon.smithy.mcp.bundle.api.model.SmithyBundle; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.loader.ModelAssembler; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.shapes.StructureShape; + +public class Bundles { + + private static final PluginProviders PLUGIN_PROVIDERS = PluginProviders.builder().build(); + + private Bundles() {} + + public static Service getService(Bundle bundle) { + if (bundle.type() != Bundle.Type.smithyBundle) { + throw new IllegalArgumentException("Bundle is not a smithy bundle"); + } + SmithyBundle smithyBundle = bundle.getValue(); + var model = getModel(smithyBundle); + var plugin = PLUGIN_PROVIDERS.getPlugin(smithyBundle.getConfigType(), smithyBundle.getConfig()); + return ProxyService.builder() + .model(model) + .clientConfigurator(plugin::configureClient) + .service(ShapeId.from(smithyBundle.getServiceName())) + .build(); + } + + private static Model getModel(SmithyBundle bundle) { + var modelAssemble = new ModelAssembler().putProperty(ModelAssembler.ALLOW_UNKNOWN_TRAITS, true) + .addUnparsedModel("bundle.json", bundle.getModel()); + var additionalInput = bundle.getAdditionalInput(); + if (additionalInput != null) { + modelAssemble.addUnparsedModel("additionalInput.smithy", additionalInput.getModel()); + var model = modelAssemble.assemble().unwrap(); + var template = model.expectShape(ShapeId.from(additionalInput.getIdentifier())).asStructureShape().get(); + var b = model.toBuilder(); + // mix in the generic arg members + for (var op : model.getOperationShapes()) { + var input = model.expectShape(op.getInput().get(), StructureShape.class).toBuilder(); + for (var member : template.members()) { + input.addMember(member.toBuilder() + .id(ShapeId.from(input.getId().toString() + "$" + member.getMemberName())) + .build()); + } + b.addShape(input.build()); + } + + for (var service : model.getServiceShapes()) { + b.addShape(service.toBuilder() + // trim the endpoint rules because they're huge and we don't need them + .removeTrait(ShapeId.from("smithy.rules#endpointRuleSet")) + .removeTrait(ShapeId.from("smithy.rules#endpointTests")) + .build()); + } + return b.build(); + } + return modelAssemble.assemble().unwrap(); + } +} diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/PluginProviders.java b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/PluginProviders.java new file mode 100644 index 000000000..1309ea140 --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/PluginProviders.java @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.mcp.bundle.api; + +import java.util.HashMap; +import java.util.Map; +import software.amazon.smithy.java.core.serde.document.Document; + +public final class PluginProviders { + private final Map providers; + + private PluginProviders(Builder builder) { + this.providers = builder.providers; + } + + public BundlePlugin getPlugin(String identifier, Document input) { + var provider = providers.get(identifier); + if (provider == null) { + throw new NullPointerException("no auth provider named " + identifier); + } + + return provider.createBundlePlugin(input); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private static final Map BASE_PROVIDERS = + ServiceLoaderLoader.load(BundlePluginFactory.class, + BundlePluginFactory::identifier); + + private Map providers; + + private Builder() { + + } + + public Builder addProvider(BundlePluginFactory provider) { + if (providers == null) { + providers = new HashMap<>(BASE_PROVIDERS); + } + providers.put(provider.identifier(), provider); + return this; + } + + public PluginProviders build() { + if (providers == null) { + providers = BASE_PROVIDERS; + } + return new PluginProviders(this); + } + + } +} diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/ServiceLoaderLoader.java b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/ServiceLoaderLoader.java new file mode 100644 index 000000000..3a76717e1 --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/ServiceLoaderLoader.java @@ -0,0 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.mcp.bundle.api; + +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.function.Function; + +final class ServiceLoaderLoader { + static Map load(Class clazz, Function id) { + Map s = new HashMap<>(); + for (T service : ServiceLoader.load(clazz)) { + s.put(id.apply(service), service); + } + return s; + } +} diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/StaticAuthSchemeResolver.java b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/StaticAuthSchemeResolver.java new file mode 100644 index 000000000..c95e3b53e --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/StaticAuthSchemeResolver.java @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.mcp.bundle.api; + +import java.util.List; +import software.amazon.smithy.java.auth.api.Signer; +import software.amazon.smithy.java.auth.api.identity.Identity; +import software.amazon.smithy.java.auth.api.identity.IdentityResolver; +import software.amazon.smithy.java.auth.api.identity.IdentityResolvers; +import software.amazon.smithy.java.client.core.auth.scheme.AuthScheme; +import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeOption; +import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolver; +import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolverParams; +import software.amazon.smithy.java.context.Context; +import software.amazon.smithy.model.shapes.ShapeId; + +public final class StaticAuthSchemeResolver implements AuthSchemeResolver { + static final StaticAuthSchemeResolver INSTANCE = new StaticAuthSchemeResolver(); + static final ShapeId CONFIGURED_AUTH = ShapeId.from("modelbundle#configuredAuth"); + private static final List AUTH_SCHEME_OPTION = List.of(new AuthSchemeOption(CONFIGURED_AUTH)); + + public static StaticAuthSchemeResolver getInstance() { + return INSTANCE; + } + + private StaticAuthSchemeResolver() { + + } + + @Override + public List resolveAuthScheme(AuthSchemeResolverParams params) { + return AUTH_SCHEME_OPTION; + } + + public static AuthScheme staticScheme( + AuthScheme actual + ) { + return new AuthScheme<>() { + @Override + public ShapeId schemeId() { + return StaticAuthSchemeResolver.CONFIGURED_AUTH; + } + + @Override + public Class requestClass() { + return actual.requestClass(); + } + + @Override + public Class identityClass() { + return actual.identityClass(); + } + + @Override + public Signer signer() { + return actual.signer(); + } + + @Override + public IdentityResolver identityResolver(IdentityResolvers resolvers) { + return actual.identityResolver(resolvers); + } + + @Override + public Context getSignerProperties(Context context) { + return actual.getSignerProperties(context); + } + + @Override + public Context getIdentityProperties(Context context) { + return actual.getIdentityProperties(context); + } + }; + } +} diff --git a/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/bundle.smithy b/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/bundle.smithy new file mode 100644 index 000000000..d17356105 --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/bundle.smithy @@ -0,0 +1,49 @@ +$version: "2" + +namespace software.amazon.smithy.mcp.bundle.api + +union Bundle { + smithyBundle: SmithyBundle +} + +structure SmithyBundle { + /// unique identifier for the configuration type. used to resolve the appropriate Bundler. + @required + configType: String + + /// fully-qualified ShapeId of the service + @required + serviceName: String + + /// Bundle-specific configuration. If this bundle does not require configuration, this + /// field may be omitted. + config: Document + + /// model that describes the service. The service given in `serviceName` must be present. + @required + model: SmithyModel + + /// model describing the generic arguments that must be present in every request. If this + /// bundle does not require generic arguments, this field may be omitted. + additionalInput: AdditionalInput +} + +structure BundleMetadata { + @required + name: String + + @required + description: String + + version: String +} + +string SmithyModel + +structure AdditionalInput { + @required + identifier: String + + @required + model: SmithyModel +} diff --git a/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/manifest b/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/manifest new file mode 100644 index 000000000..b20775b86 --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/manifest @@ -0,0 +1 @@ +bundle.smithy \ No newline at end of file diff --git a/mcp/mcp-cli-api/build.gradle.kts b/mcp/mcp-cli-api/build.gradle.kts index 8f96b87fc..1948fca5f 100644 --- a/mcp/mcp-cli-api/build.gradle.kts +++ b/mcp/mcp-cli-api/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { api(project(":core")) api(libs.smithy.model) api(libs.picocli) - api(project(":model-bundler:bundle-api")) + api(project(":mcp:mcp-bundle-api")) implementation(project(":codecs:json-codec")) implementation(project(":logging")) smithyBuild(project(":codegen:plugins:types-codegen")) diff --git a/mcp/mcp-cli-api/model/main.smithy b/mcp/mcp-cli-api/model/main.smithy index 563f5c046..7bf0a03f8 100644 --- a/mcp/mcp-cli-api/model/main.smithy +++ b/mcp/mcp-cli-api/model/main.smithy @@ -2,19 +2,18 @@ $version: "2" namespace smithy.mcp.cli -use software.amazon.smithy.modelbundle.api#Bundle - structure Config { - toolBundles: ToolBundleConfigs + toolBundles: McpBundleConfigs + registries: Registries } -map ToolBundleConfigs { +map McpBundleConfigs { key: String - value: ToolBundleConfig + value: McpBundleConfig } -union ToolBundleConfig { - smithyModeled: SmithyModeledToolBundleConfig +union McpBundleConfig { + smithyModeled: SmithyModeledBundleConfig genericConfig: GenericToolBundleConfig } @@ -25,39 +24,43 @@ structure CommonToolConfig { blockListedTools: ToolNames } -structure SmithyModeledToolBundleConfig with [CommonToolConfig] { - bundlePlugins: BundlePlugins +map Registries { + key: String + value: RegistryConfig +} - allowListedTools: ToolNames +union RegistryConfig { + javaRegistry: JavaRegistry +} - blockListedTools: ToolNames +structure JavaRegistry with [CommonRegistryConfig] { + jars: Locations +} - serviceDescriptor: Bundle +@mixin +structure CommonRegistryConfig { + name: String +} - // TODO separate this into another location and just reference it here. +structure SmithyModeledBundleConfig with [CommonToolConfig] { + @required + bundleLocation: Location } -structure GenericToolBundleConfig with [CommonToolConfig] { - config: Document +list Locations { + member: Location } -list BundlePlugins { - member: BundlePlugin +union Location { + fileLocation: String } -structure BundlePlugin { - name: String - jars: FilePaths +structure GenericToolBundleConfig with [CommonToolConfig] { + config: Document } list ToolNames { member: ToolName } -list FilePaths { - member: FilePath -} - -string FilePath - string ToolName diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddToolBundle.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java similarity index 81% rename from mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddToolBundle.java rename to mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java index 745be2a30..f18543a70 100644 --- a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddToolBundle.java +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java @@ -5,9 +5,9 @@ package software.amazon.smithy.java.mcp.cli; +import java.nio.file.Path; import java.util.Set; import software.amazon.smithy.java.mcp.cli.model.Config; -import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig; /** * Abstract base class for CLI commands that add tool bundles to the Smithy MCP configuration. @@ -15,9 +15,8 @@ * Subclasses must implement methods to provide tool bundle configuration details and specify * whether existing configurations can be overwritten. * - * @param The specific type of ToolBundleConfig to be added */ -public abstract class AbstractAddToolBundle extends ConfigurationCommand { +public abstract class AbstractAddBundle extends SmithyMcpCommand implements ConfigurationCommand { @Override public final void execute(Config config) throws Exception { @@ -26,16 +25,20 @@ public final void execute(Config config) throws Exception { + " already exists. Either choose a new name or pass --overwrite to overwrite the existing tool bundle"); } var newConfig = getNewToolConfig(); - ConfigUtils.addToolConfig(config, getToolBundleName(), newConfig); + ConfigUtils.addMcpBundle(config, getToolBundleName(), newConfig); System.out.println("Added tool bundle " + getToolBundleName()); } + protected final Path getBundleFileLocation() { + return ConfigUtils.getBundleFileLocation(getToolBundleName()); + } + /** * Returns a new tool configuration instance to be added to the Smithy MCP config. * * @return A new tool bundle configuration */ - protected abstract T getNewToolConfig(); + protected abstract CliBundle getNewToolConfig(); /** * Returns the name under which this tool bundle will be registered. diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/CliBundle.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/CliBundle.java new file mode 100644 index 000000000..ef6a12191 --- /dev/null +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/CliBundle.java @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +import software.amazon.smithy.java.mcp.cli.model.McpBundleConfig; +import software.amazon.smithy.mcp.bundle.api.model.Bundle; + +//TODO find a better name for this. +public record CliBundle( + Bundle mcpBundle, + McpBundleConfig mcpBundleConfig) {} 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 79be7e735..4a04ae227 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 @@ -5,17 +5,24 @@ package software.amazon.smithy.java.mcp.cli; +import static software.amazon.smithy.java.io.ByteBufferUtils.getBytes; + import java.io.FileWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Comparator; import java.util.HashMap; +import java.util.ServiceLoader; +import software.amazon.smithy.java.core.schema.SerializableStruct; import software.amazon.smithy.java.io.ByteBufferUtils; import software.amazon.smithy.java.json.JsonCodec; import software.amazon.smithy.java.mcp.cli.model.Config; -import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig; +import software.amazon.smithy.java.mcp.cli.model.McpBundleConfig; +import software.amazon.smithy.mcp.bundle.api.model.Bundle; /** * Utility class for managing Smithy MCP configuration files. @@ -28,15 +35,30 @@ private ConfigUtils() {} private static final JsonCodec JSON_CODEC = JsonCodec.builder().build(); - /** - * Gets the path to the config file. - * - * @return The path to the config file - */ - private static Path getConfigPath() { + private static final Path CONFIG_DIR = resolveFromHomeDir(".config", "smithy-mcp"); + private static final Path BUNDLE_DIR = CONFIG_DIR.resolve("bundles"); + private static final Path CONFIG_PATH = CONFIG_DIR.resolve("config.json"); + + private static final DefaultConfigProvider DEFAULT_CONFIG_PROVIDER; + + static { + try { + ensureDirectoryExists(CONFIG_DIR); + ensureDirectoryExists(BUNDLE_DIR); + } catch (IOException e) { + throw new RuntimeException(e); + } + + DEFAULT_CONFIG_PROVIDER = ServiceLoader.load(DefaultConfigProvider.class) + .stream() + .map(ServiceLoader.Provider::get) + .min(Comparator.comparing(DefaultConfigProvider::priority)) + .orElse(new EmptyDefaultConfigProvider()); + } + + private static Path resolveFromHomeDir(String... paths) { String userHome = System.getProperty("user.home"); - Path configDir = Paths.get(userHome, ".config", "smithy-mcp"); - return configDir.resolve("config.json"); + return Paths.get(userHome, paths); } /** @@ -44,10 +66,9 @@ private static Path getConfigPath() { * * @throws IOException If there's an error creating directories */ - private static void ensureConfigDirExists() throws IOException { - Path configDir = getConfigPath(); - if (!Files.exists(configDir)) { - Files.createDirectories(configDir); + private static void ensureDirectoryExists(Path dir) throws IOException { + if (!Files.exists(dir)) { + Files.createDirectories(dir); } } @@ -58,19 +79,17 @@ private static void ensureConfigDirExists() throws IOException { * @throws IOException If there's an error creating directories or the file */ public static Config loadOrCreateConfig() throws IOException { - Path configFile = getConfigPath(); - ensureConfigDirExists(); - // Check if the config file exists, create it if it doesn't - var file = configFile.toFile(); - if (!file.exists()) { + if (!CONFIG_PATH.toFile().exists()) { // Create an empty JSON object as the default config - try (var writer = new FileWriter(file, StandardCharsets.UTF_8)) { - writer.write("{}"); - } + Files.write(CONFIG_PATH, toJson(DEFAULT_CONFIG_PROVIDER.getConfig()), StandardOpenOption.CREATE_NEW); } - return fromJson(Files.readAllBytes(configFile)); + return fromJson(Files.readAllBytes(CONFIG_PATH)); + } + + static Path getBundleFileLocation(String bundleName) { + return BUNDLE_DIR.resolve(bundleName + ".json"); } /** @@ -80,10 +99,7 @@ public static Config loadOrCreateConfig() throws IOException { * @throws IOException If there's an error writing to the file */ public static void updateConfig(Config config) throws IOException { - Path configFile = getConfigPath(); - ensureConfigDirExists(); - - var file = configFile.toFile(); + var file = CONFIG_PATH.toFile(); try (var writer = new FileWriter(file, StandardCharsets.UTF_8)) { writer.write(ByteBufferUtils.asString(JSON_CODEC.serialize(config))); } @@ -95,29 +111,23 @@ public static void updateConfig(Config config) throws IOException { * @param json The JSON data as a byte array * @return The deserialized Config object with defaults applied */ - public static Config fromJson(byte[] json) { - return adjustDefaults(Config.builder().deserialize(JSON_CODEC.createDeserializer(json)).build()); + private static Config fromJson(byte[] json) { + return Config.builder().deserialize(JSON_CODEC.createDeserializer(json)).build(); } - /** - * Applies default values to a configuration if needed. - * - * @param config The configuration to adjust - * @return The configuration with defaults applied - */ - private static Config adjustDefaults(Config config) { - return config; + private static byte[] toJson(SerializableStruct struct) { + return getBytes(JSON_CODEC.serialize(struct)); } /** * Adds a new tool bundle configuration to an existing configuration and saves it. * - * @param existingConfig The existing configuration to update - * @param name The name under which to register the tool bundle + * @param existingConfig The existing configuration to update + * @param name The name under which to register the tool bundle * @param toolBundleConfig The tool bundle configuration to add * @throws IOException If there's an error writing the updated configuration */ - public static void addToolConfig(Config existingConfig, String name, ToolBundleConfig toolBundleConfig) + private static void addMcpBundleConfig(Config existingConfig, String name, McpBundleConfig toolBundleConfig) throws IOException { var existingToolBundles = new HashMap<>(existingConfig.getToolBundles()); existingToolBundles.put(name, toolBundleConfig); @@ -125,7 +135,23 @@ public static void addToolConfig(Config existingConfig, String name, ToolBundleC updateConfig(newConfig); } - public static void main(String[] args) throws IOException { - System.out.println(loadOrCreateConfig()); + public static Bundle getMcpBundle(String bundleName) { + try { + return Bundle.builder() + .deserialize(JSON_CODEC.createDeserializer(Files.readAllBytes(getBundleFileLocation(bundleName)))) + .build(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void addMcpBundle(Config config, String toolBundleName, CliBundle mcpBundleConfig) + throws IOException { + var serializedBundle = toJson(mcpBundleConfig.mcpBundle()); + Files.write(getBundleFileLocation(toolBundleName), + serializedBundle, + StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.CREATE); + addMcpBundleConfig(config, toolBundleName, mcpBundleConfig.mcpBundleConfig()); } } diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigurationCommand.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigurationCommand.java index 6fccac730..379d189bd 100644 --- a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigurationCommand.java +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/ConfigurationCommand.java @@ -11,6 +11,6 @@ * This class extends SmithyMcpCommand to provide a common base for all commands * that modify the MCP configuration. Subclasses should implement the execute method. */ -public abstract class ConfigurationCommand extends SmithyMcpCommand { +public interface ConfigurationCommand { } diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/DefaultConfigProvider.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/DefaultConfigProvider.java new file mode 100644 index 000000000..76da8ed75 --- /dev/null +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/DefaultConfigProvider.java @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +import software.amazon.smithy.java.mcp.cli.model.Config; + +public interface DefaultConfigProvider { + Config getConfig(); + + int priority(); +} diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/EmptyDefaultConfigProvider.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/EmptyDefaultConfigProvider.java new file mode 100644 index 000000000..f3091975d --- /dev/null +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/EmptyDefaultConfigProvider.java @@ -0,0 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +import software.amazon.smithy.java.mcp.cli.model.Config; + +public class EmptyDefaultConfigProvider implements DefaultConfigProvider { + + @Override + public Config getConfig() { + return Config.builder().build(); + } + + @Override + public int priority() { + return Integer.MIN_VALUE; + } +} diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/Registry.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/Registry.java new file mode 100644 index 000000000..e10c96e53 --- /dev/null +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/Registry.java @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +import java.util.List; +import software.amazon.smithy.mcp.bundle.api.model.Bundle; +import software.amazon.smithy.mcp.bundle.api.model.BundleMetadata; + +public interface Registry { + + String name(); + + List listMcpBundles(); + + Bundle getMcpBundle(String name); + +} diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/RegistryUtils.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/RegistryUtils.java new file mode 100644 index 000000000..122dfb5c1 --- /dev/null +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/RegistryUtils.java @@ -0,0 +1,31 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli; + +import java.util.Map; +import java.util.ServiceLoader; +import java.util.ServiceLoader.Provider; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class RegistryUtils { + + private static final Map JAVA_REGISTRIES; + + static { + JAVA_REGISTRIES = ServiceLoader.load(Registry.class) + .stream() + .map(Provider::get) + .collect(Collectors.toMap(Registry::name, Function.identity())); + } + + public static Registry getRegistry(String name) { + if (JAVA_REGISTRIES.containsKey(name)) { + return JAVA_REGISTRIES.get(name); + } + throw new IllegalStateException("No such registry: " + name); + } +} diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/SmithyMcpCommand.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/SmithyMcpCommand.java index 403475d08..bb45f7d6a 100644 --- a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/SmithyMcpCommand.java +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/SmithyMcpCommand.java @@ -32,7 +32,7 @@ public final Integer call() throws Exception { System.out.println("Invalid input : [" + e.getMessage() + "]"); return 2; } catch (Exception e) { - LOG.error("Unexpected error", e); + e.printStackTrace(System.out); return 1; } } diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddBundle.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddBundle.java new file mode 100644 index 000000000..de1e711a2 --- /dev/null +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddBundle.java @@ -0,0 +1,32 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.java.mcp.cli.commands; + +import static picocli.CommandLine.Command; + +import picocli.CommandLine.Option; +import software.amazon.smithy.java.mcp.cli.RegistryUtils; +import software.amazon.smithy.java.mcp.cli.SmithyMcpCommand; +import software.amazon.smithy.java.mcp.cli.model.Config; + +@Command(name = "add-bundle", description = "Downloads and adds a bundle from the MCP registry.") +public class AddBundle extends SmithyMcpCommand { + + @Option(names = {"-r", "--registry"}, + description = "Name of the registry to list the bundles from. If not provided will list tools across all registries.") + String registry; + + @Option(names = {"-n", "--name"}, description = "Name of the MCP Bundle to install.") + String name; + + @Override + protected void execute(Config config) { + if (registry != null && !config.getRegistries().containsKey(registry)) { + throw new IllegalArgumentException("The registry '" + registry + "' does not exist."); + } + RegistryUtils.getRegistry(registry).getMcpBundle(name); + } +} diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddSmithyToolBundle.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddSmithyBundle.java similarity index 78% rename from mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddSmithyToolBundle.java rename to mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddSmithyBundle.java index bb85c4ef9..ed00e3a75 100644 --- a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddSmithyToolBundle.java +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/AddSmithyBundle.java @@ -8,8 +8,8 @@ import java.util.Set; import picocli.CommandLine; import picocli.CommandLine.Command; -import software.amazon.smithy.java.mcp.cli.AbstractAddToolBundle; -import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig.SmithyModeledMember; +import software.amazon.smithy.java.mcp.cli.AbstractAddBundle; +import software.amazon.smithy.java.mcp.cli.CliBundle; /** * Command to add a Smithy tool bundle to the MCP configuration. @@ -17,9 +17,9 @@ * This command allows users to add a new Smithy tool bundle to their MCP configuration. * Currently under development and hidden from the CLI help. */ -@Command(name = "add-smithy-tool-bundle", description = "Add a smithy tool bundle.", hidden = true) +@Command(name = "add-smithy-bundle", description = "Add a smithy bundle.", hidden = true) //TODO implement and unhide -public class AddSmithyToolBundle extends AbstractAddToolBundle { +public class AddSmithyBundle extends AbstractAddBundle { @CommandLine.Option(names = "--overwrite", description = "Overwrite existing config", @@ -30,7 +30,7 @@ public class AddSmithyToolBundle extends AbstractAddToolBundle System.out.println(bundle.getName() + " : " + bundle.getDescription())); + + } +} diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java index bed6014bf..94838e508 100644 --- a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java @@ -9,15 +9,16 @@ import java.util.List; import picocli.CommandLine.Command; import picocli.CommandLine.Parameters; +import software.amazon.smithy.java.mcp.cli.ConfigUtils; import software.amazon.smithy.java.mcp.cli.SmithyMcpCommand; import software.amazon.smithy.java.mcp.cli.model.Config; -import software.amazon.smithy.java.mcp.cli.model.SmithyModeledToolBundleConfig; -import software.amazon.smithy.java.mcp.cli.model.ToolBundleConfig; +import software.amazon.smithy.java.mcp.cli.model.McpBundleConfig; +import software.amazon.smithy.java.mcp.cli.model.SmithyModeledBundleConfig; import software.amazon.smithy.java.mcp.server.McpServer; import software.amazon.smithy.java.server.FilteredService; import software.amazon.smithy.java.server.OperationFilters; import software.amazon.smithy.java.server.Service; -import software.amazon.smithy.modelbundle.api.Bundles; +import software.amazon.smithy.mcp.bundle.api.Bundles; /** * Command to start a Smithy MCP server exposing specified tool bundles. @@ -47,7 +48,7 @@ public void execute(Config config) { throw new IllegalArgumentException( "No Tool Bundles have been configured. Configure one using the configure-tool-bundle command."); } - List toolBundleConfigs = new ArrayList<>(toolBundles.size()); + List toolBundleConfigs = new ArrayList<>(toolBundles.size()); for (var toolBundle : toolBundles) { var toolBundleConfig = config.getToolBundles().get(toolBundle); if (toolBundleConfig == null) { @@ -59,8 +60,9 @@ public void execute(Config config) { for (var toolBundleConfig : toolBundleConfigs) { switch (toolBundleConfig.type()) { case smithyModeled -> { - SmithyModeledToolBundleConfig bundleConfig = toolBundleConfig.getValue(); - Service service = Bundles.getProxyService(bundleConfig.getServiceDescriptor()); + SmithyModeledBundleConfig bundleConfig = toolBundleConfig.getValue(); + Service service = + Bundles.getService(ConfigUtils.getMcpBundle(bundleConfig.getName())); if (bundleConfig.hasAllowListedTools() || bundleConfig.hasBlockListedTools()) { var filter = OperationFilters.allowList(bundleConfig.getAllowListedTools()) .and(OperationFilters.blockList(bundleConfig.getBlockListedTools())); diff --git a/mcp/mcp-server/build.gradle.kts b/mcp/mcp-server/build.gradle.kts index aeda54ce4..99bb173b8 100644 --- a/mcp/mcp-server/build.gradle.kts +++ b/mcp/mcp-server/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation(project(":context")) implementation(project(":codecs:json-codec")) implementation(project(":mcp:mcp-schemas")) - implementation(project(":model-bundler:bundle-api")) + implementation(project(":mcp:mcp-bundle-api")) } spotbugs { diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java index f7bdc05bb..840de9888 100644 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java +++ b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java @@ -11,6 +11,7 @@ import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.modelbundle.api.model.Bundle; +import software.amazon.smithy.modelbundle.api.model.Model.Type; public class Bundles { @@ -18,7 +19,10 @@ public class Bundles { private Bundles() {} - public static ProxyService getProxyService(Bundle bundle) { + public static ProxyService forSmithyBundle(Bundle bundle) { + if (bundle.getModel().type() != Type.smithyModel) { + throw new IllegalArgumentException("Bundle is not a smithy bundle"); + } var model = getModel(bundle); var plugin = PLUGIN_PROVIDERS.getPlugin(bundle.getConfigType(), bundle.getConfig()); return ProxyService.builder() diff --git a/settings.gradle.kts b/settings.gradle.kts index 1cf1dda51..df7affdef 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -94,9 +94,7 @@ include(":mcp:mcp-schemas") include(":mcp:mcp-server") include(":mcp:mcp-cli") include(":mcp:mcp-cli-api") - -include(":model-bundler:bundle-api") -include(":model-bundler:bundle-cli") +include(":mcp:mcp-bundle-api") include("coral-bundle-types") include("mcp:mcp-bundle-api") \ No newline at end of file From 2d3c5623691ecf986c8b13a2e729f4419d6d1bd2 Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 6 May 2025 17:50:08 -0700 Subject: [PATCH 05/13] Remove unused class --- .../smithy/java/server/ProxyService.java | 57 ------------------- 1 file changed, 57 deletions(-) diff --git a/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java b/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java index 5e218a46f..f12af51ae 100644 --- a/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java +++ b/server/server-proxy/src/main/java/software/amazon/smithy/java/server/ProxyService.java @@ -5,29 +5,21 @@ package software.amazon.smithy.java.server; -import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.CompletableFuture; import java.util.function.BiFunction; import java.util.function.UnaryOperator; import software.amazon.smithy.java.auth.api.identity.Identity; import software.amazon.smithy.java.auth.api.identity.IdentityResolver; import software.amazon.smithy.java.aws.client.core.settings.RegionSetting; -import software.amazon.smithy.java.client.core.ClientProtocol; -import software.amazon.smithy.java.client.core.MessageExchange; import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolver; -import software.amazon.smithy.java.client.core.endpoint.Endpoint; import software.amazon.smithy.java.client.core.endpoint.EndpointResolver; -import software.amazon.smithy.java.context.Context; import software.amazon.smithy.java.core.error.ModeledException; -import software.amazon.smithy.java.core.schema.ApiOperation; import software.amazon.smithy.java.core.schema.Schema; import software.amazon.smithy.java.core.schema.SerializableStruct; -import software.amazon.smithy.java.core.serde.Codec; import software.amazon.smithy.java.core.serde.TypeRegistry; import software.amazon.smithy.java.core.serde.document.Document; import software.amazon.smithy.java.dynamicclient.DocumentException; @@ -239,53 +231,4 @@ private StructDocument createStructDocument(ToShapeId shape, Document value) { } } - private static class NoOpClientProtocol implements ClientProtocol { - - private static final NoOpClientProtocol INSTANCE = new NoOpClientProtocol(); - - private NoOpClientProtocol() { - - } - - @Override - public ShapeId id() { - return null; - } - - @Override - public Codec payloadCodec() { - return null; - } - - @Override - public MessageExchange messageExchange() { - return null; - } - - @Override - public I createRequest( - ApiOperation operation, - I1 input, - Context context, - URI endpoint - ) { - return null; - } - - @Override - public I setServiceEndpoint(I request, Endpoint endpoint) { - return null; - } - - @Override - public CompletableFuture deserializeResponse( - ApiOperation operation, - Context context, - TypeRegistry errorRegistry, - I request, - O response - ) { - return null; - } - } } From e4bb91d35c567750247bba0c2f654fd8b00a9f0d Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 6 May 2025 17:51:13 -0700 Subject: [PATCH 06/13] Remove duplicate modules --- settings.gradle.kts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index df7affdef..dd7d0a295 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -94,7 +94,4 @@ include(":mcp:mcp-schemas") include(":mcp:mcp-server") include(":mcp:mcp-cli") include(":mcp:mcp-cli-api") -include(":mcp:mcp-bundle-api") - -include("coral-bundle-types") -include("mcp:mcp-bundle-api") \ No newline at end of file +include(":mcp:mcp-bundle-api") \ No newline at end of file From c2c3471a2bee03b60fb2e6a227e31e04a1e4f14c Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 6 May 2025 17:52:27 -0700 Subject: [PATCH 07/13] Remove model bundler modules --- model-bundler/bundle-api/build.gradle.kts | 50 ------------ model-bundler/bundle-api/license.txt | 4 - model-bundler/bundle-api/smithy-build.json | 9 --- .../smithy/modelbundle/api/BundlePlugin.java | 21 ----- .../modelbundle/api/BundlePluginFactory.java | 14 ---- .../smithy/modelbundle/api/Bundler.java | 16 ---- .../modelbundle/api/BundlerFactory.java | 12 --- .../smithy/modelbundle/api/Bundlers.java | 57 -------------- .../smithy/modelbundle/api/Bundles.java | 70 ----------------- .../modelbundle/api/PluginProviders.java | 59 -------------- .../modelbundle/api/ServiceLoaderLoader.java | 21 ----- .../api/StaticAuthSchemeResolver.java | 78 ------------------- .../resources/META-INF/smithy/bundle.smithy | 37 --------- .../main/resources/META-INF/smithy/manifest | 1 - model-bundler/bundle-cli/build.gradle.kts | 55 ------------- model-bundler/bundle-cli/license.txt | 4 - model-bundler/bundle-cli/smithy-build.json | 9 --- .../java/modelbundle/cli/ModelBundler.java | 39 ---------- 18 files changed, 556 deletions(-) delete mode 100644 model-bundler/bundle-api/build.gradle.kts delete mode 100644 model-bundler/bundle-api/license.txt delete mode 100644 model-bundler/bundle-api/smithy-build.json delete mode 100644 model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java delete mode 100644 model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePluginFactory.java delete mode 100644 model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundler.java delete mode 100644 model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlerFactory.java delete mode 100644 model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundlers.java delete mode 100644 model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java delete mode 100644 model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java delete mode 100644 model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ServiceLoaderLoader.java delete mode 100644 model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/StaticAuthSchemeResolver.java delete mode 100644 model-bundler/bundle-api/src/main/resources/META-INF/smithy/bundle.smithy delete mode 100644 model-bundler/bundle-api/src/main/resources/META-INF/smithy/manifest delete mode 100644 model-bundler/bundle-cli/build.gradle.kts delete mode 100644 model-bundler/bundle-cli/license.txt delete mode 100644 model-bundler/bundle-cli/smithy-build.json delete mode 100644 model-bundler/bundle-cli/src/main/java/software/amazon/smithy/java/modelbundle/cli/ModelBundler.java diff --git a/model-bundler/bundle-api/build.gradle.kts b/model-bundler/bundle-api/build.gradle.kts deleted file mode 100644 index b070c9af6..000000000 --- a/model-bundler/bundle-api/build.gradle.kts +++ /dev/null @@ -1,50 +0,0 @@ -plugins { - application - id("smithy-java.module-conventions") - id("software.amazon.smithy.gradle.smithy-base") -} - -description = "This module implements the model-bundler utility" - -extra["displayName"] = "Smithy :: Java :: Model Bundler" -extra["moduleName"] = "software.amazon.smithy.java.modelbundle.api" - -dependencies { - smithyBuild(project(":codegen:plugins:types-codegen")) - - implementation(project(":core")) - implementation(libs.smithy.model) - api(project(":client:client-auth-api")) - api(project(":client:client-core")) - api(project(":dynamic-schemas")) - api(project(":server:server-proxy")) -} - -afterEvaluate { - val typePath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-type-codegen") - sourceSets { - main { - java { - srcDir(typePath) - include("software/**") - } - resources { - srcDir(typePath) - include("META-INF/**") - } - } - } -} - -tasks.named("compileJava") { - dependsOn("smithyBuild") -} - -// Needed because sources-jar needs to run after smithy-build is done -tasks.sourcesJar { - mustRunAfter("compileJava") -} - -tasks.processResources { - dependsOn("compileJava") -} diff --git a/model-bundler/bundle-api/license.txt b/model-bundler/bundle-api/license.txt deleted file mode 100644 index 5f97ab495..000000000 --- a/model-bundler/bundle-api/license.txt +++ /dev/null @@ -1,4 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ \ No newline at end of file diff --git a/model-bundler/bundle-api/smithy-build.json b/model-bundler/bundle-api/smithy-build.json deleted file mode 100644 index def776964..000000000 --- a/model-bundler/bundle-api/smithy-build.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "version": "1.0", - "plugins": { - "java-type-codegen": { - "namespace": "software.amazon.smithy.modelbundle.api", - "headerFile": "license.txt" - } - } -} diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java deleted file mode 100644 index 67a8a9f5f..000000000 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.modelbundle.api; - -import software.amazon.smithy.java.client.core.Client; -import software.amazon.smithy.java.client.core.RequestOverrideConfig; - -/** - * A BundlePlugin applies the settings specified in a {@link software.amazon.smithy.modelbundle.api.model.Bundle} - * on a per-call basis. - */ -public interface BundlePlugin { - /** - * Applies the bundle-specific settings to a client call. - * @return a {@link RequestOverrideConfig.Builder} with the settings from the bundle applied - */ - > B configureClient(B clientBuilder); -} diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePluginFactory.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePluginFactory.java deleted file mode 100644 index 2461018ab..000000000 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePluginFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.modelbundle.api; - -import software.amazon.smithy.java.core.serde.document.Document; - -public interface BundlePluginFactory { - String identifier(); - - BundlePlugin createBundlePlugin(Document input); -} diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundler.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundler.java deleted file mode 100644 index 597a0d454..000000000 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundler.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.modelbundle.api; - -import software.amazon.smithy.modelbundle.api.model.Bundle; -import software.amazon.smithy.utils.SmithyInternalApi; -import software.amazon.smithy.utils.SmithyUnstableApi; - -@SmithyInternalApi -@SmithyUnstableApi -public interface Bundler { - Bundle bundle(); -} diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlerFactory.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlerFactory.java deleted file mode 100644 index d58e61a2d..000000000 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlerFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.modelbundle.api; - -public interface BundlerFactory { - String identifier(); - - Bundler newBundler(String... args); -} diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundlers.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundlers.java deleted file mode 100644 index 24ef78426..000000000 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundlers.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.modelbundle.api; - -import java.util.HashMap; -import java.util.Map; - -public final class Bundlers { - private final Map providers; - - private Bundlers(Builder builder) { - this.providers = builder.providers; - } - - public Bundler getProvider(String identifier, String... args) { - var provider = providers.get(identifier); - if (provider == null) { - throw new NullPointerException("no bundler named " + identifier); - } - - return provider.newBundler(args); - } - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - private static final Map BASE_PROVIDERS = - ServiceLoaderLoader.load(BundlerFactory.class, BundlerFactory::identifier); - - private Map providers; - - private Builder() { - - } - - public Builder addProvider(BundlerFactory provider) { - if (providers == null) { - providers = new HashMap<>(BASE_PROVIDERS); - } - providers.put(provider.identifier(), provider); - return this; - } - - public Bundlers build() { - if (providers == null) { - providers = BASE_PROVIDERS; - } - return new Bundlers(this); - } - - } -} diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java deleted file mode 100644 index 840de9888..000000000 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/Bundles.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.modelbundle.api; - -import software.amazon.smithy.java.server.ProxyService; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.loader.ModelAssembler; -import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.modelbundle.api.model.Bundle; -import software.amazon.smithy.modelbundle.api.model.Model.Type; - -public class Bundles { - - private static final PluginProviders PLUGIN_PROVIDERS = PluginProviders.builder().build(); - - private Bundles() {} - - public static ProxyService forSmithyBundle(Bundle bundle) { - if (bundle.getModel().type() != Type.smithyModel) { - throw new IllegalArgumentException("Bundle is not a smithy bundle"); - } - var model = getModel(bundle); - var plugin = PLUGIN_PROVIDERS.getPlugin(bundle.getConfigType(), bundle.getConfig()); - return ProxyService.builder() - .model(model) - .clientConfigurator(plugin::configureClient) - .service(ShapeId.from(bundle.getServiceName())) - .build(); - } - - private static Model getModel(Bundle bundle) { - var modelAssemble = new ModelAssembler().putProperty(ModelAssembler.ALLOW_UNKNOWN_TRAITS, true) - .addUnparsedModel("bundle.json", bundle.getModel().getValue()); - var args = bundle.getRequestArguments(); - if (args != null) { - modelAssemble.addUnparsedModel("args.smithy", bundle.getRequestArguments().getModel().getValue()); - var model = modelAssemble.assemble().unwrap(); - var template = model.expectShape(ShapeId.from(args.getIdentifier())).asStructureShape().get(); - var b = model.toBuilder(); - // mix in the generic arg members - for (var op : model.getOperationShapes()) { - var input = model.expectShape(op.getInput().get(), StructureShape.class).toBuilder(); - for (var member : template.members()) { - input.addMember(member.toBuilder() - .id(ShapeId.from(input.getId().toString() + "$" + member.getMemberName())) - .build()); - } - b.addShape(input.build()); - } - - for (var service : model.getServiceShapes()) { - b.addShape(service.toBuilder() - // trim the endpoint rules because they're huge and we don't need them - .removeTrait(ShapeId.from("smithy.rules#endpointRuleSet")) - .removeTrait(ShapeId.from("smithy.rules#endpointTests")) - .build()); - } - return b.build(); - } - return modelAssemble.assemble().unwrap(); - } - - public static BundlePlugin getBundlePlugin(Bundle bundle) { - return PLUGIN_PROVIDERS.getPlugin(bundle.getConfigType(), bundle.getConfig()); - } -} diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java deleted file mode 100644 index 35337c8a8..000000000 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.modelbundle.api; - -import java.util.HashMap; -import java.util.Map; -import software.amazon.smithy.java.core.serde.document.Document; - -public final class PluginProviders { - private final Map providers; - - private PluginProviders(Builder builder) { - this.providers = builder.providers; - } - - public BundlePlugin getPlugin(String identifier, Document input) { - var provider = providers.get(identifier); - if (provider == null) { - throw new NullPointerException("no auth provider named " + identifier); - } - - return provider.createBundlePlugin(input); - } - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - private static final Map BASE_PROVIDERS = - ServiceLoaderLoader.load(BundlePluginFactory.class, - BundlePluginFactory::identifier); - - private Map providers; - - private Builder() { - - } - - public Builder addProvider(BundlePluginFactory provider) { - if (providers == null) { - providers = new HashMap<>(BASE_PROVIDERS); - } - providers.put(provider.identifier(), provider); - return this; - } - - public PluginProviders build() { - if (providers == null) { - providers = BASE_PROVIDERS; - } - return new PluginProviders(this); - } - - } -} diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ServiceLoaderLoader.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ServiceLoaderLoader.java deleted file mode 100644 index b6e4f049e..000000000 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ServiceLoaderLoader.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.modelbundle.api; - -import java.util.HashMap; -import java.util.Map; -import java.util.ServiceLoader; -import java.util.function.Function; - -final class ServiceLoaderLoader { - static Map load(Class clazz, Function id) { - Map s = new HashMap<>(); - for (T service : ServiceLoader.load(clazz)) { - s.put(id.apply(service), service); - } - return s; - } -} diff --git a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/StaticAuthSchemeResolver.java b/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/StaticAuthSchemeResolver.java deleted file mode 100644 index eb3ccccf1..000000000 --- a/model-bundler/bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/StaticAuthSchemeResolver.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.modelbundle.api; - -import java.util.List; -import software.amazon.smithy.java.auth.api.Signer; -import software.amazon.smithy.java.auth.api.identity.Identity; -import software.amazon.smithy.java.auth.api.identity.IdentityResolver; -import software.amazon.smithy.java.auth.api.identity.IdentityResolvers; -import software.amazon.smithy.java.client.core.auth.scheme.AuthScheme; -import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeOption; -import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolver; -import software.amazon.smithy.java.client.core.auth.scheme.AuthSchemeResolverParams; -import software.amazon.smithy.java.context.Context; -import software.amazon.smithy.model.shapes.ShapeId; - -public final class StaticAuthSchemeResolver implements AuthSchemeResolver { - static final StaticAuthSchemeResolver INSTANCE = new StaticAuthSchemeResolver(); - static final ShapeId CONFIGURED_AUTH = ShapeId.from("modelbundle#configuredAuth"); - private static final List AUTH_SCHEME_OPTION = List.of(new AuthSchemeOption(CONFIGURED_AUTH)); - - public static StaticAuthSchemeResolver getInstance() { - return INSTANCE; - } - - private StaticAuthSchemeResolver() { - - } - - @Override - public List resolveAuthScheme(AuthSchemeResolverParams params) { - return AUTH_SCHEME_OPTION; - } - - public static AuthScheme staticScheme( - AuthScheme actual - ) { - return new AuthScheme<>() { - @Override - public ShapeId schemeId() { - return StaticAuthSchemeResolver.CONFIGURED_AUTH; - } - - @Override - public Class requestClass() { - return actual.requestClass(); - } - - @Override - public Class identityClass() { - return actual.identityClass(); - } - - @Override - public Signer signer() { - return actual.signer(); - } - - @Override - public IdentityResolver identityResolver(IdentityResolvers resolvers) { - return actual.identityResolver(resolvers); - } - - @Override - public Context getSignerProperties(Context context) { - return actual.getSignerProperties(context); - } - - @Override - public Context getIdentityProperties(Context context) { - return actual.getIdentityProperties(context); - } - }; - } -} diff --git a/model-bundler/bundle-api/src/main/resources/META-INF/smithy/bundle.smithy b/model-bundler/bundle-api/src/main/resources/META-INF/smithy/bundle.smithy deleted file mode 100644 index 96cdc062d..000000000 --- a/model-bundler/bundle-api/src/main/resources/META-INF/smithy/bundle.smithy +++ /dev/null @@ -1,37 +0,0 @@ -$version: "2" - -namespace software.amazon.smithy.modelbundle.api - -structure Bundle { - /// unique identifier for the configuration type. used to resolve the appropriate Bundler. - @required - configType: String - - /// fully-qualified ShapeId of the service - @required - serviceName: String - - /// Bundle-specific configuration. If this bundle does not require configuration, this - /// field may be omitted. - config: Document - - /// model that describes the service. The service given in `serviceName` must be present. - @required - model: Model - - /// model describing the generic arguments that must be present in every request. If this - /// bundle does not require generic arguments, this field may be omitted. - requestArguments: GenericArguments -} - -union Model { - smithyModel: String -} - -structure GenericArguments { - @required - identifier: String - - @required - model: Model -} diff --git a/model-bundler/bundle-api/src/main/resources/META-INF/smithy/manifest b/model-bundler/bundle-api/src/main/resources/META-INF/smithy/manifest deleted file mode 100644 index b20775b86..000000000 --- a/model-bundler/bundle-api/src/main/resources/META-INF/smithy/manifest +++ /dev/null @@ -1 +0,0 @@ -bundle.smithy \ No newline at end of file diff --git a/model-bundler/bundle-cli/build.gradle.kts b/model-bundler/bundle-cli/build.gradle.kts deleted file mode 100644 index d64461907..000000000 --- a/model-bundler/bundle-cli/build.gradle.kts +++ /dev/null @@ -1,55 +0,0 @@ -plugins { - application - id("smithy-java.module-conventions") - alias(libs.plugins.shadow) -} - -description = "This module implements the model-bundler utility" - -extra["displayName"] = "Smithy :: Java :: Model Bundler" -extra["moduleName"] = "software.amazon.smithy.java.modelbundle.cli" - -dependencies { - implementation(project(":core")) - implementation(project(":model-bundler:bundle-api")) - implementation(libs.smithy.model) - implementation(project(":codecs:json-codec")) - - // TODO dynamically load this dependency instead of bundling it - implementation(project(":aws:aws-service-bundler")) - - shadow(project(":core")) - shadow(project(":model-bundler:bundle-api")) -} - -val mainClassName = "software.amazon.smithy.java.modelbundler.ModelBundler" -application { - mainClass.set(mainClassName) - applicationName = "model-bundler" -} - -tasks { - shadowJar { - archiveClassifier.set("") - mergeServiceFiles() - manifest { - attributes["Main-Class"] = mainClassName - } - } - - jar { - finalizedBy(shadowJar) - } - - distZip { - dependsOn(shadowJar) - } - - distTar { - dependsOn(shadowJar) - } - - startScripts { - dependsOn(shadowJar) - } -} diff --git a/model-bundler/bundle-cli/license.txt b/model-bundler/bundle-cli/license.txt deleted file mode 100644 index 5f97ab495..000000000 --- a/model-bundler/bundle-cli/license.txt +++ /dev/null @@ -1,4 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ \ No newline at end of file diff --git a/model-bundler/bundle-cli/smithy-build.json b/model-bundler/bundle-cli/smithy-build.json deleted file mode 100644 index dc6c899ae..000000000 --- a/model-bundler/bundle-cli/smithy-build.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "version": "1.0", - "plugins": { - "java-type-codegen": { - "namespace": "software.amazon.smithy.model.bundle", - "headerFile": "license.txt" - } - } -} diff --git a/model-bundler/bundle-cli/src/main/java/software/amazon/smithy/java/modelbundle/cli/ModelBundler.java b/model-bundler/bundle-cli/src/main/java/software/amazon/smithy/java/modelbundle/cli/ModelBundler.java deleted file mode 100644 index 661ad9209..000000000 --- a/model-bundler/bundle-cli/src/main/java/software/amazon/smithy/java/modelbundle/cli/ModelBundler.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.java.modelbundle.cli; - -import java.io.BufferedOutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import software.amazon.smithy.java.json.JsonCodec; -import software.amazon.smithy.modelbundle.api.Bundlers; - -public final class ModelBundler { - public static void main(String[] args) { - // TODO: arg parsing - // remove the output argument from the provided arguments - if (args.length < 2) { - throw new RuntimeException("Expected at least two arguments: a bundler name and an output location"); - } - - var bundlerName = args[0]; - var output = args[1]; - - String[] argsWithoutOutput = new String[args.length - 2]; - if (args.length > 2) { - System.arraycopy(args, 2, argsWithoutOutput, 0, argsWithoutOutput.length); - } - - var bundle = Bundlers.builder().build().getProvider(bundlerName, argsWithoutOutput).bundle(); - var codec = JsonCodec.builder().build(); - try (var os = new BufferedOutputStream(Files.newOutputStream(Path.of(output))); - var serializer = codec.createSerializer(os)) { - bundle.serialize(serializer); - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} From b5482cfb579232b8368d3e4b72d3d0302ee6ec48 Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 6 May 2025 17:54:32 -0700 Subject: [PATCH 08/13] Use Files.write --- .../amazon/smithy/java/io/ByteBufferUtils.java | 11 ----------- .../amazon/smithy/java/mcp/cli/ConfigUtils.java | 5 +---- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java b/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java index 653c26d14..edbaf4efe 100644 --- a/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java +++ b/io/src/main/java/software/amazon/smithy/java/io/ByteBufferUtils.java @@ -7,8 +7,6 @@ import java.io.InputStream; import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.Base64; public final class ByteBufferUtils { @@ -35,15 +33,6 @@ public static byte[] getBytes(ByteBuffer buffer) { return bytes; } - public static String asString(ByteBuffer buffer) { - return asString(buffer, StandardCharsets.UTF_8); - } - - public static String asString(ByteBuffer buffer, Charset charset) { - byte[] bytes = getBytes(buffer); - return new String(bytes, charset); - } - public static InputStream byteBufferInputStream(ByteBuffer buffer) { return new ByteBufferBackedInputStream(buffer); } 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 4a04ae227..815cad558 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 @@ -99,10 +99,7 @@ static Path getBundleFileLocation(String bundleName) { * @throws IOException If there's an error writing to the file */ public static void updateConfig(Config config) throws IOException { - var file = CONFIG_PATH.toFile(); - try (var writer = new FileWriter(file, StandardCharsets.UTF_8)) { - writer.write(ByteBufferUtils.asString(JSON_CODEC.serialize(config))); - } + Files.write(CONFIG_PATH, toJson(config), StandardOpenOption.CREATE_NEW); } /** From bfd0b8b8e648e2fa98ae558a1f31923c18dde6c5 Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 6 May 2025 17:55:39 -0700 Subject: [PATCH 09/13] Remove commented out code --- .../java/mcp/cli/commands/StartServer.java | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java index 94838e508..c7508f2c4 100644 --- a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java @@ -82,28 +82,4 @@ public void execute(Config config) { mcpServer.shutdown().join(); } } - - //TODO remove these after external types Schema if fixed. - // private static software.amazon.smithy.modelbundle.api.model.Bundle convert(Bundle bundle) { - // return software.amazon.smithy.modelbundle.api.model.Bundle.builder() - // .config(bundle.getConfig()) - // .configType(bundle.getConfigType()) - // .serviceName(bundle.getServiceName()) - // .model(convert(bundle.getModel())) - // .requestArguments(convert(bundle.getRequestArguments())) - // .build(); - // } - // - // private static software.amazon.smithy.modelbundle.api.model.GenericArguments convert( - // GenericArguments genericArguments - // ) { - // return software.amazon.smithy.modelbundle.api.model.GenericArguments.builder() - // .model(convert(genericArguments.getModel())) - // .identifier(genericArguments.getIdentifier()) - // .build(); - // } - // - // private static software.amazon.smithy.modelbundle.api.model.Model convert(Model model) { - // return software.amazon.smithy.modelbundle.api.model.Model.builder().smithyModel(model.getValue()).build(); - // } } From 7395da8440fbffc03e6e5b763d3a5acaca862719 Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 6 May 2025 18:18:14 -0700 Subject: [PATCH 10/13] More fixes --- .../amazon/smithy/mcp/bundle/api/Bundles.java | 46 ++++++++++++++-- .../smithy/java/mcp/cli/ConfigUtils.java | 3 -- mcp/mcp-schemas/model/main.smithy | 6 +++ .../smithy/java/mcp/server/McpServer.java | 52 +++++++++++++++---- 4 files changed, 88 insertions(+), 19 deletions(-) diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java index ce1c29019..4e2f485aa 100644 --- a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java +++ b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java @@ -13,6 +13,7 @@ import software.amazon.smithy.model.loader.ModelAssembler; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.StreamingTrait; public class Bundles { @@ -43,15 +44,50 @@ private static Model getModel(SmithyBundle bundle) { var model = modelAssemble.assemble().unwrap(); var template = model.expectShape(ShapeId.from(additionalInput.getIdentifier())).asStructureShape().get(); var b = model.toBuilder(); + // mix in the generic arg members for (var op : model.getOperationShapes()) { - var input = model.expectShape(op.getInput().get(), StructureShape.class).toBuilder(); - for (var member : template.members()) { - input.addMember(member.toBuilder() - .id(ShapeId.from(input.getId().toString() + "$" + member.getMemberName())) + boolean skipOperation = false; + if (op.getOutput().isPresent()) { + for (var member : model.expectShape(op.getOutputShape(), StructureShape.class).members()) { + if (model.expectShape(member.getTarget()).hasTrait(StreamingTrait.class)) { + b.removeShape(op.toShapeId()); + skipOperation = true; + break; + } + } + } + + if (skipOperation) { + continue; + } + + if (op.getInput().isEmpty()) { + b.addShape(op.toBuilder() + .input(template) .build()); + } else { + var shape = model.expectShape(op.getInputShape(), StructureShape.class); + for (var member : shape.members()) { + if (model.expectShape(member.getTarget()).hasTrait(StreamingTrait.class)) { + b.removeShape(op.toShapeId()); + skipOperation = true; + break; + } + } + + if (skipOperation) { + continue; + } + + var input = shape.toBuilder(); + for (var member : template.members()) { + input.addMember(member.toBuilder() + .id(ShapeId.from(input.getId().toString() + "$" + member.getMemberName())) + .build()); + } + b.addShape(input.build()); } - b.addShape(input.build()); } for (var service : model.getServiceShapes()) { 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 815cad558..3b727880d 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 @@ -7,9 +7,7 @@ import static software.amazon.smithy.java.io.ByteBufferUtils.getBytes; -import java.io.FileWriter; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -18,7 +16,6 @@ import java.util.HashMap; import java.util.ServiceLoader; import software.amazon.smithy.java.core.schema.SerializableStruct; -import software.amazon.smithy.java.io.ByteBufferUtils; import software.amazon.smithy.java.json.JsonCodec; import software.amazon.smithy.java.mcp.cli.model.Config; import software.amazon.smithy.java.mcp.cli.model.McpBundleConfig; diff --git a/mcp/mcp-schemas/model/main.smithy b/mcp/mcp-schemas/model/main.smithy index 2ef9e5cf3..29e894807 100644 --- a/mcp/mcp-schemas/model/main.smithy +++ b/mcp/mcp-schemas/model/main.smithy @@ -117,6 +117,12 @@ structure ToolInputSchema { properties: PropertiesMap required: StringList + + @required + additionalProperties: PrimitiveBoolean = false + + @jsonName("$schema") + schema: String = "http://json-schema.org/draft-07/schema#" } map PropertiesMap { diff --git a/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpServer.java b/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpServer.java index 797713a5b..3c9478411 100644 --- a/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpServer.java +++ b/mcp/mcp-server/src/main/java/software/amazon/smithy/java/mcp/server/McpServer.java @@ -40,7 +40,6 @@ import software.amazon.smithy.java.server.Operation; import software.amazon.smithy.java.server.Server; import software.amazon.smithy.java.server.Service; -import software.amazon.smithy.model.traits.DocumentationTrait; import software.amazon.smithy.utils.SmithyUnstableApi; @SmithyUnstableApi @@ -51,6 +50,7 @@ public final class McpServer implements Server { private static final JsonCodec CODEC = JsonCodec.builder() .settings(JsonSettings.builder() .serializeTypeInDocuments(false) + .useJsonName(true) .build()) .build(); @@ -186,8 +186,7 @@ private static Map createTools(List serviceList) { .name(operationName) .description(createDescription(serviceName, operationName, - schema - .expectTrait(TraitKey.DOCUMENTATION_TRAIT))) + schema)) .inputSchema(createInputSchema(operation.getApiOperation().inputSchema())) .build(); tools.put(operation.name(), new Tool(toolInfo, operation)); @@ -204,22 +203,53 @@ private static ToolInputSchema createInputSchema(Schema schema) { if (member.hasTrait(TraitKey.REQUIRED_TRAIT)) { requiredProperties.add(name); } - var details = PropertyDetails.builder() - .typeMember(member.type().toString()) - .description(member.expectTrait(TraitKey.DOCUMENTATION_TRAIT).getValue()) - .build(); - properties.put(name, details); + // adapt types to json-schema types + // https://json-schema.org/draft-07/schema# + var type = switch (member.type()) { + case BYTE, SHORT, INTEGER, INT_ENUM, LONG, FLOAT, DOUBLE -> "number"; + case ENUM, BLOB -> "string"; + case LIST, SET -> "array"; + case TIMESTAMP -> resolveTimestampType(member.memberTarget()); + case MAP, DOCUMENT, STRUCTURE, UNION -> "object"; + case STRING, BIG_DECIMAL, BIG_INTEGER -> "string"; + case BOOLEAN -> "boolean"; + default -> throw new RuntimeException("unsupported type: " + member.type() + "on member " + member); + }; + var details = PropertyDetails.builder().typeMember(type); + var documentation = member.getTrait(TraitKey.DOCUMENTATION_TRAIT); + if (documentation != null) { + details.description(documentation.getValue()); + } + properties.put(name, details.build()); } return ToolInputSchema.builder().properties(properties).required(requiredProperties).build(); } + private static String resolveTimestampType(Schema schema) { + var trait = schema.getTrait(TraitKey.TIMESTAMP_FORMAT_TRAIT); + if (trait == null) { + // default is epoch-seconds + return "number"; + } + return switch (trait.getFormat()) { + case EPOCH_SECONDS -> "number"; + case DATE_TIME, HTTP_DATE -> "string"; + default -> throw new RuntimeException("unknown timestamp format: " + trait.getFormat()); + }; + } + private static String createDescription( String serviceName, String operationName, - DocumentationTrait documentationTrait + Schema schema ) { - return "This tool invokes %s API of %s .".formatted(operationName, serviceName) + - documentationTrait.getValue(); + var documentationTrait = schema.getTrait(TraitKey.DOCUMENTATION_TRAIT); + if (documentationTrait != null) { + return "This tool invokes %s API of %s.".formatted(operationName, serviceName) + + documentationTrait.getValue(); + } else { + return "This tool invokes %s API of %s.".formatted(operationName, serviceName); + } } @Override From 8fd380615962bf113ee82cb9679898c892ce133b Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Tue, 6 May 2025 18:21:02 -0700 Subject: [PATCH 11/13] Directly return bundle location --- .../java/aws/mcp/cli/commands/AddAwsServiceBundle.java | 2 +- .../amazon/smithy/java/mcp/cli/AbstractAddBundle.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java index 9940ce555..cfdd69191 100644 --- a/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java +++ b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java @@ -39,7 +39,7 @@ protected CliBundle getNewToolConfig() { var bundleConfig = McpBundleConfig.builder() .smithyModeled(SmithyModeledBundleConfig.builder() .name(awsServiceName) - .bundleLocation(Location.builder().fileLocation(getBundleFileLocation().toString()).build()) + .bundleLocation(getBundleFileLocation()) .build()) .build(); return new CliBundle(bundle, bundleConfig); diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java index f18543a70..af9d64091 100644 --- a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java @@ -8,6 +8,7 @@ import java.nio.file.Path; import java.util.Set; import software.amazon.smithy.java.mcp.cli.model.Config; +import software.amazon.smithy.java.mcp.cli.model.Location; /** * Abstract base class for CLI commands that add tool bundles to the Smithy MCP configuration. @@ -29,8 +30,8 @@ public final void execute(Config config) throws Exception { System.out.println("Added tool bundle " + getToolBundleName()); } - protected final Path getBundleFileLocation() { - return ConfigUtils.getBundleFileLocation(getToolBundleName()); + protected final Location getBundleFileLocation() { + return Location.builder().fileLocation(ConfigUtils.getBundleFileLocation(getToolBundleName()).toString()).build(); } /** From b7a6dea7be6760a6ef2dee87f7d983859bd0315c Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Wed, 7 May 2025 13:29:28 -0700 Subject: [PATCH 12/13] Address feedback --- .../mcp/cli/commands/AddAwsServiceBundle.java | 4 +- .../provider/AwsServiceBundle.java | 27 +++-- .../AwsServiceBundlePluginFactory.java | 4 +- ...mithy.modelbundle.api.BundlePluginFactory} | 0 aws/aws-service-bundler/build.gradle.kts | 2 +- .../bundler/AwsServiceBundler.java | 29 +++-- ...mazon.smithy.mcp.bundle.api.BundlerFactory | 1 - .../bundler/AwsServiceBundlerTest.java | 3 +- .../server/mcp/BundleMCPServerExample.java | 4 +- mcp/mcp-bundle-api/build.gradle.kts | 2 +- mcp/mcp-bundle-api/smithy-build.json | 3 +- .../amazon/smithy/mcp/bundle/api/Bundles.java | 104 ----------------- .../smithy/mcp/bundle/api/McpBundles.java | 22 ++++ .../main/resources/META-INF/smithy/manifest | 2 +- .../META-INF/smithy/mcpbundle.smithy | 19 ++++ .../java/mcp/cli/AbstractAddBundle.java | 5 +- .../smithy/java/mcp/cli/ConfigUtils.java | 2 +- .../mcp/cli/EmptyDefaultConfigProvider.java | 2 +- .../java/mcp/cli/commands/StartServer.java | 4 +- .../model-bundle-api/build.gradle.kts | 51 +++++++++ model-bundle/model-bundle-api/license.txt | 4 + .../model-bundle-api/smithy-build.json | 9 ++ .../smithy/modelbundle}/api/BundlePlugin.java | 2 +- .../modelbundle}/api/BundlePluginFactory.java | 2 +- .../smithy/modelbundle/api/ModelBundler.java | 12 +- .../smithy/modelbundle/api/ModelBundles.java | 106 ++++++++++++++++++ .../modelbundle}/api/PluginProviders.java | 2 +- .../modelbundle}/api/ServiceLoaderLoader.java | 2 +- .../api/StaticAuthSchemeResolver.java | 2 +- .../resources/META-INF/smithy/bundle.smithy | 16 +-- .../main/resources/META-INF/smithy/manifest | 1 + settings.gradle.kts | 5 +- 32 files changed, 273 insertions(+), 180 deletions(-) rename aws/aws-service-bundle/src/main/resources/META-INF/services/{software.amazon.smithy.mcp.bundle.api.BundlePluginFactory => software.amazon.smithy.modelbundle.api.BundlePluginFactory} (100%) delete mode 100644 aws/aws-service-bundler/src/main/resources/META-INF/services/software.amazon.smithy.mcp.bundle.api.BundlerFactory delete mode 100644 mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java create mode 100644 mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/McpBundles.java create mode 100644 mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/mcpbundle.smithy create mode 100644 model-bundle/model-bundle-api/build.gradle.kts create mode 100644 model-bundle/model-bundle-api/license.txt create mode 100644 model-bundle/model-bundle-api/smithy-build.json rename {mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle => model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle}/api/BundlePlugin.java (93%) rename {mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle => model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle}/api/BundlePluginFactory.java (86%) rename mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundler.java => model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ModelBundler.java (68%) create mode 100644 model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ModelBundles.java rename {mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle => model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle}/api/PluginProviders.java (97%) rename {mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle => model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle}/api/ServiceLoaderLoader.java (91%) rename {mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle => model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle}/api/StaticAuthSchemeResolver.java (98%) rename {mcp/mcp-bundle-api => model-bundle/model-bundle-api}/src/main/resources/META-INF/smithy/bundle.smithy (80%) create mode 100644 model-bundle/model-bundle-api/src/main/resources/META-INF/smithy/manifest diff --git a/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java index cfdd69191..e25b4cb2d 100644 --- a/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java +++ b/aws/aws-mcp-cli-commands/src/main/java/software/amazon/smithy/java/aws/mcp/cli/commands/AddAwsServiceBundle.java @@ -11,9 +11,9 @@ import software.amazon.smithy.java.aws.servicebundle.bundler.AwsServiceBundler; import software.amazon.smithy.java.mcp.cli.AbstractAddBundle; import software.amazon.smithy.java.mcp.cli.CliBundle; -import software.amazon.smithy.java.mcp.cli.model.Location; import software.amazon.smithy.java.mcp.cli.model.McpBundleConfig; import software.amazon.smithy.java.mcp.cli.model.SmithyModeledBundleConfig; +import software.amazon.smithy.mcp.bundle.api.model.Bundle; @Command(name = "add-aws-bundle") public class AddAwsServiceBundle extends AbstractAddBundle { @@ -42,7 +42,7 @@ protected CliBundle getNewToolConfig() { .bundleLocation(getBundleFileLocation()) .build()) .build(); - return new CliBundle(bundle, bundleConfig); + return new CliBundle(Bundle.builder().smithyBundle(bundle).build(), bundleConfig); } @Override diff --git a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java index a590cc49c..6b18976a3 100644 --- a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java +++ b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundle.java @@ -21,8 +21,8 @@ import software.amazon.smithy.java.client.core.interceptors.CallHook; import software.amazon.smithy.java.client.core.interceptors.ClientInterceptor; import software.amazon.smithy.java.core.serde.document.Document; -import software.amazon.smithy.mcp.bundle.api.BundlePlugin; -import software.amazon.smithy.mcp.bundle.api.StaticAuthSchemeResolver; +import software.amazon.smithy.modelbundle.api.BundlePlugin; +import software.amazon.smithy.modelbundle.api.StaticAuthSchemeResolver; final class AwsServiceBundle implements BundlePlugin { private final AwsServiceMetadata serviceMetadata; @@ -36,7 +36,7 @@ final class AwsServiceBundle implements BundlePlugin { @Override public > B configureClient(B clientBuilder) { clientBuilder.addInterceptor(new AwsServiceClientInterceptor(serviceMetadata, authScheme)); - clientBuilder.endpointResolver(EndpointResolver.staticEndpoint("http://dummyurl.com")); + clientBuilder.endpointResolver(EndpointResolver.staticEndpoint("http://localhost")); return clientBuilder; } @@ -53,18 +53,17 @@ public ClientConfig modifyBeforeCall(CallHook hook) { var endpoint = URI.create(Objects.requireNonNull(serviceMetadata.getEndpoints().get(input.getAwsRegion()), "no endpoint for region " + input.getAwsRegion())); - try (var sdkCredentialsProvider = ProfileCredentialsProvider.create(input.getAwsProfileName())) { - var identityResolver = new SdkCredentialsResolver(sdkCredentialsProvider); + var identityResolver = + new SdkCredentialsResolver(ProfileCredentialsProvider.create(input.getAwsProfileName())); - return hook.config() - .withRequestOverride(RequestOverrideConfig.builder() - .putConfig(RegionSetting.REGION, input.getAwsRegion()) - .endpointResolver(EndpointResolver.staticEndpoint(endpoint)) - .addIdentityResolver(identityResolver) - .authSchemeResolver(StaticAuthSchemeResolver.getInstance()) - .putSupportedAuthSchemes(StaticAuthSchemeResolver.staticScheme(authScheme)) - .build()); - } + return hook.config() + .withRequestOverride(RequestOverrideConfig.builder() + .putConfig(RegionSetting.REGION, input.getAwsRegion()) + .endpointResolver(EndpointResolver.staticEndpoint(endpoint)) + .addIdentityResolver(identityResolver) + .authSchemeResolver(StaticAuthSchemeResolver.getInstance()) + .putSupportedAuthSchemes(StaticAuthSchemeResolver.staticScheme(authScheme)) + .build()); } } } diff --git a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundlePluginFactory.java b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundlePluginFactory.java index 706f3461f..dce5113c8 100644 --- a/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundlePluginFactory.java +++ b/aws/aws-service-bundle/src/main/java/software/amazon/smithy/java/aws/servicebundle/provider/AwsServiceBundlePluginFactory.java @@ -7,8 +7,8 @@ import software.amazon.smithy.awsmcp.model.AwsServiceMetadata; import software.amazon.smithy.java.core.serde.document.Document; -import software.amazon.smithy.mcp.bundle.api.BundlePlugin; -import software.amazon.smithy.mcp.bundle.api.BundlePluginFactory; +import software.amazon.smithy.modelbundle.api.BundlePlugin; +import software.amazon.smithy.modelbundle.api.BundlePluginFactory; public final class AwsServiceBundlePluginFactory implements BundlePluginFactory { public AwsServiceBundlePluginFactory() { diff --git a/aws/aws-service-bundle/src/main/resources/META-INF/services/software.amazon.smithy.mcp.bundle.api.BundlePluginFactory b/aws/aws-service-bundle/src/main/resources/META-INF/services/software.amazon.smithy.modelbundle.api.BundlePluginFactory similarity index 100% rename from aws/aws-service-bundle/src/main/resources/META-INF/services/software.amazon.smithy.mcp.bundle.api.BundlePluginFactory rename to aws/aws-service-bundle/src/main/resources/META-INF/services/software.amazon.smithy.modelbundle.api.BundlePluginFactory diff --git a/aws/aws-service-bundler/build.gradle.kts b/aws/aws-service-bundler/build.gradle.kts index 86337d0f2..4691b0ad7 100644 --- a/aws/aws-service-bundler/build.gradle.kts +++ b/aws/aws-service-bundler/build.gradle.kts @@ -8,7 +8,7 @@ extra["displayName"] = "Smithy :: Java :: AWS :: Service Bundler" extra["moduleName"] = "software.amazon.smithy.java.aws.servicebundle.bundler" dependencies { - implementation(project(":mcp:mcp-bundle-api")) + implementation(project(":model-bundle:model-bundle-api")) implementation(libs.smithy.model) implementation(project(":aws:aws-mcp-types")) // we need to be able to resolve the sigv4 and protocol traits diff --git a/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java b/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java index f506d2e1e..69df4c46a 100644 --- a/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java +++ b/aws/aws-service-bundler/src/main/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundler.java @@ -21,10 +21,6 @@ import software.amazon.smithy.awsmcp.model.AwsServiceMetadata; import software.amazon.smithy.awsmcp.model.PreRequest; import software.amazon.smithy.java.core.serde.document.Document; -import software.amazon.smithy.mcp.bundle.api.Bundler; -import software.amazon.smithy.mcp.bundle.api.model.AdditionalInput; -import software.amazon.smithy.mcp.bundle.api.model.Bundle; -import software.amazon.smithy.mcp.bundle.api.model.SmithyBundle; import software.amazon.smithy.model.loader.ModelAssembler; import software.amazon.smithy.model.node.BooleanNode; import software.amazon.smithy.model.node.Node; @@ -32,8 +28,11 @@ import software.amazon.smithy.model.shapes.ModelSerializer; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.EndpointTrait; +import software.amazon.smithy.modelbundle.api.ModelBundler; +import software.amazon.smithy.modelbundle.api.model.AdditionalInput; +import software.amazon.smithy.modelbundle.api.model.SmithyBundle; -public final class AwsServiceBundler extends Bundler { +public final class AwsServiceBundler extends ModelBundler { private static final ShapeId ENDPOINT_TESTS = ShapeId.from("smithy.rules#endpointTests"); // visible for testing @@ -65,7 +64,7 @@ public AwsServiceBundler(String serviceName) { } @Override - public Bundle bundle() { + public SmithyBundle bundle() { try { var modelString = resolver.getModel(serviceName); var model = new ModelAssembler() @@ -92,16 +91,14 @@ public Bundle bundle() { bundle.endpoints(parseEndpoints(trait.toNode().expectObjectNode())); } } - return Bundle.builder() - .smithyBundle(SmithyBundle.builder() - .config(Document.of(bundle.build())) - .configType("aws") - .serviceName(model.getServiceShapes().iterator().next().getId().toString()) - .model(serializeModel(model)) - .additionalInput(AdditionalInput.builder() - .identifier(PreRequest.$ID.toString()) - .model(loadModel("/META-INF/smithy/bundle.smithy")) - .build()) + return SmithyBundle.builder() + .config(Document.of(bundle.build())) + .configType("aws") + .serviceName(model.getServiceShapes().iterator().next().getId().toString()) + .model(serializeModel(model)) + .additionalInput(AdditionalInput.builder() + .identifier(PreRequest.$ID.toString()) + .model(loadModel("/META-INF/smithy/bundle.smithy")) .build()) .build(); } catch (Exception e) { diff --git a/aws/aws-service-bundler/src/main/resources/META-INF/services/software.amazon.smithy.mcp.bundle.api.BundlerFactory b/aws/aws-service-bundler/src/main/resources/META-INF/services/software.amazon.smithy.mcp.bundle.api.BundlerFactory deleted file mode 100644 index 3a4b26378..000000000 --- a/aws/aws-service-bundler/src/main/resources/META-INF/services/software.amazon.smithy.mcp.bundle.api.BundlerFactory +++ /dev/null @@ -1 +0,0 @@ -software.amazon.smithy.java.aws.servicebundle.bundler.AwsServiceBundlerFactory \ No newline at end of file diff --git a/aws/aws-service-bundler/src/test/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerTest.java b/aws/aws-service-bundler/src/test/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerTest.java index da39fb7bd..349d9adaa 100644 --- a/aws/aws-service-bundler/src/test/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerTest.java +++ b/aws/aws-service-bundler/src/test/java/software/amazon/smithy/java/aws/servicebundle/bundler/AwsServiceBundlerTest.java @@ -12,13 +12,12 @@ import java.util.Objects; import org.junit.jupiter.api.Test; import software.amazon.smithy.awsmcp.model.AwsServiceMetadata; -import software.amazon.smithy.mcp.bundle.api.model.SmithyBundle; public class AwsServiceBundlerTest { @Test public void accessAnalyzer() { var bundler = new AwsServiceBundler("accessanalyzer-2019-11-01.json", AwsServiceBundlerTest::getModel); - SmithyBundle bundle = bundler.bundle().getValue(); + var bundle = bundler.bundle(); var config = bundle.getConfig().asShape(AwsServiceMetadata.builder()); assertEquals("access-analyzer", config.getSigv4SigningName()); diff --git a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java index 2fd6bf690..e4986f9ff 100644 --- a/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java +++ b/examples/mcp-server/src/main/java/software/amazon/smithy/java/example/server/mcp/BundleMCPServerExample.java @@ -3,7 +3,7 @@ import software.amazon.smithy.java.json.JsonCodec; import software.amazon.smithy.java.mcp.server.McpServer; import software.amazon.smithy.mcp.bundle.api.model.Bundle; -import software.amazon.smithy.mcp.bundle.api.Bundles; +import software.amazon.smithy.mcp.bundle.api.McpBundles; import java.util.Objects; @@ -14,7 +14,7 @@ public static void main(String[] args) throws Exception { var mcpServer = McpServer.builder() .stdio() .name("smithy-mcp-server") - .addService(Bundles.getService(bundle.getValue())) + .addService(McpBundles.getService(bundle.getValue())) .build(); mcpServer.start(); diff --git a/mcp/mcp-bundle-api/build.gradle.kts b/mcp/mcp-bundle-api/build.gradle.kts index bee4546de..ede0fed45 100644 --- a/mcp/mcp-bundle-api/build.gradle.kts +++ b/mcp/mcp-bundle-api/build.gradle.kts @@ -14,8 +14,8 @@ dependencies { implementation(project(":core")) implementation(libs.smithy.model) - api(project(":client:client-auth-api")) api(project(":server:server-api")) + api(project(":model-bundle:model-bundle-api")) api(project(":client:client-core")) api(project(":dynamic-schemas")) implementation(project(":server:server-proxy")) diff --git a/mcp/mcp-bundle-api/smithy-build.json b/mcp/mcp-bundle-api/smithy-build.json index 357bce589..dcdd30843 100644 --- a/mcp/mcp-bundle-api/smithy-build.json +++ b/mcp/mcp-bundle-api/smithy-build.json @@ -3,7 +3,8 @@ "plugins": { "java-type-codegen": { "namespace": "software.amazon.smithy.mcp.bundle.api", - "headerFile": "license.txt" + "headerFile": "license.txt", + "useExternalTypes": true } } } diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java deleted file mode 100644 index 4e2f485aa..000000000 --- a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundles.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.mcp.bundle.api; - -import software.amazon.smithy.java.server.ProxyService; -import software.amazon.smithy.java.server.Service; -import software.amazon.smithy.mcp.bundle.api.model.Bundle; -import software.amazon.smithy.mcp.bundle.api.model.SmithyBundle; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.loader.ModelAssembler; -import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.model.traits.StreamingTrait; - -public class Bundles { - - private static final PluginProviders PLUGIN_PROVIDERS = PluginProviders.builder().build(); - - private Bundles() {} - - public static Service getService(Bundle bundle) { - if (bundle.type() != Bundle.Type.smithyBundle) { - throw new IllegalArgumentException("Bundle is not a smithy bundle"); - } - SmithyBundle smithyBundle = bundle.getValue(); - var model = getModel(smithyBundle); - var plugin = PLUGIN_PROVIDERS.getPlugin(smithyBundle.getConfigType(), smithyBundle.getConfig()); - return ProxyService.builder() - .model(model) - .clientConfigurator(plugin::configureClient) - .service(ShapeId.from(smithyBundle.getServiceName())) - .build(); - } - - private static Model getModel(SmithyBundle bundle) { - var modelAssemble = new ModelAssembler().putProperty(ModelAssembler.ALLOW_UNKNOWN_TRAITS, true) - .addUnparsedModel("bundle.json", bundle.getModel()); - var additionalInput = bundle.getAdditionalInput(); - if (additionalInput != null) { - modelAssemble.addUnparsedModel("additionalInput.smithy", additionalInput.getModel()); - var model = modelAssemble.assemble().unwrap(); - var template = model.expectShape(ShapeId.from(additionalInput.getIdentifier())).asStructureShape().get(); - var b = model.toBuilder(); - - // mix in the generic arg members - for (var op : model.getOperationShapes()) { - boolean skipOperation = false; - if (op.getOutput().isPresent()) { - for (var member : model.expectShape(op.getOutputShape(), StructureShape.class).members()) { - if (model.expectShape(member.getTarget()).hasTrait(StreamingTrait.class)) { - b.removeShape(op.toShapeId()); - skipOperation = true; - break; - } - } - } - - if (skipOperation) { - continue; - } - - if (op.getInput().isEmpty()) { - b.addShape(op.toBuilder() - .input(template) - .build()); - } else { - var shape = model.expectShape(op.getInputShape(), StructureShape.class); - for (var member : shape.members()) { - if (model.expectShape(member.getTarget()).hasTrait(StreamingTrait.class)) { - b.removeShape(op.toShapeId()); - skipOperation = true; - break; - } - } - - if (skipOperation) { - continue; - } - - var input = shape.toBuilder(); - for (var member : template.members()) { - input.addMember(member.toBuilder() - .id(ShapeId.from(input.getId().toString() + "$" + member.getMemberName())) - .build()); - } - b.addShape(input.build()); - } - } - - for (var service : model.getServiceShapes()) { - b.addShape(service.toBuilder() - // trim the endpoint rules because they're huge and we don't need them - .removeTrait(ShapeId.from("smithy.rules#endpointRuleSet")) - .removeTrait(ShapeId.from("smithy.rules#endpointTests")) - .build()); - } - return b.build(); - } - return modelAssemble.assemble().unwrap(); - } -} diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/McpBundles.java b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/McpBundles.java new file mode 100644 index 000000000..91c658272 --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/McpBundles.java @@ -0,0 +1,22 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.mcp.bundle.api; + +import software.amazon.smithy.java.server.Service; +import software.amazon.smithy.mcp.bundle.api.model.Bundle; +import software.amazon.smithy.modelbundle.api.ModelBundles; + +public class McpBundles { + + private McpBundles() {} + + public static Service getService(Bundle bundle) { + return switch (bundle.type()) { + case smithyBundle -> ModelBundles.getService(bundle.getValue()); + default -> throw new IllegalArgumentException("Unsupported bundle type: " + bundle.type()); + }; + } +} diff --git a/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/manifest b/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/manifest index b20775b86..946b7ad4c 100644 --- a/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/manifest +++ b/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/manifest @@ -1 +1 @@ -bundle.smithy \ No newline at end of file +mcpbundle.smithy \ No newline at end of file diff --git a/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/mcpbundle.smithy b/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/mcpbundle.smithy new file mode 100644 index 000000000..e24e213b2 --- /dev/null +++ b/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/mcpbundle.smithy @@ -0,0 +1,19 @@ +$version: "2" + +namespace software.amazon.smithy.mcp.bundle.api + +use software.amazon.smithy.modelbundle.api#SmithyBundle + +union Bundle { + smithyBundle: SmithyBundle +} + +structure BundleMetadata { + @required + name: String + + @required + description: String + + version: String +} diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java index af9d64091..7f24d0851 100644 --- a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/AbstractAddBundle.java @@ -5,7 +5,6 @@ package software.amazon.smithy.java.mcp.cli; -import java.nio.file.Path; import java.util.Set; import software.amazon.smithy.java.mcp.cli.model.Config; import software.amazon.smithy.java.mcp.cli.model.Location; @@ -31,7 +30,9 @@ public final void execute(Config config) throws Exception { } protected final Location getBundleFileLocation() { - return Location.builder().fileLocation(ConfigUtils.getBundleFileLocation(getToolBundleName()).toString()).build(); + return Location.builder() + .fileLocation(ConfigUtils.getBundleFileLocation(getToolBundleName()).toString()) + .build(); } /** 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 3b727880d..e9045afb8 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 @@ -96,7 +96,7 @@ static Path getBundleFileLocation(String bundleName) { * @throws IOException If there's an error writing to the file */ public static void updateConfig(Config config) throws IOException { - Files.write(CONFIG_PATH, toJson(config), StandardOpenOption.CREATE_NEW); + Files.write(CONFIG_PATH, toJson(config), StandardOpenOption.TRUNCATE_EXISTING); } /** diff --git a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/EmptyDefaultConfigProvider.java b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/EmptyDefaultConfigProvider.java index f3091975d..93150a7d8 100644 --- a/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/EmptyDefaultConfigProvider.java +++ b/mcp/mcp-cli-api/src/main/java/software/amazon/smithy/java/mcp/cli/EmptyDefaultConfigProvider.java @@ -7,7 +7,7 @@ import software.amazon.smithy.java.mcp.cli.model.Config; -public class EmptyDefaultConfigProvider implements DefaultConfigProvider { +public final class EmptyDefaultConfigProvider implements DefaultConfigProvider { @Override public Config getConfig() { diff --git a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java index c7508f2c4..20c4261bf 100644 --- a/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java +++ b/mcp/mcp-cli/src/main/java/software/amazon/smithy/java/mcp/cli/commands/StartServer.java @@ -18,7 +18,7 @@ import software.amazon.smithy.java.server.FilteredService; import software.amazon.smithy.java.server.OperationFilters; import software.amazon.smithy.java.server.Service; -import software.amazon.smithy.mcp.bundle.api.Bundles; +import software.amazon.smithy.mcp.bundle.api.McpBundles; /** * Command to start a Smithy MCP server exposing specified tool bundles. @@ -62,7 +62,7 @@ public void execute(Config config) { case smithyModeled -> { SmithyModeledBundleConfig bundleConfig = toolBundleConfig.getValue(); Service service = - Bundles.getService(ConfigUtils.getMcpBundle(bundleConfig.getName())); + McpBundles.getService(ConfigUtils.getMcpBundle(bundleConfig.getName())); if (bundleConfig.hasAllowListedTools() || bundleConfig.hasBlockListedTools()) { var filter = OperationFilters.allowList(bundleConfig.getAllowListedTools()) .and(OperationFilters.blockList(bundleConfig.getBlockListedTools())); diff --git a/model-bundle/model-bundle-api/build.gradle.kts b/model-bundle/model-bundle-api/build.gradle.kts new file mode 100644 index 000000000..8b99cd7c0 --- /dev/null +++ b/model-bundle/model-bundle-api/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + application + id("smithy-java.module-conventions") + id("software.amazon.smithy.gradle.smithy-base") +} + +description = "This module implements the model-bundle utility" + +extra["displayName"] = "Smithy :: Java :: Model Bundle" +extra["moduleName"] = "software.amazon.smithy.java.modelbundle.api" + +dependencies { + smithyBuild(project(":codegen:plugins:types-codegen")) + + implementation(project(":core")) + implementation(libs.smithy.model) + api(project(":client:client-auth-api")) + api(project(":client:client-core")) + api(project(":dynamic-schemas")) + api(project(":server:server-api")) + api(project(":server:server-proxy")) +} + +afterEvaluate { + val typePath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-type-codegen") + sourceSets { + main { + java { + srcDir(typePath) + include("software/**") + } + resources { + srcDir(typePath) + include("META-INF/**") + } + } + } +} + +tasks.named("compileJava") { + dependsOn("smithyBuild") +} + +// Needed because sources-jar needs to run after smithy-build is done +tasks.sourcesJar { + mustRunAfter(tasks.compileJava) +} + +tasks.processResources { + dependsOn(tasks.compileJava) +} diff --git a/model-bundle/model-bundle-api/license.txt b/model-bundle/model-bundle-api/license.txt new file mode 100644 index 000000000..5f97ab495 --- /dev/null +++ b/model-bundle/model-bundle-api/license.txt @@ -0,0 +1,4 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ \ No newline at end of file diff --git a/model-bundle/model-bundle-api/smithy-build.json b/model-bundle/model-bundle-api/smithy-build.json new file mode 100644 index 000000000..5a78c012c --- /dev/null +++ b/model-bundle/model-bundle-api/smithy-build.json @@ -0,0 +1,9 @@ +{ + "version": "1.0", + "plugins": { + "java-type-codegen": { + "namespace": "software.amazon.smithy.modelbundle.api", + "headerFile": "license.txt" + } + } +} \ No newline at end of file diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePlugin.java b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java similarity index 93% rename from mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePlugin.java rename to model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java index b310103b6..67a8a9f5f 100644 --- a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePlugin.java +++ b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePlugin.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.mcp.bundle.api; +package software.amazon.smithy.modelbundle.api; import software.amazon.smithy.java.client.core.Client; import software.amazon.smithy.java.client.core.RequestOverrideConfig; diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePluginFactory.java b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePluginFactory.java similarity index 86% rename from mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePluginFactory.java rename to model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePluginFactory.java index dde289d3c..2461018ab 100644 --- a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/BundlePluginFactory.java +++ b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/BundlePluginFactory.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.mcp.bundle.api; +package software.amazon.smithy.modelbundle.api; import software.amazon.smithy.java.core.serde.document.Document; diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundler.java b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ModelBundler.java similarity index 68% rename from mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundler.java rename to model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ModelBundler.java index aab6de56c..ca20a46b6 100644 --- a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/Bundler.java +++ b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ModelBundler.java @@ -3,26 +3,26 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.mcp.bundle.api; +package software.amazon.smithy.modelbundle.api; import java.io.BufferedReader; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.stream.Collectors; -import software.amazon.smithy.mcp.bundle.api.model.Bundle; +import software.amazon.smithy.modelbundle.api.model.SmithyBundle; import software.amazon.smithy.utils.SmithyInternalApi; import software.amazon.smithy.utils.SmithyUnstableApi; @SmithyInternalApi @SmithyUnstableApi -public abstract class Bundler { +public abstract class ModelBundler { - public abstract Bundle bundle(); + public abstract SmithyBundle bundle(); - protected String loadModel(String path) { + protected static String loadModel(String path) { try (var reader = new BufferedReader(new InputStreamReader( - Objects.requireNonNull(Bundler.class.getResourceAsStream(path)), + Objects.requireNonNull(ModelBundler.class.getResourceAsStream(path)), StandardCharsets.UTF_8))) { return reader.lines().collect(Collectors.joining("\n")); } catch (Exception e) { diff --git a/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ModelBundles.java b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ModelBundles.java new file mode 100644 index 000000000..61c184f0c --- /dev/null +++ b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ModelBundles.java @@ -0,0 +1,106 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.modelbundle.api; + +import software.amazon.smithy.java.server.ProxyService; +import software.amazon.smithy.java.server.Service; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.loader.ModelAssembler; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.StreamingTrait; +import software.amazon.smithy.modelbundle.api.model.SmithyBundle; + +public final class ModelBundles { + + private ModelBundles() {} + + private static final PluginProviders PLUGIN_PROVIDERS = PluginProviders.builder().build(); + + public static Service getService(SmithyBundle smithyBundle) { + var model = getModel(smithyBundle); + var plugin = PLUGIN_PROVIDERS.getPlugin(smithyBundle.getConfigType(), smithyBundle.getConfig()); + return ProxyService.builder() + .model(model) + .clientConfigurator(plugin::configureClient) + .service(ShapeId.from(smithyBundle.getServiceName())) + .build(); + } + + private static Model getModel(SmithyBundle bundle) { + var modelAssemble = new ModelAssembler().putProperty(ModelAssembler.ALLOW_UNKNOWN_TRAITS, true) + .addUnparsedModel("bundle.json", bundle.getModel()); + var additionalInput = bundle.getAdditionalInput(); + Model model; + StructureShape additionalInputShape = null; + if (additionalInput != null) { + modelAssemble.addUnparsedModel("additionalInput.smithy", additionalInput.getModel()); + model = modelAssemble.assemble().unwrap(); + additionalInputShape = + model.expectShape(ShapeId.from(additionalInput.getIdentifier())).asStructureShape().get(); + + } else { + model = modelAssemble.assemble().unwrap(); + } + var b = model.toBuilder(); + + // mix in the generic arg members + for (var op : model.getOperationShapes()) { + boolean skipOperation = false; + if (op.getOutput().isPresent()) { + for (var member : model.expectShape(op.getOutputShape(), StructureShape.class).members()) { + if (model.expectShape(member.getTarget()).hasTrait(StreamingTrait.class)) { + b.removeShape(op.toShapeId()); + skipOperation = true; + break; + } + } + } + + if (skipOperation) { + continue; + } + + if (op.getInput().isEmpty() && additionalInputShape != null) { + b.addShape(op.toBuilder() + .input(additionalInputShape) + .build()); + } else { + var shape = model.expectShape(op.getInputShape(), StructureShape.class); + for (var member : shape.members()) { + if (model.expectShape(member.getTarget()).hasTrait(StreamingTrait.class)) { + b.removeShape(op.toShapeId()); + skipOperation = true; + break; + } + } + + if (skipOperation) { + continue; + } + + if (additionalInputShape != null) { + var input = shape.toBuilder(); + for (var member : additionalInputShape.members()) { + input.addMember(member.toBuilder() + .id(ShapeId.from(input.getId().toString() + "$" + member.getMemberName())) + .build()); + } + b.addShape(input.build()); + } + } + } + + for (var service : model.getServiceShapes()) { + b.addShape(service.toBuilder() + // trim the endpoint rules because they're huge and we don't need them + .removeTrait(ShapeId.from("smithy.rules#endpointRuleSet")) + .removeTrait(ShapeId.from("smithy.rules#endpointTests")) + .build()); + } + return b.build(); + } +} diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/PluginProviders.java b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java similarity index 97% rename from mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/PluginProviders.java rename to model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java index 1309ea140..35337c8a8 100644 --- a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/PluginProviders.java +++ b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/PluginProviders.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.mcp.bundle.api; +package software.amazon.smithy.modelbundle.api; import java.util.HashMap; import java.util.Map; diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/ServiceLoaderLoader.java b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ServiceLoaderLoader.java similarity index 91% rename from mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/ServiceLoaderLoader.java rename to model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ServiceLoaderLoader.java index 3a76717e1..b6e4f049e 100644 --- a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/ServiceLoaderLoader.java +++ b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/ServiceLoaderLoader.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.mcp.bundle.api; +package software.amazon.smithy.modelbundle.api; import java.util.HashMap; import java.util.Map; diff --git a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/StaticAuthSchemeResolver.java b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/StaticAuthSchemeResolver.java similarity index 98% rename from mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/StaticAuthSchemeResolver.java rename to model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/StaticAuthSchemeResolver.java index c95e3b53e..eb3ccccf1 100644 --- a/mcp/mcp-bundle-api/src/main/java/software/amazon/smithy/mcp/bundle/api/StaticAuthSchemeResolver.java +++ b/model-bundle/model-bundle-api/src/main/java/software/amazon/smithy/modelbundle/api/StaticAuthSchemeResolver.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package software.amazon.smithy.mcp.bundle.api; +package software.amazon.smithy.modelbundle.api; import java.util.List; import software.amazon.smithy.java.auth.api.Signer; diff --git a/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/bundle.smithy b/model-bundle/model-bundle-api/src/main/resources/META-INF/smithy/bundle.smithy similarity index 80% rename from mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/bundle.smithy rename to model-bundle/model-bundle-api/src/main/resources/META-INF/smithy/bundle.smithy index d17356105..da40288e4 100644 --- a/mcp/mcp-bundle-api/src/main/resources/META-INF/smithy/bundle.smithy +++ b/model-bundle/model-bundle-api/src/main/resources/META-INF/smithy/bundle.smithy @@ -1,10 +1,6 @@ $version: "2" -namespace software.amazon.smithy.mcp.bundle.api - -union Bundle { - smithyBundle: SmithyBundle -} +namespace software.amazon.smithy.modelbundle.api structure SmithyBundle { /// unique identifier for the configuration type. used to resolve the appropriate Bundler. @@ -28,16 +24,6 @@ structure SmithyBundle { additionalInput: AdditionalInput } -structure BundleMetadata { - @required - name: String - - @required - description: String - - version: String -} - string SmithyModel structure AdditionalInput { diff --git a/model-bundle/model-bundle-api/src/main/resources/META-INF/smithy/manifest b/model-bundle/model-bundle-api/src/main/resources/META-INF/smithy/manifest new file mode 100644 index 000000000..b20775b86 --- /dev/null +++ b/model-bundle/model-bundle-api/src/main/resources/META-INF/smithy/manifest @@ -0,0 +1 @@ +bundle.smithy \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index dd7d0a295..b41b81968 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -94,4 +94,7 @@ include(":mcp:mcp-schemas") include(":mcp:mcp-server") include(":mcp:mcp-cli") include(":mcp:mcp-cli-api") -include(":mcp:mcp-bundle-api") \ No newline at end of file +include(":mcp:mcp-bundle-api") + +include(":model-bundle") +include(":model-bundle:model-bundle-api") \ No newline at end of file From 7945c47d8e85f964930714b8a7aa4e3e6c0881ac Mon Sep 17 00:00:00 2001 From: Adwait Kumar Singh Date: Wed, 7 May 2025 13:35:52 -0700 Subject: [PATCH 13/13] Move to CREATE --- .../java/software/amazon/smithy/java/mcp/cli/ConfigUtils.java | 2 +- 1 file changed, 1 insertion(+), 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 e9045afb8..33e711b9a 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 @@ -79,7 +79,7 @@ public static Config loadOrCreateConfig() throws IOException { if (!CONFIG_PATH.toFile().exists()) { // Create an empty JSON object as the default config - Files.write(CONFIG_PATH, toJson(DEFAULT_CONFIG_PROVIDER.getConfig()), StandardOpenOption.CREATE_NEW); + Files.write(CONFIG_PATH, toJson(DEFAULT_CONFIG_PROVIDER.getConfig()), StandardOpenOption.CREATE); } return fromJson(Files.readAllBytes(CONFIG_PATH));