From 5af485610b0315361d7c015b291ee1081c5025e8 Mon Sep 17 00:00:00 2001 From: John O'Reilly Date: Sun, 27 Jul 2025 18:35:02 +0100 Subject: [PATCH 1/7] Remote MCP Server --- agents/src/main/kotlin/adk/adk_agent.kt | 55 ++++++++++++---- backend/.gitignore | 1 + backend/build.gradle.kts | 62 +++++++++++++++++++ backend/src/jvmMain/kotlin/Server.kt | 30 +++++++++ .../data/ClimateTraceRepository.kt | 7 ++- .../climatetrace/remote/ClimateTraceApi.kt | 12 ++++ .../xcschemes/iosApp.xcscheme | 44 ++----------- settings.gradle.kts | 1 + 8 files changed, 157 insertions(+), 55 deletions(-) create mode 100644 backend/.gitignore create mode 100644 backend/build.gradle.kts create mode 100644 backend/src/jvmMain/kotlin/Server.kt diff --git a/agents/src/main/kotlin/adk/adk_agent.kt b/agents/src/main/kotlin/adk/adk_agent.kt index d368ff2..36ce9f3 100644 --- a/agents/src/main/kotlin/adk/adk_agent.kt +++ b/agents/src/main/kotlin/adk/adk_agent.kt @@ -6,6 +6,9 @@ import com.google.adk.agents.LlmAgent import com.google.adk.events.Event import com.google.adk.models.Gemini import com.google.adk.runner.InMemoryRunner +import com.google.adk.tools.AgentTool +import com.google.adk.tools.BaseTool +import com.google.adk.tools.GoogleSearchTool import com.google.adk.tools.LongRunningFunctionTool import com.google.adk.tools.mcp.McpToolset import com.google.genai.Client @@ -26,30 +29,56 @@ class ClimateTraceAgent { fun initAgent(): BaseAgent { val apiKeyGoogle = "" -// val mcpTools = McpToolset( -// ServerParameters -// .builder("java") -// .args("-jar", "/Users/joreilly/dev/github/ClimateTraceKMP/mcp-server/build/libs/serverAll.jar", "--stdio") -// .build() -// ).loadTools().join() + val climateTools = McpToolset( + ServerParameters + .builder("java") + .args("-jar", "/Users/joreilly/dev/github/ClimateTraceKMP/mcp-server/build/libs/serverAll.jar", "--stdio") + .build() + ).loadTools().join() + +// val getCountriesTool = LongRunningFunctionTool.create(ClimateTraceTool::class.java, "getCountries") +// val getEmissionsTool = LongRunningFunctionTool.create(ClimateTraceTool::class.java, "getEmissions") +// val climateTools = listOf(getCountriesTool, getEmissionsTool, GoogleSearchTool()) - val getCountriesTool = LongRunningFunctionTool.create(ClimateTraceTool::class.java, "getCountries") - val getEmissionsTool = LongRunningFunctionTool.create(ClimateTraceTool::class.java, "getEmissions") - val mcpTools = listOf(getCountriesTool, getEmissionsTool) val model = Gemini( - "gemini-1.5-pro", + "gemini-2.0-flash", Client.builder() .apiKey(apiKeyGoogle) .build() ) - return LlmAgent.builder() - .name(NAME) + + val searchAgent = LlmAgent.builder() + .name("SearchAgent") + .model(model) + .description("Google Search agent") + .instruction("You're a specialist in Google Search") + .tools(GoogleSearchTool()) + .build() + + val climateAgent = LlmAgent.builder() + .name("ClimateAgent") .model(model) .description("Agent to answer climate emissions related questions.") .instruction("You are an agent that provides climate emissions related information. Use 3 letter country codes.") - .tools(mcpTools) + .tools(climateTools) + .build() + + return LlmAgent.builder() + .name(NAME) + .description("Agent to answer climate emissions related questions.") + //.instruction("You are an agent that provides climate emissions related information. Use 3 letter country codes. Use SearchAgent for population data.") + .instruction(""" + You are an agent that provides climate emissions related information. + + Use `ClimateAgent` for emission data. + Use `SearchAgent` for population data. + """) + + .model(model) + //.subAgents(searchAgent, climateAgent) + .tools(AgentTool.create(searchAgent), AgentTool.create(climateAgent)) .build() } } diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts new file mode 100644 index 0000000..9d967d9 --- /dev/null +++ b/backend/build.gradle.kts @@ -0,0 +1,62 @@ +@file:OptIn(ExperimentalKotlinGradlePluginApi::class) + +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + +plugins { + kotlin("multiplatform") + alias(libs.plugins.kotlinx.serialization) + alias(libs.plugins.shadowPlugin) + alias(libs.plugins.jib) +} + +kotlin { + jvm() { + withJava() + binaries { + executable { + mainClass.set("ServerKt") + } + } + } + + sourceSets { + jvmMain.dependencies { + implementation(libs.kotlinx.coroutines) + implementation(libs.kotlinx.serialization) + + implementation("io.ktor:ktor-server-core:3.2.1") + implementation("io.ktor:ktor-server-netty:3.2.1") + implementation("io.ktor:ktor-server-cors:3.2.1") + implementation("io.ktor:ktor-serialization-kotlinx-json:3.2.1") + implementation("io.ktor:ktor-server-content-negotiation:3.2.1") + + implementation("ch.qos.logback:logback-classic:1.5.8") + + //implementation(projects.composeApp) + implementation(libs.mcp.kotlin) + implementation(projects.mcpServer) + } + } +} + +tasks.named("shadowJar") { + manifest { + attributes["Main-Class"] = "ServerKt" + } +} + +tasks.withType { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } +tasks.withType { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } + + +jib { + from.image = "docker.io/library/eclipse-temurin:21" + + to { + image = "gcr.io/climatetrace-mcp/climatetrace-mcp-server" + } + container { + ports = listOf("8080") + mainClass = "ServerKt" + } +} \ No newline at end of file diff --git a/backend/src/jvmMain/kotlin/Server.kt b/backend/src/jvmMain/kotlin/Server.kt new file mode 100644 index 0000000..3d68d82 --- /dev/null +++ b/backend/src/jvmMain/kotlin/Server.kt @@ -0,0 +1,30 @@ +import io.ktor.server.application.* +import io.ktor.server.cio.CIO +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import io.ktor.server.response.respond +import io.ktor.server.routing.* +import io.ktor.server.sse.SSE +import io.modelcontextprotocol.kotlin.sdk.server.mcp + + + +fun main() { + val server = configureMcpServer() + + val port = System.getenv().getOrDefault("PORT", "8080").toInt() + embeddedServer(CIO, port, host = "0.0.0.0") { + install(SSE) + + routing { + mcp { + server + } + + get("/hi") { + call.respond("hello!!") + + } + } + }.start(wait = true) +} diff --git a/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/data/ClimateTraceRepository.kt b/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/data/ClimateTraceRepository.kt index 18721cf..8313769 100644 --- a/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/data/ClimateTraceRepository.kt +++ b/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/data/ClimateTraceRepository.kt @@ -10,9 +10,10 @@ class ClimateTraceRepository( private val api: ClimateTraceApi ) { suspend fun fetchCountries() : List { - val countries: List? = store.get() - if (countries.isNullOrEmpty()) return api.fetchCountries().also { store.set(it) } - return countries +// val countries: List? = store.get() +// if (countries.isNullOrEmpty()) return api.fetchCountries().also { store.set(it) } +// return countries + return api.fetchCountries() } suspend fun fetchCountryEmissionsInfo(countryCode: String, year: String) = api.fetchCountryEmissionsInfo(listOf(countryCode), year) diff --git a/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/remote/ClimateTraceApi.kt b/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/remote/ClimateTraceApi.kt index 61c7e27..48e914c 100644 --- a/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/remote/ClimateTraceApi.kt +++ b/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/remote/ClimateTraceApi.kt @@ -6,9 +6,21 @@ import io.ktor.client.request.get import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +/** + * Represents the result of an API call to fetch assets. + * @property assets A list of [Asset] objects. + */ @Serializable data class AssetsResult(val assets: List) +/** + * Represents a single asset. + * @property id The unique identifier of the asset. + * @property name The name of the asset. + * @property assetType The type of the asset. + * @property sector The sector to which the asset belongs. + * @property thumbnail A URL to a thumbnail image for the asset. + */ @Serializable data class Asset( @SerialName("Id") diff --git a/iosApp/iosApp.xcodeproj/xcuserdata/joreilly.xcuserdatad/xcschemes/iosApp.xcscheme b/iosApp/iosApp.xcodeproj/xcuserdata/joreilly.xcuserdatad/xcschemes/iosApp.xcscheme index d7bc3b2..b655292 100644 --- a/iosApp/iosApp.xcodeproj/xcuserdata/joreilly.xcuserdatad/xcschemes/iosApp.xcscheme +++ b/iosApp/iosApp.xcodeproj/xcuserdata/joreilly.xcuserdatad/xcschemes/iosApp.xcscheme @@ -1,16 +1,10 @@ - + version = "1.3"> + + buildForRunning = "YES"> - - - + - - - - - - diff --git a/settings.gradle.kts b/settings.gradle.kts index 8e5292f..895c2d7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,4 +23,5 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") rootProject.name = "ClimateTraceKMP" include(":composeApp") include(":mcp-server") +include(":backend") include(":agents") From 4b675b94f73092647843e0c8547e1d80339f2008 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Mon, 28 Jul 2025 11:54:13 +0100 Subject: [PATCH 2/7] native image support --- gradle/libs.versions.toml | 2 + gradle/wrapper/gradle-wrapper.properties | 2 +- mcp-server/build.gradle.kts | 31 +- .../climatetrace/reachability-metadata.json | 597 ++++++++++++++++++ 4 files changed, 624 insertions(+), 8 deletions(-) create mode 100644 mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eda77c9..8d3cbf1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,6 +20,7 @@ kmpNativeCoroutines = "1.0.0-ALPHA-45" kmpObservableViewModel = "1.0.0-BETA-12" kstore = "1.0.0" ktor = "3.2.2" +logbackClassic = "1.5.18" treemapChart = "0.1.3" voyager= "1.1.0-beta03" molecule = "2.1.0" @@ -58,6 +59,7 @@ ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version ktor-client-darwin = { group = "io.ktor", name = "ktor-client-darwin", version.ref = "ktor" } ktor-client-java = { group = "io.ktor", name = "ktor-client-java", version.ref = "ktor" } +logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logbackClassic" } voyager = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } harawata-appdirs = { module = "net.harawata:appdirs", version.ref = "harawata-appdirs" } koalaplot = { module = "io.github.koalaplot:koalaplot-core", version.ref = "koalaplot" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/mcp-server/build.gradle.kts b/mcp-server/build.gradle.kts index ecd36eb..6498087 100644 --- a/mcp-server/build.gradle.kts +++ b/mcp-server/build.gradle.kts @@ -1,21 +1,26 @@ +@file:Suppress("UnstableApiUsage") + plugins { alias(libs.plugins.kotlinJvm) alias(libs.plugins.kotlinx.serialization) alias(libs.plugins.shadowPlugin) alias(libs.plugins.jib) application + id("org.graalvm.buildtools.native") version "0.11.0" } dependencies { implementation(libs.mcp.kotlin) implementation(libs.koin.core) - implementation("ch.qos.logback:logback-classic:1.5.8") implementation(projects.composeApp) + implementation(libs.logback.classic) } java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion.set(JavaLanguageVersion.of(24)) + vendor.set(JvmVendorSpec.GRAAL_VM) + nativeImageCapable.set(true) } } @@ -23,11 +28,23 @@ application { mainClass = "McpServerKt" } -tasks.shadowJar { - archiveFileName.set("serverAll.jar") - archiveClassifier.set("") - manifest { - attributes["Main-Class"] = "McpServerKt" +graalvmNative { + agent { + enabled.set(true) + } + + binaries { + all { + javaLauncher.set(javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(24)) + vendor.set(JvmVendorSpec.GRAAL_VM) + nativeImageCapable.set(true) + }) + } + named("main") { + imageName.set("climate-trace-mcp") + mainClass.set("MainKt") + } } } diff --git a/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json b/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json new file mode 100644 index 0000000..16299af --- /dev/null +++ b/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json @@ -0,0 +1,597 @@ +{ + "reflection": [ + { + "type": "[Lio.ktor.network.selector.SelectInterest;" + }, + { + "type": "[Ljava.lang.Object;" + }, + { + "type": "io.ktor.client.HttpClient", + "fields": [ + { + "name": "closed" + } + ] + }, + { + "type": "io.ktor.client.content.ProgressListener" + }, + { + "type": "io.ktor.client.engine.HttpClientEngineBase", + "fields": [ + { + "name": "closed" + } + ] + }, + { + "type": "io.ktor.client.engine.java.JavaHttpEngineContainer" + }, + { + "type": "io.ktor.client.plugins.HttpSend" + }, + { + "type": "io.ktor.client.plugins.api.ClientPluginImpl" + }, + { + "type": "io.ktor.client.plugins.api.ClientPluginInstance" + }, + { + "type": "io.ktor.client.plugins.logging.HttpClientCallLogger" + }, + { + "type": "io.ktor.http.ContentType" + }, + { + "type": "io.ktor.http.HttpStatusCode" + }, + { + "type": "io.ktor.network.selector.InterestSuspensionsMap", + "fields": [ + { + "name": "acceptHandlerReference" + }, + { + "name": "connectHandlerReference" + }, + { + "name": "readHandlerReference" + }, + { + "name": "writeHandlerReference" + } + ] + }, + { + "type": "io.ktor.network.selector.LockFreeMPSCQueue", + "fields": [ + { + "name": "curRef" + } + ] + }, + { + "type": "io.ktor.network.selector.LockFreeMPSCQueueCore", + "fields": [ + { + "name": "nextRef" + }, + { + "name": "stateRef" + } + ] + }, + { + "type": "io.ktor.network.selector.SelectableBase", + "fields": [ + { + "name": "_interestedOps" + } + ] + }, + { + "type": "io.ktor.serialization.kotlinx.json.KotlinxSerializationJsonExtensionProvider" + }, + { + "type": "io.ktor.server.application.PluginInstance" + }, + { + "type": "io.ktor.server.engine.BaseApplicationResponse" + }, + { + "type": "io.ktor.server.routing.RoutingCall" + }, + { + "type": "io.ktor.server.routing.RoutingRoot" + }, + { + "type": "io.ktor.util.Attributes" + }, + { + "type": "io.ktor.util.collections.CopyOnWriteHashMap", + "fields": [ + { + "name": "current" + } + ] + }, + { + "type": "io.ktor.util.internal.LockFreeLinkedListNode", + "fields": [ + { + "name": "_next" + }, + { + "name": "_prev" + }, + { + "name": "removedRef" + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.CallToolRequest", + "fields": [ + { + "name": "Companion" + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.CallToolRequest$Companion", + "methods": [ + { + "name": "serializer", + "parameterTypes": [] + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.InitializeRequest", + "fields": [ + { + "name": "Companion" + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.InitializeRequest$Companion", + "methods": [ + { + "name": "serializer", + "parameterTypes": [] + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.ListToolsRequest", + "fields": [ + { + "name": "Companion" + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.ListToolsRequest$Companion", + "methods": [ + { + "name": "serializer", + "parameterTypes": [] + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.PingRequest", + "fields": [ + { + "name": "Companion" + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.PingRequest$Companion", + "methods": [ + { + "name": "serializer", + "parameterTypes": [] + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.server.Server", + "fields": [ + { + "name": "_prompts" + }, + { + "name": "_resources" + }, + { + "name": "_tools" + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.shared.Protocol", + "fields": [ + { + "name": "_notificationHandlers" + }, + { + "name": "_progressHandlers" + }, + { + "name": "_requestHandlers" + }, + { + "name": "_responseHandlers" + } + ] + }, + { + "type": "java.lang.ClassValue" + }, + { + "type": "java.net.StandardSocketOptions" + }, + { + "type": "kotlin.Any" + }, + { + "type": "kotlin.Boolean" + }, + { + "type": "kotlin.Metadata", + "methods": [ + { + "name": "bv", + "parameterTypes": [] + }, + { + "name": "d1", + "parameterTypes": [] + }, + { + "name": "d2", + "parameterTypes": [] + }, + { + "name": "k", + "parameterTypes": [] + }, + { + "name": "mv", + "parameterTypes": [] + }, + { + "name": "pn", + "parameterTypes": [] + }, + { + "name": "xi", + "parameterTypes": [] + }, + { + "name": "xs", + "parameterTypes": [] + } + ] + }, + { + "type": "kotlin.SafePublicationLazyImpl", + "fields": [ + { + "name": "_value" + } + ] + }, + { + "type": "kotlin.Unit" + }, + { + "type": "kotlin.collections.List" + }, + { + "type": "kotlin.jvm.internal.DefaultConstructorMarker" + }, + { + "type": "kotlin.reflect.jvm.internal.ReflectionFactoryImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "kotlin.reflect.jvm.internal.impl.load.java.ErasedOverridabilityCondition" + }, + { + "type": "kotlin.reflect.jvm.internal.impl.load.java.FieldOverridabilityCondition" + }, + { + "type": "kotlin.reflect.jvm.internal.impl.load.java.JavaIncompatibilityRulesOverridabilityCondition" + }, + { + "type": "kotlinx.coroutines.CancellableContinuationImpl", + "fields": [ + { + "name": "_decisionAndIndex$volatile" + }, + { + "name": "_parentHandle$volatile" + }, + { + "name": "_state$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.CancelledContinuation", + "fields": [ + { + "name": "_resumed$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.CompletedExceptionally", + "fields": [ + { + "name": "_handled$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.EventLoopImplBase", + "fields": [ + { + "name": "_delayed$volatile" + }, + { + "name": "_isCompleted$volatile" + }, + { + "name": "_queue$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.JobSupport", + "fields": [ + { + "name": "_parentHandle$volatile" + }, + { + "name": "_state$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.JobSupport$Finishing", + "fields": [ + { + "name": "_exceptionsHolder$volatile" + }, + { + "name": "_isCompleting$volatile" + }, + { + "name": "_rootCause$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.flow.StateFlowImpl", + "fields": [ + { + "name": "_state$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.ConcurrentLinkedListNode", + "fields": [ + { + "name": "_next$volatile" + }, + { + "name": "_prev$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.DispatchedContinuation", + "fields": [ + { + "name": "_reusableCancellableContinuation$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.LimitedDispatcher", + "fields": [ + { + "name": "runningWorkers$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.LockFreeLinkedListNode", + "fields": [ + { + "name": "_next$volatile" + }, + { + "name": "_prev$volatile" + }, + { + "name": "_removedRef$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.LockFreeTaskQueue", + "fields": [ + { + "name": "_cur$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.LockFreeTaskQueueCore", + "fields": [ + { + "name": "_next$volatile" + }, + { + "name": "_state$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.Segment", + "fields": [ + { + "name": "cleanedAndPointers$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.internal.ThreadSafeHeap", + "fields": [ + { + "name": "_size$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.scheduling.CoroutineScheduler", + "fields": [ + { + "name": "_isTerminated$volatile" + }, + { + "name": "controlState$volatile" + }, + { + "name": "parkedWorkersStack$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.scheduling.CoroutineScheduler$Worker", + "fields": [ + { + "name": "workerCtl$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.scheduling.WorkQueue", + "fields": [ + { + "name": "blockingTasksInBuffer$volatile" + }, + { + "name": "consumerIndex$volatile" + }, + { + "name": "lastScheduledTask$volatile" + }, + { + "name": "producerIndex$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.sync.MutexImpl", + "fields": [ + { + "name": "owner$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.sync.SemaphoreAndMutexImpl", + "fields": [ + { + "name": "_availablePermits$volatile" + }, + { + "name": "deqIdx$volatile" + }, + { + "name": "enqIdx$volatile" + }, + { + "name": "head$volatile" + }, + { + "name": "tail$volatile" + } + ] + }, + { + "type": "sun.security.provider.NativePRNG", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.security.SecureRandomParameters" + ] + } + ] + }, + { + "type": "sun.security.provider.SHA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + } + ], + "resources": [ + { + "glob": "META-INF/services/io.ktor.client.HttpClientEngineContainer" + }, + { + "glob": "META-INF/services/io.ktor.serialization.kotlinx.KotlinxSerializationExtensionProvider" + }, + { + "glob": "META-INF/services/java.lang.System$LoggerFinder" + }, + { + "glob": "META-INF/services/java.nio.channels.spi.SelectorProvider" + }, + { + "glob": "META-INF/services/kotlin.reflect.jvm.internal.impl.resolve.ExternalOverridabilityCondition" + }, + { + "glob": "META-INF/services/kotlin.reflect.jvm.internal.impl.util.ModuleVisibilityHelper" + }, + { + "glob": "META-INF/services/org.slf4j.spi.SLF4JServiceProvider" + }, + { + "glob": "org/slf4j/impl/StaticLoggerBinder.class" + } + ], + "bundles": [], + "jni": [ + { + "type": "java.lang.Boolean", + "methods": [ + { + "name": "getBoolean", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + } + ] +} \ No newline at end of file From d3e0207070886a959567332d1ae061e32deeb88d Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Mon, 28 Jul 2025 12:04:13 +0100 Subject: [PATCH 3/7] added reachability-metadata.json --- .../climatetrace/reachability-metadata.json | 859 +++++++++++++++++- 1 file changed, 827 insertions(+), 32 deletions(-) diff --git a/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json b/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json index 16299af..a8321f1 100644 --- a/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json +++ b/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json @@ -6,6 +6,143 @@ { "type": "[Ljava.lang.Object;" }, + { + "type": "apple.security.AppleProvider", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "ch.qos.logback.classic.BasicConfigurator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "ch.qos.logback.classic.joran.SerializedModelConfigurator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "ch.qos.logback.classic.spi.LogbackServiceProvider" + }, + { + "type": "ch.qos.logback.classic.util.DefaultJoranConfigurator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.AESCipher$General", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.ARCFOURCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DESCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DESedeCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DHParameters", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.TlsMasterSecretGenerator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.johnoreilly.climatetrace.remote.Country", + "fields": [ + { + "name": "Companion" + } + ] + }, + { + "type": "dev.johnoreilly.climatetrace.remote.Country$Companion", + "methods": [ + { + "name": "serializer", + "parameterTypes": [] + } + ] + }, { "type": "io.ktor.client.HttpClient", "fields": [ @@ -14,6 +151,17 @@ } ] }, + { + "type": "io.ktor.client.HttpClientConfig" + }, + { + "type": "io.ktor.client.call.HttpClientCall", + "fields": [ + { + "name": "received" + } + ] + }, { "type": "io.ktor.client.content.ProgressListener" }, @@ -25,9 +173,26 @@ } ] }, + { + "type": "io.ktor.client.engine.HttpClientEngineCapability" + }, + { + "type": "io.ktor.client.engine.HttpClientEngineConfig" + }, { "type": "io.ktor.client.engine.java.JavaHttpEngineContainer" }, + { + "type": "io.ktor.client.engine.java.JavaHttpResponseBodyHandler$JavaHttpResponseBodySubscriber", + "fields": [ + { + "name": "closed" + }, + { + "name": "subscription" + } + ] + }, { "type": "io.ktor.client.plugins.HttpSend" }, @@ -38,7 +203,18 @@ "type": "io.ktor.client.plugins.api.ClientPluginInstance" }, { - "type": "io.ktor.client.plugins.logging.HttpClientCallLogger" + "type": "io.ktor.client.plugins.logging.HttpClientCallLogger", + "fields": [ + { + "name": "requestLogged" + }, + { + "name": "responseLogged" + } + ] + }, + { + "type": "io.ktor.client.request.ResponseAdapter" }, { "type": "io.ktor.http.ContentType" @@ -46,6 +222,9 @@ { "type": "io.ktor.http.HttpStatusCode" }, + { + "type": "io.ktor.http.content.TextContent" + }, { "type": "io.ktor.network.selector.InterestSuspensionsMap", "fields": [ @@ -90,6 +269,23 @@ } ] }, + { + "type": "io.ktor.network.sockets.SocketBase", + "fields": [ + { + "name": "actualCloseFlag" + }, + { + "name": "closeFlag" + }, + { + "name": "readerJob" + }, + { + "name": "writerJob" + } + ] + }, { "type": "io.ktor.serialization.kotlinx.json.KotlinxSerializationJsonExtensionProvider" }, @@ -99,12 +295,21 @@ { "type": "io.ktor.server.engine.BaseApplicationResponse" }, + { + "type": "io.ktor.server.plugins.MutableOriginConnectionPoint" + }, + { + "type": "io.ktor.server.request.DoubleReceivePreventionToken" + }, { "type": "io.ktor.server.routing.RoutingCall" }, { "type": "io.ktor.server.routing.RoutingRoot" }, + { + "type": "io.ktor.server.sse.SSEServerContent" + }, { "type": "io.ktor.util.Attributes" }, @@ -130,6 +335,31 @@ } ] }, + { + "type": "io.ktor.util.reflect.TypeInfo" + }, + { + "type": "io.ktor.utils.io.ByteChannel", + "fields": [ + { + "name": "_closedCause" + }, + { + "name": "suspensionSlot" + } + ] + }, + { + "type": "io.ktor.utils.io.ByteReadChannel" + }, + { + "type": "io.ktor.utils.io.pool.DefaultPool", + "fields": [ + { + "name": "top" + } + ] + }, { "type": "io.modelcontextprotocol.kotlin.sdk.CallToolRequest", "fields": [ @@ -148,7 +378,7 @@ ] }, { - "type": "io.modelcontextprotocol.kotlin.sdk.InitializeRequest", + "type": "io.modelcontextprotocol.kotlin.sdk.CallToolResult", "fields": [ { "name": "Companion" @@ -156,7 +386,7 @@ ] }, { - "type": "io.modelcontextprotocol.kotlin.sdk.InitializeRequest$Companion", + "type": "io.modelcontextprotocol.kotlin.sdk.CallToolResult$Companion", "methods": [ { "name": "serializer", @@ -165,7 +395,7 @@ ] }, { - "type": "io.modelcontextprotocol.kotlin.sdk.ListToolsRequest", + "type": "io.modelcontextprotocol.kotlin.sdk.InitializeRequest", "fields": [ { "name": "Companion" @@ -173,7 +403,7 @@ ] }, { - "type": "io.modelcontextprotocol.kotlin.sdk.ListToolsRequest$Companion", + "type": "io.modelcontextprotocol.kotlin.sdk.InitializeRequest$Companion", "methods": [ { "name": "serializer", @@ -182,7 +412,7 @@ ] }, { - "type": "io.modelcontextprotocol.kotlin.sdk.PingRequest", + "type": "io.modelcontextprotocol.kotlin.sdk.InitializeResult", "fields": [ { "name": "Companion" @@ -190,7 +420,7 @@ ] }, { - "type": "io.modelcontextprotocol.kotlin.sdk.PingRequest$Companion", + "type": "io.modelcontextprotocol.kotlin.sdk.InitializeResult$Companion", "methods": [ { "name": "serializer", @@ -199,53 +429,165 @@ ] }, { - "type": "io.modelcontextprotocol.kotlin.sdk.server.Server", + "type": "io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse", "fields": [ { - "name": "_prompts" - }, - { - "name": "_resources" - }, + "name": "Companion" + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse$Companion", + "methods": [ { - "name": "_tools" + "name": "serializer", + "parameterTypes": [] } ] }, { - "type": "io.modelcontextprotocol.kotlin.sdk.shared.Protocol", + "type": "io.modelcontextprotocol.kotlin.sdk.ListToolsRequest", "fields": [ { - "name": "_notificationHandlers" - }, - { - "name": "_progressHandlers" - }, - { - "name": "_requestHandlers" - }, - { - "name": "_responseHandlers" + "name": "Companion" } ] }, { - "type": "java.lang.ClassValue" + "type": "io.modelcontextprotocol.kotlin.sdk.ListToolsRequest$Companion", + "methods": [ + { + "name": "serializer", + "parameterTypes": [] + } + ] }, { - "type": "java.net.StandardSocketOptions" + "type": "io.modelcontextprotocol.kotlin.sdk.ListToolsResult", + "fields": [ + { + "name": "Companion" + } + ] }, { - "type": "kotlin.Any" + "type": "io.modelcontextprotocol.kotlin.sdk.ListToolsResult$Companion", + "methods": [ + { + "name": "serializer", + "parameterTypes": [] + } + ] }, { - "type": "kotlin.Boolean" + "type": "io.modelcontextprotocol.kotlin.sdk.PingRequest", + "fields": [ + { + "name": "Companion" + } + ] }, { - "type": "kotlin.Metadata", + "type": "io.modelcontextprotocol.kotlin.sdk.PingRequest$Companion", "methods": [ { - "name": "bv", + "name": "serializer", + "parameterTypes": [] + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.TextContent", + "fields": [ + { + "name": "Companion" + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.TextContent$Companion", + "methods": [ + { + "name": "serializer", + "parameterTypes": [] + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.server.Server", + "fields": [ + { + "name": "_prompts" + }, + { + "name": "_resources" + }, + { + "name": "_tools" + } + ] + }, + { + "type": "io.modelcontextprotocol.kotlin.sdk.shared.Protocol", + "fields": [ + { + "name": "_notificationHandlers" + }, + { + "name": "_progressHandlers" + }, + { + "name": "_requestHandlers" + }, + { + "name": "_responseHandlers" + } + ] + }, + { + "type": "java.lang.Boolean" + }, + { + "type": "java.lang.ClassValue" + }, + { + "type": "java.lang.Object" + }, + { + "type": "java.net.StandardSocketOptions" + }, + { + "type": "java.security.AlgorithmParametersSpi" + }, + { + "type": "java.security.KeyStoreSpi" + }, + { + "type": "java.security.interfaces.RSAPrivateKey" + }, + { + "type": "java.security.interfaces.RSAPublicKey" + }, + { + "type": "java.util.List" + }, + { + "type": "java.util.Map" + }, + { + "type": "kotlin.Any" + }, + { + "type": "kotlin.Boolean" + }, + { + "type": "kotlin.Long" + }, + { + "type": "kotlin.Metadata", + "methods": [ + { + "name": "bv", "parameterTypes": [] }, { @@ -292,6 +634,15 @@ { "type": "kotlin.collections.List" }, + { + "type": "kotlin.collections.Map" + }, + { + "type": "kotlin.collections.MutableMap" + }, + { + "type": "kotlin.coroutines.jvm.internal.BaseContinuationImpl" + }, { "type": "kotlin.jvm.internal.DefaultConstructorMarker" }, @@ -343,6 +694,14 @@ } ] }, + { + "type": "kotlinx.coroutines.DispatchedCoroutine", + "fields": [ + { + "name": "_decision$volatile" + } + ] + }, { "type": "kotlinx.coroutines.EventLoopImplBase", "fields": [ @@ -357,6 +716,17 @@ } ] }, + { + "type": "kotlinx.coroutines.InvokeOnCancelling", + "fields": [ + { + "name": "_invoked$volatile" + } + ] + }, + { + "type": "kotlinx.coroutines.Job" + }, { "type": "kotlinx.coroutines.JobSupport", "fields": [ @@ -382,6 +752,38 @@ } ] }, + { + "type": "kotlinx.coroutines.channels.BufferedChannel", + "fields": [ + { + "name": "_closeCause$volatile" + }, + { + "name": "bufferEnd$volatile" + }, + { + "name": "bufferEndSegment$volatile" + }, + { + "name": "closeHandler$volatile" + }, + { + "name": "completedExpandBuffersAndPauseFlag$volatile" + }, + { + "name": "receiveSegment$volatile" + }, + { + "name": "receivers$volatile" + }, + { + "name": "sendSegment$volatile" + }, + { + "name": "sendersAndCloseStatus$volatile" + } + ] + }, { "type": "kotlinx.coroutines.flow.StateFlowImpl", "fields": [ @@ -458,6 +860,9 @@ } ] }, + { + "type": "kotlinx.coroutines.internal.StackTraceRecoveryKt" + }, { "type": "kotlinx.coroutines.internal.ThreadSafeHeap", "fields": [ @@ -533,6 +938,60 @@ } ] }, + { + "type": "sun.security.pkcs12.PKCS12KeyStore", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.DSA$SHA224withDSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.DSA$SHA256withDSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.JavaKeyStore$DualFormatJKS", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.JavaKeyStore$JKS", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, { "type": "sun.security.provider.NativePRNG", "methods": [ @@ -552,9 +1011,282 @@ "parameterTypes": [] } ] + }, + { + "type": "sun.security.provider.SHA2$SHA224", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA2$SHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA5$SHA384", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA5$SHA512", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.X509Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.certpath.PKIXCertPathValidator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.PSSParameters", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSAKeyFactory$Legacy", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSAPSSSignature", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSASignature$SHA224withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSASignature$SHA256withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.KeyManagerFactoryImpl$SunX509", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.SSLContextImpl$DefaultSSLContext", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.x509.AuthorityInfoAccessExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.AuthorityKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.BasicConstraintsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.CRLDistributionPointsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.CertificatePoliciesExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.ExtendedKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.IssuerAlternativeNameExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.KeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.NetscapeCertTypeExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.PrivateKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.SubjectAlternativeNameExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.SubjectKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] } ], "resources": [ + { + "glob": "META-INF/services/ch.qos.logback.classic.spi.Configurator" + }, { "glob": "META-INF/services/io.ktor.client.HttpClientEngineContainer" }, @@ -564,9 +1296,18 @@ { "glob": "META-INF/services/java.lang.System$LoggerFinder" }, + { + "glob": "META-INF/services/java.net.spi.InetAddressResolverProvider" + }, + { + "glob": "META-INF/services/java.net.spi.URLStreamHandlerProvider" + }, { "glob": "META-INF/services/java.nio.channels.spi.SelectorProvider" }, + { + "glob": "META-INF/services/java.time.zone.ZoneRulesProvider" + }, { "glob": "META-INF/services/kotlin.reflect.jvm.internal.impl.resolve.ExternalOverridabilityCondition" }, @@ -577,7 +1318,32 @@ "glob": "META-INF/services/org.slf4j.spi.SLF4JServiceProvider" }, { - "glob": "org/slf4j/impl/StaticLoggerBinder.class" + "glob": "logback-test.scmo" + }, + { + "glob": "logback-test.xml" + }, + { + "glob": "logback.scmo" + }, + { + "glob": "logback.xml" + }, + { + "module": "java.base", + "glob": "jdk/internal/icu/impl/data/icudt76b/nfc.nrm" + }, + { + "module": "java.base", + "glob": "jdk/internal/icu/impl/data/icudt76b/nfkc.nrm" + }, + { + "module": "java.base", + "glob": "jdk/internal/icu/impl/data/icudt76b/uprops.icu" + }, + { + "module": "java.base", + "glob": "sun/net/idn/uidna.spp" } ], "bundles": [], @@ -592,6 +1358,35 @@ ] } ] + }, + { + "type": "sun.management.VMManagementImpl", + "fields": [ + { + "name": "compTimeMonitoringSupport" + }, + { + "name": "currentThreadCpuTimeSupport" + }, + { + "name": "objectMonitorUsageSupport" + }, + { + "name": "otherThreadCpuTimeSupport" + }, + { + "name": "remoteDiagnosticCommandsSupport" + }, + { + "name": "synchronizerUsageSupport" + }, + { + "name": "threadAllocatedMemorySupport" + }, + { + "name": "threadContentionMonitoringSupport" + } + ] } ] } \ No newline at end of file From b0a2844269b39941ccbb6ef4bea73562d01a098a Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Mon, 28 Jul 2025 22:32:19 +0100 Subject: [PATCH 4/7] Draft jib/native build --- gradle/libs.versions.toml | 1 + mcp-server/build.gradle.kts | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8d3cbf1..33e21a6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -83,6 +83,7 @@ kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", vers kmpNativeCoroutines = { id = "com.rickclephas.kmp.nativecoroutines", version.ref = "kmpNativeCoroutines" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } kotlinJvm = { id = "org.jetbrains.kotlin.jvm" } +graalvm = { id = "org.graalvm.buildtools.native", version = "0.11.0" } shadowPlugin = { id = "com.gradleup.shadow", version.ref = "shadowPlugin" } jib = { id = "com.google.cloud.tools.jib", version.ref = "jib" } diff --git a/mcp-server/build.gradle.kts b/mcp-server/build.gradle.kts index 6498087..f75939f 100644 --- a/mcp-server/build.gradle.kts +++ b/mcp-server/build.gradle.kts @@ -6,7 +6,7 @@ plugins { alias(libs.plugins.shadowPlugin) alias(libs.plugins.jib) application - id("org.graalvm.buildtools.native") version "0.11.0" + alias(libs.plugins.graalvm) } dependencies { @@ -40,22 +40,37 @@ graalvmNative { vendor.set(JvmVendorSpec.GRAAL_VM) nativeImageCapable.set(true) }) + buildArgs("--enable-url-protocols=http,https") } named("main") { imageName.set("climate-trace-mcp") - mainClass.set("MainKt") + mainClass.set("McpServerKt") } } } jib { - from.image = "docker.io/library/eclipse-temurin:21" + from.image = "docker.io/library/alpine:3.22" to { image = "gcr.io/climatetrace-mcp/climatetrace-mcp-server" } container { ports = listOf("8080") - mainClass = "McpServerKt" + entrypoint = listOf("/climate-trace-mcp") } + extraDirectories { + paths { + path { + // copies a single-file.xml + setFrom("build/native/nativeCompile") + into = "/" + includes = listOf("climate-trace-mcp") + } + } + } +} + +tasks.named("jib").configure { + dependsOn(tasks.named("nativeCompile")) } From 3398cafe3fd5eddfd3c20c1d3e5b5f753b174af8 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Tue, 29 Jul 2025 10:14:50 +0100 Subject: [PATCH 5/7] Fix build --- mcp-server/build.gradle.kts | 4 -- .../climatetrace/reachability-metadata.json | 69 +++++++++++-------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/mcp-server/build.gradle.kts b/mcp-server/build.gradle.kts index f75939f..3484d27 100644 --- a/mcp-server/build.gradle.kts +++ b/mcp-server/build.gradle.kts @@ -3,7 +3,6 @@ plugins { alias(libs.plugins.kotlinJvm) alias(libs.plugins.kotlinx.serialization) - alias(libs.plugins.shadowPlugin) alias(libs.plugins.jib) application alias(libs.plugins.graalvm) @@ -19,8 +18,6 @@ dependencies { java { toolchain { languageVersion.set(JavaLanguageVersion.of(24)) - vendor.set(JvmVendorSpec.GRAAL_VM) - nativeImageCapable.set(true) } } @@ -37,7 +34,6 @@ graalvmNative { all { javaLauncher.set(javaToolchains.launcherFor { languageVersion.set(JavaLanguageVersion.of(24)) - vendor.set(JvmVendorSpec.GRAAL_VM) nativeImageCapable.set(true) }) buildArgs("--enable-url-protocols=http,https") diff --git a/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json b/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json index a8321f1..5be5e35 100644 --- a/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json +++ b/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json @@ -6,15 +6,6 @@ { "type": "[Ljava.lang.Object;" }, - { - "type": "apple.security.AppleProvider", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, { "type": "ch.qos.logback.classic.BasicConfigurator", "methods": [ @@ -143,6 +134,23 @@ } ] }, + { + "type": "dev.johnoreilly.climatetrace.remote.CountryAssetEmissionsInfo", + "fields": [ + { + "name": "Companion" + } + ] + }, + { + "type": "dev.johnoreilly.climatetrace.remote.CountryAssetEmissionsInfo$Companion", + "methods": [ + { + "name": "serializer", + "parameterTypes": [] + } + ] + }, { "type": "io.ktor.client.HttpClient", "fields": [ @@ -462,23 +470,6 @@ } ] }, - { - "type": "io.modelcontextprotocol.kotlin.sdk.ListToolsResult", - "fields": [ - { - "name": "Companion" - } - ] - }, - { - "type": "io.modelcontextprotocol.kotlin.sdk.ListToolsResult$Companion", - "methods": [ - { - "name": "serializer", - "parameterTypes": [] - } - ] - }, { "type": "io.modelcontextprotocol.kotlin.sdk.PingRequest", "fields": [ @@ -553,6 +544,14 @@ { "type": "java.lang.Object" }, + { + "type": "java.lang.String", + "fields": [ + { + "name": "Companion" + } + ] + }, { "type": "java.net.StandardSocketOptions" }, @@ -628,6 +627,9 @@ } ] }, + { + "type": "kotlin.String" + }, { "type": "kotlin.Unit" }, @@ -1329,10 +1331,6 @@ { "glob": "logback.xml" }, - { - "module": "java.base", - "glob": "jdk/internal/icu/impl/data/icudt76b/nfc.nrm" - }, { "module": "java.base", "glob": "jdk/internal/icu/impl/data/icudt76b/nfkc.nrm" @@ -1348,6 +1346,17 @@ ], "bundles": [], "jni": [ + { + "type": "McpServerKt", + "methods": [ + { + "name": "main", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, { "type": "java.lang.Boolean", "methods": [ From 2da441cea2c088d0c9711766700033c5e05af8c3 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Tue, 29 Jul 2025 10:18:04 +0100 Subject: [PATCH 6/7] Fix last API call --- .../climatetrace/reachability-metadata.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json b/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json index 5be5e35..71f463e 100644 --- a/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json +++ b/mcp-server/src/main/resources/META-INF/native-image/climatetrace/reachability-metadata.json @@ -151,6 +151,23 @@ } ] }, + { + "type": "dev.johnoreilly.climatetrace.remote.CountryEmissionsInfo", + "fields": [ + { + "name": "Companion" + } + ] + }, + { + "type": "dev.johnoreilly.climatetrace.remote.CountryEmissionsInfo$Companion", + "methods": [ + { + "name": "serializer", + "parameterTypes": [] + } + ] + }, { "type": "io.ktor.client.HttpClient", "fields": [ From ac106bc4afea97c8205ef501a215aa635b288da4 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Tue, 29 Jul 2025 10:25:39 +0100 Subject: [PATCH 7/7] Build automatically --- mcp-server/build.gradle.kts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mcp-server/build.gradle.kts b/mcp-server/build.gradle.kts index 3484d27..db543a5 100644 --- a/mcp-server/build.gradle.kts +++ b/mcp-server/build.gradle.kts @@ -1,5 +1,8 @@ @file:Suppress("UnstableApiUsage") +import com.google.cloud.tools.jib.gradle.JibTask + + plugins { alias(libs.plugins.kotlinJvm) alias(libs.plugins.kotlinx.serialization) @@ -58,15 +61,13 @@ jib { extraDirectories { paths { path { - // copies a single-file.xml setFrom("build/native/nativeCompile") into = "/" - includes = listOf("climate-trace-mcp") } } } } -tasks.named("jib").configure { +tasks.withType { dependsOn(tasks.named("nativeCompile")) }