AI Protocol Mocking (MCP & A2A)
MockServer can mock AI protocol servers — including MCP (Model Context Protocol) servers and A2A (Agent-to-Agent Protocol) agents — enabling you to test AI clients against predictable mock services without running real AI infrastructure.
This is distinct from MockServer's built-in MCP control plane (which lets AI assistants control MockServer). These features let you mock other people's MCP and A2A servers.
SSE Streaming Responses
Server-Sent Events (SSE) responses stream events to clients over a long-lived HTTP connection. This is the transport used by MCP's Streamable HTTP protocol and many other AI APIs.
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "GET",
"path": "/events"
},
"httpSseResponse": {
"statusCode": 200,
"events": [
{
"event": "message",
"data": "hello world",
"id": "1"
},
{
"event": "update",
"data": "second event",
"id": "2"
}
],
"closeConnection": true
}
}'
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "GET",
"path": "/events"
},
"httpSseResponse": {
"statusCode": 200,
"events": [
{
"event": "message",
"data": "first event",
"id": "1"
},
{
"event": "message",
"data": "delayed event",
"id": "2",
"delay": {
"timeUnit": "MILLISECONDS",
"value": 500
}
}
],
"closeConnection": true
}
}'
Each event is sent after the specified delay, enabling realistic simulation of streaming APIs.
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "GET",
"path": "/events"
},
"httpSseResponse": {
"statusCode": 200,
"events": [
{
"event": "message",
"data": "line 1\nline 2\nline 3",
"id": "1"
}
],
"closeConnection": true
}
}'
Multi-line data is automatically split into multiple data: lines per the SSE specification.
WebSocket Mocking
Mock WebSocket endpoints that perform the upgrade handshake and send a sequence of text or binary messages. Useful for testing clients that connect to WebSocket APIs (GraphQL subscriptions, real-time feeds, etc.).
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("GET")
.withPath("/ws")
)
.respondWithWebSocket(
webSocketResponse()
.withMessage(webSocketMessage("hello"))
.withMessage(webSocketMessage("world"))
.withCloseConnection(true)
);
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "GET",
"path": "/ws"
},
"httpWebSocketResponse": {
"messages": [
{"text": "hello"},
{"text": "world"}
],
"closeConnection": true
}
}'
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "GET",
"path": "/ws"
},
"httpWebSocketResponse": {
"messages": [
{"text": "immediate"},
{"text": "delayed", "delay": {"timeUnit": "MILLISECONDS", "value": 500}}
],
"closeConnection": true
}
}'
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "GET",
"path": "/graphql"
},
"httpWebSocketResponse": {
"subprotocol": "graphql-ws",
"messages": [
{"text": "{\"type\": \"connection_ack\"}"},
{"text": "{\"type\": \"next\", \"payload\": {\"data\": {\"newMessage\": \"hello\"}}}"}
],
"closeConnection": true
}
}'
The subprotocol field is included in the WebSocket handshake response, allowing clients that require specific subprotocols (like graphql-ws or graphql-transport-ws) to connect successfully.
JSON-RPC Body Matching
Match HTTP requests containing JSON-RPC 2.0 bodies by method name. This is the foundation for mocking any JSON-RPC based protocol (MCP, A2A, language servers, etc.).
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("POST")
.withPath("/rpc")
.withBody(jsonRpc("tools/list"))
)
.respond(
response()
.withStatusCode(200)
.withBody("{\"jsonrpc\": \"2.0\", \"result\": {\"tools\": []}, \"id\": 1}")
);
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "POST",
"path": "/rpc",
"body": {
"type": "JSON_RPC",
"method": "tools/list"
}
},
"httpResponse": {
"statusCode": 200,
"body": "{\"jsonrpc\": \"2.0\", \"result\": {\"tools\": []}, \"id\": 1}"
}
}'
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "POST",
"path": "/rpc",
"body": {
"type": "JSON_RPC",
"method": "tools/call",
"paramsSchema": "{\"type\": \"object\", \"properties\": {\"name\": {\"type\": \"string\"}}, \"required\": [\"name\"]}"
}
},
"httpResponse": {
"statusCode": 200,
"body": "{\"jsonrpc\": \"2.0\", \"result\": {\"content\": [{\"type\": \"text\", \"text\": \"result\"}]}, \"id\": 1}"
}
}'
The paramsSchema field is a JSON Schema that validates the params field of the JSON-RPC request. The request only matches if the params are valid according to the schema.
MCP Server Mocking
The McpMockBuilder generates a complete set of expectations to make MockServer behave as a mock MCP (Model Context Protocol) server. This enables testing MCP clients without running a real MCP server.
The builder handles all protocol details: initialize, ping, tools/list, tools/call, resources/list, resources/read, prompts/list, prompts/get, and notifications/initialized. JSON-RPC response IDs are automatically echoed from the request.
import static org.mockserver.client.McpMockBuilder.mcpMock;
MockServerClient mockServerClient = new MockServerClient("localhost", 1080);
mcpMock("/mcp")
.withServerName("TestMCP")
.withServerVersion("1.0.0")
.withTool("get_weather")
.withDescription("Get weather for a city")
.withInputSchema("{\"type\": \"object\", \"properties\": {\"city\": {\"type\": \"string\"}}, \"required\": [\"city\"]}")
.respondingWith("72F and sunny")
.and()
.withTool("search")
.withDescription("Search the knowledge base")
.respondingWith("No results found")
.and()
.applyTo(mockServerClient);
MockServerClient mockServerClient = new MockServerClient("localhost", 1080);
mcpMock("/mcp")
.withServerName("ConfigServer")
.withResource("config://app")
.withName("App Configuration")
.withDescription("Application configuration")
.withMimeType("application/json")
.withContent("{\"debug\": true, \"logLevel\": \"info\"}")
.and()
.withResource("docs://readme")
.withName("README")
.withMimeType("text/plain")
.withContent("Welcome to the application")
.and()
.applyTo(mockServerClient);
MockServerClient mockServerClient = new MockServerClient("localhost", 1080);
mcpMock("/mcp")
.withServerName("PromptServer")
.withPrompt("code_review")
.withDescription("Review code changes for issues")
.withArgument("language", "Programming language", true)
.withArgument("style", "Review style (brief/detailed)", false)
.respondingWith("user", "Please review the following code for bugs and style issues")
.and()
.applyTo(mockServerClient);
MockServerClient mockServerClient = new MockServerClient("localhost", 1080);
mcpMock("/mcp")
.withServerName("FullMCPServer")
.withServerVersion("2.0.0")
.withProtocolVersion("2025-03-26")
.withTool("calculator")
.withDescription("Basic arithmetic calculator")
.withInputSchema("{\"type\": \"object\", \"properties\": {\"expression\": {\"type\": \"string\"}}, \"required\": [\"expression\"]}")
.respondingWith("42")
.and()
.withTool("failing_tool")
.withDescription("A tool that always fails")
.respondingWith("Something went wrong", true)
.and()
.withResource("config://app")
.withName("App Config")
.withContent("{\"version\": \"1.0\"}")
.and()
.withPrompt("summarize")
.withDescription("Summarize text")
.respondingWith("user", "Please summarize the following text concisely")
.and()
.applyTo(mockServerClient);
This creates expectations for all MCP methods: initialize, ping, notifications/initialized, tools/list, tools/call (per tool), resources/list, resources/read (per resource), prompts/list, and prompts/get (per prompt).
A2A Agent Mocking
The A2aMockBuilder generates expectations to make MockServer behave as a mock A2A (Agent-to-Agent Protocol) agent. This enables testing A2A clients that discover and communicate with agents.
The builder handles: Agent Card discovery (GET /.well-known/agent.json), tasks/send, tasks/get, and tasks/cancel.
import static org.mockserver.client.A2aMockBuilder.a2aMock;
MockServerClient mockServerClient = new MockServerClient("localhost", 1080);
a2aMock("/agent")
.withAgentName("TranslationAgent")
.withAgentDescription("Translates text between languages")
.withAgentVersion("1.0.0")
.withSkill("translate")
.withName("Translation")
.withDescription("Translates text between any two languages")
.withTag("translation")
.withTag("language")
.withExample("Translate 'hello' to Spanish")
.and()
.withDefaultTaskResponse("Translation complete: Hola")
.applyTo(mockServerClient);
MockServerClient mockServerClient = new MockServerClient("localhost", 1080);
a2aMock("/agent")
.withAgentName("SmartAgent")
.withSkill("translate")
.withName("Translation")
.and()
.withSkill("summarize")
.withName("Summarization")
.and()
.onTaskSend()
.matchingMessage("translate.*")
.respondingWith("Translated: Bonjour le monde")
.and()
.onTaskSend()
.matchingMessage("summarize.*")
.respondingWith("Summary: Brief overview of the content")
.and()
.onTaskSend()
.matchingMessage("fail.*")
.respondingWith("Unable to process request", true)
.and()
.applyTo(mockServerClient);
Custom task handlers use regex pattern matching on the message text. Handlers with more specific patterns should be added first. The default tasks/send handler matches any message that doesn't match a custom handler.
MockServerClient mockServerClient = new MockServerClient("localhost", 1080);
// Create the mock A2A agent
a2aMock("/agent")
.withAgentName("TestAgent")
.withAgentVersion("1.0.0")
.withAgentUrl("http://localhost:1080/agent")
.withAgentCardPath("/.well-known/agent.json")
.withSkill("process")
.withName("Data Processing")
.withDescription("Process data")
.and()
.withDefaultTaskResponse("Processing complete")
.applyTo(mockServerClient);
// Client workflow:
// 1. GET /.well-known/agent.json → discovers agent card with skills
// 2. POST /agent (tasks/send) → sends message, gets completed task
// 3. POST /agent (tasks/get) → retrieves task status
// 4. POST /agent (tasks/cancel) → cancels a running task