feat(infraestrutura): mcp-dev v3.0 — SDK TypeScript v2 e transporte HTTP obrigatórios

Regra de Ouro adicionada ao topo da skill: SDK v2 (@modelcontextprotocol/server),
API de alto nível McpServer+registerTool (API de baixo nível Server+setRequestHandler
proibida), transporte HTTP obrigatório (stdio só como excepção documentada), e
proibição explícita de interpolação de dados em comandos shell/SSH.

Motivado por revisão de segurança ao mcp-kivicare (19-08-2026): 8 bugs reais
corrigidos em produção, incluindo injecção de comandos shell crítica e hooks
nativos disparados com o tipo errado. Regras adicionadas para nunca mais acontecer
em silêncio.

- SKILL.md: nova secção Regra de Ouro, API/annotations/capabilities actualizadas
  para v2, transportes HTTP obrigatório, changelog v3.0.0, healing log com os
  3 bugs mais graves do incidente.
- references/templates.md: reescrito para createMcpHandler + createMcpExpressApp
  (HTTP) e serveStdio (excepção), package.json com pacotes v2.
- references/best-practices.md: capabilities/annotations/error handling
  actualizados para API v2, checklist de segurança com regra anti-injecção de
  comandos shell, tabela de transportes com HTTP obrigatório.
- references/evaluation-guide.md: script de evaluations reescrito para
  Client+StreamableHTTPClientTransport in-process (sem porta/socket).
- infraestrutura: 1.2.2 -> 1.3.0
This commit is contained in:
2026-08-19 09:16:31 +01:00
parent 4e33c1d697
commit 18e48db490
5 changed files with 240 additions and 321 deletions
@@ -130,12 +130,15 @@ npx @modelcontextprotocol/inspector
# Testar cada tool manualmente
```
### Método Automatizado (Script TypeScript)
### Método Automatizado (Script TypeScript, SDK v2 — in-process, sem porta/socket)
`handler.fetch` do `createMcpHandler` serve pedidos em processo — o transporte de teste nunca liga de facto ao URL, por isso não há porta a reservar nem servidor a arrancar/parar à volta de cada corrida. Ver [testing.md](https://ts.sdk.modelcontextprotocol.io/v2/testing.md).
```typescript
// eval/run-evals.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
import { createMcpHandler } from '@modelcontextprotocol/server';
import { createServer } from '../src/server.js'; // a mesma factory usada em src/http.ts
interface EvalResult {
id: string;
@@ -160,7 +163,6 @@ async function runEval(
const result = await client.callTool({ name: toolName, arguments: input });
const duration = Date.now() - start;
// Verificar assertions
if (assertions.notError && result.isError) {
return { id: toolName, passed: false, duration, error: 'Esperava sucesso, recebeu erro' };
}
@@ -170,7 +172,7 @@ async function runEval(
}
if (assertions.containsFields) {
const text = result.content[0]?.text || '';
const text = (result.content?.[0] as { text?: string })?.text || '';
for (const field of assertions.containsFields) {
if (!text.includes(field)) {
return { id: toolName, passed: false, duration, error: `Campo "${field}" não encontrado` };
@@ -191,12 +193,12 @@ async function runEval(
// Executar todas as evaluations
async function main() {
const transport = new StdioClientTransport({
command: 'node',
args: ['dist/index.js']
const handler = createMcpHandler(createServer);
const transport = new StreamableHTTPClientTransport(new URL('http://test.local/mcp'), {
fetch: (url, init) => handler.fetch(new Request(url, init)),
});
const client = new Client({ name: 'eval-client', version: '1.0.0' });
const client = new Client({ name: 'eval-client', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });
await client.connect(transport);
const results: EvalResult[] = [];
@@ -226,6 +228,7 @@ async function main() {
});
await client.close();
await handler.close();
process.exit(passed === total ? 0 : 1);
}
@@ -277,4 +280,4 @@ jobs:
---
*evaluation-guide.md v1.0 | 2026-03-10*
*evaluation-guide.md v2.0 | 2026-08-19*