Skip to content

Adding MCP problem type basic sampler infrastructure#1628

Open
guidorc wants to merge 26 commits into
WebFuzzing:masterfrom
guidorc:feature/mcp-blackbox-sampler
Open

Adding MCP problem type basic sampler infrastructure#1628
guidorc wants to merge 26 commits into
WebFuzzing:masterfrom
guidorc:feature/mcp-blackbox-sampler

Conversation

@guidorc

@guidorc guidorc commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adding the basic search infrastructure for blackbox fuzzing of Model Context Protocol (MCP) servers. The approach is: connect to the target server, discover its capabilities via the standard MCP handshake, automatically construct a mutable input representation (gene tree) for each tool and resource from their JSON Schema definitions, then use a search algorithm to evolve a sequences of calls that maximise coverage; reaching each capability and obtaining a non-error response

Components

MCP client (HttpMcpClient, McpClient, McpDTOs)

An HTTP-based client that implements the MCP JSON-RPC protocol: initialization handshake, tool invocation (tools/call), static resource reads (resources/read), and capability discovery (tools/list, resources/list, resources/templates/list).

Action model (McpAction, McpToolCallAction, McpResourceReadAction)

Two concrete action types mirror the two MCP interaction patterns: calling a named tool with structured arguments, and reading a resource by URI (including template URIs with mutable parameters).

Individual (McpIndividual)

A test case represented as an ordered sequence of McpActions, integrating with EvoMaster's existing EnterpriseIndividual and mutation infrastructure.

Sampler (McpSampler)

On startup, connects to the target MCP server, performs the handshake, and discovers all tools and resources. Each tool's JSON Schema input definition is converted into an ObjectGene tree used by the search engine to mutate arguments. Pre-built single-action individuals ensure every capability is exercised at least once before random search begins.

Fitness function (McpBlackBoxFitness)

Evaluates individuals by executing their actions sequentially and scoring coverage targets based on observable protocol signals: 1.0 for a successful tool call or resource read, 0.5 for a server-reported error (partial credit keeps the search gradient useful). Exceptions break the action sequence early.

McpBlackBoxModule

Wires all the above into EvoMaster's dependency injection container and registers the problem type in the main entry point.

Follow Up

  • McpTestCaseWriter — the test output pipeline currently uses NoTestCaseWriter, so generated test files have empty bodies. A dedicated writer that emits executable MCP
    client calls is needed.
  • Format-aware gene building — string fields carrying semantic formats (date-time, email, uuid) are built as plain StringGene. The sampler needs a three-layer cascade
    (explicit JSON Schema format → type switch → name-based heuristics) to generate valid values for these fields, matching the approach used by the REST builder.
  • SortingHelper / TestCaseNamingStrategyFactory — McpIndividual is not yet registered in the test sorting and naming strategies, causing a handled warning during output.

guido-rodriguez_sfemu and others added 15 commits May 28, 2026 22:11
Introduces a new problem domain for blackbox testing of MCP (Model
Context Protocol) servers, following the same architecture as the
existing GraphQL and RPC problem domains.

New components under core/src/main/kotlin/.../problem/mcp/:
- McpAction (abstract base), McpToolCallAction (tools/call),
  McpResourceReadAction (resources/read), McpInputParam, McpUriParam
- McpIndividual: chromosome wrapping sequences of MCP actions
- McpCallResult: per-action execution record storing isError flag
- client/McpClient (interface), client/HttpMcpClient (JSON-RPC 2.0
  over HTTP with pagination), client/McpDTOs
- service/McpSampler: @PostConstruct discovery via tools/list,
  resources/list, resources/templates/list; gene tree building from
  JSON Schema; random and smart (ad-hoc) sampling
- service/McpFitness (abstract base), service/McpBlackBoxFitness:
  per-action scoring using isError flag and successful reads as signals
- service/McpBlackBoxModule: Guice bindings mirroring GraphQLBlackBoxModule

Unit tests cover action construction, individual copy/mutation, and
HttpMcpClient JSON-RPC parsing via WireMock stubs.
- Add ProblemType.MCP (experimental) to EMConfig enum
- Add bbTargetUrl validation for MCP black-box mode
- Route ProblemType.MCP to McpBlackBoxModule in Main.init()
- Add getAlgorithmKeyMcp() supporting RANDOM, MIO, MOSA, WTS, SMARTS
- Dispatch MCP algorithm key in Main.run()
- Bind NoTestCaseWriter in McpBlackBoxModule

EvoMaster can now be invoked with:
  --blackBox true --problemType MCP --bbTargetUrl <url>
Connection failures during @PostConstruct were surfacing as an uncaught
InvocationTargetException labelled as an EvoMaster bug. Wrap the
listTools() call so a failed connection to the MCP server produces a
clean, user-readable SutProblemException instead.
Servers may return 400/404/405 for MCP methods they don't support
(e.g., resources/list on a tools-only server). Previously this caused
an IOException from HttpURLConnection.inputStream on non-2xx responses,
crashing the sampler during initialization.

Extracted openConnection() helper and made post() return null on
400/404/405, which callers treat as an empty/error result.
* Setting up MCP Problem Type Skeleton

* remove db related size from MCP individual

* Fix for resource read action id

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* feedback

* adjust resource read action parameters

---------

Co-authored-by: guido-rodriguez_sfemu <guido.rodriguez@salesforce.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@guidorc
guidorc changed the base branch from master to external-pr-mcp July 4, 2026 16:37
@guidorc
guidorc changed the base branch from external-pr-mcp to master July 4, 2026 16:41
@guidorc
guidorc requested a review from Pgarrett July 7, 2026 00:57
Comment thread core/src/main/kotlin/org/evomaster/core/problem/mcp/client/HttpMcpClient.kt Outdated
Comment thread core/src/main/kotlin/org/evomaster/core/problem/mcp/client/HttpMcpClient.kt Outdated
Comment thread core/src/main/kotlin/org/evomaster/core/problem/mcp/client/HttpMcpClient.kt Outdated
Comment thread core/src/main/kotlin/org/evomaster/core/problem/mcp/client/HttpMcpClient.kt Outdated
Comment thread core/src/main/kotlin/org/evomaster/core/problem/mcp/client/HttpMcpClient.kt Outdated
Comment thread core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpSampler.kt Outdated
Comment thread core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpSampler.kt Outdated
Comment thread core/src/main/kotlin/org/evomaster/core/problem/mcp/service/McpSampler.kt Outdated
Comment thread core/src/main/kotlin/org/evomaster/core/problem/mcp/McpIndividual.kt Outdated
Comment thread core/src/test/kotlin/org/evomaster/core/problem/mcp/client/HttpMcpClientTest.kt Outdated
@guidorc guidorc changed the title Adding MCP problem type basic fuzzing infrastructure Adding MCP problem type basic sampler infrastructure Jul 13, 2026
@guidorc
guidorc force-pushed the feature/mcp-blackbox-sampler branch from c7cc742 to d4c0c34 Compare July 13, 2026 03:05
@guidorc
guidorc requested a review from Pgarrett July 14, 2026 02:36
/**
* MCP protocol version negotiated during the initialize handshake.
*/
const val PROTOCOL_VERSION = "2025-11-25"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a question, could this be a parameter for when fuzzing an MCP API? Or are protocol versions not backward compatible?

mapOf(
"protocolVersion" to "2024-11-05",
"capabilities" to emptyMap<String, Any>(),
"clientInfo" to mapOf("name" to "EvoMaster", "version" to "1.0.0")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unaddressed comment @guidorc

?: return McpToolResult(isError = true)
val result = response["result"] as? Map<String, Any?> ?: return McpToolResult(isError = true)
val rawContent = result["content"] as? List<Map<String, Any?>> ?: emptyList()
val content = rawContent.map { c ->

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Favour readability, this could be extracted to a getMcpContent method

val rawContent = result["content"] as? List<Map<String, Any?>> ?: emptyList()
val content = rawContent.map { c ->
val type = c["type"] as? String
?: throw IllegalStateException("tools/call response content item missing required 'type' field")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add more information on which tool call is actually causing this error? If we're fuzzing to test an API, it's best if we provide the most information to our users. Applicable to the other exceptions being thrown

import javax.ws.rs.client.Entity
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import kotlin.String

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are all this imports needed?

val result = response["result"] as? Map<String, Any?> ?: return McpResourceResult()
val rawContents = result["contents"] as? List<Map<String, Any?>> ?: emptyList()
val contents = rawContents.map { c ->
McpContent(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come this McpContent can be created without type and we throw an exception for the one in callTool? It's not a good practice to use the same object to represent different things. Either this should have a type assigned, or it should be a different object

/** Content item within a tool or resource response. */
data class McpContent(
val type: String,
val type: String? = null,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything is nullable? Why? I fear we might hide issues due to this flexibility

}

// Discover tools
val tools = mcpClient.listTools()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe extract this and subsequent discoveries to their own methods. That way we have a more concise and declarative initialize method

return ind
}

val n = randomness.nextInt(1, getMaxTestSizeDuringSampler())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's n? Choose a better name

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants