# How to integrate Listennotes MCP with LlamaIndex

```json
{
  "title": "How to integrate Listennotes MCP with LlamaIndex",
  "toolkit": "Listennotes",
  "toolkit_slug": "listennotes",
  "framework": "LlamaIndex",
  "framework_slug": "llama-index",
  "url": "https://composio.dev/toolkits/listennotes/framework/llama-index",
  "markdown_url": "https://composio.dev/toolkits/listennotes/framework/llama-index.md",
  "updated_at": "2026-05-12T10:17:48.898Z"
}
```

## Introduction

This guide walks you through connecting Listennotes to LlamaIndex using the Composio tool router. By the end, you'll have a working Listennotes agent that can find top tech podcasts from last week, get audience stats for this podcast, list curated playlists about entrepreneurship through natural language commands.
This guide will help you understand how to give your LlamaIndex agent real control over a Listennotes account through Composio's Listennotes MCP server.
Before we dive in, let's take a quick look at the key ideas and tools involved.

## Also integrate Listennotes with

- [OpenAI Agents SDK](https://composio.dev/toolkits/listennotes/framework/open-ai-agents-sdk)
- [Claude Agent SDK](https://composio.dev/toolkits/listennotes/framework/claude-agents-sdk)
- [Claude Code](https://composio.dev/toolkits/listennotes/framework/claude-code)
- [Claude Cowork](https://composio.dev/toolkits/listennotes/framework/claude-cowork)
- [Codex](https://composio.dev/toolkits/listennotes/framework/codex)
- [OpenClaw](https://composio.dev/toolkits/listennotes/framework/openclaw)
- [Hermes](https://composio.dev/toolkits/listennotes/framework/hermes-agent)
- [CLI](https://composio.dev/toolkits/listennotes/framework/cli)
- [Google ADK](https://composio.dev/toolkits/listennotes/framework/google-adk)
- [LangChain](https://composio.dev/toolkits/listennotes/framework/langchain)
- [Vercel AI SDK](https://composio.dev/toolkits/listennotes/framework/ai-sdk)
- [Mastra AI](https://composio.dev/toolkits/listennotes/framework/mastra-ai)
- [CrewAI](https://composio.dev/toolkits/listennotes/framework/crew-ai)

## 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 Listennotes
- Connect LlamaIndex to the Listennotes MCP server
- Build a Listennotes-powered agent using LlamaIndex
- Interact with Listennotes 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 Listennotes MCP server, and what's possible with it?

The Listennotes MCP server is an implementation of the Model Context Protocol that connects your AI agent and assistants like Claude, Cursor, etc directly to your Listennotes account. It provides structured and secure access to the Listennotes podcast search platform, so your agent can discover, analyze, and organize podcasts, retrieve episode details, and explore curated recommendations on your behalf.
- Powerful podcast discovery and search: Let your agent fetch top-rated or genre-specific podcasts, explore curated lists, or search for the best shows to match your interests.
- In-depth episode and podcast metadata retrieval: Retrieve detailed information about specific episodes or podcasts, including descriptions, publication dates, and audience metrics, to support research or content curation.
- Bulk data operations for podcasts and episodes: Fetch metadata for multiple podcasts or episodes in a single request, making it easy to keep libraries or dashboards up to date with the latest content.
- Playlist and curated collection management: Access and organize playlists or curated collections, helping users browse, recommend, or share themed groups of podcasts.
- Genre exploration and content organization: Retrieve comprehensive genre lists to power advanced filtering, personalized recommendations, or dynamic content categorization.

## Supported Tools

| Tool slug | Name | Description |
|---|---|---|
| `LISTENNOTES_BATCH_FETCH_BASIC_META_DATA_FOR_EPISODES` | Post episodes by ids | The listennotestest_episodes_post endpoint allows users to retrieve metadata for multiple podcast episodes in a single request. This POST endpoint accepts a list of episode IDs and returns detailed information about each specified episode. It's particularly useful for bulk retrieval of episode data, reducing the number of API calls needed when working with multiple episodes. The endpoint should be used when you need to fetch information about specific, known episodes rather than searching or browsing. Keep in mind that this endpoint is focused on retrieval only and does not support creating, updating, or deleting episode data. |
| `LISTENNOTES_BATCH_FETCH_BASIC_META_DATA_FOR_PODCASTS` | Create podcast via form data | The listennotestest_podcasts_post endpoint allows users to retrieve information about multiple podcasts using various identifiers such as Listen Notes IDs, RSS feed URLs, Apple Podcasts IDs, or Spotify IDs. It can optionally fetch the latest episodes for the specified podcasts. This endpoint is useful for bulk podcast data retrieval and for staying updated on recent episodes across multiple shows. It's particularly valuable for applications that need to sync or update podcast information from different platforms. The endpoint has a limit of returning up to 15 latest episodes per request, so for comprehensive episode history, multiple calls may be necessary. |
| `LISTENNOTES_FETCH_A_CURATED_LIST_OF_PODCASTS_BY_ID` | Retrieve curated podcast by id | Retrieves detailed information about a specific curated podcast using its unique identifier. This endpoint allows users to access comprehensive data about a carefully selected podcast collection, including its contents, curator information, and associated metadata. It's particularly useful for applications that want to display or analyze curated podcast lists. The endpoint should be used when detailed information about a known curated podcast is required, but it won't provide a list of all available curated podcasts or allow modifications to the curated content. |
| `LISTENNOTES_FETCH_A_LIST_OF_BEST_PODCASTS_BY_GENRE` | Fetch best podcasts list | The getBestPodcasts endpoint retrieves a curated list of the best podcasts from the Listen Notes platform. It allows users to access a collection of top-rated or popular podcasts, which can be filtered by genre, region, and content rating. This endpoint is useful for discovering high-quality podcast content, creating recommendation systems, or populating podcast directories. The results are paginated for efficient data retrieval and can be customized to exclude explicit content if required. While it provides a valuable overview of top podcasts, it may not include real-time updates or notifications about new episodes or changes in podcast rankings. |
| `LISTENNOTES_FETCH_A_LIST_OF_PODCAST_GENRES` | Retrieve genre list | The GetGenres endpoint retrieves a comprehensive list of available genres within the listennotestest platform. This endpoint is designed to provide users with up-to-date information on content categories, which can be used for filtering, organizing, or subscribing to specific types of content. It's particularly useful for applications that need to populate genre-based dropdown menus, create content recommendation systems, or set up genre-specific notification preferences. The endpoint does not require any input parameters, making it simple to use for fetching the entire genre catalog. However, users should be aware that the response may contain a large amount of data, depending on the number of genres available in the system. |
| `LISTENNOTES_FETCH_A_LIST_OF_YOUR_PLAYLISTS` | Get playlists | Retrieves a list of playlists from the Listen Notes platform. This endpoint allows users to fetch all available playlists associated with their account or publicly accessible playlists. It should be used when you need to display a collection of playlists, such as in a user's library or for browsing purposes. The endpoint returns basic information about each playlist, which may include the playlist name, creator, number of episodes, and other relevant metadata. Note that this endpoint likely returns a limited number of playlists per request, and pagination might be necessary for retrieving large collections. It does not provide detailed information about the episodes within each playlist; separate API calls would be required to fetch that level of detail. |
| `LISTENNOTES_FETCH_A_RANDOM_PODCAST_EPISODE` | Listen to just listen endpoint | The 'just_listen' endpoint is a basic listener or health check mechanism for the listennotestest app. It allows users to verify the availability and responsiveness of the service without requiring any input parameters. This endpoint should be used for monitoring the status of the listennotestest service or as a simple ping to ensure the API is operational. It's important to note that this endpoint likely doesn't provide any specific data about podcasts or user accounts. The response is expected to be minimal, possibly just confirming that the service is up and running. While useful for basic health checks, it should not be relied upon for any complex operations or data retrieval within the listennotestest ecosystem. |
| `LISTENNOTES_FETCH_AUDIENCE_DEMOGRAPHICS_FOR_A_PODCAST` | Get podcast audience by id | Retrieves audience information for a specific podcast identified by its unique ID. This endpoint allows users to access demographic data, listening statistics, or other audience-related metrics for a particular podcast. It's useful for podcast creators, marketers, or analysts who want to understand their audience better or track the performance of their content. The tool returns aggregated data about the podcast's listeners, which may include but is not limited to age groups, geographic distribution, listening habits, or engagement metrics. It should be used when detailed audience insights for a specific podcast are needed. Note that this endpoint does not provide real-time listener counts or individual listener data to protect user privacy. |
| `LISTENNOTES_FETCH_CURATED_LISTS_OF_PODCASTS` | Get curated podcasts | Retrieves a list of curated podcasts from the Listen Notes platform. This endpoint provides access to a collection of handpicked and organized podcasts, likely curated by experts or algorithms. It's useful for discovering high-quality content or featuring recommended podcasts to users. The endpoint returns a selection of podcasts, possibly grouped by themes, genres, or other criteria. While it offers a curated selection, it may not provide exhaustive search capabilities or personalized recommendations based on individual user preferences. |
| `LISTENNOTES_FETCH_DETAILED_META_DATA_FOR_AN_EPISODE_BY_ID` | Retrieve episode by id | Retrieves detailed information about a specific episode using its unique identifier. This endpoint allows users to access comprehensive data about an individual episode, which may include metadata such as title, description, publication date, duration, and associated podcast information. It's particularly useful when you need to display or process information about a single episode. The endpoint should be used when detailed information about a known episode is required, but it won't provide lists of episodes or search functionality. Note that the response structure and the exact fields returned are not specified in the given schema, so the actual content may vary based on the API implementation. |
| `LISTENNOTES_FETCH_PODCAST_EPISODES_BY_ID` | Fetch Podcast Details And Episodes | Retrieves detailed information about a specific podcast using its unique identifier. This endpoint allows users to fetch comprehensive data about a particular podcast, including but not limited to its title, description, author, episode list, and other metadata. It's ideal for applications that need to display in-depth information about a single podcast or integrate podcast data into their systems. The endpoint should be used when detailed information about a known podcast is required, rather than for searching or browsing multiple podcasts. Note that this endpoint only provides information for podcasts that exist within the Listen Notes database and may not include real-time updates for very recent changes to the podcast. |
| `LISTENNOTES_FETCH_PODCAST_LANGUAGES` | Fetch podcast languages | Retrieves a list of supported languages in the Listen Notes API. This endpoint provides information about the languages available for use within the platform, which can be crucial for internationalization and localization efforts. Use this endpoint when you need to know which languages are supported by the API, such as for filtering content or setting user preferences. The response likely includes language codes and possibly their corresponding names or additional metadata. Note that this endpoint doesn't accept any parameters, offering a static list of supported languages. |
| `LISTENNOTES_FETCH_PODCASTS_BY_A_PUBLISHER_S_DOMAIN_NAME` | Get podcast domains by name | Retrieves a list of podcasts associated with a specified domain name. This endpoint allows users to discover podcasts that are produced or hosted by a particular website or organization. It's useful for content aggregation, competitive analysis, or finding podcasts related to specific brands or companies. The tool returns podcast information for the given domain, which may include multiple podcasts if the domain produces several shows. It should be used when you need to find all podcasts linked to a specific web domain. Note that this endpoint may not capture podcasts that are hosted on general podcast platforms unless they have a custom domain setup. |
| `LISTENNOTES_FETCH_RECOMMENDATIONS_FOR_AN_EPISODE` | Get episode recommendations by id | Retrieves a list of recommended podcast episodes based on a specific episode ID. This endpoint utilizes the Listen Notes API to generate personalized content suggestions, helping users discover new episodes similar to ones they've already enjoyed. It's ideal for enhancing user engagement and content discovery within podcast applications. The recommendations are likely based on factors such as episode content, listener behavior, and popularity. Use this endpoint when you want to provide users with tailored podcast suggestions or implement a "You might also like" feature. Note that the quality and relevance of recommendations may vary depending on the popularity and metadata of the source episode. |
| `LISTENNOTES_FETCH_RECOMMENDATIONS_FOR_A_PODCAST` | Get podcast recommendations by id | Retrieves a list of podcast recommendations based on a specified podcast ID. This endpoint is designed to help users discover new podcasts that are similar or related to a podcast they already enjoy. It can be used to enhance user experience by providing personalized content suggestions, increasing engagement, and broadening the listener's podcast library. The recommendations are likely based on factors such as genre, topics, popularity, and listening patterns of users with similar interests. While this tool is excellent for content discovery, it should not be relied upon for comprehensive podcast information or metadata retrieval. |
| `LISTENNOTES_FETCH_RELATED_SEARCH_TERMS` | Fetch related searches data | Retrieves a list of related search queries based on the current context or user's recent search activity. This endpoint is useful for enhancing user experience by suggesting alternative or complementary search terms, potentially increasing engagement and discovery within the platform. It should be used when you want to provide users with additional search options or ideas related to their current interests or queries. The endpoint does not require any explicit parameters, relying instead on server-side logic to determine the context for generating related searches. Note that the specific algorithm for generating related searches and the format of the response are not detailed in the schema, so integrators should be prepared to handle various response structures. |
| `LISTENNOTES_FETCH_SUPPORTED_REGIONS` | Fetch Supported Regions | Retrieves information about available regions in the listennotestest platform. This endpoint allows users to fetch a list of regions, which can be used for filtering content, setting up notifications, or managing regional preferences. It should be used when the client needs to know what regions are supported by the platform or when regional data is required for other operations. The endpoint returns basic details about each region, potentially including region codes, names, and any associated metadata. It does not provide detailed statistics or real-time data about events within these regions. |
| `LISTENNOTES_FETCH_TRENDING_SEARCH_TERMS` | Retrieve trending searches | Retrieves a list of currently trending search terms related to podcasts. This endpoint provides real-time insights into popular topics and interests among podcast listeners. It's particularly useful for content creators, marketers, and researchers who want to stay updated on current trends in the podcast industry. The endpoint returns a curated list of search terms, likely ranked by popularity or recent search volume. While it offers valuable trend data, it does not provide detailed analytics or historical trend information. Users should be aware that the trending searches may change frequently and might be influenced by various factors such as current events, seasonal topics, or platform-specific promotions. |
| `LISTENNOTES_FIND_EPISODES_BY_TITLE` | Search Episode Titles | The search_episode_titles endpoint allows users to search for and retrieve episode titles based on specified criteria. It provides a powerful way to find relevant episodes quickly, supporting real-time updates and notifications for changes in episode titles. This endpoint is particularly useful for applications that need to display or monitor podcast content, offering up-to-date information on episode titles across various shows or categories. While it excels at title searches, it may not provide full episode details or content, focusing primarily on the title information. |
| `LISTENNOTES_FULL_TEXT_SEARCH` | Search operation endpoint | The search endpoint allows users to query notifications or events within the listennotestest platform. It provides a way to find specific notifications based on keywords or identifiers, helping users locate relevant information quickly. This GET request likely returns a list of matching notifications or events, which can be useful for monitoring specific activities or retrieving historical data. The endpoint supports basic search functionality and potentially includes options for pagination and sorting to manage large result sets efficiently. |
| `LISTENNOTES_GET_PLAYLIST_BY_ID` | Fetch Playlist Info | Retrieves detailed information about a specific playlist using its unique identifier. This endpoint allows users to fetch comprehensive data about a playlist, including its name, description, creator, tracks, and other relevant metadata. It's particularly useful when you need to display or process information about a known playlist. The endpoint should be used when you have the playlist's ID and require its current, up-to-date details. Note that this endpoint only provides read access to playlist data and does not allow for modifications. It may not include real-time listening statistics or user-specific interaction data with the playlist. |
| `LISTENNOTES_REFRESH_RSS_FEED_OF_A_PODCAST` | Post podcast rss by id | Retrieves or generates an RSS feed for a specific podcast identified by its unique ID. This endpoint allows users to access the podcast's content in a standardized RSS format, which can be used for syndication or integration with podcast players and aggregators. The RSS feed typically includes information such as episode titles, descriptions, publication dates, and audio file URLs. Use this endpoint when you need to programmatically access a podcast's RSS feed, for example, to set up notifications for new episodes or to integrate the podcast content into another application. Note that this endpoint does not create or modify the podcast itself; it only provides access to the existing podcast data in RSS format. |
| `LISTENNOTES_REQUEST_TO_DELETE_A_PODCAST` | Delete podcast by id | Deletes a specific podcast from the system based on its unique identifier. This endpoint should be used when you want to permanently remove a podcast and all its associated data from the platform. It's important to note that this action is irreversible, so it should be used with caution. Once a podcast is deleted, it cannot be recovered, and all related notifications and updates for that podcast will cease. This endpoint is typically used for managing outdated content, removing test data, or complying with content removal requests. |
| `LISTENNOTES_SPELL_CHECK_ON_A_SEARCH_TERM` | Spell check retrieval | The spellcheck endpoint provides a spell-checking service for text input. It allows users to verify the spelling of words or phrases, likely related to audio content or podcast descriptions within the listennotestest platform. This tool should be used when there's a need to ensure the correctness of textual content, such as titles, descriptions, or tags associated with audio files or podcasts. The endpoint doesn't specify any parameters, suggesting it might use query parameters for input or have a default behavior. It's important to note that without more specific information, the exact input method and response format are not clear, which may require additional documentation or testing to fully understand its usage. |
| `LISTENNOTES_SUBMIT_A_PODCAST_TO_LISTEN_NOTES_DATABASE` | Submit podcast rss url | The submit_podcast endpoint allows users to submit a podcast for inclusion in the Listen Notes database. This tool should be used when a user wants to add a new podcast to the Listen Notes platform or update an existing podcast's information. It requires the RSS feed URL of the podcast and optionally accepts an email address for status notifications. The endpoint is designed for simplicity, focusing solely on podcast submission without providing additional podcast management features. Note that submission does not guarantee immediate inclusion; podcasts are subject to review before being added to the database. |
| `LISTENNOTES_TYPEAHEAD_SEARCH` | Get typeahead suggestions | The typeahead endpoint provides real-time search suggestions as users type their queries. It's designed to enhance the user experience by offering relevant autocomplete options, making it easier for users to find podcasts, episodes, or other content on the Listen Notes platform. This endpoint should be used to implement a dynamic search box that updates suggestions with each keystroke. It's particularly useful for improving search efficiency and accuracy by guiding users towards valid search terms. The endpoint likely returns a JSON array of matching results, which may include basic information like titles, IDs, and possibly thumbnail images for each suggestion. It's important to note that this endpoint is intended for quick, lightweight suggestions and may not provide full details about each result - for comprehensive information, additional API calls might be necessary. |

## Supported Triggers

None listed.

## Creating MCP Server - Stand-alone vs Composio SDK

The Listennotes MCP server is an implementation of the Model Context Protocol that connects your AI agent to Listennotes. It provides structured and secure access so your agent can perform Listennotes operations on your behalf through a secure, permission-based interface.
With Composio's managed implementation, you don't have to create your own developer app. For production, if you're building an end product, we recommend using your own credentials. The managed server helps you prototype fast and go from 0-1 faster.

## Step-by-step Guide

### 1. 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 Listennotes account and project
- Basic familiarity with async Python/Typescript

### 1. Getting API Keys for OpenAI, Composio, and Listennotes

No description provided.

### 2. Installing dependencies

No description provided.
```python
pip install composio-llamaindex llama-index llama-index-llms-openai llama-index-tools-mcp python-dotenv
```

```typescript
npm install @composio/llamaindex @llamaindex/openai @llamaindex/tools @llamaindex/workflow dotenv
```

### 3. Set environment variables

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 Listennotes access
```bash
OPENAI_API_KEY=your-openai-api-key
COMPOSIO_API_KEY=your-composio-api-key
COMPOSIO_USER_ID=your-user-id
```

### 4. Import modules

No description provided.
```python
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()
```

```typescript
import "dotenv/config";
import readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";

import { Composio } from "@composio/core";

import { mcp } from "@llamaindex/tools";
import { agent as createAgent } from "@llamaindex/workflow";
import { openai } from "@llamaindex/openai";

dotenv.config();
```

### 5. Load environment variables and initialize Composio

No description provided.
```python
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")
```

```typescript
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const COMPOSIO_API_KEY = process.env.COMPOSIO_API_KEY;
const COMPOSIO_USER_ID = process.env.COMPOSIO_USER_ID;

if (!OPENAI_API_KEY) throw new Error("OPENAI_API_KEY is not set");
if (!COMPOSIO_API_KEY) throw new Error("COMPOSIO_API_KEY is not set");
if (!COMPOSIO_USER_ID) throw new Error("COMPOSIO_USER_ID is not set");
```

### 6. Create a Tool Router session and build the agent function

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, listennotes)
- 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 Listennotes tools.
- The MCP tools are mapped to LlamaIndex-compatible tools and plug them into the Agent.
```python
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=["listennotes"],
    )

    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 Listennotes actions."
    system_prompt = """
    You are a helpful assistant connected to Composio Tool Router.
    Use the available tools to answer user queries and perform Listennotes actions.
    """
    return ReActAgent(tools=tools, llm=llm, description=description, system_prompt=system_prompt, verbose=True)
```

```typescript
async function buildAgent() {

  console.log(`Initializing Composio client...${COMPOSIO_USER_ID!}...`);
  console.log(`COMPOSIO_USER_ID: ${COMPOSIO_USER_ID!}...`);

  const composio = new Composio({
    apiKey: COMPOSIO_API_KEY,
    provider: new LlamaindexProvider(),
  });

  const session = await composio.create(
    COMPOSIO_USER_ID!,
    {
      toolkits: ["listennotes"],
    },
  );

  const mcpUrl = session.mcp.url;
  console.log(`Composio Tool Router MCP URL: ${mcpUrl}`);

  const server = mcp({
    url: mcpUrl,
    clientName: "composio_tool_router_with_llamaindex",
    requestInit: {
      headers: {
        "x-api-key": COMPOSIO_API_KEY!,
      },
    },
    // verbose: true,
  });

  const tools = await server.tools();

  const llm = openai({ apiKey: OPENAI_API_KEY, model: "gpt-5" });

  const agent = createAgent({
    name: "composio_tool_router_with_llamaindex",
        description : "An agent that uses Composio Tool Router MCP tools to perform actions.",
    systemPrompt:
      "You are a helpful assistant connected to Composio Tool Router."+
"Use the available tools to answer user queries and perform Listennotes actions." ,
    llm,
    tools,
  });

  return agent;
}
```

### 7. Create an interactive chat loop

No description provided.
```python
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}")
```

```typescript
async function chatLoop(agent: ReturnType<typeof createAgent>) {
  const rl = readline.createInterface({ input, output });

  console.log("Type 'quit' or 'exit' to stop.");

  while (true) {
    let userInput: string;

    try {
      userInput = (await rl.question("\nYou: ")).trim();
    } catch {
      console.log("\nAgent: Bye!");
      break;
    }

    if (!userInput) {
      continue;
    }

    const lower = userInput.toLowerCase();
    if (lower === "quit" || lower === "exit") {
      console.log("Agent: Bye!");
      break;
    }

    try {
      process.stdout.write("Agent: ");

      const stream = agent.runStream(userInput);
      let finalResult: any = null;

      for await (const event of stream) {
        // The event.data contains the streamed content
        const data: any = event.data;

        // Check for streaming delta content
        if (data?.delta) {
          process.stdout.write(data.delta);
        }

        // Store final result for fallback
        if (data?.result || data?.message) {
          finalResult = data;
        }
      }

      // If no streaming happened, show the final result
      if (finalResult) {
        const answer =
          finalResult.result ??
          finalResult.message?.content ??
          finalResult.message ??
          "";
        if (answer && typeof answer === "string" && !answer.includes("[object")) {
          process.stdout.write(answer);
        }
      }

      console.log(); // New line after streaming completes
    } catch (err: any) {
      console.error("\nAgent error:", err?.message ?? err);
    }
  }

  rl.close();
}
```

### 8. Define the main entry point

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 Listennotes
```python
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!")
```

```typescript
async function main() {
  try {
    const agent = await buildAgent();
    await chatLoop(agent);
  } catch (err) {
    console.error("Failed to start agent:", err);
    process.exit(1);
  }
}

main();
```

### 9. Run the agent

When prompted, authenticate and authorise your agent with Listennotes, then start asking questions.
```bash
python llamaindex_agent.py
```

```typescript
npx ts-node llamaindex-agent.ts
```

## Complete Code

```python
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=["listennotes"],
    )

    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 Listennotes actions."
    system_prompt = """
    You are a helpful assistant connected to Composio Tool Router.
    Use the available tools to answer user queries and perform Listennotes 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!")
```

```typescript
import "dotenv/config";
import readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";

import { Composio } from "@composio/core";
import { LlamaindexProvider } from "@composio/llamaindex";

import { mcp } from "@llamaindex/tools";
import { agent as createAgent } from "@llamaindex/workflow";
import { openai } from "@llamaindex/openai";

dotenv.config();

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const COMPOSIO_API_KEY = process.env.COMPOSIO_API_KEY;
const COMPOSIO_USER_ID = process.env.COMPOSIO_USER_ID;

if (!OPENAI_API_KEY) {
    throw new Error("OPENAI_API_KEY is not set in the environment");
  }
if (!COMPOSIO_API_KEY) {
    throw new Error("COMPOSIO_API_KEY is not set in the environment");
  }
if (!COMPOSIO_USER_ID) {
    throw new Error("COMPOSIO_USER_ID is not set in the environment");
  }

async function buildAgent() {

  console.log(`Initializing Composio client...${COMPOSIO_USER_ID!}...`);
  console.log(`COMPOSIO_USER_ID: ${COMPOSIO_USER_ID!}...`);

  const composio = new Composio({
    apiKey: COMPOSIO_API_KEY,
    provider: new LlamaindexProvider(),
  });

  const session = await composio.create(
    COMPOSIO_USER_ID!,
    {
      toolkits: ["listennotes"],
    },
  );

  const mcpUrl = session.mcp.url;
  console.log(`Composio Tool Router MCP URL: ${mcpUrl}`);

  const server = mcp({
    url: mcpUrl,
    clientName: "composio_tool_router_with_llamaindex",
    requestInit: {
      headers: {
        "x-api-key": COMPOSIO_API_KEY!,
      },
    },
    // verbose: true,
  });

  const tools = await server.tools();

  const llm = openai({ apiKey: OPENAI_API_KEY, model: "gpt-5" });

  const agent = createAgent({
    name: "composio_tool_router_with_llamaindex",
    description:
      "An agent that uses Composio Tool Router MCP tools to perform actions.",
    systemPrompt:
      "You are a helpful assistant connected to Composio Tool Router."+
"Use the available tools to answer user queries and perform Listennotes actions." ,
    llm,
    tools,
  });

  return agent;
}

async function chatLoop(agent: ReturnType<typeof createAgent>) {
  const rl = readline.createInterface({ input, output });

  console.log("Type 'quit' or 'exit' to stop.");

  while (true) {
    let userInput: string;

    try {
      userInput = (await rl.question("\nYou: ")).trim();
    } catch {
      console.log("\nAgent: Bye!");
      break;
    }

    if (!userInput) {
      continue;
    }

    const lower = userInput.toLowerCase();
    if (lower === "quit" || lower === "exit") {
      console.log("Agent: Bye!");
      break;
    }

    try {
      process.stdout.write("Agent: ");

      const stream = agent.runStream(userInput);
      let finalResult: any = null;

      for await (const event of stream) {
        // The event.data contains the streamed content
        const data: any = event.data;

        // Check for streaming delta content
        if (data?.delta) {
          process.stdout.write(data.delta);
        }

        // Store final result for fallback
        if (data?.result || data?.message) {
          finalResult = data;
        }
      }

      // If no streaming happened, show the final result
      if (finalResult) {
        const answer =
          finalResult.result ??
          finalResult.message?.content ??
          finalResult.message ??
          "";
        if (answer && typeof answer === "string" && !answer.includes("[object")) {
          process.stdout.write(answer);
        }
      }

      console.log(); // New line after streaming completes
    } catch (err: any) {
      console.error("\nAgent error:", err?.message ?? err);
    }
  }

  rl.close();
}

async function main() {
  try {
    const agent = await buildAgent();
    await chatLoop(agent);
  } catch (err: any) {
    console.error("Failed to start agent:", err?.message ?? err);
    process.exit(1);
  }
}

main();
```

## Conclusion

You've successfully connected Listennotes to LlamaIndex through Composio's Tool Router MCP layer.
Key takeaways:
- Tool Router dynamically exposes Listennotes 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 Listennotes MCP Agent with another framework

- [OpenAI Agents SDK](https://composio.dev/toolkits/listennotes/framework/open-ai-agents-sdk)
- [Claude Agent SDK](https://composio.dev/toolkits/listennotes/framework/claude-agents-sdk)
- [Claude Code](https://composio.dev/toolkits/listennotes/framework/claude-code)
- [Claude Cowork](https://composio.dev/toolkits/listennotes/framework/claude-cowork)
- [Codex](https://composio.dev/toolkits/listennotes/framework/codex)
- [OpenClaw](https://composio.dev/toolkits/listennotes/framework/openclaw)
- [Hermes](https://composio.dev/toolkits/listennotes/framework/hermes-agent)
- [CLI](https://composio.dev/toolkits/listennotes/framework/cli)
- [Google ADK](https://composio.dev/toolkits/listennotes/framework/google-adk)
- [LangChain](https://composio.dev/toolkits/listennotes/framework/langchain)
- [Vercel AI SDK](https://composio.dev/toolkits/listennotes/framework/ai-sdk)
- [Mastra AI](https://composio.dev/toolkits/listennotes/framework/mastra-ai)
- [CrewAI](https://composio.dev/toolkits/listennotes/framework/crew-ai)

## Related Toolkits

- [Youtube](https://composio.dev/toolkits/youtube) - YouTube is a leading video-sharing platform for uploading, streaming, and discovering content. It empowers creators and businesses to reach global audiences and monetize their work.
- [Amara](https://composio.dev/toolkits/amara) - Amara is a collaborative platform for creating and managing subtitles and captions for videos. It helps make content accessible and multilingual for global audiences.
- [Cats](https://composio.dev/toolkits/cats) - Cats is an API with a huge library of cat images, breed data, and cat facts. It makes finding adorable cat photos and trivia effortless for your apps and users.
- [Chatfai](https://composio.dev/toolkits/chatfai) - Chatfai is an AI platform that lets users talk to AI versions of fictional characters from books, movies, and games. It offers an engaging, interactive experience for fans to chat, roleplay, and explore creative dialogues.
- [Cincopa](https://composio.dev/toolkits/cincopa) - Cincopa is a multimedia platform for uploading, managing, and customizing videos, images, and audio. It helps you deliver engaging media experiences with robust APIs and flexible integrations.
- [Dungeon fighter online](https://composio.dev/toolkits/dungeon_fighter_online) - Dungeon Fighter Online (DFO) is an arcade-style, side-scrolling action RPG packed with dynamic combat and progression. Play solo or with friends to battle monsters, complete quests, and upgrade your characters.
- [Elevenlabs](https://composio.dev/toolkits/elevenlabs) - Elevenlabs is an advanced AI voice generation platform for lifelike, multilingual speech synthesis. Perfect for creating natural voices for videos, apps, and business content in seconds.
- [Elevenreader](https://composio.dev/toolkits/elevenreader) - Elevenreader is an AI-powered text-to-speech service by ElevenLabs that converts written content into lifelike audio. It enables fast, natural audio generation from any text.
- [Epic games](https://composio.dev/toolkits/epic_games) - Epic Games is a leading video game publisher and digital storefront, known for Fortnite and Unreal Engine. It lets gamers access, manage, and purchase games all in one place.
- [Fal.ai](https://composio.dev/toolkits/fal_ai) - Fal.ai is a generative media platform offering 600+ AI models for images, video, voice, and audio. Developers use Fal.ai for fast, scalable access to cutting-edge generative AI tools.
- [Giphy](https://composio.dev/toolkits/giphy) - Giphy is the largest online library for searching and sharing GIFs and stickers. Instantly add vibrant animated content to your apps, chats, and workflows.
- [Headout](https://composio.dev/toolkits/headout) - Headout is a global platform for booking travel experiences, tours, and entertainment. It helps users discover and secure activities at top destinations, all in one place.
- [Imagekit io](https://composio.dev/toolkits/imagekit_io) - ImageKit.io is a cloud-based media management platform for image and video delivery. Instantly optimize, transform, and deliver visuals globally via a lightning-fast CDN.
- [News api](https://composio.dev/toolkits/news_api) - News api is a REST API for searching and retrieving live news articles from across the web. Instantly access headlines, coverage, and breaking stories from thousands of sources.
- [RAWG Video Games Database](https://composio.dev/toolkits/rawg_video_games_database) - RAWG Video Games Database is the largest video game discovery and info service. Instantly access comprehensive details, ratings, and release dates for thousands of games.
- [Seat geek](https://composio.dev/toolkits/seat_geek) - SeatGeek is a live event platform offering APIs for concerts, sports, and theater data. Instantly access events, venues, and performers info for smarter ticketing and discovery.
- [Shotstack](https://composio.dev/toolkits/shotstack) - Shotstack is a cloud platform for programmatically generating videos, images, and audio. Automate creative content production at scale with flexible RESTful APIs.
- [Spotify](https://composio.dev/toolkits/spotify) - Spotify is a streaming service for music and podcasts with millions of tracks from artists worldwide. Enjoy personalized playlists, recommendations, and seamless listening across all your devices.
- [Ticketmaster](https://composio.dev/toolkits/ticketmaster) - Ticketmaster is a global platform for event discovery, ticket sales, and live entertainment management. Get real-time access to events and streamline ticketing for fans and organizers.
- [Gmail](https://composio.dev/toolkits/gmail) - Gmail is Google's email service with powerful spam protection, search, and G Suite integration. It keeps your inbox organized and makes communication fast and reliable.

## Frequently Asked Questions

### What are the differences in Tool Router MCP and Listennotes MCP?

With a standalone Listennotes MCP server, the agents and LLMs can only access a fixed set of Listennotes tools tied to that server. However, with the Composio Tool Router, agents can dynamically load tools from Listennotes 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 Listennotes tools.

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

Yes, absolutely. You can configure which Listennotes 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 Listennotes data and credentials are handled as safely as possible.

---
[See all toolkits](https://composio.dev/toolkits) · [Composio docs](https://docs.composio.dev/llms.txt)
