# How to integrate Canva MCP with LlamaIndex

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

## Introduction

This guide walks you through connecting Canva to LlamaIndex using the Composio tool router. By the end, you'll have a working Canva agent that can create a new instagram post design, list your brand templates for social use, start a folder for this project’s assets through natural language commands.
This guide will help you understand how to give your LlamaIndex agent real control over a Canva account through Composio's Canva MCP server.
Before we dive in, let's take a quick look at the key ideas and tools involved.

## Also integrate Canva with

- [ChatGPT](https://composio.dev/toolkits/canva/framework/chatgpt)
- [Antigravity](https://composio.dev/toolkits/canva/framework/antigravity)
- [OpenAI Agents SDK](https://composio.dev/toolkits/canva/framework/open-ai-agents-sdk)
- [Claude Agent SDK](https://composio.dev/toolkits/canva/framework/claude-agents-sdk)
- [Claude Code](https://composio.dev/toolkits/canva/framework/claude-code)
- [Claude Cowork](https://composio.dev/toolkits/canva/framework/claude-cowork)
- [Codex](https://composio.dev/toolkits/canva/framework/codex)
- [Cursor](https://composio.dev/toolkits/canva/framework/cursor)
- [VS Code](https://composio.dev/toolkits/canva/framework/vscode)
- [OpenCode](https://composio.dev/toolkits/canva/framework/opencode)
- [OpenClaw](https://composio.dev/toolkits/canva/framework/openclaw)
- [Hermes](https://composio.dev/toolkits/canva/framework/hermes-agent)
- [CLI](https://composio.dev/toolkits/canva/framework/cli)
- [Google ADK](https://composio.dev/toolkits/canva/framework/google-adk)
- [LangChain](https://composio.dev/toolkits/canva/framework/langchain)
- [Vercel AI SDK](https://composio.dev/toolkits/canva/framework/ai-sdk)
- [Mastra AI](https://composio.dev/toolkits/canva/framework/mastra-ai)
- [CrewAI](https://composio.dev/toolkits/canva/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 Canva
- Connect LlamaIndex to the Canva MCP server
- Build a Canva-powered agent using LlamaIndex
- Interact with Canva 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 Canva MCP server, and what's possible with it?

The Canva MCP server is an implementation of the Model Context Protocol that connects your AI agent and assistants like Claude, Cursor, etc directly to your Canva account. It provides structured and secure access to your Canva designs, templates, folders, assets, and user details, so your agent can create designs, organize projects, manage assets, and collaborate on feedback for you.
- Automated design creation and asset integration: Direct your agent to generate new Canva designs using templates or custom dimensions, and add assets from your projects automatically.
- Seamless folder and project organization: Have the agent create user or subfolders to keep your Canva projects structured and easily accessible.
- Asset management and cleanup: Let your agent fetch upload statuses, manage, or delete assets by ID, helping you keep your design library up to date.
- Collaborative design feedback: Empower your agent to add comments or reply within designs, making it easy to facilitate feedback and teamwork directly in Canva.
- User and team information retrieval: Quickly obtain user or team details, allowing your agent to personalize interactions and automate workflows based on your Canva account info.

## Supported Tools

| Tool slug | Name | Description |
|---|---|---|
| `CANVA_ACCESS_USER_SPECIFIC_BRAND_TEMPLATES_LIST` | Access user specific brand templates list | Lists brand templates available to the user (Canva Enterprise only). This endpoint retrieves all brand templates the user has access to. Brand templates are pre-designed layouts with variable data fields that can be populated programmatically using the autofill API. Returns template metadata including IDs, names, and dataset schemas. |
| `CANVA_CREATE_ASSET_UPLOAD_JOB` | Create Asset Upload Job | Uploads an asset file to the user's Canva content library. This endpoint initiates an asynchronous upload job for images, videos, audio files, PDFs, or fonts. Returns a job ID to track the upload progress. Once complete, the asset can be used in designs, referenced by its asset ID, and managed through other asset endpoints. IMPORTANT: This is an async operation. Use CANVA_FETCH_ASSET_UPLOAD_JOB_STATUS to poll the job status until it reaches 'success' or 'failed' status. |
| `CANVA_CREATE_COMMENT_REPLY_IN_DESIGN` | Create comment reply in design | This preview API allows replying to comments within a design on Canva, with a limit of 100 replies per comment. Users should note potential unannounced changes, and that preview APIs are not eligible for public integrations. |
| `CANVA_CREATE_DESIGN_COMMENT_IN_PREVIEW_API` | Create design comment in preview api | Creates a new comment thread on a Canva design. This preview API allows adding text comments to designs for collaboration and feedback. Returns the created comment with ID, author info, and timestamp. Comments can be replied to using the comment reply endpoint. |
| `CANVA_CREATE_DESIGN_IMPORT_JOB` | Create Design Import Job | Imports an external file as a new Canva design. This endpoint converts documents (PDF, Word, PowerPoint, Excel) and design files (PSD, AI) into editable Canva designs. The import runs asynchronously and returns a job ID to track progress and retrieve the created design. IMPORTANT: This is an async operation. Use CANVA_RETRIEVE_DESIGN_IMPORT_JOB_STATUS to poll the job status until it reaches 'success' or 'failed' to get the final design ID. |
| `CANVA_CREATE_DESIGN_RESIZE_JOB` | Create Design Resize Job | Creates a resized copy of an existing design (Canva Pro/Enterprise only). This endpoint creates a new design with different dimensions from an existing one. The resize operation runs asynchronously and preserves content where possible. Returns a job ID to track progress and retrieve the new design once complete. IMPORTANT: This is an async operation. Use CANVA_RETRIEVE_DESIGN_RESIZE_JOB_STATUS to poll the job status until completion to get the resized design ID and access URLs. |
| `CANVA_CREATE_URL_ASSET_UPLOAD_JOB` | Create URL Asset Upload Job | Tool to create an asynchronous Canva asset import job from a public URL. Use when you need to import an asset into Canva directly from a publicly accessible URL without S3 choreography. |
| `CANVA_DELETE_ASSET_BY_ID` | Delete asset by id | You can delete an asset by specifying its `assetId`. This operation mirrors the behavior in the Canva UI. Deleting an item moves it to the trash. Deleting an asset doesn't remove it from designs that already use it. |
| `CANVA_EXCHANGE_OAUTH20_ACCESS_OR_REFRESH_TOKEN` | Exchange oauth 2 0 access or refresh token | The OAuth 2.0 endpoint issues time-limited (4-hour) access tokens of up to 4KB for user authorization via codes or refresh tokens. It requires client ID/secret for authentication. |
| `CANVA_FETCH_ASSET_UPLOAD_JOB_STATUS` | Fetch asset upload job status | Polls for asset upload job completion status. Use this after CANVA_CREATE_ASSET_UPLOAD_JOB to check the upload progress. Repeatedly call this endpoint until a 'success' or 'failed' status is received to get the final asset ID and metadata. IMPORTANT: This tool is ONLY for direct file upload jobs (CANVA_CREATE_ASSET_UPLOAD_JOB). Do NOT use this for URL import jobs created by CANVA_CREATE_URL_ASSET_UPLOAD_JOB - those use a different API endpoint. Use CANVA_GET_URL_ASSET_UPLOADS_JOBID to poll URL import job status. |
| `CANVA_FETCH_CANVA_CONNECT_SIGNING_PUBLIC_KEYS` | Fetch canva connect signing public keys | The API for verifying Canva webhooks, 'connect/keys,' is in preview, meaning unstable, not for public integrations, and provides a rotating JWK for signature verification to prevent replay attacks. Cache keys for efficiency. |
| `CANVA_FETCH_CURRENT_USER_DETAILS` | Fetch current user details | Returns the User ID, Team ID, and display name of the user account associated with the provided access token. |
| `CANVA_FETCH_DESIGN_METADATA_AND_ACCESS_INFORMATION` | Fetch design metadata and access information | Gets the metadata for a design. This includes owner information, URLs for editing and viewing, and thumbnail information. |
| `CANVA_GET_DESIGN_COMMENT_REPLIES` | Get design comment thread replies | Retrieves a list of replies for a comment or suggestion thread on a design. Use when you need to view all replies in a specific thread. |
| `CANVA_GET_DESIGN_COMMENT_REPLY` | Get specific design comment reply | Retrieves a specific reply to a comment or suggestion thread on a design. Use when you need to view details of a particular reply. |
| `CANVA_GET_DESIGN_EXPORT_JOB_RESULT` | Get design export job result | Polls for design export job completion status. Use this after CANVA_CREATE_CANVA_DESIGN_EXPORT_JOB to check the export progress. Call this endpoint repeatedly until the job is complete to receive download links for the exported design pages. Download URLs expire after 30 days. |
| `CANVA_GET_DESIGNS_DESIGNID_COMMENTS_THREADID` | Get designs designid comments threadid | Retrieves metadata for a comment or suggestion thread on a design. Use when you need to get details about a specific thread including content, author, timestamps, and status. |
| `CANVA_GET_DESIGNS_DESIGNID_EXPORT_FORMATS` | Get design export formats | Lists available file formats for exporting a design. Use this to check which export formats (PDF, JPG, PNG, SVG, PPTX, GIF, MP4) are supported for a specific design. |
| `CANVA_GET_URL_ASSET_UPLOADS_JOBID` | Get URL asset upload job status | Tool to retrieve the status and result of a URL-based asset upload job. Use this after creating a URL asset upload job to check progress and retrieve the uploaded asset ID when successful. |
| `CANVA_GET_URL_IMPORTS_JOBID` | Get URL import job status | Polls for URL import job completion status. Use this after creating a URL import job to check the import progress. Repeatedly call this endpoint until a 'success' or 'failed' status is received to get the final imported design ID and metadata. |
| `CANVA_GET_USERS_ME_CAPABILITIES` | Get user capabilities | Lists the API capabilities for the user account associated with the provided access token. Use when you need to check what features are available based on the user's Canva plan. |
| `CANVA_INITIATE_CANVA_DESIGN_AUTOFILL_JOB` | Initiate canva design autofill job | Upcoming brand template ID updates require migration within 6 months. Canva Enterprise users can auto-fill designs using various data types, including experimental chart data. Monitor jobs with specific API. |
| `CANVA_LIST_DESIGN_PAGES_WITH_PAGINATION` | List design pages with pagination | Preview API for Canva: subject to unannounced changes and not for public integrations. Lists metadata for design pages with optional `offset` and `limit`; not applicable to all design types. |
| `CANVA_LIST_FOLDER_ITEMS_BY_TYPE_WITH_SORTING` | List folder items by type with sorting | Lists the items in a folder, including each item's `type`. Folders can contain: - Other folders. - Designs, such as Instagram posts, Presentations, and Documents ([Canva Docs](https://www.canva.com/create/documents/)). - Image assets. |
| `CANVA_LIST_USER_DESIGNS` | List User Designs | Provides a summary of Canva user designs, includes search filtering, and allows showing both self-created and shared designs with sorting options. |
| `CANVA_MOVE_ITEM_TO_SPECIFIED_FOLDER` | Move item to specified folder | Transfers an item to a different folder by specifying both the destination folder's ID and the item's ID. If the item is in various folders, an error occurs; manual relocation via Canva UI is required. |
| `CANVA_POST_DESIGNS` | Create new Canva design | Creates a new Canva design with preset type or custom dimensions. Use when creating new designs for docs, presentations, whiteboards, or with specific pixel dimensions. Optionally insert an image asset into the design. Returns design ID, edit/view URLs, and metadata. |
| `CANVA_POST_DESIGNS_DESIGNID_COMMENTS` | Post designs designid comments | Creates a comment thread on a Canva design. Use when you need to add feedback or collaboration comments to a specific design. Rate limited to 100 requests per minute per user. |
| `CANVA_POST_DESIGNS_DESIGNID_COMMENTS_THREADID_REPLIES` | Create reply to comment thread | Tool to create a reply to a comment or suggestion thread on a Canva design. Use when you need to respond to existing comments or suggestions. Maximum 100 replies per thread allowed. |
| `CANVA_POST_EXPORTS` | Start design export job | Starts a new asynchronous job to export a Canva design file. Use when exporting designs to various formats (PDF, JPG, PNG, GIF, PPTX, MP4). Returns a job ID that can be used to poll for completion status and download URLs. IMPORTANT: Format compatibility varies by design type. Before exporting, use the CANVA_GET_DESIGNS_DESIGNID_EXPORT_FORMATS action to check which formats are supported for the specific design. Attempting to export in an unsupported format will result in a 400 error (e.g., 'png export not supported for this design type'). |
| `CANVA_POST_FOLDERS` | Create folder | Tool to create a folder in Canva. Use when you need to organize designs, assets, or create nested folder structures in a user's projects or uploads folder. |
| `CANVA_POST_URL_IMPORTS` | Create URL Import Job | Tool to start an asynchronous job to import an external file from a URL as a new design in Canva. Use when you need to import a design from a publicly accessible URL. This is an async operation; poll the job status using the job ID until it reaches 'success' or 'failed'. |
| `CANVA_REMOVE_FOLDER_AND_MOVE_CONTENTS_TO_TRASH` | Remove folder and move contents to trash | Deletes a folder by moving the user's content to Trash and reassigning other users' content to their top-level projects. |
| `CANVA_RETRIEVE_APP_PUBLIC_KEY_SET` | Retrieve app public key set | Returns the Json Web Key Set (public keys) of an app. These keys are used to verify JWTs sent to app backends. |
| `CANVA_RETRIEVE_ASSET_METADATA_BY_ID` | Retrieve asset metadata by id | You can retrieve the metadata of an asset by specifying its `assetId`. |
| `CANVA_RETRIEVE_BRAND_TEMPLATE_DATASET_DEFINITION` | Retrieve brand template dataset definition | Canva's brand template IDs will change later this year, including a 6-month integration migration. API access requires Enterprise membership, providing autofill for images, text, and charts, although chart data could change during preview. |
| `CANVA_RETRIEVE_CANVA_ENTERPRISE_BRAND_TEMPLATE_METADATA` | Retrieve canva enterprise brand template metadata | Upcoming update will change brand template IDs; integrations must migrate within 6 months. API use requires Canva Enterprise membership. |
| `CANVA_RETRIEVE_DESIGN_AUTOFILL_JOB_STATUS` | Retrieve design autofill job status | API users with Canva Enterprise membership can retrieve design autofill job results, potentially requiring multiple requests until a `success` or `failed` status is received. |
| `CANVA_RETRIEVE_DESIGN_IMPORT_JOB_STATUS` | Retrieve design import job status | Polls for design import job completion status. Use this after CANVA_CREATE_DESIGN_IMPORT_JOB to check the import progress. Repeatedly call this endpoint until a 'success' or 'failed' status is received to get the final imported design ID and metadata. |
| `CANVA_RETRIEVE_DESIGN_RESIZE_JOB_STATUS` | Retrieve Design Resize Job Status | Retrieves the status and results of a design resize job. Polls for design resize job completion status. Use this after CANVA_CREATE_DESIGN_RESIZE_JOB to check the resize progress. Keep polling until status is 'success' or 'failed'. Successful jobs include the new design ID and temporary access URLs. |
| `CANVA_RETRIEVE_FOLDER_DETAILS_BY_ID` | Retrieve folder details by id | Gets the name and other details of a folder using a folder's `folderID`. |
| `CANVA_RETRIEVE_USER_PROFILE_DATA` | Retrieveuserprofiledata | Currently, this returns the display name of the user account associated with the provided access token. More user information is expected to be included in the future. |
| `CANVA_REVOKE_OAUTH_TOKENS` | Revoke oauth tokens | Revoke a refresh token to end its lineage and user consent, requiring re-authentication. Authenticate using either basic access with Base64-encoded credentials or body parameters with client ID and secret. |
| `CANVA_UPDATE_ASSET_S_NAME_AND_TAGS_BY_ID` | Update asset s name and tags by id | You can update the name and tags of an asset by specifying its `assetId`. Updating the tags replaces all existing tags of the asset. |
| `CANVA_UPDATE_FOLDER_DETAILS_BY_ID` | Update folder details by id | Updates a folder's details using its `folderID`. Currently, you can only update a folder's name. |
| `CANVA_VALIDATE_OAUTH_TOKEN_PROPERTIES` | Validate oauth token properties | Check an access token's validity and properties via introspection, requiring authentication. Use Basic access (Base64 encoded `client_id:client_secret`) or body parameters for credentialing. |

## Supported Triggers

None listed.

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

The Canva MCP server is an implementation of the Model Context Protocol that connects your AI agent to Canva. It provides structured and secure access so your agent can perform Canva 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 Canva account and project
- Basic familiarity with async Python/Typescript

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

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 Canva 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, canva)
- 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 Canva 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=["canva"],
    )

    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 Canva actions."
    system_prompt = """
    You are a helpful assistant connected to Composio Tool Router.
    Use the available tools to answer user queries and perform Canva 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: ["canva"],
    },
  );

  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 Canva 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 Canva
```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 Canva, 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=["canva"],
    )

    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 Canva actions."
    system_prompt = """
    You are a helpful assistant connected to Composio Tool Router.
    Use the available tools to answer user queries and perform Canva 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: ["canva"],
    },
  );

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

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

## Related Toolkits

- [Figma](https://composio.dev/toolkits/figma) - Figma is a collaborative interface design tool for teams and individuals. It streamlines design workflows with real-time collaboration and easy sharing.
- [Abyssale](https://composio.dev/toolkits/abyssale) - Abyssale is a creative automation platform for generating images, videos, GIFs, PDFs, and HTML5 content programmatically. It streamlines and scales visual content production for marketing, design, and operations teams.
- [Alttext ai](https://composio.dev/toolkits/alttext_ai) - AltText.ai is a service that generates alt text for images automatically. It helps boost accessibility and SEO for your visual content.
- [Bannerbear](https://composio.dev/toolkits/bannerbear) - Bannerbear is an API-driven platform for generating images and videos automatically at scale. It helps businesses create custom graphics, social visuals, and marketing assets using powerful templates.
- [Claid ai](https://composio.dev/toolkits/claid_ai) - Claid.ai delivers AI-driven image editing APIs for tasks like background removal, upscaling, and color correction. It helps automate and enhance image workflows with powerful, developer-friendly tools.
- [Cloudinary](https://composio.dev/toolkits/cloudinary) - Cloudinary is a cloud-based platform for managing, uploading, and transforming images and videos. It streamlines media workflows and delivers optimized assets globally.
- [Cults](https://composio.dev/toolkits/cults) - Cults is a digital marketplace for 3D printing models, connecting designers and makers. It lets creators share, sell, and discover a huge variety of printable designs easily.
- [DeepImage](https://composio.dev/toolkits/deepimage) - DeepImage is an AI-powered image enhancer and upscaler. Get higher-quality images with just a few clicks.
- [Dreamstudio](https://composio.dev/toolkits/dreamstudio) - DreamStudio is Stability AI’s platform for generating and editing images with AI. It lets you easily turn ideas into stunning visuals, fast.
- [Dynapictures](https://composio.dev/toolkits/dynapictures) - Dynapictures is a cloud-based platform for generating personalized images at scale. Instantly create hundreds of custom visuals using your data sources, like Google Sheets.
- [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.
- [Gamma](https://composio.dev/toolkits/gamma) - Gamma is an AI-powered platform for making beautiful, interactive presentations and documents. It lets anyone create and share engaging decks with minimal effort.
- [Html to image](https://composio.dev/toolkits/html_to_image) - Html to image converts HTML and CSS into images or captures web page screenshots. Instantly generate visuals from code or web content—no manual screenshots needed.
- [Imagior](https://composio.dev/toolkits/imagior) - Imagior is an AI-powered image generation platform that lets you create and customize images using dynamic templates and APIs. Perfect for businesses and creators needing fast, scalable visuals without design hassle.
- [Imejis io](https://composio.dev/toolkits/imejis_io) - Imejis io is an API-based image generation platform with powerful customization and template support. It lets you create and modify images in seconds, no manual design work required.
- [Imgix](https://composio.dev/toolkits/imgix) - Imgix is a real-time image processing and delivery service for developers. It helps you optimize, transform, and deliver images efficiently at any scale.
- [Kraken io](https://composio.dev/toolkits/kraken_io) - Kraken.io is an image optimization and compression platform. It helps you shrink image file sizes while keeping visual quality intact.
- [Logo dev](https://composio.dev/toolkits/logo_dev) - Logo.dev is an API and database for high-resolution company logos and brand metadata. Instantly fetch official logos from any domain without scraping or manual searching.
- [Miro](https://composio.dev/toolkits/miro) - Miro is a collaborative online whiteboard platform for teams to brainstorm, design, and manage projects visually. It streamlines teamwork by enabling real-time idea sharing, diagramming, and workflow planning in a single space.
- [Mural](https://composio.dev/toolkits/mural) - Mural is a digital whiteboard platform for distributed visual collaboration. It helps teams brainstorm, map ideas, and diagram together in real time.

## Frequently Asked Questions

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

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

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

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

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