mcp-mongo
README.md
# mcp-mongo
MCP Server com acesso a MongoDB — Clean Architecture, Repository Pattern, Motor (async).
Compatível com **qualquer agente MCP**: Claude Code, Claude Desktop, LangChain, LlamaIndex e outros via HTTP.
---
## Índice
- [Visão Geral](#visão-geral)
- [Arquitetura](#arquitetura)
- [Estrutura de Ficheiros](#estrutura-de-ficheiros)
- [Instalação](#instalação)
- [Configuração](#configuração)
- [Conexão com o Banco](#conexão-com-o-banco)
- [Transporte MCP](#transporte-mcp)
- [Referência completa de variáveis](#referência-completa-de-variáveis)
- [Execução](#execução)
- [Capacidades MCP](#capacidades-mcp)
- [Tools](#tools)
- [Resources](#resources)
- [Prompts](#prompts)
- [Segurança](#segurança)
- [Testes](#testes)
- [Integração com Agentes](#integração-com-agentes)
- [Claude Code / Claude Desktop](#claude-code--claude-desktop)
- [LangChain / LlamaIndex](#langchain--llamaindex)
- [Agentes HTTP genéricos](#agentes-http-genéricos)
- [Docker](#docker)
- [Decisões de Arquitetura](#decisões-de-arquitetura)
---
## Visão Geral
Este servidor implementa o **Model Context Protocol (MCP)** para expor um banco de dados MongoDB a modelos de linguagem e agentes de IA.
O agente pode:
- Executar queries `find()` e `aggregate()` com filtros, projeções e ordenação
- Inspecionar databases, collections e inferir esquemas via amostragem de documentos
- Listar índices e obter estatísticas de collections
- Executar operações de escrita (quando explicitamente habilitado)
O servidor é **read-only por padrão** e funciona com **qualquer instância MongoDB** — basta mudar o `.env` ou a variável `MONGODB_URI`.
---
## Arquitetura
```
┌─────────────────────────────────────────────────────────┐
│ Agente (Claude / LangChain / …) │
└────────────────────────┬────────────────────────────────┘
│ MCP Protocol
stdio │ ou HTTP (SSE / streamable-http)
┌────────────────────────▼────────────────────────────────┐
│ MCP Server (FastMCP) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
│ │ Tools │ │ Resources │ │ Prompts │ │
│ └────┬─────┘ └─────┬─────┘ └──────────────────┘ │
│ │ │ │
│ ┌────▼───────────────▼────────────────────────────┐ │
│ │ Repositories │ │
│ │ BaseRepository → QueryRepository │ │
│ │ → SchemaRepository │ │
│ └────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌────────────────────▼────────────────────────────┐ │
│ │ Database (Motor AsyncIOMotorClient) │ │
│ └────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌────────────────────▼────────────────────────────┐ │
│ │ Config (Pydantic Settings + .env) │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ TCP
┌────────────────────────▼────────────────────────────────┐
│ MongoDB │
└─────────────────────────────────────────────────────────┘
```
**Regra de dependência:** cada camada conhece apenas a camada imediatamente abaixo. Tools não conhecem o banco diretamente; Config não conhece ninguém.
---
## Estrutura de Ficheiros
```
mcp-mongo/
│
├── .env # Configuração activa (não commitado)
├── pyproject.toml # Dependências, scripts, ruff, mypy, pytest
├── .gitignore
│
├── src/
│ └── mcp_mongo/
│ ├── __init__.py
│ ├── server.py # Entry point: cria FastMCP e selecciona transporte
│ │
│ ├── config/
│ │ ├── __init__.py
│ │ └── settings.py # Pydantic Settings — MONGODB_URI ou MONGODB_*
│ │
│ ├── database/
│ │ ├── __init__.py
│ │ └── connection.py # Singleton DatabaseClient (Motor) + lifecycle
│ │
│ ├── repositories/
│ │ ├── __init__.py
│ │ ├── base.py # find_many, find_one, aggregate, writes + _serialize BSON
│ │ ├── query_repository.py # find/aggregate com guard de writes
│ │ └── schema_repository.py # Introspecção: databases, collections, schema inferido, índices, stats
│ │
│ ├── tools/
│ │ ├── __init__.py
│ │ ├── query_tools.py # find, find_one, aggregate, count, insert, update, delete
│ │ └── schema_tools.py # list_databases, list_collections, describe_collection, list_indexes, get_collection_stats
│ │
│ ├── resources/
│ │ ├── __init__.py
│ │ └── schema_resources.py # URIs: mongo://databases, mongo://db/{db}/collections, …
│ │
│ └── prompts/
│ ├── __init__.py
│ └── mongo_prompts.py # explore_database, analyse_collection, write_query, write_aggregation, optimise_query
│
└── tests/
├── __init__.py
├── conftest.py
└── tools/
├── test_query_repository.py
└── test_schema_repository.py
```
---
## Instalação
**Pré-requisitos:** Python 3.11+, [uv](https://docs.astral.sh/uv/), MongoDB acessível.
```bash
cd mcp-mongo
# Instalar dependências
uv sync
# Com dependências de desenvolvimento
uv sync --extra dev
```
---
## Configuração
Toda a configuração é feita no ficheiro `.env` na raiz do projeto.
```bash
cp .env .env.local
# editar com os dados do teu ambiente
```
### Conexão com o Banco
Existem duas formas de configurar a conexão — usa a que for mais conveniente:
**Opção A — `MONGODB_URI` (tem prioridade)**
Uma única variável com a URI completa:
```dotenv
MONGODB_URI=mongodb://user:password@host:27017/dbname
```
Também suporta URIs com replica sets e opções adicionais:
```dotenv
MONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/dbname
```
**Opção B — variáveis individuais**
```dotenv
MONGODB_HOST=localhost
MONGODB_PORT=27017
MONGODB_DB=mydb
MONGODB_USER=myuser
MONGODB_PASSWORD=secret
```
> Se `MONGODB_URI` estiver definida, os valores de `MONGODB_HOST`, `MONGODB_PORT`, etc., são ignorados. Caso contrário, as vars individuais são usadas. Se nenhuma for fornecida, o servidor tenta ligar a `localhost:27017/test`.
### Transporte MCP
A variável `MCP_TRANSPORT` define como o servidor comunica com o agente:
| Valor | Protocolo | Endpoint | Indicado para |
|---|---|---|---|
| `stdio` *(padrão)* | stdin/stdout | — | Claude Code, Claude Desktop, agentes locais |
| `sse` | HTTP Server-Sent Events | `http://host:port/sse` | LangChain, LlamaIndex, agentes HTTP legados |
| `streamable-http` | HTTP streaming | `http://host:port/mcp` | Agentes MCP modernos via HTTP |
Para HTTP, define também o host e a porta:
```dotenv
MCP_TRANSPORT=sse
MCP_HOST=0.0.0.0
MCP_PORT=8080
```
### Referência completa de variáveis
| Variável | Padrão | Descrição |
|---|---|---|
| `MONGODB_URI` | — | URI completa (prioridade sobre vars individuais) |
| `MONGODB_HOST` | `localhost` | Host do MongoDB |
| `MONGODB_PORT` | `27017` | Porta |
| `MONGODB_DB` | `test` | Database padrão |
| `MONGODB_USER` | *(vazio)* | Utilizador |
| `MONGODB_PASSWORD` | *(vazio)* | Password |
| `MONGODB_AUTH_SOURCE` | `admin` | Database de autenticação |
| `MONGODB_MIN_POOL_SIZE` | `2` | Conexões mínimas no pool |
| `MONGODB_MAX_POOL_SIZE` | `10` | Conexões máximas no pool |
| `MONGODB_SERVER_SELECTION_TIMEOUT` | `5000` | Timeout de seleção de servidor (ms) |
| `MONGODB_ALLOWED_DATABASES` | *(vazio = todos)* | Databases expostos (vírgula separados) |
| `MONGODB_ALLOW_WRITES` | `false` | Habilita insert/update/delete |
| `MCP_SERVER_NAME` | `mongo-mcp` | Nome do servidor MCP |
| `MCP_LOG_LEVEL` | `INFO` | Nível de log (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
| `MCP_TRANSPORT` | `stdio` | Transporte: `stdio`, `sse`, `streamable-http` |
| `MCP_HOST` | `0.0.0.0` | Host do servidor HTTP (só para SSE/streamable-http) |
| `MCP_PORT` | `8080` | Porta do servidor HTTP (só para SSE/streamable-http) |
---
## Execução
```bash
# Modo stdio (padrão)
uv run mcp-mongo
# Modo SSE — servidor HTTP na porta 8080
MCP_TRANSPORT=sse uv run mcp-mongo
# Modo desenvolvimento com MCP Inspector
uv run mcp dev src/mcp_mongo/server.py
```
---
## Capacidades MCP
### Tools
Tools são funções que o agente chama activamente para executar operações.
---
#### `find`
Executa um `find()` em uma collection e retorna os documentos como JSON.
| Parâmetro | Tipo | Obrigatório | Padrão | Descrição |
|---|---|---|---|---|
| `database` | `str` | sim | — | Nome do database MongoDB |
| `collection` | `str` | sim | — | Nome da collection |
| `filter` | `dict` | não | `{}` | Filtro MongoDB (ex: `{"status": "active"}`) |
| `projection` | `dict` | não | `{}` | Campos a incluir/excluir (ex: `{"name": 1, "_id": 0}`) |
| `sort` | `list` | não | — | Lista de pares `[campo, direção]` (ex: `[["age", -1]]`) |
| `limit` | `int` | não | `100` | Número máximo de documentos |
| `skip` | `int` | não | `0` | Documentos a saltar (paginação) |
```json
// Exemplo
{
"database": "shop",
"collection": "orders",
"filter": {"status": "pending"},
"sort": [["created_at", -1]],
"limit": 20
}
```
---
#### `find_one`
Executa um `find_one()` e retorna o primeiro documento encontrado.
| Parâmetro | Tipo | Obrigatório | Padrão | Descrição |
|---|---|---|---|---|
| `database` | `str` | sim | — | Nome do database |
| `collection` | `str` | sim | — | Nome da collection |
| `filter` | `dict` | não | `{}` | Filtro MongoDB |
| `projection` | `dict` | não | `{}` | Campos a incluir/excluir |
---
#### `aggregate`
Executa um aggregation pipeline e retorna os resultados como JSON.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `database` | `str` | sim | Nome do database |
| `collection` | `str` | sim | Nome da collection |
| `pipeline` | `list` | sim | Lista de estágios de agregação |
```json
// Exemplo — vendas por categoria
{
"database": "shop",
"collection": "orders",
"pipeline": [
{ "$match": { "status": "completed" } },
{ "$group": { "_id": "$category", "total": { "$sum": "$amount" } } },
{ "$sort": { "total": -1 } }
]
}
```
---
#### `count_documents`
Conta documentos que correspondem ao filtro.
| Parâmetro | Tipo | Obrigatório | Padrão | Descrição |
|---|---|---|---|---|
| `database` | `str` | sim | — | Nome do database |
| `collection` | `str` | sim | — | Nome da collection |
| `filter` | `dict` | não | `{}` | Filtro MongoDB (vazio = conta todos) |
---
#### `insert_documents`
Insere um ou mais documentos. Requer `MONGODB_ALLOW_WRITES=true`.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `database` | `str` | sim | Nome do database |
| `collection` | `str` | sim | Nome da collection |
| `documents` | `list` | sim | Lista de documentos a inserir |
Retorna os IDs gerados (`inserted_id` para um, `inserted_ids` para múltiplos).
---
#### `update_documents`
Atualiza documentos correspondentes ao filtro. Requer `MONGODB_ALLOW_WRITES=true`.
| Parâmetro | Tipo | Obrigatório | Padrão | Descrição |
|---|---|---|---|---|
| `database` | `str` | sim | — | Nome do database |
| `collection` | `str` | sim | — | Nome da collection |
| `filter` | `dict` | sim | — | Filtro para selecionar documentos |
| `update` | `dict` | sim | — | Operação de atualização (ex: `{"$set": {...}}`) |
| `upsert` | `bool` | não | `false` | Cria o documento se não existir |
Retorna `matched_count`, `modified_count` e `upserted_id`.
---
#### `delete_documents`
Remove documentos correspondentes ao filtro. Requer `MONGODB_ALLOW_WRITES=true`.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `database` | `str` | sim | Nome do database |
| `collection` | `str` | sim | Nome da collection |
| `filter` | `dict` | sim | Filtro para selecionar documentos a remover |
> **Atenção:** `filter: {}` remove **todos** os documentos da collection.
Retorna `deleted_count`.
---
#### `list_databases`
Lista todos os databases não-sistema disponíveis no servidor MongoDB (`admin`, `local` e `config` são sempre excluídos).
---
#### `list_collections`
Lista collections de um database.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `database` | `str` | sim | Nome do database |
---
#### `describe_collection`
Infere o esquema de uma collection via amostragem de documentos. Retorna campos, tipos dominantes e frequência de presença.
| Parâmetro | Tipo | Obrigatório | Padrão | Descrição |
|---|---|---|---|---|
| `database` | `str` | sim | — | Nome do database |
| `collection` | `str` | sim | — | Nome da collection |
| `sample_size` | `int` | não | `100` | Número de documentos a amostrar |
Exemplo de saída:
```json
[
{ "field": "_id", "dominant_type": "string", "presence_pct": 100.0 },
{ "field": "name", "dominant_type": "string", "presence_pct": 100.0 },
{ "field": "age", "dominant_type": "int", "presence_pct": 98.0 },
{ "field": "address.city", "dominant_type": "string", "presence_pct": 75.0 },
{ "field": "address.zip", "dominant_type": "string", "presence_pct": 60.0 },
{ "field": "tags", "dominant_type": "array", "presence_pct": 45.0 }
]
```
Campos de documentos embutidos (`address.city`) são expandidos automaticamente.
---
#### `list_indexes`
Lista os índices de uma collection com unicidade, esparsidade e campos cobertos.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `database` | `str` | sim | Nome do database |
| `collection` | `str` | sim | Nome da collection |
---
#### `get_collection_stats`
Retorna estatísticas de uma collection via `$collStats` (MongoDB 3.6+).
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| `database` | `str` | sim | Nome do database |
| `collection` | `str` | sim | Nome da collection |
Inclui: `document_count`, `size_kb`, `avg_document_size_bytes`, `storage_size_kb`, `index_count`, `total_index_size_kb`.
---
### Resources
Resources expõem dados como URIs navegáveis — o agente lê-os para obter contexto antes de agir.
| URI | Descrição |
|---|---|
| `mongo://databases` | Lista todos os databases não-sistema |
| `mongo://db/{database}/collections` | Lista collections de um database |
| `mongo://db/{database}/collection/{collection}` | Schema inferido + índices + stats de uma collection |
---
### Prompts
Prompts são templates reutilizáveis que guiam o agente numa tarefa complexa.
| Prompt | Parâmetros | Descrição |
|---|---|---|
| `explore_database` | — | Roteiro para explorar um banco MongoDB desconhecido |
| `analyse_collection` | `database`, `collection` | Análise detalhada de uma collection |
| `write_query` | `question` | Gera um `find()` ou `aggregate()` a partir de linguagem natural |
| `write_aggregation` | `question` | Gera um aggregation pipeline para análise de dados |
| `optimise_query` | `database`, `collection`, `filter_hint` | Analisa e sugere optimizações para uma query |
---
## Segurança
| Mecanismo | Detalhe |
|---|---|
| **Read-only por padrão** | Writes bloqueados por guard em cada método — sem acesso ao banco |
| **Databases permitidos** | `MONGODB_ALLOWED_DATABASES` limita a exposição de dados |
| **Serialização BSON segura** | `ObjectId`, `Decimal128` e outros tipos BSON são convertidos para strings — sem falhas de serialização |
| **Logs sanitizados** | Password nunca aparece em logs (`safe_uri`) |
| **Erros sanitizados** | Exceções retornam mensagem simples ao agente, sem stack trace |
| **Pool limitado** | `max_pool_size=10` por padrão — evita saturar o servidor |
| **Ping no startup** | Falha imediatamente se as credenciais ou o host estiverem incorrectos |
Para habilitar escritas:
```dotenv
MONGODB_ALLOW_WRITES=true
```
---
## Testes
Os testes são de integração e requerem uma instância MongoDB acessível. Usam o database temporário `test_mcp_tmp`, que é criado e destruído automaticamente.
```bash
# Todos os testes (usa .env por padrão)
uv run pytest
# Sobrepor a URI para os testes
TEST_MONGODB_URI=mongodb://localhost:27017/test uv run pytest
# Com coverage
uv run pytest --cov=src/mcp_mongo --cov-report=html
# Verbose
uv run pytest -v
```
---
## Integração com Agentes
### Claude Code / Claude Desktop
Adicionar ao `~/.claude.json` (user-level, disponível em todos os projetos):
```json
{
"mcpServers": {
"mongo": {
"type": "stdio",
"command": "uv",
"args": [
"--directory", "/caminho/para/mcp-mongo",
"run", "mcp-mongo"
],
"env": {
"MONGODB_URI": "mongodb://user:pass@host:27017/mydb"
}
}
}
}
```
---
### LangChain / LlamaIndex
Iniciar o servidor em modo SSE:
```bash
MCP_TRANSPORT=sse MCP_PORT=8080 uv run mcp-mongo
```
Conectar a partir do agente:
```python
# LangChain + MCP
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"mongo": {
"url": "http://localhost:8080/sse",
"transport": "sse",
}
})
tools = await client.get_tools()
```
---
### Agentes HTTP genéricos
Iniciar em modo `streamable-http`:
```bash
MCP_TRANSPORT=streamable-http MCP_PORT=8080 uv run mcp-mongo
```
Endpoint disponível em `http://localhost:8080/mcp`.
---
### Docker
```dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install uv && uv sync
EXPOSE 8080
CMD ["uv", "run", "mcp-mongo"]
```
```bash
docker run -p 8080:8080 \
-e MONGODB_URI=mongodb://user:pass@host:27017/mydb \
-e MCP_TRANSPORT=sse \
mcp-mongo
```
---
## Decisões de Arquitetura
### Conexão dinâmica — `MONGODB_URI` vs `MONGODB_*`
`MONGODB_URI` é o padrão de facto em ambientes cloud (MongoDB Atlas, Railway, Render, etc.) e suporta opções avançadas como replica sets e `mongodb+srv`. As vars individuais são mais legíveis para desenvolvimento local. O `model_validator` do Pydantic extrai os campos da URI se fornecida, garantindo que a URI interna está sempre correta independentemente de qual forma foi usada.
### Motor em vez de PyMongo síncrono
Motor é o driver oficial async do MongoDB para Python. Construído sobre PyMongo mas com interface `asyncio` nativa — não é um wrapper de thread pool. Necessário para coexistir com FastMCP que opera em loop de eventos asyncio.
### Schema por amostragem — sem `$jsonSchema`
MongoDB é schemaless por design. A abordagem escolhida (`$sample` + inferência de tipos) devolve um esquema descritivo de documentos reais sem exigir validação de schema configurada no banco. O `presence_pct` indica quais campos são obrigatórios na prática vs opcionais, o que é informação mais útil para o agente do que uma definição formal.
### Serialização BSON centralizada em `_serialize()`
Tipos BSON como `ObjectId`, `Decimal128` e `datetime` não são serializáveis em JSON nativo. A função `_serialize()` no `BaseRepository` converte recursivamente todos os valores antes de retornar ao agente, eliminando erros de serialização em qualquer tool ou resource.
### Transporte configurável
O protocolo MCP suporta vários transportes. `stdio` é o padrão para agentes locais (Claude Code lança o processo e comunica por stdin/stdout). Para agentes remotos ou multi-tenant, `sse` e `streamable-http` expõem o servidor como um serviço HTTP sem qualquer alteração de código — só muda a variável de ambiente.
### `src/` layout
Previne que o Python encontre o módulo via path local sem instalação — o que mascararia erros de packaging e tornaria os testes menos fiáveis.
### Repository Pattern
Isola as operações Motor das tools MCP. As tools expressam intenção (`describe_collection`), os repositórios expressam implementação (`$sample` + inferência). Trocar a estratégia de introspecção não exige tocar nas tools.
### Lifespan para o client Motor
Garante que o client abre (e valida com `ping`) antes do servidor aceitar requests, e fecha sempre ao terminar — mesmo com `Ctrl+C` ou sinal do OS. Não há risco de conexões a vazar ou de o agente receber requests antes do banco estar pronto.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues