Skip to main content
Glama

vora_calls

Query call history and analytics to review call results, agent performance, and AI recommendations. Filter by call ID, agent, or status, and get aggregate metrics including conversion rates and top objections.

Instructions

Query your call history, results, and analytics. Use to:

  • Check the result of a specific call (by call_id)

  • View recent call history for an agent

  • Get aggregate analytics with conversion rates, top objections, and AI recommendations

The analytics include AI-generated recommendations for improving your calls.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
call_idNoGet full details for a specific call.
agent_idNoFilter calls by voice agent.
statusNoFilter by call status.
lastNoReturn last N calls. Default 10, max 100.
include_analyticsNoInclude aggregate metrics: conversion rate, avg duration, top objections, best call times, AI recommendations. Default false.

Implementation Reference

  • Main handler function registerVoraCalls that registers the 'vora_calls' tool on the MCP server. The handler queries call history, results, and analytics via GET /v1/agent-api/calls (list) or GET /v1/agent-api/calls/{call_id} (detail).
    export function registerVoraCalls(server: McpServer): void {
      server.tool(
        "vora_calls",
        `Query your call history, results, and analytics. Use to:
    - Check the result of a specific call (by call_id)
    - View recent call history for an agent
    - Get aggregate analytics with conversion rates, top objections, and AI recommendations
    
    The analytics include AI-generated recommendations for improving your calls.`,
        {
          call_id: z
            .string()
            .optional()
            .describe("Get full details for a specific call."),
          agent_id: z
            .string()
            .optional()
            .describe("Filter calls by voice agent."),
          status: z
            .enum([
              "dialing",
              "in_progress",
              "completed",
              "failed",
              "no_answer",
              "voicemail",
            ])
            .optional()
            .describe("Filter by call status."),
          last: z
            .number()
            .optional()
            .describe("Return last N calls. Default 10, max 100."),
          include_analytics: z
            .boolean()
            .optional()
            .describe(
              "Include aggregate metrics: conversion rate, avg duration, top objections, best call times, AI recommendations. Default false."
            ),
        },
        async (params) => {
          const client = getApiClient();
    
          try {
            // Single call detail
            if (params.call_id) {
              const call = await client.get<CallDetailResponse>(
                `/v1/agent-api/calls/${params.call_id}`
              );
    
              const lines: string[] = [
                `Call ${call.call_id} — ${call.status}`,
                `Phone: ${call.phone}`,
                `Duration: ${call.duration_seconds}s`,
                `Outcome: ${call.outcome}`,
                `Lead Score: ${call.outcome_details.lead_score}/100`,
                `Interested: ${call.outcome_details.interested}`,
                `Sentiment: ${call.outcome_details.sentiment}`,
                `Next Step: ${call.outcome_details.next_step}`,
              ];
    
              if (call.outcome_details.meeting_time)
                lines.push(`Meeting: ${call.outcome_details.meeting_time}`);
              if (call.outcome_details.objections_raised.length > 0)
                lines.push(
                  `Objections: ${call.outcome_details.objections_raised.join(", ")}`
                );
              lines.push(`Cost: ${call.cost_usdc} USDC`);
              lines.push(`Transcript: ${call.transcript_url}`);
              lines.push(`Recording: ${call.recording_url}`);
    
              if (call.learnings.length > 0) {
                lines.push(`\nLearnings:`);
                call.learnings.forEach((l) => lines.push(`  - ${l}`));
              }
    
              return {
                content: [{ type: "text" as const, text: lines.join("\n") }],
              };
            }
    
            // Call list with optional analytics
            const queryParams = new URLSearchParams();
            if (params.agent_id) queryParams.set("agent_id", params.agent_id);
            if (params.status) queryParams.set("status", params.status);
            if (params.last) queryParams.set("last", String(params.last));
            if (params.include_analytics)
              queryParams.set("include_analytics", "true");
    
            const response = await client.get<CallsResponse>(
              `/v1/agent-api/calls?${queryParams.toString()}`
            );
    
            const lines: string[] = [`${response.calls.length} calls found:\n`];
    
            for (const call of response.calls) {
              lines.push(
                `  ${call.call_id} | ${call.phone} | ${call.status} | ${call.outcome} | Score: ${call.lead_score} | ${call.duration_seconds}s | ${call.cost_usdc} USDC | ${call.created_at}`
              );
            }
    
            if (response.analytics) {
              const a = response.analytics;
              lines.push(`\n--- Analytics ---`);
              lines.push(`Total Calls: ${a.total_calls}`);
              lines.push(
                `Conversion Rate: ${(a.conversion_rate * 100).toFixed(1)}%`
              );
              lines.push(`Avg Duration: ${a.avg_duration_seconds}s`);
              lines.push(`Avg Cost/Call: ${a.avg_cost_per_call}`);
              lines.push(`Top Objections: ${a.top_objections.join(", ")}`);
              lines.push(`Best Time to Call: ${a.best_time_to_call}`);
              lines.push(`\nRecommendations:`);
              a.recommendations.forEach((r) => lines.push(`  - ${r}`));
            }
    
            return {
              content: [{ type: "text" as const, text: lines.join("\n") }],
            };
          } catch (error) {
            return {
              content: [
                {
                  type: "text" as const,
                  text: `Query error: ${error instanceof Error ? error.message : String(error)}`,
                },
              ],
              isError: true,
            };
          }
        }
      );
    }
  • TypeScript interfaces (CallSummary, CallsResponse, CallDetailResponse) defining the schema for API responses about calls.
    interface CallSummary {
      call_id: string;
      phone: string;
      status: string;
      outcome: string;
      lead_score: number;
      duration_seconds: number;
      cost_usdc: string;
      created_at: string;
    }
    
    interface CallsResponse {
      calls: CallSummary[];
      analytics?: {
        total_calls: number;
        conversion_rate: number;
        avg_duration_seconds: number;
        avg_cost_per_call: string;
        top_objections: string[];
        best_time_to_call: string;
        recommendations: string[];
      };
    }
    
    interface CallDetailResponse {
      call_id: string;
      status: string;
      phone: string;
      duration_seconds: number;
      outcome: string;
      outcome_details: {
        interested: boolean;
        budget_confirmed?: boolean;
        decision_maker?: boolean;
        next_step: string;
        meeting_time?: string;
        objections_raised: string[];
        sentiment: string;
        lead_score: number;
      };
      transcript_url: string;
      recording_url: string;
      cost_usdc: string;
      learnings: string[];
      created_at: string;
    }
  • Zod input schema for the 'vora_calls' tool: call_id (optional string), agent_id (optional string), status (optional enum), last (optional number), include_analytics (optional boolean).
      call_id: z
        .string()
        .optional()
        .describe("Get full details for a specific call."),
      agent_id: z
        .string()
        .optional()
        .describe("Filter calls by voice agent."),
      status: z
        .enum([
          "dialing",
          "in_progress",
          "completed",
          "failed",
          "no_answer",
          "voicemail",
        ])
        .optional()
        .describe("Filter by call status."),
      last: z
        .number()
        .optional()
        .describe("Return last N calls. Default 10, max 100."),
      include_analytics: z
        .boolean()
        .optional()
        .describe(
          "Include aggregate metrics: conversion rate, avg duration, top objections, best call times, AI recommendations. Default false."
        ),
    },
  • Import of registerVoraCalls from ./vora-calls.js in the tools index module.
    import { registerVoraCalls } from "./vora-calls.js";
    import { registerVoraUpdateAgent } from "./vora-update-agent.js";
    
    export function registerTools(server: McpServer): void {
      registerVoraRegister(server);
      registerVoraCreateAgent(server);
      registerVoraCall(server);
      registerVoraCalls(server);
  • Registration call: registerVoraCalls(server) called from registerTools(), which is called from src/index.ts.
    registerVoraCalls(server);
    registerVoraUpdateAgent(server);
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description must convey behavior. It describes a read-only query operation, mentions AI-generated recommendations, and implies no side effects. Could explicitly state non-destructive nature, but sufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Efficiently uses bullet points, front-loads purpose. Every sentence adds value. No redundant or vague wording.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers main use cases and analytics feature. No output schema, but description hints at return content. Could mention defaults (e.g., last=10) or pagination, but adequate for typical usage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Input schema has 100% coverage; all parameters have descriptions. Description maps to some params (call_id, agent_id, include_analytics) but adds little extra meaning beyond listing them. Baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states the tool queries call history, results, and analytics. Lists specific use cases (specific call, recent history, aggregate analytics). Distinguishes from sibling 'vora_call' (singular) by covering multiple calls and analytics.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit scenarios (by call_id, agent_id, analytics). Lacks comparison with siblings like 'vora_call' for single-call vs. list/analytics. Still offers clear context for when to use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/stefanstojanovicstefa-creator/vora-voice-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server