{
    "generated_by": "php artisan mcp:manifest",
    "transport": "streamable-http",
    "root": {
        "path": "/mcp",
        "name": "Everything you granted",
        "summary": "One URL carrying every tool this connection reaches. The consent screen decides what that is; the per-domain URLs below are for deliberately narrow connections.",
        "tool_count": 265,
        "tools_hash": "f478398c"
    },
    "protocol_versions": [
        "2025-11-25",
        "2025-06-18",
        "2025-03-26"
    ],
    "auth": {
        "oauth": {
            "discovery": "/.well-known/oauth-authorization-server",
            "registration": "/oauth/register",
            "dynamic_client_registration": true,
            "pkce": "S256",
            "capabilities": [
                {
                    "scope": "mcp:overview",
                    "label": "Overview and analytics",
                    "help": "How the business is doing \u2014 calls, messages, spend, and what needs attention.",
                    "default": true,
                    "servers": [
                        "overview"
                    ]
                },
                {
                    "scope": "mcp:calls",
                    "label": "Calls",
                    "help": "Call history, recordings, transcripts and Call Studio scripts.",
                    "default": true,
                    "servers": [
                        "calls"
                    ]
                },
                {
                    "scope": "mcp:routing",
                    "label": "Call routing",
                    "help": "Routing rules, ring groups, working hours and forwarding targets.",
                    "default": true,
                    "servers": [
                        "routing"
                    ]
                },
                {
                    "scope": "mcp:numbers",
                    "label": "Phone numbers",
                    "help": "What you own, what is available, what one costs, and how it is configured.",
                    "default": true,
                    "servers": [
                        "numbers"
                    ]
                },
                {
                    "scope": "mcp:meetings",
                    "label": "Meetings",
                    "help": "See and schedule meetings, and invite people to them.",
                    "default": true,
                    "servers": [
                        "meetings"
                    ]
                },
                {
                    "scope": "mcp:builders",
                    "label": "Call flows and chat flows",
                    "help": "Build and edit your IVRs and WhatsApp conversation flows \u2014 as drafts.",
                    "default": true,
                    "servers": [
                        "ivr",
                        "flows"
                    ]
                },
                {
                    "scope": "mcp:data",
                    "label": "Data tables",
                    "help": "The tables your business defined for itself \u2014 read records, save them, shape fields, run reports \u2014 and the business rules (limits, fees, eligibility, opening hours) your flows enforce. Flows and IVRs read the same tables and the same rules.",
                    "default": true,
                    "servers": [
                        "data"
                    ]
                },
                {
                    "scope": "mcp:studio",
                    "label": "Voice and audio",
                    "help": "Voices, and generating spoken prompts for your call flows.",
                    "default": true,
                    "servers": [
                        "studio"
                    ]
                },
                {
                    "scope": "mcp:contacts",
                    "label": "Contacts",
                    "help": "Your contact book and groups.",
                    "default": true,
                    "servers": [
                        "contacts"
                    ]
                },
                {
                    "scope": "mcp:agents",
                    "label": "AI agents",
                    "help": "Your AI agents, what they know, how they behave, and what they have done.",
                    "default": true,
                    "servers": [
                        "agents"
                    ]
                },
                {
                    "scope": "mcp:commerce",
                    "label": "Orders and shop",
                    "help": "Customer orders, products, brands and categories.",
                    "default": true,
                    "servers": [
                        "orders",
                        "shop"
                    ]
                },
                {
                    "scope": "mcp:support",
                    "label": "Support tickets",
                    "help": "Tickets and your knowledge base.",
                    "default": true,
                    "servers": [
                        "tickets",
                        "kb"
                    ]
                },
                {
                    "scope": "mcp:accounts",
                    "label": "Connected accounts",
                    "help": "Which WhatsApp numbers, social profiles and mailboxes are connected, and what each can do.",
                    "default": true,
                    "servers": [
                        "accounts"
                    ]
                },
                {
                    "scope": "mcp:approvals",
                    "label": "Approvals",
                    "help": "Decisions people in your business are waiting on \u2014 what is pending, what was decided, and why. Answering one is separate.",
                    "default": true,
                    "servers": [
                        "approvals"
                    ]
                },
                {
                    "scope": "mcp:payments",
                    "label": "Payments",
                    "help": "Money your customers pay you: what has been asked for, what arrived, and each payment's history. Asking for money and refunding it need the spending tick as well.",
                    "default": true,
                    "servers": [
                        "payments"
                    ]
                },
                {
                    "scope": "mcp:automations",
                    "label": "Automations",
                    "help": "What your business has set up to happen on its own \u2014 what reacts to an event, what runs on a rhythm, and a log of what actually fired. Changing any of it is separate.",
                    "default": true,
                    "servers": [
                        "automations"
                    ]
                },
                {
                    "scope": "mcp:alerts",
                    "label": "Alerts and service levels",
                    "help": "How your business watches itself: what it has asked to be told about, how quickly it promises to do things, what it checks before letting something through, and a log of everything that fired \u2014 including anything that reached nobody. Changing any of it is separate.",
                    "default": true,
                    "servers": [
                        "alerts"
                    ]
                },
                {
                    "scope": "mcp:operations",
                    "label": "Operations",
                    "help": "The named things your business can do \u2014 create a booking, register a customer, process a refund. Seeing what they are is included; DOING one needs the ticks its own steps call for.",
                    "default": true,
                    "servers": [
                        "operations"
                    ]
                },
                {
                    "scope": "mcp:navigate",
                    "label": "Finding things",
                    "help": "Where pages and settings live in the app, so it can point you to them.",
                    "default": true,
                    "servers": [
                        "navigate",
                        "content"
                    ]
                },
                {
                    "scope": "mcp:messaging",
                    "label": "Messaging",
                    "help": "Templates, sender IDs, campaigns and your message history. Sending is separate.",
                    "default": false,
                    "servers": [
                        "messaging"
                    ]
                },
                {
                    "scope": "mcp:inbox",
                    "label": "Inbox",
                    "help": "Read your customer conversations across WhatsApp, SMS and social.",
                    "default": false,
                    "servers": [
                        "inbox"
                    ]
                },
                {
                    "scope": "mcp:comments",
                    "label": "Comments",
                    "help": "Read comments on your Facebook, Instagram and TikTok posts.",
                    "default": false,
                    "servers": [
                        "comments"
                    ]
                },
                {
                    "scope": "mcp:groups",
                    "label": "WhatsApp groups",
                    "help": "Groups your business runs from its WhatsApp number.",
                    "default": false,
                    "servers": [
                        "groups"
                    ]
                }
            ],
            "elevations": [
                {
                    "scope": "mcp:publish",
                    "label": "Publish things",
                    "help": "Make a call flow answer real calls, a chat flow reach real customers, or a routing change go live.",
                    "default": false
                },
                {
                    "scope": "mcp:send",
                    "label": "Send messages and place calls",
                    "help": "Send an SMS or WhatsApp to a real person, reply to a customer, or ring a phone.",
                    "default": false
                },
                {
                    "scope": "mcp:spend",
                    "label": "Start purchases and ask customers to pay",
                    "help": "Begin buying a number or topping up, and ask your customers to pay you. You still approve every payment yourself, on your phone, and a refund still waits for somebody in your business to say yes.",
                    "default": false
                },
                {
                    "scope": "mcp:delete",
                    "label": "Delete things",
                    "help": "Permanently remove flows, audio, contacts and tickets.",
                    "default": false
                },
                {
                    "scope": "mcp:write",
                    "label": "Save and change records",
                    "help": "Create, update and upsert rows in your data tables, and save reports.",
                    "default": false
                },
                {
                    "scope": "mcp:shape",
                    "label": "Change tables and fields",
                    "help": "Create tables, add, rename, retype or remove fields. This changes what every screen and flow sees.",
                    "default": false
                },
                {
                    "scope": "mcp:automate",
                    "label": "Set up things that run without you",
                    "help": "Create or change an automation: something that reacts to an event on its own, or runs on a rhythm \u2014 including sending your business's data to an address outside it.",
                    "default": false
                },
                {
                    "scope": "mcp:approve",
                    "label": "Answer approvals for you",
                    "help": "Approve or reject a request somebody is waiting on \u2014 releasing a discount, a refund or a payout that was deliberately held for a person to sign off.",
                    "default": false
                }
            ]
        },
        "bearer": {
            "header": "Authorization: Bearer momo_mcp_\u2026",
            "issued_at": "/app/api-credentials"
        }
    },
    "presets": [
        {
            "key": "read",
            "label": "Read only",
            "description": "It can look, and change nothing."
        },
        {
            "key": "builder",
            "label": "Builder",
            "description": "It can build and edit drafts. It cannot publish, and it cannot spend money."
        },
        {
            "key": "full",
            "label": "Full",
            "description": "It can publish flows to customers and start payments."
        }
    ],
    "server_count": 28,
    "tool_count": 282,
    "unique_tool_count": 265,
    "servers": [
        {
            "key": "ivr",
            "name": "IVR",
            "summary": "Build and edit call flows: read the graph, apply node operations, validate, simulate, version and assign to numbers.",
            "path": "/mcp/v1/ivr",
            "module": "calls",
            "instructions": "Build and edit the call flows (IVRs) that answer this business's phone lines. \n\nWork in this order: list_ivr_flows to see what exists, get_ivr_flow to read one, get_ivr_catalog ONCE before your first edit \u2014 it gives the only node kinds that exist, the exact fields each allows, and the real resource ids on this account. Then apply_ivr_ops. \n\nEverything you write is a DRAFT. You cannot make a flow answer real calls; the user publishes. Say so rather than implying a change is live. \n\nThe validator is strict and its messages are exact \u2014 when a batch is rejected, read the message, fix that specific thing and retry rather than guessing. Pass expected_version from get_ivr_flow so you never overwrite an edit somebody made while you were thinking. \n\nMany of these businesses serve Kiswahili-speaking callers; write prompts in the language the user asks for, and if they are writing to you in Kiswahili, default to Kiswahili. \n\nHow it LOOKS is part of the job. Give every node a \"name\" \u2014 the builder prints it on the box, so a flow of unnamed nodes reads as \"menu / decision / play\". format_ivr_layout takes direction, spacing and only_ids, so \"make it read left to right\", \"it looks cramped\" and \"straighten those three\" are one call each rather than invented coordinates. \n\nsimulate_ivr_flow walks the PUBLISHED version with a script of key presses and returns the transcript \u2014 it cannot walk an unpublished draft, so say which version it walked. list_ivr_versions shows the history and rollback_ivr_flow restores a version into the draft only; nothing changes for callers until the user publishes. get_ivr_data_tables names the tables the flow reads and writes; pin_ivr_data_table only changes what the View data page shows. \n\nSeveral node kinds point at something outside the flow \u2014 a queue, a schedule, a payment or HTTP profile. list_ivr_resources shows what exists and upsert_ivr_resource creates it, so \"hold them for the next free agent\" is a flow you can finish rather than a thing to refuse. Never invent one of those ids.",
            "tools": [
                {
                    "name": "get_ivr_call_steps",
                    "root_name": "get_ivr_call_steps",
                    "version": "1828ab0c",
                    "description": "The steps one caller actually went through inside a call flow: which node they reached, what they pressed, and where the call ended. Use this to diagnose a real call rather than re-reading the flow definition \u2014 a flow can validate perfectly and still lose callers.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "calls.ivr-steps.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_ivr_call_steps arguments",
                        "type": "object",
                        "properties": {
                            "call_reference": {
                                "type": "string",
                                "description": "The call's room name, from the calls tools."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max steps to return (default 50, max 500)."
                            },
                            "order": {
                                "type": "string",
                                "description": "Step order. Default asc, which reads the way the call happened.",
                                "enum": [
                                    "asc",
                                    "desc"
                                ]
                            }
                        },
                        "required": [
                            "call_reference"
                        ]
                    }
                },
                {
                    "name": "get_ivr_catalog",
                    "root_name": "get_ivr_catalog",
                    "version": "ff05c216",
                    "description": "The IVR building reference: every node kind you may use, the exact fields each one allows, which action dialect it speaks, and the resource ids that actually exist on this account (agents, models, voices, SMS senders, audio assets). ALWAYS call this before your first apply_ivr_ops \u2014 inventing a field or an id is the most common way a batch is rejected.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_ivr_catalog arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "get_ivr_data_tables",
                    "root_name": "get_ivr_data_tables",
                    "version": "a4e69064",
                    "description": "The data tables a call flow reads or writes \u2014 which node touches which table and how \u2014 plus any tables pinned to its View data page. Use it before editing a data node, or when the user asks where a flow keeps its records; get_data_table_schema then gives the columns.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_ivr_data_tables arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow whose tables you want."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "get_ivr_flow",
                    "root_name": "get_ivr_flow",
                    "version": "ab01f7b8",
                    "description": "Read one call flow in full: every node, the entry point, the current version number, and what the IVR engine validator says about it right now. Read this before proposing edits, and pass the version back to apply_ivr_ops.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_ivr_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow id, from list_ivr_flows."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "lint_ivr_expression",
                    "root_name": "lint_ivr_expression",
                    "version": "7b4acfe9",
                    "description": "Check a single branch condition or variable path against the flow expression language, without touching a flow. Use it before putting an expression into a node \u2014 a rejected batch tells you the graph was refused, this tells you which expression and why.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "lint_ivr_expression arguments",
                        "type": "object",
                        "properties": {
                            "expression": {
                                "type": "string",
                                "description": "The expression to check, e.g. vars.balance > 1000 \u2014 the same text a branch condition holds."
                            },
                            "kind": {
                                "type": "string",
                                "description": "\"expression\" for a condition (default); \"assignment_path\" for the left-hand side of a set, like vars.customer.name.",
                                "enum": [
                                    "expression",
                                    "assignment_path"
                                ]
                            }
                        },
                        "required": [
                            "expression"
                        ]
                    }
                },
                {
                    "name": "list_ivr_assignments",
                    "root_name": "list_ivr_assignments",
                    "version": "e02dc8f3",
                    "description": "Which phone numbers answer with which call flow. Read this before saying a flow is live: publishing makes a flow available, assigning it to a number is what makes a caller hear it, and the two are separate steps.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_ivr_assignments arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "list_ivr_flows",
                    "root_name": "list_ivr_flows",
                    "version": "d1ab45b8",
                    "description": "List the call (IVR) flows on this account: name, status, size, whether it has unpublished changes, and when it last changed. Start here before editing anything.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_ivr_flows arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "Filter by status: draft, active or paused."
                            },
                            "search": {
                                "type": "string",
                                "description": "Filter by name."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max flows to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_ivr_resources",
                    "root_name": "list_ivr_resources",
                    "version": "ece02c64",
                    "description": "List the things a call-flow node can point at: queues (queue.queueId), schedules and holiday calendars (decision conditions), SMS / email / payment / speech / webhook profiles, HTTP profiles (http_request.profileId, webhook_notify.profileId), database connections (sql_query.datasourceId), recorded audio (a prompt's assetId), and the do-not-call and VIP lists. Call this before writing any node that carries an id \u2014 the ids are real and must be copied, never invented.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_ivr_resources arguments",
                        "type": "object",
                        "properties": {
                            "kind": {
                                "type": "string",
                                "description": "Which family to list. queues for a queue node, http_profiles for http_request and webhook_notify, datasources for sql_query, payment_profiles for pay, audio_assets for a prompt that plays a recording instead of speaking, and so on.",
                                "enum": [
                                    "queues",
                                    "schedules",
                                    "holiday_calendars",
                                    "experiments",
                                    "sms_profiles",
                                    "email_profiles",
                                    "payment_profiles",
                                    "stt_profiles",
                                    "webhook_profiles",
                                    "http_profiles",
                                    "datasources",
                                    "audio_assets",
                                    "dnc",
                                    "vip"
                                ]
                            },
                            "refresh": {
                                "type": "boolean",
                                "description": "Re-read from the telephony service first. Slower; use it when the user says they just created something and you cannot see it."
                            }
                        },
                        "required": [
                            "kind"
                        ]
                    }
                },
                {
                    "name": "list_ivr_versions",
                    "root_name": "list_ivr_versions",
                    "version": "bb5f5d85",
                    "description": "The version history of a call flow, newest first: each publish and each rollback, who did it and when, and which snapshot the phone system is running. Use it before rollback_ivr_flow, or when the user asks what changed and when.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_ivr_versions arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow whose history you want."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "simulate_ivr_flow",
                    "root_name": "simulate_ivr_flow",
                    "version": "b8034762",
                    "description": "Walk a published call flow the way a caller would and get back the step-by-step transcript the web simulator shows \u2014 prompts played, digits taken, where the call ended. No phone rings. Pass digits_json for a quick keypad walk, or script_json for the full timeline (speech, hangups, transfer outcomes). The flow must have been published at least once.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.simulate"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "simulate_ivr_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to simulate."
                            },
                            "digits_json": {
                                "type": "string",
                                "description": "A JSON array of key presses in order, e.g. [\"1\",\"3\"]. Each is pressed two seconds after the last."
                            },
                            "script_json": {
                                "type": "string",
                                "description": "Instead of digits: the full simulator script {\"ctx\":{...},\"timeline\":{\"inputs\":[{\"at\":ms,\"type\":\"dtmf|speech|hangup|silence\",...}],\"transfers\":[...],\"outbound\":{...}}}."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "validate_ivr_flow",
                    "root_name": "validate_ivr_flow",
                    "version": "c43a91e7",
                    "description": "Check a flow against the real IVR engine validator without changing anything. Pass ops_json to test a batch BEFORE applying it \u2014 useful when you are unsure and would rather not write a draft you have to undo.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "validate_ivr_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to check."
                            },
                            "ops_json": {
                                "type": "string",
                                "description": "Optional {\"ops\":[...]} to test against the flow without writing."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "apply_ivr_ops",
                    "root_name": "apply_ivr_ops",
                    "version": "370cd99e",
                    "description": "Build or edit a call flow by applying graph operations to its draft. The whole batch is checked by the real IVR engine validator before anything is written, and the result appears immediately on the canvas if the user has it open. The flow stays a DRAFT \u2014 publishing is the user's.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "apply_ivr_ops arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to edit, from list_ivr_flows."
                            },
                            "ops_json": {
                                "type": "string",
                                "description": "A JSON object string {\"ops\":[...]}. Each op is {\"op\":\"add_node\",\"node\":{...}} | {\"op\":\"update_node\",\"id\":\"...\",\"set\":{...}} | {\"op\":\"remove_node\",\"id\":\"...\"} | {\"op\":\"set_entry\",\"id\":\"...\"}. Max 30. Call get_ivr_catalog first for the node kinds and fields."
                            },
                            "expected_version": {
                                "type": "integer",
                                "description": "The version you read in get_ivr_flow. Strongly recommended: it is what stops you overwriting a change somebody else made in the meantime."
                            },
                            "auto_layout": {
                                "type": "boolean",
                                "description": "Arrange the canvas as a tidy tree after applying (default true). Set false only if you are placing nodes yourself with format_ivr_layout."
                            }
                        },
                        "required": [
                            "flow_id",
                            "ops_json"
                        ]
                    }
                },
                {
                    "name": "assign_ivr_to_number",
                    "root_name": "assign_ivr_to_number",
                    "version": "0926e0e3",
                    "description": "Make a phone number answer with a call flow, or clear it. This reaches REAL callers the moment it succeeds \u2014 the next person to ring that number hears the new flow. The flow must be published first. Pass flow_id: null to clear a number.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "assign_ivr_to_number arguments",
                        "type": "object",
                        "properties": {
                            "number": {
                                "type": "string",
                                "description": "The phone number in international format, e.g. +255752771650. From list_ivr_assignments or list_my_numbers."
                            },
                            "flow_id": {
                                "type": "integer",
                                "description": "The call flow to put on the number. Omit or pass null to clear the number so it answers with no flow."
                            },
                            "mode": {
                                "type": "string",
                                "description": "How the number runs the flow. Leave unset to keep what it already uses.",
                                "enum": [
                                    "inherit_flow",
                                    "traditional",
                                    "ai_assisted"
                                ]
                            }
                        },
                        "required": [
                            "number"
                        ]
                    }
                },
                {
                    "name": "create_ivr_flow",
                    "root_name": "create_ivr_flow",
                    "version": "a6159aad",
                    "description": "Create a new call flow, optionally building its whole graph in the same call by passing operations. It appears on the user's flow list immediately, as a draft.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.create"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_ivr_flow arguments",
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "What to call the flow, e.g. \"Main line\"."
                            },
                            "ops_json": {
                                "type": "string",
                                "description": "Optional {\"ops\":[...]} to build the graph in the same call. Same shape as apply_ivr_ops."
                            }
                        },
                        "required": [
                            "name"
                        ]
                    }
                },
                {
                    "name": "delete_ivr_flow",
                    "root_name": "delete_ivr_flow",
                    "version": "48a62902",
                    "description": "Delete a call flow permanently. Refuses while any phone number still routes to it, and names those numbers \u2014 deleting a flow a live number points at would drop real calls.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.delete"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "delete_ivr_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to delete."
                            },
                            "confirm": {
                                "type": "string",
                                "description": "The exact flow name, as confirmation. Ask the user before sending this."
                            },
                            "confirm_unverified": {
                                "type": "string",
                                "description": "Only when the phone system was unreachable and the user has explicitly accepted the risk: \"yes\"."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "format_ivr_layout",
                    "root_name": "format_ivr_layout",
                    "version": "4fa53b3d",
                    "description": "Tidy the canvas \u2014 arrange the nodes as a readable tree, the same as the builder's \"Format layout\" button. Call this after building or reshaping a flow, or the user opens a pile of overlapping boxes. You can steer it: direction (down the page or across it), spacing (compact / normal / roomy), and only_ids to straighten just a few nodes and leave the rest where the user put them. positions_json places every node yourself.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "format_ivr_layout arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to arrange."
                            },
                            "direction": {
                                "type": "string",
                                "description": "Which way the call reads: \"top_to_bottom\" (default, and what the builder does) or \"left_to_right\" for a wide flow with few branches.",
                                "enum": [
                                    "top_to_bottom",
                                    "left_to_right"
                                ]
                            },
                            "spacing": {
                                "type": "string",
                                "description": "How far apart: \"compact\" to fit a big flow on one screen, \"normal\" (default), or \"roomy\" when the user says it looks cramped.",
                                "enum": [
                                    "compact",
                                    "normal",
                                    "roomy"
                                ]
                            },
                            "only_ids": {
                                "type": "array",
                                "description": "Straighten just these node ids and leave every other node exactly where it is. They are placed relative to the corner they already occupy, so the rest of the canvas does not appear to move. Omit to tidy the whole flow.",
                                "items": {
                                    "type": "string"
                                }
                            },
                            "positions_json": {
                                "type": "string",
                                "description": "Optional explicit placement: a JSON object of node id => {\"x\":123,\"y\":456}. Omit it to auto-arrange, which is what you usually want. Overrides direction/spacing/only_ids."
                            },
                            "expected_version": {
                                "type": "integer",
                                "description": "The version from get_ivr_flow."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "pin_ivr_data_table",
                    "root_name": "pin_ivr_data_table",
                    "version": "ab02a647",
                    "description": "Pin a data table to a call flow's View data page so the team sees its records next to the flow. It changes what the page shows and nothing else: no node, no access for the flow. Use when the user wants a table kept in view alongside this flow.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "pin_ivr_data_table arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to pin the table to."
                            },
                            "table_id": {
                                "type": "string",
                                "description": "The data table id (uuid), from list_data_tables."
                            }
                        },
                        "required": [
                            "flow_id",
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "publish_ivr_flow",
                    "root_name": "publish_ivr_flow",
                    "version": "f4cf0d45",
                    "description": "Make a call flow LIVE on this business's phone lines. Real callers reach it immediately. Only use when the user has explicitly asked to publish \u2014 building and validating never require this.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.publish"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "publish_ivr_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to publish."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "rollback_ivr_flow",
                    "root_name": "rollback_ivr_flow",
                    "version": "0faea550",
                    "description": "Restore an earlier version of a call flow INTO THE DRAFT, replacing whatever is on the canvas now. Callers are not affected until the user publishes \u2014 this is how the IVR builder undoes a bad edit. Use when the user asks to go back to a previous version; list_ivr_versions gives the ids.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "rollback_ivr_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to roll back."
                            },
                            "version_id": {
                                "type": "integer",
                                "description": "The version id to restore, from list_ivr_versions."
                            },
                            "confirm": {
                                "type": "boolean",
                                "description": "Set true only after the user has agreed that the current draft is replaced."
                            }
                        },
                        "required": [
                            "flow_id",
                            "version_id"
                        ]
                    }
                },
                {
                    "name": "unpin_ivr_data_table",
                    "root_name": "unpin_ivr_data_table",
                    "version": "ed94d588",
                    "description": "Take a hand-pinned data table off a call flow's View data page. Only pins go: a table a node actually reads or writes stays listed until that node is removed. Use when the user no longer wants the table shown with this flow.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "unpin_ivr_data_table arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to unpin the table from."
                            },
                            "table_id": {
                                "type": "string",
                                "description": "The data table id (uuid) that was pinned."
                            }
                        },
                        "required": [
                            "flow_id",
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "update_ivr_flow",
                    "root_name": "update_ivr_flow",
                    "version": "3ffaef4a",
                    "description": "Rename a call flow, or change whether it is a draft, active or paused. Renaming is safe and reversible; pausing an active flow stops it answering, so say what you are about to do first.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_ivr_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to change."
                            },
                            "name": {
                                "type": "string",
                                "description": "A new name."
                            },
                            "status": {
                                "type": "string",
                                "description": "draft, active or paused."
                            },
                            "confirm_pause": {
                                "type": "boolean",
                                "description": "Required to pause a flow that is currently live."
                            },
                            "expected_version": {
                                "type": "integer",
                                "description": "The version from get_ivr_flow."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "upsert_ivr_resource",
                    "root_name": "upsert_ivr_resource",
                    "version": "5b20a925",
                    "description": "Create or update the resources a call-flow node points at: queues, schedules, holiday calendars, A/B experiments, SMS / email / payment / speech / webhook provider profiles, and HTTP profiles. Pass the id to change an existing one, omit it to create. Everything else about the flow stays a draft \u2014 this only makes the resource exist so a node can reference it.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "upsert_ivr_resource arguments",
                        "type": "object",
                        "properties": {
                            "kind": {
                                "type": "string",
                                "description": "What to create or change. list_ivr_resources shows what already exists.",
                                "enum": [
                                    "queues",
                                    "schedules",
                                    "holiday_calendars",
                                    "experiments",
                                    "http_profiles",
                                    "sms_profiles",
                                    "email_profiles",
                                    "payment_profiles",
                                    "stt_profiles",
                                    "webhook_profiles"
                                ]
                            },
                            "name": {
                                "type": "string",
                                "description": "What to call it. Required when creating; omit to leave an existing name alone."
                            },
                            "id": {
                                "type": "string",
                                "description": "The id of an existing resource, from list_ivr_resources. Omit to create a new one."
                            },
                            "config_json": {
                                "type": "string",
                                "description": "A JSON object of the rest of its settings. queues: maxConcurrent, priorityMode (fifo|priority), skills, holdMusicAssetId. schedules: timezone and slots are BOTH required, plus holidayCalendarId. holiday_calendars: timezone, dates. experiments: variants (at least two, each {\"name\":\"...\",\"weight\":50}), status, stickyByCaller. *_profiles: provider, config. http_profiles: method and url are required, plus headers, queryParams, timeoutMs, maxResponseChars, enabled."
                            }
                        },
                        "required": [
                            "kind"
                        ]
                    }
                }
            ]
        },
        {
            "key": "flows",
            "name": "Message flows",
            "summary": "Build and edit WhatsApp conversation flows: nodes, edges, triggers, validation, simulation and analytics.",
            "path": "/mcp/v1/flows",
            "module": "flows",
            "instructions": "Build the WhatsApp conversations this business has with its customers \u2014 menus, questions, orders, handoffs. \n\nOrder of work: list_message_flows, get_message_flow to read one, get_flow_catalog ONCE before your first edit (it gives the node kinds, the config keys and named exits each one has, and the WhatsApp limits), then apply_flow_ops. \n\nThen SIMULATE. simulate_message_flow shows exactly what a customer would see and sends nothing to anyone. A flow you have not simulated is a flow you have not checked. \n\nEverything you write is a DRAFT and reaches nobody. A published flow intercepts real customer messages ahead of the AI agent, so publishing is the user's decision, never a step you take to be helpful. \n\nDesign within WhatsApp's limits rather than against them: three buttons, ten list rows, and outside the 24-hour window only a template will send. Many of these customers write in Kiswahili \u2014 match the language the business actually uses with its customers. \n\nOnce a flow is live, get_message_flow_insights says where customers drop off, list_flow_versions shows what has been published and rollback_message_flow puts an earlier version back (it goes live at once, so it is gated like publish). get_flow_data_tables names the tables the flow reads and writes; pin_flow_data_table only changes what the View data page shows.",
            "tools": [
                {
                    "name": "diff_message_flow_versions",
                    "root_name": "diff_message_flow_versions",
                    "version": "3bee5179",
                    "description": "What changed between two versions of a message flow \u2014 steps added, removed and changed field by field, plus triggers and settings. Leave `to` out to compare a published version against the current draft, which is what somebody about to publish wants to know. Ids come from list_flow_versions.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "diff_message_flow_versions arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow whose versions you are comparing."
                            },
                            "from_version_id": {
                                "type": "integer",
                                "description": "The OLDER side, from list_flow_versions."
                            },
                            "to_version_id": {
                                "type": "integer",
                                "description": "The newer side. Leave it out to compare against the current draft."
                            }
                        },
                        "required": [
                            "flow_id",
                            "from_version_id"
                        ]
                    }
                },
                {
                    "name": "get_flow_catalog",
                    "root_name": "get_flow_catalog",
                    "version": "52d5fb1a",
                    "description": "The message-flow building reference: every node kind, the config keys it takes and what each means, the named exits (\"outs\") each one leaves by, whether it waits for a reply, and whether it can actually be published yet. Also the templating rule and the WhatsApp limits you must design within. ALWAYS read this before your first apply_flow_ops.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_flow_catalog arguments",
                        "type": "object",
                        "properties": {
                            "topic": {
                                "type": "string",
                                "description": "Ask for one deep dive instead of the whole catalog: transaction (the transaction and lock block nodes), allocate, rule, transition, approval, collect_payment or record_trigger. Omit it for the full catalog, which lists these under \"topics\"."
                            }
                        }
                    }
                },
                {
                    "name": "get_flow_data_tables",
                    "root_name": "get_flow_data_tables",
                    "version": "988ee1e7",
                    "description": "The data tables a message flow reads or writes \u2014 which node touches which table and how \u2014 plus any tables pinned to its View data page. Use it before editing a data node, or when the user asks where a flow keeps its records; get_data_table_schema then gives the columns.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_flow_data_tables arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow whose tables you want."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "get_message_flow",
                    "root_name": "get_message_flow",
                    "version": "30472a8c",
                    "description": "Read one message flow in full: nodes, edges, triggers, its version number, and what the validator currently says. Pass the version back to apply_flow_ops so you do not overwrite somebody else.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_message_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow id, from list_message_flows."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "get_message_flow_insights",
                    "root_name": "get_message_flow_insights",
                    "version": "1525064a",
                    "description": "How a message flow is performing with real customers over a period: sessions started, completed and failed, why they ended, and the drop-off per node \u2014 where conversations stop. Use it when the user asks whether a flow works, which step loses people, or what to fix first.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_message_flow_insights arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to report on."
                            },
                            "range": {
                                "type": "string",
                                "description": "How far back: \"7d\", \"30d\" (default), \"90d\", \"2w\" or a number of days, max 365."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "list_flow_versions",
                    "root_name": "list_flow_versions",
                    "version": "6ae09688",
                    "description": "The publish history of a message flow, newest first: every version that has been live, who published it, when, and which one customers are on right now. Use it before rollback_message_flow, or when the user asks what changed and when.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_flow_versions arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow whose history you want."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "list_message_flows",
                    "root_name": "list_message_flows",
                    "version": "91dff075",
                    "description": "List the WhatsApp conversation flows on this account, with status, priority, how many triggers each has and whether it is actually live for customers.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_message_flows arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "draft, active, paused or archived."
                            },
                            "search": {
                                "type": "string",
                                "description": "Filter by name."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "replay_flow_session",
                    "root_name": "replay_flow_session",
                    "version": "af81e26e",
                    "description": "Take a conversation that really happened and re-run its turns against the flow's CURRENT draft, then say where the two paths part company. Nothing is sent: every emitter is faked, the run is marked simulated and it is rolled back. Use it to answer \"would the fix have helped this customer?\" \u2014 session ids come from get_message_flow_insights.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.simulate"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "replay_flow_session arguments",
                        "type": "object",
                        "properties": {
                            "session_id": {
                                "type": "integer",
                                "description": "The session to replay."
                            },
                            "flow_id": {
                                "type": "integer",
                                "description": "Optional: refuse if the session is not on this flow."
                            }
                        },
                        "required": [
                            "session_id"
                        ]
                    }
                },
                {
                    "name": "run_flow_scenarios",
                    "root_name": "run_flow_scenarios",
                    "version": "eb984b93",
                    "description": "Run the tests written against a message flow and report which expectations held. Every provider is faked and the whole run is rolled back, so nothing is sent, charged or saved. An ENABLED test that fails also refuses the next publish \u2014 so run this before publish_message_flow and tell the user what failed.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.simulate"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "run_flow_scenarios arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow whose tests to run."
                            },
                            "scenario_id": {
                                "type": "integer",
                                "description": "Run just this one test. Leave it out to run them all."
                            },
                            "enabled_only": {
                                "type": "boolean",
                                "description": "Default true \u2014 only the tests that can refuse a publish. False runs the switched-off ones too."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "simulate_message_flow",
                    "root_name": "simulate_message_flow",
                    "version": "3fbf446f",
                    "description": "Drive a scripted conversation through the flow and get back exactly what a customer would see. Nothing is sent to anyone. This is how you check your own work before handing it over \u2014 use it.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.simulate"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "simulate_message_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to simulate."
                            },
                            "inbound_json": {
                                "type": "string",
                                "description": "A JSON array of the customer's messages in order, e.g. [\"hi\",\"1\",\"Amina\"]."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "apply_flow_ops",
                    "root_name": "apply_flow_ops",
                    "version": "65b46727",
                    "description": "Build or edit a WhatsApp conversation flow by applying graph operations to its draft. Checked by the real flow validator before anything is written, and the user's canvas updates immediately. The flow stays a DRAFT \u2014 you cannot make it reach customers.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "flows.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "apply_flow_ops arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to edit."
                            },
                            "ops_json": {
                                "type": "string",
                                "description": "A JSON object string {\"ops\":[...]}. Ops: add_node, update_node, remove_node, set_edge {from,out,to}, remove_edge {from,out}, set_entry. Max 40. Call get_flow_catalog first \u2014 an edge \"out\" must be one the node kind actually has."
                            },
                            "expected_version": {
                                "type": "integer",
                                "description": "The version from get_message_flow. Stops you overwriting somebody else."
                            },
                            "auto_layout": {
                                "type": "boolean",
                                "description": "Arrange the canvas after applying (default true). Set false only if you are placing nodes yourself."
                            }
                        },
                        "required": [
                            "flow_id",
                            "ops_json"
                        ]
                    }
                },
                {
                    "name": "create_message_flow",
                    "root_name": "create_message_flow",
                    "version": "7902aac8",
                    "description": "Create a new WhatsApp conversation flow. It starts as a draft with one node, appears on the user's list immediately, and reaches nobody until they publish it.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "flows.create"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_message_flow arguments",
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "What to call the flow, e.g. \"Ordering\"."
                            },
                            "description": {
                                "type": "string",
                                "description": "One line on what it does."
                            },
                            "first_message": {
                                "type": "string",
                                "description": "The opening message. Defaults to a Kiswahili greeting."
                            }
                        },
                        "required": [
                            "name"
                        ]
                    }
                },
                {
                    "name": "format_flow_layout",
                    "root_name": "format_flow_layout",
                    "version": "764ad480",
                    "description": "Tidy the canvas \u2014 arrange every node as a readable top-to-bottom tree, the same as the builder's \"Format layout\" button. Call this after building or reshaping a flow. You can also place nodes yourself with positions_json.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "flows.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "format_flow_layout arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to arrange."
                            },
                            "positions_json": {
                                "type": "string",
                                "description": "Optional explicit placement: node id => {\"x\":123,\"y\":456}. Omit to auto-arrange."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "pin_flow_data_table",
                    "root_name": "pin_flow_data_table",
                    "version": "db35330f",
                    "description": "Pin a data table to a message flow's View data page so the team sees its records next to the flow. It changes what the page shows and nothing else: no node, no access for the flow. Use when the user wants a table kept in view alongside this flow.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "flows.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "pin_flow_data_table arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to pin the table to."
                            },
                            "table_id": {
                                "type": "string",
                                "description": "The data table id (uuid), from list_data_tables."
                            }
                        },
                        "required": [
                            "flow_id",
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "publish_message_flow",
                    "root_name": "publish_message_flow",
                    "version": "e598e436",
                    "description": "Make a message flow LIVE. It will start intercepting real customer conversations on WhatsApp, ahead of any AI agent. Only use when the user has explicitly asked to publish.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "flows.publish"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "publish_message_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to publish."
                            },
                            "acknowledge_overlap": {
                                "type": "boolean",
                                "description": "Set true only after telling the user another live flow answers the same words and they confirmed."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "rollback_message_flow",
                    "root_name": "rollback_message_flow",
                    "version": "80775f5c",
                    "description": "Put an earlier published version of a message flow back LIVE for customers, as a new version so history stays complete. It also replaces the current draft with that snapshot. Use only when the user has asked to undo a publish; list_flow_versions gives the ids.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "flows.publish"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "rollback_message_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to roll back."
                            },
                            "version_id": {
                                "type": "integer",
                                "description": "The version id to restore, from list_flow_versions."
                            },
                            "confirm": {
                                "type": "boolean",
                                "description": "Set true only after the user has agreed that this version goes live now and the draft is replaced."
                            }
                        },
                        "required": [
                            "flow_id",
                            "version_id"
                        ]
                    }
                },
                {
                    "name": "set_flow_triggers",
                    "root_name": "set_flow_triggers",
                    "version": "b85fac64",
                    "description": "Replace a flow's trigger list \u2014 what makes it start. The list is ORDERED and the first match wins, so send the whole list, not a patch. Reports which other flows on the account would compete for the same messages.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "flows.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "set_flow_triggers arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to set triggers on."
                            },
                            "triggers_json": {
                                "type": "string",
                                "description": "A JSON array of triggers, ordered, first match wins. e.g. [{\"type\":\"keyword\",\"match\":\"any\",\"values\":[\"order\",\"oda\"],\"channels\":[\"whatsapp\"]}]"
                            }
                        },
                        "required": [
                            "flow_id",
                            "triggers_json"
                        ]
                    }
                },
                {
                    "name": "unpin_flow_data_table",
                    "root_name": "unpin_flow_data_table",
                    "version": "c21d624d",
                    "description": "Take a hand-pinned data table off a message flow's View data page. Only pins go: a table a node actually reads or writes stays listed until that node is removed. Use when the user no longer wants the table shown with this flow.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "flows.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "unpin_flow_data_table arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to unpin the table from."
                            },
                            "table_id": {
                                "type": "string",
                                "description": "The data table id (uuid) that was pinned."
                            }
                        },
                        "required": [
                            "flow_id",
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "update_message_flow",
                    "root_name": "update_message_flow",
                    "version": "37b18937",
                    "description": "Rename a message flow, change its description, or change its status and priority. Priority decides which flow wins when two match the same message \u2014 lower runs first. Pausing an active flow stops it answering customers.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "flows.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_message_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to change."
                            },
                            "name": {
                                "type": "string",
                                "description": "A new name."
                            },
                            "description": {
                                "type": "string",
                                "description": "One line on what it does."
                            },
                            "status": {
                                "type": "string",
                                "description": "draft, active, paused or archived."
                            },
                            "priority": {
                                "type": "integer",
                                "description": "1\u20139999. Lower runs first when two flows match the same message."
                            },
                            "confirm_pause": {
                                "type": "boolean",
                                "description": "Required to pause or archive a flow that is currently live."
                            },
                            "expected_version": {
                                "type": "integer",
                                "description": "The version from get_message_flow."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                }
            ]
        },
        {
            "key": "data",
            "name": "Data tables",
            "summary": "The tables this business defined for itself and their records: read with filters, create/update/upsert rows, shape fields, run and save reports, and group related tables into folders with reports that read across them. Flows and IVRs read the same tables.",
            "path": "/mcp/v1/data",
            "module": "data",
            "instructions": "The tables this business defined for itself \u2014 customers, orders, bookings, anything a conversation or a call should remember \u2014 and the records in them. Message flows and phone menus read and write the same tables through their Find/Save/Delete record steps; what you change here is what they see next.\n\nOrder of work: list_data_tables, then get_data_table_schema for the one you need \u2014 field keys, types and the operators each accepts all come from there, never from guesswork. query_data_records reads with a JSON condition tree and cursor paging; get_data_record reads one. get_data_record_history says who changed one record, when, and from what to what; list_data_changes is the same trail across a whole table, filtered by since and actor_kind. A table may have a status field with a state machine on it: get_data_table_states lists the states and which moves are legal from each, and transition_data_record makes one move with a reason. An illegal move is refused as a conflict naming the legal ones \u2014 do not retry it, pick a state it allows. To write, prefer upsert_data_record on a unique field (a phone, an order reference) so you never create duplicates; create_data_record and update_data_record are the plain forms; bulk_create_data_records writes up to 200 rows in one call (mode create or upsert) and reports each row. Values are validated against the field types and a refusal names the field \u2014 fix the value, do not retry the same call. Every create accepts an idempotency_key: pass one whenever a retry must not make a second copy \u2014 the first answer is replayed for 24 hours.\n\nShaping a table (create_data_table, update_data_table, add_data_column, update_data_column, reorder_data_columns, delete_data_column) changes what every screen and every flow sees at once, so say what you are about to change before doing it; get_data_table_usage tells you which flows, reports and group would feel it. Indexes: request_data_column_index (kind btree to sort and filter on big tables, kind unique to forbid duplicates and enable upsert; or columns with 2 to 4 field keys for a \"unique together\" rule \u2014 one booking per room per day \u2014 which the schema lists under unique_sets) builds in the background and the schema shows index_status; drop_data_column_index removes one, takes the same columns list for a set, and is required before a key or type change. delete_data_table removes a table with all its records and needs confirm = its slug; called without confirm it only reports the record count and the flows that use it \u2014 read that back first. Reports: list_data_reports (table_id, or group_id for a group's cross-table reports) gives ready-made definitions; suggest_data_reports offers three to six definitions read off a table or group; run_data_report computes one; save_data_report keeps one for the Reports tab; delete_data_report removes a saved one. A report may add a breakdown (second dimension), a compare with the previous period or year, a formula measure over the other measures, a top-N with the rest folded into \"other\", a sort and limit, or a percent-of-total \u2014 the run tools describe each key with an example. \n\nGroups are folders of related tables (\"Mauzo\": customers, orders, payments): list_data_groups and get_data_group read them (get_data_group carries the overview \u2014 totals, records over time by table, amount headlines, the relations between members); create_data_group, update_data_group, set_data_table_group, reorder_data_group_tables and reorder_data_groups arrange them (all \"Change tables and fields\"); delete_data_group dissolves a folder and keeps its tables; run_data_group_report and save_data_group_report compute and keep reports whose series come from different tables and line up on one time axis, compare by table, or combine through a formula. Every report cell carries a drill {table_id, filter, range} \u2014 pass the filter to query_data_records to show the rows behind a number. Writes are separate ticks on this connection: saving records and reports needs \"Save and change records\", shaping tables, fields, indexes and groups needs \"Change tables and fields\", and deleting any of them needs \"Delete things\" on top. If a call comes back \"not allowed\", tell the person which one to tick when they reconnect rather than retrying. A connection is also held to a number of writes per minute; \"writing too fast\" means wait, not retry at once.",
            "tools": [
                {
                    "name": "evaluate_business_rule",
                    "root_name": "evaluate_business_rule",
                    "version": "ae31d075",
                    "description": "Ask a business rule for its answer, rather than working it out yourself. Give it the rule key and the values it needs and it answers `passed` (may this go ahead?), `value` (the fee, the number left, the branch to take, whether the business is open) and `reason` \u2014 a sentence written for the customer, which you should quote rather than paraphrase. Nothing is changed and nothing is reserved: a limit that answers \"one left\" does not hold that one for you. Use this before quoting a charge or promising a slot, and say plainly when a rule refuses.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "rules.view",
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "evaluate_business_rule arguments",
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string",
                                "description": "The rule key, e.g. daily_withdrawals."
                            },
                            "inputs": {
                                "type": "string",
                                "description": "A JSON object of the values the rule needs, e.g. {\"amount\": 50000, \"subject\": \"+255712345678\"}. Pass `at` as an ISO-8601 time to ask about a moment other than now."
                            }
                        },
                        "required": [
                            "key"
                        ]
                    }
                },
                {
                    "name": "get_business_rule",
                    "root_name": "get_business_rule",
                    "version": "11031495",
                    "description": "One business rule in full: its kind and the definition behind it \u2014 the ceiling and the period for a limit, the formula and tiers for a fee, the condition for an eligibility test, the hours and holidays for a window, the map for a choice. Read this when you need to explain WHY a rule said no, or before changing one with upsert_business_rule, so you keep the parts you are not changing.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "rules.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_business_rule arguments",
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string",
                                "description": "The rule key, e.g. daily_withdrawals."
                            }
                        },
                        "required": [
                            "key"
                        ]
                    }
                },
                {
                    "name": "get_data_export",
                    "root_name": "get_data_export",
                    "version": "2b264696",
                    "description": "One export by id: its status and, once ready, the signed download link (no login needed; expires with the file), plus rows, size, any note (e.g. a PDF that became xlsx), any error, and the delivery outcome. Poll this after export_data_records or export_data_report answered \"rendering\".",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_data_export arguments",
                        "type": "object",
                        "properties": {
                            "export_id": {
                                "type": "string",
                                "description": "The export id from export_data_records, export_data_report or list_data_exports."
                            }
                        },
                        "required": [
                            "export_id"
                        ]
                    }
                },
                {
                    "name": "get_data_group",
                    "root_name": "get_data_group",
                    "version": "6778f941",
                    "description": "One table group with its member tables and the overview for a range: totals, a card per table (records, columns, the headline amount total, records created in the range), records over time stacked by table, every amount-like column totalled across the group, the relations between member tables, and the saved cross-table reports. Every card carries a drill {table_id, filter, range} you can pass to query_data_records to see the rows behind a number.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_data_group arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug from list_data_groups."
                            },
                            "range": {
                                "type": "string",
                                "description": "A preset (today, yesterday, last_7_days, last_30_days, last_90_days, this_month, last_month) or a JSON period {\"from\":\"YYYY-MM-DD\",\"to\":\"YYYY-MM-DD\"}. Default last_30_days."
                            }
                        },
                        "required": [
                            "group_id"
                        ]
                    }
                },
                {
                    "name": "get_data_record",
                    "root_name": "get_data_record",
                    "version": "3935c788",
                    "description": "Read one record of a table by its id: every field value, its source (ui, api, mcp, a flow or an IVR), timestamps and the record title. Use query_data_records when you only know a value, not the id.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_data_record arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "record_id": {
                                "type": "string",
                                "description": "The record id."
                            }
                        },
                        "required": [
                            "table_id",
                            "record_id"
                        ]
                    }
                },
                {
                    "name": "get_data_record_history",
                    "root_name": "get_data_record_history",
                    "version": "d6877361",
                    "description": "What has happened to one record: every create, edit and delete, newest first, with who made it (a person, an API key, an assistant connection, a message flow, a phone menu or the platform itself), when, which fields moved and from what to what. Use it to answer \"who changed this\" and \"what did it say before\" \u2014 the record itself only shows the current values.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_data_record_history arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "record_id": {
                                "type": "string",
                                "description": "The record id. The record may already be deleted \u2014 its trail is still here."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many entries, newest first (default 25, max 100)."
                            }
                        },
                        "required": [
                            "table_id",
                            "record_id"
                        ]
                    }
                },
                {
                    "name": "get_data_table_governance",
                    "root_name": "get_data_table_governance",
                    "version": "48388040",
                    "description": "How one table is governed: who may see it (per-table access lines for roles, teams and people), which fields are hidden on the way out and who may see through them, how long records are kept before they are deleted or anonymised, whether the table is under legal hold, and the last retention runs. A table with no access lines is not restricted at all \u2014 whoever holds the Data permission sees it. Use this before explaining why somebody cannot see a table, or before changing a retention rule.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_data_table_governance arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "The table id (uuid) or slug from list_data_tables."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "get_data_table_schema",
                    "root_name": "get_data_table_schema",
                    "version": "38c4cf19",
                    "description": "Everything about one table: every field with its key, type, validation rules, the filter operators it accepts, whether it is required/unique/indexed, the select options, and the quotas in use. Read this before writing records or filters \u2014 keys and operators come from here, not from guesswork. Also lists the field types available when adding a column.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_data_table_schema arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "The table id (uuid) or slug from list_data_tables."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "get_data_table_states",
                    "root_name": "get_data_table_states",
                    "version": "ced879cc",
                    "description": "The state machine behind a table's status fields: every state (key, label, colour, whether a new record starts there and whether it is an end state) and, for each one, exactly which states a record in it may move to next. Read this before transition_data_record or before writing a status value \u2014 a move that is not listed here is refused, and the state keys are what a record stores. Answers an empty fields list when the table has no status field.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_data_table_states arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "The table id (uuid) or slug from list_data_tables."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "get_data_table_usage",
                    "root_name": "get_data_table_usage",
                    "version": "563dd259",
                    "description": "Where a table is used: the message flows and the IVR phone menus that read or write it through their Find/Save/Delete record steps (with each flow's name and whether it reads, writes or both), the reports saved on it, and the group (folder) it sits in. Call it before changing or deleting a table or a field, so you can tell the person which flows would be affected, or when they ask \"what uses this?\".",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_data_table_usage arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "list_business_rules",
                    "root_name": "list_business_rules",
                    "version": "b2aa30f4",
                    "description": "Every business rule this account has defined: the limits, fees, eligibility tests, opening windows and routing maps that its message flows, phone menus and assistants all enforce. Read this before quoting a charge, promising a booking or telling a customer they qualify for something \u2014 the rule is the answer, not your own arithmetic. Returns each rule's key (what a flow asks for), label, kind, whether it is switched on, and one line about it; call get_business_rule for the full definition.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "rules.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_business_rules arguments",
                        "type": "object",
                        "properties": {
                            "kind": {
                                "type": "string",
                                "description": "Only this kind: limit, fee, eligibility, window or choice."
                            },
                            "enabled_only": {
                                "type": "boolean",
                                "description": "Leave out the rules that are switched off."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many to return (default 100, max 200)."
                            }
                        }
                    }
                },
                {
                    "name": "list_data_actions",
                    "root_name": "list_data_actions",
                    "version": "70284bb7",
                    "description": "The record actions a table defines (.data-store/03 \u00a7D): the buttons people press on a record \u2014 start a message flow for its phone, call a webhook with it, set some fields, export it as a file, or open a link built from it. Each comes back as {id, label, icon, kind: flow|webhook|set_fields|export|open_url, scope: row|bulk|both, confirm, permission} plus what the kind needs (flow_id, url, patch, format). Run one with run_data_action.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_data_actions arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "list_data_changes",
                    "root_name": "list_data_changes",
                    "version": "185a9e68",
                    "description": "Recent changes across one table, newest first: which records were created, edited or deleted, by whom, and which fields moved. Filter with since (an ISO-8601 moment or a relative window like \"last_7_days\") and actor_kind (user, api, mcp, flow, ivr, schedule, system, import) to answer \"what did the flow write last night\" or \"what has anyone touched today\". get_data_record_history is the same trail for one record.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_data_changes arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "since": {
                                "type": "string",
                                "description": "Only changes at or after this moment: an ISO-8601 timestamp, or one of today, last_24_hours, last_7_days, last_30_days, last_90_days. Omit for the most recent changes whenever they were."
                            },
                            "actor_kind": {
                                "type": "string",
                                "description": "Only changes made by this kind of writer: user, api, mcp, flow, ivr, schedule, system, import."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many entries, newest first (default 25, max 100)."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "list_data_exports",
                    "root_name": "list_data_exports",
                    "version": "87f5c04b",
                    "description": "The files this business has exported from its tables and reports, newest first: what each is (records / report, format), its status (pending, rendering, ready, failed, expired), row count, who asked (a person, a message flow, a phone menu, an assistant, a schedule), how it was delivered, when it expires, and a signed download link while it is ready. Pass table_id to narrow to one table.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_data_exports arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Only exports of this table (id or slug)."
                            },
                            "status": {
                                "type": "string",
                                "description": "Only this status: pending, rendering, ready, failed or expired."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Rows, default 20, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "list_data_groups",
                    "root_name": "list_data_groups",
                    "version": "9c9c84ea",
                    "description": "List the table groups of this business \u2014 named folders of related tables (\"Mauzo\": customers, orders, payments) with a report layer that reads across every table in them. Each row carries the member count and the records across them. get_data_group reads one with its overview; run_data_group_report computes across its tables.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_data_groups arguments",
                        "type": "object",
                        "properties": {
                            "search": {
                                "type": "string",
                                "description": "Filter by name or slug."
                            }
                        }
                    }
                },
                {
                    "name": "list_data_reports",
                    "root_name": "list_data_reports",
                    "version": "13515dd3",
                    "description": "The reports available on a table (pass table_id): the defaults derived from its fields (record count, records over time, totals of number fields, breakdowns of select/boolean fields) and the ones people saved. Each carries a ready definition you can pass to run_data_report as it is, or tweak. Pass group_id instead for the cross-table reports saved on a group (run them with run_data_group_report; the group's live overview is on get_data_group). Exactly one of table_id or group_id.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_data_reports arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug \u2014 for a table's reports."
                            },
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug \u2014 for a group's cross-table reports instead."
                            }
                        }
                    }
                },
                {
                    "name": "list_data_tables",
                    "root_name": "list_data_tables",
                    "version": "c9d35f01",
                    "description": "List the data tables this business defined for itself (customers, orders, bookings \u2014 whatever a flow or a person keeps here), with record counts, how many fields each has and the group (folder) it sits in. Start here; every other data tool takes a table_id (or slug) from this list.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_data_tables arguments",
                        "type": "object",
                        "properties": {
                            "search": {
                                "type": "string",
                                "description": "Filter by name or slug."
                            },
                            "group_id": {
                                "type": "string",
                                "description": "Only the tables of this group (id or slug from list_data_groups)."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "query_data_records",
                    "root_name": "query_data_records",
                    "version": "a1c92540",
                    "description": "Read records from a table, newest first, with an optional filter, free-text search and sort. Pages by cursor: pass next_cursor from the previous answer to continue, never a page number. A filter is a JSON condition tree: {\"all\":[{\"column\":\"opt_in\",\"op\":\"equals\",\"value\":true},{\"any\":[...]}]}. Leaves are {column, op, value}; nest \"all\"/\"any\" freely. Operators: equals, not_equals, contains, starts_with, greater_than, less_than, between ([low, high]), is_empty, is_not_empty, in (a list). Each column type accepts a subset \u2014 get_data_table_schema lists them per column. System columns: $id, $created_at, $updated_at (temporal ops; a value may be {\"relative\":\"last_30_days\"} \u2014 presets today, yesterday, last_7_days, last_30_days, last_90_days, this_month, last_month), $source (equals/in/starts_with: ui, api, mcp, import, seed, flow:<id>, ivr:<id>). Sorting on a field that is not indexed is refused on large tables; sort by $created_at instead. Each row carries its id, data, source, timestamps and a title.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "query_data_records arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "filter": {
                                "type": "string",
                                "description": "JSON condition tree (see the tool description)."
                            },
                            "search": {
                                "type": "string",
                                "description": "Free text matched against the text, phone and email fields."
                            },
                            "sort": {
                                "type": "string",
                                "description": "A field key or $created_at. Default $created_at."
                            },
                            "dir": {
                                "type": "string",
                                "description": "asc or desc (default desc)."
                            },
                            "cursor": {
                                "type": "string",
                                "description": "next_cursor from the previous page."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Rows per page, default 25, max 100."
                            },
                            "with_count": {
                                "type": "boolean",
                                "description": "Also count every matching record (one extra query)."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "run_data_group_report",
                    "root_name": "run_data_group_report",
                    "version": "7dcf173e",
                    "description": "Run a report across the tables of a group and get the rows back: orders total and new customers per week on one axis, totals per table side by side, or a ratio between two tables. A group report definition is JSON: {\"series\":[{\"table_id\":\"<member table id or slug>\",\"metric\":{\"fn\":\"sum\",\"column\":\"amount\"},\"filters\":<condition tree or null>,\"label\":\"Orders\",\"breakdown\":{\"column\":\"status\",\"top\":5},\"date_column\":\"paid_at\"},{\"table_id\":\"\u2026\",\"metric\":{\"fn\":\"count\"},\"label\":\"Customers\"},{\"label\":\"Orders per customer\",\"formula\":\"orders / customers\"}],\"dimension\":{\"column\":\"$created_at\",\"bucket\":\"day|week|month|quarter\"} (one shared time axis; rows {bucket, series:{label: value}, drill:{label: {table_id, filter, range}}}) or {\"kind\":\"table\"} (one row per series: {label, table_id, value, drill}) or null (one number per series),\"date_range\":{\"relative\":\"last_30_days\"} or {\"from\":\"2026-08-01\",\"to\":\"2026-09-01\"},\"chart\":\"number|line|bar|stacked_bar|donut|table\",\"compare\":\"previous_period\"|\"previous_year\" (optional),\"sort\":{\"by\":\"value|label\",\"dir\":\"asc|desc\"} and \"limit\" (by-table reports only). A formula series names other series by their slugified label (Orders \u2192 orders; \"Kiasi (TZS)\" \u2192 kiasi_tzs) and may use + - * / parentheses and percent_of(a,b). Every table_id must be a member of the group; each series is validated against its own table. A measure with \"as\":\"percent_of_total\" also answers its share of the total. Every run is bounded by the date range (default last 90 days) and a 10-second budget across all series; results are cached for a minute. Every cell carries a drill {table_id, filter, range} for query_data_records. A filter is a JSON condition tree: {\"all\":[{\"column\":\"opt_in\",\"op\":\"equals\",\"value\":true},{\"any\":[...]}]}. Leaves are {column, op, value}; nest \"all\"/\"any\" freely. Operators: equals, not_equals, contains, starts_with, greater_than, less_than, between ([low, high]), is_empty, is_not_empty, in (a list). Each column type accepts a subset \u2014 get_data_table_schema lists them per column. System columns: $id, $created_at, $updated_at (temporal ops; a value may be {\"relative\":\"last_30_days\"} \u2014 presets today, yesterday, last_7_days, last_30_days, last_90_days, this_month, last_month), $source (equals/in/starts_with: ui, api, mcp, import, seed, flow:<id>, ivr:<id>).",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "run_data_group_report arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug."
                            },
                            "definition": {
                                "type": "string",
                                "description": "JSON group report definition (see the tool description). Member tables may be named by slug."
                            }
                        },
                        "required": [
                            "group_id",
                            "definition"
                        ]
                    }
                },
                {
                    "name": "run_data_report",
                    "root_name": "run_data_report",
                    "version": "87a3f6d7",
                    "description": "Run an aggregate over a table and get the rows back: a count, a sum by region, records per week. A report definition is JSON: {\"metrics\":[{\"fn\":\"sum\",\"column\":\"amount\"},{\"fn\":\"count\"}],\"dimension\":{\"column\":\"region\"} or {\"column\":\"$created_at\",\"bucket\":\"week\"} or null,\"filters\":<condition tree or null>,\"date_range\":{\"relative\":\"last_30_days\"} or {\"from\":\"2026-08-01\",\"to\":\"2026-09-01\"},\"chart\":\"number|line|bar|stacked_bar|donut|table\"}. fn: count, count_distinct, sum, avg, min, max (the last four need a number/currency column). A dimension may be a select, boolean or relation field, or a date field with bucket day|week|month|quarter. No dimension gives one number. Every run is bounded by a date range (default last 90 days) and a 10-second budget; results are cached for a minute. Optional keys: \"breakdown\":{\"column\":\"status\",\"top\":5} splits every row into per-value series (rows gain series:{value:{metrics}}, values past top fold into \"other\"); \"compare\":\"previous_period\"|\"previous_year\" re-runs the same query over the preceding window and adds previous/delta/delta_pct per metric on every row; a measure {\"fn\":\"formula\",\"expr\":\"sum_amount / count\",\"label\":\"Average order\"} is computed per row from the other measures' keys (+ - * / parentheses, percent_of(a,b); division by zero gives null); \"dimension\":{\"column\":\"region\",\"top\":6} with \"sort\":{\"by\":\"sum_amount\",\"dir\":\"desc\"} and \"limit\":20 cut a category dimension; a measure with \"as\":\"percent_of_total\" also answers <key>_pct per row. Example: {\"metrics\":[{\"fn\":\"sum\",\"column\":\"amount\"},{\"fn\":\"count\"},{\"fn\":\"formula\",\"expr\":\"sum_amount / count\",\"label\":\"Average\"}],\"dimension\":{\"column\":\"$created_at\",\"bucket\":\"week\"},\"compare\":\"previous_period\",\"date_range\":{\"relative\":\"last_90_days\"},\"chart\":\"line\"}. A filter is a JSON condition tree: {\"all\":[{\"column\":\"opt_in\",\"op\":\"equals\",\"value\":true},{\"any\":[...]}]}. Leaves are {column, op, value}; nest \"all\"/\"any\" freely. Operators: equals, not_equals, contains, starts_with, greater_than, less_than, between ([low, high]), is_empty, is_not_empty, in (a list). Each column type accepts a subset \u2014 get_data_table_schema lists them per column. System columns: $id, $created_at, $updated_at (temporal ops; a value may be {\"relative\":\"last_30_days\"} \u2014 presets today, yesterday, last_7_days, last_30_days, last_90_days, this_month, last_month), $source (equals/in/starts_with: ui, api, mcp, import, seed, flow:<id>, ivr:<id>).",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "run_data_report arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "definition": {
                                "type": "string",
                                "description": "JSON report definition (see the tool description)."
                            }
                        },
                        "required": [
                            "table_id",
                            "definition"
                        ]
                    }
                },
                {
                    "name": "suggest_data_reports",
                    "root_name": "suggest_data_reports",
                    "version": "c372ef6f",
                    "description": "Three to six ready-made report definitions read off a table's or a group's schema \u2014 the amount headline by week against the previous period, the top category by amount broken down by a second category, a computed average per record, records over time, a share-of-total split, and for a group a cross-table ratio when two members share a relation. Each comes with a name and a one-line reason; pass its definition to run_data_report (table_id) or run_data_group_report (group_id) as it is, or tweak it. Pass exactly one of table_id or group_id.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "suggest_data_reports arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug \u2014 suggestions for one table."
                            },
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug \u2014 suggestions across the group's tables."
                            }
                        }
                    }
                },
                {
                    "name": "add_data_column",
                    "root_name": "add_data_column",
                    "version": "033c866b",
                    "description": "Add a field to a table. The grid, the form, the filters and the default reports pick it up on the next load \u2014 nothing else to do. Types: text, long_text, number, currency, boolean, date, datetime, phone, email, select, multi_select, status, relation, file, auto_number. Config by type \u2014 number/currency: {precision, unit|currency, min, max}; select/multi_select: {options:[{key,label,color}]}; status: {states:[{key,label,color,initial,final}],transitions:[{from:'<key>|*',to:'<key>',label,requires}],strict:true} \u2014 a state machine, so a record may only be created in an initial state and only move along a declared transition (get_data_table_states reads one back); relation: {target_table_id}; phone: {default_region:\"TZ\"}; auto_number: {prefix:\"ORD-\", pad:6, yearly:false}; any: {default, ui:{is_title_field, is_summary_metric, help_text, placeholder, hidden_in_grid, hidden_in_form}}. An auto_number is written by the platform only: never send a value for it, and existing records are numbered in the background when the field is added. The key is derived from the label unless given. Indexes and uniqueness are set afterwards from the Fields tab by the owner (they build in the background).",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "add_data_column arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "label": {
                                "type": "string",
                                "description": "What people see, e.g. \"Kiasi cha mwisho\"."
                            },
                            "type": {
                                "type": "string",
                                "description": "One of: text, long_text, number, currency, boolean, date, datetime, phone, email, select, status, multi_select, relation, file, auto_number."
                            },
                            "key": {
                                "type": "string",
                                "description": "Machine key used in filters and flows ({{vars.record.key}}); derived from the label if omitted."
                            },
                            "required": {
                                "type": "boolean",
                                "description": "Refuse records without a value. Default false."
                            },
                            "config": {
                                "type": "string",
                                "description": "JSON object of type settings (see the tool description)."
                            }
                        },
                        "required": [
                            "table_id",
                            "label",
                            "type"
                        ]
                    }
                },
                {
                    "name": "bulk_create_data_records",
                    "root_name": "bulk_create_data_records",
                    "version": "5325cef5",
                    "description": "Write many records in one call \u2014 an import from a spreadsheet, a list the person dictated, a batch pulled from another system. rows is a JSON array of at most 200 objects keyed by field key (from get_data_table_schema). mode \"create\" inserts every row; mode \"upsert\" matches each row on match_column (a unique field the row must contain) and updates the existing record or creates one, so re-running the same batch never duplicates. Rows are written one by one through the same checks as create_data_record: a row that fails validation is skipped and reported with its field errors while the others go in; the batch stops at the first quota refusal (records or storage) and reports how many were written. The answer lists every row by index with ok, id or errors. Pass an idempotency_key (any string you make up, e.g. a UUID) when a retry must not create a second copy: the first successful answer is kept for 24 hours and replayed for the same key, so a call that timed out can be repeated safely.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.records.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "bulk_create_data_records arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "rows": {
                                "type": "string",
                                "description": "JSON array (max 200) of objects keyed by field key."
                            },
                            "mode": {
                                "type": "string",
                                "description": "\"create\" (default) inserts every row; \"upsert\" matches on match_column and updates or creates."
                            },
                            "match_column": {
                                "type": "string",
                                "description": "For upsert: the unique field every row carries, e.g. phone."
                            },
                            "idempotency_key": {
                                "type": "string",
                                "description": "Optional: a key you make up so a retried call replays the first answer instead of writing the batch again (kept 24 h)."
                            }
                        },
                        "required": [
                            "table_id",
                            "rows"
                        ]
                    }
                },
                {
                    "name": "create_data_group",
                    "root_name": "create_data_group",
                    "version": "58fc0b64",
                    "description": "Create a table group \u2014 a named folder of related tables with a report layer across them. Give it a human name (\"Mauzo\", \"Bookings\"); the slug is derived unless you pass one; optionally list the tables (ids or slugs) to put in it straight away. A table belongs to at most one group, so a listed table already in another group moves. Counts against the workspace's group quota. Pass an idempotency_key (any string you make up, e.g. a UUID) when a retry must not create a second copy: the first successful answer is kept for 24 hours and replayed for the same key, so a call that timed out can be repeated safely.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_data_group arguments",
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "Human name, e.g. \"Mauzo\"."
                            },
                            "slug": {
                                "type": "string",
                                "description": "Optional machine name: lowercase letters, digits, underscores, starting with a letter."
                            },
                            "description": {
                                "type": "string",
                                "description": "One line on what the group holds."
                            },
                            "icon": {
                                "type": "string",
                                "description": "An emoji shown before the name, e.g. \"\ud83d\uded2\"."
                            },
                            "color": {
                                "type": "string",
                                "description": "A palette key: gray, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose."
                            },
                            "table_ids": {
                                "type": "string",
                                "description": "JSON list (or comma list) of table ids or slugs to put in the group, in order."
                            },
                            "idempotency_key": {
                                "type": "string",
                                "description": "Optional: a key you make up so a retried call cannot create the group twice (kept 24 h)."
                            }
                        },
                        "required": [
                            "name"
                        ]
                    }
                },
                {
                    "name": "create_data_record",
                    "root_name": "create_data_record",
                    "version": "eacc2df3",
                    "description": "Create one record. Pass data as a JSON object keyed by field key (from get_data_table_schema). Values are checked against each field's type: numbers as numbers, booleans as true/false, dates as YYYY-MM-DD, datetimes as ISO-8601, phones in any Tanzanian form (0712\u2026, +255\u2026), select values as option keys. A required field missing, a unique field clashing, or a value of the wrong shape is refused with the field named, and nothing is saved. To avoid duplicates on a unique field, prefer upsert_data_record. For many rows at once use bulk_create_data_records. Pass an idempotency_key (any string you make up, e.g. a UUID) when a retry must not create a second copy: the first successful answer is kept for 24 hours and replayed for the same key, so a call that timed out can be repeated safely.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.records.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_data_record arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "data": {
                                "type": "string",
                                "description": "JSON object: {\"phone\":\"+255712345678\",\"name\":\"Asha\"}."
                            },
                            "idempotency_key": {
                                "type": "string",
                                "description": "Optional: a key you make up so a retried call cannot create the record twice (kept 24 h)."
                            }
                        },
                        "required": [
                            "table_id",
                            "data"
                        ]
                    }
                },
                {
                    "name": "create_data_table",
                    "root_name": "create_data_table",
                    "version": "e95f0eb0",
                    "description": "Create a new, empty data table for this business. Give it a human name (\"Wateja\", \"Bookings\"); the slug is derived unless you pass one. Then add fields with add_data_column. Counts against the workspace's table quota. Pass an idempotency_key (any string you make up, e.g. a UUID) when a retry must not create a second copy: the first successful answer is kept for 24 hours and replayed for the same key, so a call that timed out can be repeated safely.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_data_table arguments",
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "Human name, e.g. \"Wateja\" or \"Bookings\"."
                            },
                            "slug": {
                                "type": "string",
                                "description": "Optional machine name: lowercase letters, digits, underscores, starting with a letter. Derived from the name if omitted."
                            },
                            "description": {
                                "type": "string",
                                "description": "One line on what the table holds."
                            },
                            "icon": {
                                "type": "string",
                                "description": "An emoji shown before the name, e.g. \"\ud83d\udc65\"."
                            },
                            "idempotency_key": {
                                "type": "string",
                                "description": "Optional: a key you make up so a retried call cannot create the table twice (kept 24 h)."
                            }
                        },
                        "required": [
                            "name"
                        ]
                    }
                },
                {
                    "name": "delete_data_column",
                    "root_name": "delete_data_column",
                    "version": "9edd7d19",
                    "description": "Remove a field from a table. The field disappears from the grid, form, filters and reports immediately; its index is dropped in the background; the stored values are purged after a day, so a mistake is recoverable by the owner until then. The key stays reserved \u2014 a new field cannot reuse it. Flows that reference the field will fail validation until edited.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "delete_data_column arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "column": {
                                "type": "string",
                                "description": "The field key or id to remove."
                            }
                        },
                        "required": [
                            "table_id",
                            "column"
                        ]
                    }
                },
                {
                    "name": "delete_data_group",
                    "root_name": "delete_data_group",
                    "version": "02b5838d",
                    "description": "Delete a table group (folder). The tables in it are KEPT \u2014 they simply become ungrouped, with every field and record intact \u2014 but the group's own saved cross-table reports go with it. Use it to dissolve a folder the person no longer wants; to remove a whole table and its records use delete_data_table instead. Says which tables were left ungrouped.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "delete_data_group arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug from list_data_groups."
                            }
                        },
                        "required": [
                            "group_id"
                        ]
                    }
                },
                {
                    "name": "delete_data_record",
                    "root_name": "delete_data_record",
                    "version": "28d4105a",
                    "description": "Delete records by id (one, or a comma-separated list). Soft: the rows leave every list and count but the owner can still recover them from the database for a while. Returns how many were removed.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.records.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "delete_data_record arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "record_ids": {
                                "type": "string",
                                "description": "One record id, or several separated by commas."
                            }
                        },
                        "required": [
                            "table_id",
                            "record_ids"
                        ]
                    }
                },
                {
                    "name": "delete_data_report",
                    "root_name": "delete_data_report",
                    "version": "62c1c257",
                    "description": "Remove a saved report from a table's Reports tab (pass table_id) or from a group's (pass group_id) \u2014 exactly one of the two, plus the report_id from list_data_reports. Only the saved definition goes; the records it counted are untouched, and the default reports derived from the fields cannot be removed. Use it when a saved card is wrong or no longer wanted; to change one instead, save_data_report / save_data_group_report with its report_id.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.reports.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "delete_data_report arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug (for a table report)."
                            },
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug (for a group report)."
                            },
                            "report_id": {
                                "type": "string",
                                "description": "The saved report's id."
                            }
                        },
                        "required": [
                            "report_id"
                        ]
                    }
                },
                {
                    "name": "delete_data_table",
                    "root_name": "delete_data_table",
                    "version": "9789fb07",
                    "description": "Delete a whole table \u2014 its fields, every record, its saved reports and its flow bindings \u2014 permanently and at once; there is no recovery. Because of that it works in two steps: called without confirm it only reports what would go (the record count and the message flows and phone menus that read or write the table), and nothing is deleted. Read that back to the person; when they agree, call again with confirm set to the table's slug exactly. A flow that used the table will fail its Find/Save/Delete steps until edited.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "delete_data_table arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "confirm": {
                                "type": "string",
                                "description": "The table's slug, typed exactly, once the person has agreed. Omit it first to see what would be deleted."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "drop_data_column_index",
                    "root_name": "drop_data_column_index",
                    "version": "86a4d85f",
                    "description": "Drop the index a field has, or a \"unique together\" rule. One field: pass column. The field stops being sortable on big tables at once; if it was unique, duplicates are allowed again from this moment and upsert_data_record can no longer match on it. A set: pass columns with the same field keys the rule was made with (get_data_table_schema lists them under unique_sets) and that combination may repeat again. The physical index is removed in the background (index_status dropping, then gone). Needed before update_data_column may rename the field's key or change its type, and the way to free an index slot when the quota is full. Nothing about the values changes.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "drop_data_column_index arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "column": {
                                "type": "string",
                                "description": "The field key or id, to drop the index on ONE field. Leave out when passing columns."
                            },
                            "columns": {
                                "type": "string",
                                "description": "The field keys of a \"unique together\" rule to drop, as a JSON list [\"room\",\"day\"] or a comma list, exactly as get_data_table_schema lists them under unique_sets."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "export_data_records",
                    "root_name": "export_data_records",
                    "version": "261bc2d2",
                    "description": "Turn a table's records into a file \u2014 CSV, an Excel workbook (xlsx) or a PDF \u2014 with an optional filter, sort and choice of columns, and either get a signed download link or have it delivered. Up to 50,000 rows (a PDF holds 2,000; a longer list comes back as xlsx and the export's note says so). Short lists (\u2264 500 rows) render on this call; longer ones render in the background \u2014 poll get_data_export. Delivery is optional: deliver_via none (default \u2014 you get a signed download link), whatsapp (a document to the phone in deliver_to, which must already have a WhatsApp conversation with this business), email (an attachment to the address in deliver_to; deliver_subject optional) or sms (the link, texted to deliver_to). Sending needs the \"Send messages and place calls\" tick on this connection. Files expire after 72 hours; get_data_export answers the current status and link. A filter is a JSON condition tree: {\"all\":[{\"column\":\"opt_in\",\"op\":\"equals\",\"value\":true},{\"any\":[...]}]}. Leaves are {column, op, value}; nest \"all\"/\"any\" freely. Operators: equals, not_equals, contains, starts_with, greater_than, less_than, between ([low, high]), is_empty, is_not_empty, in (a list). Each column type accepts a subset \u2014 get_data_table_schema lists them per column. System columns: $id, $created_at, $updated_at (temporal ops; a value may be {\"relative\":\"last_30_days\"} \u2014 presets today, yesterday, last_7_days, last_30_days, last_90_days, this_month, last_month), $source (equals/in/starts_with: ui, api, mcp, import, seed, flow:<id>, ivr:<id>).",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "export_data_records arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "format": {
                                "type": "string",
                                "description": "csv (default), xlsx or pdf."
                            },
                            "filter": {
                                "type": "string",
                                "description": "JSON condition tree (see the tool description)."
                            },
                            "search": {
                                "type": "string",
                                "description": "Free text matched against the text, phone and email fields."
                            },
                            "sort": {
                                "type": "string",
                                "description": "A field key or $created_at (default: newest first)."
                            },
                            "dir": {
                                "type": "string",
                                "description": "asc or desc (default desc)."
                            },
                            "columns": {
                                "type": "string",
                                "description": "JSON list or comma list of field keys to include, in order. Default: every field."
                            },
                            "title": {
                                "type": "string",
                                "description": "Title on the file (PDF header, filename). Default: the table name."
                            },
                            "max_rows": {
                                "type": "integer",
                                "description": "Cap the rows (1\u201350,000)."
                            },
                            "deliver_via": {
                                "type": "string",
                                "description": "none (default), whatsapp, email or sms."
                            },
                            "deliver_to": {
                                "type": "string",
                                "description": "Phone (whatsapp/sms) or email address the file goes to."
                            },
                            "deliver_caption": {
                                "type": "string",
                                "description": "Caption under a WhatsApp document."
                            },
                            "deliver_subject": {
                                "type": "string",
                                "description": "Email subject (default: the title)."
                            },
                            "wait": {
                                "type": "boolean",
                                "description": "Render on this call when the list is short (default true)."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "export_data_report",
                    "root_name": "export_data_report",
                    "version": "17bbef96",
                    "description": "Render a report as a file \u2014 a PDF with the table, totals and bars, an Excel workbook (a Summary sheet plus one sheet per series for a group report) or a CSV \u2014 and get a signed link or have it delivered. Pass table_id with a definition (the same JSON run_data_report takes) or report_id (a saved report from list_data_reports); or group_id with a group report definition (run_data_group_report's) or report_id. Optional range overrides the definition's date_range, e.g. {\"relative\":\"last_30_days\"}. Reports always render on this call. Delivery is optional: deliver_via none (default \u2014 you get a signed download link), whatsapp (a document to the phone in deliver_to, which must already have a WhatsApp conversation with this business), email (an attachment to the address in deliver_to; deliver_subject optional) or sms (the link, texted to deliver_to). Sending needs the \"Send messages and place calls\" tick on this connection. Files expire after 72 hours; get_data_export answers the current status and link.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "export_data_report arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug (table report)."
                            },
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug (group report) \u2014 instead of table_id."
                            },
                            "definition": {
                                "type": "string",
                                "description": "JSON report definition (see run_data_report / run_data_group_report)."
                            },
                            "report_id": {
                                "type": "string",
                                "description": "A saved report id, instead of a definition."
                            },
                            "range": {
                                "type": "string",
                                "description": "JSON date_range override: {\"relative\":\"last_30_days\"} or {\"from\":\"2026-08-01\",\"to\":\"2026-08-31\"}."
                            },
                            "format": {
                                "type": "string",
                                "description": "pdf (default), xlsx or csv."
                            },
                            "title": {
                                "type": "string",
                                "description": "Title on the file. Default: the saved report's name, or one derived from the definition."
                            },
                            "deliver_via": {
                                "type": "string",
                                "description": "none (default), whatsapp, email or sms."
                            },
                            "deliver_to": {
                                "type": "string",
                                "description": "Phone (whatsapp/sms) or email address the file goes to."
                            },
                            "deliver_caption": {
                                "type": "string",
                                "description": "Caption under a WhatsApp document."
                            },
                            "deliver_subject": {
                                "type": "string",
                                "description": "Email subject (default: the title)."
                            }
                        }
                    }
                },
                {
                    "name": "reorder_data_columns",
                    "root_name": "reorder_data_columns",
                    "version": "0343b235",
                    "description": "Put a table's fields in a chosen order \u2014 the column order of the grid, the form and every export. Pass the fields (keys or ids) first-to-last; fields you leave out keep their relative order after the ones you named. Purely cosmetic: keys, types, values and indexes are untouched, so flows are unaffected. Use it when the person wants the name column first or the notes last.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "reorder_data_columns arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "order": {
                                "type": "string",
                                "description": "JSON list (or comma list) of field keys or ids, first to last."
                            }
                        },
                        "required": [
                            "table_id",
                            "order"
                        ]
                    }
                },
                {
                    "name": "reorder_data_group_tables",
                    "root_name": "reorder_data_group_tables",
                    "version": "e5b62339",
                    "description": "Put the tables inside a group in a chosen order \u2014 the order the group page, its overview cards and the Data page show them in. Pass the member tables (ids or slugs) first-to-last; members you leave out keep their relative order after the ones you named, and a table that is not in this group is ignored (use set_data_table_group to move it in first). Purely cosmetic: no field, record or report changes.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "reorder_data_group_tables arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug from list_data_groups."
                            },
                            "order": {
                                "type": "string",
                                "description": "JSON list (or comma list) of table ids or slugs, first to last."
                            }
                        },
                        "required": [
                            "group_id",
                            "order"
                        ]
                    }
                },
                {
                    "name": "reorder_data_groups",
                    "root_name": "reorder_data_groups",
                    "version": "cac3bf66",
                    "description": "Put the table groups (folders) of this workspace in a chosen order \u2014 the order their sections appear in on the Data page. Pass the groups (ids or slugs) first-to-last; groups you leave out keep their relative order after the ones you named. Purely cosmetic: nothing inside any group changes. Use reorder_data_group_tables for the tables within one group.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "reorder_data_groups arguments",
                        "type": "object",
                        "properties": {
                            "order": {
                                "type": "string",
                                "description": "JSON list (or comma list) of group ids or slugs, first to last."
                            }
                        },
                        "required": [
                            "order"
                        ]
                    }
                },
                {
                    "name": "request_data_column_index",
                    "root_name": "request_data_column_index",
                    "version": "48a6c824",
                    "description": "Ask for an index on a field, or for a \"unique together\" rule over several fields. One field: pass column and kind \u2014 \"btree\" makes the field sortable and fast to filter on big tables (query_data_records refuses to sort on an unindexed field past the sort threshold); \"unique\" additionally forbids two records with the same value and is what upsert_data_record matches on \u2014 a phone, an order reference. Several fields: pass columns as a list of 2 to 4 field keys instead of column, and no two records may share that whole combination while each value on its own may repeat \u2014 one booking per room per day, one enrolment per student per course. A unique request is refused up front when duplicates already exist, naming up to ten of them; a field can hold one index, and asking for the other kind replaces it; a set that already exists is returned unchanged. The index is built in the background: the answer carries index_status pending, and get_data_table_schema shows it move to ready (or failed, with the reason) and lists the sets under unique_sets. Counts against the per-table and per-workspace index quotas \u2014 a set costs one slot.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "request_data_column_index arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "column": {
                                "type": "string",
                                "description": "The field key or id, for an index on ONE field. Leave out when passing columns."
                            },
                            "columns": {
                                "type": "string",
                                "description": "For a \"unique together\" rule: 2 to 4 field keys as a JSON list [\"room\",\"day\"] or a comma list. No two records may then share that whole combination. Leave out when passing column."
                            },
                            "kind": {
                                "type": "string",
                                "description": "\"btree\" for sorting and filtering, or \"unique\" to forbid duplicate values. Required with column; a set of columns is always unique."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "run_data_action",
                    "root_name": "run_data_action",
                    "version": "70945f49",
                    "description": "Run a record action on one or more records: what a person gets by pressing the action button in the grid. list_data_actions shows the actions a table defines and what each does. Pass record_ids as a JSON list or comma list (up to 200; a row-scoped action takes one). Answers a line per record \u2014 {id, ok, message, url} \u2014 and a summary; an export answers the file too. Needs \"Save and change records\"; an action of kind flow or webhook also needs \"Send messages and place calls\" on this connection, because it reaches outside the account.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "run_data_action arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "action_id": {
                                "type": "string",
                                "description": "The action id from list_data_actions."
                            },
                            "record_ids": {
                                "type": "string",
                                "description": "JSON list or comma list of record ids (up to 200)."
                            }
                        },
                        "required": [
                            "table_id",
                            "action_id",
                            "record_ids"
                        ]
                    }
                },
                {
                    "name": "save_data_group_report",
                    "root_name": "save_data_group_report",
                    "version": "deb4b11d",
                    "description": "Save a cross-table report so it appears in the group's Reports tab for everyone, or update one by report_id (name, description, definition, pinned). A group report definition is JSON: {\"series\":[{\"table_id\":\"<member table id or slug>\",\"metric\":{\"fn\":\"sum\",\"column\":\"amount\"},\"filters\":<condition tree or null>,\"label\":\"Orders\",\"breakdown\":{\"column\":\"status\",\"top\":5},\"date_column\":\"paid_at\"},{\"table_id\":\"\u2026\",\"metric\":{\"fn\":\"count\"},\"label\":\"Customers\"},{\"label\":\"Orders per customer\",\"formula\":\"orders / customers\"}],\"dimension\":{\"column\":\"$created_at\",\"bucket\":\"day|week|month|quarter\"} (one shared time axis; rows {bucket, series:{label: value}, drill:{label: {table_id, filter, range}}}) or {\"kind\":\"table\"} (one row per series: {label, table_id, value, drill}) or null (one number per series),\"date_range\":{\"relative\":\"last_30_days\"} or {\"from\":\"2026-08-01\",\"to\":\"2026-09-01\"},\"chart\":\"number|line|bar|stacked_bar|donut|table\",\"compare\":\"previous_period\"|\"previous_year\" (optional),\"sort\":{\"by\":\"value|label\",\"dir\":\"asc|desc\"} and \"limit\" (by-table reports only). A formula series names other series by their slugified label (Orders \u2192 orders; \"Kiasi (TZS)\" \u2192 kiasi_tzs) and may use + - * / parentheses and percent_of(a,b). Every table_id must be a member of the group; each series is validated against its own table. A measure with \"as\":\"percent_of_total\" also answers its share of the total. The definition is validated against the group before it is stored.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.reports.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "save_data_group_report arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug."
                            },
                            "report_id": {
                                "type": "string",
                                "description": "Update this saved report instead of creating one."
                            },
                            "name": {
                                "type": "string",
                                "description": "Report name (required when creating)."
                            },
                            "description": {
                                "type": "string",
                                "description": "One line on what it shows."
                            },
                            "definition": {
                                "type": "string",
                                "description": "JSON group report definition (required when creating)."
                            },
                            "is_pinned": {
                                "type": "boolean",
                                "description": "Pin it to the top of the Reports tab."
                            }
                        },
                        "required": [
                            "group_id"
                        ]
                    }
                },
                {
                    "name": "save_data_report",
                    "root_name": "save_data_report",
                    "version": "56f91062",
                    "description": "Save a report so it appears in the table's Reports tab for everyone, or update one by report_id (name, description, definition, pinned). A report definition is JSON: {\"metrics\":[{\"fn\":\"sum\",\"column\":\"amount\"},{\"fn\":\"count\"}],\"dimension\":{\"column\":\"region\"} or {\"column\":\"$created_at\",\"bucket\":\"week\"} or null,\"filters\":<condition tree or null>,\"date_range\":{\"relative\":\"last_30_days\"} or {\"from\":\"2026-08-01\",\"to\":\"2026-09-01\"},\"chart\":\"number|line|bar|stacked_bar|donut|table\"}. fn: count, count_distinct, sum, avg, min, max (the last four need a number/currency column). A dimension may be a select, boolean or relation field, or a date field with bucket day|week|month|quarter. No dimension gives one number. Optional keys: \"breakdown\":{\"column\":\"status\",\"top\":5} splits every row into per-value series (rows gain series:{value:{metrics}}, values past top fold into \"other\"); \"compare\":\"previous_period\"|\"previous_year\" re-runs the same query over the preceding window and adds previous/delta/delta_pct per metric on every row; a measure {\"fn\":\"formula\",\"expr\":\"sum_amount / count\",\"label\":\"Average order\"} is computed per row from the other measures' keys (+ - * / parentheses, percent_of(a,b); division by zero gives null); \"dimension\":{\"column\":\"region\",\"top\":6} with \"sort\":{\"by\":\"sum_amount\",\"dir\":\"desc\"} and \"limit\":20 cut a category dimension; a measure with \"as\":\"percent_of_total\" also answers <key>_pct per row. Example: {\"metrics\":[{\"fn\":\"sum\",\"column\":\"amount\"},{\"fn\":\"count\"},{\"fn\":\"formula\",\"expr\":\"sum_amount / count\",\"label\":\"Average\"}],\"dimension\":{\"column\":\"$created_at\",\"bucket\":\"week\"},\"compare\":\"previous_period\",\"date_range\":{\"relative\":\"last_90_days\"},\"chart\":\"line\"}. The definition is validated against the table before it is stored.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.reports.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "save_data_report arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "report_id": {
                                "type": "string",
                                "description": "Update this saved report instead of creating one."
                            },
                            "name": {
                                "type": "string",
                                "description": "Report name (required when creating)."
                            },
                            "description": {
                                "type": "string",
                                "description": "One line on what it shows."
                            },
                            "definition": {
                                "type": "string",
                                "description": "JSON report definition (required when creating)."
                            },
                            "is_pinned": {
                                "type": "boolean",
                                "description": "Pin it to the top of the Reports tab."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "schedule_data_report",
                    "root_name": "schedule_data_report",
                    "version": "65c1a73e",
                    "description": "Create, change or remove a standing export: the same spec rendered daily, weekly or monthly at a local time and delivered by WhatsApp, email or SMS (or just kept on the Exports page with deliver via none). spec is JSON in the export_data_records / export_data_report shape: {\"kind\":\"records\",\"table_id\":\"\u2026\",\"filter\":\u2026,\"columns\":[\u2026],\"format\":\"xlsx\",\"title\":\"\u2026\"} or {\"kind\":\"table_report\",\"table_id\":\"\u2026\",\"definition\":{\u2026}|\"saved_report_id\":\"\u2026\",\"format\":\"pdf\"} or {\"kind\":\"group_report\",\"group_id\":\"\u2026\",\"definition\":{\u2026}}. deliver is JSON {\"via\":\"whatsapp|email|sms|none\",\"to\":\"\u2026\",\"subject\":\"\u2026\",\"caption\":\"\u2026\"} \u2014 whatsapp and sms need to; the phone must already have a WhatsApp conversation for whatsapp. Pass schedule_id to change or (with delete = true) remove one; omit it to create. Without any argument but list = true, answers the existing schedules.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.reports.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "schedule_data_report arguments",
                        "type": "object",
                        "properties": {
                            "schedule_id": {
                                "type": "string",
                                "description": "An existing schedule to change or delete; omit to create."
                            },
                            "list": {
                                "type": "boolean",
                                "description": "true: just list the schedules."
                            },
                            "delete": {
                                "type": "boolean",
                                "description": "true with schedule_id: remove it."
                            },
                            "name": {
                                "type": "string",
                                "description": "What this schedule is for, e.g. \"Weekly orders to the owner\"."
                            },
                            "spec": {
                                "type": "string",
                                "description": "JSON export spec (see the tool description)."
                            },
                            "cadence": {
                                "type": "string",
                                "description": "daily, weekly or monthly."
                            },
                            "at": {
                                "type": "string",
                                "description": "Local time HH:MM, e.g. 08:00."
                            },
                            "weekday": {
                                "type": "integer",
                                "description": "Weekly: 1 (Monday) to 7 (Sunday)."
                            },
                            "day": {
                                "type": "integer",
                                "description": "Monthly: day of month 1\u201328."
                            },
                            "timezone": {
                                "type": "string",
                                "description": "IANA zone, default Africa/Dar_es_Salaam."
                            },
                            "deliver": {
                                "type": "string",
                                "description": "JSON {\"via\":\"whatsapp|email|sms|none\",\"to\":\"\u2026\",\"subject\":\"\u2026\",\"caption\":\"\u2026\"}."
                            },
                            "enabled": {
                                "type": "boolean",
                                "description": "false pauses the schedule."
                            }
                        }
                    }
                },
                {
                    "name": "set_data_record_file_from_url",
                    "root_name": "set_data_record_file_from_url",
                    "version": "27208ec0",
                    "description": "Put a file into a file field of one record by fetching it from a URL: the server downloads it, keeps a copy in this workspace and stores it on the record. Pass table_id, record_id, the file field's column key, and an http(s) url (a document link, a voicemail recording, a public image); a WhatsApp media id works too. Optional name sets the file name shown. A field that takes one file is replaced; a field that takes many gets the file added. The field's allowed types and size cap apply and a refusal names the field. The answer is the record with signed one-hour links (url, thumb) on every file value.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.records.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "set_data_record_file_from_url arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "record_id": {
                                "type": "string",
                                "description": "The record id."
                            },
                            "column": {
                                "type": "string",
                                "description": "Key of the file field."
                            },
                            "url": {
                                "type": "string",
                                "description": "http(s) URL of the file, or a WhatsApp media id."
                            },
                            "name": {
                                "type": "string",
                                "description": "File name to show (optional; taken from the URL otherwise)."
                            }
                        },
                        "required": [
                            "table_id",
                            "record_id",
                            "column",
                            "url"
                        ]
                    }
                },
                {
                    "name": "set_data_table_grant",
                    "root_name": "set_data_table_grant",
                    "version": "a578823b",
                    "description": "Decide who may see one table. subject_type is \"role\" (a role name like manager or agent), \"team\" (an agent group id) or \"user\" (a person id); level is none, view, edit or manage. Pass remove:true to take one line away. THE FIRST LINE ON A TABLE CHANGES IT for everybody: until then the table is unrestricted and whoever holds the Data permission sees it, and afterwards only the people named do. A line can only narrow what somebody already holds \u2014 granting \"manage\" to a viewer does not let them edit \u2014 and the account owner always keeps full access. get_data_table_governance reads the lines back and lists the roles, teams and people that exist.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "set_data_table_grant arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "The table id (uuid) or slug from list_data_tables."
                            },
                            "subject_type": {
                                "type": "string",
                                "description": "role, team or user."
                            },
                            "subject_id": {
                                "type": "string",
                                "description": "A role name, an agent group id, or a client user id. get_data_table_governance lists what exists."
                            },
                            "level": {
                                "type": "string",
                                "description": "none, view, edit or manage. Ignored when remove is true."
                            },
                            "remove": {
                                "type": "boolean",
                                "description": "Take this access line away instead of setting it."
                            }
                        },
                        "required": [
                            "table_id",
                            "subject_type",
                            "subject_id"
                        ]
                    }
                },
                {
                    "name": "set_data_table_group",
                    "root_name": "set_data_table_group",
                    "version": "1b04699f",
                    "description": "Move a table into a group, or out of any group (omit group_id, or pass an empty one). A table belongs to at most one group; moving it out of one folder into another is one call. Nothing about the table's fields or records changes \u2014 only where it is filed and which group reports can read it.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "set_data_table_group arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug to move the table into; leave empty to take it out of its group."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "set_data_table_retention",
                    "root_name": "set_data_table_retention",
                    "version": "4b2a90fe",
                    "description": "Set how long a table keeps its records. days is the age at which a record is past the window; action is \"delete\" (soft-delete the record) or \"anonymise\" (keep the row and empty the fields named in field_rules, so the counts still work but the person is out of them). field_rules is {\"<field key>\": \"clear\"|\"redact\"|\"hash\"} \u2014 redact and hash only fit text and email fields, everything else can only be cleared, and a required field cannot be cleared. Pass legal_hold:true to suspend the whole policy: the nightly sweep will record that it ran and changed nothing, which is what a hold has to look like. Pass remove:true to drop the policy. The sweep runs nightly; it never runs from this tool.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "set_data_table_retention arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "The table id (uuid) or slug from list_data_tables."
                            },
                            "days": {
                                "type": "integer",
                                "description": "Keep records this many days (1\u20133650), then act."
                            },
                            "action": {
                                "type": "string",
                                "description": "delete or anonymise."
                            },
                            "date_column": {
                                "type": "string",
                                "description": "Count the age from $created_at (default), $updated_at, or a date field of the table."
                            },
                            "field_rules": {
                                "type": "string",
                                "description": "JSON object for anonymise: {\"phone\":\"clear\",\"notes\":\"redact\",\"email\":\"hash\"}."
                            },
                            "enabled": {
                                "type": "boolean",
                                "description": "Set false to keep the policy but stop the sweep."
                            },
                            "legal_hold": {
                                "type": "boolean",
                                "description": "Suspend every sweep of this table while true."
                            },
                            "remove": {
                                "type": "boolean",
                                "description": "Drop the retention policy entirely."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "transition_data_record",
                    "root_name": "transition_data_record",
                    "version": "70bef6dc",
                    "description": "Move one record to another state \u2014 a booking to confirmed, an order to paid, a ticket to closed. Only the moves the table's owner declared are allowed: get_data_table_states says which state the record may go to next, and an illegal move (paid back to draft) is refused as a conflict with the legal ones named, nothing saved. Pass a reason and it goes in the record's history beside the change. Moving a record that is already in that state is not an error: it answers changed = false.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.records.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "transition_data_record arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "record_id": {
                                "type": "string",
                                "description": "The record id."
                            },
                            "to": {
                                "type": "string",
                                "description": "The state key to move into, from get_data_table_states (its label also works)."
                            },
                            "reason": {
                                "type": "string",
                                "description": "Why, in one line. Kept on the history entry so a person reading the trail later knows."
                            },
                            "column": {
                                "type": "string",
                                "description": "Which status field, when the table has more than one. Left out, the table's only status field is used."
                            }
                        },
                        "required": [
                            "table_id",
                            "record_id",
                            "to"
                        ]
                    }
                },
                {
                    "name": "update_data_column",
                    "root_name": "update_data_column",
                    "version": "8a9fdb36",
                    "description": "Change a field: its label, key, type, required flag or config. Only the arguments you pass change. Renaming the key or changing the type is refused while the field has an index (drop it first from the Fields tab); changing a type keeps old values, and values that no longer fit read as empty. Types: text, long_text, number, currency, boolean, date, datetime, phone, email, select, multi_select, status, relation, file, auto_number. Config by type \u2014 number/currency: {precision, unit|currency, min, max}; select/multi_select: {options:[{key,label,color}]}; status: {states:[{key,label,color,initial,final}],transitions:[{from:'<key>|*',to:'<key>',label,requires}],strict:true} \u2014 a state machine, so a record may only be created in an initial state and only move along a declared transition (get_data_table_states reads one back); relation: {target_table_id}; phone: {default_region:\"TZ\"}; auto_number: {prefix:\"ORD-\", pad:6, yearly:false} (the platform assigns the value; never send one); any: {default, ui:{is_title_field, is_summary_metric, help_text, placeholder, hidden_in_grid, hidden_in_form}}.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_data_column arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "column": {
                                "type": "string",
                                "description": "The field key or id."
                            },
                            "label": {
                                "type": "string",
                                "description": "New label."
                            },
                            "key": {
                                "type": "string",
                                "description": "New key (refused while indexed)."
                            },
                            "type": {
                                "type": "string",
                                "description": "New type (refused while indexed)."
                            },
                            "required": {
                                "type": "boolean",
                                "description": "Whether a value is required."
                            },
                            "config": {
                                "type": "string",
                                "description": "JSON object replacing the type settings."
                            }
                        },
                        "required": [
                            "table_id",
                            "column"
                        ]
                    }
                },
                {
                    "name": "update_data_group",
                    "root_name": "update_data_group",
                    "version": "4940fcab",
                    "description": "Rename or restyle a table group (folder): its name, one-line description, emoji icon or palette colour. Only the arguments you pass change; the slug, the member tables and the group's saved reports stay. Use it when the person wants the folder called something else or coloured differently \u2014 to add or remove tables use set_data_table_group, to change their order use reorder_data_group_tables.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_data_group arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "string",
                                "description": "Group id or slug from list_data_groups."
                            },
                            "name": {
                                "type": "string",
                                "description": "New human name (at most 80 characters)."
                            },
                            "description": {
                                "type": "string",
                                "description": "New one-line description; pass an empty string to clear it."
                            },
                            "icon": {
                                "type": "string",
                                "description": "New emoji icon; pass an empty string to clear it."
                            },
                            "color": {
                                "type": "string",
                                "description": "A palette key (gray, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose); pass an empty string to clear it."
                            }
                        },
                        "required": [
                            "group_id"
                        ]
                    }
                },
                {
                    "name": "update_data_record",
                    "root_name": "update_data_record",
                    "version": "df2cf0c4",
                    "description": "Change some fields of one record by id. Only the keys you pass change; pass null to clear a field. Values are checked against each field's type: numbers as numbers, booleans as true/false, dates as YYYY-MM-DD, datetimes as ISO-8601, phones in any Tanzanian form (0712\u2026, +255\u2026), select values as option keys. A required field missing, a unique field clashing, or a value of the wrong shape is refused with the field named, and nothing is saved.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.records.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_data_record arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "record_id": {
                                "type": "string",
                                "description": "The record id."
                            },
                            "data": {
                                "type": "string",
                                "description": "JSON object of the fields to change."
                            }
                        },
                        "required": [
                            "table_id",
                            "record_id",
                            "data"
                        ]
                    }
                },
                {
                    "name": "update_data_table",
                    "root_name": "update_data_table",
                    "version": "8a4cba00",
                    "description": "Rename or re-describe a table: its human name, one-line description or emoji icon. Only the arguments you pass change; the slug, the fields and the records stay exactly as they are, so flows keep working. Use it when the person wants \"Wateja\" called \"Customers\" or wants a table to explain itself on the Data page \u2014 not to change fields (update_data_column) or to move it into a folder (set_data_table_group).",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_data_table arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "name": {
                                "type": "string",
                                "description": "New human name (at most 80 characters)."
                            },
                            "description": {
                                "type": "string",
                                "description": "New one-line description; pass an empty string to clear it."
                            },
                            "icon": {
                                "type": "string",
                                "description": "New emoji icon; pass an empty string to clear it."
                            }
                        },
                        "required": [
                            "table_id"
                        ]
                    }
                },
                {
                    "name": "upsert_business_rule",
                    "root_name": "upsert_business_rule",
                    "version": "5bd88e37",
                    "description": "Create or change a business rule \u2014 what this business enforces, everywhere at once. A rule saved here is read by every message flow, phone menu and assistant that asks for its key, so changing one changes real outcomes for real customers: what they are charged, how often they may do something, whether they qualify, and when you are open. Confirm the numbers with the account owner before you save.\n\nPass the key to change an existing rule; the kind of an existing rule cannot change, because callers depend on the shape of its answer. `definition` is a JSON object whose shape depends on the kind:\n- limit: {max, per: hour|day|week|month|rolling|total, rolling_seconds?, table_id?, subject_column?, subject_input?, where?, used_input?, reason?}\n- fee: {expression: \"amount * 0.03\", tiers?: [{above, expression}], tier_input?, min?, max?, round?, currency?}\n- eligibility: {subject: inputs|record, table_id?, match_column?, match_input?, record_id_input?, condition: {all: [{column, op, value}]}, reason?}\n- window: {source: schedule|ivr_schedule|inline, schedule_id?, ivr_schedule_id?, timezone?, slots?: [{day, start, end}], holidays?: [{date, name}], reason?}\n- choice: {input, map: {value: outcome}, rules?: [{when, then}], default?}\n\nA definition that cannot run is refused with the field named, and nothing is saved.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "rules.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "upsert_business_rule arguments",
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string",
                                "description": "The name a flow asks for, e.g. daily_withdrawals. Pass an existing key to change that rule."
                            },
                            "label": {
                                "type": "string",
                                "description": "What a person reads, e.g. \"Daily withdrawals\"."
                            },
                            "kind": {
                                "type": "string",
                                "description": "limit, fee, eligibility, window or choice. Required when creating; cannot change afterwards."
                            },
                            "definition": {
                                "type": "string",
                                "description": "A JSON object shaped for the kind \u2014 see the description."
                            },
                            "description": {
                                "type": "string",
                                "description": "One sentence on why this rule exists."
                            },
                            "enabled": {
                                "type": "boolean",
                                "description": "Whether the rule is enforced (default true)."
                            }
                        },
                        "required": [
                            "key"
                        ]
                    }
                },
                {
                    "name": "upsert_data_record",
                    "root_name": "upsert_data_record",
                    "version": "51a83380",
                    "description": "Create or update a record matched on one unique field \u2014 the right call for \"save this customer by phone\". match_column must be a field marked unique in get_data_table_schema; data must contain it. If a record with that value exists it is updated with the other keys, otherwise one is created. Values are checked against each field's type: numbers as numbers, booleans as true/false, dates as YYYY-MM-DD, datetimes as ISO-8601, phones in any Tanzanian form (0712\u2026, +255\u2026), select values as option keys. A required field missing, a unique field clashing, or a value of the wrong shape is refused with the field named, and nothing is saved. Pass an idempotency_key (any string you make up, e.g. a UUID) when a retry must not create a second copy: the first successful answer is kept for 24 hours and replayed for the same key, so a call that timed out can be repeated safely.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "data.records.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "upsert_data_record arguments",
                        "type": "object",
                        "properties": {
                            "table_id": {
                                "type": "string",
                                "description": "Table id or slug."
                            },
                            "match_column": {
                                "type": "string",
                                "description": "The unique field to match on, e.g. phone."
                            },
                            "data": {
                                "type": "string",
                                "description": "JSON object including the match field."
                            },
                            "idempotency_key": {
                                "type": "string",
                                "description": "Optional: a key you make up so a retried call is replayed rather than run twice (kept 24 h)."
                            }
                        },
                        "required": [
                            "table_id",
                            "match_column",
                            "data"
                        ]
                    }
                }
            ]
        },
        {
            "key": "approvals",
            "name": "Approvals",
            "summary": "Decisions a person has been asked for before something happens: read the queue, read one in full with every comment on it, answer one.",
            "path": "/mcp/v1/approvals",
            "module": "approvals",
            "instructions": "Decisions somebody in this business has been asked for before something happens: a discount, a refund, a payout, a record leaving draft. \n\nOrder of work: list_approvals shows what is waiting on the person you are acting for. get_approval reads one in full \u2014 what is being asked, the amount, who else has answered and what they said. decide_approval answers it. \n\nTwo things gate every decision, and neither is negotiable. The person you act for must hold the permission, AND the approval's own policy must name them as an approver \u2014 `can_decide` on each row is the honest answer. A refusal that says \"this approval is not addressed to you\" is not a bug and not something to work around. \n\nAn approval exists because a business wanted a HUMAN to decide. Do not answer one on your own initiative: read it out, say plainly what approving would set in motion, and let the person tell you what to do. A settled approval cannot be un-settled by anybody, including us.",
            "tools": [
                {
                    "name": "get_approval",
                    "root_name": "get_approval",
                    "version": "cc487b47",
                    "description": "Read one approval in full: what is being asked and why, the amount if there is one, who it is waiting on, every decision already recorded with its comment, and when it runs out of time.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "approvals.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_approval arguments",
                        "type": "object",
                        "properties": {
                            "approval_id": {
                                "type": "integer",
                                "description": "The approval to read."
                            }
                        },
                        "required": [
                            "approval_id"
                        ]
                    }
                },
                {
                    "name": "list_approvals",
                    "root_name": "list_approvals",
                    "version": "8da9cca8",
                    "description": "List approval requests in this workspace: what is waiting on you right now, what has been settled, and who said what. Defaults to the ones waiting on you, because that is the only list anybody can act on.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "approvals.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_approvals arguments",
                        "type": "object",
                        "properties": {
                            "waiting_on_me": {
                                "type": "boolean",
                                "description": "Only approvals this person may answer right now. Defaults to true when no state is given."
                            },
                            "state": {
                                "type": "string",
                                "description": "pending | approved | rejected | expired | all. Ignored when waiting_on_me is true."
                            },
                            "kind": {
                                "type": "string",
                                "description": "Only approvals of this kind, as the requester labelled them."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "At most this many, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "decide_approval",
                    "root_name": "decide_approval",
                    "version": "0b1c26a1",
                    "description": "Approve or reject one approval, as the person this connection belongs to, with an optional comment. This is a real-world act: whatever was held waiting for a person now proceeds, or does not. Read the approval first and say what you are about to do.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "approvals.decide"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "decide_approval arguments",
                        "type": "object",
                        "properties": {
                            "approval_id": {
                                "type": "integer",
                                "description": "The approval to answer."
                            },
                            "decision": {
                                "type": "string",
                                "description": "approve or reject."
                            },
                            "comment": {
                                "type": "string",
                                "description": "Why. Kept on the record and shown to whoever asked. Say something: a bare refusal helps nobody."
                            }
                        },
                        "required": [
                            "approval_id",
                            "decision"
                        ]
                    }
                }
            ]
        },
        {
            "key": "payments",
            "name": "Payments",
            "summary": "Money this business collects from its customers: what has been asked for and where each one got to, one payment's whole timeline, asking a customer to pay, and refunds. Not the business's own Momo bill.",
            "path": "/mcp/v1/payments",
            "module": "payments",
            "instructions": "Money this business collects from ITS customers, and money it gives back. This is NOT the business's own Momo bill \u2014 that lives on the overview server. Getting the two the wrong way round would tell somebody their customers owe them what they in fact owe us.\n\nOrder of work: list_payments to see what has been asked for and where it got to, get_payment to read one in full with its whole timeline, create_payment_intent to ask a customer to pay, refund_payment to give money back.\n\nAMOUNTS ARE WHOLE NUMBERS OF THE SMALLEST UNIT. 40000 means TZS 400.00. Every payment comes back with both the integer and a formatted string; use the string when you speak, and never convert between them yourself. A decimal is refused rather than rounded.\n\nA PAYMENT ASKED FOR IS NOT A PAYMENT RECEIVED. `pending` means the customer has been asked and has not answered. Only `paid`, `partly_refunded` and `refunded` mean money arrived. Never tell a customer their payment went through because a tool call succeeded \u2014 check the state.\n\nRefunds always go to a person. refund_payment writes the refund down and raises an approval; no money moves until somebody in the business answers it. Say that plainly rather than telling the customer their money is on its way. Payouts \u2014 money leaving the business \u2014 cannot be started from here at all, by design.",
            "tools": [
                {
                    "name": "get_payment",
                    "root_name": "get_payment",
                    "version": "0d023fe6",
                    "description": "Read one payment in full: the amount, who was asked, where it got to, everything that has happened to it in order, what it wrote in the books, and any refunds raised against it. Accepts the payment id or its human reference like PAY-20260908-0042.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "payments.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_payment arguments",
                        "type": "object",
                        "properties": {
                            "payment_id": {
                                "type": "string",
                                "description": "The payment id, or its reference (PAY-YYYYMMDD-NNNN)."
                            }
                        },
                        "required": [
                            "payment_id"
                        ]
                    }
                },
                {
                    "name": "list_payments",
                    "root_name": "list_payments",
                    "version": "cb38d765",
                    "description": "List payments this business has asked its customers for: what was asked, who was asked, and where each one got to. Defaults to the ones still waiting on a customer, because those are the only ones anybody can act on. Amounts come back both as a whole number of the smallest unit and as a formatted string \u2014 never divide or multiply them yourself.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "payments.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_payments arguments",
                        "type": "object",
                        "properties": {
                            "state": {
                                "type": "string",
                                "description": "open (the default) | all | draft | pending | authorised | paid | failed | expired | cancelled | refunded | partly_refunded."
                            },
                            "subject_id": {
                                "type": "string",
                                "description": "Only payments raised for this record \u2014 an order id, a Daftari record id, an invoice number."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "At most this many, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "create_payment_intent",
                    "root_name": "create_payment_intent",
                    "version": "8a37cd6f",
                    "description": "Ask a customer to pay: create a payment and, unless told not to, send the request \u2014 a mobile money prompt to their phone, or a checkout link to give them. This asks a real person for real money, so only use it when the customer has agreed to pay now. The amount is a whole number of the currency's smallest unit (40000 is TZS 400.00), never a decimal.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "payments.collect"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_payment_intent arguments",
                        "type": "object",
                        "properties": {
                            "amount_minor": {
                                "type": "integer",
                                "description": "A whole number of the smallest currency unit. 40000 means TZS 400.00. Never a decimal."
                            },
                            "currency": {
                                "type": "string",
                                "description": "ISO code, e.g. TZS. Defaults to TZS."
                            },
                            "payer_name": {
                                "type": "string",
                                "description": "Who is paying, as they would like to be addressed."
                            },
                            "payer_phone": {
                                "type": "string",
                                "description": "Their mobile money number. Required for a phone prompt."
                            },
                            "payer_email": {
                                "type": "string",
                                "description": "Their email, for a card or bank checkout receipt."
                            },
                            "method": {
                                "type": "string",
                                "description": "ussd_push (a PIN prompt on their phone), link (a checkout page) or lipa (a short number they pay from any wallet app). Defaults to link."
                            },
                            "subject_type": {
                                "type": "string",
                                "description": "What is being paid for, e.g. an order or table name."
                            },
                            "subject_id": {
                                "type": "string",
                                "description": "The id of the thing being paid for."
                            },
                            "send": {
                                "type": "boolean",
                                "description": "Ask the customer now. True by default; pass false to write the payment down without contacting anybody."
                            },
                            "idempotency_key": {
                                "type": "string",
                                "description": "Your own key for this request. The same key always returns the same payment rather than asking twice."
                            }
                        },
                        "required": [
                            "amount_minor"
                        ]
                    }
                },
                {
                    "name": "refund_payment",
                    "root_name": "refund_payment",
                    "version": "4dfb4eed",
                    "description": "Give a customer their money back, in full or in part. This raises a refund and asks a person in the business to approve it before any money moves \u2014 you cannot complete a refund on your own, and that is deliberate. Read the payment first with get_payment, say plainly what you are about to refund and why, and let the person decide.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "payments.refund"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "refund_payment arguments",
                        "type": "object",
                        "properties": {
                            "payment_id": {
                                "type": "string",
                                "description": "The payment to refund \u2014 its id or its reference (PAY-YYYYMMDD-NNNN)."
                            },
                            "reason": {
                                "type": "string",
                                "description": "Why. Kept on the record, shown to whoever approves it, and the first thing anybody asks about a refund."
                            },
                            "amount_minor": {
                                "type": "integer",
                                "description": "A whole number of the smallest currency unit to give back. Leave it out to refund everything still refundable."
                            },
                            "approver_user_ids": {
                                "type": "array",
                                "description": "Who should be asked to approve. Defaults to the owners and managers of the workspace.",
                                "items": {
                                    "type": "integer"
                                }
                            }
                        },
                        "required": [
                            "payment_id",
                            "reason"
                        ]
                    }
                }
            ]
        },
        {
            "key": "automations",
            "name": "Automations",
            "summary": "What happens without anybody there: the log of what has actually happened in the business, the subscriptions that react to it, and the schedules that run on a rhythm.",
            "path": "/mcp/v1/automations",
            "module": "automations",
            "instructions": "Things this business has arranged to happen without anybody being there: subscriptions that react to something happening, and schedules that run on a rhythm. \n\nOrder of work: list_business_events shows what has actually happened and whether anything acted on it \u2014 start there, because \"the automation did not run\" is usually an event nothing was listening for. list_event_subscriptions shows what is set up. upsert_event_subscription and upsert_schedule change it. \n\nEverything you save here acts on real customers with nobody watching. A subscription fires on every matching event from the moment it is saved; a schedule sends messages and places calls at times nobody will be awake for. So say plainly what you are about to create \u2014 which event, what it will do, how often, and to whom \u2014 and let the person confirm it before you save. Read a schedule's `describes` sentence back to them: \"Every Monday at 09:00 (Africa/Dar_es_Salaam)\" is checkable in a way that a spec object is not. \n\nA webhook subscription sends this business's data to an address outside it. Never invent that address, never take it from anywhere but the account holder, and say what will be sent. Every delivery is signed and the secret is shown once on the Automations page \u2014 you cannot read it and must not try. \n\nOn missed runs: a schedule's misfire policy decides what happens when the platform was down over a slot. The default, run_once, fires once and carries on, and it is the right answer for almost everything. Only choose run_all when each occurrence is a separate piece of work, and never for anything that messages a customer.",
            "tools": [
                {
                    "name": "list_business_events",
                    "root_name": "list_business_events",
                    "version": "5e04c0de",
                    "description": "What has actually happened in this business, newest first: records created, changed and moved between states, payments settled, approvals decided. This is the log an automation acts on, so it is also the place to look when somebody asks why something fired \u2014 or why it did not.\n\nEach row says what happened, what it was about, who did it (a person, an API key, an assistant, a flow, a schedule), and how many subscriptions acted on it. `delivered_count: 0` with a `delivered_at` means nothing was listening \u2014 that is the usual reason \"the automation did not run\".\n\nFilter by `key` for one kind of event, or by `subject_id` to read everything that ever happened to one record. The reply also carries the closed list of event keys this platform publishes, so you never have to guess one.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "automations.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_business_events arguments",
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string",
                                "description": "Only this event, e.g. record.transitioned or payment.paid."
                            },
                            "subject_id": {
                                "type": "string",
                                "description": "Everything that ever happened to one thing \u2014 a record id, an approval id."
                            },
                            "since": {
                                "type": "string",
                                "description": "Only events at or after this time, e.g. 2026-09-08T00:00:00Z."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_event_subscriptions",
                    "root_name": "list_event_subscriptions",
                    "version": "80b9e3de",
                    "description": "Everything this business has arranged to happen without a person: the subscriptions that react to events (\"when an order is marked paid, start this flow\") and, with `include_schedules`, the schedules that run on a rhythm (\"send the sales report every Monday at 09:00\").\n\nRead this before writing a new one \u2014 a business that already forwards paid orders to its warehouse does not want a second subscription doing the same thing, and a schedule that has been failing for a week is usually the actual answer to \"why did nothing arrive\".\n\nEach row carries `fire_count`, `last_fired_at`, and `last_error` when the last attempt failed. A subscription switched off by repeated failures says so in `last_error`; switching it back on clears the counter.\n\nSigning secrets are never returned. `signed: true` says a webhook is signed; the secret itself is shown once, on the Automations page.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "automations.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_event_subscriptions arguments",
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string",
                                "description": "Only subscriptions listening for this event (wildcard rows are always included)."
                            },
                            "kind": {
                                "type": "string",
                                "description": "Only this kind: flow, notification, webhook or agent."
                            },
                            "enabled_only": {
                                "type": "boolean",
                                "description": "Leave out the ones that are switched off."
                            },
                            "include_schedules": {
                                "type": "boolean",
                                "description": "Also return the schedules that run on a rhythm."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many subscriptions to return (default 50, max 200)."
                            }
                        }
                    }
                },
                {
                    "name": "upsert_event_subscription",
                    "root_name": "upsert_event_subscription",
                    "version": "6ab49195",
                    "description": "Create, change or remove a subscription: \"when this happens in the business, do that\". What you save here acts on real events without anybody watching, so confirm the details with the account holder before saving one \u2014 especially a webhook, which sends this business's data to an address outside it.\n\n`key` is one of the events this platform publishes (list_business_events returns them all), or `*` for every event. `kind` decides what `target` means and cannot change once saved:\n\n- flow \u2014 target is a flow id. The event must name a conversation for the flow to talk into; set config.conversation_path to the field that carries one.\n- notification \u2014 target is who to tell: user ids separated by commas, a role name, or * for everybody. It goes through the notification engine, so people's own preferences, quiet hours and opt-outs all still apply.\n- webhook \u2014 target is an https URL. Every delivery is signed (HMAC-SHA256 over \"<timestamp>.<raw body>\" in the X-Momo-Signature header) and retried on a temporary failure; the secret is shown once, on the Automations page. Addresses inside our own network are refused.\n- agent \u2014 target is an assistant id, and config.prompt is the standing instruction it gets.\n\n`filter` narrows it to matching events, written as a condition over the event: {\"all\":[{\"column\":\"record.status\",\"op\":\"equals\",\"value\":\"paid\"}]}. Read fields with dots \u2014 record.status, table.slug, changes.status.to.\n\nPass `id` to change one, or `id` with `delete: true` to remove it. A subscription that has failed ten times in a row switches itself off; saving it with enabled: true clears that.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "automations.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "upsert_event_subscription arguments",
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "integer",
                                "description": "An existing subscription to change or delete; omit to create."
                            },
                            "delete": {
                                "type": "boolean",
                                "description": "With id, remove that subscription."
                            },
                            "key": {
                                "type": "string",
                                "description": "The event to listen for: record.created, record.updated, record.deleted, record.transitioned, payment.paid, payment.failed, payment.refunded, order.completed, approval.requested, approval.settled, booking.confirmed, ticket.opened, ticket.closed, call.completed, message.received, operation.completed, operation.failed, or * for all."
                            },
                            "kind": {
                                "type": "string",
                                "description": "flow, notification, webhook or agent. Required when creating; cannot change afterwards."
                            },
                            "target": {
                                "type": "string",
                                "description": "What to act on \u2014 a flow id, who to tell, a URL, or an assistant id. See the description."
                            },
                            "label": {
                                "type": "string",
                                "description": "What a person reads, e.g. \"Paid orders to the warehouse\"."
                            },
                            "filter": {
                                "type": "string",
                                "description": "A JSON condition over the event; leave out to fire on every one."
                            },
                            "config": {
                                "type": "string",
                                "description": "A JSON object of per-kind extras \u2014 title/body for a notification, prompt for an agent, conversation_path for a flow."
                            },
                            "enabled": {
                                "type": "boolean",
                                "description": "Whether it is switched on (default true)."
                            },
                            "rotate_secret": {
                                "type": "boolean",
                                "description": "For a webhook: issue a new signing secret. The old one stops working immediately."
                            }
                        }
                    }
                },
                {
                    "name": "upsert_schedule",
                    "root_name": "upsert_schedule",
                    "version": "a6a45b47",
                    "description": "Create, change or remove a schedule: something this business does again, on a rhythm, with nobody there. A schedule sends real messages, places real calls and writes real rows, so confirm the times and the recipients with the account holder before saving one.\n\n`kind` decides what it does and cannot change once saved:\n- report \u2014 render an export and deliver it. payload: {\"export\": {\u2026the export_data_records / export_data_report spec\u2026}, \"deliver\": {\"via\":\"whatsapp|email|sms|none\",\"to\":\"\u2026\"}}\n- record \u2014 write a row into a table. target is the table id; payload: {\"record\": {field: value}}. {{slot_date}} and {{slot_time}} become the run's own date and time.\n- message \u2014 send one SMS or WhatsApp. target is the phone number; payload: {\"channel\":\"sms|whatsapp\",\"body\":\"\u2026\"}\n- flow \u2014 start a message flow. target is the flow id; payload: {\"conversation_id\": 123}\n- call \u2014 ring everybody in a contact group. target is the contact group id; payload: {\"call_mode\":\"ai|human\",\"agent_config_id\":\u2026}\n\n`spec` is the rhythm: {\"every\":1,\"unit\":\"minutes|hours|days|weeks|months\",\"at\":\"08:00\",\"weekdays\":[1,3,5],\"day_of_month\":1,\"timezone\":\"Africa/Dar_es_Salaam\",\"until\":\"2026-12-31\",\"count\":10}. `at` applies to days, weeks and months; `weekdays` (1 = Monday) to weeks; `day_of_month` (1\u201328) to months. The shortest interval is every 5 minutes. Left out, the timezone is the account's own.\n\n`misfire_policy` says what happens to runs missed while the platform was down: run_once (fire once and carry on \u2014 the default and almost always right), skip (do not fire at all), run_all (catch up, capped). Sixty missed minutes must never become sixty messages.\n\nPass `id` to change one, or `id` with `delete: true` to remove it.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "automations.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "upsert_schedule arguments",
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string",
                                "description": "An existing schedule to change or delete; omit to create."
                            },
                            "delete": {
                                "type": "boolean",
                                "description": "With id, remove that schedule."
                            },
                            "name": {
                                "type": "string",
                                "description": "What a person reads, e.g. \"Monday sales report\"."
                            },
                            "kind": {
                                "type": "string",
                                "description": "report, record, message, flow or call. Required when creating; cannot change afterwards."
                            },
                            "spec": {
                                "type": "string",
                                "description": "A JSON object describing the rhythm \u2014 see the description."
                            },
                            "target": {
                                "type": "string",
                                "description": "What it acts on: a table id, a phone number, a flow id, a contact group id. Not used by report."
                            },
                            "payload": {
                                "type": "string",
                                "description": "A JSON object of the kind's own arguments \u2014 see the description."
                            },
                            "misfire_policy": {
                                "type": "string",
                                "description": "run_once (default), skip or run_all."
                            },
                            "enabled": {
                                "type": "boolean",
                                "description": "Whether it runs (default true)."
                            }
                        }
                    }
                }
            ]
        },
        {
            "key": "alerts",
            "name": "Alerts & service levels",
            "summary": "The business watching itself: the alert rules it wrote, the service-level promises and the clocks running against them, the risk rules that hold or refuse an action, and one log of everything that fired \u2014 including what reached nobody.",
            "path": "/mcp/v1/alerts",
            "module": "alerts",
            "instructions": "How this business watches itself: alert rules that fire when something goes over a limit, service levels that put a clock on how quickly things happen, and risk rules that hold or refuse an action before it goes ahead. \n\nOrder of work: list_alerts first. It is the log of everything all three engines have actually raised, and most questions (\"did anything go wrong last night?\", \"why was I not told?\") are answered there without changing anything. Look at the `delivery` block on each one \u2014 an alert whose state is not `delivered` FIRED but reached nobody, and the reason says whether that was no recipients, no channel switched on, or a send that failed. That is usually the real answer to \"the alerting is not working\". \n\nlist_alert_rules shows what is being watched and what each rule is reading RIGHT NOW (`last_value` against `threshold`), so you can say \"it is at three of ten\" rather than only \"it has not fired\". A rule carrying `last_error` is broken, not quiet \u2014 say so plainly. \n\nlist_sla_clocks shows what is running against a promise, worst first. list_risk_decisions shows what was allowed, held, blocked or flagged, and every decision carries the inputs it was given and what each rule read. Quote those when somebody asks why something was refused; never guess at a reason. \n\nOn writing. upsert_alert_rule, upsert_sla_policy and upsert_risk_rule all change what happens to real traffic with nobody watching, and two of them are stronger than they look: a service level whose breach action is `transition` MOVES A RECORD on its own, and a risk rule whose action is `block` REFUSES A REAL CUSTOMER. Say plainly what you are about to create \u2014 what is watched, what counts as bad, who is told, and what happens \u2014 and let the person confirm before you save. \n\nOn dedupe, because it is the thing people misread: an alert rule fires at most once per window. A rule with a sixty-minute window fires once an hour however bad the hour is, and again the next hour if it is still bad. If somebody says \"it only told me once\", that is the design and the `dedupe_minutes` field is how to change it.",
            "tools": [
                {
                    "name": "list_alert_rules",
                    "root_name": "list_alert_rules",
                    "version": "25f90218",
                    "description": "What this business has asked to be watched: its alert rules, and \u2014 with `include_policies` \u2014 its service-level promises and risk rules too, since a person asking \"what are we watching for?\" means all three.\n\nEach alert rule carries `describes`, one checkable sentence (\"Tell me when failed notifications goes over 5 in an hour\"), and `last_value` against `threshold`, which is what it is reading right now. That lets you answer \"it is at three of ten\" instead of only \"it has not fired\" \u2014 a healthy rule and a broken one produce the same silence otherwise.\n\nA rule with `last_error` set is BROKEN, not quiet: it could not be measured at all. Say that plainly rather than reporting it as fine.\n\n`dedupe_minutes` is how long a rule stays quiet after firing, defaulting to its window. It is why a rule fires once about a bad hour rather than twelve times.\n\nThe reply also carries the closed list of alert kinds this platform can measure, so you never have to guess one.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "alerts.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_alert_rules arguments",
                        "type": "object",
                        "properties": {
                            "enabled_only": {
                                "type": "boolean",
                                "description": "Only rules that are switched on."
                            },
                            "include_policies": {
                                "type": "boolean",
                                "description": "Also return the service-level promises and the risk rules."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many alert rules to return (default 50, max 200)."
                            }
                        }
                    }
                },
                {
                    "name": "list_alerts",
                    "root_name": "list_alerts",
                    "version": "7975b73d",
                    "description": "Everything this business's own watching has raised, newest first: alert rules that went over a limit, service levels that were warned about, breached or escalated, and risk rules that flagged or refused something. All three engines write here, so this is the one place to answer \"did anything go wrong?\".\n\nRead the `delivery` block on every row. An alert whose state is not `delivered` FIRED and reached nobody, and `delivery.reason` says which of the three reasons it was: nobody was named or nobody named is still active, no channel is switched on for this workspace, or the send itself failed. That is nearly always the real answer when somebody says the alerting is not working \u2014 the noticing worked and the telling did not.\n\n`undelivered_only: true` narrows to exactly those. Start there when the complaint is \"I was never told\".",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "alerts.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_alerts arguments",
                        "type": "object",
                        "properties": {
                            "kind": {
                                "type": "string",
                                "description": "Only this kind, e.g. notification_failure, sla_breach, risk_block."
                            },
                            "rule_key": {
                                "type": "string",
                                "description": "Only alerts raised by this rule or policy key."
                            },
                            "undelivered_only": {
                                "type": "boolean",
                                "description": "Only the ones that fired and reached nobody."
                            },
                            "since": {
                                "type": "string",
                                "description": "Only alerts at or after this time, e.g. 2026-09-09T00:00:00Z."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_risk_decisions",
                    "root_name": "list_risk_decisions",
                    "version": "37a24f19",
                    "description": "Every risk decision this business made, newest first: what was allowed, what was flagged, what was held for a person to approve, and what was refused outright.\n\nEvery row carries its own explanation and you should quote it rather than guess. `inputs` is what the engine was given \u2014 the amount, the customer, the device, the country. `signals` is every rule that ran, what it READ, what it compared that against, and whether it tripped. When somebody asks why a payment was refused, the answer is in those two fields and nowhere else.\n\n`decision: hold` means the action has NOT gone ahead and has NOT been refused: somebody is being asked, and `approval_id` names the approval they are answering. Never report a hold as an allow.\n\nA signal marked `errored` did not find anything \u2014 it could not be read at all \u2014 and is deliberately never treated as a trip. A decision made while a signal was erroring is worth mentioning.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "alerts.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_risk_decisions arguments",
                        "type": "object",
                        "properties": {
                            "decision": {
                                "type": "string",
                                "description": "allow, alert, hold or block."
                            },
                            "subject_id": {
                                "type": "string",
                                "description": "Everything ever decided about one thing \u2014 a payment id, a record id."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_sla_clocks",
                    "root_name": "list_sla_clocks",
                    "version": "b030173a",
                    "description": "What is running against a service-level promise right now, worst first \u2014 the orders, tickets and records whose clock is closest to running out.\n\n`pct_used` is the number that matters: 80 means four fifths of the promised time is gone. `state` is where it has got to \u2014 running, warned, breached, escalated. `elapsed_minutes` counts only the time the clock was actually running, so under a working-hours policy a weekend adds nothing to it, and comparing `started_at` with the wall clock will disagree with this number on purpose.\n\n`outcome.transition` appears on a breached clock whose policy moves the record on. When `applied` is false the state machine REFUSED the move and `refused` says why \u2014 the breach still stands, and the record did not move. That is a fact somebody needs to know rather than assume.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "alerts.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_sla_clocks arguments",
                        "type": "object",
                        "properties": {
                            "state": {
                                "type": "string",
                                "description": "running, warned, breached, escalated, met or stopped. Omit for everything still ticking."
                            },
                            "policy_key": {
                                "type": "string",
                                "description": "Only clocks under this policy."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "upsert_alert_rule",
                    "root_name": "upsert_alert_rule",
                    "version": "71beb6d1",
                    "description": "Create or change an alert rule: what to watch, what counts as too much, over what window, and who to tell. Matched on `key` \u2014 the same key updates the rule that already has it.\n\nSay plainly what you are about to create before you save it: what is watched, the limit, the window, and who will be woken. It fires against real traffic from the moment it is saved.\n\nTwo things people get wrong and you should state rather than let them discover:\n\n- The threshold is STRICTLY over. \"Over 5\" does not fire at 5.\n- A rule fires at most once per window. A sixty-minute window means one alert an hour however bad the hour is, and again next hour if it is still bad. `dedupe_minutes` changes that quiet period; leaving it out ties it to the window, which is what makes one bad hour one alert.\n\n`kind` cannot be changed on an existing rule \u2014 the whole `config` shape belongs to the kind, and the alerts already logged were measured against the old one. Make a new rule with a new key instead.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "alerts.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "upsert_alert_rule arguments",
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string",
                                "description": "The rule's handle. An existing one is updated."
                            },
                            "kind": {
                                "type": "string",
                                "description": "What to watch: failure_rate, stuck_state, queue_depth, payment_stuck, flow_broken, tool_outage, notification_failure. Cannot change later."
                            },
                            "threshold": {
                                "type": "number",
                                "description": "Fire when the reading goes STRICTLY over this. A count for most kinds; a percentage for failure_rate."
                            },
                            "window_minutes": {
                                "type": "integer",
                                "description": "How far back to look, 5 minutes to 14 days."
                            },
                            "label": {
                                "type": "string",
                                "description": "What a person sees. Defaults to the kind's own name."
                            },
                            "dedupe_minutes": {
                                "type": "integer",
                                "description": "How long it stays quiet after firing. Defaults to the window."
                            },
                            "config": {
                                "type": "object",
                                "description": "Per-kind settings, e.g. {\"source\":\"notifications\"} or {\"table_id\":\"orders\",\"status\":\"confirmed\",\"minutes\":120}."
                            },
                            "recipients": {
                                "type": "object",
                                "description": "Who to tell: {\"users\":[1,2]} or {\"roles\":[\"owner\"]}. Omit to tell everybody in the workspace."
                            },
                            "enabled": {
                                "type": "boolean",
                                "description": "Switch it on or off."
                            }
                        },
                        "required": [
                            "key",
                            "kind",
                            "threshold",
                            "window_minutes"
                        ]
                    }
                },
                {
                    "name": "upsert_risk_rule",
                    "root_name": "upsert_risk_rule",
                    "version": "e3603b9d",
                    "description": "Create or change a risk rule: what to look at before something goes ahead, and what to do when it looks wrong. Matched on `key`.\n\nTHIS IS THE STRONGEST WRITE ON THIS SERVER. A rule whose action is `block` REFUSES A REAL CUSTOMER, and one whose action is `hold` stops the action and raises an approval a person has to answer. Say exactly what the rule will catch and what it will do, and let the account holder confirm before you save.\n\nThe four signals:\n\n- `velocity` \u2014 how often something has already happened for this customer. It is the same counting a `limit` business rule does, and `{\"rule_key\":\"daily_withdrawals\"}` points at one instead of repeating its numbers, so the risk engine and the flows enforcing the same limit can never drift apart.\n- `amount` \u2014 `{\"above\": 2000000}`, or your own condition, or an existing `eligibility` rule by key.\n- `new_device` \u2014 needs a `device` and a `subject` in the inputs. It never fingerprints anybody; it only remembers what it was told.\n- `geography` \u2014 an allow list, a deny list, or `{\"unusual_for_subject\": true}` for somewhere this customer has not acted from before.\n\nTwo rules can trip at once. The strongest action always wins \u2014 block over hold over alert \u2014 and `priority` only decides whose sentence gets quoted, never what the answer is.\n\nFor `hold`, `config.approval` is a Phase 2 approval policy and it is checked when you save: a policy naming nobody is refused here rather than failing on a real customer's payment.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "alerts.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "upsert_risk_rule arguments",
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string",
                                "description": "The rule's handle. An existing one is updated."
                            },
                            "signal": {
                                "type": "string",
                                "description": "What to look at: velocity, amount, new_device, geography. Cannot change later."
                            },
                            "action": {
                                "type": "string",
                                "description": "allow, alert, hold or block. hold raises an approval; block refuses."
                            },
                            "definition": {
                                "type": "object",
                                "description": "The signal's own settings, or {\"rule_key\":\"...\"} to reuse a business rule."
                            },
                            "label": {
                                "type": "string",
                                "description": "What a person sees."
                            },
                            "config": {
                                "type": "object",
                                "description": "For hold: {\"approval\":{...}}. For alert: {\"recipients\":{\"roles\":[\"owner\"]}}."
                            },
                            "priority": {
                                "type": "integer",
                                "description": "Lowest first. Decides whose sentence is quoted, never the answer."
                            },
                            "enabled": {
                                "type": "boolean",
                                "description": "Switch it on or off."
                            }
                        },
                        "required": [
                            "key",
                            "signal",
                            "action",
                            "definition"
                        ]
                    }
                },
                {
                    "name": "upsert_sla_policy",
                    "root_name": "upsert_sla_policy",
                    "version": "21d1bfe1",
                    "description": "Create or change a service-level promise: what it is about, the status that starts the clock, how long the business has, and what happens when it runs out. Matched on `key`.\n\nRead the saved policy's `describes` sentence back to the person before you consider this done \u2014 \"A record that reaches 'confirmed' must leave it within 4 hours of working time\" is checkable in a way that a settings object is not.\n\nTwo things to say out loud before saving:\n\n- `breach_action: \"transition\"` MOVES THE RECORD ON when the promise is missed, with nobody watching. It goes through the status machine, so a move the business has not declared is refused rather than forced \u2014 but a declared one happens. Name the destination state when you describe it.\n- `calendar.mode: \"business_hours\"` makes the clock stop outside working hours, so a four-hour promise made on Friday afternoon can breach on Monday. Working hours come from an opening-window business rule (`calendar.rule_key`) or the workspace's own schedule, holidays included \u2014 never from a second calendar this feature keeps.\n\nA policy applies to everything already sitting in the start status, not just to what arrives afterwards. Writing one on a backlog reports the backlog immediately, which is usually what somebody wants and always a surprise if nobody said so.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "alerts.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "upsert_sla_policy arguments",
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string",
                                "description": "The policy's handle. An existing one is updated."
                            },
                            "subject_type": {
                                "type": "string",
                                "description": "record or ticket. Cannot change later."
                            },
                            "target_minutes": {
                                "type": "integer",
                                "description": "How long the business has, in minutes of whatever the calendar counts."
                            },
                            "start_on": {
                                "type": "object",
                                "description": "What starts the clock, e.g. {\"status\":\"confirmed\"}."
                            },
                            "subject": {
                                "type": "object",
                                "description": "Which ones: {\"table_id\":\"orders\",\"column\":\"status\"} for a record, {\"priority\":\"urgent\"} for a ticket."
                            },
                            "stop_on": {
                                "type": "object",
                                "description": "What stops it, e.g. {\"status\":\"paid\"}. Omit and simply leaving the start status stops it."
                            },
                            "label": {
                                "type": "string",
                                "description": "What a person sees."
                            },
                            "warn_at_pct": {
                                "type": "integer",
                                "description": "Warn at this share of the target, 1-99. Default 80."
                            },
                            "breach_action": {
                                "type": "string",
                                "description": "notify, escalate or transition. transition MOVES the record."
                            },
                            "breach_config": {
                                "type": "object",
                                "description": "For transition: {\"to\":\"cancelled\"}. For escalate: {\"escalate_to\":{\"roles\":[\"owner\"]}}."
                            },
                            "escalate_after_minutes": {
                                "type": "integer",
                                "description": "Minutes past the breach before escalating. Omit for no escalation stage."
                            },
                            "calendar": {
                                "type": "object",
                                "description": "{\"mode\":\"24_7\"} or {\"mode\":\"business_hours\",\"rule_key\":\"opening_hours\"}."
                            },
                            "recipients": {
                                "type": "object",
                                "description": "Who to tell: {\"roles\":[\"manager\"]}."
                            },
                            "enabled": {
                                "type": "boolean",
                                "description": "Switch it on or off."
                            }
                        },
                        "required": [
                            "key",
                            "subject_type",
                            "target_minutes",
                            "start_on"
                        ]
                    }
                }
            ]
        },
        {
            "key": "operations",
            "name": "Operations",
            "summary": "The named things this business can do \u2014 create a booking, register a customer, process a refund \u2014 each written down once, and the log of every time one ran.",
            "path": "/mcp/v1/operations",
            "module": "operations",
            "instructions": "The named things this business can do \u2014 create a booking, register a customer, process a refund, close a case. Each one was written down by the business itself and already knows its own rules: which values are needed and in what shape, which records to write, which limits to check, what to charge, who to tell. \n\nOrder of work: list_operations to see what exists, describe_operation to read one in full, run_operation to do it. \n\nPrefer an operation over doing the same thing yourself out of separate tools. If this workspace has a `create_booking` operation, use it rather than writing a row, checking a limit and sending a confirmation as three calls \u2014 the operation does all three under one validation and undoes its own writes if any part fails, and yours would not. \n\nBefore running one, read its `effects` and say what will actually happen in those words: `records` means rows change, `money` means a real customer is asked to pay, `message` means a message lands on a real phone, `approval` means people are notified and asked to decide, `external` means this business's data goes to an address outside it. Let the account holder confirm anything beyond `records` before you call it. \n\nOn failures: an operation refuses bad input BEFORE doing anything and names the field, so read the refusal rather than retrying the same values. When a step fails part-way, the answer says which record writes were put back and \u2014 this matters \u2014 what could NOT be put back, because a message already sent and money already asked for do not come back. Report that honestly rather than saying it was all undone. \n\nAn operation never waits. If one raises an approval, it ends there: the people named have been asked, and nothing else in that operation has run or will run. Do not tell somebody their request was approved because the operation succeeded.",
            "tools": [
                {
                    "name": "describe_operation",
                    "root_name": "describe_operation",
                    "version": "f165bed2",
                    "description": "One operation in full: every value it asks for with the kind it must be (a phone number, a date, an amount), what it does step by step, what it hands back, and what running it would actually cause \u2014 records written, money asked for, messages sent, approvals raised.\n\nRead the `effects` before you run one. `money` means a real customer is asked to pay; `message` means a message lands on somebody's phone. Say what will happen and let the account holder confirm it before you call run_operation.\n\n`recent_runs` shows the last few attempts, which is usually the fastest answer to \"did that work\" \u2014 including which step failed and how long it took.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "operations.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "describe_operation arguments",
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string",
                                "description": "The operation's key, as list_operations returns it."
                            }
                        },
                        "required": [
                            "key"
                        ]
                    }
                },
                {
                    "name": "list_operations",
                    "root_name": "list_operations",
                    "version": "bfc17cc7",
                    "description": "Everything this business has written down as a named thing it can do \u2014 create a booking, register a customer, process a refund, close a case. Start here before deciding to do any of that yourself: an operation already knows the workspace's own rules, so calling one is both safer and shorter than reproducing it out of separate tool calls.\n\nEach entry says what it asks for, what it does, and whether it is switched on. describe_operation gives one in full, including the exact input names; run_operation does it.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "operations.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_operations arguments",
                        "type": "object",
                        "properties": {
                            "enabled_only": {
                                "type": "boolean",
                                "description": "Only the ones that can actually be run right now."
                            }
                        }
                    }
                },
                {
                    "name": "run_operation",
                    "root_name": "run_operation",
                    "version": "498abf9d",
                    "description": "Do one of this business's named operations: create the booking, register the customer, process the refund.\n\nCall describe_operation first and read its `effects`. An operation can write records, ask a real customer to pay, send a message to a real phone and raise an approval that notifies real people \u2014 say which of those will happen, in those words, and let the account holder confirm before you run it.\n\n`inputs` is a JSON object keyed exactly as describe_operation lists them. The operation validates every value itself and refuses the whole thing before anything happens, naming the field it did not like \u2014 so pass what you have and read the refusal rather than guessing at formats.\n\nPass `idempotency_key` (any string you make up) whenever a retry must not do it twice: the first answer is kept and replayed for the same key, so a call that timed out can be repeated safely.\n\nThe answer's `steps` say what each part did and how long it took. When it fails, `rolled_back` says which record writes were put back and `not_undone` says what could not be \u2014 a message already sent, money already asked for. Read that back to the person rather than saying it was undone.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "operations.run"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "run_operation arguments",
                        "type": "object",
                        "properties": {
                            "key": {
                                "type": "string",
                                "description": "The operation's key, as list_operations returns it."
                            },
                            "inputs": {
                                "type": "string",
                                "description": "A JSON object of the values it asks for, keyed exactly as describe_operation lists them."
                            },
                            "idempotency_key": {
                                "type": "string",
                                "description": "Any string you make up. The same key replays the first answer instead of doing it twice."
                            }
                        },
                        "required": [
                            "key"
                        ]
                    }
                }
            ]
        },
        {
            "key": "studio",
            "name": "Studio",
            "summary": "Voice and audio: browse the voice library, generate speech, convert audio and publish it for use in an IVR.",
            "path": "/mcp/v1/studio",
            "module": null,
            "instructions": "Voice and audio for this business: generate spoken prompts for call flows, voicemail greetings and anywhere else audio is needed. \n\nGeneration costs the account real money at a vendor, so: check list_assets in case the clip already exists, pick the voice with list_voices (previews are free, generating variations to compare is not), and generate once. Identical text in the same voice within a day is reused automatically. \n\nThree steps to get audio into a call flow, and the middle one is the one people forget: generate_speech makes the clip, publish_asset_to_ivr makes it usable by the IVR, and only then can a node reference it. \n\nWrite the text in the language the caller will actually hear. For most of these businesses that is Kiswahili or a mix of Kiswahili and English.",
            "tools": [
                {
                    "name": "get_asset",
                    "root_name": "get_asset",
                    "version": "fc94eee4",
                    "description": "Check one audio asset \u2014 mainly to see whether a generation that was still running has finished.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "asset-studio.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_asset arguments",
                        "type": "object",
                        "properties": {
                            "asset_id": {
                                "type": "integer",
                                "description": "The asset id."
                            }
                        },
                        "required": [
                            "asset_id"
                        ]
                    }
                },
                {
                    "name": "list_assets",
                    "root_name": "list_assets",
                    "version": "55ed7b79",
                    "description": "List the audio already in this account's Asset Studio. Check here before generating \u2014 the clip you need may exist.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "asset-studio.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_assets arguments",
                        "type": "object",
                        "properties": {
                            "search": {
                                "type": "string",
                                "description": "Filter by name."
                            },
                            "status": {
                                "type": "string",
                                "description": "ready, processing or failed."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "list_voices",
                    "root_name": "list_voices",
                    "version": "d9762d10",
                    "description": "List the voices available for generating speech, with language, gender, style and a preview URL. Pick from here rather than generating candidates \u2014 previews already exist and cost nothing, generation costs money.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "asset-studio.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_voices arguments",
                        "type": "object",
                        "properties": {
                            "language": {
                                "type": "string",
                                "description": "e.g. \"sw\" for Kiswahili, \"en\" for English."
                            },
                            "gender": {
                                "type": "string",
                                "description": "male or female."
                            },
                            "provider": {
                                "type": "string",
                                "description": "Filter to one provider."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "generate_speech",
                    "root_name": "generate_speech",
                    "version": "5dd9ab56",
                    "description": "Turn text into spoken audio using one of the account's voices, and put it in Asset Studio. Use it for IVR greetings, menu prompts and voicemail messages. Generation costs money, so pick the voice with list_voices first and do not generate variations speculatively.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "asset-studio.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "generate_speech arguments",
                        "type": "object",
                        "properties": {
                            "text": {
                                "type": "string",
                                "description": "What to say. Write it in the language the caller will hear."
                            },
                            "voice_id": {
                                "type": "string",
                                "description": "A voice_id from list_voices."
                            },
                            "name": {
                                "type": "string",
                                "description": "A name for the clip, e.g. \"greeting_sw\"."
                            },
                            "wait_ms": {
                                "type": "integer",
                                "description": "How long to wait for it to finish before returning a handle. Default 8000, max 20000."
                            },
                            "confirm_long": {
                                "type": "boolean",
                                "description": "Required for text over 1200 characters, after checking with the user."
                            }
                        },
                        "required": [
                            "text",
                            "voice_id",
                            "name"
                        ]
                    }
                },
                {
                    "name": "publish_asset_to_ivr",
                    "root_name": "publish_asset_to_ivr",
                    "version": "3b2eebf6",
                    "description": "Make an Asset Studio clip usable inside a call flow. This step is required and easy to forget: an Asset Studio id is NOT an IVR asset id, and a node that references the wrong one will not play.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.assets.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "publish_asset_to_ivr arguments",
                        "type": "object",
                        "properties": {
                            "asset_id": {
                                "type": "integer",
                                "description": "The Asset Studio asset to publish."
                            }
                        },
                        "required": [
                            "asset_id"
                        ]
                    }
                }
            ]
        },
        {
            "key": "numbers",
            "name": "Numbers",
            "summary": "Phone numbers: what you own, what is available, what one costs, and how to pay for it.",
            "path": "/mcp/v1/numbers",
            "module": "calls",
            "instructions": "Phone numbers for this business: what they own, what is available, what one costs, and how to pay. \n\nTHE RULE: you never move money. Every payment path ends with a PERSON doing something \u2014 approving a PIN prompt on their handset, paying a lipa number from their own app, or clicking a checkout link. \n\nThe order is fixed and exists to protect the user: search_available_numbers, then quote_number for the binding total, then READ THAT TOTAL BACK and get their agreement, then start_number_payment. A payment cannot be started without a live quote, so the price they agreed is the price they pay. \n\nAccepted is not paid. A push payment comes back \"processing\", meaning the prompt is out and unanswered \u2014 say exactly that, and never tell someone their number is bought before check_payment_status says paid. \n\nIf a payment seems not to have worked, call check_payment_status. Do not start a second one.",
            "tools": [
                {
                    "name": "check_payment_status",
                    "root_name": "check_payment_status",
                    "version": "62f6c248",
                    "description": "Check whether a payment you started has actually settled. \"Processing\" means the prompt is out and unanswered \u2014 wait for it, do not start a second payment.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "numbers.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "check_payment_status arguments",
                        "type": "object",
                        "properties": {
                            "payment_id": {
                                "type": "integer",
                                "description": "The payment_id from start_number_payment."
                            }
                        },
                        "required": [
                            "payment_id"
                        ]
                    }
                },
                {
                    "name": "list_my_numbers",
                    "root_name": "list_my_numbers",
                    "version": "7b8bfcd0",
                    "description": "The phone numbers this business already owns.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "numbers.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_my_numbers arguments",
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "quote_number",
                    "root_name": "quote_number",
                    "version": "c98d1e2f",
                    "description": "Get the binding total for buying a number \u2014 monthly fee plus any one-off deposit, converted to the billing currency. Returns a quote_id that expires in 15 minutes. You MUST read the total back to the user and get their agreement before starting a payment.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "numbers.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "quote_number arguments",
                        "type": "object",
                        "properties": {
                            "catalog_id": {
                                "type": "integer",
                                "description": "The catalog_id from search_available_numbers."
                            }
                        },
                        "required": [
                            "catalog_id"
                        ]
                    }
                },
                {
                    "name": "search_available_numbers",
                    "root_name": "search_available_numbers",
                    "version": "a86e1247",
                    "description": "Search phone numbers available to buy right now, with their monthly price. Prices here are indicative \u2014 quote_number gives the binding total including any deposit.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "numbers.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "search_available_numbers arguments",
                        "type": "object",
                        "properties": {
                            "prefix": {
                                "type": "string",
                                "description": "E.164 prefix, e.g. \"+255\"."
                            },
                            "number_type_id": {
                                "type": "integer",
                                "description": "Restrict to one number type."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "request_number",
                    "root_name": "request_number",
                    "version": "032f4711",
                    "description": "Ask for a phone number that is not in the available list \u2014 a specific prefix, a country, a vanity number. An administrator prices it and the user pays the quoted deposit. Nothing is reserved and nothing is charged by this call.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "numbers.purchase"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "request_number arguments",
                        "type": "object",
                        "properties": {
                            "business_use_case": {
                                "type": "string",
                                "description": "What the business will use the number for."
                            },
                            "preferred_number": {
                                "type": "string",
                                "description": "A specific number or prefix they would like."
                            },
                            "notes": {
                                "type": "string",
                                "description": "Anything else the administrator should know."
                            }
                        },
                        "required": [
                            "business_use_case"
                        ]
                    }
                },
                {
                    "name": "start_number_payment",
                    "root_name": "start_number_payment",
                    "version": "827723c5",
                    "description": "Begin paying for a number. You never move money: this hands back a prompt on the customer's phone, a lipa number, or a checkout link, and a PERSON completes it. Requires a live quote_id, so the price the user agreed is the price they pay.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "numbers.purchase"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "start_number_payment arguments",
                        "type": "object",
                        "properties": {
                            "quote_id": {
                                "type": "string",
                                "description": "A live quote_id from quote_number."
                            },
                            "method": {
                                "type": "string",
                                "description": "push (PIN prompt on their phone), lipa_namba (short number they pay to), or link (checkout page). Ask the user which they prefer."
                            },
                            "payer_msisdn": {
                                "type": "string",
                                "description": "Required for push: the phone that gets the prompt."
                            },
                            "idempotency_key": {
                                "type": "string",
                                "description": "Send the same key when retrying, so a retry never starts a second payment."
                            }
                        },
                        "required": [
                            "quote_id",
                            "method"
                        ]
                    }
                }
            ]
        },
        {
            "key": "groups",
            "name": "WhatsApp groups",
            "summary": "Groups the business runs from its WhatsApp number: create, invite, post, approve joins, remove members.",
            "path": "/mcp/v1/groups",
            "module": "groups",
            "instructions": "Run the WhatsApp groups this business creates from its own number: small rooms of up to 8 people, invite-only. \n\nOrder of work: list_groups, get_group for one. create_group makes a new one \u2014 WhatsApp confirms it a moment later, so check get_group for status=active before inviting. Nobody can be added to a group: send_group_invite sends each person the approved invite template and they choose to join. \n\nsend_group_message posts to everyone in the room and is billed per member delivered to. Free-form text needs a member to have written in the last 24 hours; otherwise pass an approved template name. \n\nGroups need an Official Business Account (the green tick). If a number is refused for that reason, say so plainly; nothing here can change it.",
            "tools": [
                {
                    "name": "get_group",
                    "root_name": "get_group",
                    "version": "5005fc01",
                    "description": "One WhatsApp group in full: members and their state, pending join requests, the invite link, and recent activity.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.groups.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_group arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "integer",
                                "description": "The group id from list_groups."
                            }
                        },
                        "required": [
                            "group_id"
                        ]
                    }
                },
                {
                    "name": "list_groups",
                    "root_name": "list_groups",
                    "version": "1849d872",
                    "description": "List the WhatsApp groups this business runs: subject, status, how many of the 8 seats are taken, pending join requests, and the invite link.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.groups.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_groups arguments",
                        "type": "object",
                        "properties": {
                            "phone_number_id": {
                                "type": "string",
                                "description": "Only groups on this business number."
                            },
                            "status": {
                                "type": "string",
                                "description": "creating | active | suspended | failed | deleted | all. Default: everything except deleted."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "At most this many, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "approve_join_request",
                    "root_name": "approve_join_request",
                    "version": "c7b8593f",
                    "description": "Approve or reject people waiting to join an approval-required WhatsApp group. Join request ids come from get_group.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.groups.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "approve_join_request arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "integer",
                                "description": "The group id from list_groups."
                            },
                            "join_request_ids": {
                                "type": "string",
                                "description": "One or more join request ids, comma-separated."
                            },
                            "decision": {
                                "type": "string",
                                "description": "approve (default) or reject."
                            }
                        },
                        "required": [
                            "group_id",
                            "join_request_ids"
                        ]
                    }
                },
                {
                    "name": "create_group",
                    "root_name": "create_group",
                    "version": "5c6a4fac",
                    "description": "Create a WhatsApp group from a business number. WhatsApp confirms it a moment later; invitees, if given, get the invite template once it does. Needs an Official Business Account.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.groups.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_group arguments",
                        "type": "object",
                        "properties": {
                            "subject": {
                                "type": "string",
                                "description": "The group name, up to 128 characters."
                            },
                            "description": {
                                "type": "string",
                                "description": "Optional, up to 2048 characters."
                            },
                            "join_approval_mode": {
                                "type": "string",
                                "description": "auto_approve (anyone with the link joins) or approval_required (the business approves each request)."
                            },
                            "phone_number_id": {
                                "type": "string",
                                "description": "The business number to create it from; the default number when omitted."
                            },
                            "invitees": {
                                "type": "string",
                                "description": "Phone numbers to invite once the group is live, comma-separated, at most 7."
                            }
                        },
                        "required": [
                            "subject"
                        ]
                    }
                },
                {
                    "name": "remove_group_participant",
                    "root_name": "remove_group_participant",
                    "version": "cfad2be8",
                    "description": "Remove people from a WhatsApp group. They can only come back through a fresh invite.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.groups.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "remove_group_participant arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "integer",
                                "description": "The group id from list_groups."
                            },
                            "phones": {
                                "type": "string",
                                "description": "Phone numbers or wa_ids to remove, comma-separated, at most 8."
                            }
                        },
                        "required": [
                            "group_id",
                            "phones"
                        ]
                    }
                },
                {
                    "name": "send_group_invite",
                    "root_name": "send_group_invite",
                    "version": "6a2b9f1d",
                    "description": "Invite people into a WhatsApp group by sending each one the approved invite-link template. Joining is their choice; the roster updates when they tap the link.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.groups.manage",
                        "communications.send"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "send_group_invite arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "integer",
                                "description": "The group id from list_groups."
                            },
                            "phones": {
                                "type": "string",
                                "description": "Phone numbers in international format, comma-separated."
                            }
                        },
                        "required": [
                            "group_id",
                            "phones"
                        ]
                    }
                },
                {
                    "name": "send_group_message",
                    "root_name": "send_group_message",
                    "version": "faab165e",
                    "description": "Post a message into a WhatsApp group: text, a media link, or an approved template. Text and media only work within 24 hours of a member's last message; a template always works. Every member delivered to is billed.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.groups.view",
                        "communications.send"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "send_group_message arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "integer",
                                "description": "The group id from list_groups."
                            },
                            "text": {
                                "type": "string",
                                "description": "The message, or the caption when media_url is given."
                            },
                            "media_url": {
                                "type": "string",
                                "description": "A public URL to an image, video, audio file or document."
                            },
                            "media_type": {
                                "type": "string",
                                "description": "image | video | audio | document. Default document."
                            },
                            "template": {
                                "type": "string",
                                "description": "An approved template name, for when the 24-hour window is closed."
                            }
                        },
                        "required": [
                            "group_id"
                        ]
                    }
                }
            ]
        },
        {
            "key": "agents",
            "name": "Agents",
            "summary": "Your own AI specialists: see the roster and ask one a question.",
            "path": "/mcp/v1/agents",
            "module": null,
            "instructions": "The AI specialists this business has built \u2014 a support desk, a sales agent, sometimes a whole team with their own knowledge bases \u2014 and everything behind them: how they behave, what they can call, what they know, and what they have actually done. \n\nThey cannot see your conversation, so any question you put to one must be self-contained: include the product, the order number, the amount, the names. A question that assumes context gets a useless answer. \n\nNOTHING here reaches a live call by itself. Everything you create or change is saved on the account and a person presses Apply Changes for the phone system to pick it up. Say that plainly rather than implying an edit is already answering callers. \n\nEvery agent must have an AI model \u2014 one saved without one is rejected by the phone system \u2014 so create_agent refuses up front when it cannot resolve one, and says which part is missing. Agent names with spaces are perfectly fine. \n\nsimulate_agent is the safe way to check an agent before anyone else hears it: it runs the real configuration, places no call and contacts nobody. Use it before telling a user their agent is ready. \n\nWhen something went wrong on a real conversation, list_engine_runs then get_engine_run shows every step the AI took and every tool it called \u2014 that is where the answer to \"why did it say that\" lives.",
            "tools": [
                {
                    "name": "get_agent",
                    "root_name": "get_agent",
                    "version": "e92d40e2",
                    "description": "One AI agent in full: its persona and instructions, the greeting it opens with, which model and voice it runs on, the tools it can call, the groups it belongs to, and whether the phone system has it yet.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "agents.ai.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_agent arguments",
                        "type": "object",
                        "properties": {
                            "agent_id": {
                                "type": "integer",
                                "description": "The agent to read. list_agents gives the ids."
                            }
                        },
                        "required": [
                            "agent_id"
                        ]
                    }
                },
                {
                    "name": "get_engine_run",
                    "root_name": "get_engine_run",
                    "version": "0d6517f9",
                    "description": "Inside one AI thinking run: what it was asked, every step it took in order, which tools it called and what came back, what it answered, and where the time and the money went. This is how you find out why an agent said something odd.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "agents.engine.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_engine_run arguments",
                        "type": "object",
                        "properties": {
                            "run": {
                                "type": "string",
                                "description": "The run reference from list_engine_runs."
                            }
                        },
                        "required": [
                            "run"
                        ]
                    }
                },
                {
                    "name": "list_agent_groups",
                    "root_name": "list_agent_groups",
                    "version": "f872cfd7",
                    "description": "How this business groups its agents \u2014 a support desk, a sales team, a legal panel \u2014 with who is in each one, AI and human alike. Grouping is organisational only: it does not decide who gets a call or who can see a conversation.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "agents.ai.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_agent_groups arguments",
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Max groups to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_agent_tools",
                    "root_name": "list_agent_tools",
                    "version": "bca548b4",
                    "description": "What one agent can actually do on a call: every tool attached to it, what kind each is (an HTTP call, an MCP server, one of this platform's own servers), where it points and what it is for. Read this before attaching another.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "agents.ai.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_agent_tools arguments",
                        "type": "object",
                        "properties": {
                            "agent_id": {
                                "type": "integer",
                                "description": "The agent whose tools to list."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max tools to return (default 25, max 100)."
                            }
                        },
                        "required": [
                            "agent_id"
                        ]
                    }
                },
                {
                    "name": "list_agents",
                    "root_name": "list_agents",
                    "version": "6b127ddb",
                    "description": "The AI specialists this business has set up \u2014 what each one is for and whether it is available. Ask one a question with ask_agent when it knows something you do not.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "agents.ai.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_agents arguments",
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "list_engine_runs",
                    "root_name": "list_engine_runs",
                    "version": "b4502bcc",
                    "description": "What the AI has actually been doing: every thinking run on this account with what set it off, whether it succeeded, which model answered, how many steps it took, how long it took and what it cost. Filter by agent or by status to find the failures.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "agents.engine.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_engine_runs arguments",
                        "type": "object",
                        "properties": {
                            "agent_id": {
                                "type": "integer",
                                "description": "Only this agent's runs."
                            },
                            "status": {
                                "type": "string",
                                "description": "queued, running, succeeded, failed, denied, timed_out, handoff or awaiting_human."
                            },
                            "trigger": {
                                "type": "string",
                                "description": "What set the run off, e.g. call_consult or inbound_message."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max runs to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_knowledge",
                    "root_name": "list_knowledge",
                    "version": "9788f874",
                    "description": "What the AI agents on this account have been taught: the knowledge collections, the documents in each, and whether each one has finished indexing. A document that is not \"ready\" is not being used to answer anybody yet.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "agents.ai.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_knowledge arguments",
                        "type": "object",
                        "properties": {
                            "collection_id": {
                                "type": "integer",
                                "description": "Read one collection only."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max collections to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "simulate_agent",
                    "root_name": "simulate_agent",
                    "version": "3ada3197",
                    "description": "Try an agent out: say something to it as if you were a caller and see exactly what it would answer, which tools it would reach for and how it would use them. No real call is placed and nobody is contacted; it runs against the agent's real configuration and uses a small amount of AI credit.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "agents.ai.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "simulate_agent arguments",
                        "type": "object",
                        "properties": {
                            "agent_id": {
                                "type": "integer",
                                "description": "The agent to try."
                            },
                            "caller_says": {
                                "type": "string",
                                "description": "What the pretend caller says this turn."
                            },
                            "history_json": {
                                "type": "string",
                                "description": "Earlier turns as JSON: [{\"role\":\"caller|agent\",\"content\":\"\u2026\"}], oldest first."
                            },
                            "direction": {
                                "type": "string",
                                "description": "\"inbound\" (default) or \"outbound\"."
                            },
                            "caller_number": {
                                "type": "string",
                                "description": "The number the pretend caller is calling from."
                            },
                            "caller_name": {
                                "type": "string",
                                "description": "The pretend caller's name."
                            },
                            "language": {
                                "type": "string",
                                "description": "\"auto\" (default), \"en\" or \"sw\"."
                            },
                            "tool_mocks": {
                                "type": "object",
                                "description": "Canned results for tools, keyed by tool name, so a rehearsal never hits a real endpoint."
                            }
                        },
                        "required": [
                            "agent_id",
                            "caller_says"
                        ]
                    }
                },
                {
                    "name": "add_knowledge",
                    "root_name": "add_knowledge",
                    "version": "7064da41",
                    "description": "Teach the AI agents something: add a titled piece of writing \u2014 a policy, a price list, an FAQ answer \u2014 to a knowledge collection. It is queued for indexing and only starts answering questions once indexing finishes.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "agents.ai.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "add_knowledge arguments",
                        "type": "object",
                        "properties": {
                            "title": {
                                "type": "string",
                                "description": "What this piece is about, e.g. \"Refund policy\". Agents retrieve by it."
                            },
                            "body": {
                                "type": "string",
                                "description": "The text itself."
                            },
                            "collection_id": {
                                "type": "integer",
                                "description": "An existing collection to add it to. list_knowledge gives the ids."
                            },
                            "collection_name": {
                                "type": "string",
                                "description": "A collection by name, created if it does not exist yet."
                            }
                        },
                        "required": [
                            "title",
                            "body"
                        ]
                    }
                },
                {
                    "name": "attach_tool_to_agent",
                    "root_name": "attach_tool_to_agent",
                    "version": "a2ead8a6",
                    "description": "Give an agent a new tool it can call during a conversation: an HTTP endpoint, or an external MCP server. The agent keeps every tool it already had. Nothing reaches live calls until a person applies changes.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "agents.ai.tools.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "attach_tool_to_agent arguments",
                        "type": "object",
                        "properties": {
                            "agent_id": {
                                "type": "integer",
                                "description": "The agent to give the tool to."
                            },
                            "name": {
                                "type": "string",
                                "description": "What the agent calls it, e.g. \"check_order_status\". Letters, numbers and underscores."
                            },
                            "description": {
                                "type": "string",
                                "description": "What it does and when to use it. This is what the agent reads to decide."
                            },
                            "url": {
                                "type": "string",
                                "description": "The endpoint to call."
                            },
                            "type": {
                                "type": "string",
                                "description": "\"http_call\" (default) or \"mcp_server\" for an external MCP server."
                            },
                            "method": {
                                "type": "string",
                                "description": "HTTP method for an http_call tool. Default GET."
                            },
                            "parameters": {
                                "type": "object",
                                "description": "JSON-schema properties for the arguments the agent should supply."
                            }
                        },
                        "required": [
                            "agent_id",
                            "name",
                            "description",
                            "url"
                        ]
                    }
                },
                {
                    "name": "create_agent",
                    "root_name": "create_agent",
                    "version": "05236291",
                    "description": "Create a new AI agent from a name and its instructions. It is saved locally and does NOT answer calls until a person applies changes. Refuses up front, and says why, if no AI model can be resolved for it \u2014 an agent without a model is rejected by the phone system.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "agents.ai.create"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_agent arguments",
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "What to call the agent. Spaces are fine, e.g. \"Customer Support\"."
                            },
                            "instructions": {
                                "type": "string",
                                "description": "What this agent is for, how it should behave, and what it must not do."
                            },
                            "mode": {
                                "type": "string",
                                "description": "\"realtime\" (speech to speech, the default) or \"pipeline\" (separate ear, brain and voice)."
                            },
                            "model_id": {
                                "type": "string",
                                "description": "Optional catalogue model id. Left out, the platform default for this mode is used; if there is none, this call is refused rather than making an agent the phone system will reject."
                            },
                            "greeting": {
                                "type": "string",
                                "description": "The line it opens with, when it speaks first."
                            },
                            "first_speaker": {
                                "type": "string",
                                "description": "\"agent\" or \"caller\" \u2014 who talks first."
                            }
                        },
                        "required": [
                            "name",
                            "instructions"
                        ]
                    }
                },
                {
                    "name": "set_group_members",
                    "root_name": "set_group_members",
                    "version": "ed45d6d5",
                    "description": "Set exactly who is in an agent group. This REPLACES the membership rather than adding to it: anybody you leave out is removed, so read list_agent_groups first and send the full list you want.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "agents.groups.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "set_group_members arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "integer",
                                "description": "The group to set. list_agent_groups gives the ids."
                            },
                            "agent_ids": {
                                "type": "array",
                                "description": "The AI agents that should be in the group, by id. Anything omitted is removed."
                            },
                            "client_user_ids": {
                                "type": "array",
                                "description": "The people who should be in the group, by client user id. Anything omitted is removed."
                            }
                        },
                        "required": [
                            "group_id"
                        ]
                    }
                },
                {
                    "name": "update_agent",
                    "root_name": "update_agent",
                    "version": "0ffddcdb",
                    "description": "Change an existing AI agent: its name, its instructions, its greeting, or who speaks first. Only the fields you pass change. The edit is saved locally and reaches real calls only when a person applies changes.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "agents.ai.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_agent arguments",
                        "type": "object",
                        "properties": {
                            "agent_id": {
                                "type": "integer",
                                "description": "The agent to change."
                            },
                            "name": {
                                "type": "string",
                                "description": "New display name. Spaces are fine."
                            },
                            "instructions": {
                                "type": "string",
                                "description": "Replacement instructions. This replaces the whole prompt, so send the full text."
                            },
                            "greeting": {
                                "type": "string",
                                "description": "The opening line. Send an empty string to clear it."
                            },
                            "first_speaker": {
                                "type": "string",
                                "description": "\"agent\" or \"caller\"."
                            }
                        },
                        "required": [
                            "agent_id"
                        ]
                    }
                }
            ]
        },
        {
            "key": "orders",
            "name": "Orders",
            "summary": "Customer orders across every platform: find, read, move status, request payment.",
            "path": "/mcp/v1/orders",
            "module": "catalogue",
            "instructions": "Order tools: find a customer's orders by phone, username, email or name; read one in full; move it to a new status; ask the customer to pay; and tie an order to the chat it belongs to on any platform. Everything is scoped to the authenticated tenant and is platform-blind \u2014 an order from the hosted storefront reads exactly like one from a WhatsApp cart. Quote order numbers, totals and payment states exactly as returned; never invent an order or tell a customer something is paid when it is not. An order with no linked chat cannot be messaged: link it first, or say so.",
            "tools": [
                {
                    "name": "find-chats-for-order-tool",
                    "root_name": "find-chats-for-order-tool",
                    "version": "be029874",
                    "description": "Find the chats an order could belong to, on any platform, best guess first. Use it before linking an order that arrived without a chat \u2014 from the storefront, over the counter, or by import. Each result says whether it can actually be linked and why not.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "find-chats-for-order-tool arguments",
                        "type": "object",
                        "properties": {
                            "order_id": {
                                "type": "integer",
                                "description": "The order to find a chat for."
                            },
                            "search": {
                                "type": "string",
                                "description": "Optional name, number or username to narrow the search."
                            }
                        },
                        "required": [
                            "order_id"
                        ]
                    }
                },
                {
                    "name": "get-order-tool",
                    "root_name": "get-order-tool",
                    "version": "4ac4e174",
                    "description": "Get one order in full: the items ordered, the total, the current status with its history, and every payment attempt against it including whether it has been paid.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-order-tool arguments",
                        "type": "object",
                        "properties": {
                            "order_id": {
                                "type": "integer",
                                "description": "The order id, as returned by the order list."
                            }
                        },
                        "required": [
                            "order_id"
                        ]
                    }
                },
                {
                    "name": "link-order-to-chat-tool",
                    "root_name": "link-order-to-chat-tool",
                    "version": "b2abde88",
                    "description": "Tie an order to a customer chat on any platform, so status updates and payment requests can actually reach them. Find the chat with find-chats-for-order first; never guess a conversation id.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "link-order-to-chat-tool arguments",
                        "type": "object",
                        "properties": {
                            "order_id": {
                                "type": "integer",
                                "description": "The order to tie to a chat."
                            },
                            "conversation_id": {
                                "type": "integer",
                                "description": "The chat, from find-chats-for-order."
                            }
                        },
                        "required": [
                            "order_id",
                            "conversation_id"
                        ]
                    }
                },
                {
                    "name": "list-orders-tool",
                    "root_name": "list-orders-tool",
                    "version": "9ed96b10",
                    "description": "List orders, newest first. Filter by status, platform, date, or who the customer is \u2014 a phone number, a username, an email or a name. Use it to answer \"where is my order\" and \"has my payment gone through\" when the customer cannot quote an order number.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-orders-tool arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "Only orders in this status.",
                                "enum": [
                                    "pending",
                                    "confirmed",
                                    "processing",
                                    "shipped",
                                    "delivered",
                                    "cancelled",
                                    "refunded"
                                ]
                            },
                            "customer": {
                                "type": "string",
                                "description": "Who the customer is: a phone number in any format, a username, an email, or a name."
                            },
                            "customer_phone": {
                                "type": "string",
                                "description": "Deprecated alias for `customer`. Phone in any format; the last 9 digits are matched."
                            },
                            "platform": {
                                "type": "string",
                                "description": "Only orders that came from this platform, e.g. whatsapp, storefront, instagram, manual."
                            },
                            "since": {
                                "type": "string",
                                "description": "Only orders placed on or after this ISO 8601 date."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Maximum orders to return (1-25, default 10).",
                                "default": 10
                            }
                        }
                    }
                },
                {
                    "name": "update-order-status-tool",
                    "root_name": "update-order-status-tool",
                    "version": "3f904c65",
                    "description": "Move an order to a new status (confirmed, processing, shipped, delivered, cancelled, refunded) and optionally tell the customer. Use only when the business has actually decided \u2014 never to guess or reassure.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update-order-status-tool arguments",
                        "type": "object",
                        "properties": {
                            "order_id": {
                                "type": "integer",
                                "description": "The order to move."
                            },
                            "status": {
                                "type": "string",
                                "description": "The new status.",
                                "enum": [
                                    "pending",
                                    "confirmed",
                                    "processing",
                                    "shipped",
                                    "delivered",
                                    "cancelled",
                                    "refunded"
                                ]
                            },
                            "notify_customer": {
                                "type": "boolean",
                                "description": "Message the customer about the change (default: true).",
                                "default": true
                            }
                        },
                        "required": [
                            "order_id",
                            "status"
                        ]
                    }
                },
                {
                    "name": "request-order-payment-tool",
                    "root_name": "request-order-payment-tool",
                    "version": "02ded816",
                    "description": "Ask the customer to pay for an order \u2014 a mobile-money push to their phone, or a checkout link. Returns the payment reference and, for card or link methods, the URL to send them. Only use when the customer has agreed to pay now.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "request-order-payment-tool arguments",
                        "type": "object",
                        "properties": {
                            "order_id": {
                                "type": "integer",
                                "description": "The order to collect payment for."
                            },
                            "method": {
                                "type": "string",
                                "description": "Payment method, e.g. mobile_money or card. Defaults to mobile money.",
                                "default": "mobile_money"
                            },
                            "payer_msisdn": {
                                "type": "string",
                                "description": "Phone number to bill, if it differs from the one on the order."
                            }
                        },
                        "required": [
                            "order_id"
                        ]
                    }
                }
            ]
        },
        {
            "key": "shop",
            "name": "Shop",
            "summary": "Products, brands and categories, plus the order tools.",
            "path": "/mcp/v1/shop",
            "module": "catalogue",
            "instructions": "Shop tools: search the product catalogue, read a product, list brands and categories, look up orders, move an order to a new status, and ask a customer to pay. Everything is scoped to the authenticated tenant and reads the shop itself, so it works whether or not the business sells through WhatsApp. Quote prices and stock exactly as returned; never invent a product, an order or a payment state.",
            "tools": [
                {
                    "name": "get-order-tool",
                    "root_name": "get-order-tool",
                    "version": "4ac4e174",
                    "description": "Get one order in full: the items ordered, the total, the current status with its history, and every payment attempt against it including whether it has been paid.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-order-tool arguments",
                        "type": "object",
                        "properties": {
                            "order_id": {
                                "type": "integer",
                                "description": "The order id, as returned by the order list."
                            }
                        },
                        "required": [
                            "order_id"
                        ]
                    }
                },
                {
                    "name": "get-product-tool",
                    "root_name": "get-product-tool",
                    "version": "5b267bc9",
                    "description": "Get the full detail of one product by its SKU: description, price, sale price, stock count, condition, brand, category and image.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-product-tool arguments",
                        "type": "object",
                        "properties": {
                            "sku": {
                                "type": "string",
                                "description": "The product SKU, as returned by the product search."
                            }
                        },
                        "required": [
                            "sku"
                        ]
                    }
                },
                {
                    "name": "list-brands-and-categories-tool",
                    "root_name": "list-brands-and-categories-tool",
                    "version": "8df75782",
                    "description": "List the brands and categories this shop sells, with how many products each holds. Use it to answer \"what brands do you carry\" or to offer a customer somewhere to start.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-brands-and-categories-tool arguments",
                        "type": "object",
                        "properties": {
                            "kind": {
                                "type": "string",
                                "description": "Which list to return (default: both).",
                                "enum": [
                                    "brands",
                                    "categories",
                                    "both"
                                ],
                                "default": "both"
                            }
                        }
                    }
                },
                {
                    "name": "list-orders-tool",
                    "root_name": "list-orders-tool",
                    "version": "9ed96b10",
                    "description": "List orders, newest first. Filter by status, platform, date, or who the customer is \u2014 a phone number, a username, an email or a name. Use it to answer \"where is my order\" and \"has my payment gone through\" when the customer cannot quote an order number.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-orders-tool arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "Only orders in this status.",
                                "enum": [
                                    "pending",
                                    "confirmed",
                                    "processing",
                                    "shipped",
                                    "delivered",
                                    "cancelled",
                                    "refunded"
                                ]
                            },
                            "customer": {
                                "type": "string",
                                "description": "Who the customer is: a phone number in any format, a username, an email, or a name."
                            },
                            "customer_phone": {
                                "type": "string",
                                "description": "Deprecated alias for `customer`. Phone in any format; the last 9 digits are matched."
                            },
                            "platform": {
                                "type": "string",
                                "description": "Only orders that came from this platform, e.g. whatsapp, storefront, instagram, manual."
                            },
                            "since": {
                                "type": "string",
                                "description": "Only orders placed on or after this ISO 8601 date."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Maximum orders to return (1-25, default 10).",
                                "default": 10
                            }
                        }
                    }
                },
                {
                    "name": "search-products-tool",
                    "root_name": "search-products-tool",
                    "version": "a566062e",
                    "description": "Search the shop for products by name, SKU, brand, category or description. Use this to answer \"do you have\u2026\", \"how much is\u2026\" and \"what do you sell\" questions. Returns price, stock and brand for each match.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "search-products-tool arguments",
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "What the customer asked for \u2014 a product name, SKU, brand or keyword."
                            },
                            "brand": {
                                "type": "string",
                                "description": "Restrict results to one brand."
                            },
                            "in_stock_only": {
                                "type": "boolean",
                                "description": "Only return products currently in stock."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Maximum products to return (1-25, default 10).",
                                "default": 10
                            }
                        }
                    }
                },
                {
                    "name": "update-order-status-tool",
                    "root_name": "update-order-status-tool",
                    "version": "3f904c65",
                    "description": "Move an order to a new status (confirmed, processing, shipped, delivered, cancelled, refunded) and optionally tell the customer. Use only when the business has actually decided \u2014 never to guess or reassure.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update-order-status-tool arguments",
                        "type": "object",
                        "properties": {
                            "order_id": {
                                "type": "integer",
                                "description": "The order to move."
                            },
                            "status": {
                                "type": "string",
                                "description": "The new status.",
                                "enum": [
                                    "pending",
                                    "confirmed",
                                    "processing",
                                    "shipped",
                                    "delivered",
                                    "cancelled",
                                    "refunded"
                                ]
                            },
                            "notify_customer": {
                                "type": "boolean",
                                "description": "Message the customer about the change (default: true).",
                                "default": true
                            }
                        },
                        "required": [
                            "order_id",
                            "status"
                        ]
                    }
                },
                {
                    "name": "request-order-payment-tool",
                    "root_name": "request-order-payment-tool",
                    "version": "02ded816",
                    "description": "Ask the customer to pay for an order \u2014 a mobile-money push to their phone, or a checkout link. Returns the payment reference and, for card or link methods, the URL to send them. Only use when the customer has agreed to pay now.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "request-order-payment-tool arguments",
                        "type": "object",
                        "properties": {
                            "order_id": {
                                "type": "integer",
                                "description": "The order to collect payment for."
                            },
                            "method": {
                                "type": "string",
                                "description": "Payment method, e.g. mobile_money or card. Defaults to mobile money.",
                                "default": "mobile_money"
                            },
                            "payer_msisdn": {
                                "type": "string",
                                "description": "Phone number to bill, if it differs from the one on the order."
                            }
                        },
                        "required": [
                            "order_id"
                        ]
                    }
                }
            ]
        },
        {
            "key": "tickets",
            "name": "Tickets",
            "summary": "Support tickets: create, update, assign, reply, labels and notifications.",
            "path": "/mcp/v1/tickets",
            "module": null,
            "instructions": "MCP server for managing support tickets. Provides tools to list, create, update, delete tickets, manage replies, assign agents, change statuses, manage labels, and handle ticket notifications. All operations are scoped to the authenticated user's tenant.",
            "tools": [
                {
                    "name": "add-ticket-reply-tool",
                    "root_name": "add-ticket-reply-tool",
                    "version": "10555643",
                    "description": "Add a reply or internal note to a ticket. Replies can be sent externally via SMS or WhatsApp if a channel is specified. Use type \"note\" for internal notes visible only to agents.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "add-ticket-reply-tool arguments",
                        "type": "object",
                        "properties": {
                            "ticket_id": {
                                "type": "string",
                                "description": "The UUID of the ticket to reply to."
                            },
                            "body": {
                                "type": "string",
                                "description": "The reply message body. Supports @mentions to notify agents."
                            },
                            "type": {
                                "type": "string",
                                "description": "Type of reply: \"reply\" for customer-visible response, \"note\" for internal agent note.",
                                "enum": [
                                    "reply",
                                    "note"
                                ],
                                "default": "reply"
                            },
                            "is_internal": {
                                "type": "boolean",
                                "description": "Whether this reply is internal (only visible to agents).",
                                "default": false
                            },
                            "channel": {
                                "type": "string",
                                "description": "Channel to send the reply through. If set to sms/whatsapp/email, the reply will be sent externally.",
                                "enum": [
                                    "internal",
                                    "sms",
                                    "whatsapp",
                                    "email"
                                ]
                            }
                        },
                        "required": [
                            "ticket_id",
                            "body"
                        ]
                    }
                },
                {
                    "name": "assign-ticket-tool",
                    "root_name": "assign-ticket-tool",
                    "version": "8f384043",
                    "description": "Assign a ticket to an agent. Automatically changes status from \"open\" to \"in_progress\" when assigning. Pass null to unassign.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "assign-ticket-tool arguments",
                        "type": "object",
                        "properties": {
                            "ticket_id": {
                                "type": "string",
                                "description": "The UUID of the ticket to assign."
                            },
                            "assigned_to_id": {
                                "type": "integer",
                                "description": "The ID of the agent to assign to. Pass null or omit to unassign."
                            }
                        },
                        "required": [
                            "ticket_id"
                        ]
                    }
                },
                {
                    "name": "change-ticket-status-tool",
                    "root_name": "change-ticket-status-tool",
                    "version": "73be4700",
                    "description": "Change a ticket's status. Automatically manages SLA timestamps: sets resolved_at when resolving, closed_at when closing, and clears both when reopening.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "change-ticket-status-tool arguments",
                        "type": "object",
                        "properties": {
                            "ticket_id": {
                                "type": "string",
                                "description": "The UUID of the ticket."
                            },
                            "status": {
                                "type": "string",
                                "description": "The new status: open, in_progress, waiting, resolved, or closed.",
                                "enum": [
                                    "open",
                                    "in_progress",
                                    "waiting",
                                    "resolved",
                                    "closed"
                                ]
                            }
                        },
                        "required": [
                            "ticket_id",
                            "status"
                        ]
                    }
                },
                {
                    "name": "create-ticket-label-tool",
                    "root_name": "create-ticket-label-tool",
                    "version": "b54e0a6a",
                    "description": "Create a new ticket label with a name, hex color, and optional description. Label names must be unique per tenant.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create-ticket-label-tool arguments",
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "Label name (must be unique per tenant)."
                            },
                            "color": {
                                "type": "string",
                                "description": "Hex color code, e.g. \"#FF5733\"."
                            },
                            "description": {
                                "type": "string",
                                "description": "Optional label description."
                            }
                        },
                        "required": [
                            "name",
                            "color"
                        ]
                    }
                },
                {
                    "name": "create-ticket-tool",
                    "root_name": "create-ticket-tool",
                    "version": "4d709f2f",
                    "description": "Create a new support ticket. Requires subject, priority, and channel. Optionally attach customer details, labels, and link to a conversation, call, or contact.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create-ticket-tool arguments",
                        "type": "object",
                        "properties": {
                            "subject": {
                                "type": "string",
                                "description": "Ticket subject line."
                            },
                            "priority": {
                                "type": "string",
                                "description": "Ticket priority level.",
                                "enum": [
                                    "low",
                                    "medium",
                                    "high",
                                    "urgent"
                                ]
                            },
                            "channel": {
                                "type": "string",
                                "description": "The channel through which the ticket was created.",
                                "enum": [
                                    "whatsapp",
                                    "sms",
                                    "phone",
                                    "email",
                                    "web",
                                    "internal"
                                ]
                            },
                            "description": {
                                "type": "string",
                                "description": "Detailed ticket description."
                            },
                            "assigned_to_id": {
                                "type": "integer",
                                "description": "ID of the agent to assign the ticket to."
                            },
                            "conversation_id": {
                                "type": "integer",
                                "description": "ID of a linked conversation."
                            },
                            "call_id": {
                                "type": "integer",
                                "description": "ID of a linked call."
                            },
                            "contact_id": {
                                "type": "integer",
                                "description": "ID of a linked contact."
                            },
                            "customer_name": {
                                "type": "string",
                                "description": "Customer name."
                            },
                            "customer_email": {
                                "type": "string",
                                "description": "Customer email address."
                            },
                            "customer_phone": {
                                "type": "string",
                                "description": "Customer phone number."
                            },
                            "customer_company": {
                                "type": "string",
                                "description": "Customer company name."
                            },
                            "label_ids": {
                                "type": "array",
                                "description": "Array of label IDs to attach to the ticket."
                            }
                        },
                        "required": [
                            "subject",
                            "priority",
                            "channel"
                        ]
                    }
                },
                {
                    "name": "get-ticket-notifications-tool",
                    "root_name": "get-ticket-notifications-tool",
                    "version": "f16b5547",
                    "description": "Get the authenticated user's ticket notifications including mentions, assignments, replies, and status changes. Returns the most recent 30 notifications with unread count.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-ticket-notifications-tool arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "get-ticket-stats-tool",
                    "root_name": "get-ticket-stats-tool",
                    "version": "23fc50fc",
                    "description": "Get ticket statistics including counts by status and urgent ticket count. Respects the user's view permissions.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-ticket-stats-tool arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "get-ticket-tool",
                    "root_name": "get-ticket-tool",
                    "version": "2f90de0e",
                    "description": "Get full details of a specific ticket including description, customer info, replies, labels, and linked conversation/call/contact.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-ticket-tool arguments",
                        "type": "object",
                        "properties": {
                            "ticket_id": {
                                "type": "string",
                                "description": "The UUID of the ticket to retrieve."
                            }
                        },
                        "required": [
                            "ticket_id"
                        ]
                    }
                },
                {
                    "name": "list-team-members-tool",
                    "root_name": "list-team-members-tool",
                    "version": "e55ac862",
                    "description": "List team members (agents) for the current tenant. Use this to discover agent IDs for ticket assignment.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-team-members-tool arguments",
                        "type": "object",
                        "properties": {
                            "search": {
                                "type": "string",
                                "description": "Search by name or email."
                            },
                            "role": {
                                "type": "string",
                                "description": "Filter by role (e.g. owner, manager, agent)."
                            }
                        }
                    }
                },
                {
                    "name": "list-ticket-labels-tool",
                    "root_name": "list-ticket-labels-tool",
                    "version": "5396c988",
                    "description": "List ticket labels for the current tenant. Optionally filter by name search query.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-ticket-labels-tool arguments",
                        "type": "object",
                        "properties": {
                            "search": {
                                "type": "string",
                                "description": "Optional search query to filter labels by name."
                            }
                        }
                    }
                },
                {
                    "name": "list-tickets-tool",
                    "root_name": "list-tickets-tool",
                    "version": "5b51ef9e",
                    "description": "List and filter support tickets. Supports filtering by status, priority, assigned agent, creator, label, channel, date range, and free-text search. Returns paginated results with sort options.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-tickets-tool arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "Filter by ticket status.",
                                "enum": [
                                    "open",
                                    "in_progress",
                                    "waiting",
                                    "resolved",
                                    "closed"
                                ]
                            },
                            "priority": {
                                "type": "string",
                                "description": "Filter by ticket priority.",
                                "enum": [
                                    "low",
                                    "medium",
                                    "high",
                                    "urgent"
                                ]
                            },
                            "assigned_to_id": {
                                "type": "integer",
                                "description": "Filter by assigned agent ID. Use list-team-members to discover IDs."
                            },
                            "created_by_id": {
                                "type": "integer",
                                "description": "Filter by the agent who created the ticket."
                            },
                            "label_id": {
                                "type": "integer",
                                "description": "Filter by label ID."
                            },
                            "channel": {
                                "type": "string",
                                "description": "Filter by channel.",
                                "enum": [
                                    "whatsapp",
                                    "sms",
                                    "phone",
                                    "email",
                                    "web",
                                    "internal"
                                ]
                            },
                            "created_after": {
                                "type": "string",
                                "description": "Filter tickets created on or after this ISO 8601 date (e.g. 2026-03-01)."
                            },
                            "created_before": {
                                "type": "string",
                                "description": "Filter tickets created on or before this ISO 8601 date (e.g. 2026-03-31)."
                            },
                            "search": {
                                "type": "string",
                                "description": "Free-text search across subject, description, customer name, email, phone, and ticket number."
                            },
                            "sort_by": {
                                "type": "string",
                                "description": "Sort field (default: created_at).",
                                "enum": [
                                    "created_at",
                                    "updated_at",
                                    "priority",
                                    "ticket_number"
                                ],
                                "default": "created_at"
                            },
                            "sort_order": {
                                "type": "string",
                                "description": "Sort direction (default: desc).",
                                "enum": [
                                    "asc",
                                    "desc"
                                ],
                                "default": "desc"
                            },
                            "page": {
                                "type": "integer",
                                "description": "Page number for pagination (default: 1).",
                                "default": 1
                            },
                            "per_page": {
                                "type": "integer",
                                "description": "Results per page (1-50, default: 20).",
                                "default": 20
                            }
                        }
                    }
                },
                {
                    "name": "mark-ticket-notifications-read-tool",
                    "root_name": "mark-ticket-notifications-read-tool",
                    "version": "3384c0d1",
                    "description": "Mark ticket notifications as read. Provide specific notification IDs or omit to mark all unread notifications as read.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "mark-ticket-notifications-read-tool arguments",
                        "type": "object",
                        "properties": {
                            "ids": {
                                "type": "array",
                                "description": "Specific notification IDs to mark as read. Omit to mark all unread notifications."
                            }
                        }
                    }
                },
                {
                    "name": "sync-ticket-labels-tool",
                    "root_name": "sync-ticket-labels-tool",
                    "version": "7707327b",
                    "description": "Sync labels on a ticket. Replaces all existing labels with the provided set. Pass an empty array to remove all labels.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "sync-ticket-labels-tool arguments",
                        "type": "object",
                        "properties": {
                            "ticket_id": {
                                "type": "string",
                                "description": "The UUID of the ticket."
                            },
                            "label_ids": {
                                "type": "array",
                                "description": "Array of label IDs to set on the ticket. Pass empty array to remove all."
                            }
                        },
                        "required": [
                            "ticket_id",
                            "label_ids"
                        ]
                    }
                },
                {
                    "name": "update-ticket-label-tool",
                    "root_name": "update-ticket-label-tool",
                    "version": "4e11cc28",
                    "description": "Update a ticket label's name, color, or description. Only provided fields are updated.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update-ticket-label-tool arguments",
                        "type": "object",
                        "properties": {
                            "label_id": {
                                "type": "integer",
                                "description": "The ID of the label to update."
                            },
                            "name": {
                                "type": "string",
                                "description": "Updated label name (must be unique within tenant)."
                            },
                            "color": {
                                "type": "string",
                                "description": "Updated hex color code (e.g. #FF5733)."
                            },
                            "description": {
                                "type": "string",
                                "description": "Updated description."
                            }
                        },
                        "required": [
                            "label_id"
                        ]
                    }
                },
                {
                    "name": "update-ticket-tool",
                    "root_name": "update-ticket-tool",
                    "version": "b2de7695",
                    "description": "Update an existing ticket's subject, description, priority, channel, or customer details. Only provided fields are updated.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update-ticket-tool arguments",
                        "type": "object",
                        "properties": {
                            "ticket_id": {
                                "type": "string",
                                "description": "The UUID of the ticket to update."
                            },
                            "subject": {
                                "type": "string",
                                "description": "Updated subject line."
                            },
                            "description": {
                                "type": "string",
                                "description": "Updated description."
                            },
                            "priority": {
                                "type": "string",
                                "description": "Updated priority level.",
                                "enum": [
                                    "low",
                                    "medium",
                                    "high",
                                    "urgent"
                                ]
                            },
                            "channel": {
                                "type": "string",
                                "description": "Updated channel.",
                                "enum": [
                                    "whatsapp",
                                    "sms",
                                    "phone",
                                    "email",
                                    "web",
                                    "internal"
                                ]
                            },
                            "customer_name": {
                                "type": "string",
                                "description": "Updated customer name."
                            },
                            "customer_email": {
                                "type": "string",
                                "description": "Updated customer email."
                            },
                            "customer_phone": {
                                "type": "string",
                                "description": "Updated customer phone."
                            },
                            "customer_company": {
                                "type": "string",
                                "description": "Updated customer company."
                            },
                            "label_ids": {
                                "type": "array",
                                "description": "Array of label IDs to sync (replaces existing labels)."
                            }
                        },
                        "required": [
                            "ticket_id"
                        ]
                    }
                },
                {
                    "name": "delete-ticket-label-tool",
                    "root_name": "delete-ticket-label-tool",
                    "version": "2be5f484",
                    "description": "Delete a ticket label. Removes the label from all tickets that have it.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "delete-ticket-label-tool arguments",
                        "type": "object",
                        "properties": {
                            "label_id": {
                                "type": "integer",
                                "description": "The ID of the label to delete."
                            }
                        },
                        "required": [
                            "label_id"
                        ]
                    }
                },
                {
                    "name": "delete-ticket-tool",
                    "root_name": "delete-ticket-tool",
                    "version": "129446bb",
                    "description": "Permanently delete a ticket and all its replies.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "delete-ticket-tool arguments",
                        "type": "object",
                        "properties": {
                            "ticket_id": {
                                "type": "string",
                                "description": "The UUID of the ticket to delete."
                            }
                        },
                        "required": [
                            "ticket_id"
                        ]
                    }
                }
            ]
        },
        {
            "key": "kb",
            "name": "Knowledge base",
            "summary": "Your knowledge base: categories, search and full article text.",
            "path": "/mcp/v1/kb",
            "module": null,
            "instructions": "Read-only MCP server for a tenant's knowledge base. Provides full access to categories and articles \u2014 browse, search, and read any content. All data is scoped to the authenticated tenant.\n\n## Quick Start\n1. **get-kb-overview** \u2014 Start here. Returns all categories, article counts, and recently updated articles in a single call. Gives you the full picture of what's available.\n\n## Browsing by Structure\n2. **list-categories** \u2014 List all categories with descriptions and article counts.\n3. **get-category** \u2014 Get a single category (by ID or slug) with all its articles listed.\n4. **list-articles** \u2014 Paginated article list. Filter by category (ID or slug). Returns metadata only.\n\n## Reading Content\n5. **get-article** \u2014 Get a single article (by ID or slug) with full markdown content. Use this to read the actual body of any article.\n\n## Searching\n6. **search-articles** \u2014 Multi-word AND search across titles, excerpts, and content. Returns results ranked by relevance with context snippets.\n\n## Typical Workflows\n- **Answer a question**: search-articles(\"billing setup\") \u2192 get-article(best match ID)\n- **Browse a topic**: get-kb-overview \u2192 get-category(\"platform-guide\") \u2192 get-article(article slug)\n- **Find everything**: list-articles(page=1, per_page=50) to paginate through all content\n\n## Notes\n- All identifiers accept both numeric IDs and string slugs.\n- Articles contain markdown content that may include headings, lists, code blocks, and links.\n- By default, only published content is returned. Pass published_only=false to include drafts.\n- This server is read-only. No content can be created, updated, or deleted through these tools.",
            "tools": [
                {
                    "name": "get-article-tool",
                    "root_name": "get-article-tool",
                    "version": "1b55e41e",
                    "description": "Get the full content of a single knowledge base article by ID or slug. Returns all metadata and the complete markdown body. This is the tool to use when you need to read an article's actual content.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-article-tool arguments",
                        "type": "object",
                        "properties": {
                            "identifier": {
                                "type": "string",
                                "description": "Article ID (numeric) or slug (string). Example: \"7\" or \"getting-started\"."
                            }
                        },
                        "required": [
                            "identifier"
                        ]
                    }
                },
                {
                    "name": "get-category-tool",
                    "root_name": "get-category-tool",
                    "version": "df59caab",
                    "description": "Get a single knowledge base category by ID or slug, including all its articles with titles and excerpts. Use this to browse all articles within a specific category.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-category-tool arguments",
                        "type": "object",
                        "properties": {
                            "identifier": {
                                "type": "string",
                                "description": "Category ID (numeric) or slug (string). Example: \"42\" or \"platform-guide\"."
                            },
                            "published_only": {
                                "type": "boolean",
                                "description": "When true (default), returns only published articles. Set to false to include drafts."
                            }
                        },
                        "required": [
                            "identifier"
                        ]
                    }
                },
                {
                    "name": "get-kb-overview-tool",
                    "root_name": "get-kb-overview-tool",
                    "version": "1539aa4c",
                    "description": "Get a complete overview of the tenant knowledge base. Returns all categories with article counts, total statistics, and the most recently updated articles. Use this as the starting point to understand what content is available.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-kb-overview-tool arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "list-articles-tool",
                    "root_name": "list-articles-tool",
                    "version": "d4356c6b",
                    "description": "List knowledge base articles with pagination. Filter by category (ID or slug) and published status. Returns article metadata without full content \u2014 use get-article to retrieve the full markdown body.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-articles-tool arguments",
                        "type": "object",
                        "properties": {
                            "category": {
                                "type": "string",
                                "description": "Filter by category ID (numeric) or slug (string). Omit to list all articles."
                            },
                            "published_only": {
                                "type": "boolean",
                                "description": "When true (default), returns only published articles. Set to false to include drafts."
                            },
                            "page": {
                                "type": "integer",
                                "description": "Page number for pagination. Default: 1."
                            },
                            "per_page": {
                                "type": "integer",
                                "description": "Articles per page (1-50). Default: 25."
                            }
                        }
                    }
                },
                {
                    "name": "list-categories-tool",
                    "root_name": "list-categories-tool",
                    "version": "820e42ab",
                    "description": "List all knowledge base categories for this tenant with article counts. Each category has an ID, slug, name, description, and the number of published articles it contains.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-categories-tool arguments",
                        "type": "object",
                        "properties": {
                            "include_unpublished": {
                                "type": "boolean",
                                "description": "When true, includes unpublished (draft) categories. Default: false (published only)."
                            }
                        }
                    }
                },
                {
                    "name": "search-articles-tool",
                    "root_name": "search-articles-tool",
                    "version": "683c2d7b",
                    "description": "Search knowledge base articles by keyword across titles, excerpts, and full markdown content. Supports multi-word queries with AND logic \u2014 all words must match. Returns matching articles ranked by relevance (title matches first, then excerpt, then body). Use get-article to read the full content of any result.",
                    "writes": false,
                    "read_only": false,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "search-articles-tool arguments",
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "Search keywords. Multiple words use AND logic \u2014 all must match. Example: \"billing setup\" finds articles containing both \"billing\" and \"setup\"."
                            },
                            "published_only": {
                                "type": "boolean",
                                "description": "When true (default), searches only published articles. Set to false to include drafts."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Maximum results to return (1-30). Default: 20."
                            }
                        },
                        "required": [
                            "query"
                        ]
                    }
                }
            ]
        },
        {
            "key": "content",
            "name": "Platform content",
            "summary": "Public help articles, changelog, roadmap and system status.",
            "path": "/mcp/v1/content",
            "module": null,
            "instructions": "Read-only MCP server for public content: Knowledge Base, Changelog, Roadmap, and System Status.\n\n## Knowledge Base\n- list-kb-categories \u2192 list-articles (by category or all) \u2192 get-article (full content)\n- search-articles for free-text search across titles, excerpts, and content\n\n## Changelog\n- list-changelog (paginated, filterable by version) \u2192 get-changelog-entry (full content)\n\n## Roadmap\n- list-roadmap (filterable by status: planned/in_progress/released) \u2192 get-roadmap-item (full details)\n\n## System Status\n- get-status-overview for overall health + all services\n- list-services for service details \u2192 get-service-metrics (response times, uptime)\n- list-incidents (active, resolved, scheduled maintenance) \u2192 get-incident (full timeline)\n\nAll tools are read-only and return only published/visible content.",
            "tools": [
                {
                    "name": "get-article-tool",
                    "root_name": "get_help_article",
                    "version": "679a00b6",
                    "description": "Get the full content of a published knowledge base article by its slug or ID. Returns the complete markdown content.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-article-tool arguments",
                        "type": "object",
                        "properties": {
                            "slug": {
                                "type": "string",
                                "description": "Article slug or numeric ID."
                            }
                        },
                        "required": [
                            "slug"
                        ]
                    }
                },
                {
                    "name": "get-changelog-entry-tool",
                    "root_name": "get-changelog-entry-tool",
                    "version": "0e5d5cfa",
                    "description": "Get the full content of a published changelog entry by its ID.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-changelog-entry-tool arguments",
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "integer",
                                "description": "Changelog entry ID."
                            }
                        },
                        "required": [
                            "id"
                        ]
                    }
                },
                {
                    "name": "get-incident-tool",
                    "root_name": "get-incident-tool",
                    "version": "d602dc26",
                    "description": "Get full details of an incident or scheduled maintenance by ID. Includes the complete timeline of status updates and affected services.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-incident-tool arguments",
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "integer",
                                "description": "Incident ID."
                            }
                        },
                        "required": [
                            "id"
                        ]
                    }
                },
                {
                    "name": "get-roadmap-item-tool",
                    "root_name": "get-roadmap-item-tool",
                    "version": "70356c5b",
                    "description": "Get full details of a published roadmap item by its slug or ID. Returns the complete description and timeline.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-roadmap-item-tool arguments",
                        "type": "object",
                        "properties": {
                            "slug": {
                                "type": "string",
                                "description": "Roadmap item slug or numeric ID."
                            }
                        },
                        "required": [
                            "slug"
                        ]
                    }
                },
                {
                    "name": "get-service-metrics-tool",
                    "root_name": "get-service-metrics-tool",
                    "version": "2b4e73a3",
                    "description": "Get performance metrics for a specific service: response times, uptime percentages, and availability over a time period (default: 24 hours, max: 90 days).",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-service-metrics-tool arguments",
                        "type": "object",
                        "properties": {
                            "service_slug": {
                                "type": "string",
                                "description": "Service slug. Use list-services to discover slugs."
                            },
                            "hours": {
                                "type": "integer",
                                "description": "Lookback period in hours (1-2160, default: 24).",
                                "default": 24
                            }
                        },
                        "required": [
                            "service_slug"
                        ]
                    }
                },
                {
                    "name": "get-status-overview-tool",
                    "root_name": "get-status-overview-tool",
                    "version": "ad40c530",
                    "description": "Get the overall system status: all services grouped, active incidents count, scheduled maintenance, and an aggregate health indicator. Use this first to understand current system health.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get-status-overview-tool arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "list-articles-tool",
                    "root_name": "list_help_articles",
                    "version": "6a76cf0d",
                    "description": "List published knowledge base articles. Optionally filter by category slug. Returns titles and excerpts \u2014 use get-article for full content.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-articles-tool arguments",
                        "type": "object",
                        "properties": {
                            "category_slug": {
                                "type": "string",
                                "description": "Filter by category slug. Use list-kb-categories to discover slugs."
                            },
                            "page": {
                                "type": "integer",
                                "description": "Page number (default: 1).",
                                "default": 1
                            },
                            "per_page": {
                                "type": "integer",
                                "description": "Results per page (1-50, default: 25).",
                                "default": 25
                            }
                        }
                    }
                },
                {
                    "name": "list-changelog-tool",
                    "root_name": "list-changelog-tool",
                    "version": "6ca0c75f",
                    "description": "List published changelog entries, newest first. Optionally filter by version string. Returns titles and versions \u2014 use get-changelog-entry for full content.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-changelog-tool arguments",
                        "type": "object",
                        "properties": {
                            "version": {
                                "type": "string",
                                "description": "Filter by version string (partial match). Example: \"2.1\""
                            },
                            "search": {
                                "type": "string",
                                "description": "Free-text search across title and content."
                            },
                            "page": {
                                "type": "integer",
                                "description": "Page number (default: 1).",
                                "default": 1
                            },
                            "per_page": {
                                "type": "integer",
                                "description": "Results per page (1-50, default: 20).",
                                "default": 20
                            }
                        }
                    }
                },
                {
                    "name": "list-incidents-tool",
                    "root_name": "list-incidents-tool",
                    "version": "45b0cc8c",
                    "description": "List incidents and scheduled maintenance. Filter by type (incident/maintenance), status (active/resolved), or recency. Returns summaries \u2014 use get-incident for full timeline.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-incidents-tool arguments",
                        "type": "object",
                        "properties": {
                            "type": {
                                "type": "string",
                                "description": "Filter by type.",
                                "enum": [
                                    "incident",
                                    "maintenance"
                                ]
                            },
                            "filter": {
                                "type": "string",
                                "description": "Filter by resolution status.",
                                "enum": [
                                    "active",
                                    "resolved"
                                ]
                            },
                            "days": {
                                "type": "integer",
                                "description": "Only show incidents from the last N days (1-365)."
                            },
                            "page": {
                                "type": "integer",
                                "description": "Page number (default: 1).",
                                "default": 1
                            },
                            "per_page": {
                                "type": "integer",
                                "description": "Results per page (1-50, default: 20).",
                                "default": 20
                            }
                        }
                    }
                },
                {
                    "name": "list-kb-categories-tool",
                    "root_name": "list-kb-categories-tool",
                    "version": "ab96ba0e",
                    "description": "List all published knowledge base categories with article counts. Use the category slug or ID to filter articles with list-articles.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-kb-categories-tool arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "list-roadmap-tool",
                    "root_name": "list-roadmap-tool",
                    "version": "d3cb3107",
                    "description": "List published roadmap items. Optionally filter by status (planned, in_progress, released). Returns summaries \u2014 use get-roadmap-item for full details.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-roadmap-tool arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "Filter by status: planned, in_progress, released.",
                                "enum": [
                                    "planned",
                                    "in_progress",
                                    "released"
                                ]
                            },
                            "search": {
                                "type": "string",
                                "description": "Free-text search across title and summary."
                            }
                        }
                    }
                },
                {
                    "name": "list-services-tool",
                    "root_name": "list-services-tool",
                    "version": "9a273025",
                    "description": "List all visible services with their current status, uptime, and response time. Optionally filter by group name or status.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list-services-tool arguments",
                        "type": "object",
                        "properties": {
                            "group": {
                                "type": "string",
                                "description": "Filter by service group name."
                            },
                            "status": {
                                "type": "string",
                                "description": "Filter by status: operational, degraded_performance, partial_outage, major_outage, under_maintenance.",
                                "enum": [
                                    "operational",
                                    "degraded_performance",
                                    "partial_outage",
                                    "major_outage",
                                    "under_maintenance"
                                ]
                            }
                        }
                    }
                },
                {
                    "name": "search-articles-tool",
                    "root_name": "search_help_articles",
                    "version": "3ee3b2d3",
                    "description": "Search published knowledge base articles by keyword. Searches across title, excerpt, and content. Supports multi-word AND queries.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "search-articles-tool arguments",
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "Search keywords (space-separated, AND logic)."
                            }
                        },
                        "required": [
                            "query"
                        ]
                    }
                }
            ]
        },
        {
            "key": "calls",
            "name": "Calls",
            "summary": "Call history, recordings, transcripts, events and Call Studio scripts.",
            "path": "/mcp/v1/calls",
            "module": "calls",
            "instructions": "The phone calls this business has made and received, and the scripts its agents follow. \n\nWork in this order: list_calls to find the call, get_call to read it, then list_call_events when you need to explain WHY it ended that way and get_call_transcript when you need to know what was said. \n\nANSWER FROM THE RECORD, NOT FROM THE SHAPE OF IT. `status`, `outcome`, `duration_seconds` and the hang-up reason are stored facts. Anything under `advisory` \u2014 who answered, how the recording went \u2014 is reconstructed and can change between two reads of the same call, so never quote it as the record. If a user asks a question the record does not answer, say the record does not answer it. \n\nTwo tools reach the real world and neither can be undone. place_call rings a live handset: read the number back and get an explicit yes first, and never dial a number the user did not name. update_call_script changes what agents say on the NEXT live call the moment it saves \u2014 read the current script first, send the whole list back including what you are keeping, and tell the user what you retired. \n\nRecordings and transcripts are other people's conversations. Quote them to answer the question that was asked, do not summarise them onward, and masked lines stay masked \u2014 there is no unmask here. \n\nMany of these businesses serve Kiswahili-speaking callers. If the user writes to you in Kiswahili, answer in Kiswahili.",
            "tools": [
                {
                    "name": "get_call",
                    "root_name": "get_call",
                    "version": "6ad948bc",
                    "description": "One call in full: both legs, when it started and ended, how long it lasted, the outcome and hang-up reason, which agent or person handled it, and what recordings exist. Take the call_id from list_calls, or pass the room_name if that is what you have.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "calls.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_call arguments",
                        "type": "object",
                        "properties": {
                            "call_id": {
                                "type": "integer",
                                "description": "The call to read, from list_calls."
                            },
                            "room_name": {
                                "type": "string",
                                "description": "Alternative to call_id: the PBX room name, if that is the identifier you were given."
                            }
                        }
                    }
                },
                {
                    "name": "get_call_recording",
                    "root_name": "get_call_recording",
                    "version": "674ff9f6",
                    "description": "A time-limited link to listen to a call recording. It hands back a URL for a person to open, never the audio itself, and the link expires \u2014 so give it to the user rather than storing it. Call get_call first to see which recordings a call has.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "calls.recordings.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_call_recording arguments",
                        "type": "object",
                        "properties": {
                            "call_id": {
                                "type": "integer",
                                "description": "The call, from list_calls."
                            },
                            "room_name": {
                                "type": "string",
                                "description": "Alternative to call_id: the PBX room name."
                            },
                            "recording_id": {
                                "type": "string",
                                "description": "Which recording, from get_call. Omit for the most recent one on the call."
                            },
                            "expires_in_seconds": {
                                "type": "integer",
                                "description": "How long the link should stay valid (60 to 604800, default 900)."
                            }
                        }
                    }
                },
                {
                    "name": "get_call_scripts",
                    "root_name": "get_call_scripts",
                    "version": "5f9fc765",
                    "description": "The Call Studio scripts agents follow on a live call: the core block asked on every call, plus each category with its questions in order, both English and Kiswahili labels, types, options and conditions. Read this before proposing any change to what agents say.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "calls.studio.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_call_scripts arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "get_call_transcript",
                    "root_name": "get_call_transcript",
                    "version": "7231efb5",
                    "description": "What was actually said on a call, in order, labelled by speaker. Sensitive lines \u2014 card details, anything a node marked PII or PCI \u2014 come back masked and cannot be unmasked through this connection. Use it to answer \"what did the customer ask for\" rather than guessing from the outcome.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "calls.transcripts.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_call_transcript arguments",
                        "type": "object",
                        "properties": {
                            "call_id": {
                                "type": "integer",
                                "description": "The call to read, from list_calls."
                            },
                            "room_name": {
                                "type": "string",
                                "description": "Alternative to call_id: the PBX room name."
                            },
                            "speaker": {
                                "type": "string",
                                "description": "Only one side: user or agent."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max lines to return (default 100, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_call_events",
                    "root_name": "list_call_events",
                    "version": "c5823b31",
                    "description": "The event timeline for one call, oldest first \u2014 ringing, dispatch, forward, answer, hang-up and every failure in between. This is the tool that answers \"why did this call drop\": look for a *_failed event and read its reason before offering a theory.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "calls.events.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_call_events arguments",
                        "type": "object",
                        "properties": {
                            "call_id": {
                                "type": "integer",
                                "description": "The call, from list_calls."
                            },
                            "room_name": {
                                "type": "string",
                                "description": "Alternative to call_id: the PBX room name."
                            },
                            "event_type": {
                                "type": "string",
                                "description": "Only events whose type contains this, e.g. \"forward\" or \"fail\"."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max events to return (default 50, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_calls",
                    "root_name": "list_calls",
                    "version": "7b6f154e",
                    "description": "The call history for this business, newest first: who called whom, how long it lasted, how it ended and whether it was recorded. Filter by direction, status, outcome, phone number, agent or date range. Start here before asking about any individual call.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "calls.history.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_calls arguments",
                        "type": "object",
                        "properties": {
                            "direction": {
                                "type": "string",
                                "description": "inbound, outbound or internal."
                            },
                            "status": {
                                "type": "string",
                                "description": "ringing, in_progress, ended, missed, failed or rejected."
                            },
                            "outcome": {
                                "type": "string",
                                "description": "The settled outcome recorded for the call, e.g. answered, no_answer, busy."
                            },
                            "number": {
                                "type": "string",
                                "description": "Match either leg of the call against this phone number or fragment."
                            },
                            "agent_config_id": {
                                "type": "string",
                                "description": "Only calls handled by this AI agent configuration."
                            },
                            "date_from": {
                                "type": "string",
                                "description": "Earliest call date, YYYY-MM-DD."
                            },
                            "date_to": {
                                "type": "string",
                                "description": "Latest call date, YYYY-MM-DD."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max calls to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "place_call",
                    "root_name": "place_call",
                    "version": "41146353",
                    "description": "Ring a real phone. This dials a live handset immediately \u2014 there is no draft, no preview and no undo \u2014 so read the number back to the user and get their agreement before calling it. Accepts a phone number, or a colleague's username for an internal call.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "calls.place"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "place_call arguments",
                        "type": "object",
                        "properties": {
                            "to": {
                                "type": "string",
                                "description": "Who to ring: a phone number (international format preferred; a local number is completed from the caller ID's country) or a colleague's username for an internal call."
                            },
                            "from_number": {
                                "type": "string",
                                "description": "Which of the account's own numbers to show as caller ID. Omit for the first one this person may dial from. Ignored for internal calls."
                            },
                            "use_agent": {
                                "type": "boolean",
                                "description": "Let an AI agent take the call instead of a person (default false)."
                            },
                            "agent_config_id": {
                                "type": "string",
                                "description": "Which AI agent, when use_agent is true."
                            },
                            "record_call": {
                                "type": "boolean",
                                "description": "Record this call. Leave unset to follow the number's own recording policy."
                            }
                        },
                        "required": [
                            "to"
                        ]
                    }
                },
                {
                    "name": "update_call_script",
                    "root_name": "update_call_script",
                    "version": "44936ba5",
                    "description": "Replace one Call Studio category's question list in a single change \u2014 reorder, edit, add and retire together. This changes what agents ask on LIVE calls the moment it saves. Send the complete list you want: any question you leave out is retired, and past answers keep resolving to it.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "calls.scripts.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_call_script arguments",
                        "type": "object",
                        "properties": {
                            "category_key": {
                                "type": "string",
                                "description": "Which category to replace, from get_call_scripts. \"core\" is the block asked on every call."
                            },
                            "questions_json": {
                                "type": "string",
                                "description": "A JSON object string {\"questions\":[ ... ]}, max 40. Each question is {\"key\":\"lowercase_snake\",\"labelEn\":\"...\",\"labelSw\":\"...\",\"type\":\"text|number|money|date|enum|boolean|phone\",\"options\":[\"...\"] (enum only, at least two),\"required\":true|false,\"conditionKey\":\"...\",\"conditionValue\":\"...\",\"source\":\"agent|auto|either\"}. Order in the array is the order agents are asked. Include every question you want to KEEP."
                            }
                        },
                        "required": [
                            "category_key",
                            "questions_json"
                        ]
                    }
                }
            ]
        },
        {
            "key": "routing",
            "name": "Call routing",
            "summary": "Routing rules, ring groups, working hours and forwarding targets.",
            "path": "/mcp/v1/routing",
            "module": "calls",
            "instructions": "Where this business's incoming calls go: routing rules, ring groups, working hours and forwarding. \n\nNOTHING HERE IS A DRAFT. Every write reaches live callers on the next call \u2014 there is no publish step to hide behind. Read first (list_dispatch_rules, get_dispatch_rule, list_ring_groups, get_work_hours), say what you intend to change in plain words, get an explicit yes, then write. \n\nFORWARDING IS THE ONE THAT BITES. A forward must pass TWO independent gates, and failing either drops the caller with no announcement and no error in any log the user can see:\\n  1. the destination is on that number's forwarding allow-list \u2014 this server checks it, and REFUSES a routing write that would forward somewhere not allow-listed;\n  2. DIRECT_FORWARD_ENABLED, a switch inside the call platform that CANNOT be read from here.\nSo never tell a user forwarding is working. Say the allow-list is correct and the platform switch is unverified, and that one real test call is the only honest proof. If a user reports \"forwarding saved but calls drop\", this is almost always why: check list_forwarding_targets before anything else. \n\nRules are evaluated in priority order and the FIRST match wins, so a new rule can silently shadow an existing one \u2014 read the whole list before adding. Deleting a rule sends its callers to the next match, which is a routing change too, not merely a tidy-up. \n\nA ring group with no members rings nobody and overflows immediately. Order matters in a sequential group: it is who gets rung first, and people notice. \n\nMany of these businesses serve Kiswahili-speaking callers. If the user writes to you in Kiswahili, answer in Kiswahili.",
            "tools": [
                {
                    "name": "get_dispatch_rule",
                    "root_name": "get_dispatch_rule",
                    "version": "ec329386",
                    "description": "One routing rule in full: every condition it matches on, the action it takes, its fallback, and \u2014 when it forwards \u2014 whether the destination is actually on the forwarding allow-list. A forward that is not on the allow-list drops callers silently, so this check is part of reading the rule.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "call-routing.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_dispatch_rule arguments",
                        "type": "object",
                        "properties": {
                            "rule_id": {
                                "type": "string",
                                "description": "The rule to read, from list_dispatch_rules."
                            }
                        },
                        "required": [
                            "rule_id"
                        ]
                    }
                },
                {
                    "name": "get_ring_group",
                    "root_name": "get_ring_group",
                    "version": "ac41a15c",
                    "description": "One ring group in full: its members in ringing order, the phone numbers pointed at it, and what happens on overflow. If it overflows to a forward, this also says what can be checked about the two forwarding gates from here.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ring-groups.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_ring_group arguments",
                        "type": "object",
                        "properties": {
                            "ring_group_id": {
                                "type": "integer",
                                "description": "The ring group to read, from list_ring_groups."
                            }
                        },
                        "required": [
                            "ring_group_id"
                        ]
                    }
                },
                {
                    "name": "get_work_hours",
                    "root_name": "get_work_hours",
                    "version": "0e7547d0",
                    "description": "The working-hours schedule this business runs on: the account default, any number that overrides it, and every schedule available to choose from. Routing rules use \"during work hours\" and \"outside work hours\", so this is what decides which of them fires.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "call-routing.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_work_hours arguments",
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Max schedules to list (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_dispatch_rules",
                    "root_name": "list_dispatch_rules",
                    "version": "9815ace9",
                    "description": "The call routing rules for this account, in the order they are evaluated: what each one matches on and where it sends the call. First match wins, so read the whole list before concluding a rule is unreachable or adding another.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "call-routing.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_dispatch_rules arguments",
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Max rules to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_forwarding_targets",
                    "root_name": "list_forwarding_targets",
                    "version": "1ac7b4f0",
                    "description": "The forwarding allow-list for one of this account's phone numbers \u2014 the only destinations a call on that line may be sent to. A forward to a number that is NOT on this list is hung up with no announcement and no error, so check here before trusting any forwarding rule.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "numbers.forwarding.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_forwarding_targets arguments",
                        "type": "object",
                        "properties": {
                            "number": {
                                "type": "string",
                                "description": "One of this account's phone numbers, in international format (+255...)."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max targets to return (default 25, max 100)."
                            }
                        },
                        "required": [
                            "number"
                        ]
                    }
                },
                {
                    "name": "list_ring_groups",
                    "root_name": "list_ring_groups",
                    "version": "6f0e2a92",
                    "description": "The ring groups on this account: who a call rings, whether it rings everyone at once or one after another, how long it waits, and what happens when nobody picks up. Read this before changing who is on call.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ring-groups.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_ring_groups arguments",
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Max ring groups to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "add_forwarding_target",
                    "root_name": "add_forwarding_target",
                    "version": "39b2b873",
                    "description": "Allow one destination to receive calls forwarded from one of this account's numbers. This is the first of the two gates every forward must pass; without it the caller is hung up with no announcement. Adding a destination does not by itself forward anything \u2014 a routing rule still has to send calls there.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "numbers.forwarding.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "add_forwarding_target arguments",
                        "type": "object",
                        "properties": {
                            "number": {
                                "type": "string",
                                "description": "Which of this account's numbers, in international format (+255...). The allow-list is per number."
                            },
                            "target": {
                                "type": "string",
                                "description": "The destination phone number calls may be forwarded to, in international format. Use this or team_member_id."
                            },
                            "team_member_id": {
                                "type": "integer",
                                "description": "A colleague to allow instead: their own phone number, name and role are used. Use this or target."
                            },
                            "name": {
                                "type": "string",
                                "description": "A label for the destination, so the allow-list reads as people rather than numbers."
                            }
                        },
                        "required": [
                            "number"
                        ]
                    }
                },
                {
                    "name": "create_dispatch_rule",
                    "root_name": "create_dispatch_rule",
                    "version": "d27b7c3f",
                    "description": "Add a call routing rule. It takes effect on the very next inbound call \u2014 there is no draft state here. If the rule forwards, the destination is checked against the forwarding allow-list first and the rule is REFUSED rather than saved half-working, because a forward that is not allow-listed hangs callers up with no error anywhere.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "call-routing.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_dispatch_rule arguments",
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "A short name for the rule. Overrides any name inside rule_json."
                            },
                            "rule_json": {
                                "type": "string",
                                "description": "A JSON object string. {\"name\":\"...\",\"priority\":10,\"enabled\":true,\"conditions\":[{\"type\":\"phone_number|phone_number_prefix|caller_prefix|phone_number_set|caller_list|caller_not_in_list|time_schedule|outside_schedule|during_work_hours|outside_work_hours|all_numbers\",\"value\":\"+255...\",\"callerListId\":\"uuid\",\"scheduleId\":\"uuid\"}],\"actionType\":\"dispatch_agent|dispatch_ivr|ring_group|ring_user|forward|reject|voicemail\",\"actionConfig\":{\"agentConfigId\":\"...\",\"ivrFlowId\":\"...\",\"ringGroupId\":\"...\",\"clientUserId\":\"...\",\"forwardTo\":\"+255...\",\"timeoutSeconds\":30},\"fallbackActionType\":\"...\",\"fallbackActionConfig\":{...}}. Rules are evaluated by priority and the first match wins."
                            }
                        },
                        "required": [
                            "rule_json"
                        ]
                    }
                },
                {
                    "name": "create_ring_group",
                    "root_name": "create_ring_group",
                    "version": "94d7bd79",
                    "description": "Create a ring group \u2014 a set of people a call rings, either all at once or one after another, with a rule for what happens when nobody answers. The group is created empty: set_ring_group_members decides who is in it, and it rings nobody until you do.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ring-groups.create"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_ring_group arguments",
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "What to call the group, e.g. \"Sales\" or \"After hours\"."
                            },
                            "description": {
                                "type": "string",
                                "description": "A sentence saying who this group is for."
                            },
                            "strategy": {
                                "type": "string",
                                "description": "simultaneous rings everyone at once; sequential rings them one after another in member order."
                            },
                            "timeout_seconds": {
                                "type": "integer",
                                "description": "How long to ring before overflow, 5 to 120 seconds."
                            },
                            "overflow_action": {
                                "type": "string",
                                "description": "What happens when nobody answers: hangup, forward, voicemail or queue."
                            },
                            "overflow_target": {
                                "type": "string",
                                "description": "Required when overflow_action is forward: the phone number in international format (+255...) to send the caller to."
                            },
                            "enabled": {
                                "type": "boolean",
                                "description": "Whether the group is in service (default true)."
                            },
                            "queue_wait_seconds": {
                                "type": "integer",
                                "description": "Queue only: how long a caller waits before the queue times out."
                            },
                            "queue_timeout_action": {
                                "type": "string",
                                "description": "Queue only: hangup, dispatch_agent, forward or voicemail when the queue times out."
                            },
                            "queue_timeout_agent_config_id": {
                                "type": "string",
                                "description": "Queue only: which AI agent takes over, required when queue_timeout_action is dispatch_agent."
                            }
                        },
                        "required": [
                            "name",
                            "strategy",
                            "timeout_seconds",
                            "overflow_action"
                        ]
                    }
                },
                {
                    "name": "delete_dispatch_rule",
                    "root_name": "delete_dispatch_rule",
                    "version": "c03421af",
                    "description": "Delete a call routing rule permanently. Callers that used to match it fall through to the next rule, or to the number's own settings if none matches \u2014 which can silently change where every call goes. Read the rule with get_dispatch_rule and get an explicit yes before calling this.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "call-routing.delete"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "delete_dispatch_rule arguments",
                        "type": "object",
                        "properties": {
                            "rule_id": {
                                "type": "string",
                                "description": "The rule to delete, from list_dispatch_rules."
                            }
                        },
                        "required": [
                            "rule_id"
                        ]
                    }
                },
                {
                    "name": "set_ring_group_members",
                    "root_name": "set_ring_group_members",
                    "version": "7fe24b44",
                    "description": "Set exactly who is in a ring group, in ringing order. Send the complete list you want: anybody not in it is removed, and for a sequential group the order you send is the order phones ring. This changes who is called on the very next inbound call.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ring-groups.members.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "set_ring_group_members arguments",
                        "type": "object",
                        "properties": {
                            "ring_group_id": {
                                "type": "integer",
                                "description": "The ring group to change, from list_ring_groups."
                            },
                            "client_user_ids": {
                                "type": "array",
                                "description": "The complete list of team member ids who should be in the group, in ringing order. Anybody not listed is removed. Ids come from get_ring_group or the team directory.",
                                "items": {
                                    "type": "integer"
                                }
                            }
                        },
                        "required": [
                            "ring_group_id",
                            "client_user_ids"
                        ]
                    }
                },
                {
                    "name": "set_work_hours",
                    "root_name": "set_work_hours",
                    "version": "cb4af38d",
                    "description": "Point the account, or one phone number, at a working-hours schedule. This takes effect immediately and changes which routing rules fire: every \"during work hours\" and \"outside work hours\" rule starts answering differently on the next call. Pass no schedule_id to clear it.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "call-routing.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "set_work_hours arguments",
                        "type": "object",
                        "properties": {
                            "schedule_id": {
                                "type": "string",
                                "description": "The schedule to use, from get_work_hours. Omit to clear the schedule."
                            },
                            "number": {
                                "type": "string",
                                "description": "Set it for one phone number in international format (+255...). Omit to set the account default."
                            }
                        }
                    }
                },
                {
                    "name": "update_dispatch_rule",
                    "root_name": "update_dispatch_rule",
                    "version": "db66bee6",
                    "description": "Change an existing call routing rule. The change reaches live callers on the very next call. Read the rule with get_dispatch_rule first and send back the fields you want changed; if you make it forward somewhere, the destination is checked against the forwarding allow-list and the change is refused rather than saved half-working.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "call-routing.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_dispatch_rule arguments",
                        "type": "object",
                        "properties": {
                            "rule_id": {
                                "type": "string",
                                "description": "The rule to change, from list_dispatch_rules."
                            },
                            "changes_json": {
                                "type": "string",
                                "description": "A JSON object string with only the fields you are changing \u2014 same shape as create_dispatch_rule's rule_json (name, priority, enabled, conditions, actionType, actionConfig, fallbackActionType, fallbackActionConfig). Sending `conditions` REPLACES the whole condition list, so include the ones you are keeping."
                            }
                        },
                        "required": [
                            "rule_id",
                            "changes_json"
                        ]
                    }
                },
                {
                    "name": "update_ring_group",
                    "root_name": "update_ring_group",
                    "version": "d4ff89e5",
                    "description": "Change how a ring group behaves: whether it rings everyone at once or in order, how long it waits, and what happens when nobody answers. The change applies to the next call that reaches the group. Fields you leave out keep their current value.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ring-groups.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_ring_group arguments",
                        "type": "object",
                        "properties": {
                            "ring_group_id": {
                                "type": "integer",
                                "description": "The group to change, from list_ring_groups."
                            },
                            "name": {
                                "type": "string",
                                "description": "What to call the group, e.g. \"Sales\" or \"After hours\"."
                            },
                            "description": {
                                "type": "string",
                                "description": "A sentence saying who this group is for."
                            },
                            "strategy": {
                                "type": "string",
                                "description": "simultaneous rings everyone at once; sequential rings them one after another in member order."
                            },
                            "timeout_seconds": {
                                "type": "integer",
                                "description": "How long to ring before overflow, 5 to 120 seconds."
                            },
                            "overflow_action": {
                                "type": "string",
                                "description": "What happens when nobody answers: hangup, forward, voicemail or queue."
                            },
                            "overflow_target": {
                                "type": "string",
                                "description": "Required when overflow_action is forward: the phone number in international format (+255...) to send the caller to."
                            },
                            "enabled": {
                                "type": "boolean",
                                "description": "Whether the group is in service (default true)."
                            },
                            "queue_wait_seconds": {
                                "type": "integer",
                                "description": "Queue only: how long a caller waits before the queue times out."
                            },
                            "queue_timeout_action": {
                                "type": "string",
                                "description": "Queue only: hangup, dispatch_agent, forward or voicemail when the queue times out."
                            },
                            "queue_timeout_agent_config_id": {
                                "type": "string",
                                "description": "Queue only: which AI agent takes over, required when queue_timeout_action is dispatch_agent."
                            }
                        },
                        "required": [
                            "ring_group_id"
                        ]
                    }
                }
            ]
        },
        {
            "key": "meetings",
            "name": "Meetings",
            "summary": "See and schedule meetings, and invite people to them.",
            "path": "/mcp/v1/meetings",
            "module": "calls",
            "instructions": "The voice and video meetings this business runs: see them, book them, open them, end them. \n\nTHIS CONNECTION TELLS NOBODY ANYTHING. schedule_meeting creates a room and invite_to_meeting opens it and hands back a link \u2014 neither sends a message to a single person. Always give the user the link and say the invitation is still theirs to send, rather than implying people have been invited. \n\nA scheduled time must carry its time zone (2026-09-10T14:30:00+03:00). A time without one books the meeting in the wrong hour for everybody, and the platform refuses it rather than guessing. \n\ncancel_meeting on a LIVE meeting disconnects everybody in the room instantly and warns none of them. Ask first, every time. \n\ndial_out_to_meeting \u2014 ringing a phone so somebody joins by answering \u2014 IS NOT BUILT ON THE CALL PLATFORM YET. It will tell you so plainly. That is a missing platform feature, not a bad number and not a broken meeting: say exactly that, and offer the join link instead. Do not retry it, and do not reword it as a generic failure. \n\nMany of these businesses serve Kiswahili-speaking callers. If the user writes to you in Kiswahili, answer in Kiswahili.",
            "tools": [
                {
                    "name": "get_meeting",
                    "root_name": "get_meeting",
                    "version": "0648f40e",
                    "description": "One meeting in full: its title, state, schedule, whether it records itself, who is currently in the room, and the link people use to join. Use it before inviting anybody, so the link you hand out belongs to the meeting you mean.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "calls.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_meeting arguments",
                        "type": "object",
                        "properties": {
                            "meeting_id": {
                                "type": "string",
                                "description": "The meeting to read, from list_meetings."
                            }
                        },
                        "required": [
                            "meeting_id"
                        ]
                    }
                },
                {
                    "name": "list_meetings",
                    "root_name": "list_meetings",
                    "version": "06aa87dd",
                    "description": "The meetings on this account: what they are called, when they are scheduled, and whether each one is still to come, live now, or finished. Filter by status to answer \"what is coming up\" without reading the whole history.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "calls.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_meetings arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "Only meetings in this state: scheduled, ready, live, ended or cancelled."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max meetings to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "cancel_meeting",
                    "root_name": "cancel_meeting",
                    "version": "d5447f24",
                    "description": "End a meeting. If it is live, everybody in the room is disconnected immediately; if it has not started, its link stops working. Nobody is told, so check with the user before calling this on a meeting other people are in.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "calls.participants.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "cancel_meeting arguments",
                        "type": "object",
                        "properties": {
                            "meeting_id": {
                                "type": "string",
                                "description": "The meeting to end, from list_meetings."
                            }
                        },
                        "required": [
                            "meeting_id"
                        ]
                    }
                },
                {
                    "name": "dial_out_to_meeting",
                    "root_name": "dial_out_to_meeting",
                    "version": "c01e161d",
                    "description": "Ring a phone and put the person into a meeting when they answer, so they join without a link. NOTE: the call platform has not shipped this yet and it will tell you so \u2014 that is a missing platform feature, not a bad number, and the honest answer is to send the join link instead.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "calls.place"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "dial_out_to_meeting arguments",
                        "type": "object",
                        "properties": {
                            "meeting_id": {
                                "type": "string",
                                "description": "The meeting to ring them into, from list_meetings."
                            },
                            "to": {
                                "type": "string",
                                "description": "The phone number to ring, in international format (+255...)."
                            },
                            "from_number": {
                                "type": "string",
                                "description": "Which of this account's own numbers to ring from \u2014 a meeting has no caller ID of its own."
                            },
                            "participant_name": {
                                "type": "string",
                                "description": "What to call them in the room."
                            },
                            "transport": {
                                "type": "string",
                                "description": "sip for a normal call, whatsapp for a WhatsApp voice call (the number must be WhatsApp-enabled)."
                            }
                        },
                        "required": [
                            "meeting_id",
                            "to",
                            "from_number"
                        ]
                    }
                },
                {
                    "name": "invite_to_meeting",
                    "root_name": "invite_to_meeting",
                    "version": "11149934",
                    "description": "Open a meeting for guests and hand back the link to invite them with. It marks a still-scheduled meeting ready so the link actually works, then returns it. It does NOT send anything to anybody \u2014 the user shares the link themselves.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "calls.participants.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "invite_to_meeting arguments",
                        "type": "object",
                        "properties": {
                            "meeting_id": {
                                "type": "string",
                                "description": "The meeting to open, from list_meetings."
                            },
                            "names": {
                                "type": "array",
                                "description": "Who the link is for, so the reply names them back. Only a reminder for the user \u2014 nobody is contacted.",
                                "items": {
                                    "type": "string"
                                }
                            }
                        },
                        "required": [
                            "meeting_id"
                        ]
                    }
                },
                {
                    "name": "schedule_meeting",
                    "root_name": "schedule_meeting",
                    "version": "ca2e0291",
                    "description": "Create a meeting \u2014 either starting now or booked for a moment in the future \u2014 and hand back the link people join with. Nobody is told about it: this creates the room, and sending the link to anyone is a separate, human step.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "calls.place"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "schedule_meeting arguments",
                        "type": "object",
                        "properties": {
                            "title": {
                                "type": "string",
                                "description": "What the meeting is called, as attendees will see it."
                            },
                            "description": {
                                "type": "string",
                                "description": "A sentence about what the meeting is for."
                            },
                            "scheduled_at": {
                                "type": "string",
                                "description": "When it starts, as an ISO 8601 time WITH a time zone (2026-09-10T14:30:00+03:00 or ...Z). Omit to start it now."
                            },
                            "auto_record": {
                                "type": "boolean",
                                "description": "Record the meeting from the moment it starts (default false)."
                            }
                        },
                        "required": [
                            "title"
                        ]
                    }
                }
            ]
        },
        {
            "key": "messaging",
            "name": "Messaging",
            "summary": "Templates, sender IDs, campaigns, message history \u2014 and sending SMS and WhatsApp.",
            "path": "/mcp/v1/messaging",
            "module": "marketing",
            "instructions": "Send and manage this business's SMS and WhatsApp: templates, sender IDs, campaigns, delivery history. \n\nComposing and sending are DIFFERENT tools on purpose. draft_message writes the message and checks everything that decides whether it would be delivered \u2014 it sends nothing, ever. send_sms, send_whatsapp and send_bulk_sms put a message on a real person's phone, cost money, and cannot be undone. Many connections are allowed the first and not the second; when a send is refused, say so plainly and hand the user the draft rather than looking for another route. \n\nThe rule that governs WhatsApp: outside the 24-hour customer-service window \u2014 24 hours since the customer last wrote \u2014 only an APPROVED template is delivered. Free text sent outside it is dropped by Meta silently, so it looks sent and never arrives. Check with draft_message, and reach for list_templates before promising anything. \n\nBefore any bulk send, tell the user how many people it reaches and what it costs; send_bulk_sms will not run until you pass that count back. \n\nMany of these customers write in Kiswahili. Write in the language they are using, and default to Kiswahili when the user is writing to you in it.",
            "tools": [
                {
                    "name": "draft_message",
                    "root_name": "draft_message",
                    "version": "c1215803",
                    "description": "Compose a message and check it against everything that decides whether it would actually be delivered \u2014 the 24-hour WhatsApp window, template approval, the sender ID, the do-not-contact list and the SMS segment cost. SENDS NOTHING: it hands back the finished text for a human to send.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "draft_message arguments",
                        "type": "object",
                        "properties": {
                            "channel": {
                                "type": "string",
                                "description": "Which channel the message is for. Default whatsapp.",
                                "enum": [
                                    "sms",
                                    "whatsapp"
                                ]
                            },
                            "to": {
                                "type": "string",
                                "description": "The recipient, so the window, the do-not-contact list and the thread can be checked. Optional \u2014 omit for a generic draft."
                            },
                            "body": {
                                "type": "string",
                                "description": "The message text. Ignored when a template is given, since the template body is what Meta sends."
                            },
                            "template": {
                                "type": "string",
                                "description": "An approved template name or id, from list_templates. Required outside the 24-hour window."
                            },
                            "variables": {
                                "type": "array",
                                "description": "Values for the template placeholders, in order: the first fills {{1}}.",
                                "items": {
                                    "type": "string"
                                }
                            },
                            "sender": {
                                "type": "string",
                                "description": "An SMS sender ID to send from. Checked for approval."
                            }
                        }
                    }
                },
                {
                    "name": "get_campaign",
                    "root_name": "get_campaign",
                    "version": "f7611799",
                    "description": "Read one campaign in full: its audience, message, schedule, recurrence, and the live delivery breakdown \u2014 how many were delivered, are still queued, and failed, with the commonest failure reason.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.campaigns.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_campaign arguments",
                        "type": "object",
                        "properties": {
                            "campaign": {
                                "type": "string",
                                "description": "The campaign uid or id, from list_campaigns."
                            }
                        },
                        "required": [
                            "campaign"
                        ]
                    }
                },
                {
                    "name": "get_message_history",
                    "root_name": "get_message_history",
                    "version": "42b91418",
                    "description": "What was sent and what happened to it: delivery states, failure reasons and billed segments across SMS and WhatsApp, filtered by direction, status, channel, contact or date. Use it to answer \"did my message arrive\" with the real status instead of a guess.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.reports.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_message_history arguments",
                        "type": "object",
                        "properties": {
                            "direction": {
                                "type": "string",
                                "description": "Only messages sent by the business, or only ones received.",
                                "enum": [
                                    "inbound",
                                    "outbound"
                                ]
                            },
                            "status": {
                                "type": "string",
                                "description": "queued, sent, delivered, read, failed or received."
                            },
                            "channel": {
                                "type": "string",
                                "description": "sms or whatsapp."
                            },
                            "contact": {
                                "type": "string",
                                "description": "A phone number in any format, or part of one \u2014 the last nine digits are matched."
                            },
                            "since": {
                                "type": "string",
                                "description": "Only messages on or after this ISO 8601 date."
                            },
                            "until": {
                                "type": "string",
                                "description": "Only messages on or before this ISO 8601 date."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max messages to return (default 25, max 100). The summary counts every match, not just these."
                            }
                        }
                    }
                },
                {
                    "name": "get_template",
                    "root_name": "get_template",
                    "version": "7829b667",
                    "description": "Read one message template in full: its body, header, footer, buttons, the variables it expects, its Meta approval state and \u2014 when it was rejected \u2014 why. Use it before sending so the variables you supply match the ones the template declares.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.templates.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_template arguments",
                        "type": "object",
                        "properties": {
                            "template": {
                                "type": "string",
                                "description": "The template name or id, from list_templates."
                            }
                        },
                        "required": [
                            "template"
                        ]
                    }
                },
                {
                    "name": "list_campaigns",
                    "root_name": "list_campaigns",
                    "version": "926b34cf",
                    "description": "List bulk messaging campaigns, newest first, with how many recipients each has reached and how many failed. Filter by status or channel. Shows the campaigns this person may see \u2014 a personal-scope member sees the ones they created.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.campaigns.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_campaigns arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "Filter by state: draft, scheduled, running, paused, completed or cancelled."
                            },
                            "channel": {
                                "type": "string",
                                "description": "Filter by channel: sms or whatsapp."
                            },
                            "search": {
                                "type": "string",
                                "description": "Filter by campaign name."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max campaigns to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_sender_ids",
                    "root_name": "list_sender_ids",
                    "version": "c3f017dc",
                    "description": "List the SMS sender IDs on this account with their per-country approval state. Only an APPROVED sender ID puts the business name on an SMS in that country; a pending one is not usable yet, and saying otherwise sends the user to chase a delivery that never happens.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.sender-ids.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_sender_ids arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "Filter by state: pending, approved or rejected."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max sender IDs to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_templates",
                    "root_name": "list_templates",
                    "version": "38bfd699",
                    "description": "List the message templates on this account with their Meta approval state and the variables each one takes. Read this before drafting or sending WhatsApp: outside the 24-hour window an APPROVED template is the only thing that gets delivered.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.templates.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_templates arguments",
                        "type": "object",
                        "properties": {
                            "channel": {
                                "type": "string",
                                "description": "Filter to templates usable on one channel: whatsapp or sms."
                            },
                            "approved_only": {
                                "type": "boolean",
                                "description": "Only WhatsApp templates Meta has approved \u2014 the ones that will actually deliver outside the 24-hour window."
                            },
                            "search": {
                                "type": "string",
                                "description": "Filter by name or body text."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max templates to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_whatsapp_senders",
                    "root_name": "list_whatsapp_senders",
                    "version": "c5ea703e",
                    "description": "The WhatsApp numbers this business can send from, with the name each shows to customers. Read this before send_whatsapp when the account has more than one \u2014 passing the wrong `from`, or omitting it and letting the default apply, sends from a number the customer may not recognise.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_whatsapp_senders arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "create_template",
                    "root_name": "create_template",
                    "version": "1e25b0f1",
                    "description": "Write a new message template and, for WhatsApp, submit it to Meta for review. Review takes minutes to a day and Meta may reject it \u2014 the template cannot be sent to anyone until it comes back approved, so tell the user that rather than implying it is ready.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.templates.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_template arguments",
                        "type": "object",
                        "properties": {
                            "display_name": {
                                "type": "string",
                                "description": "What a human calls this template, e.g. \"Order shipped\"."
                            },
                            "body": {
                                "type": "string",
                                "description": "The message text. Use {{1}}, {{2}} for the parts that change per recipient."
                            },
                            "channels": {
                                "type": "array",
                                "description": "Which channels it is for: [\"whatsapp\"], [\"sms\"], or both. Default whatsapp.",
                                "items": {
                                    "type": "string"
                                }
                            },
                            "category": {
                                "type": "string",
                                "description": "Meta's category. Utility for transactional notices, marketing for promotions.",
                                "enum": [
                                    "marketing",
                                    "utility",
                                    "authentication"
                                ]
                            },
                            "language": {
                                "type": "string",
                                "description": "Language code, e.g. en or sw. Default en."
                            },
                            "name": {
                                "type": "string",
                                "description": "The machine handle. Derived from display_name when omitted."
                            },
                            "header_text": {
                                "type": "string",
                                "description": "An optional one-line text header."
                            },
                            "footer": {
                                "type": "string",
                                "description": "An optional footer, max 60 characters."
                            },
                            "whatsapp_business_account_id": {
                                "type": "string",
                                "description": "Which WhatsApp business account to submit to. Required only when the account has more than one."
                            }
                        },
                        "required": [
                            "display_name",
                            "body"
                        ]
                    }
                },
                {
                    "name": "request_sender_id",
                    "root_name": "request_sender_id",
                    "version": "a0048540",
                    "description": "Open a request for an SMS sender ID in one country \u2014 the business name that shows as the sender. This only files the request: an administrator reviews it against the operator rules, and nothing can be sent from the name until it is approved.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.sender-ids.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "request_sender_id arguments",
                        "type": "object",
                        "properties": {
                            "sender_id": {
                                "type": "string",
                                "description": "The sender name, 3-11 characters, letters digits and spaces only."
                            },
                            "country": {
                                "type": "string",
                                "description": "Where it will be used: ISO code (TZ) or country name."
                            },
                            "notes": {
                                "type": "string",
                                "description": "Anything the reviewer should know \u2014 what the business is, what these messages are for."
                            }
                        },
                        "required": [
                            "sender_id",
                            "country"
                        ]
                    }
                },
                {
                    "name": "send_bulk_sms",
                    "root_name": "send_bulk_sms",
                    "version": "8b3d1d59",
                    "description": "Send one SMS to many real phones at once. It will not send until you pass confirm_recipient_count matching the number of recipients exactly \u2014 a bulk send is irreversible, costs one message per segment per person, and a mistyped list is the expensive kind of mistake.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.campaigns.manage",
                        "communications.send"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "send_bulk_sms arguments",
                        "type": "object",
                        "properties": {
                            "to": {
                                "type": "array",
                                "description": "The recipient numbers in international format. A comma-separated string is also accepted.",
                                "items": {
                                    "type": "string"
                                }
                            },
                            "body": {
                                "type": "string",
                                "description": "The message text, sent identically to everyone."
                            },
                            "confirm_recipient_count": {
                                "type": "integer",
                                "description": "How many recipients you are sending to. Must equal the list length exactly, or nothing is sent."
                            },
                            "sender": {
                                "type": "string",
                                "description": "An approved sender ID to send from. The account default is used when omitted."
                            },
                            "interval_seconds": {
                                "type": "integer",
                                "description": "Seconds between each send, 0-10. Default 1, which keeps gateways happy."
                            }
                        },
                        "required": [
                            "to",
                            "body",
                            "confirm_recipient_count"
                        ]
                    }
                },
                {
                    "name": "send_sms",
                    "root_name": "send_sms",
                    "version": "f38275ee",
                    "description": "Send one SMS to one real phone. This is irreversible and it costs money from the account wallet \u2014 an SMS over 160 characters is billed as several. Use draft_message first if the user has not approved the exact wording.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.send"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "send_sms arguments",
                        "type": "object",
                        "properties": {
                            "to": {
                                "type": "string",
                                "description": "The recipient phone number in international format, e.g. +255755123456."
                            },
                            "body": {
                                "type": "string",
                                "description": "The message text. Required unless a template is given."
                            },
                            "template": {
                                "type": "string",
                                "description": "An active SMS template name or id to send instead of free text."
                            },
                            "variables": {
                                "type": "array",
                                "description": "Values for the template placeholders, in order.",
                                "items": {
                                    "type": "string"
                                }
                            },
                            "sender": {
                                "type": "string",
                                "description": "An approved sender ID to send from. The account default is used when omitted."
                            }
                        },
                        "required": [
                            "to"
                        ]
                    }
                },
                {
                    "name": "send_whatsapp",
                    "root_name": "send_whatsapp",
                    "version": "e26b8e53",
                    "description": "Send a WhatsApp message to one real person: free text inside the 24-hour customer-service window, or an approved template at any time. Outside that window free text is DROPPED by Meta and never arrives, so this refuses it rather than reporting a send that did not happen.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.send"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "send_whatsapp arguments",
                        "type": "object",
                        "properties": {
                            "to": {
                                "type": "string",
                                "description": "The recipient WhatsApp number in international format, e.g. +255755123456."
                            },
                            "body": {
                                "type": "string",
                                "description": "The message text. Only delivered inside the 24-hour window; outside it, use a template."
                            },
                            "template": {
                                "type": "string",
                                "description": "An APPROVED WhatsApp template name or id. The only thing that delivers outside the 24-hour window."
                            },
                            "variables": {
                                "type": "array",
                                "description": "Values for the template placeholders, in order: the first fills {{1}}.",
                                "items": {
                                    "type": "string"
                                }
                            },
                            "from": {
                                "type": "string",
                                "description": "Which of the account's WhatsApp numbers to send from, from list_whatsapp_senders. When the account has more than one, ask which rather than letting the default apply."
                            }
                        },
                        "required": [
                            "to"
                        ]
                    }
                }
            ]
        },
        {
            "key": "inbox",
            "name": "Inbox",
            "summary": "Customer conversations across WhatsApp, SMS and social \u2014 read, assign, reply.",
            "path": "/mcp/v1/inbox",
            "module": "inbox",
            "instructions": "The business's customer conversations across WhatsApp, SMS, Instagram, Messenger, TikTok and email. \n\nThese are private messages from real people who never agreed to an AI reading them. Read what the question needs and no more, quote sparingly, and do not summarise somebody's whole history because it was available. \n\nYou see only the threads this person may see: someone with personal scope sees the conversations assigned to them and the accounts they were given. An empty list is not proof the inbox is empty \u2014 say that rather than telling the user there are no messages. \n\nWork in this order: list_conversations, get_conversation to read one, then reply_to_conversation. Replying reaches a real person and cannot be unsent, and on WhatsApp outside the 24-hour window only an approved template is delivered \u2014 get_conversation tells you which side of that window a thread is on. \n\nassign_conversation hands a thread to a colleague; set_conversation_ai_mode is how a person takes a chat back from the AI, or gives it to one. \n\nMany of these customers write in Kiswahili. Reply in the language they used.",
            "tools": [
                {
                    "name": "get_conversation",
                    "root_name": "get_conversation",
                    "version": "ec38ed35",
                    "description": "Read one customer conversation: the recent messages in order, who owns it, whether the AI is answering it, and whether a free-text reply would still be delivered. Read this before replying so the answer fits what was already said.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.inbox.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_conversation arguments",
                        "type": "object",
                        "properties": {
                            "conversation_id": {
                                "type": "integer",
                                "description": "The conversation id, from list_conversations."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many recent messages to return (default 30, max 100)."
                            }
                        },
                        "required": [
                            "conversation_id"
                        ]
                    }
                },
                {
                    "name": "list_conversations",
                    "root_name": "list_conversations",
                    "version": "0d1e43e5",
                    "description": "List customer conversations across WhatsApp, SMS, Instagram, Messenger, TikTok and email \u2014 newest activity first, with who is waiting, who owns the thread and whether the reply window is still open. Shows only the threads this person may see.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.inbox.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_conversations arguments",
                        "type": "object",
                        "properties": {
                            "channel": {
                                "type": "string",
                                "description": "whatsapp, sms, instagram, messenger, tiktok or email."
                            },
                            "status": {
                                "type": "string",
                                "description": "active, archived or closed."
                            },
                            "unread_only": {
                                "type": "boolean",
                                "description": "Only threads with unread customer messages."
                            },
                            "assigned_to_me": {
                                "type": "boolean",
                                "description": "Only threads assigned to the person this connection acts for."
                            },
                            "search": {
                                "type": "string",
                                "description": "Match a contact name, number, username or the last message."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max conversations to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "search_messages",
                    "root_name": "search_messages",
                    "version": "01eb2aab",
                    "description": "Search the words inside customer conversations \u2014 \"refund\", an order number, a place name \u2014 and get the matching messages with the thread each belongs to. Searches only the conversations this person may open, so it cannot be used to read somebody else's inbox.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.inbox.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "search_messages arguments",
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "The words to look for inside message bodies."
                            },
                            "channel": {
                                "type": "string",
                                "description": "Restrict to one channel: whatsapp, sms, instagram, messenger, tiktok or email."
                            },
                            "direction": {
                                "type": "string",
                                "description": "Only what the customer wrote, or only what the business replied.",
                                "enum": [
                                    "inbound",
                                    "outbound"
                                ]
                            },
                            "since": {
                                "type": "string",
                                "description": "Only messages on or after this ISO 8601 date."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max matches to return (default 25, max 100)."
                            }
                        },
                        "required": [
                            "query"
                        ]
                    }
                },
                {
                    "name": "assign_conversation",
                    "root_name": "assign_conversation",
                    "version": "eddb12de",
                    "description": "Hand a customer conversation to a colleague or a team, or take the owner off it. The change shows immediately in everyone's inbox and holds against the routing rules for a few hours, because a person's choice should outrank a rule.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.inbox.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "assign_conversation arguments",
                        "type": "object",
                        "properties": {
                            "conversation_id": {
                                "type": "integer",
                                "description": "The conversation to hand over."
                            },
                            "assign_to_id": {
                                "type": "integer",
                                "description": "The team member id to give it to."
                            },
                            "assign_to_team_id": {
                                "type": "integer",
                                "description": "A team id, when the whole team should pick it up rather than one person."
                            },
                            "unassign": {
                                "type": "boolean",
                                "description": "Take the current owner off it and leave it unowned."
                            }
                        },
                        "required": [
                            "conversation_id"
                        ]
                    }
                },
                {
                    "name": "reply_to_conversation",
                    "root_name": "reply_to_conversation",
                    "version": "68d82a23",
                    "description": "Reply to a customer in an existing conversation. The message reaches a real person on their phone and cannot be unsent. On WhatsApp outside the 24-hour window only an approved template is delivered, so this refuses free text there instead of reporting a send that never lands.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.send"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "reply_to_conversation arguments",
                        "type": "object",
                        "properties": {
                            "conversation_id": {
                                "type": "integer",
                                "description": "The conversation to reply in, from list_conversations."
                            },
                            "body": {
                                "type": "string",
                                "description": "What to say. Write in the language the customer is using."
                            },
                            "template": {
                                "type": "string",
                                "description": "An approved template name or id \u2014 required on WhatsApp once the 24-hour window has closed."
                            },
                            "variables": {
                                "type": "array",
                                "description": "Values for the template placeholders, in order.",
                                "items": {
                                    "type": "string"
                                }
                            }
                        },
                        "required": [
                            "conversation_id"
                        ]
                    }
                },
                {
                    "name": "set_conversation_ai_mode",
                    "root_name": "set_conversation_ai_mode",
                    "version": "42861bc8",
                    "description": "Decide who answers one conversation: the AI agent, a chat flow, nobody automatic, or whatever the channel normally does. Switching to off is how a person takes a chat back from the AI mid-conversation; it changes live behaviour on the next customer message.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "communications.engine.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "set_conversation_ai_mode arguments",
                        "type": "object",
                        "properties": {
                            "conversation_id": {
                                "type": "integer",
                                "description": "The conversation to change."
                            },
                            "mode": {
                                "type": "string",
                                "description": "on = the AI answers it; off = nobody automatic does, a person must; flow = a chat flow drives it; inherit = the channel default.",
                                "enum": [
                                    "on",
                                    "off",
                                    "flow",
                                    "inherit"
                                ]
                            },
                            "flow_id": {
                                "type": "integer",
                                "description": "Which published flow to pin, when mode is flow. Without one the flow is chosen by trigger as usual."
                            }
                        },
                        "required": [
                            "conversation_id",
                            "mode"
                        ]
                    }
                }
            ]
        },
        {
            "key": "comments",
            "name": "Comments",
            "summary": "Comments on your Facebook, Instagram and TikTok posts.",
            "path": "/mcp/v1/comments",
            "module": "comments",
            "instructions": "Public comments on this business's Facebook, Instagram and TikTok posts. \n\nEverything here is public, in both directions: a reply is posted in the business's name where anyone can read it, screenshot it and quote it back. Show the user the exact wording before you post, and never post an apology, a price, a refund or a promise the business has not agreed to. \n\nWork in this order: list_comments (it defaults to the ones still needing an answer), get_comment_thread to read what has already been said under that post, then reply_to_comment. TikTok replies are limited to 150 characters \u2014 far shorter than the others \u2014 and get_comment_thread names what each platform actually allows. \n\nhide_comment takes a comment out of public view without deleting it and without telling the author; it is the calm option for something abusive. Reversible with unhide_comment. \n\nYou see only the social accounts this person is assigned to, so an empty queue is not proof nobody commented. Many commenters write in Kiswahili \u2014 answer in the language they used.",
            "tools": [
                {
                    "name": "get_comment_thread",
                    "root_name": "get_comment_thread",
                    "version": "baf5728a",
                    "description": "Read the whole comment conversation under one post: what was posted, every comment in order including the business's own replies, and what this platform actually allows you to do to a comment. Read it before replying, so you do not answer a point somebody already answered.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "comments.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_comment_thread arguments",
                        "type": "object",
                        "properties": {
                            "comment_id": {
                                "type": "integer",
                                "description": "Any comment on the post, from list_comments. The whole thread comes back around it."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max comments in the thread to return (default 50, max 100)."
                            }
                        },
                        "required": [
                            "comment_id"
                        ]
                    }
                },
                {
                    "name": "list_comments",
                    "root_name": "list_comments",
                    "version": "8e87c9c1",
                    "description": "List public comments on the business's Facebook, Instagram and TikTok posts \u2014 newest first, defaulting to the ones still needing an answer. Filter by platform, post, label or free text. Shows only the accounts this person is assigned to.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "comments.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_comments arguments",
                        "type": "object",
                        "properties": {
                            "view": {
                                "type": "string",
                                "description": "attention (default) = still needs an answer; handoff = what the AI could not answer and is waiting on a person for.",
                                "enum": [
                                    "attention",
                                    "all",
                                    "hidden",
                                    "resolved",
                                    "mine",
                                    "handoff"
                                ]
                            },
                            "platform": {
                                "type": "string",
                                "description": "Only comments from one platform.",
                                "enum": [
                                    "facebook",
                                    "instagram",
                                    "tiktok"
                                ]
                            },
                            "post_id": {
                                "type": "integer",
                                "description": "Only comments on one post."
                            },
                            "label": {
                                "type": "string",
                                "description": "Only comments carrying this label."
                            },
                            "search": {
                                "type": "string",
                                "description": "Match the comment text or the author."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max comments to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "assign_comment",
                    "root_name": "assign_comment",
                    "version": "21d1dead",
                    "description": "Give a comment to a colleague to answer, or take the owner off it. Nothing is posted publicly \u2014 this only decides whose queue it lands in, and a person's choice outranks the routing rules for a few hours afterwards.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "comments.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "assign_comment arguments",
                        "type": "object",
                        "properties": {
                            "comment_id": {
                                "type": "integer",
                                "description": "The comment to hand over."
                            },
                            "assign_to_id": {
                                "type": "integer",
                                "description": "The team member id to give it to."
                            },
                            "unassign": {
                                "type": "boolean",
                                "description": "Take the current owner off it and leave it unowned."
                            }
                        },
                        "required": [
                            "comment_id"
                        ]
                    }
                },
                {
                    "name": "hide_comment",
                    "root_name": "hide_comment",
                    "version": "b51cae1d",
                    "description": "Hide a comment so the public can no longer see it under the post. It is not deleted and the author is not told \u2014 they still see their own comment, which is what makes hiding the calm option. unhide_comment puts it back.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "comments.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "hide_comment arguments",
                        "type": "object",
                        "properties": {
                            "comment_id": {
                                "type": "integer",
                                "description": "The comment to hide, from list_comments."
                            }
                        },
                        "required": [
                            "comment_id"
                        ]
                    }
                },
                {
                    "name": "reply_to_comment",
                    "root_name": "reply_to_comment",
                    "version": "52fbb635",
                    "description": "Reply publicly to a comment on the business's Facebook, Instagram or TikTok post. Everyone can read this reply, it is posted in the business's own name, and it cannot be quietly unsent \u2014 show the user the exact wording before calling this.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "comments.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "reply_to_comment arguments",
                        "type": "object",
                        "properties": {
                            "comment_id": {
                                "type": "integer",
                                "description": "The comment to answer, from list_comments."
                            },
                            "message": {
                                "type": "string",
                                "description": "The public reply. Match the language the commenter used."
                            }
                        },
                        "required": [
                            "comment_id",
                            "message"
                        ]
                    }
                },
                {
                    "name": "unhide_comment",
                    "root_name": "unhide_comment",
                    "version": "ea372744",
                    "description": "Put a hidden comment back in public view under the post. Use it when a comment was hidden by mistake, or once the thing it complained about has been sorted out and the answer belongs where everyone can read it.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "comments.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "unhide_comment arguments",
                        "type": "object",
                        "properties": {
                            "comment_id": {
                                "type": "integer",
                                "description": "The hidden comment to restore."
                            }
                        },
                        "required": [
                            "comment_id"
                        ]
                    }
                }
            ]
        },
        {
            "key": "contacts",
            "name": "Contacts",
            "summary": "The contact book and groups.",
            "path": "/mcp/v1/contacts",
            "module": "contacts",
            "instructions": "The people this business talks to: the contact book, and the groups it is organised into. \n\nEvery contact belongs to exactly ONE group. So list_contact_groups comes first \u2014 create_contact and import_contacts both need a group id, and add_to_group MOVES a contact rather than copying it. Say \"moved\" to the user, never \"added to\", or they will expect the person to be in both. \n\nsearch_contacts handles a phone number written any way a person writes one \u2014 0712 345 678, +255712345678, 255712345678 all find the same contact. Prefer it over listing and scanning. \n\nNothing here sends anything. Adding a contact does not message them; unsubscribing one stops campaigns reaching them. Sending lives on the messaging server and needs its own permission. \n\nimport_contacts asks you to state the row count and refuses if it disagrees with the list. That is on purpose \u2014 read the count back to the user before you call it.",
            "tools": [
                {
                    "name": "get_contact",
                    "root_name": "get_contact",
                    "version": "abce7916",
                    "description": "One contact in full: name, the full phone number to dial or message, the group they belong to, whether they are subscribed, and every custom field their group defines. Takes either the numeric id or the ctc_ reference.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "contacts.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_contact arguments",
                        "type": "object",
                        "properties": {
                            "contact_id": {
                                "type": "string",
                                "description": "The contact id, or its ctc_ reference."
                            }
                        },
                        "required": [
                            "contact_id"
                        ]
                    }
                },
                {
                    "name": "list_contact_groups",
                    "root_name": "list_contact_groups",
                    "version": "4d49b7f6",
                    "description": "The contact groups this account keeps: name, how many contacts are in each, how many still accept messages, and the custom fields each group defines. Read this before creating or importing contacts \u2014 every contact belongs to a group.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "contacts.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_contact_groups arguments",
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Max groups to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_contacts",
                    "root_name": "list_contacts",
                    "version": "545d7238",
                    "description": "The contact book on this account: name, phone number, which group each one is in and whether they still accept messages. Narrow it to one group with group_id, or use search_contacts when you have a name or a number.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "contacts.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_contacts arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "integer",
                                "description": "Only contacts in this group. list_contact_groups gives the ids."
                            },
                            "subscribed_only": {
                                "type": "boolean",
                                "description": "Only contacts who still accept messages."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max contacts to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "search_contacts",
                    "root_name": "search_contacts",
                    "version": "3f1497f0",
                    "description": "Find a contact by name or by phone number, across every group on the account. Handles a number written any way \u2014 with or without the country code, with spaces or a leading zero \u2014 so \"who is 0712 345 678\" resolves to a person.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "contacts.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "search_contacts arguments",
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "A name, part of a name, or a phone number in any format."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max matches to return (default 25, max 100)."
                            }
                        },
                        "required": [
                            "query"
                        ]
                    }
                },
                {
                    "name": "add_to_group",
                    "root_name": "add_to_group",
                    "version": "3ef14d5c",
                    "description": "Put an existing contact in a different group. A contact belongs to exactly one group on this platform, so this MOVES them \u2014 they leave the group they are in now, and any campaign aimed at the old group stops including them.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "contacts.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "add_to_group arguments",
                        "type": "object",
                        "properties": {
                            "contact_id": {
                                "type": "string",
                                "description": "The contact id, or its ctc_ reference."
                            },
                            "group_id": {
                                "type": "integer",
                                "description": "The group to move them into."
                            }
                        },
                        "required": [
                            "contact_id",
                            "group_id"
                        ]
                    }
                },
                {
                    "name": "create_contact",
                    "root_name": "create_contact",
                    "version": "93d7601c",
                    "description": "Add one contact to a group: a name and a phone number, plus any custom fields that group defines. Refuses a number the group already holds rather than creating a second copy of the same person.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "contacts.create"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "create_contact arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "integer",
                                "description": "Which group to add them to. list_contact_groups gives the ids."
                            },
                            "name": {
                                "type": "string",
                                "description": "The person's name."
                            },
                            "phone": {
                                "type": "string",
                                "description": "Their phone number. Full international form is safest, e.g. +255712345678."
                            },
                            "country_code": {
                                "type": "string",
                                "description": "Optional country code when the number is written nationally, e.g. \"255\"."
                            },
                            "subscribed": {
                                "type": "boolean",
                                "description": "Whether they accept messages. Default true."
                            },
                            "custom_fields": {
                                "type": "object",
                                "description": "Values for the custom fields this group defines, keyed by field key."
                            }
                        },
                        "required": [
                            "group_id",
                            "name",
                            "phone"
                        ]
                    }
                },
                {
                    "name": "import_contacts",
                    "root_name": "import_contacts",
                    "version": "aa0abaa3",
                    "description": "Add many contacts to one group in a single call. Bounded and deliberately awkward: you must state how many rows you are importing and the count must match, because an import that quietly adds the wrong number of people is discovered weeks later on a bill.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "contacts.import"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "import_contacts arguments",
                        "type": "object",
                        "properties": {
                            "group_id": {
                                "type": "integer",
                                "description": "The group to import into. list_contact_groups gives the ids."
                            },
                            "rows_json": {
                                "type": "string",
                                "description": "A JSON array of {\"name\":\"\u2026\",\"phone\":\"\u2026\",\"country_code\":\"\u2026\",\"subscribed\":true,\"custom_fields\":{\u2026}} objects. At most 500."
                            },
                            "confirm_count": {
                                "type": "integer",
                                "description": "How many contacts you are importing. Must equal the number of rows, or nothing is imported."
                            }
                        },
                        "required": [
                            "group_id",
                            "rows_json",
                            "confirm_count"
                        ]
                    }
                },
                {
                    "name": "update_contact",
                    "root_name": "update_contact",
                    "version": "a403030f",
                    "description": "Change a contact: their name, their phone number, their custom fields, or whether they still accept messages. Only the fields you pass change. Unsubscribing here stops campaigns reaching them.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "contacts.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "update_contact arguments",
                        "type": "object",
                        "properties": {
                            "contact_id": {
                                "type": "string",
                                "description": "The contact id, or its ctc_ reference."
                            },
                            "name": {
                                "type": "string",
                                "description": "New name."
                            },
                            "phone": {
                                "type": "string",
                                "description": "New phone number."
                            },
                            "country_code": {
                                "type": "string",
                                "description": "Country code, when the new number is written nationally."
                            },
                            "subscribed": {
                                "type": "boolean",
                                "description": "Whether they accept messages. False stops campaigns reaching them."
                            },
                            "custom_fields": {
                                "type": "object",
                                "description": "Custom field values to set, keyed by field key. Merged with what is already there."
                            }
                        },
                        "required": [
                            "contact_id"
                        ]
                    }
                }
            ]
        },
        {
            "key": "overview",
            "name": "Overview",
            "summary": "The dashboard, business analytics, call stats and spend \u2014 how the business is doing.",
            "path": "/mcp/v1/overview",
            "module": null,
            "instructions": "How this business is doing: calls, messages, money, and what needs attention. \n\nStart with get_dashboard. It answers \"how are we doing\" in one call and returns an `attention` list \u2014 things that are actually wrong, each with the page that fixes it. Lead with that list rather than reciting the numbers: a list of numbers is a report, a list of what is wrong is management. \n\nIf a section is missing from a reply, look at `not_visible_to_this_connection`. It means this connection may not see it \u2014 NOT that the number is zero. Never report an omitted section as zero, and never guess at it; say the connection cannot see it and that the account owner can grant it. \n\nThen go deeper: get_call_stats for volumes over a period, get_spend_summary for where the money went, get_live_calls for what is happening this second, get_business_analytics for what the AI heard on recorded calls. \n\nget_business_analytics READS finished analyses. It cannot start one, on purpose \u2014 analysis runs on a schedule and a manual run would disturb it. If there is nothing to read, say a person has to switch AI metrics on for that number at /app/calls/business-analytics.",
            "tools": [
                {
                    "name": "get_business_analytics",
                    "root_name": "get_business_analytics",
                    "version": "15b81d8c",
                    "description": "The results of the AI analysis of recorded calls over a period: satisfaction, complaints, churn-risk flags, resolution and escalation rates, and the caller journey funnel. Reads finished analyses only \u2014 it never starts a new analysis run.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "calls.business-analytics.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_business_analytics arguments",
                        "type": "object",
                        "properties": {
                            "range": {
                                "type": "string",
                                "description": "Period to read: 24h, 7d (default) or 30d."
                            },
                            "number": {
                                "type": "string",
                                "description": "Optional. One phone number in E.164, to read that line only."
                            }
                        }
                    }
                },
                {
                    "name": "get_call_stats",
                    "root_name": "get_call_stats",
                    "version": "1691744e",
                    "description": "Call volume over a period, split by direction and by what happened: answered, missed, still live, total talk time and the daily shape. Use this when someone asks whether calls are up, or when the busy days are.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "dashboard.stats.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_call_stats arguments",
                        "type": "object",
                        "properties": {
                            "days": {
                                "type": "integer",
                                "description": "How many days back to count, ending today. Default 7, max 60."
                            }
                        }
                    }
                },
                {
                    "name": "get_dashboard",
                    "root_name": "get_dashboard",
                    "version": "bfd76a36",
                    "description": "How the business is doing right now, in one call: calls today, message delivery, wallet, who is on duty, and an \"attention\" list of things that are actually wrong with the page that fixes each one. Start here before any other overview tool.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "dashboard.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_dashboard arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "get_live_calls",
                    "root_name": "get_live_calls",
                    "version": "c2e4bde7",
                    "description": "What is happening on the phones this second: the calls currently ringing or connected, who is on each one, how long it has been running. Answers \"is anybody waiting right now\".",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "dashboard.live-calls.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_live_calls arguments",
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Max live calls to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "get_spend_summary",
                    "root_name": "get_spend_summary",
                    "version": "4d660775",
                    "description": "Where the money went over a period: total spent, broken down by category (calls, messages, AI usage, numbers), what was topped up, and the balance left. Answers \"why is my balance down\".",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "billing.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_spend_summary arguments",
                        "type": "object",
                        "properties": {
                            "days": {
                                "type": "integer",
                                "description": "How many days back to total, ending today. Default 30, max 92."
                            }
                        }
                    }
                }
            ]
        },
        {
            "key": "accounts",
            "name": "Connected accounts",
            "summary": "The WhatsApp numbers, social profiles, mailboxes and SMS routes this business has connected, and what each can actually do.",
            "path": "/mcp/v1/accounts",
            "module": null,
            "instructions": "The accounts this business has connected: WhatsApp numbers, Facebook pages, Instagram and TikTok profiles, LinkedIn, YouTube, mailboxes and SMS routes.\n\nRead this before sending, posting or replying when the account has more than one of a kind, and name the account you used rather than letting a default apply silently.\n\nAn account being listed does NOT mean it works. `health` and the capability lists are the real answer: a connection can be present and still unable to send because a scope was never granted or a token expired. Report that plainly instead of promising something that will not happen.\n\nYou cannot connect or disconnect anything here. A person does that at Settings \u2192 Accounts.",
            "tools": [
                {
                    "name": "list_connected_accounts",
                    "root_name": "list_connected_accounts",
                    "version": "1378108b",
                    "description": "Every account connected to this business \u2014 WhatsApp numbers, Facebook pages, Instagram and TikTok profiles, LinkedIn, YouTube, email and SMS \u2014 with what each can actually do right now and whether it needs attention. Start here when asked what accounts exist, or to pick which one to send, post or reply from.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "communications.accounts.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_connected_accounts arguments",
                        "type": "object",
                        "properties": {
                            "kind": {
                                "type": "string",
                                "description": "Only one kind: whatsapp, facebook, instagram, tiktok, linkedin, youtube, email or sms."
                            },
                            "needs_attention": {
                                "type": "boolean",
                                "description": "Only accounts that are not healthy \u2014 expired tokens, missing permissions, disconnections."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max accounts to return (default 25, max 100)."
                            }
                        }
                    }
                }
            ]
        },
        {
            "key": "navigate",
            "name": "Finding things",
            "summary": "Where pages and settings live in the app, and what each form asks for.",
            "path": "/mcp/v1/navigate",
            "module": null,
            "instructions": "Where things live in this app. Use it whenever someone asks where to do something, or before you tell anyone to go anywhere \u2014 guessing a URL and being wrong wastes more of their time than asking here does. \n\nfind_page is the usual first call: ask it in the words the user used (\"where do I change my greeting\") and it answers with the path and what that page is for. list_pages returns the whole map when you need to learn the shape of the app or the name a feature actually has here. \n\nFor forms: list_actions says what each one asks for and how serious saving it is \u2014 navigate (nothing is written), reversible (undoable from the same page), critical (it spends money or reaches a customer). describe_action gives you one form field by field so you can walk someone through it. \n\nTwo rules. Only pages this connection can actually open are listed, so never send anyone to a path that did not come back from here \u2014 they will be turned away at it. And nothing on this server submits anything: it describes what the person will see, and they press the button.",
            "tools": [
                {
                    "name": "describe_action",
                    "root_name": "describe_action",
                    "version": "a736d516",
                    "description": "One form in full: the page it is on, every field with what it actually asks for in plain words, what pressing save does, and whether it costs money or reaches a customer. Call it before walking someone through a form so you ask for the right things in the right order.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "describe_action arguments",
                        "type": "object",
                        "properties": {
                            "action_id": {
                                "type": "string",
                                "description": "The id from list_actions \u2014 e.g. billing.topup, contacts.group.create."
                            }
                        },
                        "required": [
                            "action_id"
                        ]
                    }
                },
                {
                    "name": "find_page",
                    "root_name": "find_page",
                    "version": "89fb7b08",
                    "description": "Answer \"where do I change X\" with a real path. Ask it in the words the user used \u2014 \"where do I top up\", \"change what callers hear first\", \"reply templates\" \u2014 and it returns the best pages with what each is for, filtered to what this connection can actually open.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "find_page arguments",
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "What the user is trying to do, in their words. \"where do I change my greeting\", \"top up\", \"delivery reports\"."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "How many pages to return. Default 5, max 20."
                            }
                        },
                        "required": [
                            "query"
                        ]
                    }
                },
                {
                    "name": "list_actions",
                    "root_name": "list_actions",
                    "version": "1c81ecfc",
                    "description": "The forms in the app this connection could walk someone through: what each one is called, which page it is on, the fields it asks for, and how serious pressing save is (navigate, reversible, or critical). Use it to prepare someone before they open the page, not to submit anything.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_actions arguments",
                        "type": "object",
                        "properties": {
                            "page": {
                                "type": "string",
                                "description": "Only forms on this page, by path \u2014 e.g. /app/billing."
                            },
                            "commit": {
                                "type": "string",
                                "description": "Only forms of this seriousness: navigate, reversible, or critical. Default: all of them."
                            }
                        }
                    }
                },
                {
                    "name": "list_pages",
                    "root_name": "list_pages",
                    "version": "b7b57d51",
                    "description": "The map of the app: every page this connection can actually open, with its path, its name in the sidebar, and what it is for. Read it once to learn where things live, then say \"Settings \u2192 Integrations \u2192 API keys\" instead of guessing a URL. Narrow it with section to keep the answer small.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_pages arguments",
                        "type": "object",
                        "properties": {
                            "section": {
                                "type": "string",
                                "description": "Only pages in this part of the app \u2014 Calls, Messaging, Marketplace, Settings, and so on. The full list of section names comes back with every answer."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "At most this many pages. Default and maximum 200."
                            }
                        }
                    }
                }
            ]
        },
        {
            "key": "account",
            "name": "Account",
            "summary": "A cross-domain starting point: overview, search, fetch, and the most-used read tools.",
            "path": "/mcp/v1/account",
            "module": null,
            "instructions": "This is a Tanzanian business communications platform: phone lines answered by call flows (IVRs), WhatsApp conversations driven by message flows, AI agents, audio, orders and support tickets. \n\nStart with get_account_overview to see what this account actually has, then search to find things by name and fetch to read one. \n\nThis server carries the common tools. For sustained work in one area there are fuller servers \u2014 /mcp/v1/ivr, /mcp/v1/flows, /mcp/v1/studio, /mcp/v1/numbers \u2014 and the user can connect those too. \n\nEverything you build is a DRAFT. You cannot publish a flow to real callers or customers, and you cannot spend the account's money: a person does both. Say so rather than implying otherwise. \n\nMany of these businesses work in Kiswahili. Match the language the user writes to you in, and write customer-facing text in the language their customers actually speak.",
            "tools": [
                {
                    "name": "fetch",
                    "root_name": "fetch",
                    "version": "8705c421",
                    "description": "Read one record in full, using an id returned by search (e.g. \"ivr:12\", \"flow:4\", \"asset:41\", \"ticket:9\").",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "fetch arguments",
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string",
                                "description": "An id from search, e.g. \"ivr:12\"."
                            }
                        },
                        "required": [
                            "id"
                        ]
                    }
                },
                {
                    "name": "get_account_overview",
                    "root_name": "get_account_overview",
                    "version": "5f2016cd",
                    "description": "A one-call picture of this account: what it is called, how many call flows and message flows it has, how many are live, how many phone numbers, how much audio. Good opening move when you do not yet know what you are working with.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_account_overview arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "get_ivr_catalog",
                    "root_name": "get_ivr_catalog",
                    "version": "ff05c216",
                    "description": "The IVR building reference: every node kind you may use, the exact fields each one allows, which action dialect it speaks, and the resource ids that actually exist on this account (agents, models, voices, SMS senders, audio assets). ALWAYS call this before your first apply_ivr_ops \u2014 inventing a field or an id is the most common way a batch is rejected.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_ivr_catalog arguments",
                        "type": "object",
                        "properties": []
                    }
                },
                {
                    "name": "get_ivr_flow",
                    "root_name": "get_ivr_flow",
                    "version": "ab01f7b8",
                    "description": "Read one call flow in full: every node, the entry point, the current version number, and what the IVR engine validator says about it right now. Read this before proposing edits, and pass the version back to apply_ivr_ops.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_ivr_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow id, from list_ivr_flows."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "get_message_flow",
                    "root_name": "get_message_flow",
                    "version": "30472a8c",
                    "description": "Read one message flow in full: nodes, edges, triggers, its version number, and what the validator currently says. Pass the version back to apply_flow_ops so you do not overwrite somebody else.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "get_message_flow arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow id, from list_message_flows."
                            }
                        },
                        "required": [
                            "flow_id"
                        ]
                    }
                },
                {
                    "name": "list_agents",
                    "root_name": "list_agents",
                    "version": "6b127ddb",
                    "description": "The AI specialists this business has set up \u2014 what each one is for and whether it is available. Ask one a question with ask_agent when it knows something you do not.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "agents.ai.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_agents arguments",
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "list_assets",
                    "root_name": "list_assets",
                    "version": "55ed7b79",
                    "description": "List the audio already in this account's Asset Studio. Check here before generating \u2014 the clip you need may exist.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "asset-studio.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_assets arguments",
                        "type": "object",
                        "properties": {
                            "search": {
                                "type": "string",
                                "description": "Filter by name."
                            },
                            "status": {
                                "type": "string",
                                "description": "ready, processing or failed."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "list_ivr_flows",
                    "root_name": "list_ivr_flows",
                    "version": "d1ab45b8",
                    "description": "List the call (IVR) flows on this account: name, status, size, whether it has unpublished changes, and when it last changed. Start here before editing anything.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "ivr.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_ivr_flows arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "Filter by status: draft, active or paused."
                            },
                            "search": {
                                "type": "string",
                                "description": "Filter by name."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Max flows to return (default 25, max 100)."
                            }
                        }
                    }
                },
                {
                    "name": "list_message_flows",
                    "root_name": "list_message_flows",
                    "version": "91dff075",
                    "description": "List the WhatsApp conversation flows on this account, with status, priority, how many triggers each has and whether it is actually live for customers.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "flows.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_message_flows arguments",
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "description": "draft, active, paused or archived."
                            },
                            "search": {
                                "type": "string",
                                "description": "Filter by name."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "list_my_numbers",
                    "root_name": "list_my_numbers",
                    "version": "7b8bfcd0",
                    "description": "The phone numbers this business already owns.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "numbers.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_my_numbers arguments",
                        "type": "object",
                        "properties": {
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "list_voices",
                    "root_name": "list_voices",
                    "version": "d9762d10",
                    "description": "List the voices available for generating speech, with language, gender, style and a preview URL. Pick from here rather than generating candidates \u2014 previews already exist and cost nothing, generation costs money.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "asset-studio.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "list_voices arguments",
                        "type": "object",
                        "properties": {
                            "language": {
                                "type": "string",
                                "description": "e.g. \"sw\" for Kiswahili, \"en\" for English."
                            },
                            "gender": {
                                "type": "string",
                                "description": "male or female."
                            },
                            "provider": {
                                "type": "string",
                                "description": "Filter to one provider."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "search",
                    "root_name": "search",
                    "version": "6665f820",
                    "description": "Search across everything in this account \u2014 call flows, message flows, audio and support tickets \u2014 and get back ids you can pass to fetch. Use it when you know roughly what you are looking for but not where it lives.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "search arguments",
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "string",
                                "description": "What to look for."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 20, max 50."
                            }
                        },
                        "required": [
                            "query"
                        ]
                    }
                },
                {
                    "name": "search_available_numbers",
                    "root_name": "search_available_numbers",
                    "version": "a86e1247",
                    "description": "Search phone numbers available to buy right now, with their monthly price. Prices here are indicative \u2014 quote_number gives the binding total including any deposit.",
                    "writes": false,
                    "read_only": true,
                    "permissions": [
                        "numbers.view"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "search_available_numbers arguments",
                        "type": "object",
                        "properties": {
                            "prefix": {
                                "type": "string",
                                "description": "E.164 prefix, e.g. \"+255\"."
                            },
                            "number_type_id": {
                                "type": "integer",
                                "description": "Restrict to one number type."
                            },
                            "limit": {
                                "type": "integer",
                                "description": "Default 25, max 100."
                            }
                        }
                    }
                },
                {
                    "name": "apply_flow_ops",
                    "root_name": "apply_flow_ops",
                    "version": "65b46727",
                    "description": "Build or edit a WhatsApp conversation flow by applying graph operations to its draft. Checked by the real flow validator before anything is written, and the user's canvas updates immediately. The flow stays a DRAFT \u2014 you cannot make it reach customers.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "flows.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "apply_flow_ops arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to edit."
                            },
                            "ops_json": {
                                "type": "string",
                                "description": "A JSON object string {\"ops\":[...]}. Ops: add_node, update_node, remove_node, set_edge {from,out,to}, remove_edge {from,out}, set_entry. Max 40. Call get_flow_catalog first \u2014 an edge \"out\" must be one the node kind actually has."
                            },
                            "expected_version": {
                                "type": "integer",
                                "description": "The version from get_message_flow. Stops you overwriting somebody else."
                            },
                            "auto_layout": {
                                "type": "boolean",
                                "description": "Arrange the canvas after applying (default true). Set false only if you are placing nodes yourself."
                            }
                        },
                        "required": [
                            "flow_id",
                            "ops_json"
                        ]
                    }
                },
                {
                    "name": "apply_ivr_ops",
                    "root_name": "apply_ivr_ops",
                    "version": "370cd99e",
                    "description": "Build or edit a call flow by applying graph operations to its draft. The whole batch is checked by the real IVR engine validator before anything is written, and the result appears immediately on the canvas if the user has it open. The flow stays a DRAFT \u2014 publishing is the user's.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "ivr.edit"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "apply_ivr_ops arguments",
                        "type": "object",
                        "properties": {
                            "flow_id": {
                                "type": "integer",
                                "description": "The flow to edit, from list_ivr_flows."
                            },
                            "ops_json": {
                                "type": "string",
                                "description": "A JSON object string {\"ops\":[...]}. Each op is {\"op\":\"add_node\",\"node\":{...}} | {\"op\":\"update_node\",\"id\":\"...\",\"set\":{...}} | {\"op\":\"remove_node\",\"id\":\"...\"} | {\"op\":\"set_entry\",\"id\":\"...\"}. Max 30. Call get_ivr_catalog first for the node kinds and fields."
                            },
                            "expected_version": {
                                "type": "integer",
                                "description": "The version you read in get_ivr_flow. Strongly recommended: it is what stops you overwriting a change somebody else made in the meantime."
                            },
                            "auto_layout": {
                                "type": "boolean",
                                "description": "Arrange the canvas as a tidy tree after applying (default true). Set false only if you are placing nodes yourself with format_ivr_layout."
                            }
                        },
                        "required": [
                            "flow_id",
                            "ops_json"
                        ]
                    }
                },
                {
                    "name": "generate_speech",
                    "root_name": "generate_speech",
                    "version": "5dd9ab56",
                    "description": "Turn text into spoken audio using one of the account's voices, and put it in Asset Studio. Use it for IVR greetings, menu prompts and voicemail messages. Generation costs money, so pick the voice with list_voices first and do not generate variations speculatively.",
                    "writes": true,
                    "read_only": false,
                    "permissions": [
                        "asset-studio.manage"
                    ],
                    "input_schema": {
                        "$schema": "https://json-schema.org/draft/2020-12/schema",
                        "title": "generate_speech arguments",
                        "type": "object",
                        "properties": {
                            "text": {
                                "type": "string",
                                "description": "What to say. Write it in the language the caller will hear."
                            },
                            "voice_id": {
                                "type": "string",
                                "description": "A voice_id from list_voices."
                            },
                            "name": {
                                "type": "string",
                                "description": "A name for the clip, e.g. \"greeting_sw\"."
                            },
                            "wait_ms": {
                                "type": "integer",
                                "description": "How long to wait for it to finish before returning a handle. Default 8000, max 20000."
                            },
                            "confirm_long": {
                                "type": "boolean",
                                "description": "Required for text over 1200 characters, after checking with the user."
                            }
                        },
                        "required": [
                            "text",
                            "voice_id",
                            "name"
                        ]
                    }
                }
            ]
        }
    ]
}