Java Agent Examples
This guide provides complete, runnable examples for building TraceMem agents in Java. Each example demonstrates a specific pattern for interacting with TraceMem's Agent MCP server.
Prerequisites
- Java 11 or higher
- No external dependencies required (uses
java.net.httpfrom Java 11+) - Access to a TraceMem Agent MCP server (default:
https://mcp.tracemem.com) - A valid TraceMem API key
Connection Details
- Endpoint:
https://mcp.tracemem.com(or set viaMCP_AGENT_URLenvironment variable) - Protocol: JSON-RPC 2.0 over HTTP
- Authentication:
Authorization: Agent <your-api-key>header
MCP Client Implementation
First, let's create a reusable MCP client class that handles JSON-RPC 2.0 communication:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
public class MCPClient {
private final String baseUrl;
private final String apiKey;
private final HttpClient httpClient;
private final AtomicInteger requestId = new AtomicInteger(0);
private volatile boolean initialized = false;
private final ObjectMapper objectMapper = new ObjectMapper();
public MCPClient(String baseUrl, String apiKey) {
this.baseUrl = baseUrl;
this.apiKey = apiKey;
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
}
private int nextId() {
return requestId.incrementAndGet();
}
private JsonNode call(String method, Map<String, Object> params) throws Exception {
Map<String, Object> request = Map.of(
"jsonrpc", "2.0",
"id", nextId(),
"method", method,
"params", params != null ? params : Map.of()
);
String requestBody = objectMapper.writeValueAsString(request);
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(baseUrl))
.header("Authorization", "Agent " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = httpClient.send(httpRequest,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new Exception("HTTP error " + response.statusCode() + ": " + response.body());
}
JsonNode jsonResponse = objectMapper.readTree(response.body());
if (jsonResponse.has("error")) {
JsonNode error = jsonResponse.get("error");
int code = error.get("code").asInt();
String message = error.get("message").asText();
String errorMsg = String.format("MCP Error %d: %s", code, message);
if (error.has("data")) {
errorMsg += " - " + error.get("data").toString();
}
throw new Exception(errorMsg);
}
return jsonResponse.has("result") ? jsonResponse.get("result") : objectMapper.createObjectNode();
}
public JsonNode initialize() throws Exception {
Map<String, Object> params = Map.of(
"protocolVersion", "2024-11-05",
"capabilities", Map.of(),
"clientInfo", Map.of(
"name", "tracemem-test-agent",
"version", "1.0.0"
)
);
JsonNode result = call("initialize", params);
initialized = true;
return result;
}
public JsonNode listTools() throws Exception {
return call("tools/list", null);
}
public JsonNode callTool(String name, Map<String, Object> arguments) throws Exception {
if (!initialized) {
initialize();
}
Map<String, Object> params = Map.of(
"name", name,
"arguments", arguments
);
JsonNode result = call("tools/call", params);
// Check if the tool result indicates an error
if (result.has("isError") && result.get("isError").asBoolean()) {
String errorMessage = "Tool call failed";
if (result.has("content") && result.get("content").isArray()
&& result.get("content").size() > 0) {
JsonNode content = result.get("content").get(0);
if (content.has("type") && content.get("type").asText().equals("text")
&& content.has("text")) {
errorMessage = content.get("text").asText();
}
}
throw new Exception(errorMessage);
}
// Parse content from tool result
if (result.has("content") && result.get("content").isArray()
&& result.get("content").size() > 0) {
JsonNode content = result.get("content").get(0);
if (content.has("type") && content.get("type").asText().equals("text")
&& content.has("text")) {
String text = content.get("text").asText();
try {
return objectMapper.readTree(text);
} catch (Exception e) {
return objectMapper.createObjectNode()
.put("raw_text", text);
}
}
}
return result;
}
public boolean isInitialized() {
return initialized;
}
}
Note: For JSON handling, you can use Jackson (com.fasterxml.jackson.core:jackson-databind) or Gson. The example above uses Jackson.
Example 1: Read Agent
This example demonstrates reading customer data from a data product.
import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
public class TestReadAgent {
public static void main(String[] args) {
try {
runReadTest();
} catch (Exception e) {
System.err.println("✗ Error: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
}
private static void runReadTest() throws Exception {
// Get configuration from environment
String mcpUrl = System.getenv("MCP_AGENT_URL");
if (mcpUrl == null || mcpUrl.isEmpty()) {
mcpUrl = "https://mcp.tracemem.com";
}
String apiKey = System.getenv("TRACEMEM_API_KEY");
if (apiKey == null || apiKey.isEmpty()) {
throw new Exception("ERROR: TRACEMEM_API_KEY environment variable is required");
}
String instance = System.getenv("TRACEMEM_INSTANCE");
String actor = System.getenv("TRACEMEM_ACTOR");
if (actor == null || actor.isEmpty()) {
actor = "test-read-agent";
}
String customerId = System.getenv("CUSTOMER_ID");
if (customerId == null || customerId.isEmpty()) {
customerId = "1003";
}
System.out.println("=".repeat(60));
System.out.println("TraceMem MCP - Read Test Agent");
System.out.println("=".repeat(60));
System.out.println();
System.out.println("Connecting to Agent MCP at: " + mcpUrl);
if (instance != null && !instance.isEmpty()) {
System.out.println("Instance: " + instance);
}
System.out.println("Actor: " + actor);
System.out.println("Customer ID: " + customerId);
System.out.println();
MCPClient client = new MCPClient(mcpUrl, apiKey);
String decisionId = null;
try {
// Initialize MCP session
System.out.println("Initializing MCP session...");
JsonNode initResult = client.initialize();
String serverName = "TraceMem Agent MCP";
if (initResult.has("serverInfo") && initResult.get("serverInfo").has("name")) {
serverName = initResult.get("serverInfo").get("name").asText();
}
System.out.println("✓ Connected to " + serverName);
System.out.println();
// Step 1: Create decision envelope
System.out.println("Step 1: Creating decision envelope...");
Map<String, Object> decisionArgs = Map.of(
"intent", "test.read.customer",
"automation_mode", "autonomous",
"instance", instance != null ? instance : "",
"actor", actor,
"metadata", Map.of(
"customer_id", customerId,
"test_type", "read"
)
);
JsonNode decision = client.callTool("decision_create", decisionArgs);
decisionId = decision.has("decision_id")
? decision.get("decision_id").asText()
: (decision.has("id") ? decision.get("id").asText() : null);
if (decisionId == null || decisionId.isEmpty()) {
throw new Exception("Failed to get decision_id from decision_create response");
}
System.out.println("✓ Decision envelope created: " + decisionId);
System.out.println();
// Step 2: Read customer data
System.out.println("Step 2: Reading customer data...");
int customerIdInt = Integer.parseInt(customerId);
Map<String, Object> readArgs = Map.of(
"decision_id", decisionId,
"product", "planetscale_read_customer_v1",
"purpose", "web_order",
"query", Map.of("id", customerIdInt)
);
JsonNode readResult = client.callTool("decision_read", readArgs);
System.out.println("✓ Customer data retrieved");
if (readResult.has("event_id")) {
System.out.println(" Event ID: " + readResult.get("event_id").asText());
}
if (readResult.has("data_ref")) {
System.out.println(" Data Reference: " + readResult.get("data_ref").asText());
}
if (readResult.has("records") && readResult.get("records").isArray()) {
JsonNode records = readResult.get("records");
if (records.size() > 0) {
System.out.println(" Records found: " + records.size());
System.out.println(" Customer data:");
System.out.println(records.get(0).toPrettyString());
} else {
System.out.println(" No records found");
}
} else {
System.out.println(" No records found");
}
System.out.println();
// Step 3: Close decision (commit)
System.out.println("Step 3: Committing decision...");
Map<String, Object> closeArgs = Map.of(
"decision_id", decisionId,
"action", "commit"
);
JsonNode closeResult = client.callTool("decision_close", closeArgs);
System.out.println("✓ Decision committed");
if (closeResult.has("status")) {
System.out.println(" Status: " + closeResult.get("status").asText());
}
System.out.println();
// Summary
System.out.println("=".repeat(60));
System.out.println("Summary");
System.out.println("=".repeat(60));
System.out.println("Decision ID: " + decisionId);
System.out.println("Result: ✓ Read operation completed successfully");
System.out.println();
} catch (Exception error) {
System.err.println("✗ Error: " + error.getMessage());
error.printStackTrace();
// Try to close decision on error
if (decisionId != null) {
try {
System.out.println("Attempting to abort decision...");
client.callTool("decision_close", Map.of(
"decision_id", decisionId,
"action", "abort",
"reason", "Error occurred: " + error.getMessage()
));
System.out.println("✓ Decision aborted");
} catch (Exception closeError) {
System.err.println("Failed to abort decision: " + closeError.getMessage());
}
}
throw error;
}
}
}
Environment Variables
TRACEMEM_API_KEY(required): Your TraceMem agent API keyMCP_AGENT_URL(optional): MCP server URL (default:https://mcp.tracemem.com)TRACEMEM_INSTANCE(optional): Instance identifierTRACEMEM_ACTOR(optional): Actor identifier (default:test-read-agent)CUSTOMER_ID(optional): Customer ID to read (default:1003)
Running the Example
export TRACEMEM_API_KEY="your-api-key"
export CUSTOMER_ID="1"
javac -cp ".:jackson-databind-2.15.0.jar" TestReadAgent.java
java -cp ".:jackson-databind-2.15.0.jar" TestReadAgent
Example 2: Insert Agent (with Policy)
This example demonstrates inserting an order with policy evaluation.
// Similar structure to read example, with these key additions:
// Step 2: Evaluate policy
System.out.println("Step 2: Evaluating discount_cap_v1 policy...");
Map<String, Object> policyArgs = Map.of(
"decision_id", decisionId,
"policy_id", "discount_cap_v1",
"inputs", Map.of("proposed_discount", proposedDiscount)
);
JsonNode policyResult = client.callTool("decision_evaluate", policyArgs);
String outcome = policyResult.has("outcome")
? policyResult.get("outcome").asText()
: "unknown";
System.out.println("✓ Policy evaluation completed");
System.out.println(" Policy ID: discount_cap_v1");
System.out.println(" Proposed Discount: " + proposedDiscount);
System.out.println(" Outcome: " + outcome);
// ... (handle policy outcomes)
// Step 3: Insert order
Map<String, Object> writeArgs = Map.of(
"decision_id", decisionId,
"product", "planetscale_insert_order_v1",
"purpose", "web_order",
"mutation", Map.of(
"operation", "insert",
"records", List.of(Map.of(
"customer_id", customerId,
"product_id", productId,
"quantity", quantity,
"total_amount", totalAmount,
"order_status", orderStatus
))
)
);
JsonNode writeResult = client.callTool("decision_write", writeArgs);
Environment Variables
TRACEMEM_API_KEY(required): Your TraceMem agent API keyMCP_AGENT_URL(optional): MCP server URL (default:https://mcp.tracemem.com)TRACEMEM_INSTANCE(optional): Instance identifierTRACEMEM_ACTOR(optional): Actor identifier (default:test-insert-agent)CUSTOMER_ID(optional): Customer ID for order (default:1001)PRODUCT_ID(optional): Product ID for order (default:2)QUANTITY(optional): Order quantity (default:1)TOTAL_AMOUNT(optional): Order total amount (default:99.99)ORDER_STATUS(optional): Order status (default:pending)PROPOSED_DISCOUNT(optional): Proposed discount for policy evaluation (default:0)
Example 3: Insert Agent (without Policy)
This example demonstrates inserting an order without policy evaluation. The code structure is similar to Example 2, but without the policy evaluation step. Use planetscale_insert_order_no_policy_v1 as the product.
Example 4: Update Agent
This example demonstrates updating product stock.
// Step 2: Update product stock
Map<String, Object> writeArgs = Map.of(
"decision_id", decisionId,
"product", "planetscale_update_product_stock_v1",
"purpose", "web_order",
"mutation", Map.of(
"operation", "update",
"records", List.of(Map.of(
"product_id", productId, // Key field for identification
"stock_quantity", stockQuantity // Field to update
))
)
);
JsonNode writeResult = client.callTool("decision_write", writeArgs);
Environment Variables
TRACEMEM_API_KEY(required): Your TraceMem agent API keyMCP_AGENT_URL(optional): MCP server URL (default:https://mcp.tracemem.com)TRACEMEM_INSTANCE(optional): Instance identifierTRACEMEM_ACTOR(optional): Actor identifier (default:test-update-agent)PRODUCT_ID(optional): Product ID to update (default:4)STOCK_QUANTITY(optional): New stock quantity (default:90)
Example 5: Delete Agent
This example demonstrates deleting a target record.
import java.util.regex.Pattern;
// UUID validation
Pattern uuidPattern = Pattern.compile(
"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
Pattern.CASE_INSENSITIVE
);
if (!uuidPattern.matcher(targetId).matches()) {
throw new Exception("ERROR: TARGET_ID must be a valid UUID format. Received: " + targetId);
}
// Step 2: Delete target record
Map<String, Object> writeArgs = Map.of(
"decision_id", decisionId,
"product", "planetscale_delete_target_v1",
"purpose", "delete_target",
"mutation", Map.of(
"operation", "delete",
"records", List.of(Map.of("id", targetId)) // Key field for deletion
)
);
JsonNode writeResult = client.callTool("decision_write", writeArgs);
Environment Variables
TRACEMEM_API_KEY(required): Your TraceMem agent API keyMCP_AGENT_URL(optional): MCP server URL (default:https://mcp.tracemem.com)TRACEMEM_INSTANCE(optional): Instance identifierTRACEMEM_ACTOR(optional): Actor identifier (default:test-delete-agent)TARGET_ID(required): UUID of target record to delete
Common Patterns
Error Handling Best Practices
Always ensure decision envelopes are properly closed, even on errors:
String decisionId = null;
try {
// Create decision and perform operations
JsonNode decision = client.callTool("decision_create", ...);
decisionId = decision.get("decision_id").asText();
// Perform operations...
// Commit on success
client.callTool("decision_close", Map.of(
"decision_id", decisionId,
"action", "commit"
));
} catch (Exception e) {
// Always abort on error
if (decisionId != null) {
try {
client.callTool("decision_close", Map.of(
"decision_id", decisionId,
"action", "abort",
"reason", "Error occurred: " + e.getMessage()
));
} catch (Exception closeError) {
// Log but don't fail on abort failure
System.err.println("Failed to abort decision: " + closeError.getMessage());
}
}
throw e;
}
Decision Envelope Lifecycle
Every agent operation should follow this pattern:
- Initialize MCP Session: Connect to the Agent MCP server
- Create Decision Envelope: Open a decision with appropriate intent
- Perform Operations: Read, write, evaluate policies, etc.
- Close Decision: Commit on success, abort on error
Environment Variable Configuration
All examples use environment variables for configuration:
# Required
export TRACEMEM_API_KEY="your-api-key"
# Optional
export MCP_AGENT_URL="https://mcp.tracemem.com"
export TRACEMEM_INSTANCE="my-instance"
export TRACEMEM_ACTOR="my-agent"
Testing Tips
- Start with Read Operations: Test read operations first to verify connectivity
- Use Test Data: Use non-production data products for testing
- Check Decision Traces: Review decision traces in the TraceMem dashboard
- Handle Errors Gracefully: Always implement proper error handling and decision cleanup