agentsystem-mcp-server

agentsystem-mcp-server

A read-only MCP server that exposes a materials distributor database with four tools for querying tables, describing schemas, sampling rows, and running SELECT queries, secured by three independent layers to prevent any write operations.

Category
访问服务器

README

agentsystem-mcp-server

Servidor MCP só-de-leitura sobre a base de dados de referência do distribuidor de materiais de construção (agentsystem-db).

Expõe quatro tools a um cliente MCP (Claude Desktop, Claude Code, MCP Inspector) e garante, por três camadas independentes, que nenhuma delas consegue escrever.

Tools

Tool O que faz
list_tables Tabelas do schema public, com contagem aproximada de linhas e tamanho em disco
describe_table Colunas, tipos, nullable, valores por omissão, chave primária e chaves estrangeiras
sample_rows Amostra de linhas de uma tabela (1 a 50, por omissão 10)
run_query Corre uma query SELECT, com as três camadas de validação

As três camadas

A ideia não é ter mais verificações — é ter verificações que não partilham o mesmo ponto de falha. Para uma escrita passar, teriam de falhar ao mesmo tempo coisas que não têm nada a ver umas com as outras.

Camada 0 — o utilizador da base de dados. A ligação usa o mcp_readonly, que tem SELECT e mais nada. Vive na DATABASE_URL_READONLY.

Camada 1 — o parser (src/seguranca/camada1-parser.ts). Confirma que a query é exatamente uma instrução SELECT e que não há nenhum nó de escrita em toda a árvore, incluindo dentro de CTEs e subqueries. Usa o parser real do PostgreSQL (libpg-query, o parser do servidor compilado para WebAssembly), não um regex — porque comentários, ponto e vírgula e CTEs contornam qualquer inspeção de texto.

Camada 2 — a transação (src/db.ts). Cada query corre dentro de BEGIN TRANSACTION READ ONLY. É redundante face à Camada 0 de propósito: a Camada 0 é uma defesa de configuração (vive num .env que pode ser trocado por engano), esta é uma defesa do próprio Postgres, que recusa escritas mesmo com uma ligação de superutilizador.

Camada 3 — os limites (src/seguranca/camada3-limites.ts). Queries sem LIMIT recebem LIMIT 200; um LIMIT explícito acima de 500 é recusado; cada query tem 5 segundos de statement_timeout aplicado do lado do Postgres.

Pré-requisitos

1. Instalar e configurar

npm install
Copy-Item .env.example .env

Abre o .env e preenche a DATABASE_URL_READONLY. A password é a READONLY_PASSWORD do .env do agentsystem-db, e a porta é a POSTGRES_HOST_PORT desse mesmo ficheiro (5434 na configuração atual, não a 5432 por omissão do Postgres):

DATABASE_URL_READONLY=postgresql://mcp_readonly:<password>@localhost:5434/distribuidor

Nunca coles aqui a DATABASE_URL_ADMIN. Este servidor não tem nenhuma razão para conseguir escrever.

2. Compilar e confirmar

npm run build
npm start

O arranque tem de mostrar, no stderr:

[mcp] parser do PostgreSQL carregado.
[mcp] ligado a distribuidor (PostgreSQL 18.4)
[mcp] current_user = mcp_readonly          <-- a confirmação que interessa
[mcp] 4 tools registadas: list_tables, describe_table, sample_rows, run_query.
[mcp] servidor pronto, à escuta em stdio.

Aquele current_user é a verificação mais barata de que a ligação não está, por engano, a usar a connection string de admin. Se lá aparecer admin_dist, o .env está errado.

O servidor fica à espera de mensagens JSON-RPC no stdin — é suposto parecer que está pendurado. Ctrl+C para sair.

3. Testar com o MCP Inspector

O Inspector é a ferramenta oficial de debug do protocolo: lança o servidor, lista as tools e deixa chamá-las à mão, sem ser preciso um cliente MCP completo.

npm run inspector

(equivale a npm run build && npx @modelcontextprotocol/inspector node dist/index.js)

Abre uma UI no browser e imprime no terminal um URL com token de sessão. Lá dentro: Connect → separador Tools → List Tools → escolhe uma → enche os argumentos → Run Tool.

Não é preciso passar a variável de ambiente ao Inspector: o servidor lê o .env sozinho. O stderr do servidor aparece no painel inferior do Inspector.

Queries para experimentar no run_query

# Query Resultado esperado
1 SELECT nome, escalao FROM clientes ORDER BY nome LIMIT 5; ✅ 5 linhas
2 WITH f AS (SELECT no_cli, sum(total) AS t FROM docs_venda WHERE cod_doc='FAT' GROUP BY 1) SELECT c.nome, round(f.t,2) FROM f JOIN clientes c ON c.no_cli=f.no_cli ORDER BY f.t DESC LIMIT 5; ✅ CTE + agregação passam
3 UPDATE clientes SET nome='x' WHERE no_cli=1; ❌ "Esta instrução é do tipo UPDATE"
4 WITH apagados AS (DELETE FROM clientes RETURNING *) SELECT * FROM apagados; ❌ "operação não permitida (DeleteStmt)"
5 SELECT 1; DROP TABLE clientes; ❌ "contém 2 instruções"
6 SELECT FROM WHERE clientes; ❌ "SQL inválido: syntax error"
7 SELECT * FROM artigos; ✅ 200 linhas, com LIMIT 200 automático
8 SELECT * FROM artigos LIMIT 5000; ❌ "excede o máximo permitido de 500"
9 SELECT * INTO nova FROM clientes; ❌ "operação não permitida (intoClause)"

O nº 7 é o que confirma a Camada 3: a resposta traz limite_aplicado e sql_executado a mostrar exatamente o que foi corrido.

O nº 9 é o mais instrutivo — SELECT ... INTO cria uma tabela, e a instrução de topo continua a ser um SelectStmt. Só a varredura recursiva o apanha.

A mesma bateria, automatizada

npm run teste       # 19 verificações através de um cliente MCP real por stdio
npm run cobertura   # passa as 50 queries do EVAL-QUESTIONS.md pela Camada 1

O teste faz o mesmo que se faria a clicar no Inspector, mas de forma reproduzível. O cobertura é o contrapeso: prova que a Camada 1 não rejeita queries legítimas — uma camada de segurança que bloqueia trabalho válido é tão inútil como uma que não bloqueia nada.

Ver a Camada 2 a trabalhar sozinha

As camadas 1 e 3 apanham quase tudo antes de a 2 entrar em jogo, portanto ela quase nunca dispara — e é preciso prová-la à parte. Como admin, de propósito:

cd ..\agentsystem-db
docker compose exec db psql -U admin_dist -d distribuidor -c "BEGIN TRANSACTION READ ONLY; UPDATE clientes SET nome='x' WHERE no_cli=1;"

Responde ERROR: cannot execute UPDATE in a read-only transaction. É a demonstração de que a Camada 2 trava escrita mesmo com a connection string errada — que é exatamente para isso que ela existe.

4. Registar no Claude Desktop

Abre a configuração:

code $env:AppData\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "distribuidor": {
      "command": "node",
      "args": ["C:\\DEV\\agentsystem-mcp-server\\dist\\index.js"],
      "env": {
        "DATABASE_URL_READONLY": "postgresql://mcp_readonly:<password>@localhost:5434/distribuidor"
      }
    }
  }
}

Reinicia o Claude Desktop a seguir (fechar a janela não chega — tem de sair pelo ícone da barra de tarefas).

Três pormenores que dão dores de cabeça:

  • O caminho tem de ser absoluto, e as barras duplicadas. O \ é o caractere de escape do JSON, portanto C:\DEV\... seria inválido.
  • A variável de ambiente TEM de ir no bloco env. O Claude Desktop lança o processo sem herdar o shell nem o diretório de trabalho, por isso o .env do projeto não é encontrado. É por isso que a configuração o repete.
  • Tem de estar compilado. O args aponta para dist/index.js, não para o TypeScript. Se mexeres no código, npm run build antes de reiniciar.

Os logs (o nosso stderr) ficam em %AppData%\Claude\logs\mcp-server-distribuidor.log.

5. Registar no Claude Code

claude mcp add distribuidor --env DATABASE_URL_READONLY="postgresql://mcp_readonly:<password>@localhost:5434/distribuidor" -- node C:\DEV\agentsystem-mcp-server\dist\index.js

Ou, para o servidor ficar disponível só neste projeto, um .mcp.json na raiz com o mesmo conteúdo do bloco mcpServers acima.

Estrutura

src/
  index.ts                    arranque, registo das tools, transporte stdio
  db.ts                       pool do pg + CAMADA 2 (transação READ ONLY)
  erros.ts                    sanitização das mensagens que saem para o cliente
  identificadores.ts          uso seguro de nomes de tabela dentro de SQL
  seguranca/
    camada1-parser.ts         uma única instrução SELECT, sem nós de escrita
    camada3-limites.ts        LIMIT automático / recusa acima do máximo
  tools/
    listTables.ts  describeTable.ts  sampleRows.ts  runQuery.ts
    resposta.ts               formatação JSON das respostas
scripts/
  testeCamadas.ts             bateria de testes por cliente MCP real
  coberturaEvals.ts           deteção de falsos positivos da Camada 1

As camadas estão em ficheiros separados de propósito: é a forma de a independência entre elas ser visível na árvore de ficheiros, e não apenas uma afirmação neste README.

Configuração opcional

Todas com valores por omissão sensatos; ver o .env.example.

Variável Omissão O que faz
MCP_TIMEOUT_MS 5000 statement_timeout por query
MCP_LIMITE_AUTOMATICO 200 LIMIT acrescentado a queries sem um
MCP_LIMITE_MAXIMO 500 LIMIT explícito máximo aceite

Notas

npm audit reporta 2 vulnerabilidades moderadas. Ambas são a mesma coisa: @hono/node-server, uma dependência transitiva do SDK do MCP, com um problema de path traversal no serve-static. Esse código só é usado pelo transporte HTTP; este servidor usa exclusivamente stdio e nunca o carrega. A correção que o npm sugere é descer o SDK uma versão major, o que traria problemas reais em troca de um risco que aqui não existe.

Funções customizadas no schema. O schema atual só tem tabelas. Se um dia forem adicionadas funções, é preciso confirmar nessa altura que nenhuma tem EXECUTE concedido a PUBLIC por omissão nem é SECURITY DEFINER com privilégios de escrita. Uma função dessas seria chamável a coberto de um SELECT, e a Camada 1 deixá-la-ia passar — para o parser, SELECT f() é uma leitura como outra qualquer.

O que a Camada 2 cobre aqui, verificado na prática e não por suposição: uma função SECURITY DEFINER que faça UPDATE é bloqueada dentro da transação READ ONLY (ERROR: cannot execute UPDATE in a read-only transaction, CONTEXT: SQL function ...). O SECURITY DEFINER muda o utilizador com que a função corre, não o estado da transação — e o modo só-leitura é uma propriedade da transação, que a função não consegue contornar.

Fica na mesma por rever, porque há duas coisas que a Camada 2 não cobre: funções assim podem ler dados a que o mcp_readonly não deveria ter acesso, e efeitos laterais que não passam pelo motor de transações (dblink, que abre uma ligação nova e independente, COPY TO PROGRAM, ou linguagens não confiáveis como plpython3u) escapam ao modo só-leitura por completo.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选