Skip to main content
Connect any LangGraph agent to HoopAI using the MCP server and the langchain-mcp-adapters library. Your agent gets all 36 HoopAI tools automatically — no manual tool wrappers needed.

Installation

pip install langchain-mcp-adapters langgraph langchain-anthropic

Quickstart

import asyncio
import os
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_anthropic import ChatAnthropic

async def main():
    async with MultiServerMCPClient(
        {
            "hoopai": {
                "transport": "streamable_http",
                "url": "https://services.leadconnectorhq.com/mcp/",
                "headers": {
                    "Authorization": f"Bearer {os.environ['HOOPAI_API_KEY']}",
                    "locationId": os.environ["HOOPAI_LOCATION_ID"],
                },
            }
        }
    ) as client:
        tools = client.get_tools()
        model = ChatAnthropic(model="claude-sonnet-4-6")
        agent = create_react_agent(model, tools)

        result = await agent.ainvoke(
            {"messages": [{"role": "user", "content": "Show me my last 5 contacts"}]}
        )
        print(result["messages"][-1].content)

asyncio.run(main())
Set these environment variables before running:
export HOOPAI_API_KEY=your_private_integration_token
export HOOPAI_LOCATION_ID=your_location_id
export ANTHROPIC_API_KEY=your_anthropic_api_key

Multi-agent architecture

For production use, split tools across specialist agents controlled by a router. This keeps each agent focused and prevents accidental cross-domain actions.

Specialist subagents

Give each subagent an allowlist of tool names so it can only call the tools relevant to its domain:
def make_specialist(client, allowed_tools: list[str], system_prompt: str):
    """Create a specialist agent with a restricted tool set."""
    all_tools = client.get_tools()
    tools = [t for t in all_tools if t.name in allowed_tools]
    model = ChatAnthropic(model="claude-sonnet-4-6")
    return create_react_agent(model, tools, state_modifier=system_prompt)

# Contacts specialist — read/write contacts and tags only
contacts_agent = make_specialist(
    client,
    allowed_tools=[
        "contacts_get-contacts",
        "contacts_get-contact",
        "contacts_create-contact",
        "contacts_update-contact",
        "contacts_upsert-contact",
        "contacts_add-tags",
        "contacts_remove-tags",
        "contacts_get-all-tasks",
    ],
    system_prompt=(
        "You manage contacts. Always confirm before creating or updating records. "
        "Never delete contacts. Return structured summaries."
    ),
)

# Conversations specialist — search and send messages
conversations_agent = make_specialist(
    client,
    allowed_tools=[
        "conversations_search-conversation",
        "conversations_get-messages",
        "conversations_send-a-new-message",
    ],
    system_prompt=(
        "You handle conversations and messaging. "
        "Always confirm the recipient and message content before sending. "
        "Never send bulk messages without explicit user approval."
    ),
)
SubagentTools to includeGuardrails
Contactsget, search, create, update, upsert, add/remove tagsConfirm before write; never bulk-delete
Conversationssearch, get messages, send messageValidate recipient; confirm before send
Calendarget events, get appointment notesRequire timezone; confirm before booking
Opportunitiessearch, get pipelines, get, updateRequire reason text for stage/value changes
Paymentsget order, list transactionsRead-only by default; gate writes behind approval

Router pattern

The router classifies intent and delegates to one specialist:
ROUTER_PROMPT = """
You are a router. Delegate to exactly one specialist based on the user's intent.
Never call MCP tools directly — only route.

Routing rules:
- Contact lookup, tagging, segmentation → contacts
- Messages, inbox, follow-ups → conversations
- Appointments, scheduling → calendar
- Deals, pipelines, stages → opportunities
- Invoices, transactions, payments → payments

If locationId is missing, ask for it before routing.
If the task is destructive (delete, bulk-update), ask for explicit confirmation first.
"""

Full multi-agent example

import asyncio
import os
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage

SPECIALISTS = {
    "contacts": [
        "contacts_get-contacts", "contacts_get-contact",
        "contacts_create-contact", "contacts_update-contact",
        "contacts_add-tags", "contacts_remove-tags",
    ],
    "conversations": [
        "conversations_search-conversation",
        "conversations_get-messages",
        "conversations_send-a-new-message",
    ],
    "opportunities": [
        "opportunities_search-opportunity",
        "opportunities_get-pipelines",
        "opportunities_get-opportunity",
        "opportunities_update-opportunity",
    ],
}

async def run_multi_agent(user_message: str):
    async with MultiServerMCPClient(
        {
            "hoopai": {
                "transport": "streamable_http",
                "url": "https://services.leadconnectorhq.com/mcp/",
                "headers": {
                    "Authorization": f"Bearer {os.environ['HOOPAI_API_KEY']}",
                    "locationId": os.environ["HOOPAI_LOCATION_ID"],
                },
            }
        }
    ) as client:
        all_tools = client.get_tools()
        model = ChatAnthropic(model="claude-sonnet-4-6")

        # Build specialist agents
        agents = {}
        for name, allowed in SPECIALISTS.items():
            tools = [t for t in all_tools if t.name in allowed]
            agents[name] = create_react_agent(model, tools)

        # Router decides which specialist to use
        router_tools = [t for t in all_tools if t.name == "locations_get-location"]
        router = create_react_agent(model, router_tools, state_modifier=(
            "You are a router. Respond with only the specialist name: "
            "contacts, conversations, or opportunities."
        ))

        route_result = await router.ainvoke(
            {"messages": [HumanMessage(content=f"Route this: {user_message}")]}
        )
        specialist_name = route_result["messages"][-1].content.strip().lower()

        agent = agents.get(specialist_name, agents["contacts"])
        result = await agent.ainvoke(
            {"messages": [HumanMessage(content=user_message)]}
        )
        return result["messages"][-1].content

result = asyncio.run(run_multi_agent("Find all contacts tagged as 'New Lead' added this week"))
print(result)

Implementation checklist

  • One shared MCP client, reused across all subagents
  • Per-subagent tool allowlists
  • Approval middleware for write operations
  • Log all MCP calls with locationId, tool name, timestamp, and status
  • Add replay tests for common workflows (contact search, send message, move opportunity)
Last modified on March 7, 2026