diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 7c86616..1f2a918 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -19,7 +19,7 @@ jobs: java-version: 21 - name: Build iOS app - run: xcodebuild -allowProvisioningUpdates -allowProvisioningUpdates -workspace iosApp/iosApp.xcodeproj/project.xcworkspace -configuration Debug -scheme iosApp -sdk iphoneos -destination name='iPhone 15' + run: xcodebuild -allowProvisioningUpdates -allowProvisioningUpdates -workspace iosApp/iosApp.xcodeproj/project.xcworkspace -configuration Debug -scheme iosApp -sdk iphoneos -destination name='iPhone 16' diff --git a/build.gradle.kts b/build.gradle.kts index 5a90072..a32fc08 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,4 +6,5 @@ plugins { alias(libs.plugins.jetbrainsCompose) apply false alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.kmpNativeCoroutines) apply false + alias(libs.plugins.jib) apply false } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/remote/requests.http b/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/remote/requests.http index e69de29..0259bae 100644 --- a/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/remote/requests.http +++ b/composeApp/src/commonMain/kotlin/dev/johnoreilly/climatetrace/remote/requests.http @@ -0,0 +1 @@ +GET https://api.climatetrace.org/v6/definitions/countries \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/dev/johnoreilly/climatetrace/di/Koin.desktop.kt b/composeApp/src/jvmMain/kotlin/dev/johnoreilly/climatetrace/di/Koin.desktop.kt index 836b37f..c049dd5 100644 --- a/composeApp/src/jvmMain/kotlin/dev/johnoreilly/climatetrace/di/Koin.desktop.kt +++ b/composeApp/src/jvmMain/kotlin/dev/johnoreilly/climatetrace/di/Koin.desktop.kt @@ -4,6 +4,7 @@ import dev.johnoreilly.climatetrace.remote.Country import io.github.xxfast.kstore.KStore import io.github.xxfast.kstore.file.storeOf import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem import net.harawata.appdirs.AppDirsFactory import org.koin.core.module.Module import org.koin.dsl.module @@ -15,7 +16,9 @@ private const val AUTHOR = "johnoreilly" actual fun dataModule(): Module = module { single>> { val filesDir: String = AppDirsFactory.getInstance() - .getUserDataDir(PACKAGE_NAME, VERSION, AUTHOR) + .getUserCacheDir(PACKAGE_NAME, VERSION, AUTHOR) + val files = Path(filesDir) + with(SystemFileSystem) { if(!exists(files)) createDirectories(files) } storeOf(file = Path(path = "$filesDir/countries.json"), default = emptyList()) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b44ed50..eda77c9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.2.0" ksp = "2.2.0-2.0.2" kotlinx-coroutines = "1.10.2" - +kotlinxSerialization = "1.9.0" agp = "8.11.1" android-compileSdk = "36" @@ -24,8 +24,8 @@ treemapChart = "0.1.3" voyager= "1.1.0-beta03" molecule = "2.1.0" mcp = "0.6.0" -shadowPlugin = "8.1.1" - +shadowPlugin = "9.0.0-rc2" +jib = "3.4.5" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } @@ -41,6 +41,7 @@ koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } kotlinx-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "kotlinxSerialization" } kmpObservableViewModel = { module = "com.rickclephas.kmp:kmp-observableviewmodel-core", version.ref = "kmpObservableViewModel" } kstore = { module = "io.github.xxfast:kstore", version.ref = "kstore" } @@ -80,4 +81,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" } -shadowPlugin = { id = "com.github.johnrengelman.shadow", version.ref = "shadowPlugin" } +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 9446a7a..ecd36eb 100644 --- a/mcp-server/build.gradle.kts +++ b/mcp-server/build.gradle.kts @@ -2,12 +2,14 @@ plugins { alias(libs.plugins.kotlinJvm) alias(libs.plugins.kotlinx.serialization) alias(libs.plugins.shadowPlugin) + alias(libs.plugins.jib) application } dependencies { implementation(libs.mcp.kotlin) implementation(libs.koin.core) + implementation("ch.qos.logback:logback-classic:1.5.8") implementation(projects.composeApp) } @@ -18,14 +20,25 @@ java { } application { - mainClass = "MainKt" + mainClass = "McpServerKt" } tasks.shadowJar { archiveFileName.set("serverAll.jar") archiveClassifier.set("") manifest { - attributes["Main-Class"] = "MainKt" + attributes["Main-Class"] = "McpServerKt" } } +jib { + from.image = "docker.io/library/eclipse-temurin:21" + + to { + image = "gcr.io/climatetrace-mcp/climatetrace-mcp-server" + } + container { + ports = listOf("8080") + mainClass = "McpServerKt" + } +} diff --git a/mcp-server/src/main/kotlin/server.kt b/mcp-server/src/main/kotlin/McpServer.kt similarity index 91% rename from mcp-server/src/main/kotlin/server.kt rename to mcp-server/src/main/kotlin/McpServer.kt index a2bd418..9209ad6 100644 --- a/mcp-server/src/main/kotlin/server.kt +++ b/mcp-server/src/main/kotlin/McpServer.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.runBlocking import kotlinx.io.asSink import kotlinx.io.buffered -import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonArray @@ -17,9 +16,24 @@ import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.putJsonObject + + +fun main(args: Array) { + val command = args.firstOrNull() ?: "--sse-server" + val port = args.getOrNull(1)?.toIntOrNull() ?: 8080 + when (command) { + "--sse-server" -> `run sse mcp server`(port) + "--stdio" -> `run mcp server using stdio`() + else -> { + System.err.println("Unknown command: $command") + } + } +} + + private val koin = initKoin(enableNetworkLogs = true).koin -fun configureServer(): Server { +fun configureMcpServer(): Server { val climateTraceRepository = koin.get() val server = Server( @@ -34,7 +48,6 @@ fun configureServer(): Server { ) ) - server.addTool( name = "get-countries", description = "List of countries" @@ -68,7 +81,6 @@ fun configureServer(): Server { content = listOf(TextContent("The 'countryCodeList' parameters are required.")) ) } - val countryAssetEmissionInfo = climateTraceRepository.fetchCountryAssetEmissionsInfo( countryCodeList = countryCodeList .jsonArray @@ -130,7 +142,7 @@ fun configureServer(): Server { * a close event. */ fun `run mcp server using stdio`() { - val server = configureServer() + val server = configureMcpServer() val transport = StdioServerTransport( System.`in`.asInput(), System.out.asSink().buffered() @@ -154,7 +166,7 @@ fun `run mcp server using stdio`() { * @param port The port number on which the SSE server should be started. */ fun `run sse mcp server`(port: Int): Unit = runBlocking { - val server = configureServer() + val server = configureMcpServer() embeddedServer(CIO, host = "0.0.0.0", port = port) { mcp { server diff --git a/mcp-server/src/main/kotlin/main.kt b/mcp-server/src/main/kotlin/main.kt deleted file mode 100644 index 0b7aa46..0000000 --- a/mcp-server/src/main/kotlin/main.kt +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Entry point. - * It initializes and runs the appropriate server mode based on the input arguments. - * - * Command-line arguments passed to the application: - * - args[0]: Specifies the server mode. Supported values are: - * - "--sse-server": Runs the SSE MCP server. - * - "--stdio": Runs the MCP server using standard input/output. - * Defaults to "--sse-server" if not provided. - * - args[1]: Specifies the port number for the server. Defaults to 3001 if not provided or invalid. - */ - - -fun main(args: Array) { - val command = args.firstOrNull() ?: "--sse-server" - val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 - when (command) { - "--sse-server" -> `run sse mcp server`(port) - "--stdio" -> `run mcp server using stdio`() - else -> { - System.err.println("Unknown command: $command") - } - } -} \ No newline at end of file