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-httplib or libcurl
  • JSON library: nlohmann/json or 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 via MCP_AGENT_URL environment 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:

cpp
#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.

cpp
#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 key
  • MCP_AGENT_URL (optional): MCP server URL (default: https://mcp.tracemem.com)
  • TRACEMEM_INSTANCE (optional): Instance identifier
  • TRACEMEM_ACTOR (optional): Actor identifier (default: test-read-agent)
  • CUSTOMER_ID (optional): Customer ID to read (default: 1003)

Running the Example

bash
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.

cpp
// 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 key
  • MCP_AGENT_URL (optional): MCP server URL (default: https://mcp.tracemem.com)
  • TRACEMEM_INSTANCE (optional): Instance identifier
  • TRACEMEM_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.

cpp
// 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 key
  • MCP_AGENT_URL (optional): MCP server URL (default: https://mcp.tracemem.com)
  • TRACEMEM_INSTANCE (optional): Instance identifier
  • TRACEMEM_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.

cpp
#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 key
  • MCP_AGENT_URL (optional): MCP server URL (default: https://mcp.tracemem.com)
  • TRACEMEM_INSTANCE (optional): Instance identifier
  • TRACEMEM_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:

cpp
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:

  1. Initialize MCP Session: Connect to the Agent MCP server
  2. Create Decision Envelope: Open a decision with appropriate intent
  3. Perform Operations: Read, write, evaluate policies, etc.
  4. Close Decision: Commit on success, abort on error

Environment Variable Configuration

All examples use environment variables for configuration:

bash
# 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

  1. Start with Read Operations: Test read operations first to verify connectivity
  2. Use Test Data: Use non-production data products for testing
  3. Check Decision Traces: Review decision traces in the TraceMem dashboard
  4. Handle Errors Gracefully: Always implement proper error handling and decision cleanup

TraceMem is trace-native infrastructure for AI agents