Skip to main content
Glama

run_group_audit

Read-onlyIdempotent

Analyze WhatsApp group memberships to identify overlapping members, members with no direct chat, and members not in your contacts.

Instructions

Run a combined group-membership audit (overlaps, no direct chat, not in contacts).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
group_limitNoHow many groups to scan
refresh_group_infoNoFetch live group metadata before analysis
overlap_min_shared_groupsNoThreshold for overlap list
min_shared_groupsNoThreshold for no-direct-chat and not-in-contacts lists

Implementation Reference

  • The runGroupAudit method on WhatsAppService: builds a group membership audit matrix, then filters for overlaps, members without direct chat, and members not in contacts, returning summary counts and detailed lists.
    async runGroupAudit(
      groupLimit = 200,
      refreshGroupInfo = false,
      overlapMinSharedGroups = 2,
      minSharedGroups = 1,
    ): Promise<{
      summary: {
        groupsProcessed: number;
        totalMembers: number;
        overlapCount: number;
        withoutDirectChatCount: number;
        notInContactsCount: number;
      };
      overlaps: Array<{
        canonicalId: string;
        ids: string[];
        name: string | null;
        pushname: string | null;
        number: string | null;
        groupCount: number;
        groups: Array<{ id: string; name: string }>;
        hasDirectChat: boolean;
        inContacts: boolean;
      }>;
      withoutDirectChat: Array<{
        canonicalId: string;
        ids: string[];
        name: string | null;
        pushname: string | null;
        number: string | null;
        groupCount: number;
        groups: Array<{ id: string; name: string }>;
        hasDirectChat: boolean;
        inContacts: boolean;
      }>;
      notInContacts: Array<{
        canonicalId: string;
        ids: string[];
        name: string | null;
        pushname: string | null;
        number: string | null;
        groupCount: number;
        groups: Array<{ id: string; name: string }>;
        hasDirectChat: boolean;
        inContacts: boolean;
      }>;
    }> {
      const matrix = await this.buildGroupAuditMatrix(
        groupLimit,
        refreshGroupInfo,
      );
      const overlapThreshold = Math.max(2, overlapMinSharedGroups);
      const baseThreshold = Math.max(1, minSharedGroups);
      const overlaps = matrix.members.filter(
        (m) => m.groupCount >= overlapThreshold,
      );
      const withoutDirectChat = matrix.members.filter(
        (m) => !m.hasDirectChat && m.groupCount >= baseThreshold,
      );
      const notInContacts = matrix.members.filter(
        (m) => !m.inContacts && m.groupCount >= baseThreshold,
      );
    
      return {
        summary: {
          groupsProcessed: matrix.groupsProcessed,
          totalMembers: matrix.members.length,
          overlapCount: overlaps.length,
          withoutDirectChatCount: withoutDirectChat.length,
          notInContactsCount: notInContacts.length,
        },
        overlaps,
        withoutDirectChat,
        notInContacts,
      };
    }
  • Registration of the 'run_group_audit' tool via server.tool() with Zod schema params and handler calling whatsappService.runGroupAudit().
    server.tool(
      "run_group_audit",
      "Run a combined group-membership audit (overlaps, no direct chat, not in contacts).",
      {
        group_limit: z
          .number()
          .int()
          .positive()
          .optional()
          .default(200)
          .describe("How many groups to scan"),
        refresh_group_info: z
          .boolean()
          .optional()
          .default(false)
          .describe("Fetch live group metadata before analysis"),
        overlap_min_shared_groups: z
          .number()
          .int()
          .positive()
          .optional()
          .default(2)
          .describe("Threshold for overlap list"),
        min_shared_groups: z
          .number()
          .int()
          .positive()
          .optional()
          .default(1)
          .describe("Threshold for no-direct-chat and not-in-contacts lists"),
      },
      async ({
        group_limit,
        refresh_group_info,
        overlap_min_shared_groups,
        min_shared_groups,
      }): Promise<CallToolResult> => {
        try {
          const result = await whatsappService.runGroupAudit(
            group_limit,
            refresh_group_info,
            overlap_min_shared_groups,
            min_shared_groups,
          );
          return {
            content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
          };
        } catch (error: any) {
          log.error("Error in run_group_audit tool:", error);
          return {
            content: [
              {
                type: "text",
                text: `Error running group audit: ${error?.message || String(error)}`,
              },
            ],
            isError: true,
          };
        }
      },
    );
  • Input schema for run_group_audit: group_limit (default 200), refresh_group_info (default false), overlap_min_shared_groups (default 2), min_shared_groups (default 1).
    {
      group_limit: z
        .number()
        .int()
        .positive()
        .optional()
        .default(200)
        .describe("How many groups to scan"),
      refresh_group_info: z
        .boolean()
        .optional()
        .default(false)
        .describe("Fetch live group metadata before analysis"),
      overlap_min_shared_groups: z
        .number()
        .int()
        .positive()
        .optional()
        .default(2)
        .describe("Threshold for overlap list"),
      min_shared_groups: z
        .number()
        .int()
        .positive()
        .optional()
        .default(1)
        .describe("Threshold for no-direct-chat and not-in-contacts lists"),
    },
  • buildGroupAuditMatrix helper: lists groups, fetches participants, builds a member->groups map, and returns the matrix used by runGroupAudit (and other audit methods).
    private async buildGroupAuditMatrix(
      groupLimit = 200,
      refreshGroupInfo = false,
    ): Promise<{
      groupsProcessed: number;
      members: Array<{
        canonicalId: string;
        ids: string[];
        name: string | null;
        pushname: string | null;
        number: string | null;
        groupCount: number;
        groups: Array<{ id: string; name: string }>;
        hasDirectChat: boolean;
        inContacts: boolean;
      }>;
    }> {
      let groups: SimpleChat[] = [];
      try {
        groups = await this.listGroups(groupLimit, false);
      } catch (error) {
        log.warn(
          { err: error, groupLimit, refreshGroupInfo },
          "Group audit: failed to list groups",
        );
        return {
          groupsProcessed: 0,
          members: [],
        };
      }
      const memberMap = new Map<
        string,
        {
          canonicalId: string;
          ids: Set<string>;
          groups: Array<{ id: string; name: string }>;
        }
      >();
    
      for (const group of groups) {
        const groupId = String(group.id || "").trim();
        if (!groupId.endsWith("@g.us")) continue;
        let participants: string[] = [];
        try {
          participants = await this.getGroupParticipantJids(
            groupId,
            refreshGroupInfo,
          );
        } catch (error) {
          log.warn(
            { err: error, groupId, refreshGroupInfo },
            "Group audit: failed to load participants",
          );
          continue;
        }
        for (const participantRaw of participants) {
          const participant = this.normalizeJid(participantRaw);
          if (!participant) continue;
          const canonicalId = this.resolveCanonicalChatId(participant);
          const existing = memberMap.get(canonicalId) || {
            canonicalId,
            ids: new Set<string>(),
            groups: [],
          };
          existing.ids.add(participant);
          if (!existing.groups.some((g) => g.id === groupId)) {
            existing.groups.push({
              id: groupId,
              name: group.name || groupId,
            });
          }
          memberMap.set(canonicalId, existing);
        }
      }
    
      const members = Array.from(memberMap.values())
        .map((entry) => {
          const profile = this.buildMemberDisplay(entry.canonicalId);
          return {
            canonicalId: entry.canonicalId,
            ids: Array.from(entry.ids.values()).sort(),
            name: profile.name,
            pushname: profile.pushname,
            number: profile.number,
            groupCount: entry.groups.length,
            groups: entry.groups.sort((a, b) => a.name.localeCompare(b.name)),
            hasDirectChat: this.hasDirectChatForParticipant(entry.canonicalId),
            inContacts: this.isParticipantInContacts(entry.canonicalId),
          };
        })
        .sort(
          (a, b) =>
            b.groupCount - a.groupCount ||
            String(a.name || "").localeCompare(String(b.name || "")),
        );
    
      return {
        groupsProcessed: groups.length,
        members,
      };
    }
  • src/server.ts:165-169 (registration)
    Tool metadata registration in server.ts marking run_group_audit as readOnly, idempotent, and openWorld.
    run_group_audit: {
      readOnlyHint: true,
      idempotentHint: true,
      openWorldHint: true,
    },
Behavior5/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and openWorldHint=true, so safety is fully disclosed. The description does not contradict any annotation; it simply describes the combined audit operation, which is consistent with a read-only, idempotent call.

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?

A single 12-word sentence that is front-loaded and contains no fluff. Every part is essential for understanding the tool's purpose.

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

Completeness5/5

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

Given the lack of an output schema and high coverage in annotations and parameter descriptions, the description sufficiently explains the tool's function. It tells the agent what will be audited (overlaps, no direct chat, not in contacts) which is adequate for use.

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

Parameters4/5

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

Schema description coverage is 100%, so the baseline is 3. The tool description adds context by naming the three audit types ('overlaps, no direct chat, not in contacts'), helping to conceptually link the parameters to these sub-audits, though it doesn't detail individual parameter behavior.

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?

The description uses a specific verb 'Run' and a specific noun 'combined group-membership audit', listing three components: overlaps, no direct chat, not in contacts. This clearly distinguishes it from sibling tools like find_members_not_in_contacts and find_members_without_direct_chat.

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?

The description implies this tool runs a combined audit covering three areas, which differentiates it from more specific sibling tools. However, it does not explicitly state when to use this tool versus running the individual audits separately, and no exclusion criteria are mentioned.

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/loglux/whatsapp-mcp-stream'

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