New protocol version released: This page may contain outdated information.
Extend your HAIP applications with powerful tool integration capabilities. Leverage the Model Context Protocol (MCP) to connect AI agents with external services, APIs, and custom tools for enhanced functionality.

Tool Integration Overview

What are Tools?

Tools are external functions or services that AI agents can call to:
  • Retrieve information (web search, database queries)
  • Perform actions (send emails, create files)
  • Process data (calculations, image analysis)
  • Interact with systems (APIs, databases)

MCP Integration

The HAIP SDK implements the Model Context Protocol (MCP) for tool lifecycle management:
// Register available tools
await client.listTools("AGENT", [
  { name: "search", description: "Search the web for information" },
  { name: "calculator", description: "Perform mathematical calculations" },
  { name: "weather", description: "Get weather information for a location" },
]);

// Handle tool calls
client.on("message", (message: HAIPMessage) => {
  if (message.type === "TOOL_CALL") {
    handleToolCall(message);
  }
});

Tool Lifecycle

1. Tool Registration

Register available tools with the agent:
// Define tools
const tools = [
  {
    name: "search",
    description: "Search the web for information",
    parameters: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "Search query",
        },
      },
      required: ["query"],
    },
  },
  {
    name: "calculator",
    description: "Perform mathematical calculations",
    parameters: {
      type: "object",
      properties: {
        expression: {
          type: "string",
          description: "Mathematical expression to evaluate",
        },
      },
      required: ["expression"],
    },
  },
];

// Register tools
await client.listTools("AGENT", tools);

2. Tool Call Handling

Handle incoming tool calls:
async function handleToolCall(message: HAIPMessage) {
  const { tool, params, call_id } = message.payload;

  try {
    // Update tool status to running
    await client.updateTool("AGENT", call_id, "RUNNING", 0);

    // Execute the tool
    const result = await executeTool(tool, params);

    // Complete the tool call
    await client.completeTool("AGENT", call_id, "OK", result);
  } catch (error) {
    console.error(`Tool execution failed:`, error);
    await client.completeTool("AGENT", call_id, "ERROR", {
      error: error.message,
    });
  }
}

async function executeTool(tool: string, params: any) {
  switch (tool) {
    case "search":
      return await performSearch(params.query);

    case "calculator":
      return { result: eval(params.expression) };

    case "weather":
      return await getWeather(params.location);

    default:
      throw new Error(`Unknown tool: ${tool}`);
  }
}

3. Tool Status Updates

Update tool execution status:
// Tool is queued
await client.updateTool("AGENT", callId, "QUEUED");

// Tool is running with progress
await client.updateTool("AGENT", callId, "RUNNING", 50); // 50% progress

// Tool is running with partial results
await client.updateTool("AGENT", callId, "RUNNING", 75, {
  partial: "Partial results available...",
});

// Tool is being cancelled
await client.updateTool("AGENT", callId, "CANCELLING");

4. Tool Completion

Complete tool execution with results:
// Successful completion
await client.completeTool("AGENT", callId, "OK", {
  results: ["Result 1", "Result 2"],
  metadata: { source: "web-search" },
});

// Error completion
await client.completeTool("AGENT", callId, "ERROR", {
  error: "Network timeout",
  details: "Failed to connect to search service",
});

// Cancelled completion
await client.completeTool("AGENT", callId, "CANCELLED", {
  reason: "User cancelled the operation",
});

Tool Implementation Examples

Web Search Tool

class WebSearchTool {
  constructor(private apiKey: string) {}

  async search(query: string): Promise<any> {
    const response = await fetch(
      `https://api.search.com/search?q=${encodeURIComponent(query)}&key=${
        this.apiKey
      }`
    );

    if (!response.ok) {
      throw new Error(`Search failed: ${response.statusText}`);
    }

    const data = await response.json();
    return {
      results: data.results.map((result: any) => ({
        title: result.title,
        url: result.url,
        snippet: result.snippet,
      })),
      totalResults: data.totalResults,
    };
  }
}

// Usage in tool handler
const searchTool = new WebSearchTool(process.env.SEARCH_API_KEY);

async function handleSearchTool(params: any) {
  const results = await searchTool.search(params.query);
  return {
    query: params.query,
    results: results.results,
    totalResults: results.totalResults,
  };
}

Calculator Tool

class CalculatorTool {
  private safeEval(expression: string): number {
    // Remove potentially dangerous characters
    const sanitized = expression.replace(/[^0-9+\-*/().\s]/g, "");

    try {
      // Use Function constructor for safer evaluation
      const result = new Function(`return ${sanitized}`)();

      if (typeof result !== "number" || !isFinite(result)) {
        throw new Error("Invalid calculation result");
      }

      return result;
    } catch (error) {
      throw new Error(`Calculation failed: ${error.message}`);
    }
  }

  calculate(expression: string): any {
    const result = this.safeEval(expression);
    return {
      expression,
      result,
      formatted: result.toLocaleString(),
    };
  }
}

// Usage
const calculator = new CalculatorTool();

async function handleCalculatorTool(params: any) {
  return calculator.calculate(params.expression);
}

Weather Tool

class WeatherTool {
  constructor(private apiKey: string) {}

  async getWeather(location: string): Promise<any> {
    const response = await fetch(
      `https://api.weatherapi.com/v1/current.json?key=${
        this.apiKey
      }&q=${encodeURIComponent(location)}`
    );

    if (!response.ok) {
      throw new Error(`Weather API failed: ${response.statusText}`);
    }

    const data = await response.json();
    return {
      location: data.location.name,
      temperature: data.current.temp_c,
      condition: data.current.condition.text,
      humidity: data.current.humidity,
      windSpeed: data.current.wind_kph,
    };
  }
}

// Usage
const weatherTool = new WeatherTool(process.env.WEATHER_API_KEY);

async function handleWeatherTool(params: any) {
  return await weatherTool.getWeather(params.location);
}

Tool Management

Tool Registry

Manage multiple tools in a registry:
class ToolRegistry {
  private tools = new Map<string, (params: any) => Promise<any>>();

  register(name: string, handler: (params: any) => Promise<any>) {
    this.tools.set(name, handler);
  }

  async execute(name: string, params: any): Promise<any> {
    const handler = this.tools.get(name);
    if (!handler) {
      throw new Error(`Tool not found: ${name}`);
    }

    return await handler(params);
  }

  getToolNames(): string[] {
    return Array.from(this.tools.keys());
  }
}

// Usage
const registry = new ToolRegistry();

registry.register("search", handleSearchTool);
registry.register("calculator", handleCalculatorTool);
registry.register("weather", handleWeatherTool);

// Execute tool
const result = await registry.execute("search", { query: "TypeScript" });

Tool Schema Management

Get tool schemas for validation:
async function getToolSchema(toolName: string) {
  await client.getToolSchema("AGENT", toolName);
}

// Handle schema response
client.on("message", (message: HAIPMessage) => {
  if (message.type === "TOOL_SCHEMA") {
    const { tool, schema } = message.payload;
    console.log(`Schema for ${tool}:`, schema);
  }
});

Tool Error Handling

Comprehensive Error Handling

class ToolErrorHandler {
  static async handleToolExecution(
    client: any,
    callId: string,
    toolName: string,
    params: any,
    executor: (params: any) => Promise<any>
  ) {
    try {
      // Update status to running
      await client.updateTool("AGENT", callId, "RUNNING", 0);

      // Execute with timeout
      const result = await Promise.race([
        executor(params),
        new Promise((_, reject) =>
          setTimeout(() => reject(new Error("Tool execution timeout")), 30000)
        ),
      ]);

      // Complete successfully
      await client.completeTool("AGENT", callId, "OK", result);
    } catch (error) {
      console.error(`Tool ${toolName} failed:`, error);

      // Determine error type
      let errorType = "ERROR";
      let errorDetails = { error: error.message };

      if (error.message.includes("timeout")) {
        errorType = "ERROR";
        errorDetails = { error: "Tool execution timed out" };
      } else if (error.message.includes("permission")) {
        errorType = "ERROR";
        errorDetails = { error: "Insufficient permissions for this tool" };
      } else if (error.message.includes("network")) {
        errorType = "ERROR";
        errorDetails = { error: "Network error occurred" };
      }

      await client.completeTool("AGENT", callId, errorType, errorDetails);
    }
  }
}

// Usage
await ToolErrorHandler.handleToolExecution(
  client,
  callId,
  "search",
  { query: "TypeScript" },
  handleSearchTool
);

Tool Retry Logic

class ToolRetryHandler {
  static async executeWithRetry(
    executor: () => Promise<any>,
    maxRetries: number = 3,
    delay: number = 1000
  ): Promise<any> {
    let lastError: Error;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await executor();
      } catch (error) {
        lastError = error as Error;

        if (attempt < maxRetries) {
          console.log(
            `Tool attempt ${attempt} failed, retrying in ${delay}ms...`
          );
          await new Promise((resolve) => setTimeout(resolve, delay));
          delay *= 2; // Exponential backoff
        }
      }
    }

    throw lastError!;
  }
}

// Usage in tool handler
async function handleSearchToolWithRetry(params: any) {
  return await ToolRetryHandler.executeWithRetry(
    () => searchTool.search(params.query),
    3,
    1000
  );
}

Tool Testing

Mock Tool Testing

class MockToolRegistry {
  private mockResults = new Map<string, any>();
  private mockErrors = new Map<string, Error>();

  setMockResult(toolName: string, result: any) {
    this.mockResults.set(toolName, result);
  }

  setMockError(toolName: string, error: Error) {
    this.mockErrors.set(toolName, error);
  }

  async execute(toolName: string, params: any): Promise<any> {
    const error = this.mockErrors.get(toolName);
    if (error) {
      throw error;
    }

    const result = this.mockResults.get(toolName);
    if (result) {
      return result;
    }

    throw new Error(`No mock result set for tool: ${toolName}`);
  }
}

// Usage in tests
const mockRegistry = new MockToolRegistry();

mockRegistry.setMockResult("search", {
  results: [{ title: "Test Result", url: "https://example.com" }],
  totalResults: 1,
});

mockRegistry.setMockError("calculator", new Error("Invalid expression"));

// Test tool execution
const result = await mockRegistry.execute("search", { query: "test" });
expect(result.results).toHaveLength(1);

Tool Integration Testing

describe("Tool Integration", () => {
  test("should handle tool call successfully", async () => {
    const client = createHAIPClient({
      url: "ws://localhost:8080",
      token: "test-token",
      transport: "websocket",
    });

    // Mock tool execution
    const mockToolCall = {
      type: "TOOL_CALL",
      payload: {
        tool: "calculator",
        params: { expression: "2 + 2" },
        call_id: "call-123",
      },
    };

    // Set up expectations
    const updateSpy = jest.spyOn(client, "updateTool");
    const completeSpy = jest.spyOn(client, "completeTool");

    // Simulate tool call
    client.emit("message", mockToolCall);

    // Wait for processing
    await new Promise((resolve) => setTimeout(resolve, 100));

    // Verify tool lifecycle
    expect(updateSpy).toHaveBeenCalledWith("AGENT", "call-123", "RUNNING", 0);
    expect(completeSpy).toHaveBeenCalledWith("AGENT", "call-123", "OK", {
      result: 4,
    });
  });
});

Tool Performance Monitoring

Tool Metrics

class ToolMetrics {
  private metrics = new Map<
    string,
    {
      calls: number;
      totalTime: number;
      errors: number;
      lastCall: number;
    }
  >();

  recordCall(toolName: string, duration: number, success: boolean) {
    const current = this.metrics.get(toolName) || {
      calls: 0,
      totalTime: 0,
      errors: 0,
      lastCall: 0,
    };

    current.calls++;
    current.totalTime += duration;
    current.lastCall = Date.now();

    if (!success) {
      current.errors++;
    }

    this.metrics.set(toolName, current);
  }

  getMetrics(toolName?: string) {
    if (toolName) {
      return this.metrics.get(toolName);
    }
    return Object.fromEntries(this.metrics);
  }

  getAverageTime(toolName: string): number {
    const metrics = this.metrics.get(toolName);
    if (!metrics || metrics.calls === 0) {
      return 0;
    }
    return metrics.totalTime / metrics.calls;
  }

  getErrorRate(toolName: string): number {
    const metrics = this.metrics.get(toolName);
    if (!metrics || metrics.calls === 0) {
      return 0;
    }
    return metrics.errors / metrics.calls;
  }
}

// Usage
const toolMetrics = new ToolMetrics();

async function executeToolWithMetrics(
  toolName: string,
  executor: () => Promise<any>
): Promise<any> {
  const startTime = Date.now();
  let success = true;

  try {
    const result = await executor();
    return result;
  } catch (error) {
    success = false;
    throw error;
  } finally {
    const duration = Date.now() - startTime;
    toolMetrics.recordCall(toolName, duration, success);
  }
}

Next Steps