ctxai
Um servidor MCP ciente de versão que previne alucinações de codificação de IA ao validar sugestões contra seus pacotes instalados reais.
Documentação
ctxai
ctxai é um servidor Model Context Protocol (MCP) que torna assistentes de codificação com IA cientes da versão do seu ambiente. Ele lê os pacotes realmente instalados, injeta esse contexto no LLM e valida cada sugestão de código contra o seu ambiente real — detectando imports alucinados, chamadas de métodos inexistentes e pacotes fantasmas perigosos antes que cheguem ao seu editor.
O problema que ele resolve
Assistentes de codificação com IA alucinam de três maneiras específicas que são difíceis de detectar:
- Alucinação de pacotes — sugerir
import helmet from 'helmet'quandohelmetnão está no seupackage.json - Alucinação de métodos — chamar
prisma.user.findFirstOrThrow()quando você está no Prisma v3, onde esse método ainda não existe - Pacotes fantasmas — inventar nomes de pacotes como
express-mongooseoureact-query-utilsque não existem no npm, que agentes mal-intencionados podem registrar como typosquats
Todos os três parecem código válido. Todos os três falham em tempo de execução — ou pior, instalam malware. O ctxai os detecta no momento da sugestão.
Como funciona
O ctxai expõe quatro ferramentas MCP que um cliente LLM (Claude Desktop, Cursor, Kiro, etc.) chama automaticamente:
get_project_context → scan project → return version fingerprint
validate_suggestion → check code → return hallucination warnings
check_package_safety → check new packages → return safety issues
get_package_docs → fetch registry → return real API info
Ferramenta 1 — get_project_context
Escaneia a raiz do seu projeto e retorna uma impressão digital estruturada de cada pacote instalado e sua versão exata:
node: express@4.18.2
node: @prisma/client@3.15.2
python: fastapi@0.100.0
python: requests@2.31.0
Esta impressão digital é injetada no contexto do LLM antes de cada resposta, restringindo-o a sugerir apenas APIs que existem nas suas versões instaladas. Os resultados são armazenados em cache por 5 minutos, para que chamadas repetidas dentro de uma sessão sejam instantâneas.
Ferramenta 2 — validate_suggestion
Recebe o código gerado por IA e a impressão digital da Ferramenta 1, e executa três camadas de validação:
| Camada | O que verifica | Tipo de aviso |
|---|---|---|
| 1 | Todo pacote importado está nas suas dependências? | MISSING_PACKAGE |
| 2 | Toda chamada de método existe na sua versão instalada? | HALLUCINATED_METHOD |
| 3 | Qual é a alternativa real mais próxima? | Sugestão no aviso |
Retorna saída legível com o identificador exato do problema, gravidade e um comando de instalação corrigido ou sugestão de método.
Exemplo de saída:
⚠️ Found 1 issue in the suggested code:
🔴 [Missing package] 'helmet' is not listed in your project dependencies.
→ Run 'npm install helmet' to add it, or check if the package name has changed.
The code above cannot run as-is. Fix the missing packages before using it.
Ferramenta 3 — check_package_safety
Verifica cada pacote novo que a IA sugere instalar contra três camadas de segurança:
| Camada | O que verifica | Tipo de problema |
|---|---|---|
| 1 | Este pacote existe no npm / PyPI? | PHANTOM_PACKAGE |
| 2 | É suspeitamente semelhante a um pacote popular? | LIKELY_TYPOSQUAT / LIKELY_CONFLATION |
| 3 | É muito novo, não tem repositório ou tem pouquíssimas versões? | LOW_TRUST_PACKAGE |
Pacotes já presentes na sua impressão digital são ignorados — você já tomou essa decisão de confiança.
Exemplo de saída:
🚨 Found 1 critical issue across 1 new package.
📦 expres
🚨 [Likely typosquat] 'expres' exists on the registry but is suspiciously
similar to 'express' (edit distance: 1). This is a known typosquatting pattern.
→ Verify you meant 'express'. If you intentionally want 'expres', inspect
its source code and maintainers before installing.
🛑 Do NOT install the flagged packages without manual verification.
Ferramenta 4 — get_package_docs
Busca metadados ao vivo do npm ou PyPI para uma versão específica de pacote. Usada pelo LLM para se autocorrigir após uma alucinação ser detectada — encontra o nome correto do método para a versão que você realmente tem instalada.
Arquitetura
ctxai/
├── src/
│ ├── index.ts # MCP server — registers all 4 tools
│ ├── formatters.ts # Converts typed results → readable MCP strings
│ ├── tools/
│ │ ├── getProjectContext.ts # Tool 1: scan project + build fingerprint
│ │ ├── validateSuggestion.ts # Tool 2: 3-layer hallucination validator
│ │ └── getPackageDocs.ts # Tool 4: live registry metadata
│ ├── utils/
│ │ ├── checkPackageSafety.ts # Tool 3: phantom/typosquat/trust checker
│ │ ├── registryClient.ts # Typed npm + PyPI registry clients
│ │ ├── typosquatDetector.ts # Levenshtein-based typosquat detection
│ │ ├── fuzzy.ts # Closest-match suggestions
│ │ ├── npmRegistry.ts # npm metadata client (used by getPackageDocs)
│ │ └── pypiRegistry.ts # PyPI metadata client (used by getPackageDocs)
│ ├── parser/
│ │ ├── responseParser.ts # Extracts imports + method calls from code
│ │ └── fingerprintBuilder.ts # Formats detected packages into fingerprint
│ ├── detectors/
│ │ ├── index.ts # Orchestrates Node + Python detection
│ │ ├── node.ts # Reads package.json + TypeScript API surface
│ │ └── python.ts # Reads requirements.txt + Python API surface
│ └── cache/
│ └── sessionCache.ts # In-memory TTL cache (5 min)
└── benchmark/
├── run.ts # Benchmark runner with hallucination metrics
└── prompts/ # 28 test cases (JSON)
Instalação
Pré-requisitos
- Node.js 18+
- TypeScript 5+
- Python 3 (opcional, para validação de projetos Python)
Build
cd ctxai
npm install
npm run build
Executar
npm start
# or in dev mode (no build step)
npm run dev
O servidor se comunica via stdio, que é o transporte MCP padrão.
Configuração do cliente MCP
Kiro
Adicione ao .kiro/settings/mcp.json no seu workspace:
{
"mcpServers": {
"ctxai": {
"command": "node",
"args": ["/absolute/path/to/ctxai/build/index.js"],
"disabled": false,
"autoApprove": [
"get_project_context",
"validate_suggestion",
"check_package_safety",
"get_package_docs"
]
}
}
}
Usuários de Windows + fnm/nvm:
nodepode não ser resolvido quando o Kiro inicia o servidor fora da sua sessão de shell. Use o caminho completo paranode.exe:"command": "C:\\Users\\YOU\\AppData\\Roaming\\fnm\\node-versions\\v20.0.0\\installation\\node.exe"Encontre seu caminho com:
Get-Command node | Select-Object -ExpandProperty Source(PowerShell)
Claude Desktop
Edite ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) ou %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"ctxai": {
"command": "node",
"args": ["/absolute/path/to/ctxai/build/index.js"]
}
}
}
Cursor / VS Code (Cline)
Edite seu cline_mcp_settings.json:
{
"mcpServers": {
"ctxai": {
"command": "node",
"args": ["/absolute/path/to/ctxai/build/index.js"],
"disabled": false,
"alwaysAllow": []
}
}
}
Após adicionar a configuração, recarregue/reconecte os servidores MCP pela paleta de comandos. Você deve ver o ctxai com 4 ferramentas listadas.
Testando a integração
1. Teste de fumaça — o servidor inicia?
npm run build
node build/index.js
# Expected: ctxai MCP server v0.1.0 running on stdio
2. Chamadas manuais de ferramentas via stdio
Teste cada ferramenta enviando JSON diretamente ao servidor:
Ferramenta 1 — escaneie seu projeto:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_project_context","arguments":{"path":"/your/project/path"}}}' \
| node build/index.js
Ferramenta 2 — detecte um pacote ausente:
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"validate_suggestion","arguments":{"code":"import helmet from \"helmet\";","contextFingerprint":"node: express@4.18.2"}}}' \
| node build/index.js
# Expected: 🔴 [Missing package] 'helmet' is not listed in your project dependencies.
Ferramenta 3 — detecte um typosquat:
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"check_package_safety","arguments":{"code":"import expres from \"expres\";","contextFingerprint":"node: express@4.18.2"}}}' \
| node build/index.js
# Expected: 🚨 [Likely typosquat] 'expres' is suspiciously similar to 'express'
Ferramenta 4 — busque documentação ao vivo:
echo '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_package_docs","arguments":{"packageName":"express","version":"4.18.2","registry":"npm"}}}' \
| node build/index.js
3. Execute o benchmark
npm run benchmark
Saída esperada:
════════════════════════════════════════════════════════════
HALLUCINATION REDUCTION METRICS
════════════════════════════════════════════════════════════
Benchmark accuracy 100.0% (28/28 tests match expected)
Detection rate 100.0%
False positives 0
Precision 100.0%
F1 Score 100.0%
Benchmark
O ctxai acompanha 28 casos de teste cobrindo todos os cenários de validação. Os resultados incluem métricas de redução de alucinação — taxa de detecção, precisão e pontuação F1 — para que você possa medir o impacto de qualquer alteração.
O que o benchmark cobre
| Categoria | Prompts | O que é testado |
|---|---|---|
| Caminho feliz | 11 | Código válido contra impressão digital correta — zero falsos positivos |
| Pacotes Node | 6 | Imports npm ausentes, únicos e múltiplos |
| Pacotes Python | 5 | Pacotes pip ausentes, normalização hífen/sublinhado, correção de nome pip |
| Alucinação de método | 2 | Métodos que não existem na versão instalada (via superfície de API simulada) |
| Multi-pacote | 2 | Testes de estresse com 3–5 pacotes alucinados de uma vez |
| Casos extremos | 2 | Impressão digital vazia, blocos de prosa+código |
Adicionando um caso de teste
Crie um arquivo JSON em benchmark/prompts/:
{
"name": "My Test Case",
"projectFingerprint": "node: express@4.18.2",
"aiGeneratedCode": "import helmet from 'helmet';\nconst app = require('express')();",
"expectedViolations": 1
}
Para testes de alucinação de método, injete uma superfície de API simulada para que o teste não exija node_modules real:
{
"name": "Prisma - Method Hallucination",
"projectFingerprint": "node: @prisma/client@3.15.2",
"aiGeneratedCode": "const prisma = new PrismaClient();\nawait prisma.user.findFirstOrThrow({ where: { id: 1 } });",
"apiSurfaceOverrides": {
"@prisma/client": ["findFirst", "findMany", "create", "update", "delete"]
},
"expectedViolations": 1,
"_note": "findFirstOrThrow was added in Prisma v4 — should be caught on v3"
}
Campos:
| Campo | Obrigatório | Descrição |
|---|---|---|
name | ✓ | Nome legível do teste |
projectFingerprint | ✓ | Pacotes instalados simulados (source: name@version por linha) |
aiGeneratedCode | ✓ | O código gerado por IA a validar |
expectedViolations | ✓ | Número exato de avisos esperados |
apiSurfaceOverrides | — | Superfície de API simulada para verificações de método (ignora node_modules) |
_note | — | Documentação interna, ignorada pelo executor |
Tipos de avisos e problemas
Avisos validate_suggestion
interface ValidationWarning {
type: "MISSING_PACKAGE" | "HALLUCINATED_METHOD" | "UNKNOWN_PACKAGE";
severity: "error" | "warning" | "info";
message: string; // Human-readable description
suggestion: string; // Correct install command or method name
offender: string; // The exact identifier that triggered the warning
packageName?: string; // Package context (HALLUCINATED_METHOD only)
installedVersion?: string; // Installed version (HALLUCINATED_METHOD only)
}
Problemas check_package_safety
interface SafetyIssue {
type: "PHANTOM_PACKAGE" | "LIKELY_TYPOSQUAT" | "LIKELY_CONFLATION"
| "LOW_TRUST_PACKAGE" | "SECURITY_HOLD";
severity: "critical" | "warning" | "info";
packageName: string;
ecosystem: "node" | "python";
message: string;
suggestion: string;
meta?: {
similarTo?: string; // The popular package it resembles
editDistance?: number; // Levenshtein distance to the popular package
ageInDays?: number; // How old the package is
versionCount?: number; // How many versions it has
hasRepository?: boolean; // Whether it has a repo link
}
}
Linguagens e ecossistemas suportados
| Linguagem | Arquivo de pacotes | Registro | Validação de método |
|---|---|---|---|
| JavaScript / TypeScript | package.json | npm | Via definições de tipo .d.ts |
| Python | requirements.txt, pyproject.toml | PyPI | Via introspecção dir() |
Mapeamento import Python → nome pip
O ctxai sabe que nomes de import em Python frequentemente diferem dos nomes de pacotes pip e gera comandos de instalação corretos:
| Import | pip install |
|---|---|
from rest_framework import ... | pip install djangorestframework |
from PIL import Image | pip install Pillow |
import cv2 | pip install opencv-python |
from sklearn import ... | pip install scikit-learn |
import jwt | pip install PyJWT |
import yaml | pip install PyYAML |
from bs4 import ... | pip install beautifulsoup4 |
Mais de 80 mapeamentos estão embutidos. Veja src/tools/validateSuggestion.ts → PYTHON_IMPORT_TO_PIP para a lista completa.
Decisões de design
Por que quatro ferramentas em vez de uma? Cada ferramenta tem uma condição de acionamento distinta. get_project_context roda uma vez por sessão. validate_suggestion roda em cada resposta de código. check_package_safety roda apenas quando novos pacotes são sugeridos. get_package_docs roda sob demanda para autocorreção. Dividi-las permite que o LLM chame apenas o que for necessário.
Por que MCP? MCP é o padrão emergente para dar aos LLMs acesso estruturado a ferramentas locais. Qualquer cliente compatível com MCP obtém o ctxai gratuitamente, sem integrações personalizadas.
Por que uma string de impressão digital em vez de JSON? O formato de impressão digital (node: express@4.18.2) é compacto, legível e eficiente em tokens. Ele cabe no contexto do LLM sem desperdiçar tokens com sintaxe JSON.
Por que não usar apenas os dados de treinamento do LLM? Os dados de treinamento são congelados em uma data de corte e não sabem o que está instalado no seu projeto. O ctxai lê seu node_modules e requirements.txt reais em tempo de execução.
Por que preferir falsos negativos a falsos positivos? Se o ctxai não conseguir determinar se um método existe (sem definições de tipo, sem stubs), ele permanece em silêncio em vez de avisar. Uma alucinação perdida é menos disruptiva do que um alarme falso em código válido.
Contribuindo
O benchmark é o melhor lugar para começar. Se você encontrar um caso em que o ctxai produza um falso positivo ou perca uma alucinação:
- Adicione um JSON de prompt em
benchmark/prompts/que reproduza o problema - Defina
expectedViolationspara o comportamento correto esperado - Execute
npm run benchmark— se falhar, o bug está confirmado - Corrija o validador e verifique se o benchmark fica verde
Licença
MIT