diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt
index e873d22342..7faf3bb0f0 100644
--- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt
+++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt
@@ -711,6 +711,9 @@ class EMConfig {
if (problemType == ProblemType.GRAPHQL && bbTargetUrl.isBlank()) {
throw ConfigProblemException("In black-box mode for GraphQL APIs, you must set the bbTargetUrl option")
}
+ if (problemType == ProblemType.MCP && base.isBlank()) {
+ throw ConfigProblemException("In black-box mode for MCP servers, you must set the bbTargetUrl option")
+ }
}
if (!blackBox && bbExperiments) {
@@ -1446,7 +1449,8 @@ class EMConfig {
REST(experimental = false),
GRAPHQL(experimental = false),
RPC(experimental = true),
- WEBFRONTEND(experimental = true);
+ WEBFRONTEND(experimental = true),
+ MCP(experimental = true);
override fun isExperimental() = experimental
}
diff --git a/core/src/main/kotlin/org/evomaster/core/Main.kt b/core/src/main/kotlin/org/evomaster/core/Main.kt
index 506955bc26..effe64074a 100644
--- a/core/src/main/kotlin/org/evomaster/core/Main.kt
+++ b/core/src/main/kotlin/org/evomaster/core/Main.kt
@@ -614,6 +614,10 @@ class Main {
WebModule()
}
+ EMConfig.ProblemType.MCP -> {
+ throw IllegalStateException("MCP server analysis is not yet supported")
+ }
+
//this should never happen, unless we add new type and forget to add it here
else -> throw IllegalStateException("Unrecognized problem type: ${config.problemType}")
}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpAction.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpAction.kt
new file mode 100644
index 0000000000..8527e8f5e5
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpAction.kt
@@ -0,0 +1,18 @@
+package org.evomaster.core.problem.mcp
+
+import org.evomaster.core.problem.api.param.Param
+import org.evomaster.core.search.action.MainAction
+import org.evomaster.core.search.gene.Gene
+
+abstract class McpAction(
+ val id: String,
+ parameters: MutableList
+) : MainAction(false, parameters) {
+
+ val actionParameters: List
+ get() = children as List
+
+ override fun getName(): String = id
+
+ override fun seeTopGenes(): List = actionParameters.flatMap { it.seeGenes() }
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpCallResult.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpCallResult.kt
new file mode 100644
index 0000000000..60327de838
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpCallResult.kt
@@ -0,0 +1,22 @@
+package org.evomaster.core.problem.mcp
+
+import org.evomaster.core.search.action.ActionResult
+
+class McpCallResult : ActionResult {
+
+ companion object {
+ const val IS_ERROR = "IS_ERROR"
+ }
+
+ constructor(sourceLocalId: String) : super(sourceLocalId)
+
+ private constructor(other: McpCallResult) : super(other)
+
+ override fun copy(): McpCallResult = McpCallResult(this)
+
+ fun setIsError(isError: Boolean) {
+ addResultValue(IS_ERROR, isError.toString())
+ }
+
+ fun getIsError(): Boolean = getResultValue(IS_ERROR)?.toBoolean() ?: false
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpIndividual.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpIndividual.kt
new file mode 100644
index 0000000000..740f77c9ec
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpIndividual.kt
@@ -0,0 +1,45 @@
+package org.evomaster.core.problem.mcp
+
+import org.evomaster.core.problem.api.ApiWsIndividual
+import org.evomaster.core.problem.enterprise.EnterpriseChildTypeVerifier
+import org.evomaster.core.problem.enterprise.SampleType
+import org.evomaster.core.search.GroupsOfChildren
+import org.evomaster.core.search.Individual
+import org.evomaster.core.search.StructuralElement
+import org.evomaster.core.search.action.ActionComponent
+
+class McpIndividual(
+ sampleType: SampleType,
+ allActions: MutableList,
+ mainSize: Int = allActions.size,
+ sqlSize: Int = 0,
+ mongoSize: Int = 0,
+ groups: GroupsOfChildren =
+ getEnterpriseTopGroups(allActions, mainSize, sqlSize, mongoSize, 0, 0, 0, 0)
+) : ApiWsIndividual(
+ sampleType = sampleType,
+ children = allActions,
+ childTypeVerifier = EnterpriseChildTypeVerifier(McpAction::class.java),
+ groups = groups
+) {
+
+ override fun copyContent(): Individual {
+ return McpIndividual(
+ sampleType,
+ children.map { it.copy() }.toMutableList() as MutableList,
+ mainSize = groupsView()!!.sizeOfGroup(GroupsOfChildren.MAIN)
+ )
+ }
+
+ fun addMcpAction(relativePosition: Int = -1, action: McpAction) {
+ addMainActionInEmptyEnterpriseGroup(relativePosition, action)
+ }
+
+ fun removeMcpActionAt(relativePosition: Int) {
+ removeMainActionGroupAt(relativePosition)
+ }
+
+ override fun seeMainExecutableActions(): List {
+ return super.seeMainExecutableActions() as List
+ }
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpInputParam.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpInputParam.kt
new file mode 100644
index 0000000000..035ab3d118
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpInputParam.kt
@@ -0,0 +1,8 @@
+package org.evomaster.core.problem.mcp
+
+import org.evomaster.core.problem.api.param.Param
+import org.evomaster.core.search.gene.Gene
+
+class McpInputParam(name: String, gene: Gene) : Param(name, gene) {
+ override fun copyContent(): Param = McpInputParam(name, gene.copy())
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpResourceReadAction.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpResourceReadAction.kt
new file mode 100644
index 0000000000..1ec9c4246b
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpResourceReadAction.kt
@@ -0,0 +1,33 @@
+package org.evomaster.core.problem.mcp
+
+import org.evomaster.core.search.action.Action
+
+/**
+ * Action to read an MCP resource.
+ * For direct resources (isTemplate=false), the URI is fixed and no genes are mutable.
+ * For template resources (isTemplate=true), one StringGene per URI template variable
+ * (e.g. {city} in weather:///{city}/current) is created and fuzzed independently;
+ * call resolvedUri() to interpolate current gene values into the template.
+ */
+class McpResourceReadAction(
+ val uriTemplate: String,
+ val uriParams: List,
+ val isTemplate: Boolean = false
+) : McpAction(
+ id = "resource",
+ parameters = uriParams.toMutableList()
+) {
+ override fun getName(): String = "resource:$uriTemplate"
+
+ fun resolvedUri(): String {
+ if (!isTemplate) return uriTemplate
+ var result = uriTemplate
+ uriParams.forEach { param ->
+ result = result.replace("{${param.name}}", param.primaryGene().getValueAsRawString())
+ }
+ return result
+ }
+
+ override fun copyContent(): Action =
+ McpResourceReadAction(uriTemplate, uriParams.map { it.copy() as McpUriParam }, isTemplate)
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpToolCallAction.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpToolCallAction.kt
new file mode 100644
index 0000000000..bd4ebdda1a
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpToolCallAction.kt
@@ -0,0 +1,17 @@
+package org.evomaster.core.problem.mcp
+
+import org.evomaster.core.search.action.Action
+import org.evomaster.core.search.gene.ObjectGene
+
+class McpToolCallAction(
+ val toolName: String,
+ val inputSchema: ObjectGene,
+ val description: String = ""
+) : McpAction(
+ id = "tool:$toolName",
+ parameters = mutableListOf(McpInputParam("input", inputSchema))
+) {
+ override fun copyContent(): Action {
+ return McpToolCallAction(toolName, inputSchema.copy() as ObjectGene, description)
+ }
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpUriParam.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpUriParam.kt
new file mode 100644
index 0000000000..46d9bbdcb7
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/McpUriParam.kt
@@ -0,0 +1,8 @@
+package org.evomaster.core.problem.mcp
+
+import org.evomaster.core.problem.api.param.Param
+import org.evomaster.core.search.gene.Gene
+
+class McpUriParam(name: String, gene: Gene) : Param(name, gene) {
+ override fun copyContent(): Param = McpUriParam(name, gene.copy())
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/client/HttpMcpClient.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/client/HttpMcpClient.kt
new file mode 100644
index 0000000000..f3b6a801a4
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/client/HttpMcpClient.kt
@@ -0,0 +1,28 @@
+package org.evomaster.core.problem.mcp.client
+
+class HttpMcpClient(private val baseUrl: String) : McpClient {
+
+ fun initialize() {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+
+ override fun listTools(): List {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+
+ override fun listResources(): List {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+
+ override fun listResourceTemplates(): List {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+
+ override fun callTool(name: String, arguments: Map): McpToolResult {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+
+ override fun readResource(uri: String): McpResourceResult {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/client/McpClient.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/client/McpClient.kt
new file mode 100644
index 0000000000..8f0cc0cda7
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/client/McpClient.kt
@@ -0,0 +1,9 @@
+package org.evomaster.core.problem.mcp.client
+
+interface McpClient {
+ fun listTools(): List
+ fun listResources(): List
+ fun listResourceTemplates(): List
+ fun callTool(name: String, arguments: Map): McpToolResult
+ fun readResource(uri: String): McpResourceResult
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/client/McpDTOs.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/client/McpDTOs.kt
new file mode 100644
index 0000000000..5fea8e0f36
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/client/McpDTOs.kt
@@ -0,0 +1,36 @@
+package org.evomaster.core.problem.mcp.client
+
+data class McpToolDefinition(
+ val name: String,
+ val description: String = "",
+ val inputSchema: Map = emptyMap()
+)
+
+data class McpResourceDefinition(
+ val uri: String,
+ val name: String = "",
+ val description: String = "",
+ val mimeType: String? = null
+)
+
+data class McpResourceTemplate(
+ val uriTemplate: String,
+ val name: String = "",
+ val description: String = ""
+)
+
+data class McpToolResult(
+ val content: List = emptyList(),
+ val isError: Boolean = false
+)
+
+data class McpResourceResult(
+ val contents: List = emptyList()
+)
+
+data class McpContent(
+ val type: String,
+ val text: String? = null,
+ val uri: String? = null,
+ val mimeType: String? = null
+)
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpBlackBoxFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpBlackBoxFitness.kt
new file mode 100644
index 0000000000..fe65b84d33
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpBlackBoxFitness.kt
@@ -0,0 +1,17 @@
+package org.evomaster.core.problem.mcp.service
+
+import org.evomaster.core.problem.mcp.McpIndividual
+import org.evomaster.core.search.EvaluatedIndividual
+
+class McpBlackBoxFitness : McpFitness() {
+
+ override fun doCalculateCoverage(
+ individual: McpIndividual,
+ targets: Set,
+ allTargets: Boolean,
+ fullyCovered: Boolean,
+ descriptiveIds: Boolean,
+ ): EvaluatedIndividual? {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpBlackBoxModule.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpBlackBoxModule.kt
new file mode 100644
index 0000000000..be0fbc4310
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpBlackBoxModule.kt
@@ -0,0 +1,79 @@
+package org.evomaster.core.problem.mcp.service
+
+import com.google.inject.AbstractModule
+import com.google.inject.TypeLiteral
+import org.evomaster.core.output.service.NoTestCaseWriter
+import org.evomaster.core.output.service.TestCaseWriter
+import org.evomaster.core.problem.enterprise.service.EnterpriseSampler
+import org.evomaster.core.problem.mcp.McpIndividual
+import org.evomaster.core.remote.service.RemoteController
+import org.evomaster.core.remote.service.RemoteControllerImplementation
+import org.evomaster.core.search.service.Archive
+import org.evomaster.core.search.service.FitnessFunction
+import org.evomaster.core.search.service.FlakinessDetector
+import org.evomaster.core.search.service.Minimizer
+import org.evomaster.core.search.service.Sampler
+
+class McpBlackBoxModule(
+ val usingRemoteController: Boolean
+) : AbstractModule() {
+
+ override fun configure() {
+
+ bind(object : TypeLiteral>() {})
+ .to(McpSampler::class.java)
+ .asEagerSingleton()
+
+ bind(object : TypeLiteral>() {})
+ .to(McpSampler::class.java)
+ .asEagerSingleton()
+
+ bind(object : TypeLiteral>() {})
+ .to(McpSampler::class.java)
+ .asEagerSingleton()
+
+ bind(McpSampler::class.java)
+ .asEagerSingleton()
+
+ bind(object : TypeLiteral>() {})
+ .to(McpBlackBoxFitness::class.java)
+ .asEagerSingleton()
+
+ bind(object : TypeLiteral>() {})
+ .to(McpBlackBoxFitness::class.java)
+ .asEagerSingleton()
+
+ bind(object : TypeLiteral>() {})
+ .asEagerSingleton()
+
+ bind(object : TypeLiteral>() {})
+ .to(object : TypeLiteral>() {})
+
+ bind(Archive::class.java)
+ .to(object : TypeLiteral>() {})
+
+ bind(object : TypeLiteral>() {})
+ .asEagerSingleton()
+
+ bind(object : TypeLiteral>() {})
+ .to(object : TypeLiteral>() {})
+ .asEagerSingleton()
+
+ bind(object : TypeLiteral>() {})
+ .asEagerSingleton()
+
+ bind(object : TypeLiteral>() {})
+ .to(object : TypeLiteral>() {})
+ .asEagerSingleton()
+
+ if (usingRemoteController) {
+ bind(RemoteController::class.java)
+ .to(RemoteControllerImplementation::class.java)
+ .asEagerSingleton()
+ }
+
+ bind(TestCaseWriter::class.java)
+ .to(NoTestCaseWriter::class.java)
+ .asEagerSingleton()
+ }
+}
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpFitness.kt
new file mode 100644
index 0000000000..dd09fa7764
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpFitness.kt
@@ -0,0 +1,6 @@
+package org.evomaster.core.problem.mcp.service
+
+import org.evomaster.core.problem.mcp.McpIndividual
+import org.evomaster.core.search.service.FitnessFunction
+
+abstract class McpFitness : FitnessFunction()
diff --git a/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpSampler.kt b/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpSampler.kt
new file mode 100644
index 0000000000..e4b5e27c3a
--- /dev/null
+++ b/core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpSampler.kt
@@ -0,0 +1,36 @@
+package org.evomaster.core.problem.mcp.service
+
+import org.evomaster.client.java.controller.api.dto.SutInfoDto
+import org.evomaster.core.problem.api.service.ApiWsSampler
+import org.evomaster.core.problem.mcp.McpIndividual
+import org.evomaster.core.problem.mcp.client.HttpMcpClient
+import javax.annotation.PostConstruct
+
+class McpSampler : ApiWsSampler() {
+
+ private lateinit var mcpClient: HttpMcpClient
+
+ @PostConstruct
+ fun initialize() {
+ mcpClient = HttpMcpClient(config.base)
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+
+ override fun sampleAtRandom(): McpIndividual {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+
+ override fun smartSample(): McpIndividual {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+
+ override fun hasSpecialInitForSmartSampler(): Boolean {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+
+ override fun initSeededTests(infoDto: SutInfoDto?) {
+ throw UnsupportedOperationException("MCP server analysis is not yet supported")
+ }
+
+ fun getMcpClient(): HttpMcpClient = mcpClient
+}