XLSX Tools MCP
Um servidor MCP para ler, analisar e editar com precisão pastas de trabalho do Excel (.xlsx), preservando a estrutura existente, estilos, fórmulas e a integridade dos dados. Suporta operações de células, gerenciamento de planilhas e de linhas/colunas, formatação, agregação de dados, recálculo de fórmulas e acesso seguro e concorrente a arquivos.
Documentação
xlsx-tools-mcp
Um servidor MCP para ler e escrever arquivos Excel (.xlsx) com alta precisão, preservando a estrutura, estilos e fórmulas existentes do arquivo.
Visão Geral
xlsx-tools-mcp expõe 20 ferramentas do Model Context Protocol (MCP) que dão a um agente LLM acesso preciso de leitura e escrita que preserva a estrutura de arquivos Excel .xlsx. Ele roda como um servidor MCP padrão via stdio: você o instala e registra com um cliente MCP (Claude Code, OpenCode, etc.), e o agente do cliente pode listar planilhas, ler intervalos de células, pesquisar valores, agregar dados, escrever células/fórmulas, gerenciar planilhas/linhas/colunas, aplicar estilos e forçar o recálculo de fórmulas.
Ele é construído em torno do princípio de que editar uma pasta de trabalho existente não deve destruir o que não toca.
Recursos
- Escritas que preservam a estrutura via openpyxl — as escritas carregam a pasta de trabalho existente e a salvam de volta, preservando estilos, células mescladas, comentários e qualquer aspecto que a edição não toque.
- Resultados de fórmulas nunca desatualizados via recálculo do LibreOffice — openpyxl escreve strings de fórmulas, mas nunca as avalia. Após cada escrita de valor/fórmula, o servidor executa uma passagem headless do LibreOffice para recalcular resultados reais e então retorna
errors_found— quaisquer valores de erro do Excel (#REF!,#DIV/0!,#N/A, …) produzidos pelo recálculo. - Leituras rápidas via python-calamine — um parser baseado em Rust para inferência de tipos precisa e rápida, com fallback automático para openpyxl quando você precisa de fórmulas/estilos/comentários ou quando o calamine não consegue analisar o arquivo.
- Agrupamento/agregação baseado em pandas —
aggregate_sheetagrupa e agrega sobre o caminho de leitura normal, então células mescladas e estilos no intervalo de origem são preservados antes do achatamento. - Bloqueio por arquivo — chamadas de ferramentas concorrentes (ou outros processos) que tocam a mesma pasta de trabalho são serializadas via um arquivo
<path>.lockirmão (filelock), então escritas nunca se intercalam e corrompem o arquivo. - Proteção contra bombas XML — o pacote
defusedxmlé uma dependência automática; openpyxl o detecta e usa seu parser XML endurecido, então XMLxlsxhostil não pode se expandir em exaustão de recursos. - Pré-carregar arquivos na inicialização — defina
XLSX_MCP_FILESpara pré-carregar uma ou mais pastas de trabalho; as ferramentas podem então ser chamadas compathomitido ou com um alias curto em vez de um caminho completo do sistema de arquivos.
Estatísticas de Download
Ao vivo via pypistats.org, downloads sem espelhamento. Estes contam eventos de download, não usuários únicos ou instalações — um usuário pode acionar muitos downloads (CI, reinstalações, reconstruções Docker, espelhamentos).
Arquitetura
┌──────────────────────── Supervisor (MCP transport, stdio)
│ src/xlsx_tools_mcp/server.py 20 MCP tools + instructions
│ src/xlsx_tools_mcp/settings.py env vars, preloaded files, path resolution
│ src/xlsx_tools_mcp/locking.py per-file <path>.lock serialization
│ src/xlsx_tools_mcp/errors.py domain error types
│ src/xlsx_tools_mcp/recalc.py LibreOffice headless recalc + error scanning
│
├─ Read path
│ src/xlsx_tools_mcp/io/reader.py calamine primary → openpyxl fallback
│ src/xlsx_tools_mcp/io/transform.py pandas aggregation on read results
│
└─ Write path
src/xlsx_tools_mcp/io/writer.py openpyxl → LibreOffice recalc → scan errors
A camada de io (io/) é deliberadamente desacoplada do transporte MCP (server.py). Cada ferramenta MCP é um wrapper fino que resolve o caminho alvo, adquire o bloqueio por arquivo e chama uma função da camada de io. Isso mantém a lógica central independente do MCP, para que possa ser testada diretamente (veja tests/).
A compensação do recálculo
Após uma escrita que toca valores de células ou fórmulas, o servidor executa soffice --headless --convert-to xlsx no arquivo para que cada fórmula obtenha um valor computado real. Este ciclo reexporta a pasta de trabalho inteira e recalcula fórmulas — é uma compensação, não uma garantia de preservação bit a bit. Recursos que openpyxl preservaria de outra forma podem não sobreviver de forma idêntica: tabelas dinâmicas, gráficos, validação de dados, alguns formatos e alguns nomes definidos.
Se você está trabalhando em uma pasta de trabalho estruturalmente complexa onde esse risco importa, você pode passar recalculate=False nas ferramentas de escrita de valor/fórmula (write_cells, append_rows, insert_rows, delete_rows, insert_columns, delete_columns) para salvar apenas com openpyxl e pular o ciclo completamente.
Requisitos
- Python ≥ 3.10
- LibreOffice — opcional, mas recomendado. Necessário apenas para recálculo de fórmulas. Sem ele, as escritas ainda funcionam (salvas via openpyxl), mas as fórmulas não são recalculadas e um aviso é retornado no campo
message.
Instale o LibreOffice:
# macOS
brew install --cask libreoffice
# Debian / Ubuntu
sudo apt-get install -y libreoffice-calc
O servidor encontra o LibreOffice verificando soffice / libreoffice em PATH e o local de instalação padrão do macOS (/Applications/LibreOffice.app/Contents/MacOS/soffice).
Instalação
O servidor fala transporte stdio (MCP padrão): após a instalação, ele aguarda um cliente MCP conectar e chamar ferramentas. Você normalmente não o executa você mesmo; você o registra com um cliente.
1. Do PyPI via uvx (recomendado — sem clone)
uvx xlsx-tools-mcp
uvx busca e executa o pacote publicado sem poluir seu projeto. Esta é a maneira mais simples de ativar um cliente MCP (veja os trechos de configuração abaixo).
2. Do código-fonte
git clone https://github.com/ruriazz/xlsx-tools-mcp.git
cd xlsx-tools-mcp
uv sync
# run the server (useful for local dev / debugging):
uv run xlsx-tools-mcp
3. Via pip
pip install xlsx-tools-mcp
Isso instala o ponto de entrada do console, então você pode executar o servidor diretamente:
xlsx-tools-mcp
Configuração para clientes MCP
O registro mais simples para cada cliente usa uvx xlsx-tools-mcp (sem clone, sempre a versão publicada).
Claude Code
claude mcp add xlsx-tools-mcp -- uvx xlsx-tools-mcp
Ou via .mcp.json no seu projeto:
{
"mcpServers": {
"xlsx-tools-mcp": { "command": "uvx", "args": ["xlsx-tools-mcp"] }
}
}
OpenCode
Em opencode.json (projeto) ou ~/.config/opencode/opencode.json (global):
{
"mcp": {
"xlsx-tools-mcp": { "type": "local", "command": ["uvx", "xlsx-tools-mcp"], "enabled": true }
}
}
Ao executar a partir de um clone do código-fonte
Se você clonou o repositório em vez de instalar do PyPI, aponte o cliente para seu checkout local trocando uvx xlsx-tools-mcp pela forma dinâmica uv run (use o caminho absoluto para o clone):
Claude Code .mcp.json:
{
"mcpServers": {
"xlsx-tools-mcp": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/xlsx-reader", "run", "xlsx-tools-mcp"]
}
}
}
OpenCode:
{
"mcp": {
"xlsx-tools-mcp": {
"type": "local",
"command": ["uv", "--directory", "/absolute/path/to/xlsx-reader", "run", "xlsx-tools-mcp"],
"enabled": true
}
}
}
Substitua /absolute/path/to/xlsx-reader pela localização real do seu clone.
Pré-carregando arquivos (XLSX_MCP_FILES)
Defina a variável de ambiente XLSX_MCP_FILES na seção env da configuração do servidor MCP (não no seu shell interativo — o servidor é iniciado pelo cliente) para pré-carregar pastas de trabalho na inicialização. Formato: entradas alias=absolute/path separadas por vírgula, ou caminhos absolutos simples:
XLSX_MCP_FILES=name=/abs/path/to/name.xlsx,report=/data/report.xlsx
Caminhos simples recebem um alias padrão baseado no nome do arquivo:
XLSX_MCP_FILES=/abs/path/to/sales.xlsx
Com alias/filename como o alias:
- Um arquivo configurado → cada ferramenta pode ser chamada com
pathcompletamente omitido. - Múltiplos arquivos configurados → passe o alias (ou nome do arquivo) como
path. list_configured_files()retorna o mapeamento alias → caminho absoluto.- Caminhos absolutos e relativos simples ainda funcionam para arquivos que você não pré-carregou.
Claude Code — .mcp.json com pré-carregamento:
{
"mcpServers": {
"xlsx-tools-mcp": {
"command": "uvx",
"args": ["xlsx-tools-mcp"],
"env": {
"XLSX_MCP_FILES": "report=/data/report.xlsx,sales=/data/sales.xlsx"
}
}
}
}
OpenCode com pré-carregamento:
{
"mcp": {
"xlsx-tools-mcp": {
"type": "local",
"command": ["uvx", "xlsx-tools-mcp"],
"env": { "XLSX_MCP_FILES": "report=/data/report.xlsx,sales=/data/sales.xlsx" },
"enabled": true
}
}
}
Referência de ferramentas
Todas as 20 ferramentas. Salvo indicação em contrário, path aceita um caminho do sistema de arquivos, um alias/nome de arquivo pré-carregado, ou pode ser omitido quando exatamente um arquivo está pré-carregado. create_workbook é a exceção — seu path é obrigatório porque um novo arquivo nunca é pré-carregado.
Formato da resposta (todas as ferramentas de escrita): cada ferramenta de escrita retorna
{"saved": bool, "recalculated": bool, "errors_found": list, "message": str}. Quando não vazio,errors_foundé uma lista de{"sheet": "...", "cell": "B2", "error": "#DIV/0!"}.
Inspecionar / Ler
| Ferramenta | Descrição |
|---|---|
list_configured_files() | Lista arquivos pré-carregados na inicialização via XLSX_MCP_FILES, como um mapa alias → caminho absoluto. Chame isso primeiro se não tiver certeza do que está disponível. |
list_sheets(path?) | Lista cada planilha na pasta de trabalho com contagens aproximadas de linhas/colunas (calamine). |
get_workbook_info(path?) | Metadados no nível da pasta de trabalho: dimensões exatas por planilha, max_row/max_column, estado da planilha, a planilha ativa e nomes definidos. |
read_sheet(sheet, cell_range?, max_rows?, path?) | Lê valores de células como uma matriz 2D endereçada absolutamente a partir de A1. cell_range é um intervalo opcional no estilo A1 (ex.: "B2:F20"); omita para ler toda a área usada. max_rows opcionalmente limita o número de linhas retornadas. |
get_cell(sheet, cell, path?) | Detalhe completo para uma única célula: valor (computado em cache), fórmula, formato numérico, fonte (negrito/itálico/tamanho/cor), cor de preenchimento, estado de mesclagem, comentário. |
search_workbook(query, sheet?, match_case?, limit?, path?) | Pesquisa de substring em uma ou todas as planilhas. sheet restringe a uma planilha; match_case=True torna sensível a maiúsculas/minúsculas; limit limita correspondências. Retorna {"sheet", "cell", "value"}. |
aggregate_sheet(sheet, group_by, agg, cell_range?, has_header?, path?) | Agrupa e agrega com pandas. group_by é uma lista de nomes de colunas (tirados da linha de cabeçalho); agg mapeia nome da coluna → função de agregação, ex.: {"amount": "sum"}. has_header=True (padrão) lê nomes de colunas da primeira linha. Retorna {columns, records, row_count}. |
Escrita
| Ferramenta | Descrição |
|---|---|
create_workbook(path, sheets?, overwrite?) | Cria uma nova pasta de trabalho .xlsx/.xlsm. sheets padrão é ["Sheet1"]. overwrite=True substitui um arquivo existente. path é obrigatório (novos arquivos nunca são pré-carregados). |
write_cells(sheet, cells, create_sheet_if_missing?, recalculate?, path?) | Escreve valores e/ou fórmulas em células específicas. cells é uma lista de {"cell": "A1", "value": ...} ou {"cell": "B1", "formula": "=A1*2"}. Opcionalmente crie a planilha primeiro; recalculate=True (padrão) executa o recálculo do LibreOffice. |
append_rows(sheet, rows, create_sheet_if_missing?, recalculate?, path?) | Acrescenta linhas após a última linha usada. rows é uma lista de linhas, cada uma uma lista de valores de células em ordem de coluna. |
create_sheet(sheet, index?, path?) | Adiciona uma nova planilha vazia. index é uma posição de inserção baseada em zero; omita para acrescentar no final. |
delete_sheet(sheet, path?) | Exclui uma planilha. Falha se for a única planilha restante. |
insert_rows(sheet, start_row, count?, recalculate?, path?) | Insere linhas em branco antes de start_row (baseado em 1), deslocando linhas existentes para baixo. count padrão é 1. |
delete_rows(sheet, start_row, count?, recalculate?, path?) | Exclui linhas começando em start_row (baseado em 1), deslocando linhas abaixo para cima. count padrão é 1. |
insert_columns(sheet, start_column, count?, recalculate?, path?) | Insere colunas em branco antes de start_column (baseado em 1), deslocando colunas existentes para a direita. count padrão é 1. |
delete_columns(sheet, start_column, count?, recalculate?, path?) | Exclui colunas começando em start_column (baseado em 1), deslocando colunas à direita para a esquerda. count padrão é 1. |
merge_cells(sheet, cell_range, path?) | Mescla um intervalo retangular (ex.: "A1:C1") em uma única célula. |
unmerge_cells(sheet, cell_range, path?) | Desfaz uma mesclagem em um intervalo previamente mesclado. |
set_cell_style(sheet, cell_range, style, path?) | Aplica formatação a um intervalo (ex.: "A1:D1"). Chaves style: bold, italic, font_size, font_color (RGB hex, ex.: "FF0000"), bg_color (RGB hex), horizontal, vertical (alinhamento), border ("thin", "medium", "thick", …), number_format (ex.: "#,##0.00"). |
recalculate_workbook(path?) | Força uma passagem de recálculo headless do LibreOffice e relata quaisquer erros de fórmula encontrados. |
Exemplo de payload — write_cells
Uma chamada escrevendo uma fórmula e um valor:
{
"sheet": "Sheet1",
"cells": [
{ "cell": "A1", "value": 100 },
{ "cell": "B1", "formula": "=A1*2" }
],
"recalculate": true,
"path": "/data/budget.xlsx"
}
Resposta correspondente:
{
"saved": true,
"recalculated": true,
"errors_found": [],
"message": "Recalculated with LibreOffice headless."
}
Se uma fórmula tocada por isso produziu um erro, errors_found pareceria:
{
"saved": true,
"recalculated": true,
"errors_found": [
{ "sheet": "Sheet1", "cell": "C5", "error": "#DIV/0!" }
],
"message": "Recalculated with LibreOffice headless."
}
Segurança e concorrência
- Proteção contra bombas XML —
defusedxmlé uma dependência automática deste pacote. openpyxl o auto-detecta e usa seu parser XML endurecido, então um.xlsxmalicioso (um zip de XML) não pode acionar exaustão de recursos por expansão de entidades. Nenhuma configuração necessária. - Bloqueio por arquivo — cada leitura/escrita adquire um arquivo
<path>.lockirmão (viafilelock). Chamadas de ferramentas concorrentes ou outros processos que tocam a mesma pasta de trabalho são serializados para que escritas nunca se intercalem e corrompam o arquivo. - Timeout de recálculo —
XLSX_MCP_RECALC_TIMEOUT(segundos, padrão60) limita quanto tempo a passagem de recálculo do LibreOffice pode executar. - Timeout de bloqueio —
XLSX_MCP_LOCK_TIMEOUT(segundos, padrão10) limita quanto tempo uma ferramenta esperará para adquirir o bloqueio por arquivo antes de falhar.
Solução de problemas
errors_foundestá vazio mesmo com minha fórmula quebrada — o recálculo provavelmente não foi executado. Verifique o campomessage: se ele disser que o LibreOffice não foi encontrado, o arquivo foi salvo via openpyxl como está e as fórmulas não foram recalculadas (os valores em cache podem estar desatualizados). Instale o LibreOffice (veja Requisitos).- O recálculo está lento ou expira — aumente
XLSX_MCP_RECALC_TIMEOUT(padrão 60s). Em caso de timeout, o arquivo ainda é salvo, masrecalculatedseráfalseemessageindica que o recálculo expirou. LockTimeoutErrorem acesso concorrente — outra operação está segurando o bloqueio. AumenteXLSX_MCP_LOCK_TIMEOUT(padrão 10s) ou tente novamente quando a outra operação terminar.- "Sheet not found" — a mensagem de erro lista os nomes das planilhas disponíveis, para que você possa escolher a correta.
pathobrigatório / nenhum arquivo configurado — você chamou uma ferramenta sempath, mas nenhum (ou múltiplos) arquivo está pré-carregado. Pré-carregue um arquivo viaXLSX_MCP_FILES, passe um alias explícito ou passe um caminho bruto.
Desenvolvimento / Contribuição
Veja CONTRIBUTING.md. Execute a suíte de testes com:
uv run pytest