# Directory Structure
```
├── .gitignore
├── .pre-commit-config.yaml
├── claude_desktop_config.json
├── claude.png
├── LICENSE
├── llamacloud_mcp
│ ├── __init__.py
│ └── main.py
├── pyproject.toml
├── README.md
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
.env
data/
llamacloud-testing-service-account.json
test-credentials.py
test-index.py
```
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
```yaml
---
default_language_version:
python: python3
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-byte-order-marker
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
args: [--allow-multiple-documents]
- id: detect-private-key
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.12.1
hooks:
- id: ruff-format
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
hooks:
- id: mypy
additional_dependencies:
[
"types-Deprecated",
"types-PyYAML",
"types-botocore",
"types-aiobotocore",
"types-protobuf==4.24.0.4",
"types-redis",
"types-requests",
"types-setuptools",
"types-click",
]
args:
[
--disallow-untyped-defs,
--ignore-missing-imports,
--python-version=3.11,
]
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
additional_dependencies: [tomli]
- repo: https://github.com/pappasam/toml-sort
rev: v0.23.1
hooks:
- id: toml-sort-fix
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# LlamaIndex MCP demos
`llamacloud-mcp` is a tool that allows you to use LlamaCloud as an MCP server. It can be used to query LlamaCloud indexes and extract data from files.
It allows for:
- specifying one or more indexes to use for context retrieval.
- specifying one or more extract agents to use for data extraction
- configuring project and organization ids
- configuring the transport to use for the MCP server (stdio, sse, streamable-http)
## Getting Started
1. Install [uv](https://docs.astral.sh/uv/getting-started/installation/)
2. Run `uvx llamacloud-mcp@latest --help` to see the available options.
3. Configure your MCP client to use the `llamacloud-mcp` server. You can either launch the server directly with `uvx llamacloud-mcp@latest` or use a `claude_desktop_config.json` file to connect with claude desktop.
### Usage
```bash
% uvx llamacloud-mcp@latest --help
Usage: llamacloud-mcp [OPTIONS]
Options:
--index TEXT Index definition in the format
name:description. Can be used multiple
times.
--extract-agent TEXT Extract agent definition in the format
name:description. Can be used multiple
times.
--project-id TEXT Project ID for LlamaCloud
--org-id TEXT Organization ID for LlamaCloud
--transport [stdio|sse|streamable-http]
Transport to run the MCP server on. One of
"stdio", "sse", "streamable-http".
--api-key TEXT API key for LlamaCloud
--help Show this message and exit.
```
### Configure Claude Desktop
1. Install [Claude Desktop](https://claude.ai/download)
2. In the menu bar choose `Claude` -> `Settings` -> `Developer` -> `Edit Config`. This will show up a config file that you can edit in your preferred text editor.
3. Create a add the following "mcpServers" to the config file, where each `--index` is a new index tool that you define, and each `--extract-agent` is an extraction agent tool.
4. You'll want your config to look something like this (make sure to replace `$YOURPATH` with the path to the repository):
```json
{
"mcpServers": {
"llama_index_docs_server": {
"command": "uvx",
"args": [
"llamacloud-mcp@latest",
"--index",
"your-index-name:Description of your index",
"--index",
"your-other-index-name:Description of your other index",
"--extract-agent",
"extract-agent-name:Description of your extract agent",
"--project-name",
"<Your LlamaCloud Project Name>",
"--org-id",
"<Your LlamaCloud Org ID>",
"--api-key",
"<Your LlamaCloud API Key>"
]
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"<your directory you want filesystem tool to have access to>"
]
}
}
}
```
Make sure to **restart Claude Desktop** after configuring the file.
Now you're ready to query! You should see a tool icon with your server listed underneath the query box in Claude Desktop, like this:

## LlamaCloud as an MCP server From Scratch
To provide a local MCP server that can be used by a client like Claude Desktop, you can use `mcp-server.py`. You can use this to provide a tool that will use RAG to provide Claude with up-to-the-second private information that it can use to answer questions. You can provide as many of these tools as you want.
### Set up your LlamaCloud index
1. Get a [LlamaCloud](https://cloud.llamaindex.ai/) account
2. [Create a new index](https://docs.cloud.llamaindex.ai/llamacloud/guides/ui) with any data source you want. In our case we used [Google Drive](https://docs.cloud.llamaindex.ai/llamacloud/integrations/data_sources/google_drive) and provided a subset of the LlamaIndex documentation as a source. You could also upload documents directly to the index if you just want to test it out.
3. Get an API key from the [LlamaCloud UI](https://cloud.llamaindex.ai/)
### Set up your MCP server
1. Clone this repository
2. Create a `.env` file and add two environment variables:
- `LLAMA_CLOUD_API_KEY` - The API key you got in the previous step
- `OPENAI_API_KEY` - An OpenAI API key. This is used to power the RAG query. You can use [any other LLM](https://docs.llamaindex.ai/en/stable/understanding/using_llms/using_llms/) if you don't want to use OpenAI.
Now let's look at the code. First you instantiate an MCP server:
```python
mcp = FastMCP('llama-index-server')
```
Then you define your tool using the `@mcp.tool()` decorator:
```python
@mcp.tool()
def llama_index_documentation(query: str) -> str:
"""Search the llama-index documentation for the given query."""
index = LlamaCloudIndex(
name="mcp-demo-2",
project_name="Rando project",
organization_id="e793a802-cb91-4e6a-bd49-61d0ba2ac5f9",
api_key=os.getenv("LLAMA_CLOUD_API_KEY"),
)
response = index.as_query_engine().query(query + " Be verbose and include code examples.")
return str(response)
```
Here our tool is called `llama_index_documentation`; it instantiates a LlamaCloud index called `mcp-demo-2` and then uses it as a query engine to answer the query, including some extra instructions in the prompt. You'll get instructions on how to set up your LlamaCloud index in the next section.
Finally, you run the server:
```python
if __name__ == "__main__":
mcp.run(transport="stdio")
```
Note the `stdio` transport, used for communicating to Claude Desktop.
## LlamaIndex as an MCP client
LlamaIndex also has an MCP client integration, meaning you can turn any MCP server into a set of tools that can be used by an agent. You can see this in `mcp-client.py`, where we use the `BasicMCPClient` to connect to our local MCP server.
For simplicity of demo, we are using the same MCP server we just set up above. Ordinarily, you would not use MCP to connect LlamaCloud to a LlamaIndex agent, you would use [QueryEngineTool](https://docs.llamaindex.ai/en/stable/examples/agent/openai_agent_with_query_engine/) and pass it directly to the agent.
### Set up your MCP server
To provide a local MCP server that can be used by an HTTP client, we need to slightly modify `mcp-server.py` to use the `run_sse_async` method instead of `run`. You can find this in `mcp-http-server.py`.
```python
mcp = FastMCP('llama-index-server',port=8000)
asyncio.run(mcp.run_sse_async())
```
### Get your tools from the MCP server
```python
mcp_client = BasicMCPClient("http://localhost:8000/sse")
mcp_tool_spec = McpToolSpec(
client=mcp_client,
# Optional: Filter the tools by name
# allowed_tools=["tool1", "tool2"],
)
tools = mcp_tool_spec.to_tool_list()
```
### Create an agent and ask a question
```python
llm = OpenAI(model="gpt-4o-mini")
agent = FunctionAgent(
tools=tools,
llm=llm,
system_prompt="You are an agent that knows how to build agents in LlamaIndex.",
)
async def run_agent():
response = await agent.run("How do I instantiate an agent in LlamaIndex?")
print(response)
if __name__ == "__main__":
asyncio.run(run_agent())
```
You're all set! You can now use the agent to answer questions from your LlamaCloud index.
```
--------------------------------------------------------------------------------
/llamacloud_mcp/__init__.py:
--------------------------------------------------------------------------------
```python
```
--------------------------------------------------------------------------------
/claude_desktop_config.json:
--------------------------------------------------------------------------------
```json
{
"mcpServers": {
"llama_index_docs_server": {
"command": "uvx",
"args": [
"llamacloud-mcp@latest",
"--indexes",
"llama-index-docs:LlamaIndex documentation",
"--extract-agents",
"llama-index-docs-extract:LlamaIndex documentation extract agent",
"--project-id",
"<your-project-id>",
"--org-id",
"<your-org-id>",
"--api-key",
"<your-api-key>",
"--transport",
"stdio"
]
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"<your directory you want filesystem tool to have access to>"
]
}
}
}
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[dependency-groups]
dev = [
"pre-commit>=4.2.0"
]
[project]
name = "llamacloud-mcp"
version = "1.0.0"
description = "Expose LlamaCloud services as MCP tools"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"llama-index-indices-managed-llama-cloud>=0.6.9",
"mcp[cli]>=1.6.0",
"python-dotenv>=1.1.0",
"llama-index-tools-mcp>=0.1.0",
"llama-cloud-services",
"click"
]
license = "MIT"
authors = [
{name = "Tuana Celik", email = "[email protected]"},
{name = "Laurie Voss", email = "[email protected]"},
{name = "Logan Markewich", email = "[email protected]"}
]
keywords = [
"mcp",
"llama",
"llamacloud",
"llama-cloud",
"llama-cloud-services"
]
[project.scripts]
llamacloud-mcp = "llamacloud_mcp.main:main"
[tool.hatch.build.targets.sdist]
include = ["llamacloud_mcp/"]
exclude = ["**/BUILD"]
[tool.hatch.build.targets.wheel]
include = ["llamacloud_mcp/"]
exclude = ["**/BUILD"]
```
--------------------------------------------------------------------------------
/llamacloud_mcp/main.py:
--------------------------------------------------------------------------------
```python
import click
import os
from mcp.server.fastmcp import Context, FastMCP
from llama_cloud_services import LlamaExtract
from llama_index.indices.managed.llama_cloud import LlamaCloudIndex
from typing import Awaitable, Callable, Optional
mcp = FastMCP("llama-index-server")
def make_index_tool(
index_name: str, project_id: Optional[str], org_id: Optional[str]
) -> Callable[[Context, str], Awaitable[str]]:
async def tool(ctx: Context, query: str) -> str:
try:
await ctx.info(f"Querying index: {index_name} with query: {query}")
index = LlamaCloudIndex(
name=index_name,
project_id=project_id,
organization_id=org_id,
)
response = await index.as_retriever().aretrieve(query)
return str(response)
except Exception as e:
await ctx.error(f"Error querying index: {str(e)}")
return f"Error querying index: {str(e)}"
return tool
def make_extract_tool(
agent_name: str, project_id: Optional[str], org_id: Optional[str]
) -> Callable[[Context, str], Awaitable[str]]:
async def tool(ctx: Context, file_path: str) -> str:
"""Extract data using a LlamaExtract Agent from the given file."""
try:
await ctx.info(
f"Extracting data using agent: {agent_name} with file path: {file_path}"
)
llama_extract = LlamaExtract(
organization_id=org_id,
project_id=project_id,
)
extract_agent = llama_extract.get_agent(name=agent_name)
result = await extract_agent.aextract(file_path)
return str(result)
except Exception as e:
await ctx.error(f"Error extracting data: {str(e)}")
return f"Error extracting data: {str(e)}"
return tool
@click.command()
@click.option(
"--index",
"indexes",
multiple=True,
required=False,
type=str,
help="Index definition in the format name:description. Can be used multiple times.",
)
@click.option(
"--extract-agent",
"extract_agents",
multiple=True,
required=False,
type=str,
help="Extract agent definition in the format name:description. Can be used multiple times.",
)
@click.option(
"--project-id", required=False, type=str, help="Project ID for LlamaCloud"
)
@click.option(
"--org-id", required=False, type=str, help="Organization ID for LlamaCloud"
)
@click.option(
"--transport",
default="stdio",
type=click.Choice(["stdio", "sse", "streamable-http"]),
help='Transport to run the MCP server on. One of "stdio", "sse", "streamable-http".',
)
@click.option("--api-key", required=False, type=str, help="API key for LlamaCloud")
def main(
indexes: Optional[list[str]],
extract_agents: Optional[list[str]],
project_id: Optional[str],
org_id: Optional[str],
transport: str,
api_key: Optional[str],
) -> None:
api_key = api_key or os.getenv("LLAMA_CLOUD_API_KEY")
if not api_key:
raise click.BadParameter(
"API key not found. Please provide an API key or set the LLAMA_CLOUD_API_KEY environment variable."
)
else:
os.environ["LLAMA_CLOUD_API_KEY"] = api_key
# Parse indexes into (name, description) tuples
index_info = []
if indexes:
for idx in indexes:
if ":" not in idx:
raise click.BadParameter(
f"Index '{idx}' must be in the format name:description"
)
name, description = idx.split(":", 1)
index_info.append((name, description))
# Parse extract agents into (name, description) tuples if provided
extract_agent_info = []
if extract_agents:
for agent in extract_agents:
if ":" not in agent:
raise click.BadParameter(
f"Extract agent '{agent}' must be in the format name:description"
)
name, description = agent.split(":", 1)
extract_agent_info.append((name, description))
# Dynamically register a tool for each index
for name, description in index_info:
tool_func = make_index_tool(name, project_id, org_id)
mcp.tool(name=f"query_{name}", description=description)(tool_func)
# Dynamically register a tool for each extract agent, if any
for name, description in extract_agent_info:
tool_func = make_extract_tool(name, project_id, org_id)
mcp.tool(name=f"extract_{name}", description=description)(tool_func)
mcp.run(transport=transport)
if __name__ == "__main__":
main()
```