C++ Agent Examples
This guide provides complete, runnable examples for building TraceMem agents in C++. Each example demonstrates a specific pattern for interacting with TraceMem's Agent MCP server.
Prerequisites
- C++17 or higher
- HTTP library:
cpp-httpliborlibcurl - JSON library:
nlohmann/jsonor similar - 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. This example uses cpp-httplib and nlohmann/json:
#include <httplib.h>
#include <nlohmann/json.hpp>
#include <atomic>
#include <string>
#include <memory>
using json = nlohmann::json;
class MCPClient {
private:
std::string base_url;
std::string api_key;
std::unique_ptr<httplib::Client> http_client;
std::atomic<int> request_id{0};
std::atomic<bool> initialized{false};
int nextId() {
return request_id.fetch_add(1) + 1;
}
json call(const std::string& method, const json& params = json::object()) {
json request = {
{"jsonrpc", "2.0"},
{"id", nextId()},
{"method", method},
{"params", params}
};
std::string request_body = request.dump();
httplib::Headers headers = {
{"Authorization", "Agent " + api_key},
{"Content-Type", "application/json"}
};
auto response = http_client->Post(base_url.c_str(), headers, request_body, "application/json");
if (!response) {
throw std::runtime_error("HTTP request failed");
}
if (response->status != 200) {
throw std::runtime_error("HTTP error " + std::to_string(response->status) + ": " + response->body);
}
json json_response = json::parse(response->body);
if (json_response.contains("error")) {
auto error = json_response["error"];
int code = error["code"];
std::string message = error["message"];
std::string error_msg = "MCP Error " + std::to_string(code) + ": " + message;
if (error.contains("data")) {
error_msg += " - " + error["data"].dump();
}
throw std::runtime_error(error_msg);
}
return json_response.contains("result") ? json_response["result"] : json::object();
}
public:
MCPClient(const std::string& base_url, const std::string& api_key)
: base_url(base_url), api_key(api_key) {
// Parse URL
size_t protocol_end = base_url.find("://");
if (protocol_end == std::string::npos) {
throw std::invalid_argument("Invalid URL format");
}
size_t host_start = protocol_end + 3;
size_t path_start = base_url.find('/', host_start);
std::string host = path_start != std::string::npos
? base_url.substr(host_start, path_start - host_start)
: base_url.substr(host_start);
std::string path = path_start != std::string::npos
? base_url.substr(path_start)
: "/";
http_client = std::make_unique<httplib::Client>(host.c_str());
}
json initialize() {
json params = {
{"protocolVersion", "2024-11-05"},
{"capabilities", json::object()},
{"clientInfo", {
{"name", "tracemem-test-agent"},
{"version", "1.0.0"}
}}
};
json result = call("initialize", params);
initialized.store(true);
return result;
}
json listTools() {
return call("tools/list");
}
json callTool(const std::string& name, const json& arguments) {
if (!initialized.load()) {
initialize();
}
json params = {
{"name", name},
{"arguments", arguments}
};
json result = call("tools/call", params);
// Check if the tool result indicates an error
if (result.contains("isError") && result["isError"].get<bool>()) {
std::string error_message = "Tool call failed";
if (result.contains("content") && result["content"].is_array() && result["content"].size() > 0) {
auto content = result["content"][0];
if (content.contains("type") && content["type"] == "text" && content.contains("text")) {
error_message = content["text"].get<std::string>();
}
}
throw std::runtime_error(error_message);
}
// Parse content from tool result
if (result.contains("content") && result["content"].is_array() && result["content"].size() > 0) {
auto content = result["content"][0];
if (content.contains("type") && content["type"] == "text" && content.contains("text")) {
std::string text = content["text"].get<std::string>();
try {
return json::parse(text);
} catch (...) {
return json{{"raw_text", text}};
}
}
}
return result;
}
bool isInitialized() const {
return initialized.load();
}
};
Example 1: Read Agent
This example demonstrates reading customer data from a data product.
#include <iostream>
#include <cstdlib>
#include <stdexcept>
int main() {
try {
// Get configuration from environment
std::string mcp_url = std::getenv("MCP_AGENT_URL") ? std::getenv("MCP_AGENT_URL") : "https://mcp.tracemem.com";
std::string api_key = std::getenv("TRACEMEM_API_KEY");
if (api_key.empty()) {
std::cerr << "ERROR: TRACEMEM_API_KEY environment variable is required" << std::endl;
return 1;
}
std::string instance = std::getenv("TRACEMEM_INSTANCE") ? std::getenv("TRACEMEM_INSTANCE") : "";
std::string actor = std::getenv("TRACEMEM_ACTOR") ? std::getenv("TRACEMEM_ACTOR") : "test-read-agent";
std::string customer_id = std::getenv("CUSTOMER_ID") ? std::getenv("CUSTOMER_ID") : "1003";
std::cout << std::string(60, '=') << std::endl;
std::cout << "TraceMem MCP - Read Test Agent" << std::endl;
std::cout << std::string(60, '=') << std::endl;
std::cout << std::endl;
std::cout << "Connecting to Agent MCP at: " << mcp_url << std::endl;
if (!instance.empty()) {
std::cout << "Instance: " << instance << std::endl;
}
std::cout << "Actor: " << actor << std::endl;
std::cout << "Customer ID: " << customer_id << std::endl;
std::cout << std::endl;
MCPClient client(mcp_url, api_key);
std::string decision_id;
try {
// Initialize MCP session
std::cout << "Initializing MCP session..." << std::endl;
json init_result = client.initialize();
std::string server_name = "TraceMem Agent MCP";
if (init_result.contains("serverInfo") && init_result["serverInfo"].contains("name")) {
server_name = init_result["serverInfo"]["name"].get<std::string>();
}
std::cout << "✓ Connected to " << server_name << std::endl;
std::cout << std::endl;
// Step 1: Create decision envelope
std::cout << "Step 1: Creating decision envelope..." << std::endl;
json decision_args = {
{"intent", "test.read.customer"},
{"automation_mode", "autonomous"},
{"actor", actor},
{"metadata", {
{"customer_id", customer_id},
{"test_type", "read"}
}}
};
if (!instance.empty()) {
decision_args["instance"] = instance;
}
json decision = client.callTool("decision_create", decision_args);
decision_id = decision.contains("decision_id")
? decision["decision_id"].get<std::string>()
: (decision.contains("id") ? decision["id"].get<std::string>() : "");
if (decision_id.empty()) {
throw std::runtime_error("Failed to get decision_id from decision_create response");
}
std::cout << "✓ Decision envelope created: " << decision_id << std::endl;
std::cout << std::endl;
// Step 2: Read customer data
std::cout << "Step 2: Reading customer data..." << std::endl;
int customer_id_int = std::stoi(customer_id);
json read_args = {
{"decision_id", decision_id},
{"product", "planetscale_read_customer_v1"},
{"purpose", "web_order"},
{"query", {
{"id", customer_id_int}
}}
};
json read_result = client.callTool("decision_read", read_args);
std::cout << "✓ Customer data retrieved" << std::endl;
if (read_result.contains("event_id")) {
std::cout << " Event ID: " << read_result["event_id"].get<std::string>() << std::endl;
}
if (read_result.contains("data_ref")) {
std::cout << " Data Reference: " << read_result["data_ref"].get<std::string>() << std::endl;
}
if (read_result.contains("records") && read_result["records"].is_array()) {
auto records = read_result["records"];
if (records.size() > 0) {
std::cout << " Records found: " << records.size() << std::endl;
std::cout << " Customer data:" << std::endl;
std::cout << records[0].dump(2) << std::endl;
} else {
std::cout << " No records found" << std::endl;
}
} else {
std::cout << " No records found" << std::endl;
}
std::cout << std::endl;
// Step 3: Close decision (commit)
std::cout << "Step 3: Committing decision..." << std::endl;
json close_args = {
{"decision_id", decision_id},
{"action", "commit"}
};
json close_result = client.callTool("decision_close", close_args);
std::cout << "✓ Decision committed" << std::endl;
if (close_result.contains("status")) {
std::cout << " Status: " << close_result["status"].get<std::string>() << std::endl;
}
std::cout << std::endl;
// Summary
std::cout << std::string(60, '=') << std::endl;
std::cout << "Summary" << std::endl;
std::cout << std::string(60, '=') << std::endl;
std::cout << "Decision ID: " << decision_id << std::endl;
std::cout << "Result: ✓ Read operation completed successfully" << std::endl;
std::cout << std::endl;
} catch (const std::exception& error) {
std::cerr << "✗ Error: " << error.what() << std::endl;
// Try to close decision on error
if (!decision_id.empty()) {
try {
std::cout << "Attempting to abort decision..." << std::endl;
client.callTool("decision_close", {
{"decision_id", decision_id},
{"action", "abort"},
{"reason", "Error occurred: " + std::string(error.what())}
});
std::cout << "✓ Decision aborted" << std::endl;
} catch (const std::exception& close_error) {
std::cerr << "Failed to abort decision: " << close_error.what() << std::endl;
}
}
return 1;
}
} catch (const std::exception& e) {
std::cerr << "Fatal error: " << e.what() << std::endl;
return 1;
}
return 0;
}
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"
g++ -std=c++17 -o test_read_agent test_read_agent.cpp -lhttplib -lpthread
./test_read_agent
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
std::cout << "Step 2: Evaluating discount_cap_v1 policy..." << std::endl;
json policy_args = {
{"decision_id", decision_id},
{"policy_id", "discount_cap_v1"},
{"inputs", {
{"proposed_discount", proposed_discount}
}}
};
json policy_result = client.callTool("decision_evaluate", policy_args);
std::string outcome = policy_result.contains("outcome")
? policy_result["outcome"].get<std::string>()
: "unknown";
std::cout << "✓ Policy evaluation completed" << std::endl;
std::cout << " Policy ID: discount_cap_v1" << std::endl;
std::cout << " Proposed Discount: " << proposed_discount << std::endl;
std::cout << " Outcome: " << outcome << std::endl;
// ... (handle policy outcomes)
// Step 3: Insert order
json write_args = {
{"decision_id", decision_id},
{"product", "planetscale_insert_order_v1"},
{"purpose", "web_order"},
{"mutation", {
{"operation", "insert"},
{"records", {{
{"customer_id", customer_id},
{"product_id", product_id},
{"quantity", quantity},
{"total_amount", total_amount},
{"order_status", order_status}
}}}
}}
};
json write_result = client.callTool("decision_write", write_args);
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
json write_args = {
{"decision_id", decision_id},
{"product", "planetscale_update_product_stock_v1"},
{"purpose", "web_order"},
{"mutation", {
{"operation", "update"},
{"records", {{
{"product_id", product_id}, // Key field for identification
{"stock_quantity", stock_quantity} // Field to update
}}}
}}
};
json write_result = client.callTool("decision_write", write_args);
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.
#include <regex>
// UUID validation
std::regex uuid_regex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
std::regex_constants::icase);
if (!std::regex_match(target_id, uuid_regex)) {
throw std::runtime_error("ERROR: TARGET_ID must be a valid UUID format. Received: " + target_id);
}
// Step 2: Delete target record
json write_args = {
{"decision_id", decision_id},
{"product", "planetscale_delete_target_v1"},
{"purpose", "delete_target"},
{"mutation", {
{"operation", "delete"},
{"records", {{
{"id", target_id} // Key field for deletion
}}}
}}
};
json write_result = client.callTool("decision_write", write_args);
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:
std::string decision_id;
try {
// Create decision and perform operations
json decision = client.callTool("decision_create", ...);
decision_id = decision["decision_id"].get<std::string>();
// Perform operations...
// Commit on success
client.callTool("decision_close", {
{"decision_id", decision_id},
{"action", "commit"}
});
} catch (const std::exception& e) {
// Always abort on error
if (!decision_id.empty()) {
try {
client.callTool("decision_close", {
{"decision_id", decision_id},
{"action", "abort"},
{"reason", "Error occurred: " + std::string(e.what())}
});
} catch (const std::exception& close_error) {
// Log but don't fail on abort failure
std::cerr << "Failed to abort decision: " << close_error.what() << std::endl;
}
}
throw;
}
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