From 9c3e4fa3c09747a624d80c181b793ba2e7061a73 Mon Sep 17 00:00:00 2001 From: mariano Date: Wed, 3 Sep 2025 15:35:57 -0500 Subject: [PATCH 1/3] fix: release task --- build.gradle.kts | 28 ++++++++++++++++++++++------ settings.gradle.kts | 10 ---------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f4c23bf..e060b41 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,12 +18,14 @@ plugins { scmVersion { unshallowRepoOnCI.set(true) tag { prefix.set("v") } + versionCreator("versionWithBranch") + branchVersionCreator.set(mapOf("main" to "simple")) + versionIncrementer("incrementMinor") + branchVersionIncrementer.set(mapOf("feature/.*" to "incrementMinor", "bugfix/.*" to "incrementPatch")) } group = "io.github.eschizoid" - version = rootProject.scmVersion.version - description = "MCP Server for GitHub Code Repositories Analysis" dependencies { @@ -78,11 +80,8 @@ application { mainClass.set("MainKt") } tasks.jar { manifest { attributes["Main-Class"] = "MainKt" } - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA") - from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) } @@ -99,7 +98,23 @@ tasks.jacocoTestReport { java { withSourcesJar() withJavadocJar() - toolchain { languageVersion = JavaLanguageVersion.of(23) } + toolchain { languageVersion = JavaLanguageVersion.of(24) } +} + +repositories { + mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/kotlin-mcp-sdk/sdk") + maven { + credentials { + username = + System.getenv("JRELEASER_MAVENCENTRAL_SONATYPE_USERNAME") + ?: project.properties["mavencentralSonatypeUsername"]?.toString() + password = + System.getenv("JRELEASER_MAVENCENTRAL_SONATYPE_PASSWORD") + ?: project.properties["mavencentralSonatypePassword"]?.toString() + } + url = uri("https://central.sonatype.com/") + } } spotless { @@ -196,6 +211,7 @@ jreleaser { stagingRepository("build/staging-deploy") enabled.set(true) sign.set(false) + maxRetries.set(180) } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 69ba540..edc24fc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,13 +1,3 @@ -import org.gradle.kotlin.dsl.maven - plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "mcp-github-code-analyzer" - -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - mavenCentral() - maven("https://maven.pkg.jetbrains.space/public/p/kotlin-mcp-sdk/sdk") - } -} From 64d0129cd14e7c304ba672eac0941671604413cc Mon Sep 17 00:00:00 2001 From: mariano Date: Thu, 4 Sep 2025 08:43:05 -0500 Subject: [PATCH 2/3] fix: release task --- build.gradle.kts | 6 +++++- .../kotlin/mcp/code/analysis/service/GitService.kt | 10 +++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e060b41..bf7e010 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -85,7 +85,11 @@ tasks.jar { from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }) } -tasks.test { useJUnitPlatform() } +tasks.test { + useJUnitPlatform() + // Enable Byte Buddy experimental support for Java 24 to allow MockK class transformation + jvmArgs("-Dnet.bytebuddy.experimental=true") +} tasks.jacocoTestReport { reports { diff --git a/src/main/kotlin/mcp/code/analysis/service/GitService.kt b/src/main/kotlin/mcp/code/analysis/service/GitService.kt index e7ca5b0..34336d7 100644 --- a/src/main/kotlin/mcp/code/analysis/service/GitService.kt +++ b/src/main/kotlin/mcp/code/analysis/service/GitService.kt @@ -6,17 +6,21 @@ import mcp.code.analysis.config.AppConfig import org.eclipse.jgit.api.Git /** Service for interacting with Git repositories. */ -data class GitService(private val config: AppConfig = AppConfig.fromEnv()) { +open class GitService(private val config: AppConfig? = AppConfig.fromEnv()) { /** * Clones a Git repository to a temporary directory. * + * Note: When this class is mocked in tests on newer JVMs, constructor interception may be bypassed. + * To keep behavior robust under mocking, we fallback to AppConfig.fromEnv() if `config` is null. + * * @param repoUrl The URL of the Git repository to clone. * @param branch The branch of the repository to clone. * @return The path to the cloned repository. */ - fun cloneRepository(repoUrl: String, branch: String): File { - val workDir = File(config.cloneDirectory) + open fun cloneRepository(repoUrl: String, branch: String): File { + val effectiveConfig = config ?: AppConfig.fromEnv() + val workDir = File(effectiveConfig.cloneDirectory) val repoName = extractRepoName(repoUrl) val targetDir = Files.createTempDirectory(workDir.toPath(), repoName).toFile() Git.cloneRepository().setURI(repoUrl).setDirectory(targetDir).setBranch(branch).call().use { git -> From 958b253dbf109152276cff3a785f186781e1244b Mon Sep 17 00:00:00 2001 From: mariano Date: Thu, 4 Sep 2025 14:45:08 -0500 Subject: [PATCH 3/3] fix: release task --- build.gradle.kts | 1 - .../kotlin/mcp/code/analysis/server/Mcp.kt | 130 +++++++++++++++++- .../mcp/code/analysis/service/GitService.kt | 10 +- .../mcp/code/analysis/server/McpTest.kt | 11 ++ 4 files changed, 143 insertions(+), 9 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index bf7e010..994341e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -87,7 +87,6 @@ tasks.jar { tasks.test { useJUnitPlatform() - // Enable Byte Buddy experimental support for Java 24 to allow MockK class transformation jvmArgs("-Dnet.bytebuddy.experimental=true") } diff --git a/src/main/kotlin/mcp/code/analysis/server/Mcp.kt b/src/main/kotlin/mcp/code/analysis/server/Mcp.kt index fc3f8ca..778c4b9 100644 --- a/src/main/kotlin/mcp/code/analysis/server/Mcp.kt +++ b/src/main/kotlin/mcp/code/analysis/server/Mcp.kt @@ -16,6 +16,7 @@ import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport import io.modelcontextprotocol.kotlin.sdk.server.mcp +import java.util.Locale.getDefault import kotlinx.coroutines.* import kotlinx.io.asSink import kotlinx.io.asSource @@ -240,7 +241,134 @@ class Mcp( } } - logger.info("MCP server configured successfully with 1 tool") + server.addPrompt( + name = "analyze-codebase", + description = "Generate a comprehensive analysis prompt for a codebase", + arguments = + listOf( + PromptArgument( + name = "focus", + description = "What aspect to focus on (architecture, security, performance, etc.)", + required = false, + ), + PromptArgument( + name = "language", + description = "Primary programming language of the codebase", + required = false, + ), + ), + ) { request -> + val focus = request.arguments?.get("focus") ?: "general architecture" + val language = request.arguments?.get("language") ?: "any language" + + val promptText = + """ + Please analyze this codebase with a focus on ${focus}. + + Primary language: $language + + Please provide: + 1. Overall architecture and design patterns + 2. Code quality and maintainability assessment + 3. Potential security concerns + 4. Performance considerations + 5. Recommendations for improvements + + Focus particularly on $focus aspects of the code. + """ + .trimIndent() + + GetPromptResult( + description = "Codebase analysis prompt focusing on $focus", + messages = listOf(PromptMessage(role = Role.user, content = TextContent(promptText))), + ) + } + + server.addPrompt( + name = "code-review", + description = "Generate a code review prompt template", + arguments = + listOf( + PromptArgument( + name = "type", + description = "Type of review (security, performance, style, etc.)", + required = false, + ) + ), + ) { request -> + val reviewType = request.arguments?.get("type") ?: "comprehensive" + + val promptText = + """ + Please perform a $reviewType code review of the following code. + + Review criteria: + - Code clarity and readability + - Best practices adherence + - Potential bugs or issues + - Performance implications + - Security considerations + - Maintainability + + Please provide specific feedback with examples and suggestions for improvement. + """ + .trimIndent() + + GetPromptResult( + description = + "${reviewType.replaceFirstChar { if (it.isLowerCase()) it.titlecase(getDefault()) else it.toString() }} code review template", + messages = listOf(PromptMessage(role = Role.user, content = TextContent(promptText))), + ) + } + + server.addResource( + uri = "repo://analysis-results", + name = "Repository Analysis Results", + description = "Latest repository analysis results", + mimeType = "application/json", + ) { + // Return cached analysis results or a placeholder + ReadResourceResult( + contents = + listOf( + TextResourceContents( + uri = "repo://analysis-results", + mimeType = "application/json", + text = """{"message": "No analysis results available yet. Run analyze-repository tool first."}""", + ) + ) + ) + } + + server.addResource( + uri = "repo://metrics", + name = "Repository Metrics", + description = "Code metrics and statistics", + mimeType = "application/json", + ) { + ReadResourceResult( + contents = + listOf( + TextResourceContents( + uri = "repo://metrics", + mimeType = "application/json", + text = + """ + { + "totalFiles": 0, + "linesOfCode": 0, + "languages": [], + "lastAnalyzed": null, + "complexity": "unknown" + } + """ + .trimIndent(), + ) + ) + ) + } + + logger.info("MCP server configured successfully with 1 tool, 2 prompts, and 2 resources") return server } } diff --git a/src/main/kotlin/mcp/code/analysis/service/GitService.kt b/src/main/kotlin/mcp/code/analysis/service/GitService.kt index 34336d7..e7ca5b0 100644 --- a/src/main/kotlin/mcp/code/analysis/service/GitService.kt +++ b/src/main/kotlin/mcp/code/analysis/service/GitService.kt @@ -6,21 +6,17 @@ import mcp.code.analysis.config.AppConfig import org.eclipse.jgit.api.Git /** Service for interacting with Git repositories. */ -open class GitService(private val config: AppConfig? = AppConfig.fromEnv()) { +data class GitService(private val config: AppConfig = AppConfig.fromEnv()) { /** * Clones a Git repository to a temporary directory. * - * Note: When this class is mocked in tests on newer JVMs, constructor interception may be bypassed. - * To keep behavior robust under mocking, we fallback to AppConfig.fromEnv() if `config` is null. - * * @param repoUrl The URL of the Git repository to clone. * @param branch The branch of the repository to clone. * @return The path to the cloned repository. */ - open fun cloneRepository(repoUrl: String, branch: String): File { - val effectiveConfig = config ?: AppConfig.fromEnv() - val workDir = File(effectiveConfig.cloneDirectory) + fun cloneRepository(repoUrl: String, branch: String): File { + val workDir = File(config.cloneDirectory) val repoName = extractRepoName(repoUrl) val targetDir = Files.createTempDirectory(workDir.toPath(), repoName).toFile() Git.cloneRepository().setURI(repoUrl).setDirectory(targetDir).setBranch(branch).call().use { git -> diff --git a/src/test/kotlin/mcp/code/analysis/server/McpTest.kt b/src/test/kotlin/mcp/code/analysis/server/McpTest.kt index c1b09d9..4eb54ba 100644 --- a/src/test/kotlin/mcp/code/analysis/server/McpTest.kt +++ b/src/test/kotlin/mcp/code/analysis/server/McpTest.kt @@ -19,6 +19,7 @@ class McpTest { private lateinit var serverUnderTest: Mcp private val toolHandlerSlot = slot CallToolResult>() + private val resourceHandlerSlot = slot String>() @BeforeEach fun setUp() { @@ -31,6 +32,16 @@ class McpTest { anyConstructed() .addTool(name = any(), description = any(), inputSchema = any(), handler = capture(toolHandlerSlot)) } returns Unit + + every { + anyConstructed() + .addPrompt(name = any(), description = any(), arguments = any(), promptProvider = any()) + } returns Unit + + every { + anyConstructed() + .addResource(name = any(), description = any(), uri = any(), mimeType = any(), readHandler = any()) + } returns Unit } @AfterEach