# How to integrate Affinda MCP with LlamaIndex

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

## Introduction

This guide walks you through connecting Affinda to LlamaIndex using the Composio tool router. By the end, you'll have a working Affinda agent that can extract invoice data from uploaded pdf, delete a document no longer needed, create a new tag for hr documents through natural language commands.
This guide will help you understand how to give your LlamaIndex agent real control over a Affinda account through Composio's Affinda MCP server.
Before we dive in, let's take a quick look at the key ideas and tools involved.

## Also integrate Affinda with

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

The Affinda MCP server is an implementation of the Model Context Protocol that connects your AI agent and assistants like Claude, Cursor, etc directly to your Affinda account. It provides structured and secure access to your document processing workflows, so your agent can upload files, extract data, organize workspaces, label documents, and automate annotation management on your behalf.
- AI-powered document upload and extraction: Instantly have your agent upload new documents for parsing and extract structured data from various formats using Affinda's advanced AI models.
- Workspace and collection management: Let your agent create, group, and organize documents into collections and workspaces, keeping your document processing streamlined and organized.
- Automated annotation updates: Empower your agent to batch update or modify multiple document annotations in a single request, saving you time on manual corrections.
- Document tagging and organization: Direct your agent to create tags and label documents, making it easy to categorize and quickly retrieve important files.
- Effortless cleanup and resource management: Have your agent delete unwanted documents or collections, ensuring your Affinda account stays tidy and relevant at all times.

## Supported Tools

| Tool slug | Name | Description |
|---|---|---|
| `AFFINDA_ADD_TAG_TO_DOCUMENTS` | Add Tag to Documents | Tool to add a tag to multiple documents in a single operation. Use when you need to organize or categorize multiple documents by assigning them a shared tag. Tags enable efficient filtering and grouping of documents in your workspace. |
| `AFFINDA_BATCH_UPDATE_ANNOTATIONS` | Batch Update Annotations | Batch update multiple document annotations in a single API call. Use this action to efficiently update parsed values or other fields across many annotations at once, rather than making individual update requests for each annotation. Prerequisites: - Obtain annotation IDs using the 'Get Annotations' action with a document filter - Annotations must exist in documents that have been processed by Affinda Common use cases: - Correcting OCR/extraction errors in bulk - Updating parsed values after manual review - Modifying annotation data programmatically |
| `AFFINDA_CREATE_API_USER` | Create API User | Tool to create a new API user within an organization. Use when you need to generate a new API user with authentication credentials for programmatic access to Affinda services. |
| `AFFINDA_CREATE_BATCH_ANNOTATIONS` | Batch Create Annotations | Batch create multiple document annotations in a single API call. Use this action to efficiently create multiple annotations at once for documents that have been processed by Affinda. This is useful for programmatically adding structured data to documents or importing annotation data from external sources. Prerequisites: - Documents must be created first using 'Create Document' action - Obtain document identifiers from 'Create Document' or 'Get Documents' actions - Know the data point identifiers for your collection (from extractor configuration) Common use cases: - Importing annotation data from external systems - Programmatically adding structured data to documents - Creating annotations for validation or training purposes |
| `AFFINDA_CREATE_COLLECTION` | Create Collection | Tool to create a new collection. Use after you have a valid workspace ID and want to group documents by a specific extractor within that workspace. |
| `AFFINDA_CREATE_DATA_FIELD_FOR_COLLECTION` | Create Data Field For Collection | Tool to create a data field for a collection along with a new data point. Use when you need to add a custom field to a collection for document processing and validation. |
| `AFFINDA_CREATE_DATA_SOURCE` | Create Data Source | Tool to create a custom mapping data source. Use when you need to set up a new data source for mapping AI-extracted values to your database records. |
| `AFFINDA_CREATE_DATA_SOURCE_VALUE` | Create Data Source Value | Tool to add a new value to a mapping data source. Use when you need to add entries to a data source for mapping or validation purposes. The created value can then be referenced in document extraction and mapping workflows. |
| `AFFINDA_CREATE_DOCUMENT` | Create Document | Upload a document to Affinda for parsing and data extraction. Use this action when you need to: - Parse resumes/CVs to extract candidate information - Process invoices to extract line items, amounts, and vendor details - Extract data from any supported document type (PDF, DOCX, images) You can upload either a file directly or provide a publicly accessible URL. The document will be processed by Affinda's AI extraction engine. Prerequisites: - For collection-based uploads: Get collection ID from get_collections action - For workspace-specific uploads: Get workspace ID from get_workspaces action |
| `AFFINDA_CREATE_DOCUMENT_TYPE` | Create Document Type | Tool to create a new document type in the specified organization. Use when you need to define a new category of documents for processing. Document types allow you to organize and categorize documents within an organization. |
| `AFFINDA_CREATE_EXTRACTOR` | Create Extractor | Tool to create a new extractor. Use when you need to define a custom document parser with specific fields and extraction rules, typically derived from a base extractor. |
| `AFFINDA_CREATE_FROM_DATA_DOCUMENTS` | Create Document from Data | Create a document from structured resume or job description data for Search & Match. Use this when you have structured data to create a document programmatically without file uploads. Returns an identifier for subsequent status checks via /documents/{identifier}. |
| `AFFINDA_CREATE_INDEX` | Create Index | Tool to create a new index for search and match functionality. Use when you need to set up a new index for organizing and searching documents like resumes or job descriptions. |
| `AFFINDA_CREATE_INVITATION` | Create Invitation | Tool to create a new organization invitation. Use when you need to invite a user to join an organization with a specific role. |
| `AFFINDA_CREATE_JOB_DESCRIPTION_SEARCH` | Create Job Description Search | Search through parsed job descriptions using custom criteria or resume matching. Use this action when you need to find job postings that match specific requirements such as skills, experience, location, education, or to find jobs matching a candidate's resume. Returns ranked results with detailed match scores. |
| `AFFINDA_CREATE_JOB_DESCRIPTION_SEARCH_EMBED_URL` | Create Job Description Search Embed URL | Tool to create and return a signed URL for the embeddable job description search tool. Use this when you need to generate a URL that can be embedded on a web page to provide job description search functionality. Optionally pass config_override to customize display settings, field weights, and theme. |
| `AFFINDA_CREATE_ORGANIZATION` | Create Organization | Tool to create a new organization. Use when you have a unique organization name and want to group resources under it. |
| `AFFINDA_CREATE_RESTHOOK_SUBSCRIPTION` | Create RESTHook Subscription | Tool to create a new RESTHook subscription. Use after confirming your webhook endpoint is ready to receive document event notifications. |
| `AFFINDA_CREATE_RESUME_SEARCH` | Create Resume Search | Tool to search through parsed resumes using three methods: match to a job description, match to a resume, or custom criteria. Use when you need to find candidates matching specific requirements. |
| `AFFINDA_CREATE_RESUME_SEARCH_EMBED_URL` | Create Resume Search Embed URL | Tool to create and return a signed URL for the embeddable resume search tool. Use this when you need to generate a URL that can be embedded on a web page to provide resume search functionality. Optionally pass config_override to customize display settings, field weights, and theme. |
| `AFFINDA_CREATE_TAG` | Create Tag | Creates a new tag in the specified workspace. Tags can be used to categorize and label documents for organization and filtering. Each tag name must be unique within its workspace. Returns the created tag with its ID and metadata. |
| `AFFINDA_CREATE_VALIDATION_RESULT` | Create Validation Result | Create a validation result for document annotations in Affinda. Use this tool to record validation outcomes after parsing a document. Validation results track whether specific annotations (extracted data fields) pass or fail business rules. Prerequisites: 1. Get a document identifier from Get Documents or Create Document 2. Get annotation IDs from Get Annotations for that document 3. Define a kebab-case rule slug that identifies your validation rule Example use cases: - Recording that an invoice total matches the sum of line items - Marking that a required field is present and valid - Logging validation failures for review workflows |
| `AFFINDA_CREATE_VALIDATION_RESULTS_BATCH` | Batch Create Validation Results | Batch create multiple validation results for document annotations in a single API call. Use this action to efficiently record validation outcomes for multiple documents or rules at once, rather than making individual create requests for each validation result. This is particularly useful for bulk validation workflows or when validating multiple rules across many documents. Prerequisites: - Obtain document identifiers from Get Documents or Create Document actions - Get annotation IDs from Get Annotations for those documents - Define kebab-case rule slugs that identify your validation rules Common use cases: - Bulk recording of validation results across multiple documents - Validating multiple business rules on the same document - Automated validation workflows for large document batches |
| `AFFINDA_CREATE_WORKSPACE` | Create Workspace | Tool to create a new workspace. Use when you need to programmatically create a workspace container within an organization. |
| `AFFINDA_CREATE_WORKSPACE_MEMBERSHIP` | Create Workspace Membership | Tool to add a user to a workspace by creating a membership. Use when you need to grant a user access to a specific workspace. |
| `AFFINDA_DELETE_ANNOTATIONS_BATCH` | Batch Delete Annotations | Batch delete multiple document annotations in a single API call. Use this action to efficiently remove multiple annotations at once rather than making individual delete requests for each annotation. Prerequisites: - Obtain annotation IDs using the 'Get Annotations' action with a document filter - Annotations must exist in documents that have been processed by Affinda Common use cases: - Removing incorrect or unwanted annotations in bulk - Cleaning up annotations after document reprocessing - Deleting specific extracted data fields programmatically |
| `AFFINDA_DELETE_COLLECTION` | Delete Collection | Permanently delete a collection from Affinda by its identifier. This is a destructive operation that cannot be undone. Use Get Collections first to verify the collection ID before deletion. Note: Documents in the collection may also be affected. |
| `AFFINDA_DELETE_DATA_SOURCE` | Delete Data Source | Permanently delete a mapping data source from the database by its identifier. This is a destructive operation that cannot be undone. Use when you need to remove a data source that is no longer needed or was created in error. |
| `AFFINDA_DELETE_DATA_SOURCE_VALUE` | Delete Data Source Value | Tool to delete a specific value from a mapping data source. Use when you need to remove an incorrect or outdated value from a data source mapping. |
| `AFFINDA_DELETE_DOCUMENT` | Delete Document | Tool to delete a specific document by its ID. Use when you need to remove a document that is incorrect or no longer needed, after confirming the document ID. |
| `AFFINDA_DELETE_DOCUMENT_TYPE` | Delete Document Type | Tool to permanently delete a document type by its identifier. Use when you need to remove a document type that is no longer needed. This is a destructive operation that cannot be undone. |
| `AFFINDA_DELETE_INDEX` | Delete Index | Tool to permanently delete an index from Affinda by its name. Use when you need to remove an index that is no longer needed. This is a destructive operation that cannot be undone. |
| `AFFINDA_DELETE_INVITATION` | Delete Invitation | Tool to delete an invitation by its identifier. Use when you need to remove or revoke an invitation after confirming the invitation identifier. |
| `AFFINDA_DELETE_ORGANIZATION` | Delete Organization | Permanently deletes an organization from Affinda. This action is destructive and cannot be undone. All workspaces, collections, and documents associated with the organization will also be deleted. Use the get_organizations action first to confirm the organization identifier before deletion. |
| `AFFINDA_DELETE_RESTHOOK_SUBSCRIPTION` | Delete Resthook Subscription | Tool to delete a specific resthook subscription by ID. Use after confirming the subscription identifier when the webhook is no longer needed. |
| `AFFINDA_DELETE_TAG` | Delete Tag | Permanently delete a tag from Affinda by its ID. This is a destructive operation that cannot be undone. Use Get Tags first to verify the tag ID before deletion. |
| `AFFINDA_DELETE_VALIDATION_RESULTS` | Delete Validation Results | Delete multiple validation results in a single API call. Use this action to efficiently remove multiple validation results at once rather than making individual delete requests for each validation result. Prerequisites: - Obtain validation result IDs using the 'Get All Validation Results' action Common use cases: - Removing obsolete validation results in bulk - Cleaning up validation results after reprocessing documents - Deleting validation results for specific documents programmatically |
| `AFFINDA_DELETE_WORKSPACE` | Delete Workspace | Tool to delete a specific workspace by its ID. Use when you need to remove an unused workspace after confirming the workspace ID. |
| `AFFINDA_DELETE_WORKSPACE_MEMBERSHIP` | Delete Workspace Membership | Tool to remove a user from a workspace by membership ID. Use after confirming the workspace membership ID to revoke access. |
| `AFFINDA_GET_ALL_API_USERS` | Get All API Users | Tool to retrieve a list of all API users. Use when you need to list API users, optionally filtered by organization. |
| `AFFINDA_GET_ALL_DOCUMENT_SPLITTERS` | Get All Document Splitters | Tool to get a list of all document splitters. Use when you need to retrieve document splitters, optionally filtered by organization. |
| `AFFINDA_GET_ALL_INVITATIONS` | Get All Invitations | Tool to retrieve all invitations you created or sent to you. Use when you need to list organization invitations with optional filtering by status, role, or organization. |
| `AFFINDA_GET_ALL_ORGANIZATION_MEMBERSHIPS` | Get Organization Memberships | Retrieve all organization memberships across the account. Returns paginated results showing which users belong to which organizations. Use to audit organization access or find specific user memberships. |
| `AFFINDA_GET_ALL_TAGS` | Get Tags | Tool to list all tags. Use after confirming authentication to fetch tags across workspaces. |
| `AFFINDA_GET_ALL_VALIDATION_RESULTS` | Get All Validation Results | Tool to list validation results for documents. Use after processing documents to inspect validation outcomes. |
| `AFFINDA_GET_ALL_WORKSPACE_MEMBERSHIPS` | Get Workspace Memberships | Retrieve all workspace memberships across the account. Returns paginated results showing which users belong to which workspaces. Use to audit workspace access or find specific user memberships. |
| `AFFINDA_GET_ANNOTATIONS` | Get Annotations | Retrieves all annotations for a specific document. Annotations are extracted data fields (text, dates, numbers, tables, etc.) from parsed documents. Use this after a document has been processed to access the extracted data. |
| `AFFINDA_GET_COLLECTION` | Get Collection | Tool to retrieve details of a specific collection by its ID. Use when you need full metadata about a collection after confirming its identifier. |
| `AFFINDA_GET_COLLECTIONS` | Get Collections | Tool to retrieve a list of all collections. Use when you need to list collections in your Affinda workspace after authentication. |
| `AFFINDA_GET_DATA_SOURCE` | Get Data Source | Tool to retrieve details of a specific mapping data source by its identifier. Use when you need to fetch mapping data source metadata and schema configuration. |
| `AFFINDA_GET_DATA_SOURCE_VALUE` | Get Data Source Value | Tool to retrieve a specific value from a mapping data source. Use when you need to get details about a particular value in a data source. |
| `AFFINDA_GET_DATA_SOURCE_VALUES` | Get Data Source Values | Tool to retrieve all values from a mapping data source. Use when you need to list or search values in a specific data source after authentication. |
| `AFFINDA_GET_DOCUMENT` | Get Document | Retrieve full details and parsed data for a specific document by its identifier. Use this tool to get comprehensive information about a document including: - Processing status (ready/failed) - Document metadata (file name, page count, language) - Parsed/extracted data results - URLs for PDF, original file, and review page - Collection and workspace information The document identifier can be either a system-generated ID or a custom identifier set during document upload. |
| `AFFINDA_GET_DOCUMENT_REDACTED` | Get Document Redacted | Tool to retrieve the redacted version of a document as a PDF file. Use when you need to get a document with sensitive information removed. The original document is not modified by this operation. |
| `AFFINDA_GET_DOCUMENTS` | Get Documents | Tool to retrieve a list of all documents. Use when you need to list documents in your Affinda workspace after authentication. |
| `AFFINDA_GET_DOCUMENT_SPLITTER` | Get Document Splitter | Tool to retrieve details of a specific document splitter by its identifier. Use when you need to fetch metadata about a document splitter configuration. |
| `AFFINDA_GET_DOCUMENT_TYPE` | Get Document Type | Tool to retrieve details of a specific document type by its ID. Use when you need to confirm the configuration or metadata of a document type before processing documents. |
| `AFFINDA_GET_DOCUMENT_TYPE_JSON_SCHEMA` | Get Document Type JSON Schema | Tool to generate a JSON schema from a document type by its identifier. Use when you need the structured schema definition for a document type to understand its field structure and validation rules. |
| `AFFINDA_GET_DOCUMENT_TYPE_PYDANTIC_MODELS` | Get Document Type Pydantic Models | Tool to generate Pydantic model code from a document type's schema. Use when you need type-safe Python models for parsing documents of a specific type. |
| `AFFINDA_GET_DOCUMENT_TYPES` | Get Document Types | Retrieve all document types accessible to the authenticated user. Returns a list containing each document type's identifier, name, description, ingest email, and organization. Use this to discover available document types before uploading or processing documents. |
| `AFFINDA_GET_EXTRACTOR` | Get Extractor | Tool to retrieve detailed information about a specific extractor by its identifier. Use when you need complete metadata about an extractor's configuration, fields, and capabilities. |
| `AFFINDA_GET_EXTRACTORS` | Get Extractors | Retrieve all extractors available for an organization. Extractors are document parsing configurations that define what data to extract from documents. This includes both Affinda's built-in extractors (resumes, invoices, etc.) and any custom extractors created for the organization. Use this action to: - List available extractors before creating a collection - Find the identifier of a specific extractor type - Check if custom extractors exist for the organization |
| `AFFINDA_GET_INDEX_DOCUMENTS` | Get Index Documents | Tool to retrieve all indexed documents for a specific index. Use when you need to list documents in a search index after creating or accessing an index. |
| `AFFINDA_GET_INVITATION` | Get Invitation | Tool to retrieve details of a specific organization invitation by its identifier. Use when you need to check invitation status or get invitation metadata. |
| `AFFINDA_GET_JOB_DESCRIPTION_SEARCH_CONFIG` | Get Job Description Search Config | Tool to get the configuration for the logged in user's embeddable job description search tool. Returns display settings, field weights, and maximum results limits. |
| `AFFINDA_GET_MAPPING` | Get Mapping | Tool to retrieve a specific mapping by its identifier. Use when you need to fetch mapping configuration details for a lookup against a MappingDataSource. |
| `AFFINDA_GET_ORGANIZATION` | Get Organization | Tool to retrieve details of a specific organization by its ID. Use when you need to fetch an organization's metadata after confirming its identifier. |
| `AFFINDA_GET_ORGANIZATION_MEMBERSHIP` | Get Organization Membership | Tool to retrieve details of a specific organization membership by its ID. Use when you need to confirm a user's role and details within an organization. |
| `AFFINDA_GET_ORGANIZATIONS` | Get Organizations | Retrieves all organizations accessible to the authenticated user. Returns a list of organizations with details including identifier, name, user role, trial status, and configuration settings. Use the 'identifier' field from the response when calling other organization-related endpoints. |
| `AFFINDA_GET_RESTHOOK_SUBSCRIPTION` | Get Resthook Subscription | Tool to retrieve details of a specific resthook subscription by its ID. Use after confirming its creation to verify its settings. |
| `AFFINDA_GET_RESTHOOK_SUBSCRIPTIONS` | Get RESTHook Subscriptions | Tool to retrieve a list of all RESTHook subscriptions. Use after authenticating to list all webhook subscriptions associated with your account. |
| `AFFINDA_GET_TAG` | Get Tag | Tool to retrieve details of a specific tag by its ID. Use when you need to fetch information about a particular tag. |
| `AFFINDA_GET_USAGE_BY_WORKSPACE` | Get Usage by Workspace | Retrieves monthly document processing usage statistics for a specific workspace. Returns a list of usage records showing how many documents were processed each month. Use this to track workspace activity, monitor consumption trends, or generate usage reports. |
| `AFFINDA_GET_WORKSPACE` | Get Workspace | Tool to retrieve details of a specific workspace by its ID. Use when you need full workspace metadata after confirming its identifier. |
| `AFFINDA_GET_WORKSPACE_MEMBERSHIP` | Get Workspace Membership | Tool to retrieve details of a specific workspace membership by its ID. Use when you need to confirm a user's role and details within a workspace after obtaining the membership identifier. |
| `AFFINDA_GET_WORKSPACES` | Get Workspaces | Tool to retrieve a list of all workspaces. Use when you need to list workspaces filtered by organization and optional name. |
| `AFFINDA_LIST_DATA_POINTS` | List Data Points | Tool to retrieve all data points. Returns both custom data points and Affinda's off-the-shelf data points. Use when you need to list available data points for extractors. |
| `AFFINDA_LIST_DATA_SOURCES` | List Data Sources | Tool to retrieve the list of all custom mapping data sources. Use when you need to list or search for mapping data sources in your Affinda workspace. |
| `AFFINDA_LIST_INDEXES` | List Indexes | Tool to retrieve a list of all search indexes. Use when you need to view available indexes for searching and matching documents in Affinda. |
| `AFFINDA_LIST_MAPPINGS` | List Mappings | Tool to retrieve the list of all custom data mappings. Use when you need to list mappings for a specific data source. |
| `AFFINDA_LIST_OCCUPATION_GROUPS` | List Occupation Groups | Tool to retrieve the list of searchable occupation groups. Use when you need to get available occupation categories for job matching or classification. |
| `AFFINDA_LIST_RESUME_SEARCH_CONFIG` | List Resume Search Config | Tool to get the configuration for the logged in user's embeddable resume search tool. Use this to retrieve display field settings, search weights, maximum results, and theme customization options for the resume search interface. |
| `AFFINDA_LIST_RESUME_SEARCH_JOB_TITLE_SUGGESTIONS` | List Resume Search Job Title Suggestions | Tool to get job title suggestions based on provided job title(s). Use when you need to find related job titles for resume search. |
| `AFFINDA_LIST_RESUME_SEARCH_SKILL_SUGGESTIONS` | List Resume Search Skill Suggestions | Tool to get skill suggestions based on provided skills. Use when you need to find related skills to expand or refine resume search queries. |
| `AFFINDA_REMOVE_TAG_FROM_DOCUMENTS` | Remove Tag from Documents | Remove a tag from multiple documents in a single batch operation. Use this action to efficiently untag many documents at once rather than making individual remove requests. The operation is idempotent - removing a tag that isn't present on a document will not cause an error. |
| `AFFINDA_REPLACE_DATA_SOURCE_VALUES` | Replace Data Source Values | Tool to completely replace all values in a mapping data source. Use when you need to bulk update or refresh an entire data source's value list. Note: For large data sources (>1000 values), new values may take a few minutes to become searchable after the operation completes. |
| `AFFINDA_SPLIT_DOCUMENT_PAGES` | Split Document Pages | Split a document into multiple documents by dividing its pages. Use when you need to separate a multi-page document into distinct documents based on page groupings. Prerequisites: - Obtain the document identifier from create_document or get_document actions - Use get_document to retrieve page IDs for the pages you want to split Important notes: - Each page can only be assigned to one split - All pages should be accounted for across all splits - One split can optionally use the original document identifier to maintain the root document |
| `AFFINDA_UPDATE_ANNOTATION` | Update Annotation | Tool to update data of a single annotation in Affinda. Use when you need to correct extracted values, verify annotations, or modify annotation metadata after document processing. Prerequisites: - Obtain the annotation ID using the 'Get Annotations' action with a document filter - Annotations must exist in documents that have been processed by Affinda Common use cases: - Correcting OCR or extraction errors in individual fields - Marking annotations as verified after manual review (set isClientVerified=true) - Updating parsed values to fix incorrect data extraction - Modifying annotation coordinates or page references Note: For bulk updates across multiple annotations, use the 'Batch Update Annotations' action instead for better efficiency. |
| `AFFINDA_UPDATE_COLLECTION` | Update Collection | Tool to update specific fields of a collection. Use when you need to rename or reassign a collection after creation. |
| `AFFINDA_UPDATE_DATA_FIELD_FOR_COLLECTION` | Update Data Field For Collection | Tool to update a data field configuration for a collection's data point. Use when you need to modify field properties such as label, type, mandatory status, or validation settings after the field has been created. |
| `AFFINDA_UPDATE_DATA_SOURCE_VALUE` | Update Data Source Value | Tool to update an existing value in a mapping data source. Use when you need to modify the label or description of an existing data source value. The value identifier itself cannot be changed. |
| `AFFINDA_UPDATE_DOCUMENT` | Update Document | Tool to update specific fields of a document. Use when you need to rename, reassign, or adjust expiry and storage settings after document creation. |
| `AFFINDA_UPDATE_DOCUMENT_DATA` | Update Document Data | Update parsed data for a resume or job description document in Affinda. This endpoint is ONLY applicable for documents that have been processed with 'resume' or 'job-description' extractors. For other document types, use the batch_update_annotations endpoint instead. Use this tool when you need to correct or add parsed fields to an existing resume or job description document after it has been uploaded and processed. |
| `AFFINDA_UPDATE_DOCUMENT_TYPE` | Update Document Type | Tool to update a document type by its identifier. Use when you need to modify the name or description of an existing document type. |
| `AFFINDA_UPDATE_EXTRACTOR` | Update Extractor | Tool to update specific fields of an extractor. Use when you need to modify an extractor's name, category, validation settings, or field groups after creation. |
| `AFFINDA_UPDATE_INDEX` | Update Index | Tool to update the name of an existing search index. Use when you need to rename an index after creation. |
| `AFFINDA_UPDATE_INVITATION` | Update Invitation | Tool to update an organization invitation's role. Use when you need to change the role assigned to an invitation before it is accepted. |
| `AFFINDA_UPDATE_JOB_DESCRIPTION_SEARCH_CONFIG` | Update Job Description Search Config | Tool to update the configuration for the logged in user's embeddable job description search tool. Use to configure display fields, field weights, maximum results, and UI customization options. |
| `AFFINDA_UPDATE_MAPPING` | Update Mapping | Tool to update a specific mapping's settings. Use when you need to adjust score cutoff or ordering for a lookup against a MappingDataSource. |
| `AFFINDA_UPDATE_ORGANIZATION` | Update Organization | Tool to update specific fields of an organization. Use when you need to modify an organization's name, avatar, or webhook and validation settings after creation. |
| `AFFINDA_UPDATE_ORGANIZATION_MEMBERSHIP` | Update Organization Membership | Tool to update an organization membership's role. Use when you need to change a user's role within an organization. |
| `AFFINDA_UPDATE_RESTHOOK_SUBSCRIPTION` | Update RESTHook Subscription | Tool to update an existing RESTHook subscription. Use after confirming the subscription identifier to modify its URL, event, or activation status. |
| `AFFINDA_UPDATE_RESUME_SEARCH_CONFIG` | Update Resume Search Config | Tool to update the configuration for the logged in user's embeddable resume search tool. Use this to modify which fields are displayed, their search weights, maximum results limit, distance units, custom actions, and theme settings. |
| `AFFINDA_UPDATE_TAG` | Update Tag | Tool to update data of a tag. Use when you need to modify a tag's name or reassign it to a different workspace. Only provide the fields you want to change; other fields will remain unchanged. |
| `AFFINDA_UPDATE_WORKSPACE` | Update Workspace | Tool to update specific fields of a workspace. Use when you need to modify a workspace's name, visibility, document rejection policy, or other settings. Only provide the fields you want to change; other fields will remain unchanged. |

## Supported Triggers

None listed.

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

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

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

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 Affinda 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, affinda)
- 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 Affinda 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=["affinda"],
    )

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

  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 Affinda 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 Affinda
```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 Affinda, 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=["affinda"],
    )

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

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

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

## Related Toolkits

- [Google Drive](https://composio.dev/toolkits/googledrive) - Google Drive is a cloud storage platform for uploading, sharing, and collaborating on files. It's perfect for keeping your documents accessible and organized across devices.
- [Google Docs](https://composio.dev/toolkits/googledocs) - Google Docs is a cloud-based word processor that enables document creation and real-time collaboration. Its seamless sharing and version history make team editing and content management a breeze.
- [Google Super](https://composio.dev/toolkits/googlesuper) - Google Super is an all-in-one suite combining Gmail, Drive, Calendar, Sheets, Analytics, and more. It gives you a unified platform to manage your digital life, boosting productivity and organization.
- [Agility cms](https://composio.dev/toolkits/agility_cms) - Agility CMS is a headless content management system for building and managing digital experiences across platforms. It lets teams update content quickly and deliver omnichannel experiences with ease.
- [Algodocs](https://composio.dev/toolkits/algodocs) - Algodocs is an AI-powered platform that automates data extraction from business documents. It delivers fast, secure, and accurate processing without templates or manual training.
- [Api2pdf](https://composio.dev/toolkits/api2pdf) - Api2Pdf is a REST API for generating PDFs from HTML, URLs, and documents using powerful engines like wkhtmltopdf and Headless Chrome. It streamlines document conversion and automation for developers and businesses.
- [Aryn](https://composio.dev/toolkits/aryn) - Aryn is an AI-powered platform for parsing, extracting, and analyzing data from unstructured documents. Use it to automate document processing and unlock actionable insights from your files.
- [Boldsign](https://composio.dev/toolkits/boldsign) - Boldsign is a digital eSignature platform for sending, signing, and tracking documents online. Organizations use it to automate agreements and manage legally binding workflows efficiently.
- [Boloforms](https://composio.dev/toolkits/boloforms) - BoloForms is an eSignature platform built for small businesses, offering unlimited signatures, templates, and forms. It simplifies digital document signing and team collaboration at a predictable, fixed price.
- [Box](https://composio.dev/toolkits/box) - Box is a cloud content management and file sharing platform for businesses. It helps teams securely store, organize, and collaborate on files from anywhere.
- [Carbone](https://composio.dev/toolkits/carbone) - Carbone is a blazing-fast report generator that turns JSON data into PDFs, Word docs, spreadsheets, and more using flexible templates. It lets you automate document creation at scale with minimal code.
- [Castingwords](https://composio.dev/toolkits/castingwords) - CastingWords is a transcription service specializing in human-powered, accurate transcripts via a simple API. Get seamless audio-to-text conversion for interviews, meetings, podcasts, and more.
- [Cloudconvert](https://composio.dev/toolkits/cloudconvert) - CloudConvert is a powerful file conversion service supporting over 200 file formats. It streamlines converting, compressing, and managing documents, media, and more, all in one place.
- [Cloudlayer](https://composio.dev/toolkits/cloudlayer) - Cloudlayer is a document and asset generation service for creating PDFs and images via API or SDKs. It lets you automate high-quality doc creation, saving dev time and reducing manual work.
- [Cloudpress](https://composio.dev/toolkits/cloudpress) - Cloudpress is a content export tool for Google Docs and Notion. It automates publishing to your favorite Content Management Systems.
- [Contentful graphql](https://composio.dev/toolkits/contentful_graphql) - Contentful graphql is a content delivery API that lets you access Contentful data using GraphQL queries. It gives you efficient, flexible ways to fetch and manage structured content for any digital project.
- [Conversion tools](https://composio.dev/toolkits/conversion_tools) - Conversion Tools is an online service for converting documents between formats such as PDF, Word, Excel, XML, and CSV. It lets you automate complex document workflows with just a few clicks.
- [Convertapi](https://composio.dev/toolkits/convertapi) - ConvertAPI is a robust file conversion service for documents, images, and spreadsheets. It streamlines programmatic format changes and lets developers automate complex workflows with a single API.
- [Craftmypdf](https://composio.dev/toolkits/craftmypdf) - CraftMyPDF is a web-based service for designing and generating PDFs with templates and live data. It streamlines document creation by automating personalized PDFs at scale.
- [Docmosis](https://composio.dev/toolkits/docmosis) - Docmosis generates PDF and Word documents from user-defined templates. It's perfect for merging data fields to quickly produce reports, invoices, and business letters.

## Frequently Asked Questions

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

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

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

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

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