TypeScript Agent Examples

This guide provides complete, runnable examples for building TraceMem agents in TypeScript. Each example demonstrates a specific pattern for interacting with TraceMem's Agent MCP server.

Prerequisites

  • Node.js 18 or higher (for built-in fetch support, or use axios)
  • TypeScript 5.3 or higher
  • axios library: npm install axios
  • tsx for running TypeScript directly: npm install -D tsx
  • 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:

typescript
import axios, { AxiosInstance } from 'axios';

/**
 * JSON-RPC 2.0 request structure
 */
interface JsonRpcRequest {
  jsonrpc: '2.0';
  id: number;
  method: string;
  params?: Record<string, any>;
}

/**
 * JSON-RPC 2.0 response structure
 */
interface JsonRpcResponse {
  jsonrpc: '2.0';
  id: number;
  result?: any;
  error?: {
    code: number;
    message: string;
    data?: any;
  };
}

/**
 * MCP tool call result content
 */
interface ToolContent {
  type: string;
  text?: string;
  [key: string]: any;
}

/**
 * MCP tool call result
 */
interface ToolCallResult {
  content: ToolContent[];
  isError?: boolean;
}

/**
 * MCP Client for interacting with TraceMem Agent MCP Server
 */
export class MCPClient {
  private baseUrl: string;
  private apiKey: string;
  private httpClient: AxiosInstance;
  private requestId: number = 0;
  private initialized: boolean = false;

  constructor(baseUrl: string, apiKey: string) {
    this.baseUrl = baseUrl;
    this.apiKey = apiKey;
    this.httpClient = axios.create({
      baseURL: baseUrl,
      headers: {
        'Authorization': `Agent ${apiKey}`,
        'Content-Type': 'application/json',
      },
    });
  }

  /**
   * Get next request ID
   */
  private nextId(): number {
    this.requestId += 1;
    return this.requestId;
  }

  /**
   * Make a JSON-RPC call to MCP server
   */
  private async call(method: string, params?: Record<string, any>): Promise<any> {
    const request: JsonRpcRequest = {
      jsonrpc: '2.0',
      id: this.nextId(),
      method,
      params: params || {},
    };

    try {
      const response = await this.httpClient.post<JsonRpcResponse>('', request);
      const result = response.data;

      if (result.error) {
        const error = result.error;
        throw new Error(`MCP Error ${error.code}: ${error.message}${error.data ? ` - ${JSON.stringify(error.data)}` : ''}`);
      }

      return result.result || {};
    } catch (error: any) {
      if (error.response) {
        const errorData = error.response.data;
        if (errorData?.error) {
          throw new Error(`MCP Error ${errorData.error.code}: ${errorData.error.message}`);
        }
      }
      throw new Error(`HTTP Error: ${error.message}`);
    }
  }

  /**
   * Initialize MCP session
   */
  async initialize(): Promise<any> {
    const result = await this.call('initialize', {
      protocolVersion: '2024-11-05',
      capabilities: {},
      clientInfo: {
        name: 'tracemem-test-agent',
        version: '1.0.0',
      },
    });
    this.initialized = true;
    return result;
  }

  /**
   * List available tools
   */
  async listTools(): Promise<any> {
    return await this.call('tools/list');
  }

  /**
   * Call a tool
   */
  async callTool(name: string, arguments_: Record<string, any>): Promise<any> {
    if (!this.initialized) {
      await this.initialize();
    }

    const result = await this.call('tools/call', {
      name,
      arguments: arguments_,
    });

    // Check if the tool result indicates an error
    if (result.isError) {
      // Extract error message from content
      let errorMessage = 'Tool call failed';
      if (result.content && Array.isArray(result.content) && result.content.length > 0) {
        const content = result.content[0];
        if (content.type === 'text' && content.text) {
          errorMessage = content.text;
        }
      }
      throw new Error(errorMessage);
    }

    // Parse content from tool result
    if (result.content && Array.isArray(result.content) && result.content.length > 0) {
      const content = result.content[0];
      if (content.type === 'text' && content.text) {
        // Try to parse as JSON
        try {
          return JSON.parse(content.text);
        } catch (e) {
          // If not JSON, return as text
          return { raw_text: content.text };
        }
      }
    }

    return result;
  }

  /**
   * Check if client is initialized
   */
  isInitialized(): boolean {
    return this.initialized;
  }
}

Example 1: Read Agent

This example demonstrates reading customer data from a data product.

typescript
#!/usr/bin/env tsx
/**
 * Read Test Agent
 * 
 * Tests the read operation on planetscale_read_customer_v1 data product.
 * This agent:
 * 1. Creates a decision envelope
 * 2. Reads customer data using decision_read
 * 3. Closes the decision envelope
 */

import { MCPClient } from './mcp-client.js';

async function runReadTest() {
  // Get configuration from environment
  const mcpUrl = process.env.MCP_AGENT_URL || 'https://mcp.tracemem.com';
  const apiKey = process.env.TRACEMEM_API_KEY;
  const instance = process.env.TRACEMEM_INSTANCE;
  const actor = process.env.TRACEMEM_ACTOR || 'test-read-agent';
  const customerId = process.env.CUSTOMER_ID || '1003';

  if (!apiKey) {
    console.error('ERROR: TRACEMEM_API_KEY environment variable is required');
    process.exit(1);
  }

  console.log('='.repeat(60));
  console.log('TraceMem MCP - Read Test Agent');
  console.log('='.repeat(60));
  console.log();
  console.log(`Connecting to Agent MCP at: ${mcpUrl}`);
  if (instance) console.log(`Instance: ${instance}`);
  console.log(`Actor: ${actor}`);
  console.log(`Customer ID: ${customerId}`);
  console.log();

  const client = new MCPClient(mcpUrl, apiKey);
  let decisionId: string | null = null;

  try {
    // Initialize MCP session
    console.log('Initializing MCP session...');
    const initResult = await client.initialize();
    console.log(`✓ Connected to ${initResult.serverInfo?.name || 'TraceMem Agent MCP'}`);
    console.log();

    // Step 1: Create decision envelope
    console.log('Step 1: Creating decision envelope...');
    const decision = await client.callTool('decision_create', {
      intent: 'test.read.customer',
      automation_mode: 'autonomous',
      instance: instance,
      actor: actor,
      metadata: {
        customer_id: customerId,
        test_type: 'read',
      },
    });

    decisionId = decision.decision_id || decision.id;
    if (!decisionId) {
      throw new Error('Failed to get decision_id from decision_create response');
    }
    console.log(`✓ Decision envelope created: ${decisionId}`);
    console.log();

    // Step 2: Read customer data
    console.log('Step 2: Reading customer data...');
    const readResult = await client.callTool('decision_read', {
      decision_id: decisionId,
      product: 'planetscale_read_customer_v1',
      purpose: 'web_order',
      query: {
        id: parseInt(customerId, 10),
      },
    });

    console.log('✓ Customer data retrieved');
    if (readResult.event_id) {
      console.log(`  Event ID: ${readResult.event_id}`);
    }
    if (readResult.data_ref) {
      console.log(`  Data Reference: ${readResult.data_ref}`);
    }
    if (readResult.records && readResult.records.length > 0) {
      console.log(`  Records found: ${readResult.records.length}`);
      console.log('  Customer data:');
      console.log(JSON.stringify(readResult.records[0], null, 2));
    } else {
      console.log('  No records found');
    }
    console.log();

    // Step 3: Close decision (commit)
    console.log('Step 3: Committing decision...');
    const closeResult = await client.callTool('decision_close', {
      decision_id: decisionId,
      action: 'commit',
    });
    console.log(`✓ Decision committed`);
    if (closeResult.status) {
      console.log(`  Status: ${closeResult.status}`);
    }
    console.log();

    // Summary
    console.log('='.repeat(60));
    console.log('Summary');
    console.log('='.repeat(60));
    console.log(`Decision ID: ${decisionId}`);
    console.log('Result: ✓ Read operation completed successfully');
    console.log();

  } catch (error: any) {
    console.error(`✗ Error: ${error.message}`);
    if (error.stack) {
      console.error(error.stack);
    }

    // Try to close decision on error
    if (decisionId) {
      try {
        console.log('Attempting to abort decision...');
        await client.callTool('decision_close', {
          decision_id: decisionId,
          action: 'abort',
          reason: `Error occurred: ${error.message}`,
        });
        console.log('✓ Decision aborted');
      } catch (closeError: any) {
        console.error(`Failed to abort decision: ${closeError.message}`);
      }
    }

    process.exit(1);
  }
}

// Run the test
runReadTest().catch((error) => {
  console.error('Fatal error:', error);
  process.exit(1);
});

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"
tsx src/test-read-agent.ts

Example 2: Insert Agent (with Policy)

This example demonstrates inserting an order with policy evaluation.

typescript
#!/usr/bin/env tsx
/**
 * Insert Test Agent
 * 
 * Tests the insert operation on planetscale_insert_order_v1 data product.
 * This agent:
 * 1. Creates a decision envelope
 * 2. Evaluates discount_cap_v1 policy
 * 3. Inserts an order using decision_write with operation: "insert"
 * 4. Closes the decision envelope
 */

import { MCPClient } from './mcp-client.js';

async function runInsertTest() {
  // Get configuration from environment
  const mcpUrl = process.env.MCP_AGENT_URL || 'https://mcp.tracemem.com';
  const apiKey = process.env.TRACEMEM_API_KEY;
  const instance = process.env.TRACEMEM_INSTANCE;
  const actor = process.env.TRACEMEM_ACTOR || 'test-insert-agent';
  
  // Test data - can be overridden via environment variables
  const customerId = parseInt(process.env.CUSTOMER_ID || '1001', 10);
  const productId = parseInt(process.env.PRODUCT_ID || '2', 10);
  const quantity = parseInt(process.env.QUANTITY || '1', 10);
  const totalAmount = parseFloat(process.env.TOTAL_AMOUNT || '99.99');
  const orderStatus = process.env.ORDER_STATUS || 'pending';
  const proposedDiscount = parseFloat(process.env.PROPOSED_DISCOUNT || '0');

  if (!apiKey) {
    console.error('ERROR: TRACEMEM_API_KEY environment variable is required');
    process.exit(1);
  }

  console.log('='.repeat(60));
  console.log('TraceMem MCP - Insert Test Agent');
  console.log('='.repeat(60));
  console.log();
  console.log(`Connecting to Agent MCP at: ${mcpUrl}`);
  if (instance) console.log(`Instance: ${instance}`);
  console.log(`Actor: ${actor}`);
  console.log(`Order Data:`);
  console.log(`  Customer ID: ${customerId}`);
  console.log(`  Product ID: ${productId}`);
  console.log(`  Quantity: ${quantity}`);
  console.log(`  Total Amount: ${totalAmount}`);
  console.log(`  Order Status: ${orderStatus}`);
  console.log(`  Proposed Discount: ${proposedDiscount}`);
  console.log();

  const client = new MCPClient(mcpUrl, apiKey);
  let decisionId: string | null = null;

  try {
    // Initialize MCP session
    console.log('Initializing MCP session...');
    const initResult = await client.initialize();
    console.log(`✓ Connected to ${initResult.serverInfo?.name || 'TraceMem Agent MCP'}`);
    console.log();

    // Step 1: Create decision envelope
    console.log('Step 1: Creating decision envelope...');
    const decision = await client.callTool('decision_create', {
      intent: 'test.insert.order',
      automation_mode: 'autonomous',
      instance: instance,
      actor: actor,
      metadata: {
        customer_id: customerId.toString(),
        product_id: productId.toString(),
        test_type: 'insert',
      },
    });

    decisionId = decision.decision_id || decision.id;
    if (!decisionId) {
      throw new Error('Failed to get decision_id from decision_create response');
    }
    console.log(`✓ Decision envelope created: ${decisionId}`);
    console.log();

    // Step 2: Evaluate policy
    console.log('Step 2: Evaluating discount_cap_v1 policy...');
    const policyResult = await client.callTool('decision_evaluate', {
      decision_id: decisionId,
      policy_id: 'discount_cap_v1',
      inputs: {
        proposed_discount: proposedDiscount,
      },
    });

    const outcome = policyResult.outcome || 'unknown';
    console.log(`✓ Policy evaluation completed`);
    console.log(`  Policy ID: discount_cap_v1`);
    console.log(`  Proposed Discount: ${proposedDiscount}`);
    console.log(`  Outcome: ${outcome}`);
    if (policyResult.rationale) {
      const rationaleMessage = typeof policyResult.rationale === 'object' 
        ? policyResult.rationale.message || JSON.stringify(policyResult.rationale)
        : policyResult.rationale;
      console.log(`  Rationale: ${rationaleMessage}`);
    }
    if (policyResult.event_id) {
      console.log(`  Event ID: ${policyResult.event_id}`);
    }
    console.log();

    // Handle policy outcomes
    if (outcome === 'deny') {
      throw new Error(`Policy evaluation denied the operation. Rationale: ${policyResult.rationale?.message || 'No rationale provided'}`);
    } else if (outcome === 'requires_exception') {
      console.log('⚠ Policy requires exception/approval, but continuing with test...');
      console.log();
    } else if (outcome !== 'allow') {
      console.log(`⚠ Unexpected policy outcome: ${outcome}, continuing with test...`);
      console.log();
    }

    // Step 3: Insert order
    console.log('Step 3: Inserting order...');
    const writeResult = await client.callTool('decision_write', {
      decision_id: decisionId,
      product: 'planetscale_insert_order_v1',
      purpose: 'web_order',
      mutation: {
        operation: 'insert',
        records: [{
          customer_id: customerId,
          product_id: productId,
          quantity: quantity,
          total_amount: totalAmount,
          order_status: orderStatus,
        }],
      },
    });

    console.log('✓ Order inserted');
    if (writeResult.event_id) {
      console.log(`  Event ID: ${writeResult.event_id}`);
    }
    if (writeResult.status) {
      console.log(`  Status: ${writeResult.status}`);
    }
    if (writeResult.created_records && writeResult.created_records.length > 0) {
      console.log(`  Created ${writeResult.created_records.length} record(s):`);
      writeResult.created_records.forEach((record: any, index: number) => {
        console.log(`  Record ${index + 1}:`);
        console.log(JSON.stringify(record, null, 2));
      });
    }
    if (writeResult.mutation_summary) {
      console.log(`  Mutation Summary: ${JSON.stringify(writeResult.mutation_summary)}`);
    }
    console.log();

    // Step 4: Close decision (commit)
    console.log('Step 4: Committing decision...');
    const closeResult = await client.callTool('decision_close', {
      decision_id: decisionId,
      action: 'commit',
    });
    console.log(`✓ Decision committed`);
    if (closeResult.status) {
      console.log(`  Status: ${closeResult.status}`);
    }
    console.log();

    // Summary
    console.log('='.repeat(60));
    console.log('Summary');
    console.log('='.repeat(60));
    console.log(`Decision ID: ${decisionId}`);
    console.log('Result: ✓ Insert operation completed successfully');
    if (writeResult.created_records && writeResult.created_records.length > 0) {
      const firstRecord = writeResult.created_records[0];
      if (firstRecord.order_id) {
        console.log(`Created Order ID: ${firstRecord.order_id}`);
      }
    }
    console.log();

  } catch (error: any) {
    console.error(`✗ Error: ${error.message}`);
    if (error.stack) {
      console.error(error.stack);
    }

    // Try to close decision on error
    if (decisionId) {
      try {
        console.log('Attempting to abort decision...');
        await client.callTool('decision_close', {
          decision_id: decisionId,
          action: 'abort',
          reason: `Error occurred: ${error.message}`,
        });
        console.log('✓ Decision aborted');
      } catch (closeError: any) {
        console.error(`Failed to abort decision: ${closeError.message}`);
      }
    }

    process.exit(1);
  }
}

// Run the test
runInsertTest().catch((error) => {
  console.error('Fatal error:', error);
  process.exit(1);
});

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.

typescript
#!/usr/bin/env tsx
/**
 * Insert Test Agent
 * 
 * Tests the insert operation on planetscale_insert_order_no_policy_v1 data product.
 * This agent:
 * 1. Creates a decision envelope
 * 2. Inserts an order using decision_write with operation: "insert"
 * 3. Closes the decision envelope
 */

import { MCPClient } from './mcp-client.js';

async function runInsertTest() {
  // Get configuration from environment
  const mcpUrl = process.env.MCP_AGENT_URL || 'https://mcp.tracemem.com';
  const apiKey = process.env.TRACEMEM_API_KEY;
  const instance = process.env.TRACEMEM_INSTANCE;
  const actor = process.env.TRACEMEM_ACTOR || 'test-insert-agent';
  
  // Test data - can be overridden via environment variables
  const customerId = parseInt(process.env.CUSTOMER_ID || '1002', 10);
  const productId = parseInt(process.env.PRODUCT_ID || '4', 10);
  const quantity = parseInt(process.env.QUANTITY || '1', 10);
  const totalAmount = parseFloat(process.env.TOTAL_AMOUNT || '99.99');
  const orderStatus = process.env.ORDER_STATUS || 'pending';

  if (!apiKey) {
    console.error('ERROR: TRACEMEM_API_KEY environment variable is required');
    process.exit(1);
  }

  console.log('='.repeat(60));
  console.log('TraceMem MCP - Insert Test Agent');
  console.log('='.repeat(60));
  console.log();
  console.log(`Connecting to Agent MCP at: ${mcpUrl}`);
  if (instance) console.log(`Instance: ${instance}`);
  console.log(`Actor: ${actor}`);
  console.log(`Order Data:`);
  console.log(`  Customer ID: ${customerId}`);
  console.log(`  Product ID: ${productId}`);
  console.log(`  Quantity: ${quantity}`);
  console.log(`  Total Amount: ${totalAmount}`);
  console.log(`  Order Status: ${orderStatus}`);
  console.log();

  const client = new MCPClient(mcpUrl, apiKey);
  let decisionId: string | null = null;

  try {
    // Initialize MCP session
    console.log('Initializing MCP session...');
    const initResult = await client.initialize();
    console.log(`✓ Connected to ${initResult.serverInfo?.name || 'TraceMem Agent MCP'}`);
    console.log();

    // Step 1: Create decision envelope
    console.log('Step 1: Creating decision envelope...');
    const decision = await client.callTool('decision_create', {
      intent: 'test.insert.order',
      automation_mode: 'autonomous',
      instance: instance,
      actor: actor,
      metadata: {
        customer_id: customerId.toString(),
        product_id: productId.toString(),
        test_type: 'insert',
      },
    });

    decisionId = decision.decision_id || decision.id;
    if (!decisionId) {
      throw new Error('Failed to get decision_id from decision_create response');
    }
    console.log(`✓ Decision envelope created: ${decisionId}`);
    console.log();

    // Step 2: Insert order
    console.log('Step 2: Inserting order...');
    const writeResult = await client.callTool('decision_write', {
      decision_id: decisionId,
      product: 'planetscale_insert_order_no_policy_v1',
      purpose: 'web_order',
      mutation: {
        operation: 'insert',
        records: [{
          customer_id: customerId,
          product_id: productId,
          quantity: quantity,
          total_amount: totalAmount,
          order_status: orderStatus,
        }],
      },
    });

    console.log('✓ Order inserted');
    if (writeResult.event_id) {
      console.log(`  Event ID: ${writeResult.event_id}`);
    }
    if (writeResult.status) {
      console.log(`  Status: ${writeResult.status}`);
    }
    if (writeResult.created_records && writeResult.created_records.length > 0) {
      console.log(`  Created ${writeResult.created_records.length} record(s):`);
      writeResult.created_records.forEach((record: any, index: number) => {
        console.log(`  Record ${index + 1}:`);
        // Display key fields prominently
        if (record.order_id !== undefined) {
          console.log(`    Order ID: ${record.order_id}`);
        }
        if (record.customer_id !== undefined) {
          console.log(`    Customer ID: ${record.customer_id}`);
        }
        if (record.product_id !== undefined) {
          console.log(`    Product ID: ${record.product_id}`);
        }
        if (record.quantity !== undefined) {
          console.log(`    Quantity: ${record.quantity}`);
        }
        if (record.total_amount !== undefined) {
          console.log(`    Total Amount: ${record.total_amount}`);
        }
        if (record.order_status !== undefined) {
          console.log(`    Order Status: ${record.order_status}`);
        }
        if (record.order_date !== undefined) {
          console.log(`    Order Date: ${record.order_date}`);
        }
        // Display full record as JSON for complete details
        console.log(`    Full Record:`);
        console.log(JSON.stringify(record, null, 4));
      });
    } else {
      console.log('  ⚠ No created records returned (check if return_created is enabled)');
    }
    if (writeResult.mutation_summary) {
      console.log(`  Mutation Summary: ${JSON.stringify(writeResult.mutation_summary)}`);
    }
    console.log();

    // Step 3: Close decision (commit)
    console.log('Step 3: Committing decision...');
    const closeResult = await client.callTool('decision_close', {
      decision_id: decisionId,
      action: 'commit',
    });
    console.log(`✓ Decision committed`);
    if (closeResult.status) {
      console.log(`  Status: ${closeResult.status}`);
    }
    console.log();

    // Summary
    console.log('='.repeat(60));
    console.log('Summary');
    console.log('='.repeat(60));
    console.log(`Decision ID: ${decisionId}`);
    console.log('Result: ✓ Insert operation completed successfully');
    if (writeResult.created_records && writeResult.created_records.length > 0) {
      const firstRecord = writeResult.created_records[0];
      console.log(`Created Record:`);
      if (firstRecord.order_id !== undefined) {
        console.log(`  Order ID: ${firstRecord.order_id}`);
      }
      if (firstRecord.customer_id !== undefined) {
        console.log(`  Customer ID: ${firstRecord.customer_id}`);
      }
      if (firstRecord.product_id !== undefined) {
        console.log(`  Product ID: ${firstRecord.product_id}`);
      }
      if (firstRecord.quantity !== undefined) {
        console.log(`  Quantity: ${firstRecord.quantity}`);
      }
      if (firstRecord.total_amount !== undefined) {
        console.log(`  Total Amount: ${firstRecord.total_amount}`);
      }
      if (firstRecord.order_status !== undefined) {
        console.log(`  Order Status: ${firstRecord.order_status}`);
      }
      console.log(`  Full Record: ${JSON.stringify(firstRecord, null, 2)}`);
    } else {
      console.log('⚠ No created records returned');
    }
    console.log();

  } catch (error: any) {
    console.error(`✗ Error: ${error.message}`);
    if (error.stack) {
      console.error(error.stack);
    }

    // Try to close decision on error
    if (decisionId) {
      try {
        console.log('Attempting to abort decision...');
        await client.callTool('decision_close', {
          decision_id: decisionId,
          action: 'abort',
          reason: `Error occurred: ${error.message}`,
        });
        console.log('✓ Decision aborted');
      } catch (closeError: any) {
        console.error(`Failed to abort decision: ${closeError.message}`);
      }
    }

    process.exit(1);
  }
}

// Run the test
runInsertTest().catch((error) => {
  console.error('Fatal error:', error);
  process.exit(1);
});

Example 4: Update Agent

This example demonstrates updating product stock.

typescript
#!/usr/bin/env tsx
/**
 * Update Test Agent
 * 
 * Tests the update operation on planetscale_update_product_stock_v1 data product.
 * This agent:
 * 1. Creates a decision envelope
 * 2. Updates product stock using decision_write with operation: "update"
 * 3. Closes the decision envelope
 */

import { MCPClient } from './mcp-client.js';

async function runUpdateTest() {
  // Get configuration from environment
  const mcpUrl = process.env.MCP_AGENT_URL || 'https://mcp.tracemem.com';
  const apiKey = process.env.TRACEMEM_API_KEY;
  const instance = process.env.TRACEMEM_INSTANCE;
  const actor = process.env.TRACEMEM_ACTOR || 'test-update-agent';
  
  // Test data - can be overridden via environment variables
  const productId = parseInt(process.env.PRODUCT_ID || '4', 10);
  const stockQuantity = parseInt(process.env.STOCK_QUANTITY || '90', 10);

  if (!apiKey) {
    console.error('ERROR: TRACEMEM_API_KEY environment variable is required');
    process.exit(1);
  }

  console.log('='.repeat(60));
  console.log('TraceMem MCP - Update Test Agent');
  console.log('='.repeat(60));
  console.log();
  console.log(`Connecting to Agent MCP at: ${mcpUrl}`);
  if (instance) console.log(`Instance: ${instance}`);
  console.log(`Actor: ${actor}`);
  console.log(`Update Data:`);
  console.log(`  Product ID: ${productId}`);
  console.log(`  Stock Quantity: ${stockQuantity}`);
  console.log();

  const client = new MCPClient(mcpUrl, apiKey);
  let decisionId: string | null = null;

  try {
    // Initialize MCP session
    console.log('Initializing MCP session...');
    const initResult = await client.initialize();
    console.log(`✓ Connected to ${initResult.serverInfo?.name || 'TraceMem Agent MCP'}`);
    console.log();

    // Step 1: Create decision envelope
    console.log('Step 1: Creating decision envelope...');
    const decision = await client.callTool('decision_create', {
      intent: 'test.update.product.stock',
      automation_mode: 'autonomous',
      instance: instance,
      actor: actor,
      metadata: {
        product_id: productId.toString(),
        stock_quantity: stockQuantity.toString(),
        test_type: 'update',
      },
    });

    decisionId = decision.decision_id || decision.id;
    if (!decisionId) {
      throw new Error('Failed to get decision_id from decision_create response');
    }
    console.log(`✓ Decision envelope created: ${decisionId}`);
    console.log();

    // Step 2: Update product stock
    console.log('Step 2: Updating product stock...');
    const writeResult = await client.callTool('decision_write', {
      decision_id: decisionId,
      product: 'planetscale_update_product_stock_v1',
      purpose: 'web_order',
      mutation: {
        operation: 'update',
        records: [{
          product_id: productId,  // Key field for identification
          stock_quantity: stockQuantity,  // Field to update (is_updatable: true)
        }],
      },
    });

    console.log('✓ Product stock updated');
    if (writeResult.event_id) {
      console.log(`  Event ID: ${writeResult.event_id}`);
    }
    if (writeResult.status) {
      console.log(`  Status: ${writeResult.status}`);
    }
    if (writeResult.mutation_summary) {
      console.log(`  Mutation Summary: ${JSON.stringify(writeResult.mutation_summary)}`);
    }
    console.log();

    // Step 3: Close decision (commit)
    console.log('Step 3: Committing decision...');
    const closeResult = await client.callTool('decision_close', {
      decision_id: decisionId,
      action: 'commit',
    });
    console.log(`✓ Decision committed`);
    if (closeResult.status) {
      console.log(`  Status: ${closeResult.status}`);
    }
    console.log();

    // Summary
    console.log('='.repeat(60));
    console.log('Summary');
    console.log('='.repeat(60));
    console.log(`Decision ID: ${decisionId}`);
    console.log('Result: ✓ Update operation completed successfully');
    console.log(`Product ID: ${productId}`);
    console.log(`New Stock Quantity: ${stockQuantity}`);
    console.log();

  } catch (error: any) {
    console.error(`✗ Error: ${error.message}`);
    if (error.stack) {
      console.error(error.stack);
    }

    // Try to close decision on error
    if (decisionId) {
      try {
        console.log('Attempting to abort decision...');
        await client.callTool('decision_close', {
          decision_id: decisionId,
          action: 'abort',
          reason: `Error occurred: ${error.message}`,
        });
        console.log('✓ Decision aborted');
      } catch (closeError: any) {
        console.error(`Failed to abort decision: ${closeError.message}`);
      }
    }

    process.exit(1);
  }
}

// Run the test
runUpdateTest().catch((error) => {
  console.error('Fatal error:', error);
  process.exit(1);
});

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.

typescript
#!/usr/bin/env tsx
/**
 * Delete Test Agent
 * 
 * Tests the delete operation on planetscale_delete_target_v1 data product.
 * This agent:
 * 1. Creates a decision envelope
 * 2. Deletes a target record using decision_write with operation: "delete"
 * 3. Closes the decision envelope
 */

import { MCPClient } from './mcp-client.js';

async function runDeleteTest() {
  // Get configuration from environment
  const mcpUrl = process.env.MCP_AGENT_URL || 'https://mcp.tracemem.com';
  const apiKey = process.env.TRACEMEM_API_KEY;
  const instance = process.env.TRACEMEM_INSTANCE;
  const actor = process.env.TRACEMEM_ACTOR || 'test-delete-agent';
  
  // Test data - target ID must be a UUID
  const targetId = process.env.TARGET_ID || '0cf1e19e-00ed-4a4c-8d82-ee70f590fad8';
  
  if (!apiKey) {
    console.error('ERROR: TRACEMEM_API_KEY environment variable is required');
    process.exit(1);
  }

  if (!targetId) {
    console.error('ERROR: TARGET_ID environment variable is required (must be a UUID)');
    console.error('Example: TARGET_ID=550e8400-e29b-41d4-a716-446655440000');
    process.exit(1);
  }

  // Basic UUID format validation
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  if (!uuidRegex.test(targetId)) {
    console.error('ERROR: TARGET_ID must be a valid UUID format');
    console.error(`Received: ${targetId}`);
    console.error('Expected format: 550e8400-e29b-41d4-a716-446655440000');
    process.exit(1);
  }

  console.log('='.repeat(60));
  console.log('TraceMem MCP - Delete Test Agent');
  console.log('='.repeat(60));
  console.log();
  console.log(`Connecting to Agent MCP at: ${mcpUrl}`);
  if (instance) console.log(`Instance: ${instance}`);
  console.log(`Actor: ${actor}`);
  console.log(`Delete Data:`);
  console.log(`  Target ID: ${targetId}`);
  console.log();
  console.log('⚠ WARNING: This will delete a record from the database!');
  console.log();

  const client = new MCPClient(mcpUrl, apiKey);
  let decisionId: string | null = null;

  try {
    // Initialize MCP session
    console.log('Initializing MCP session...');
    const initResult = await client.initialize();
    console.log(`✓ Connected to ${initResult.serverInfo?.name || 'TraceMem Agent MCP'}`);
    console.log();

    // Step 1: Create decision envelope
    console.log('Step 1: Creating decision envelope...');
    const decision = await client.callTool('decision_create', {
      intent: 'test.delete.target',
      automation_mode: 'autonomous',
      instance: instance,
      actor: actor,
      metadata: {
        target_id: targetId,
        test_type: 'delete',
      },
    });

    decisionId = decision.decision_id || decision.id;
    if (!decisionId) {
      throw new Error('Failed to get decision_id from decision_create response');
    }
    console.log(`✓ Decision envelope created: ${decisionId}`);
    console.log();

    // Step 2: Delete target record
    console.log('Step 2: Deleting target record...');
    const writeResult = await client.callTool('decision_write', {
      decision_id: decisionId,
      product: 'planetscale_delete_target_v1',
      purpose: 'delete_target',
      mutation: {
        operation: 'delete',
        records: [{
          id: targetId,  // Key field for deletion (is_key: true, type: uuid)
        }],
      },
    });

    console.log('✓ Target record deleted');
    if (writeResult.event_id) {
      console.log(`  Event ID: ${writeResult.event_id}`);
    }
    if (writeResult.status) {
      console.log(`  Status: ${writeResult.status}`);
    }
    if (writeResult.mutation_summary) {
      console.log(`  Mutation Summary: ${JSON.stringify(writeResult.mutation_summary)}`);
    }
    console.log();

    // Step 3: Close decision (commit)
    console.log('Step 3: Committing decision...');
    const closeResult = await client.callTool('decision_close', {
      decision_id: decisionId,
      action: 'commit',
    });
    console.log(`✓ Decision committed`);
    if (closeResult.status) {
      console.log(`  Status: ${closeResult.status}`);
    }
    console.log();

    // Summary
    console.log('='.repeat(60));
    console.log('Summary');
    console.log('='.repeat(60));
    console.log(`Decision ID: ${decisionId}`);
    console.log('Result: ✓ Delete operation completed successfully');
    console.log(`Deleted Target ID: ${targetId}`);
    console.log();

  } catch (error: any) {
    console.error(`✗ Error: ${error.message}`);
    if (error.stack) {
      console.error(error.stack);
    }

    // Try to close decision on error
    if (decisionId) {
      try {
        console.log('Attempting to abort decision...');
        await client.callTool('decision_close', {
          decision_id: decisionId,
          action: 'abort',
          reason: `Error occurred: ${error.message}`,
        });
        console.log('✓ Decision aborted');
      } catch (closeError: any) {
        console.error(`Failed to abort decision: ${closeError.message}`);
      }
    }

    process.exit(1);
  }
}

// Run the test
runDeleteTest().catch((error) => {
  console.error('Fatal error:', error);
  process.exit(1);
});

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:

typescript
let decisionId: string | null = null;
try {
  // Create decision and perform operations
  const decision = await client.callTool('decision_create', {...});
  decisionId = decision.decision_id;
  
  // Perform operations...
  
  // Commit on success
  await client.callTool('decision_close', {
    decision_id: decisionId,
    action: 'commit',
  });
} catch (error: any) {
  // Always abort on error
  if (decisionId) {
    try {
      await client.callTool('decision_close', {
        decision_id: decisionId,
        action: 'abort',
        reason: `Error occurred: ${error.message}`,
      });
    } catch (closeError) {
      // Log but don't fail on abort failure
      console.error(`Failed to abort decision: ${closeError}`);
    }
  }
  throw error;
}

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