Skip to content

Commit 930a1a4

Browse files
author
Mark Pollack
committed
Remove MCP SDK dependency, add own JSON abstraction layer
Replace mcp-json and mcp-json-jackson2 dependencies with ACP's own JSON abstraction in com.agentclientprotocol.sdk.json: - AcpJsonMapper interface (replaces McpJsonMapper) - TypeRef<T> for generic type capture (replaces MCP TypeRef) - JacksonAcpJsonMapper implementation - ServiceLoader-based discovery via AcpJsonMapperSupplier SPI This eliminates the version conflict reported in #3 where McpJsonMapper.getDefault() was removed in MCP 0.18.0+, causing NoSuchMethodError for projects using modern MCP versions. Also updates README dependency versions from 0.9.0 to 0.11.0.
1 parent 62e5590 commit 930a1a4

41 files changed

Lines changed: 649 additions & 128 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Three API styles for building agents:
3030
<dependency>
3131
<groupId>com.agentclientprotocol</groupId>
3232
<artifactId>acp-core</artifactId>
33-
<version>0.9.0</version>
33+
<version>0.11.0</version>
3434
</dependency>
3535
```
3636

@@ -39,7 +39,7 @@ For annotation-based agent development:
3939
<dependency>
4040
<groupId>com.agentclientprotocol</groupId>
4141
<artifactId>acp-agent-support</artifactId>
42-
<version>0.9.0</version>
42+
<version>0.11.0</version>
4343
</dependency>
4444
```
4545

@@ -48,7 +48,7 @@ For WebSocket server support (agents accepting WebSocket connections):
4848
<dependency>
4949
<groupId>com.agentclientprotocol</groupId>
5050
<artifactId>acp-websocket-jetty</artifactId>
51-
<version>0.9.0</version>
51+
<version>0.11.0</version>
5252
</dependency>
5353
```
5454

@@ -327,7 +327,7 @@ import java.net.URI;
327327

328328
var transport = new WebSocketAcpClientTransport(
329329
URI.create("ws://localhost:8080/acp"),
330-
McpJsonMapper.getDefault()
330+
AcpJsonMapper.createDefault()
331331
);
332332
AcpSyncClient client = AcpClient.sync(transport).build();
333333
```
@@ -339,7 +339,7 @@ import com.agentclientprotocol.sdk.agent.transport.WebSocketAcpAgentTransport;
339339
var transport = new WebSocketAcpAgentTransport(
340340
8080, // port
341341
"/acp", // path
342-
McpJsonMapper.getDefault()
342+
AcpJsonMapper.createDefault()
343343
);
344344
AcpAsyncAgent agent = AcpAgent.async(transport)
345345
// ... handlers ...
@@ -443,13 +443,14 @@ This SDK is part of the [Agent Client Protocol](https://agentclientprotocol.com/
443443

444444
## Roadmap
445445

446-
### v0.9.0 (Current — [Maven Central](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-core))
446+
### v0.11.0 (Current — [Maven Central](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-core))
447447
- Client and Agent SDKs with async/sync APIs
448448
- Stdio and WebSocket transports
449449
- Capability negotiation
450450
- Structured error handling
451451
- Full protocol compliance (all SessionUpdate types, MCP configs, `_meta` extensibility)
452-
- 258 tests
452+
- Own JSON abstraction layer (`AcpJsonMapper`) — no MCP SDK dependency
453+
- Pluggable JSON via `ServiceLoader` SPI
453454

454455
### v1.0.0 (Planned)
455456
- Production hardening

acp-core/pom.xml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,6 @@
2323
<artifactId>reactor-core</artifactId>
2424
</dependency>
2525

26-
<!-- MCP Java SDK JSON utilities (reused for ACP) -->
27-
<dependency>
28-
<groupId>io.modelcontextprotocol.sdk</groupId>
29-
<artifactId>mcp-json</artifactId>
30-
</dependency>
31-
<dependency>
32-
<groupId>io.modelcontextprotocol.sdk</groupId>
33-
<artifactId>mcp-json-jackson2</artifactId>
34-
</dependency>
35-
3626
<!-- Jackson JSON processing -->
3727
<dependency>
3828
<groupId>com.fasterxml.jackson.core</groupId>

acp-core/src/main/java/com/agentclientprotocol/sdk/agent/DefaultAcpAsyncAgent.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import com.agentclientprotocol.sdk.spec.AcpAgentSession;
1515
import com.agentclientprotocol.sdk.spec.AcpAgentTransport;
1616
import com.agentclientprotocol.sdk.spec.AcpSchema;
17-
import io.modelcontextprotocol.json.TypeRef;
17+
import com.agentclientprotocol.sdk.json.TypeRef;
1818
import org.slf4j.Logger;
1919
import org.slf4j.LoggerFactory;
2020
import reactor.core.publisher.Mono;

acp-core/src/main/java/com/agentclientprotocol/sdk/agent/transport/StdioAcpAgentTransport.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import com.agentclientprotocol.sdk.spec.AcpSchema;
2222
import com.agentclientprotocol.sdk.spec.AcpSchema.JSONRPCMessage;
2323
import com.agentclientprotocol.sdk.util.Assert;
24-
import io.modelcontextprotocol.json.McpJsonMapper;
25-
import io.modelcontextprotocol.json.TypeRef;
24+
import com.agentclientprotocol.sdk.json.AcpJsonMapper;
25+
import com.agentclientprotocol.sdk.json.TypeRef;
2626
import org.slf4j.Logger;
2727
import org.slf4j.LoggerFactory;
2828
import reactor.core.publisher.Flux;
@@ -56,7 +56,7 @@ public class StdioAcpAgentTransport implements AcpAgentTransport {
5656

5757
private static final Logger logger = LoggerFactory.getLogger(StdioAcpAgentTransport.class);
5858

59-
private final McpJsonMapper jsonMapper;
59+
private final AcpJsonMapper jsonMapper;
6060

6161
private final InputStream inputStream;
6262

@@ -87,15 +87,15 @@ public class StdioAcpAgentTransport implements AcpAgentTransport {
8787
* System.in and System.out for communication.
8888
*/
8989
public StdioAcpAgentTransport() {
90-
this(McpJsonMapper.getDefault());
90+
this(AcpJsonMapper.createDefault());
9191
}
9292

9393
/**
9494
* Creates a new StdioAcpAgentTransport with the specified JsonMapper using
9595
* System.in and System.out for communication.
9696
* @param jsonMapper The JsonMapper to use for JSON serialization/deserialization
9797
*/
98-
public StdioAcpAgentTransport(McpJsonMapper jsonMapper) {
98+
public StdioAcpAgentTransport(AcpJsonMapper jsonMapper) {
9999
this(jsonMapper, System.in, System.out);
100100
}
101101

@@ -106,7 +106,7 @@ public StdioAcpAgentTransport(McpJsonMapper jsonMapper) {
106106
* @param inputStream The input stream to read messages from (client → agent)
107107
* @param outputStream The output stream to write messages to (agent → client)
108108
*/
109-
public StdioAcpAgentTransport(McpJsonMapper jsonMapper, InputStream inputStream, OutputStream outputStream) {
109+
public StdioAcpAgentTransport(AcpJsonMapper jsonMapper, InputStream inputStream, OutputStream outputStream) {
110110
Assert.notNull(jsonMapper, "The JsonMapper can not be null");
111111
Assert.notNull(inputStream, "The InputStream can not be null");
112112
Assert.notNull(outputStream, "The OutputStream can not be null");

acp-core/src/main/java/com/agentclientprotocol/sdk/client/AcpAsyncClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import com.agentclientprotocol.sdk.capabilities.NegotiatedCapabilities;
1010
import com.agentclientprotocol.sdk.spec.AcpClientTransport;
11-
import io.modelcontextprotocol.json.TypeRef;
11+
import com.agentclientprotocol.sdk.json.TypeRef;
1212
import com.agentclientprotocol.sdk.spec.AcpSchema;
1313
import com.agentclientprotocol.sdk.spec.AcpSession;
1414
import com.agentclientprotocol.sdk.util.Assert;
@@ -53,7 +53,7 @@
5353
* AgentParameters params = AgentParameters.builder("gemini")
5454
* .arg("--experimental-acp")
5555
* .build();
56-
* StdioAcpClientTransport transport = new StdioAcpClientTransport(params, McpJsonMapper.getDefault());
56+
* StdioAcpClientTransport transport = new StdioAcpClientTransport(params, AcpJsonMapper.createDefault());
5757
*
5858
* // Create client
5959
* AcpAsyncClient client = AcpClient.async(transport)

acp-core/src/main/java/com/agentclientprotocol/sdk/client/AcpClient.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import com.agentclientprotocol.sdk.spec.AcpSchema;
2020
import com.agentclientprotocol.sdk.spec.AcpSession;
2121
import com.agentclientprotocol.sdk.util.Assert;
22-
import io.modelcontextprotocol.json.TypeRef;
22+
import com.agentclientprotocol.sdk.json.TypeRef;
2323
import org.slf4j.Logger;
2424
import org.slf4j.LoggerFactory;
2525
import reactor.core.publisher.Mono;
@@ -58,7 +58,7 @@
5858
* AgentParameters params = AgentParameters.builder("gemini")
5959
* .arg("--experimental-acp")
6060
* .build();
61-
* StdioAcpClientTransport transport = new StdioAcpClientTransport(params, McpJsonMapper.getDefault());
61+
* StdioAcpClientTransport transport = new StdioAcpClientTransport(params, AcpJsonMapper.createDefault());
6262
*
6363
* // Build client
6464
* AcpAsyncClient client = AcpClient.async(transport)
@@ -509,7 +509,7 @@ public AcpAsyncClient build() {
509509
if (!sessionUpdateConsumers.isEmpty()) {
510510
notificationHandlers.put(AcpSchema.METHOD_SESSION_UPDATE, params -> {
511511
AcpSchema.SessionNotification notification = transport.unmarshalFrom(params,
512-
new io.modelcontextprotocol.json.TypeRef<AcpSchema.SessionNotification>() {
512+
new TypeRef<AcpSchema.SessionNotification>() {
513513
});
514514
logger.debug("Received session update for session: {}", notification.sessionId());
515515

@@ -904,7 +904,7 @@ public AcpSyncClient build() {
904904
if (!sessionUpdateConsumers.isEmpty()) {
905905
notificationHandlers.put(AcpSchema.METHOD_SESSION_UPDATE, params -> {
906906
AcpSchema.SessionNotification notification = transport.unmarshalFrom(params,
907-
new io.modelcontextprotocol.json.TypeRef<AcpSchema.SessionNotification>() {
907+
new TypeRef<AcpSchema.SessionNotification>() {
908908
});
909909
logger.debug("Received session update for session: {}", notification.sessionId());
910910

acp-core/src/main/java/com/agentclientprotocol/sdk/client/transport/StdioAcpClientTransport.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
import java.util.function.Consumer;
1616
import java.util.function.Function;
1717

18-
import io.modelcontextprotocol.json.TypeRef;
19-
import io.modelcontextprotocol.json.McpJsonMapper;
18+
import com.agentclientprotocol.sdk.json.AcpJsonMapper;
19+
import com.agentclientprotocol.sdk.json.TypeRef;
2020
import com.agentclientprotocol.sdk.spec.AcpClientTransport;
2121
import com.agentclientprotocol.sdk.spec.AcpSchema;
2222
import com.agentclientprotocol.sdk.spec.AcpSchema.JSONRPCMessage;
@@ -58,7 +58,7 @@ public class StdioAcpClientTransport implements AcpClientTransport {
5858
/** The agent process being communicated with */
5959
private Process process;
6060

61-
private McpJsonMapper jsonMapper;
61+
private AcpJsonMapper jsonMapper;
6262

6363
/** Scheduler for handling inbound messages from the agent process */
6464
private Scheduler inboundScheduler;
@@ -84,15 +84,15 @@ public class StdioAcpClientTransport implements AcpClientTransport {
8484
* @param params The parameters for configuring the agent process
8585
*/
8686
public StdioAcpClientTransport(AgentParameters params) {
87-
this(params, McpJsonMapper.getDefault());
87+
this(params, AcpJsonMapper.createDefault());
8888
}
8989

9090
/**
9191
* Creates a new StdioAcpClientTransport with the specified parameters and JsonMapper.
9292
* @param params The parameters for configuring the agent process
9393
* @param jsonMapper The JsonMapper to use for JSON serialization/deserialization
9494
*/
95-
public StdioAcpClientTransport(AgentParameters params, McpJsonMapper jsonMapper) {
95+
public StdioAcpClientTransport(AgentParameters params, AcpJsonMapper jsonMapper) {
9696
Assert.notNull(params, "The params can not be null");
9797
Assert.notNull(jsonMapper, "The JsonMapper can not be null");
9898

acp-core/src/main/java/com/agentclientprotocol/sdk/client/transport/WebSocketAcpClientTransport.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
import com.agentclientprotocol.sdk.spec.AcpSchema;
2020
import com.agentclientprotocol.sdk.spec.AcpSchema.JSONRPCMessage;
2121
import com.agentclientprotocol.sdk.util.Assert;
22-
import io.modelcontextprotocol.json.McpJsonMapper;
23-
import io.modelcontextprotocol.json.TypeRef;
22+
import com.agentclientprotocol.sdk.json.AcpJsonMapper;
23+
import com.agentclientprotocol.sdk.json.TypeRef;
2424
import org.slf4j.Logger;
2525
import org.slf4j.LoggerFactory;
2626
import reactor.core.publisher.Mono;
@@ -56,7 +56,7 @@ public class WebSocketAcpClientTransport implements AcpClientTransport {
5656

5757
private final URI serverUri;
5858

59-
private final McpJsonMapper jsonMapper;
59+
private final AcpJsonMapper jsonMapper;
6060

6161
private final HttpClient httpClient;
6262

@@ -83,7 +83,7 @@ public class WebSocketAcpClientTransport implements AcpClientTransport {
8383
* @param serverUri The WebSocket URI to connect to (e.g., "ws://localhost:8080/acp")
8484
* @param jsonMapper The JsonMapper to use for JSON serialization/deserialization
8585
*/
86-
public WebSocketAcpClientTransport(URI serverUri, McpJsonMapper jsonMapper) {
86+
public WebSocketAcpClientTransport(URI serverUri, AcpJsonMapper jsonMapper) {
8787
this(serverUri, jsonMapper, HttpClient.newBuilder()
8888
.executor(Executors.newCachedThreadPool(r -> {
8989
Thread t = new Thread(r, "acp-ws-client");
@@ -99,7 +99,7 @@ public WebSocketAcpClientTransport(URI serverUri, McpJsonMapper jsonMapper) {
9999
* @param jsonMapper The JsonMapper to use for JSON serialization/deserialization
100100
* @param httpClient The HttpClient to use for WebSocket connections
101101
*/
102-
public WebSocketAcpClientTransport(URI serverUri, McpJsonMapper jsonMapper, HttpClient httpClient) {
102+
public WebSocketAcpClientTransport(URI serverUri, AcpJsonMapper jsonMapper, HttpClient httpClient) {
103103
Assert.notNull(serverUri, "The serverUri can not be null");
104104
Assert.notNull(jsonMapper, "The JsonMapper can not be null");
105105
Assert.notNull(httpClient, "The HttpClient can not be null");
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*/
4+
5+
package com.agentclientprotocol.sdk.json;
6+
7+
import java.io.IOException;
8+
import java.util.ServiceLoader;
9+
10+
/**
11+
* Abstraction for JSON serialization/deserialization to decouple the SDK from any
12+
* specific JSON library.
13+
*
14+
* <p>
15+
* A default implementation is discovered at runtime via {@link ServiceLoader} using the
16+
* {@link AcpJsonMapperSupplier} SPI. The built-in Jackson-based implementation is
17+
* registered automatically.
18+
* </p>
19+
*
20+
* <p>
21+
* Alternative implementations (e.g., for Micronaut serialization) can be provided by
22+
* implementing {@link AcpJsonMapperSupplier} and registering it in
23+
* {@code META-INF/services/com.agentclientprotocol.sdk.json.AcpJsonMapperSupplier}.
24+
* </p>
25+
*
26+
* @author Mark Pollack
27+
*/
28+
public interface AcpJsonMapper {
29+
30+
/**
31+
* Creates a default AcpJsonMapper by discovering an {@link AcpJsonMapperSupplier}
32+
* via {@link ServiceLoader}. The first available supplier on the classpath is used.
33+
* @return a new AcpJsonMapper instance
34+
* @throws java.util.ServiceConfigurationError if no supplier is found
35+
*/
36+
static AcpJsonMapper createDefault() {
37+
return ServiceLoader.load(AcpJsonMapperSupplier.class)
38+
.findFirst()
39+
.orElseThrow(() -> new java.util.ServiceConfigurationError(
40+
"No AcpJsonMapperSupplier found on the classpath"))
41+
.get();
42+
}
43+
44+
/**
45+
* Deserialize JSON string into a target type.
46+
* @param content JSON as String
47+
* @param type target class
48+
* @return deserialized instance
49+
* @param <T> generic type
50+
* @throws IOException on parse errors
51+
*/
52+
<T> T readValue(String content, Class<T> type) throws IOException;
53+
54+
/**
55+
* Deserialize JSON bytes into a target type.
56+
* @param content JSON as bytes
57+
* @param type target class
58+
* @return deserialized instance
59+
* @param <T> generic type
60+
* @throws IOException on parse errors
61+
*/
62+
<T> T readValue(byte[] content, Class<T> type) throws IOException;
63+
64+
/**
65+
* Deserialize JSON string into a parameterized target type.
66+
* @param content JSON as String
67+
* @param type parameterized type reference
68+
* @return deserialized instance
69+
* @param <T> generic type
70+
* @throws IOException on parse errors
71+
*/
72+
<T> T readValue(String content, TypeRef<T> type) throws IOException;
73+
74+
/**
75+
* Deserialize JSON bytes into a parameterized target type.
76+
* @param content JSON as bytes
77+
* @param type parameterized type reference
78+
* @return deserialized instance
79+
* @param <T> generic type
80+
* @throws IOException on parse errors
81+
*/
82+
<T> T readValue(byte[] content, TypeRef<T> type) throws IOException;
83+
84+
/**
85+
* Convert a value to a given type, useful for mapping nested JSON structures.
86+
* @param fromValue source value
87+
* @param type target class
88+
* @return converted value
89+
* @param <T> generic type
90+
*/
91+
<T> T convertValue(Object fromValue, Class<T> type);
92+
93+
/**
94+
* Convert a value to a given parameterized type.
95+
* @param fromValue source value
96+
* @param type target type reference
97+
* @return converted value
98+
* @param <T> generic type
99+
*/
100+
<T> T convertValue(Object fromValue, TypeRef<T> type);
101+
102+
/**
103+
* Serialize an object to JSON string.
104+
* @param value object to serialize
105+
* @return JSON as String
106+
* @throws IOException on serialization errors
107+
*/
108+
String writeValueAsString(Object value) throws IOException;
109+
110+
/**
111+
* Serialize an object to JSON bytes.
112+
* @param value object to serialize
113+
* @return JSON as bytes
114+
* @throws IOException on serialization errors
115+
*/
116+
byte[] writeValueAsBytes(Object value) throws IOException;
117+
118+
}

0 commit comments

Comments
 (0)