QGIS MCP Server
by israelwcg011
README.md
# QGIS MCP Server
Plugin do QGIS Desktop que sobe um servidor MCP **dentro** da sessão do QGIS em
execução, expondo o projeto aberto do usuário a clientes MCP.
Não é um servidor standalone e não roda headless: as tools operam sobre o
projeto que está na tela, no estado em que ele se encontra. Uma camada criada
por geoprocessamento aparece no painel do usuário na hora.
## Requisitos
- QGIS Desktop 3.28 ou superior (Windows)
- `fastmcp` e `uvicorn` instalados **no Python embutido do QGIS**
O PyQGIS não é instalável via pip — ele vem acoplado ao build do QGIS. Por isso
as dependências vão para o Python do QGIS, não para o Python do sistema.
## Instalação
### 1. Dependências no Python do QGIS
Abra o **OSGeo4W Shell** (vem com o QGIS, procure no menu Iniciar) e rode:
```bat
python -m pip install fastmcp uvicorn
```
Se der erro de permissão, abra o shell como administrador. Para confirmar que
foi para o lugar certo:
```bat
python -c "import fastmcp, uvicorn; print(fastmcp.__version__)"
```
### 2. Copiar o plugin
O destino é a pasta de plugins do perfil do QGIS:
```
%APPDATA%\QGIS\QGIS3\profiles\default\python\plugins
```
Ela só existe depois que o QGIS foi aberto pelo menos uma vez. Se você usa um
perfil diferente do padrão, troque `default` pelo nome dele — a lista está em
**Configurações → Perfis do usuário**.
No **PowerShell**, a partir da raiz do repositório:
```powershell
$dest = "$env:APPDATA\QGIS\QGIS3\profiles\default\python\plugins\qgis_mcp"
robocopy .\src\qgis_mcp $dest /MIR /XD __pycache__ /XF *.pyc
```
O `/MIR` espelha a pasta: arquivos apagados do repositório somem do destino
também. Sem isso, um módulo antigo continuaria sendo importado pelo QGIS. O
robocopy termina com código de saída 1 quando copiou algo — é sucesso, não erro.
Se preferir o Explorador de Arquivos: aperte **Win+R**, cole
`%APPDATA%\QGIS\QGIS3\profiles\default\python\plugins` e copie para lá a
pasta `src\qgis_mcp` do repositório. O nome da pasta no destino precisa
continuar sendo `qgis_mcp` — é ele que o QGIS importa.
### 3. Ativar no QGIS
**Complementos → Gerenciar e Instalar Complementos → Instalados** e marque
*QGIS MCP Server*.
### 4. Iniciar o servidor
**Complementos → MCP Server → Iniciar servidor MCP**. Uma mensagem confirma o
endereço; o padrão é `http://127.0.0.1:9876/mcp`.
O log fica em **Visualizar → Painéis → Mensagens do log**, aba *QGIS MCP*.
## Conectar o cliente
O transporte é HTTP, não stdio: o QGIS já está rodando, não há processo para o
cliente lançar. O cliente fica na mesma máquina do QGIS e aponta para a
loopback.
No PowerShell, para o Claude Code:
```powershell
claude mcp add --transport http qgis http://127.0.0.1:9876/mcp
```
Em clientes que se configuram por arquivo JSON, a entrada equivalente é:
```json
{
"mcpServers": {
"qgis": { "type": "http", "url": "http://127.0.0.1:9876/mcp" }
}
}
```
Os nomes dos campos variam por cliente: o Antigravity, por exemplo, usa
`serverUrl` e não aceita `url` — veja *Conectar o Antigravity*, abaixo.
Para conectar de **outra máquina da rede local**, o servidor precisa ouvir em
todas as interfaces, não só na loopback. Defina a variável e reabra o QGIS —
`setx` só vale para processos abertos depois dela:
```powershell
setx QGIS_MCP_HOST 0.0.0.0
```
O Firewall do Windows vai pedir liberação da porta na primeira conexão. Só faça
isso em rede confiável: as tools dão acesso de leitura e escrita ao projeto, sem
autenticação. As variáveis disponíveis estão em `.env.example`.
## Conectar o Antigravity
O Antigravity roda na sua máquina, então alcança a loopback direto: não há
túnel, HTTPS nem domínio público no caminho. A URL é a mesma que o plugin
mostra ao iniciar, `http://127.0.0.1:9876/mcp`.
A ordem importa. O **QGIS precisa estar aberto e com o servidor iniciado** antes
de o Antigravity tentar conectar. Se você subir o servidor depois, não reinicie
o editor: use o botão de recarregar da própria tela de MCP.
### 1. Abrir a configuração de MCP
No painel do agente, clique em **…** no topo → **MCP Servers** → **Manage MCP
Servers**. Esse botão abre o `mcp_config.json` do perfil para edição — prefira
esse caminho a procurar o arquivo no disco, porque o local varia entre versões
do editor.
Se preferir editar à mão, o global fica em:
```
C:\Users\<seu-usuário>\.gemini\config\mcp_config.json
```
Em algumas versões o arquivo do perfil está em
`.gemini\antigravity\mcp_config.json`. Para valer só num projeto, use
`.agents\mcp_config.json` na raiz do workspace.
### 2. Declarar o servidor
```json
{
"mcpServers": {
"qgis": {
"serverUrl": "http://127.0.0.1:9876/mcp"
}
}
}
```
O campo é **`serverUrl`**. O Antigravity não aceita `url` nem `httpUrl` para
conexões HTTP — com o nome errado o servidor não aparece na lista, e não há
mensagem dizendo por quê. Se o arquivo já tiver outros servidores, acrescente a
chave `"qgis"` dentro do `mcpServers` existente em vez de substituir o bloco.
Não é preciso `headers` nem credenciais: o plugin não autentica ninguém.
### 3. Recarregar e conferir
De volta à tela **MCP Servers**, clique no botão de recarregar. O servidor
`qgis` deve aparecer com as tools listadas. Do lado do QGIS, a aba *QGIS MCP* do
painel de log (**Visualizar → Painéis → Mensagens do log**) registra a conexão.
Teste com um pedido que só o seu projeto sabe responder — "liste as camadas do
projeto aberto no QGIS", ou "quantas feições tem a camada X". Se a resposta
trouxer o que está na sua tela, a ligação está de pé.
### Conferir o servidor antes de culpar o cliente
Se nada aparece, confirme primeiro que o plugin está ouvindo. No PowerShell:
```powershell
Test-NetConnection -ComputerName 127.0.0.1 -Port 9876
```
`TcpTestSucceeded : True` quer dizer que o servidor está no ar e o problema é de
configuração do cliente.
Abrir `http://127.0.0.1:9876/mcp` no navegador **não** é um teste válido: o
endereço responde `400 Bad Request: Missing session ID`. Isso é sinal de vida, e
não erro de instalação — o transporte MCP exige POST com sessão, coisa que o
navegador não faz.
### Sobre a exposição
Ouvindo em `127.0.0.1`, o servidor não está visível para a rede: só processos da
própria máquina o alcançam. Entre eles não há distinção — não há autenticação,
então qualquer programa local que conheça a porta tem o mesmo acesso ao projeto
que o Antigravity. É o modelo dos servidores MCP locais em geral, e é bem mais
contido que o túnel público exigido pelo Spark.
## Conectar o Gemini Spark
O Gemini do Google aceita servidores MCP de terceiros através do **Spark**. Na
web: **gemini.google.com → Configurações e ajuda → Apps conectados → Apps
personalizados para o Spark → Adicionar um app personalizado**, e informe a URL
do servidor MCP. No app móvel o caminho é o mesmo, pelo menu.
Requisitos do lado do Google, hoje: ter acesso ao Spark, usar uma **conta
pessoal** (contas de trabalho ou escola não funcionam), ter 18 anos ou mais,
estar nos EUA, com a interface em inglês e o *Keep Activity* ligado.
Há uma diferença de fundo em relação aos clientes de desktop: o Spark roda na
nuvem do Google, não na sua máquina. `http://127.0.0.1:9876/mcp` é inalcançável
para ele — a URL precisa ser pública e em HTTPS. O passo do ngrok abaixo, que
para qualquer outro cliente é opcional, aqui é o único caminho.
### Expor o servidor por HTTPS com o ngrok
O agente do ngrok roda na mesma máquina do QGIS e aponta para a loopback. Assim
o plugin continua ouvindo só em `127.0.0.1` — não é preciso mexer em
`QGIS_MCP_HOST`, e nada na sua rede local passa a enxergar o servidor: só o
túnel.
1. Instale e autentique o agente (o token sai do painel do ngrok):
```bat
winget install ngrok.ngrok
ngrok config add-authtoken SEU_TOKEN
```
2. Com o servidor MCP já iniciado no QGIS, abra o túnel:
```bat
ngrok http 9876 --host-header=rewrite
```
O `--host-header=rewrite` faz o ngrok reescrever o cabeçalho `Host` para
`127.0.0.1:9876`. Sem isso, alguns servidores HTTP recusam a requisição por
proteção contra DNS rebinding.
3. Copie a URL `Forwarding` que o ngrok mostra e acrescente `/mcp`:
```
https://xxxx-xxx-xxx-xxx-xxx.ngrok-free.app/mcp
```
4. Cole essa URL no campo de app personalizado do Spark. Se o servidor não
suportar Dynamic Client Registration — é o caso deste plugin, que não tem
camada de autenticação —, o Spark oferece um bloco *Advanced features* para
credenciais; deixe-o vazio.
5. Teste pedindo ao Gemini algo simples, como listar as camadas do projeto
aberto. A aba *QGIS MCP* do painel de log mostra a chamada chegando.
No plano gratuito a URL muda a cada reinício do ngrok, e o app precisa ser
recadastrado no Spark. Um domínio reservado (plano pago) resolve isso.
### Antes de abrir o túnel
Uma URL pública do ngrok apontando para este servidor dá a **qualquer pessoa que
souber o endereço** o mesmo poder que as tools dão ao modelo: ler o projeto,
rodar geoprocessamento, sobrescrever arquivos, exportar dados. O plugin não
autentica ninguém.
Mitigações, em ordem de eficácia:
- Suba o túnel só enquanto estiver usando e encerre com `Ctrl+C` depois.
- Proteja com autenticação básica: `ngrok http 9876 --basic-auth "usuario:senha"`
(a senha precisa de 8 caracteres ou mais) e informe as credenciais no bloco
*Advanced features* do Spark. Se o Spark recusar as credenciais, volte à
primeira mitigação.
- Trabalhe num projeto de teste, não no projeto de produção, enquanto avalia.
## As tools
35 tools em quatro módulos. Todas retornam `{"success": bool, ...}`; em caso de
erro, a mensagem carrega o contexto necessário para corrigir a chamada — campos
disponíveis, camadas existentes, mensagem do parser de expressões.
### Inspeção — somente leitura
| Tool | Para quê |
|---|---|
| `get_project_info` | panorama do projeto aberto |
| `list_layers` | camadas com IDs e metadados |
| `describe_layer` | CRS, extensão, provedor, geometria |
| `get_layer_fields` | esquema de atributos |
| `count_features` | contagem, com filtro opcional |
| `get_unique_values` | valores distintos de um campo |
| `get_field_statistics` | min, max, média, mediana, desvio |
| `get_features` | leitura de feições |
| `get_layer_extent` | bounding box, com reprojeção |
### Geoprocessamento
O QGIS tem mais de mil algoritmos; não há uma tool por operação. As três
primeiras expõem o registro inteiro:
| Tool | Para quê |
|---|---|
| `list_algorithms` | procura algoritmos por palavra-chave |
| `describe_algorithm` | esquema exato de parâmetros |
| `validate_algorithm_parameters` | checagem sem executar |
| `run_algorithm` | executa, em segundo plano |
| `add_layer_from_source` | carrega arquivo como camada |
| `remove_layer` | descarta camada da sessão |
### Projeto e exportação
| Tool | Para quê |
|---|---|
| `open_project` / `save_project` / `close_project` | ciclo de vida do `.qgz` |
| `set_layer_visibility` / `rename_layer` / `move_layer` | árvore de camadas |
| `export_layer` | grava vetorial em arquivo |
| `export_map_image` | renderiza o mapa em PNG |
| `list_layouts` / `export_layout` | layouts de impressão em PDF/PNG/SVG |
Operações destrutivas exigem autorização explícita (`discard_unsaved`,
`overwrite`), que começa desligada.
### Simbologia
| Tool | Para quê |
|---|---|
| `list_color_ramps` | rampas de cor disponíveis |
| `get_layer_style` | como a camada está pintada |
| `set_single_symbol` | símbolo único |
| `set_categorized_style` | uma cor por valor de atributo |
| `set_graduated_style` | mapa coroplético por faixas |
| `set_raster_style` | pseudocor em banda de raster |
| `set_layer_labels` | rótulos |
| `set_layer_opacity` | transparência |
| `load_style_file` / `save_style_file` | estilos `.qml` |
## Arquitetura
O QGIS é uma aplicação Qt de thread única. O servidor HTTP roda numa thread de
fundo para não congelar a interface, mas tocar em `QgsProject`, camadas ou
`iface` a partir dela causa segfault.
Todo acesso ao QGIS passa por `core.call_qgis(fn)`, que enfileira `fn` na thread
principal por sinal com conexão `QueuedConnection` e aguarda o resultado de
forma assíncrona. Nenhuma tool chama a API do QGIS diretamente.
Geoprocessamento vai além disso: `run_algorithm` usa `QgsProcessingAlgRunnerTask`
para rodar em segundo plano, mantendo a interface responsiva. Algoritmos com a
flag `NoThreading` — vários do GDAL — caem automaticamente no caminho síncrono.
## Desenvolvimento
Os testes rodam sem o QGIS instalado, contra um QGIS simulado em
`tests/qgis_stub/`:
```powershell
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install -e ".[dev]"
pytest
```
Use um Python comum aqui, não o do QGIS: a suíte roda contra o stub e não deve
sujar o ambiente do QGIS. Para reinstalar o plugin depois de uma alteração,
repita o `robocopy` do passo 2 e recarregue com o *Plugin Reloader*.
(`scripts/deploy.sh` faz o mesmo a partir do WSL, se você desenvolve de lá.)
Isso valida a lógica pura — serialização, validação, truncamento, proteções e o
formato das mensagens de erro. **Não** valida as chamadas ao PyQGIS real:
assinaturas divergentes e diferenças entre builds Qt5 e Qt6 só aparecem no QGIS
de verdade.
## Problemas comuns
**O plugin não aparece na lista.** A pasta copiada precisa se chamar `qgis_mcp`
e conter `metadata.txt` e `__init__.py`. Confira em
**Complementos → Gerenciar → Inválidos**, que mostra o erro de carregamento.
**"Dependência ausente" ao iniciar.** O `fastmcp` foi instalado no Python errado.
Refaça o passo 1 usando o OSGeo4W Shell, não o PowerShell nem o Prompt de
Comando comum.
**"Não foi possível ouvir na porta".** Outra sessão do QGIS já está usando a
porta. Feche-a, ou rode `setx QGIS_MCP_PORT 9877` e reabra o QGIS.
**Alterei o código e nada mudou.** O Python do QGIS mantém os módulos em cache.
Rode o `robocopy` do passo 2 de novo e recarregue com o plugin *Plugin
Reloader*, ou reinicie o QGIS.
**A interface congela.** Alguma tool está bloqueando a thread principal por mais
tempo que o esperado. O timeout padrão é de 30s para leituras e 600s para
geoprocessamento; o log da aba *QGIS MCP* mostra o que estava rodando.
**O Antigravity não lista as tools do `qgis`.** Na ordem: o servidor foi
iniciado no menu do QGIS (a aba *QGIS MCP* do log mostra o endereço); o campo no
`mcp_config.json` é `serverUrl`, não `url`; a URL termina em `/mcp`; e você
clicou no botão de recarregar da tela **MCP Servers** depois de salvar. Um
servidor declarado enquanto o QGIS estava fechado continua marcado como falho
até esse recarregar.
**O Spark não conecta na URL do ngrok.** Confirme os três pontos, nessa ordem:
o servidor responde em `http://127.0.0.1:9876/mcp` na própria máquina; o painel
do ngrok em `http://127.0.0.1:4040` mostra a requisição chegando; e a URL
cadastrada termina em `/mcp`. Se a requisição chega e volta 4xx, é o cabeçalho
`Host` — verifique se o túnel subiu com `--host-header=rewrite`.
**Funcionava e parou depois de reiniciar o ngrok.** No plano gratuito o
subdomínio é sorteado a cada execução. Remova o app personalizado no Spark e
cadastre-o de novo com a URL nova.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues