How to integrate Klaviyo MCP with LlamaIndex

Framework Integration Gradient
Klaviyo Logo
LlamaIndex Logo
divider

Introduction

This guide walks you through connecting Klaviyo to LlamaIndex using the Composio tool router. By the end, you'll have a working Klaviyo agent that can add new subscribers to main email list, clone last week's campaign for reuse, estimate recipients for upcoming product launch, create back-in-stock alerts for a product through natural language commands.

This guide will help you understand how to give your LlamaIndex agent real control over a Klaviyo account through Composio's Klaviyo MCP server.

Before we dive in, let's take a quick look at the key ideas and tools involved.

TL;DR

Here's what you'll learn:
  • Set your OpenAI and Composio API keys
  • Install LlamaIndex and Composio packages
  • Create a Composio Tool Router session for Klaviyo
  • Connect LlamaIndex to the Klaviyo MCP server
  • Build a Klaviyo-powered agent using LlamaIndex
  • Interact with Klaviyo through natural language

What is LlamaIndex?

LlamaIndex is a data framework for building LLM applications. It provides tools for connecting LLMs to external data sources and services through agents and tools.

Key features include:

  • ReAct Agent: Reasoning and acting pattern for tool-using agents
  • MCP Tools: Native support for Model Context Protocol
  • Context Management: Maintain conversation context across interactions
  • Async Support: Built for async/await patterns

What is the Klaviyo MCP server, and what's possible with it?

The Klaviyo MCP server is an implementation of the Model Context Protocol that connects your AI agent and assistants like Claude, Cursor, etc directly to your Klaviyo account. It provides structured and secure access to your marketing campaigns, contact lists, and automation features, so your agent can perform actions like creating campaigns, managing subscribers, sending messages, and analyzing engagement—all on your behalf.

  • Automated campaign creation and sending: Instantly have your agent create new marketing campaigns, clone existing ones, and trigger campaign sends to targeted audiences.
  • Subscriber and list management: Add or update profiles in specific Klaviyo lists, subscribe contacts to marketing lists, and ensure your audience is always up to date.
  • Event and engagement tracking: Automatically record customer activities or bulk-create profile events to power segmentation and analytics.
  • Catalog and back-in-stock automation: Let your agent create product catalog categories, manage restock alerts, and help drive timely customer notifications about inventory.
  • Campaign recipient estimation and analytics: Start background jobs to estimate campaign reach and analyze how many recipients meet your targeting criteria before a send.

Supported Tools & Triggers

Tools
Add Profile to ListAdd profiles to a klaviyo list by profile ids or email addresses.
Assign campaign message templateCreates a non-reusable version of the template and assigns it to the message.
Bulk create client eventsUse the client-side endpoint with a public api key to track profile activity.
Bulk create eventsThis api endpoint creates or updates profiles by batching up to 1,000 events, with a 5mb payload limit.
Create back in stock subscriptionUse the server-side endpoint to subscribe to restock alerts, following the back in stock api guide.
Create campaignCreates a campaign given a set of parameters, then returns it.
Create campaign cloneClones an existing campaign, returning a new campaign based on the original with a new id and name.
Create Campaign Recipient Estimation JobStart an asynchronous task to estimate the number of recipients for a campaign.
Create campaign send jobTrigger a campaign to send asynchronously*rate limits*:burst: `10/s`steady: `150/m` **scopes:** `campaigns:write`
Create catalog categoryCreate a new catalog category.
Create catalog category relationships itemsCreate a new item relationship for the given category id.
Create catalog itemCreate a new catalog item.
Create catalog item relationships categoriesCreate a new catalog category relationship for the given item id.
Create catalog variantCreate a new variant for a related catalog item.
Create client back in stock subscriptionUse the endpoint for client-side back in stock notifications with a public api key.
Create client eventCreate client-side events to track profiles using a public api key, not for updating identifiers (server-side only).
Create client subscriptionEndpoint manages email/sms opt-ins using consent and requires public api key for client use.
Create couponCreates a new coupon.
Create coupon codeSynchronously creates a coupon code for the given coupon.
Create eventCreate or update a profile event with minimum identifiers and metric name.
Create listCreate a new list.
Create or update client profileUpdate user profiles without tracking using a public client-side api; use a private server-side api for identifier changes.
Create or update client push tokenThis endpoint for mobile sdks (ios & android) creates/updates push tokens using a public api key.
Create or Update ProfileCreate or update a profile in klaviyo with the given attributes.
Create or update push tokenMigrate or create push tokens with klaviyo's endpoint, utilizing their mobile sdks for ios & android.
Create profileCreate a new profile.
Create segmentCreate a segment.
Create tagSummary: instructions on creating a tag within an account's designated tag group with a maximum of 500 tags, with optional tag group specification.
Create tag groupCreate tag groups up to 50 per account, defaulting to non-exclusive unless specified.
Create tag relationships campaignsSummary: link a tag to campaigns, ensuring a limit of 100 tags per campaign.
Create tag relationships flowsAssign tags to flows using their ids in the request body, ensuring a flow is not linked to over 100 tags.
Create tag relationships listsSummary: a tag can be added to one or more lists, with a max of 100 tags per list.
Create tag relationships segmentsSummary: set tag associations with segments using the request body, limited to one tag per segment and up to 100 tags per segment.
Create templateSummary: custom html templates can be created unless an account reaches 1,000 template limit.
Create template cloneClone a template by its id, but cloning fails if account has 1,000+ templates.
Create template renderRender an email template with specific context and sparse fieldsets, then get html/plain text.
Create webhookCreate a new webhook*rate limits*:burst: `1/s`steady: `15/m` **scopes:** `webhooks:write`
Delete campaignDelete a campaign with the given campaign id.
Delete catalog categoryDelete a catalog category using the given category id.
Delete catalog category relationships itemsDelete item relationships for the given category id.
Delete catalog itemDelete a catalog item with the given item id.
Delete catalog item relationships categoriesDelete catalog category relationships for the given item id.
Delete catalog variantDelete a catalog item variant with the given variant id.
Delete couponDelete the coupon with the given coupon id.
Delete coupon codeDeletes a coupon code specified by the given identifier synchronously.
Delete flowDelete a flow with the given flow id.
Delete listDelete a list with the given list id.
Delete segmentDelete a segment with the given segment id.
Delete tagDelete the tag with the given tag id.
Delete tag groupDelete a specified tag group and its contents; associated resource links will be removed.
Delete tag relationships campaignsDisconnect a tag from campaigns using the campaign id(s) in the request body.
Delete tag relationships flowsRemove a tag's association with one or more flows.
Delete tag relationships listsRemove a tag's association with one or more lists.
Delete tag relationships segmentsUse the request body to de-associate a tag from specified segment ids.
Delete templateDelete a template with the given template id.
Delete webhookDelete a webhook with the given id.
Get accountRetrieve a single account object by its account id.
Get accountsUse a private api key to fetch an associated account's details like contact info, timezone, and currency, as well as validate the key.
Get bulk profile import jobGet a bulk profile import job with the given job id.
Get bulk profile import job errorsGet import errors for the bulk profile import job with the given id.
Get bulk profile import job listsGet list for the bulk profile import job with the given id.
Get bulk profile import job profilesGet profiles for the bulk profile import job with the given id.
Get bulk profile import job relationships listsGet list relationship for the bulk profile import job with the given id.
Get bulk profile import job relationships profilesGet profile relationships for the bulk profile import job with the given id.
Get bulk profile import jobsGet all bulk profile import jobs.
Get CampaignRetrieve a specific campaign by its id from klaviyo.
Get campaign campaign messagesReturn all messages that belong to the given campaign.
Get campaign messageReturns a specific message based on a required id.
Get campaign message campaignReturn the related campaign*rate limits*:burst: `10/s`steady: `150/m` **scopes:** `campaigns:read`
Get campaign message relationships campaignReturns the id of the related campaign*rate limits*:burst: `10/s`steady: `150/m` **scopes:** `campaigns:read`
Get campaign message relationships templateReturns the id of the related template*rate limits*:burst: `10/s`steady: `150/m` **scopes:** `campaigns:read` `templates:read`
Get campaign message templateReturn the related template*rate limits*:burst: `10/s`steady: `150/m` **scopes:** `campaigns:read` `templates:read`
Get campaign recipient estimationGet estimated recipients for a given campaign id using `create campaign recipient estimation job`.
Get campaign recipient estimation jobRetrieve the status of a recipient estimation job triggered with the `create campaign recipient estimation job` endpoint.
Get campaign relationships campaign messagesReturns the ids of all messages associated with the given campaign.
Get campaign relationships tagsReturns the ids of all tags associated with the given campaign.
Get CampaignsRetrieve campaigns from your klaviyo account.
Get campaign send jobGet a campaign send job*rate limits*:burst: `10/s`steady: `150/m` **scopes:** `campaigns:read`
Get campaign tagsReturn all tags that belong to the given campaign.
Get catalog categoriesRetrieve up to 100 account catalog categories, sortable by creation date.
Get catalog categoryGet a catalog category with the given category id.
Get catalog category itemsRetrieve up to 100 sorted items per request from a category using the category id.
Get catalog category relationships itemsGet all items in the given category id.
Get catalog itemGet a specific catalog item with the given item id.
Get catalog item categoriesRetrieve the catalog categories for an item by id, sorted by 'created' date, with a 100-category maximum per request.
Get catalog item relationships categoriesGet all catalog categories that a particular item is in.
Get catalog itemsRetrieve up to 100 sorted catalog items per account, with `$custom` integration and `$default` type.
Get catalog item variantsRetrieve up to 100 variants per request for a specific item id, sortable by creation date.
Get catalog variantGet a catalog item variant with the given variant id.
Get catalog variantsRetrieve up to 100 account variants per request, sortable by creation date.
Get couponGet a specific coupon with the given coupon id.
Get coupon codeReturns a coupon code specified by the given identifier.
Get coupon code bulk create jobGet a coupon code bulk create job with the given job id.
Get coupon code bulk create jobsGet all coupon code bulk create jobs.
Get coupon code relationships couponGets a list of coupon code relationships associated with the given coupon id*rate limits*:burst: `75/s`steady: `700/m` **scopes:** `coupon-codes:read`
Get coupon codesObtains coupon codes using necessary coupon or profile filters.
Get coupon codes for couponGets a list of coupon codes associated with the given coupon id*rate limits*:burst: `75/s`steady: `700/m` **scopes:** `coupon-codes:read`
Get coupon for coupon codeGet the coupon associated with a given coupon code id.
Get coupon relationships coupon codesGets the coupon relationship associated with the given coupon code id*rate limits*:burst: `75/s`steady: `700/m` **scopes:** `coupons:read`
Get couponsGet all coupons in an account.
Get create categories jobGet a catalog category bulk create job with the given job id.
Get create categories jobsGet all catalog category bulk create jobs.
Get create items jobGet a catalog item bulk create job with the given job id.
Get create items jobsGet all catalog item bulk create jobs.
Get create variants jobGet a catalog variant bulk create job with the given job id.
Get create variants jobsGet all catalog variant bulk create jobs.
Get delete categories jobGet a catalog category bulk delete job with the given job id.
Get delete categories jobsGet all catalog category bulk delete jobs.
Get delete items jobGet a catalog item bulk delete job with the given job id.
Get delete items jobsGet all catalog item bulk delete jobs.
Get delete variants jobGet a catalog variant bulk delete job with the given job id.
Get delete variants jobsGet all catalog variant bulk delete jobs.
Get eventGet an event with the given event id.
Get event metricGet the metric for an event with the given event id.
Get event profileGet the profile associated with an event with the given event id.
Get event relationships metricGet a list of related metrics for an event*rate limits*:burst: `350/s`steady: `3500/m` **scopes:** `events:read` `metrics:read`
Get event relationships profileGet profile [relationships](https://developers.
Get eventsGet all events in an account requests can be sorted by the following fields: `datetime`, `timestamp` returns a maximum of 200 events per page.
Get flowGet a flow with the given flow id.
Get flow actionGet a flow action from a flow with the given flow action id.
Get flow action for messageGet the flow action for a flow message with the given message id.
Get flow action messagesRetrieve up to 50 flow messages per request by action id, sortable by various fields, with ascending/descending options, and paginated using `page[size]` and `page[number]`.
Get flow action relationships flowGet the flow associated with the given action id.
Get flow action relationships messagesRetrieves up to 50 flow message relationships per request for a specified flow action id, with cursor pagination.
Get flow flow actionsGet all flow actions associated with the given flow id.
Get flow for flow actionGet the flow associated with the given action id.
Get flow messageGet the flow message of a flow with the given message id.
Get flow message relationships actionGet the [relationship](https://developers.
Get flow message relationships templateReturns the id of the related template*rate limits*:burst: `3/s`steady: `60/m` **scopes:** `templates:read`
Get flow message templateReturn the related template*rate limits*:burst: `3/s`steady: `60/m` **scopes:** `templates:read`
Get flow relationships flow actionsRetrieve all flow action relationships for a specific flow id, sortable by `id`, `status`, `created`, `updated`.
Get flow relationships tagsReturn the tag ids of all tags associated with the given flow.
Get flowsGet all flows in an account.
Get flow tagsReturn all tags associated with the given flow id.
Get formGet the form with the given id.
Get form for form versionGet the form associated with the given form version.
Get form id for form versionGet the id of the form associated with the given form version.
Get formsGet all forms in an account.
Get form versionGet the form version with the given id.
Get imageGet the image with the given image id.
Get imagesGet all images in an account.
Get listApi allows 75 req/sec and 700 req/min, but with 'profile count' param, it's 1 req/sec and 15 req/min.
Get list profilesRetrieve profiles in a list by id, filterable by email/phone/push token/join date, sortable by join date.
Get list relationships profilesGet profile membership [relationships](https://developers.
Get list relationships tagsReturns the tag ids of all tags associated with the given list.
Get ListsRetrieve marketing lists from your klaviyo account.
Get list tagsReturn all tags associated with the given list id.
Get metricGet a metric with the given metric id.
Get metricsGet all metrics in an account.
Get profileGet the profile with the given profile id.
Get profile listsGet list memberships for a profile with the given profile id.
Get profile relationships listsGet list memberships for a profile with the given profile id.
Get profile relationships segmentsGet segment membership relationships for a profile with the given profile id.
Get ProfilesRetrieve profiles from your klaviyo account.
Get profile segmentsGet segment memberships for a profile with the given profile id.
Get segmentFetch a segment by id with default rates of 75/s and 700/m, or with `additional-fields` at 1/s and 15/m.
Get segment profilesRetrieve profiles in a segment by id, filtering by email, phone, token, or join date, and sorting by join date.
Get segment relationships profilesGet all profile membership [relationships](https://developers.
Get segment relationships tagsIf `related resource` is `tags`, returns the tag ids of all tags associated with the given segment id.
Get segmentsFetch segments from an account with filters like `name`, `created`, and `updated`.
Get segment tagsReturn all tags associated with the given segment id.
Get tagRetrieve the tag with the given tag id.
Get tag groupRetrieve the tag group with the given tag group id.
Get tag group relationships tagsReturns the tag ids of all tags inside the given tag group.
Get tag groupsRetrieve up to 25 tag groups per account, sortable/filterable by specific attributes.
Get tag group tagsReturn the tags for a given tag group id.
Get tag relationships campaignsReturns the ids of all campaigns associated with the given tag.
Get tag relationships flowsReturns the ids of all flows associated with the given tag.
Get tag relationships listsReturns the ids of all lists associated with the given tag.
Get tag relationships segmentsReturns the ids of all segments associated with the given tag.
Get tag relationships tag groupReturns the id of the tag group related to the given tag.
Get tagsRetrieve up to 50 account tags at once, filterable/sortable by name or id, with cursor pagination.
Get tag tag groupReturns the tag group resource for a given tag id.
Get templateGet a template with the given template id.
Get templatesRetrieve account templates with sorting options (`id`, `name`, `created`, `updated`).
Get update categories jobGet a catalog category bulk update job with the given job id.
Get update categories jobsGet all catalog category bulk update jobs.
Get update items jobGet a catalog item bulk update job with the given job id.
Get update items jobsGet all catalog item bulk update jobs.
Get update variants jobGet a catalog variate bulk update job with the given job id.
Get update variants jobsGet all catalog variant bulk update jobs.
Get version ids for formGet the ids of the form versions for the given form.
Get versions for formGet the form versions for the given form.
Get webhookGet the webhook with the given id.
Get webhooksGet all webhooks in an account.
Get webhook topicGet the webhook topic with the given id.
Get webhook topicsGet all webhook topics in a klaviyo account.
Merge profilesQueue a task to merge one source profile into a destination profile using their ids.
Query campaign valuesReturns the requested campaign analytics values data*rate limits*:burst: `1/s`steady: `2/m`daily: `225/d` **scopes:** `campaigns:read`
Query flow seriesReturns the requested flow analytics series data*rate limits*:burst: `1/s`steady: `2/m`daily: `225/d` **scopes:** `flows:read`
Query flow valuesReturns the requested flow analytics values data*rate limits*:burst: `1/s`steady: `2/m`daily: `225/d` **scopes:** `flows:read`
Query metric aggregatesThe klaviyo endpoint fetches metric events, handling json requests for custom data queries, sorting, and filtering; offers grouping and time-based filters; requires adherence to rate limits (3 requests per second, 60 per minute) under 'metrics:read'.
Remove Profile from ListRemove profiles from a klaviyo list by profile ids or email addresses.
Request profile deletionTo delete a profile, use only one identifier: email, phone number, or id.
Spawn bulk profile import jobInitiate a job to create/update a batch of profiles, up to 10,000 with a max size of 5mb per request.
Spawn coupon code bulk create jobCreate a coupon-code-bulk-create-job to bulk create a list of coupon codes.
Spawn create categories jobCreate bulk job for up to 100 catalog categories with a 5mb size limit and a max of 500 concurrent jobs.
Spawn create items jobCreate batches of up to 100 catalog items with a 5mb size limit using the bulk job, which allows 500 concurrent jobs.
Spawn create variants jobInitiate a job to bulk create up to 100 catalog variants, with a 5mb payload size limit.
Spawn delete categories jobDelete multiple catalog categories in bulk, with a limit of 100 per request and a 5mb payload size.
Spawn delete items jobDelete batches of catalog items with a bulk job, max 100 items/request, 5mb size limit, and up to 500 concurrent jobs.
Spawn delete variants jobDelete multiple catalog variants with a bulk job, max 100 per request, 5mb size limit.
Spawn update categories jobCreate a job to bulk update up to 100 categories, with a 5mb size limit and a maximum of 500 concurrent jobs.
Spawn update items jobYou can bulk update up to 100 catalog items with a 5mb payload limit.
Spawn update variants jobCreate a job to bulk update up to 100 catalog variants with a 5mb payload limit.
Subscribe profilesThe api supports double opt-in for marketing, with 'historical import' bypassing consent.
Suppress profilesSuppress profiles by email, segment, or list id to stop email marketing, regardless of consent.
Unregister client push tokenThis endpoint unsubscribes a push token, for use with klaviyo's mobile sdks and a public api key.
Unsubscribe profilesOpt-out profiles from email or sms marketing.
Unsuppress profilesRemove 'user suppressed' blocks on profiles manually via email, segment, or list id.
Update CampaignUpdate a campaign with the specified attributes.
Update campaign messageUpdate a campaign message*rate limits*:burst: `10/s`steady: `150/m` **scopes:** `campaigns:write`
Update campaign send jobPermanently cancel the campaign, setting the status to canceled or revert the campaign, setting the status back to draft*rate limits*:burst: `10/s`steady: `150/m` **scopes:** `campaigns:write`
Update catalog categoryUpdate a catalog category with the given category id.
Update catalog category relationships itemsUpdate item relationships for the given category id.
Update catalog itemUpdate a catalog item with the given item id.
Update catalog item relationships categoriesUpdate catalog category relationships for the given item id.
Update catalog variantUpdate a catalog item variant with the given variant id.
Update coupon*rate limits*:burst: `3/s`steady: `60/m` **scopes:** `coupons:write`
Update coupon codeUpdates a coupon code specified by the given identifier synchronously.
Update flow statusUpdate the status of a flow with the given flow id, and all actions in that flow.
Update imageUpdate the image with the given image id.
Update listUpdate the name of a list with the given list id.
Update profileUpdate profiles with the provided id.
Update segmentUpdate a segment with the given segment id.
Update tagUpdate the tag with the given tag id.
Update tag groupUpdate the tag group with the given tag group id.
Update templateUpdate a template with the given template id.
Update webhookUpdate the webhook with the given id.
Upload image from fileUpload an image from a file.
Upload image from urlImport an image from a url or data uri.

What is the Composio tool router, and how does it fit here?

What is Tool Router?

Composio's Tool Router helps agents find the right tools for a task at runtime. You can plug in multiple toolkits (like Gmail, HubSpot, and GitHub), and the agent will identify the relevant app and action to complete multi-step workflows. This can reduce token usage and improve the reliability of tool calls. Read more here: Getting started with Tool Router

The tool router generates a secure MCP URL that your agents can access to perform actions.

How the Tool Router works

The Tool Router follows a three-phase workflow:

  1. Discovery: Searches for tools matching your task and returns relevant toolkits with their details.
  2. Authentication: Checks for active connections. If missing, creates an auth config and returns a connection URL via Auth Link.
  3. Execution: Executes the action using the authenticated connection.

Step-by-step Guide

Prerequisites

Before you begin, make sure you have:
  • Python 3.8/Node 16 or higher installed
  • A Composio account with the API key
  • An OpenAI API key
  • A Klaviyo account and project
  • Basic familiarity with async Python/Typescript

Getting API Keys for OpenAI, Composio, and Klaviyo

OpenAI API key (OPENAI_API_KEY)
  • Go to the OpenAI dashboard
  • Create an API key if you don't have one
  • Assign it to OPENAI_API_KEY in .env
Composio API key and user ID
  • Log into the Composio dashboard
  • Copy your API key from Settings
    • Use this as COMPOSIO_API_KEY
  • Pick a stable user identifier (email or ID)
    • Use this as COMPOSIO_USER_ID

Installing dependencies

pip install composio-llamaindex llama-index llama-index-llms-openai llama-index-tools-mcp python-dotenv

Create a new Python project and install the necessary dependencies:

  • composio-llamaindex: Composio's LlamaIndex integration
  • llama-index: Core LlamaIndex framework
  • llama-index-llms-openai: OpenAI LLM integration
  • llama-index-tools-mcp: MCP client for LlamaIndex
  • python-dotenv: Environment variable management

Set environment variables

bash
OPENAI_API_KEY=your-openai-api-key
COMPOSIO_API_KEY=your-composio-api-key
COMPOSIO_USER_ID=your-user-id

Create a .env file in your project root:

These credentials will be used to:

  • Authenticate with OpenAI's GPT-5 model
  • Connect to Composio's Tool Router
  • Identify your Composio user session for Klaviyo access

Import modules

import asyncio
import os
import dotenv

from composio import Composio
from composio_llamaindex import LlamaIndexProvider
from llama_index.core.agent.workflow import ReActAgent
from llama_index.core.workflow import Context
from llama_index.llms.openai import OpenAI
from llama_index.tools.mcp import BasicMCPClient, McpToolSpec

dotenv.load_dotenv()

Create a new file called klaviyo_llamaindex_agent.py and import the required modules:

Key imports:

  • asyncio: For async/await support
  • Composio: Main client for Composio services
  • LlamaIndexProvider: Adapts Composio tools for LlamaIndex
  • ReActAgent: LlamaIndex's reasoning and action agent
  • BasicMCPClient: Connects to MCP endpoints
  • McpToolSpec: Converts MCP tools to LlamaIndex format

Load environment variables and initialize Composio

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
COMPOSIO_API_KEY = os.getenv("COMPOSIO_API_KEY")
COMPOSIO_USER_ID = os.getenv("COMPOSIO_USER_ID")

if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY is not set in the environment")
if not COMPOSIO_API_KEY:
    raise ValueError("COMPOSIO_API_KEY is not set in the environment")
if not COMPOSIO_USER_ID:
    raise ValueError("COMPOSIO_USER_ID is not set in the environment")

What's happening:

This ensures missing credentials cause early, clear errors before the agent attempts to initialise.

Create a Tool Router session and build the agent function

async def build_agent() -> ReActAgent:
    composio_client = Composio(
        api_key=COMPOSIO_API_KEY,
        provider=LlamaIndexProvider(),
    )

    session = composio_client.create(
        user_id=COMPOSIO_USER_ID,
        toolkits=["klaviyo"],
    )

    mcp_url = session.mcp.url
    print(f"Composio MCP URL: {mcp_url}")

    mcp_client = BasicMCPClient(mcp_url, headers={"x-api-key": COMPOSIO_API_KEY})
    mcp_tool_spec = McpToolSpec(client=mcp_client)
    tools = await mcp_tool_spec.to_tool_list_async()

    llm = OpenAI(model="gpt-5")

    description = "An agent that uses Composio Tool Router MCP tools to perform Klaviyo actions."
    system_prompt = """
    You are a helpful assistant connected to Composio Tool Router.
    Use the available tools to answer user queries and perform Klaviyo actions.
    """
    return ReActAgent(tools=tools, llm=llm, description=description, system_prompt=system_prompt, verbose=True)

What's happening here:

  • We create a Composio client using your API key and configure it with the LlamaIndex provider
  • We then create a tool router MCP session for your user, specifying the toolkits we want to use (in this case, klaviyo)
  • The session returns an MCP HTTP endpoint URL that acts as a gateway to all your configured tools
  • LlamaIndex will connect to this endpoint to dynamically discover and use the available Klaviyo tools.
  • The MCP tools are mapped to LlamaIndex-compatible tools and plug them into the Agent.

Create an interactive chat loop

async def chat_loop(agent: ReActAgent) -> None:
    ctx = Context(agent)
    print("Type 'quit', 'exit', or Ctrl+C to stop.")

    while True:
        try:
            user_input = input("\nYou: ").strip()
        except (KeyboardInterrupt, EOFError):
            print("\nBye!")
            break

        if not user_input or user_input.lower() in {"quit", "exit"}:
            print("Bye!")
            break

        try:
            print("Agent: ", end="", flush=True)
            handler = agent.run(user_input, ctx=ctx)

            async for event in handler.stream_events():
                # Stream token-by-token from LLM responses
                if hasattr(event, "delta") and event.delta:
                    print(event.delta, end="", flush=True)
                # Show tool calls as they happen
                elif hasattr(event, "tool_name"):
                    print(f"\n[Using tool: {event.tool_name}]", flush=True)

            # Get final response
            response = await handler
            print()  # Newline after streaming
        except KeyboardInterrupt:
            print("\n[Interrupted]")
            continue
        except Exception as e:
            print(f"\nError: {e}")

What's happening here:

  • We're creating a direct terminal interface to chat with your Klaviyo database
  • The LLM's responses are streamed to the CLI for faster interaction.
  • The agent uses context to maintain conversation history
  • You can type 'quit' or 'exit' to stop the chat loop gracefully
  • Agent responses and any errors are displayed in a clear, readable format

Define the main entry point

async def main() -> None:
    agent = await build_agent()
    await chat_loop(agent)

if __name__ == "__main__":
    # Handle Ctrl+C gracefully
    signal.signal(signal.SIGINT, lambda s, f: (print("\nBye!"), exit(0)))
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nBye!")

What's happening here:

  • We're orchestrating the entire application flow
  • The agent gets built with proper error handling
  • Then we kick off the interactive chat loop so you can start talking to Klaviyo

Run the agent

npx ts-node llamaindex-agent.ts

When prompted, authenticate and authorise your agent with Klaviyo, then start asking questions.

Complete Code

Here's the complete code to get you started with Klaviyo and LlamaIndex:

import asyncio
import os
import signal
import dotenv

from composio import Composio
from composio_llamaindex import LlamaIndexProvider
from llama_index.core.agent.workflow import ReActAgent
from llama_index.core.workflow import Context
from llama_index.llms.openai import OpenAI
from llama_index.tools.mcp import BasicMCPClient, McpToolSpec

dotenv.load_dotenv()

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
COMPOSIO_API_KEY = os.getenv("COMPOSIO_API_KEY")
COMPOSIO_USER_ID = os.getenv("COMPOSIO_USER_ID")

if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY is not set")
if not COMPOSIO_API_KEY:
    raise ValueError("COMPOSIO_API_KEY is not set")
if not COMPOSIO_USER_ID:
    raise ValueError("COMPOSIO_USER_ID is not set")

async def build_agent() -> ReActAgent:
    composio_client = Composio(
        api_key=COMPOSIO_API_KEY,
        provider=LlamaIndexProvider(),
    )

    session = composio_client.create(
        user_id=COMPOSIO_USER_ID,
        toolkits=["klaviyo"],
    )

    mcp_url = session.mcp.url
    print(f"Composio MCP URL: {mcp_url}")

    mcp_client = BasicMCPClient(mcp_url, headers={"x-api-key": COMPOSIO_API_KEY})
    mcp_tool_spec = McpToolSpec(client=mcp_client)
    tools = await mcp_tool_spec.to_tool_list_async()

    llm = OpenAI(model="gpt-5")
    description = "An agent that uses Composio Tool Router MCP tools to perform Klaviyo actions."
    system_prompt = """
    You are a helpful assistant connected to Composio Tool Router.
    Use the available tools to answer user queries and perform Klaviyo actions.
    """
    return ReActAgent(
        tools=tools,
        llm=llm,
        description=description,
        system_prompt=system_prompt,
        verbose=True,
    );

async def chat_loop(agent: ReActAgent) -> None:
    ctx = Context(agent)
    print("Type 'quit', 'exit', or Ctrl+C to stop.")

    while True:
        try:
            user_input = input("\nYou: ").strip()
        except (KeyboardInterrupt, EOFError):
            print("\nBye!")
            break

        if not user_input or user_input.lower() in {"quit", "exit"}:
            print("Bye!")
            break

        try:
            print("Agent: ", end="", flush=True)
            handler = agent.run(user_input, ctx=ctx)

            async for event in handler.stream_events():
                # Stream token-by-token from LLM responses
                if hasattr(event, "delta") and event.delta:
                    print(event.delta, end="", flush=True)
                # Show tool calls as they happen
                elif hasattr(event, "tool_name"):
                    print(f"\n[Using tool: {event.tool_name}]", flush=True)

            # Get final response
            response = await handler
            print()  # Newline after streaming
        except KeyboardInterrupt:
            print("\n[Interrupted]")
            continue
        except Exception as e:
            print(f"\nError: {e}")

async def main() -> None:
    agent = await build_agent()
    await chat_loop(agent)

if __name__ == "__main__":
    # Handle Ctrl+C gracefully
    signal.signal(signal.SIGINT, lambda s, f: (print("\nBye!"), exit(0)))
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nBye!")

Conclusion

You've successfully connected Klaviyo to LlamaIndex through Composio's Tool Router MCP layer. Key takeaways:
  • Tool Router dynamically exposes Klaviyo tools through an MCP endpoint
  • LlamaIndex's ReActAgent handles reasoning and orchestration; Composio handles integrations
  • The agent becomes more capable without increasing prompt size
  • Async Python provides clean, efficient execution of agent workflows
You can easily extend this to other toolkits like Gmail, Notion, Stripe, GitHub, and more by adding them to the toolkits parameter.

How to build Klaviyo MCP Agent with another framework

FAQ

What are the differences in Tool Router MCP and Klaviyo MCP?

With a standalone Klaviyo MCP server, the agents and LLMs can only access a fixed set of Klaviyo tools tied to that server. However, with the Composio Tool Router, agents can dynamically load tools from Klaviyo and many other apps based on the task at hand, all through a single MCP endpoint.

Can I use Tool Router MCP with LlamaIndex?

Yes, you can. LlamaIndex fully supports MCP integration. You get structured tool calling, message history handling, and model orchestration while Tool Router takes care of discovering and serving the right Klaviyo tools.

Can I manage the permissions and scopes for Klaviyo while using Tool Router?

Yes, absolutely. You can configure which Klaviyo scopes and actions are allowed when connecting your account to Composio. You can also bring your own OAuth credentials or API configuration so you keep full control over what the agent can do.

How safe is my data with Composio Tool Router?

All sensitive data such as tokens, keys, and configuration is fully encrypted at rest and in transit. Composio is SOC 2 Type 2 compliant and follows strict security practices so your Klaviyo data and credentials are handled as safely as possible.

Used by agents from

Context
ASU
Letta
glean
HubSpot
Agent.ai
Altera
DataStax
Entelligence
Rolai
Context
ASU
Letta
glean
HubSpot
Agent.ai
Altera
DataStax
Entelligence
Rolai
Context
ASU
Letta
glean
HubSpot
Agent.ai
Altera
DataStax
Entelligence
Rolai

Never worry about agent reliability

We handle tool reliability, observability, and security so you never have to second-guess an agent action.