# How to integrate Todoist MCP with LlamaIndex

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

## Introduction

This guide walks you through connecting Todoist to LlamaIndex using the Composio tool router. By the end, you'll have a working Todoist agent that can add a high-priority task for today, create a new project called 'team offsite', close all completed tasks from this week through natural language commands.
This guide will help you understand how to give your LlamaIndex agent real control over a Todoist account through Composio's Todoist MCP server.
Before we dive in, let's take a quick look at the key ideas and tools involved.

## Also integrate Todoist with

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

The Todoist MCP server is an implementation of the Model Context Protocol that connects your AI agent and assistants like Claude, Cursor, etc directly to your Todoist account. It provides structured and secure access to your tasks, projects, and labels, so your agent can create tasks, manage projects, add comments, organize sections, and update your to-do lists on your behalf.
- Task creation and scheduling: Instantly ask your agent to add new tasks with specific details, deadlines, priorities, or even as subtasks within projects or sections.
- Project and workspace management: Let your agent create, organize, or delete projects and workspaces to keep your productivity system tidy and up-to-date.
- Section and label organization: Direct your agent to create, delete, or update sections and labels, helping you structure your tasks and filter lists for better focus.
- Task completion and commenting: Have your agent mark tasks as complete or add helpful comments and notes to specific tasks or projects for seamless collaboration.
- Streamlined cleanup and maintenance: Empower your agent to remove unused projects, labels, or sections, ensuring your Todoist stays clutter-free and organized.

## Supported Tools

| Tool slug | Name | Description |
|---|---|---|
| `TODOIST_ADD_WORKSPACE` | Add Workspace | Tool to create a new workspace in Todoist. Use when you need a separate workspace to organize projects. Generates UUID and temp_id automatically. |
| `TODOIST_ARCHIVE_PROJECT2` | Archive Project (API v1) | Tool to archive a project using Todoist API v1. For personal projects, archives it for the initiating user. For workspace projects, archives it for all workspace users. |
| `TODOIST_BULK_CREATE_TASKS` | Bulk Create Tasks | Create many tasks in one request using Todoist's Sync batching. Use when scaffolding projects or creating multiple tasks at once to reduce round trips. |
| `TODOIST_CLOSE_TASK_V1` | Close Task (API v1) | Tool to close (complete) a task in Todoist using API v1. Use when you need to mark a task as complete using the v1 endpoint. |
| `TODOIST_CREATE_COMMENT_V1` | Create Comment (API v1) | Tool to create a new comment on a project or task using Todoist API v1. Use when you need to add a note to a specific task or project. IMPORTANT: You must provide EXACTLY ONE of task_id or project_id - these parameters are mutually exclusive. - To comment on a task, provide task_id (and omit project_id) - To comment on a project, provide project_id (and omit task_id) - Providing both task_id AND project_id will cause an error. |
| `TODOIST_CREATE_LABEL_V1` | Create Label (API v1) | Tool to create a new personal label using API v1. Use when you need to add a new label to organize tasks. |
| `TODOIST_CREATE_PROJECT2` | Create Project (API v1) | Tool to create a new project in Todoist using the unified API v1. Use when you need to create a new project with specific parameters like name, color, or parent project. |
| `TODOIST_CREATE_SECTION_V1` | Create Section (API v1) | Tool to create a new section within a project using API v1. Use when you need to organize tasks under a new heading in a project. |
| `TODOIST_CREATE_TASK` | Create task | Create a new task in Todoist using the unified API v1. This action allows users to create tasks with various parameters including content, due dates, priority, and more. The task can be created in a specific project, section, or as a subtask of another task. Bulk creation may trigger HTTP 429; honor Retry-After response headers. |
| `TODOIST_DELETE_COMMENT` | Delete Comment | Tool to delete a specific comment from Todoist by its ID. Use when you need to permanently remove a comment. |
| `TODOIST_DELETE_LABEL_V1` | Delete Label (V1) | Tool to delete a personal label using API v1. Use when you need to permanently remove a label by its ID. All instances of the label will be removed from tasks. |
| `TODOIST_DELETE_PROJECT2` | Delete Project (API v1) | Tool to delete a project and all of its sections and tasks using Todoist API v1. Use when you need to permanently remove a project by its ID. |
| `TODOIST_DELETE_SECTION2` | Delete Section (v1) | Tool to delete a section and all tasks within it. Use when you need to permanently remove a section by its ID. |
| `TODOIST_DELETE_TASK` | Delete Task | Tool to delete a specific task from Todoist. Permanently removes the task and all its subtasks with no recycle bin or undo option. Confirm with the user before executing, especially for bulk deletions. |
| `TODOIST_DELETE_UPLOAD` | Delete Upload | Tool to delete an uploaded file from Todoist. Use when you need to remove a file that was previously uploaded. |
| `TODOIST_EXPORT_TEMPLATE_AS_FILE` | Export Template As File | Tool to export a Todoist project as a CSV template file. Use when you need to download a project template as a file for backup or sharing. |
| `TODOIST_EXPORT_TEMPLATE_AS_URL` | Export Template As URL | Tool to export a Todoist project as a shareable template URL. Use when you need to share a project structure or create copies from a template. |
| `TODOIST_FILTER_TASKS` | Filter Tasks | Tool to get all tasks matching the filter. Use when you need to retrieve tasks based on specific filter criteria. This is a paginated endpoint using cursor-based pagination. |
| `TODOIST_GET_ALL_COMMENTS` | Get All Comments | This tool retrieves all comments associated with a specific task or project in Todoist. You must provide exactly one of task_id or project_id (they are mutually exclusive - do not provide both). Returns a JSON array of comment objects with details such as id, posted_at, content, and attachment information. |
| `TODOIST_GET_ALL_PROJECTS` | Get all projects | Get all projects from a user's Todoist account. Retrieves all active (non-archived) projects; use TODOIST_LIST_ARCHIVED_WORKSPACE_PROJECTS for archived ones. Response returns the list in the projects field, with fields including project_id, name, color, parent project, sharing status, and is_inbox_project flag. Always reference projects by project_id — names are non-unique and may be localized. Detect the Inbox via is_inbox_project, not by name. Results are a point-in-time snapshot; re-call after structural changes before relying on cached project_ids. In large accounts, paginate until no next-page token remains to avoid missing projects. Check existing projects before creating new ones to prevent duplicates. |
| `TODOIST_GET_ALL_TASKS` | Get All Tasks | Fetches all INCOMPLETE tasks from Todoist and returns their details. Supports cursor-based pagination. IMPORTANT LIMITATIONS: - This endpoint only returns active (incomplete) tasks - Cannot retrieve completed tasks - use a different endpoint for that - Filter queries using 'completed', '!completed', or 'completed after' will fail - Project/label references must use ACTUAL EXISTING names from the user's account - Arbitrary text in filters causes 400 errors - use 'search: keyword' for text search Common use cases: - Get all tasks: no filter - Get today's tasks: filter="today" - Get overdue tasks: filter="overdue" - Get tasks from specific project: filter="#ProjectName" (project must exist) - Get high priority tasks: filter="p1" - Search task content: filter="search: keyword" - Paginate results: use limit and cursor parameters |
| `TODOIST_GET_BACKUPS` | Get Backups | Tool to list all available backup archives for the user. Returns archive metadata only — not live task or project data; do not use as a proxy for active data availability. May return an empty list if no backups exist or backups are not enabled. For completed task history, use /sync/v9/completed/get_all instead. |
| `TODOIST_GET_COMMENT_V1` | Get Comment (V1) | Tool to retrieve a single comment by ID using the v1 API. Use when you need to fetch details of a specific comment. |
| `TODOIST_GET_COMPLETED_TASKS_BY_COMPLETION_DATE` | Get Completed Tasks By Completion Date | Tool to retrieve completed tasks within a specified completion date window. Use when you need to fetch historical completed tasks for reporting, audits, or weekly summaries. The API restricts the completion-date range to approximately 3 months between since and until parameters. |
| `TODOIST_GET_ID_MAPPINGS` | Get ID Mappings | Tool to translate IDs between Todoist API v1 and v2. Use when you need to convert IDs between API versions for sections, tasks, comments, reminders, location_reminders, or projects. |
| `TODOIST_GET_PERSONAL_LABEL` | Get Personal Label | Tool to retrieve a personal label by its ID. Use when you need to fetch details about a specific personal label. |
| `TODOIST_GET_PRODUCTIVITY_STATS` | Get Productivity Stats | Tool to retrieve comprehensive productivity statistics for the authenticated user. Use when you need detailed completion data, karma scores, streaks, or goal tracking information for productivity reports and analysis. |
| `TODOIST_GET_PROJECT` | Get Project (API v1) | Tool to retrieve a specific project by its ID using Todoist API v1. Use when you have a project ID and need its metadata before display or update. Verify project_id matches the intended project before destructive operations — similar project names can cause mistakes; use TODOIST_GET_ALL_PROJECTS to resolve ambiguous names to IDs first. |
| `TODOIST_GET_PROJECT_FULL` | Get Full Project Data | Tool to retrieve full project data including all sections, tasks, and collaborators. Use when you need comprehensive project information beyond basic metadata. |
| `TODOIST_GET_PROJECT_PERMISSIONS` | Get Project Permissions | Tool to retrieve all available roles and their associated actions in Todoist projects. Use when you need to understand what permissions different roles have in projects. |
| `TODOIST_GET_SECTION_V1` | Get Section (v1 API) | Tool to retrieve a specific section by its ID using Todoist v1 API. Use when you need section metadata from the v1 endpoint. |
| `TODOIST_GET_SPECIAL_BACKUPS` | Get Special Backups | Tool to list special backup archives for the authenticated user's projects. Returns an empty list if no backups exist — callers must not assume archives are present. Read-only: confirms archive visibility only, not live task or project data access. |
| `TODOIST_GET_TASK2` | Get Task (API v1) | Tool to retrieve a single active (non-completed) task by ID using API v1. Use when you need to fetch task details from the v1 endpoint. |
| `TODOIST_GET_USER` | Get User | Tool to retrieve information about the currently authenticated user. Use when you need user details like email, name, preferences, karma, or subscription status. |
| `TODOIST_GET_WORKSPACE_PLAN_DETAILS` | Get Workspace Plan Details | Tool to retrieve details about a workspace's current plan and usage. Use when you need information about subscription status, member count, project limits, pricing, or trial status for a specific workspace. |
| `TODOIST_IMPORT_TEMPLATE_INTO_PROJECT_BY_ID` | Import Template Into Project By ID | Tool to import a template from Todoist's template gallery into an existing project. Use when you need to add structured tasks and sections from a pre-made template to an existing project. |
| `TODOIST_IMPORT_TEMPLATE_INTO_PROJECT_FROM_FILE` | Import Template Into Project From File | Tool to import a CSV template into an existing Todoist project from a file. Use when you need to bulk-create tasks, sections, and notes from a template file. |
| `TODOIST_INVITE_PROJECT_COLLABORATOR` | Invite Project Collaborator | Tool to invite a collaborator to a Todoist project by email. Use when the user asks to share, invite someone to, or add a collaborator to a project. For workspace or team projects, you can optionally specify a role. If the role is omitted, the workspace default role is used. For invite-only workspace projects, the caller may need admin or owner permissions; otherwise the API may return a forbidden error. |
| `TODOIST_LIST_ACTIVITIES` | List Activities | Tool to get activity logs from Todoist. Returns a paginated list of activity events for the user. Events can be filtered by object type (project, item, note), event type, and other criteria. Uses cursor-based pagination for efficient navigation through results. |
| `TODOIST_LIST_ALL_INVITATIONS_WORKSPACES` | List All Workspace Invitations | Tool to return a list containing details of all pending invitations to a workspace. Use when you need to view all pending workspace invitations. This list is not paginated, and all workspace members can access it. |
| `TODOIST_LIST_ARCHIVED_PROJECTS` | List Archived Projects | Tool to get all archived projects from Todoist. Use when you need to retrieve archived projects with pagination support. |
| `TODOIST_LIST_ARCHIVED_SECTIONS` | List Archived Sections | Tool to retrieve all archived sections for a specific project in Todoist. Use when you need to access sections that have been archived in a project. |
| `TODOIST_LIST_ARCHIVED_WORKSPACE_PROJECTS` | List Archived Workspace Projects | Tool to list all archived projects in a workspace. Archived projects are excluded from TODOIST_GET_ALL_PROJECTS; combine both tools when a complete project list is needed. Use when you need to retrieve archived workspace projects with optional filters or pagination. |
| `TODOIST_LIST_COMPLETED_TASKS` | List Completed Tasks | Tool to retrieve all completed tasks with optional project filtering. Use when you need to fetch completed tasks without date range restrictions. Note: This action uses an undocumented Todoist API endpoint (/api/v1/tasks/completed). The endpoint is not officially documented but is confirmed to work based on runtime testing. The response schemas are based on actual API responses. |
| `TODOIST_LIST_COMPLETED_TASKS_BY_DUE_DATE` | List Completed Tasks By Due Date | Tool to retrieve completed tasks within a specified due date range (up to 6 weeks). Use when you need to fetch completed tasks filtered by their original due dates rather than completion dates. |
| `TODOIST_LIST_FILTERS` | List Filters | Tool to list all filters for the authenticated user. Use when you need to retrieve the current set of custom filters. |
| `TODOIST_LIST_JOINABLE_WORKSPACES` | List Joinable Workspaces | Tool to get workspaces the user can join. Use when you need to discover available workspaces that the authenticated user can join. |
| `TODOIST_LIST_LABELS` | List Labels | Tool to get all user labels with pagination support. Use when you need to retrieve labels from the user's Todoist account. |
| `TODOIST_LIST_PENDING_WORKSPACE_INVITATIONS` | List Pending Workspace Invitations | Tool to list pending invitation emails in a workspace. Use when you need to check which email invites are still pending acceptance in a workspace. |
| `TODOIST_LIST_PROJECT_COLLABORATORS` | List Project Collaborators | Tool to get all collaborators for a given project with cursor-based pagination. Use when you need to retrieve the list of people who have access to a specific project. |
| `TODOIST_LIST_SECTIONS` | List Sections | Tool to get all active sections for the user, with optional filtering by project. Supports cursor-based pagination to handle large result sets. |
| `TODOIST_LIST_SHARED_LABELS` | List Shared Labels | Tool to retrieve shared label names from active tasks with pagination support. Use when you need to list labels across the workspace with optional filtering of personal labels. |
| `TODOIST_LIST_WORKSPACE_ACTIVE_PROJECTS` | List Workspace Active Projects | Tool to list all active workspace projects. Returns active projects that are visible to the user, including those not yet joined. For guest users, only joined workspace projects are returned. |
| `TODOIST_LIST_WORKSPACE_ARCHIVED_PROJECTS` | List Workspace Archived Projects | Tool to get archived projects in a workspace. Use when you need to retrieve a list of archived projects for a specific workspace with optional pagination. |
| `TODOIST_LIST_WORKSPACE_INVITATIONS` | List Workspace Invitations | Tool to list user emails with pending invitations to a workspace. Use when you need to check which email invites are still pending. The list is not paginated and accessible by all workspace members. |
| `TODOIST_LIST_WORKSPACE_USERS` | List Workspace Users | Tool to list users in workspace(s). Use when you need to retrieve workspace members. Returns users from a specific workspace if workspace_id is provided, otherwise returns users from all workspaces the authenticated user is part of. Not accessible by guests. |
| `TODOIST_MOVE_TASK` | Move Task | Tool to move a task to another project, section, or parent task while preserving task identity and metadata. Use when you need to relocate a task without recreating it. |
| `TODOIST_MOVE_TASK_REST_API` | Move Task (REST API) | Tool to move a task to another project, section, or parent task using the REST API. Use when you need to relocate a task while preserving its identity and metadata. |
| `TODOIST_QUICK_ADD_TASK` | Quick Add Task | Tool to add tasks using natural language parsing similar to the official Todoist clients. Use when you want to create a task quickly with natural language that includes dates, projects, labels, priority, and other task attributes in a single text string. |
| `TODOIST_REMOVE_SHARED_LABEL_V1` | Remove Shared Label (API v1) | Tool to remove a shared label from all active tasks using API v1. Use when you need to remove a shared label created by a collaborator from your account. |
| `TODOIST_RENAME_SHARED_LABELS_V1` | Rename Shared Labels (API v1) | Tool to rename a shared label across all active tasks using API v1. Use when you need to change the name of a shared label. |
| `TODOIST_REOPEN_TASK2` | Reopen Task (API v1) | Tool to reopen a completed task in Todoist using API v1. Use when you need to restore a previously completed task. Any ancestor tasks or sections will also be marked as uncomplete and restored from history. |
| `TODOIST_REORDER_TASKS` | Reorder Tasks | Reorder tasks deterministically by updating child_order in bulk via the Sync API item_reorder command. Use when you need to set specific ordering for tasks. |
| `TODOIST_SEARCH_LABELS` | Search Labels | Tool to search user labels by name with case-insensitive matching. Use when you need to find specific labels by name pattern. |
| `TODOIST_SEARCH_PROJECTS` | Search Projects | Search active user projects by name with support for wildcards and pagination. Use when you need to find projects matching a specific name pattern. |
| `TODOIST_SEARCH_SECTIONS` | Search Sections | Tool to search active sections by name, optionally filtered by project. Use when you need to find sections matching a specific name pattern across all projects or within a specific project. Supports pagination for large result sets. |
| `TODOIST_SYNC` | Todoist Sync | Tool to sync data with Todoist server, supporting both read and write operations. This is the central endpoint used by Todoist apps for all data synchronization. Use this action to: - Read multiple resource types in a single request (projects, tasks, labels, etc.) - Perform batch write operations (create/update/delete up to 100 resources at once) - Implement efficient incremental sync using sync tokens - Execute complex multi-step operations in a single atomic request For read-only operations, pass sync_token and resource_types. For write operations, include commands array with the operations to execute. Commands support batching up to 100 operations per request. |
| `TODOIST_UNARCHIVE_PROJECT2` | Unarchive Project (API v1) | Tool to unarchive a previously archived Todoist project using API v1. Use when you need to reactivate an archived project, making it visible again for the initiating user (personal projects) or all workspace users (workspace projects). |
| `TODOIST_UPDATE_COMMENT2` | Update Comment (v1) | Tool to update a comment by ID and return its content via v1 API. Use when you need to modify an existing comment's text using the v1 API endpoint. |
| `TODOIST_UPDATE_LABEL_V1_SECOND` | Update Label (API v1) | Tool to update an existing label using API v1. Use when you need to modify a label's name, color, order, or favorite status. |
| `TODOIST_UPDATE_NOTIFICATION_SETTING` | Update Notification Setting | Tool to update notification settings for the current user. Use when you need to enable or disable notifications for specific event types and channels. |
| `TODOIST_UPDATE_PROJECT2` | Update Project (API v1) | Tool to update a project's properties using Todoist API v1. Use when you need to modify project settings like name, description, favorite status, color, or view style. |
| `TODOIST_UPDATE_SECTION2` | Update Section (v1) | Tool to update an existing section by its ID using Todoist v1 API. Use when you need to rename a section. |
| `TODOIST_UPDATE_TASK` | Update Task | Tool to update an existing task's properties. Cannot change a task's project; to move a task, use TODOIST_CREATE_TASK then TODOIST_DELETE_TASK. Use when you need to modify a task's details after confirming its ID and new values. |
| `TODOIST_UPDATE_WORKSPACE_LOGO` | Update Workspace Logo | Tool to upload an image as the workspace logo or delete the existing logo. Use when you need to set or remove a workspace's branding image. |
| `TODOIST_UPLOAD_FILE` | Upload File | Tool to upload a file to Todoist. Use when you need to attach files to tasks or projects. The uploaded file can be later attached to comments or tasks using the returned file_url. |

## Supported Triggers

| Trigger slug | Name | Description |
|---|---|---|
| `TODOIST_NEW_TASK_CREATED` | New Task Created | Trigger when a new task is added to Todoist. |

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

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

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

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 Todoist 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, todoist)
- 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 Todoist 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=["todoist"],
    )

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

  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 Todoist 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 Todoist
```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 Todoist, 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=["todoist"],
    )

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

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

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

## Related Toolkits

- [Google Sheets](https://composio.dev/toolkits/googlesheets) - Google Sheets is a cloud-based spreadsheet tool for real-time collaboration and data analysis. It lets teams work together from anywhere, updating information instantly.
- [Notion](https://composio.dev/toolkits/notion) - Notion is a collaborative workspace for notes, docs, wikis, and tasks. It streamlines team knowledge, project tracking, and workflow customization in one place.
- [Airtable](https://composio.dev/toolkits/airtable) - Airtable combines the flexibility of spreadsheets with the power of a database for easy project and data management. Teams use Airtable to organize, track, and collaborate with custom views and automations.
- [Asana](https://composio.dev/toolkits/asana) - Asana is a collaborative work management platform for teams to organize and track projects. It streamlines teamwork, boosts productivity, and keeps everyone aligned on goals.
- [Google Tasks](https://composio.dev/toolkits/googletasks) - Google Tasks is a to-do list and task management tool integrated into Gmail and Google Calendar. It helps you organize, track, and complete tasks across your Google ecosystem.
- [Linear](https://composio.dev/toolkits/linear) - Linear is a modern issue tracking and project planning tool for fast-moving teams. It helps streamline workflows, organize projects, and boost productivity.
- [Jira](https://composio.dev/toolkits/jira) - Jira is Atlassian’s platform for bug tracking, issue tracking, and agile project management. It helps teams organize work, prioritize tasks, and deliver projects efficiently.
- [Clickup](https://composio.dev/toolkits/clickup) - ClickUp is an all-in-one productivity platform for managing tasks, docs, goals, and team collaboration. It streamlines project workflows so teams can work smarter and stay organized in one place.
- [Monday](https://composio.dev/toolkits/monday) - Monday.com is a customizable work management platform for project planning and collaboration. It helps teams organize tasks, automate workflows, and track progress in real time.
- [Addressfinder](https://composio.dev/toolkits/addressfinder) - Addressfinder is a data quality platform for verifying addresses, emails, and phone numbers. It helps you ensure accurate customer and contact data every time.
- [Agiled](https://composio.dev/toolkits/agiled) - Agiled is an all-in-one business management platform for CRM, projects, and finance. It helps you streamline workflows, consolidate client data, and manage business processes in one place.
- [Ascora](https://composio.dev/toolkits/ascora) - Ascora is a cloud-based field service management platform for service businesses. It streamlines scheduling, invoicing, and customer operations in one place.
- [Basecamp](https://composio.dev/toolkits/basecamp) - Basecamp is a project management and team collaboration tool by 37signals. It helps teams organize tasks, share files, and communicate efficiently in one place.
- [Beeminder](https://composio.dev/toolkits/beeminder) - Beeminder is an online goal-tracking platform that uses monetary pledges to keep you motivated. Stay accountable and hit your targets with real financial incentives.
- [Boxhero](https://composio.dev/toolkits/boxhero) - Boxhero is a cloud-based inventory management platform for SMBs, offering real-time updates, barcode scanning, and team collaboration. It helps businesses streamline stock tracking and analytics for smarter inventory decisions.
- [Breathe HR](https://composio.dev/toolkits/breathehr) - Breathe HR is cloud-based HR software for SMEs to manage employee data, absences, and performance. It simplifies HR admin, making it easy to keep employee records accurate and up to date.
- [Breeze](https://composio.dev/toolkits/breeze) - Breeze is a project management platform designed to help teams plan, track, and collaborate on projects. It streamlines workflows and keeps everyone on the same page.
- [Bugherd](https://composio.dev/toolkits/bugherd) - Bugherd is a visual feedback and bug tracking tool for websites. It helps teams and clients report website issues directly on live sites for faster fixes.
- [Canny](https://composio.dev/toolkits/canny) - Canny is a platform for managing customer feedback and feature requests. It helps teams prioritize product decisions based on real user insights.
- [Chmeetings](https://composio.dev/toolkits/chmeetings) - Chmeetings is a church management platform for events, members, donations, and volunteers. It streamlines church operations and improves community engagement.

## Frequently Asked Questions

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

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

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

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

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