inventory-mcp
by ruanderson1
README.md
# inventory-mcp
Servidor MCP de demonstração para consultas de inventário, desenvolvido em Python com FastMCP. O projeto apoia o estudo dos principais conceitos do Model Context Protocol (MCP), com separação entre transporte, interface MCP, regras de negócio, validação e dados.
O escopo atual é intencionalmente somente leitura: o servidor permite consultar produtos e quantidades em estoque, sem operações de cadastro, alteração ou exclusão.
## Tecnologias
- Python 3.11+
- FastMCP
- Pydantic
- pytest
- Ruff
## Arquitetura
- `app/server.py`: cria o servidor FastMCP, registra as tools e inicia o transporte `stdio` ou SSE.
- `app/client.py`: cliente demonstrativo que lista e chama as tools por `stdio` ou SSE.
- `app/tools/`: interface MCP; valida entradas, delega ao serviço e transforma erros esperados em respostas estáveis.
- `app/services/`: regras de consulta e carregamento do inventário.
- `app/schemas/`: modelos Pydantic que definem e validam os contratos de produto e estoque.
- `app/data/`: fonte local de dados, atualmente o arquivo `inventory.json`.
- `tests/`: testes automatizados do serviço, das tools e da configuração do servidor.
```text
Client → MCP Server → Tool → InventoryService → inventory.json
```
As tools não acessam o arquivo diretamente. Elas delegam as regras de negócio ao `InventoryService`.
## Tools MCP
### `get_product`
- **Propósito:** consultar os dados completos de um produto pelo nome.
- **Entrada:** `name` (`string` não vazia).
- **Saída em caso de sucesso:** objeto com `name`, `quantity` e `price`.
- **Saída para produto inexistente:** objeto com `error: "product_not_found"` e uma `message` descritiva.
- **Descrição MCP:** `Use this tool to retrieve the complete data of a product by name, including its price and stock quantity.`
- **Classificação:** somente leitura.
```json
{
"name": "Mouse",
"quantity": 25,
"price": 89.9
}
```
### `get_stock`
- **Propósito:** consultar somente a quantidade atual de um produto pelo nome.
- **Entrada:** `name` (`string` não vazia).
- **Saída em caso de sucesso:** objeto com `quantity`.
- **Saída para produto inexistente:** objeto com `error: "product_not_found"` e uma `message` descritiva.
- **Descrição MCP:** `Use this tool to retrieve only the current stock quantity of a product by name.`
- **Classificação:** somente leitura.
```json
{
"quantity": 25
}
```
## Validação de entrada
As tools exigem que `name` seja uma string com conteúdo. Nomes vazios ou formados apenas por espaços são rejeitados antes da consulta. O serviço aplica `strip()` para remover espaços nas extremidades e `casefold()` para comparar nomes sem diferenciação entre maiúsculas e minúsculas.
O Pydantic valida os registros carregados do JSON e os modelos de saída. Um produto deve ter nome não vazio, quantidade inteira não negativa e preço numérico não negativo. A rejeição de nomes de consulta vazios é feita por `_validate_product_name()`. Registros inválidos interrompem o carregamento com erro explícito.
## Tratamento de erros
O `InventoryService` lança `ProductNotFoundError` quando não encontra o produto solicitado. As tools capturam esse erro esperado e retornam um payload previsível:
```json
{
"error": "product_not_found",
"message": "Product not found: Monitor"
}
```
Erros de entrada, como nome vazio ou valor que não seja string, não são ocultados: são reportados como erros da chamada da tool.
## Transportes MCP
- **`stdio`:** comunica-se pela entrada e saída padrão. Neste projeto, o cliente inicia o servidor FastMCP como subprocesso, realiza as chamadas e encerra o processo ao finalizar.
- **SSE:** comunica-se por um endpoint HTTP com Server-Sent Events. Servidor e cliente rodam em processos separados; por padrão, o servidor atende em `http://127.0.0.1:8000/sse`.
## Como executar
Os comandos abaixo usam PowerShell e devem ser executados na raiz do projeto.
### Criar e ativar o ambiente virtual
```powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
```
### Instalar as dependências
```powershell
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
```
### Executar via `stdio`
O cliente usa `stdio` por padrão e inicia o servidor como subprocesso:
```powershell
.\.venv\Scripts\python.exe -m app.client
```
Para iniciar apenas o servidor diretamente:
```powershell
.\.venv\Scripts\python.exe -m app.server --transport stdio
```
### Executar via SSE
Inicie o servidor em um terminal (`sse` é o transporte padrão do servidor):
```powershell
.\.venv\Scripts\python.exe -m app.server
```
O comando explícito equivalente é `python -m app.server --transport sse`. Em outro terminal, conecte o cliente:
```powershell
.\.venv\Scripts\python.exe -m app.client --transport sse
```
O cliente aceita outro endpoint por meio de `--url`.
### Executar os testes
```powershell
.\.venv\Scripts\pytest.exe
```
### Executar o Ruff
```powershell
.\.venv\Scripts\ruff.exe check .
.\.venv\Scripts\ruff.exe format --check .
```
## Tool Risk Assessment
As tools atuais são somente leitura e não podem criar, alterar ou excluir dados. Essa decisão reduz a superfície de risco, mas não elimina possíveis impactos sobre confidencialidade e disponibilidade.
| Tool | Dados acessados | Operação | Risco atual | Possível impacto de uso indevido |
| --- | --- | --- | --- | --- |
| `get_product` | Nome, preço e quantidade | Leitura | Baixo | Exposição ou enumeração de informações do inventário |
| `get_stock` | Quantidade disponível | Leitura | Baixo | Enumeração de estoque e acompanhamento excessivo da disponibilidade |
Chamadas em grande volume ainda podem consumir recursos do servidor. Alterações futuras nas tools ou nos dados retornados devem ser acompanhadas de uma nova avaliação de risco.
### Trust Boundary
Os argumentos recebidos de um cliente MCP são tratados como entrada não confiável.
```text
MCP Client
↓
MCP Server
↓
Tool
↓
InventoryService
↓
inventory.json
```
A validação acontece antes que os argumentos sejam utilizados pela camada de serviço. O servidor não assume que os dados enviados pelo cliente são válidos apenas porque chegaram pelo protocolo MCP. Os registros do `inventory.json` também são tratados como entrada externa e validados pelo Pydantic durante o carregamento.
### MCP Tool Annotations
As tools são classificadas semanticamente de acordo com seu comportamento. As duas operações atuais declaram:
```text
readOnlyHint=true
openWorldHint=false
```
`readOnlyHint=true` informa ao cliente MCP que a operação não pretende modificar estado.
`openWorldHint=false` indica que a tool trabalha sobre um domínio fechado e conhecido — neste caso, o inventário local — em vez de consultar sistemas externos ou fontes abertas.
Essas annotations funcionam como **metadados e hints para clientes MCP**, não como mecanismos de segurança. Um cliente não deve confiar nelas como substituto de validação, autorização ou outros controles reais.
### Risco de tools de escrita
Uma futura operação como:
```text
update_stock(name, quantity)
```
teria risco significativamente maior porque modificaria o estado persistente do sistema.
Uma chamada incorreta ou maliciosa poderia alterar o produto errado, registrar valores inválidos ou permitir mudanças não autorizadas. Uma futura tool como `update_stock` exigiria validação rigorosa, autenticação, autorização, auditoria e tracing. Operações destrutivas também exigiriam confirmação ou aprovação quando aplicável.
### Risco por transporte
No `stdio`, o servidor é iniciado localmente como subprocesso do cliente, reduzindo a exposição de rede. No SSE, servidor e cliente são processos separados e a comunicação usa um endpoint HTTP. Uma eventual publicação desse endpoint fora do host local exigiria controles adicionais de acesso e disponibilidade.
## Testes
A suíte atual valida:
- carregamento, busca, normalização e erros do `InventoryService`;
- retornos das tools e conversão de produto inexistente em erro previsível;
- rejeição de nomes vazios e valores que não sejam strings;
- rejeição de registros de inventário inválidos pelo Pydantic;
- registro das tools no servidor;
- seleção e configuração dos transportes SSE e `stdio`;
- integração real via `stdio`, incluindo `list_tools()`, chamada de `get_stock` e leitura das annotations MCP.
Os cenários incluem produtos existentes e inexistentes, espaços nas extremidades, diferenças entre maiúsculas e minúsculas e entradas inválidas. No teste ponta a ponta, um cliente FastMCP real inicia o servidor como subprocesso, valida `readOnlyHint` e `openWorldHint`, consulta o estoque carregado do JSON local e encerra a conexão pelo context manager.
## Qualidade de código
O projeto utiliza type hints, separa responsabilidades entre MCP, serviços, schemas e dados, e mantém dependências mínimas. O pytest cobre os comportamentos implementados, enquanto o Ruff verifica lint, imports, compatibilidade com Python 3.11 e formatação.
## Limitações atuais
- Os dados são carregados de um arquivo JSON local.
- Não existe banco de dados.
- Não existe integração com IA ou LLM.
- Não existem tools de escrita.
- Não há autenticação ou autorização.
## Possíveis evoluções
- tracing e logging estruturado, mantidos fora do escopo atual para preservar o foco didático do projeto;
- suporte a Streamable HTTP;
- persistência em banco de dados;
- autenticação e autorização;
- tools de escrita com safeguards;
- integração futura com LLM.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues