diff --git a/java/pom.xml b/java/pom.xml
index 65bb80556..ae2af3d68 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -167,21 +167,35 @@
org.apache.maven.plugins
maven-clean-plugin
3.4.1
-
-
- true
-
+
+
+
+ default-clean
+
+ true
+ false
+
+
+
+ post-clean-sweep
+ post-clean
+
+ clean
+
+
+ true
+
+
+
org.apache.maven.plugins
diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts
index 2d6c9496b..535af044f 100644
--- a/java/scripts/codegen/java.ts
+++ b/java/scripts/codegen/java.ts
@@ -1433,6 +1433,18 @@ function wrapperResultClassName(method: RpcMethodNode): string {
) {
return rpcMethodToClassName(method.rpcMethod) + "Result";
}
+
+ // Free-form object with additionalProperties (e.g., x-opaque-json) → JsonNode
+ if (
+ result &&
+ typeof result === "object" &&
+ result.type === "object" &&
+ result.additionalProperties &&
+ !result.properties
+ ) {
+ return "JsonNode";
+ }
+
return "Void";
}
@@ -1571,7 +1583,13 @@ async function generateNamespaceApiFile(
for (const [key, method] of tree.methods) {
const resultClass = wrapperResultClassName(method);
const paramsClass = wrapperParamsClassName(method);
- if (resultClass !== "Void") allImports.add(`${packageName}.${resultClass}`);
+ if (resultClass !== "Void") {
+ if (resultClass === "JsonNode") {
+ allImports.add("com.fasterxml.jackson.databind.JsonNode");
+ } else {
+ allImports.add(`${packageName}.${resultClass}`);
+ }
+ }
if (paramsClass) allImports.add(`${packageName}.${paramsClass}`);
const { lines, needsMapper: nm } = generateApiMethod(key, method, isSession, sessionIdExpr);
@@ -1690,7 +1708,13 @@ async function generateRpcRootFile(
for (const [key, method] of tree.methods) {
const resultClass = wrapperResultClassName(method);
const paramsClass = wrapperParamsClassName(method);
- if (resultClass !== "Void") allImports.add(`${packageName}.${resultClass}`);
+ if (resultClass !== "Void") {
+ if (resultClass === "JsonNode") {
+ allImports.add("com.fasterxml.jackson.databind.JsonNode");
+ } else {
+ allImports.add(`${packageName}.${resultClass}`);
+ }
+ }
if (paramsClass) allImports.add(`${packageName}.${paramsClass}`);
const { lines, needsMapper: nm } = generateApiMethod(key, method, isSession, sessionIdExpr);
diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java
index b6c131a6a..48f4ed2c7 100644
--- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java
+++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java
@@ -7,6 +7,7 @@
package com.github.copilot.generated.rpc;
+import com.fasterxml.jackson.databind.JsonNode;
import java.util.concurrent.CompletableFuture;
import javax.annotation.processing.Generated;
@@ -68,10 +69,10 @@ public CompletableFuture listTools(SessionMcpApps
* @apiNote This method is experimental and may change in a future version.
* @since 1.0.0
*/
- public CompletableFuture callTool(SessionMcpAppsCallToolParams params) {
+ public CompletableFuture callTool(SessionMcpAppsCallToolParams params) {
com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params);
_p.put("sessionId", this.sessionId);
- return caller.invoke("session.mcp.apps.callTool", _p, Void.class);
+ return caller.invoke("session.mcp.apps.callTool", _p, JsonNode.class);
}
/**
diff --git a/java/src/test/java/com/github/copilot/RpcWrappersTest.java b/java/src/test/java/com/github/copilot/RpcWrappersTest.java
index 7b01e1d38..f19d3db01 100644
--- a/java/src/test/java/com/github/copilot/RpcWrappersTest.java
+++ b/java/src/test/java/com/github/copilot/RpcWrappersTest.java
@@ -389,6 +389,56 @@ void copilotClient_getRpc_throws_before_start() {
"getRpc() must throw IllegalStateException if called before start()");
}
+ // ── session.mcp.apps.callTool tests ───────────────────────────────────────
+
+ @Test
+ void sessionRpc_mcp_apps_callTool_invokes_correct_rpc_method() {
+ var stub = new StubCaller();
+ var session = new SessionRpc(stub, "sess-mcp");
+
+ var params = new com.github.copilot.generated.rpc.SessionMcpAppsCallToolParams(null, "my-server", "my-tool",
+ null, null);
+ session.mcp.apps.callTool(params);
+
+ assertEquals(1, stub.calls.size());
+ assertEquals("session.mcp.apps.callTool", stub.calls.get(0).method());
+ }
+
+ @Test
+ void sessionRpc_mcp_apps_callTool_injects_sessionId() {
+ var stub = new StubCaller();
+ var session = new SessionRpc(stub, "sess-ct-inject");
+
+ var params = new com.github.copilot.generated.rpc.SessionMcpAppsCallToolParams(null, "server1", "tool1", null,
+ null);
+ session.mcp.apps.callTool(params);
+
+ var sentParams = stub.calls.get(0).params();
+ assertInstanceOf(com.fasterxml.jackson.databind.node.ObjectNode.class, sentParams);
+ var node = (com.fasterxml.jackson.databind.node.ObjectNode) sentParams;
+ assertEquals("sess-ct-inject", node.get("sessionId").asText());
+ }
+
+ @Test
+ void sessionRpc_mcp_apps_callTool_returns_jsonNode_payload() throws Exception {
+ var stub = new StubCaller();
+ var mapper = new ObjectMapper();
+ var expectedResult = mapper.createObjectNode();
+ expectedResult.put("content", "hello world");
+ expectedResult.put("isError", false);
+ stub.nextResult = expectedResult;
+
+ var session = new SessionRpc(stub, "sess-payload");
+ var params = new com.github.copilot.generated.rpc.SessionMcpAppsCallToolParams(null, "echo-server", "echo",
+ null, null);
+ var future = session.mcp.apps.callTool(params);
+
+ var result = future.get();
+ assertInstanceOf(com.fasterxml.jackson.databind.JsonNode.class, result);
+ assertEquals("hello world", result.get("content").asText());
+ assertEquals(false, result.get("isError").asBoolean());
+ }
+
/**
* Helper that creates a loopback socket pair. The client side is used by
* {@link JsonRpcClient}; the server side can be read to inspect outbound