@sbissoli/mcp-surface 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,43 @@ seguinte — cada servidor faz bump explícito).
8
8
  e, pela regra da trava, obrigaria cada servidor a subir de versão sem ter mudado nada.
9
9
  Mudança de normalização é major (ou minor em 0.x), com nota de migração.
10
10
 
11
+ ## [0.2.1] — 2026-10-04
12
+
13
+ A normalização NÃO muda. Patch: a API de `/cliente` é a mesma; os controles negativos
14
+ ficam mais fortes, e os servidores herdam isso pelo caret `^0.2.0`.
15
+
16
+ ### Corrigido
17
+
18
+ - **A troca de tipo cobria UM campo só.** `quebrasDoSchema` trocava o tipo do primeiro
19
+ obrigatório tipado. No ilo-mcp-server esse campo era um objeto (`dataflow`), e os
20
+ escalares ficavam sem prova, o que obrigou a escrever `rows_count` como quebra extra à
21
+ mão. Agora a troca vale para CADA obrigatório com `type` declarado.
22
+ - **O campo anulável não era trocado.** `type: ["string", "null"]` (lista) não gerava
23
+ quebra, e é justamente a classe do defeito que o circuito existe para pegar. O valor
24
+ errado agora é o primeiro de texto, número, booleano, lista e objeto que nenhum dos tipos
25
+ declarados aceita.
26
+ - **As quebras rodam em paralelo.** Com uma troca por obrigatório, em série,
27
+ `loinc_details` do medical-terminologies-mcp passava dos 5 s do vitest sob a carga da
28
+ suíte. Cada quebra já tinha servidor e conexão próprios. A ordem dos vereditos não muda,
29
+ e a armadilha continua sendo o último.
30
+
31
+ Conferido contra os sete servidores com o build local antes de publicar: todos verdes.
32
+
33
+ ## [0.2.0] — 2026-10-04
34
+
35
+ A normalização NÃO muda: todo `surface.lock.json` continua conferindo. Minor porque a
36
+ superfície publicada do pacote cresce (subpath novo).
37
+
38
+ ### Adicionado
39
+
40
+ - **`@sbissoli/mcp-surface/cliente`**: o teste com forma de cliente, que nasceu no
41
+ ilo-mcp-server 1.3.0 (ideia de leitor, https://dev.to/arhancanli/comment/3g4i4).
42
+ `conectarComoCliente` (transporte em memória com JSON no fio), `chamarComoCliente`
43
+ (`tools/list` antes do `tools/call`, lança se o `Client` reprovar ou vier `isError`),
44
+ `controlesNegativos` (quebras derivadas do `outputSchema` listado, mais a armadilha do
45
+ `tools/list` ausente) e `quebrasDoSchema`. `@modelcontextprotocol/client` entra como peer
46
+ OPCIONAL; a raiz do pacote continua sem o `Client`, que o Worker não pode carregar.
47
+
11
48
  ## [0.1.1] — 2026-10-02
12
49
 
13
50
  Achados da adoção nos irmãos, no mesmo dia. A normalização NÃO muda: todo
package/README.md CHANGED
@@ -70,6 +70,37 @@ travar superfície nova sob a versão antiga.
70
70
  Fluxo de quem muda a superfície: `npm version <nível> --no-git-tag-version` →
71
71
  `npm run surface:lock` → commitar o lock junto.
72
72
 
73
+ ## Teste com forma de cliente (`@sbissoli/mcp-surface/cliente`)
74
+
75
+ O servidor interrogado pelo `Client` do SDK, que reprova o resultado de `tools/call`
76
+ contra o `outputSchema` **listado**. Assim o teste falha como a sessão do usuário falharia,
77
+ sem um validador escolhido por nós. É subpath à parte porque o `Client` compila schemas
78
+ com Ajv (`new Function`), que o Worker proíbe: só se importa em teste Node, e o
79
+ `@modelcontextprotocol/client` é peer opcional.
80
+
81
+ ```ts
82
+ import { chamarComoCliente, conectarComoCliente, controlesNegativos } from "@sbissoli/mcp-surface/cliente";
83
+
84
+ const client = await conectarComoCliente(buildServer(env));
85
+ const r = await chamarComoCliente(client, "minha_tool", { x: 1 }); // lança se o Client reprovar ou se vier isError
86
+
87
+ const vs = await controlesNegativos(() => buildServer(env), "minha_tool", { x: 1 });
88
+ for (const v of vs) expect(v.obtido, `${v.descricao}: ${v.mensagem ?? ""}`).toBe(v.esperado);
89
+ ```
90
+
91
+ - `conectarComoCliente` passa toda mensagem do servidor por JSON antes de entregá-la: o
92
+ transporte em memória não serializa, e chave `undefined` só some no fio.
93
+ - `chamarComoCliente` faz `tools/list` antes do primeiro `tools/call` da conexão. Sem
94
+ isso, o `Client` (2.0 a 2.2) devolve o resultado sem validar.
95
+ - `controlesNegativos` adultera o resultado entre servidor e cliente, com quebras
96
+ **derivadas do schema listado**: `structuredContent` ausente, cada obrigatório ausente e
97
+ cada obrigatório com `type` declarado recebendo um valor que nenhum tipo dele aceita (o
98
+ anulável `["string", "null"]` recebe `0`). As quebras rodam em paralelo, cada uma com
99
+ servidor próprio. Cada quebra tem de fazer a chamada falhar. O
100
+ último veredito é a armadilha (sem `tools/list`, a quebra passa calada); se o SDK mudar,
101
+ ele acusa. Quebras do próprio servidor, como campo a mais onde o schema fecha o objeto,
102
+ entram pelo 4º argumento.
103
+
73
104
  ## Observações
74
105
 
75
106
  - Uma atualização do SDK que mexa nas `capabilities` também acende a trava — de propósito:
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Teste com forma de cliente: o servidor interrogado pelo `Client` do SDK, que
3
+ * reprova o resultado de `tools/call` contra o `outputSchema` LISTADO — o teste
4
+ * falha como a sessão do usuário falharia, sem validador escolhido por nós.
5
+ *
6
+ * Ideia de leitor (https://dev.to/arhancanli/comment/3g4i4); o circuito nasceu
7
+ * no ilo-mcp-server 1.3.0 e mora aqui para os sete servidores o usarem igual.
8
+ *
9
+ * Subpath próprio (`@sbissoli/mcp-surface/cliente`), fora da raiz do pacote: o
10
+ * `Client` compila os schemas com Ajv (`new Function`), que o runtime da
11
+ * Cloudflare proíbe. Importar daqui é coisa de teste em Node, nunca de Worker.
12
+ *
13
+ * Duas armadilhas que este módulo fecha, medidas no ilo:
14
+ * - o `Client` só valida contra o schema que tem em cache do `tools/list` —
15
+ * sem `listTools` antes, `callTool` devolve o que recebeu sem validar;
16
+ * - o `InMemoryTransport` não serializa: chave `undefined` sobrevive em memória
17
+ * e some no fio (`JSON.stringify`). Aqui toda mensagem do servidor passa por
18
+ * JSON antes de chegar ao cliente, como passaria pela rede.
19
+ */
20
+ import { Client } from "@modelcontextprotocol/client";
21
+ import type { ServidorConectavel } from "./memoria.js";
22
+ /** Resultado de `tools/call` como viaja no fio (o que uma quebra recebe). */
23
+ export type ResultadoNoFio = {
24
+ content?: unknown;
25
+ structuredContent?: Record<string, unknown>;
26
+ } & Record<string, unknown>;
27
+ export interface OpcoesConexao {
28
+ /** Mexe no resultado de `tools/call` entre servidor e cliente (controle negativo). */
29
+ adulterar?: (r: ResultadoNoFio) => void;
30
+ }
31
+ export declare function conectarComoCliente(server: ServidorConectavel, opcoes?: OpcoesConexao): Promise<Client>;
32
+ /**
33
+ * Chamada que TEM de dar certo, no percurso do cliente. Lança quando o `Client`
34
+ * reprova o resultado e também quando a tool responde `isError` — o servidor do
35
+ * SDK v2 valida a própria saída e, se ela não obedece, devolve o erro em
36
+ * `isError` ("Output validation error"), não em exceção. Para afirmar uma
37
+ * recusa de propósito (parâmetro inválido), use `client.callTool` direto.
38
+ */
39
+ export declare function chamarComoCliente(client: Client, nome: string, args?: Record<string, unknown>): Promise<{
40
+ [x: string]: unknown;
41
+ _meta?: {
42
+ [x: string]: unknown;
43
+ "io.modelcontextprotocol/serverInfo"?: {
44
+ version: string;
45
+ websiteUrl?: string | undefined;
46
+ description?: string | undefined;
47
+ icons?: {
48
+ src: string;
49
+ mimeType?: string | undefined;
50
+ sizes?: string[] | undefined;
51
+ theme?: "dark" | "light" | undefined;
52
+ }[] | undefined;
53
+ name: string;
54
+ title?: string | undefined;
55
+ } | undefined;
56
+ } | undefined;
57
+ content: ({
58
+ type: "text";
59
+ text: string;
60
+ annotations?: {
61
+ audience?: ("assistant" | "user")[] | undefined;
62
+ priority?: number | undefined;
63
+ lastModified?: string | undefined;
64
+ } | undefined;
65
+ _meta?: {
66
+ [x: string]: unknown;
67
+ } | undefined;
68
+ } | {
69
+ type: "image";
70
+ data: string;
71
+ mimeType: string;
72
+ annotations?: {
73
+ audience?: ("assistant" | "user")[] | undefined;
74
+ priority?: number | undefined;
75
+ lastModified?: string | undefined;
76
+ } | undefined;
77
+ _meta?: {
78
+ [x: string]: unknown;
79
+ } | undefined;
80
+ } | {
81
+ type: "audio";
82
+ data: string;
83
+ mimeType: string;
84
+ annotations?: {
85
+ audience?: ("assistant" | "user")[] | undefined;
86
+ priority?: number | undefined;
87
+ lastModified?: string | undefined;
88
+ } | undefined;
89
+ _meta?: {
90
+ [x: string]: unknown;
91
+ } | undefined;
92
+ } | {
93
+ uri: string;
94
+ description?: string | undefined;
95
+ mimeType?: string | undefined;
96
+ size?: number | undefined;
97
+ annotations?: {
98
+ audience?: ("assistant" | "user")[] | undefined;
99
+ priority?: number | undefined;
100
+ lastModified?: string | undefined;
101
+ } | undefined;
102
+ _meta?: {
103
+ [x: string]: unknown;
104
+ } | undefined;
105
+ icons?: {
106
+ src: string;
107
+ mimeType?: string | undefined;
108
+ sizes?: string[] | undefined;
109
+ theme?: "dark" | "light" | undefined;
110
+ }[] | undefined;
111
+ name: string;
112
+ title?: string | undefined;
113
+ type: "resource_link";
114
+ } | {
115
+ type: "resource";
116
+ resource: {
117
+ uri: string;
118
+ mimeType?: string | undefined;
119
+ _meta?: {
120
+ [x: string]: unknown;
121
+ } | undefined;
122
+ text: string;
123
+ } | {
124
+ uri: string;
125
+ mimeType?: string | undefined;
126
+ _meta?: {
127
+ [x: string]: unknown;
128
+ } | undefined;
129
+ blob: string;
130
+ };
131
+ annotations?: {
132
+ audience?: ("assistant" | "user")[] | undefined;
133
+ priority?: number | undefined;
134
+ lastModified?: string | undefined;
135
+ } | undefined;
136
+ _meta?: {
137
+ [x: string]: unknown;
138
+ } | undefined;
139
+ })[];
140
+ structuredContent?: unknown;
141
+ isError?: boolean | undefined;
142
+ }>;
143
+ export interface Quebra {
144
+ descricao: string;
145
+ adulterar: (r: ResultadoNoFio) => void;
146
+ }
147
+ /**
148
+ * As quebras genéricas, DERIVADAS do schema listado (não de nomes escritos à
149
+ * mão): `structuredContent` ausente, cada campo obrigatório ausente, e cada
150
+ * obrigatório com tipo declarado trocado de tipo. Cada um, não o primeiro: até a
151
+ * 0.2.0 só o primeiro tipado era trocado, e no ilo esse caía num objeto
152
+ * (`dataflow`) e deixava os escalares sem prova.
153
+ */
154
+ export declare function quebrasDoSchema(outputSchema: Record<string, unknown>): Quebra[];
155
+ export interface Veredito {
156
+ descricao: string;
157
+ /** O que o circuito exige: o cliente reprovar a quebra (ou, na armadilha, deixá-la passar). */
158
+ esperado: "reprova" | "passa";
159
+ /** `isError`: a chamada-base falhou como tool — o caso está mal montado. */
160
+ obtido: "reprova" | "passa" | "isError";
161
+ mensagem?: string;
162
+ }
163
+ /**
164
+ * Controle negativo pelo lado do resultado: o servidor responde certo, o que
165
+ * chega ao cliente está quebrado, e a chamada TEM de falhar. Um servidor novo
166
+ * por quebra (`fabrica`), porque cada conexão leva a sua adulteração.
167
+ *
168
+ * Inclui a armadilha como veredito esperado "passa": sem `tools/list` antes, a
169
+ * primeira quebra passa calada. Se um dia o SDK passar a validar sem a lista,
170
+ * esse veredito acusa — e a regra do `listTools` pode ser revista.
171
+ *
172
+ * O teste afirma `obtido === esperado` em cada veredito; a `descricao` diz qual.
173
+ */
174
+ export declare function controlesNegativos(fabrica: () => ServidorConectavel | Promise<ServidorConectavel>, nome: string, args?: Record<string, unknown>, extras?: Quebra[]): Promise<Veredito[]>;
175
+ //# sourceMappingURL=cliente.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cliente.d.ts","sourceRoot":"","sources":["../src/cliente.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAGtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,6EAA6E;AAC7E,MAAM,MAAM,cAAc,GAAG;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1H,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;CACzC;AAID,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAYjH;AAWD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAOvG;AAED,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;CACxC;AAsBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAqB/E;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,QAAQ,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,4EAA4E;IAC5E,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,EAC/D,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAClC,MAAM,GAAE,MAAM,EAAO,GACpB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAuCrB"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Teste com forma de cliente: o servidor interrogado pelo `Client` do SDK, que
3
+ * reprova o resultado de `tools/call` contra o `outputSchema` LISTADO — o teste
4
+ * falha como a sessão do usuário falharia, sem validador escolhido por nós.
5
+ *
6
+ * Ideia de leitor (https://dev.to/arhancanli/comment/3g4i4); o circuito nasceu
7
+ * no ilo-mcp-server 1.3.0 e mora aqui para os sete servidores o usarem igual.
8
+ *
9
+ * Subpath próprio (`@sbissoli/mcp-surface/cliente`), fora da raiz do pacote: o
10
+ * `Client` compila os schemas com Ajv (`new Function`), que o runtime da
11
+ * Cloudflare proíbe. Importar daqui é coisa de teste em Node, nunca de Worker.
12
+ *
13
+ * Duas armadilhas que este módulo fecha, medidas no ilo:
14
+ * - o `Client` só valida contra o schema que tem em cache do `tools/list` —
15
+ * sem `listTools` antes, `callTool` devolve o que recebeu sem validar;
16
+ * - o `InMemoryTransport` não serializa: chave `undefined` sobrevive em memória
17
+ * e some no fio (`JSON.stringify`). Aqui toda mensagem do servidor passa por
18
+ * JSON antes de chegar ao cliente, como passaria pela rede.
19
+ */
20
+ import { Client } from "@modelcontextprotocol/client";
21
+ import { InMemoryTransport } from "@modelcontextprotocol/server";
22
+ const listados = new WeakSet();
23
+ export async function conectarComoCliente(server, opcoes = {}) {
24
+ const [lado, ladoServidor] = InMemoryTransport.createLinkedPair();
25
+ const enviar = ladoServidor.send.bind(ladoServidor);
26
+ ladoServidor.send = async (mensagem, extra) => {
27
+ const noFio = JSON.parse(JSON.stringify(mensagem));
28
+ const r = noFio.result;
29
+ if (opcoes.adulterar && r && "content" in r)
30
+ opcoes.adulterar(r);
31
+ return enviar(noFio, extra);
32
+ };
33
+ const client = new Client({ name: "mcp-surface/cliente", version: "0.0.0" });
34
+ await Promise.all([server.connect(ladoServidor), client.connect(lado)]);
35
+ return client;
36
+ }
37
+ /** O percurso do cliente: `tools/list` (uma vez por conexão) → `tools/call`. */
38
+ async function percorrer(client, nome, args) {
39
+ if (!listados.has(client)) {
40
+ await client.listTools();
41
+ listados.add(client);
42
+ }
43
+ return client.callTool({ name: nome, arguments: args });
44
+ }
45
+ /**
46
+ * Chamada que TEM de dar certo, no percurso do cliente. Lança quando o `Client`
47
+ * reprova o resultado e também quando a tool responde `isError` — o servidor do
48
+ * SDK v2 valida a própria saída e, se ela não obedece, devolve o erro em
49
+ * `isError` ("Output validation error"), não em exceção. Para afirmar uma
50
+ * recusa de propósito (parâmetro inválido), use `client.callTool` direto.
51
+ */
52
+ export async function chamarComoCliente(client, nome, args = {}) {
53
+ const r = await percorrer(client, nome, args);
54
+ if (r.isError) {
55
+ const texto = Array.isArray(r.content) ? r.content.map(c => ("text" in c ? c.text : "")).join(" ") : "";
56
+ throw new Error(`${nome} respondeu isError: ${texto}`);
57
+ }
58
+ return r;
59
+ }
60
+ /** Candidatos a valor errado, com os tipos JSON Schema que cada um satisfaz. */
61
+ const CANDIDATOS = [
62
+ ["valor-de-tipo-errado", ["string"]],
63
+ [0, ["number", "integer"]],
64
+ [true, ["boolean"]],
65
+ [[], ["array"]],
66
+ [{}, ["object"]],
67
+ ];
68
+ /**
69
+ * Um valor que NENHUM dos tipos declarados aceita — `type` simples ou lista
70
+ * (`["string", "null"]`, o campo anulável, que é onde o defeito mora). Sem
71
+ * `type` declarado não há o que trocar: `undefined`.
72
+ */
73
+ function tipoErrado(tipo) {
74
+ const tipos = typeof tipo === "string" ? [tipo] : Array.isArray(tipo) ? tipo : [];
75
+ if (tipos.length === 0)
76
+ return undefined;
77
+ return CANDIDATOS.find(([, aceitos]) => !aceitos.some(t => tipos.includes(t)))?.[0];
78
+ }
79
+ /**
80
+ * As quebras genéricas, DERIVADAS do schema listado (não de nomes escritos à
81
+ * mão): `structuredContent` ausente, cada campo obrigatório ausente, e cada
82
+ * obrigatório com tipo declarado trocado de tipo. Cada um, não o primeiro: até a
83
+ * 0.2.0 só o primeiro tipado era trocado, e no ilo esse caía num objeto
84
+ * (`dataflow`) e deixava os escalares sem prova.
85
+ */
86
+ export function quebrasDoSchema(outputSchema) {
87
+ const obrigatorios = Array.isArray(outputSchema.required) ? outputSchema.required : [];
88
+ const props = (outputSchema.properties ?? {});
89
+ const quebras = [
90
+ { descricao: "structuredContent ausente", adulterar: r => void delete r.structuredContent },
91
+ ...obrigatorios.map(campo => ({
92
+ descricao: `campo obrigatório ausente (${campo})`,
93
+ adulterar: (r) => void delete r.structuredContent?.[campo],
94
+ })),
95
+ ];
96
+ for (const campo of obrigatorios) {
97
+ const errado = tipoErrado(props[campo]?.type);
98
+ if (errado === undefined)
99
+ continue;
100
+ quebras.push({
101
+ descricao: `campo de tipo errado (${campo})`,
102
+ adulterar: r => {
103
+ if (r.structuredContent)
104
+ r.structuredContent[campo] = structuredClone(errado);
105
+ },
106
+ });
107
+ }
108
+ return quebras;
109
+ }
110
+ /**
111
+ * Controle negativo pelo lado do resultado: o servidor responde certo, o que
112
+ * chega ao cliente está quebrado, e a chamada TEM de falhar. Um servidor novo
113
+ * por quebra (`fabrica`), porque cada conexão leva a sua adulteração.
114
+ *
115
+ * Inclui a armadilha como veredito esperado "passa": sem `tools/list` antes, a
116
+ * primeira quebra passa calada. Se um dia o SDK passar a validar sem a lista,
117
+ * esse veredito acusa — e a regra do `listTools` pode ser revista.
118
+ *
119
+ * O teste afirma `obtido === esperado` em cada veredito; a `descricao` diz qual.
120
+ */
121
+ export async function controlesNegativos(fabrica, nome, args = {}, extras = []) {
122
+ const sonda = await conectarComoCliente(await fabrica());
123
+ let schema;
124
+ try {
125
+ const { tools } = await sonda.listTools();
126
+ schema = tools.find(t => t.name === nome)?.outputSchema;
127
+ }
128
+ finally {
129
+ await sonda.close();
130
+ }
131
+ if (!schema)
132
+ throw new Error(`${nome}: sem outputSchema no tools/list — não há contrato a provar`);
133
+ const quebras = [...quebrasDoSchema(schema), ...extras];
134
+ const rodar = async (q, listar) => {
135
+ const client = await conectarComoCliente(await fabrica(), { adulterar: q.adulterar });
136
+ try {
137
+ const r = listar ? await percorrer(client, nome, args) : await client.callTool({ name: nome, arguments: args });
138
+ // Erro de tool NÃO é reprovação do validador: a chamada-base tem de dar
139
+ // certo, ou o controle não prova nada — veredito que nunca confere.
140
+ if (r.isError)
141
+ return { obtido: "isError", mensagem: JSON.stringify(r.content) };
142
+ return { obtido: "passa" };
143
+ }
144
+ catch (e) {
145
+ return { obtido: "reprova", mensagem: e instanceof Error ? e.message : String(e) };
146
+ }
147
+ finally {
148
+ await client.close();
149
+ }
150
+ };
151
+ // Em paralelo: cada quebra tem servidor e conexão próprios. Desde a 0.2.1 há
152
+ // uma troca de tipo por obrigatório, e em série o medical (`loinc_details`,
153
+ // muitos obrigatórios) passava dos 5 s do vitest sob a carga da suíte.
154
+ const armadilha = quebras[1] ?? quebras[0];
155
+ return Promise.all([
156
+ ...quebras.map(async (q) => ({ descricao: q.descricao, esperado: "reprova", ...(await rodar(q, true)) })),
157
+ rodar(armadilha, false).then(r => ({
158
+ descricao: `a armadilha: sem tools/list antes, "${armadilha.descricao}" passa calada`,
159
+ esperado: "passa",
160
+ ...r,
161
+ })),
162
+ ]);
163
+ }
164
+ //# sourceMappingURL=cliente.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cliente.js","sourceRoot":"","sources":["../src/cliente.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAYjE,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAU,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAA0B,EAAE,MAAM,GAAkB,EAAE;IAC9F,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,GAAG,iBAAiB,CAAC,gBAAgB,EAAE,CAAC;IAClE,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACpD,YAAY,CAAC,IAAI,GAAG,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAoB,CAAC;QACtE,MAAM,CAAC,GAAI,KAAqC,CAAC,MAAM,CAAC;QACxD,IAAI,MAAM,CAAC,SAAS,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC;YAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACjE,OAAO,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC7E,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gFAAgF;AAChF,KAAK,UAAU,SAAS,CAAC,MAAc,EAAE,IAAY,EAAE,IAA6B;IAClF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;QACzB,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAc,EAAE,IAAY,EAAE,IAAI,GAA4B,EAAE;IACtG,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QACd,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxG,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,uBAAuB,KAAK,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAOD,gFAAgF;AAChF,MAAM,UAAU,GAA+B;IAC7C,CAAC,sBAAsB,EAAE,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC1B,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC;IACnB,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IACf,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,SAAS,UAAU,CAAC,IAAa;IAC/B,MAAM,KAAK,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IACjG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACtF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,YAAqC;IACnE,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAE,YAAY,CAAC,QAAqB,CAAC,CAAC,CAAC,EAAE,CAAC;IACrG,MAAM,KAAK,GAAG,CAAC,YAAY,CAAC,UAAU,IAAI,EAAE,CAAuC,CAAC;IACpF,MAAM,OAAO,GAAa;QACxB,EAAE,SAAS,EAAE,2BAA2B,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,iBAAiB,EAAE;QAC3F,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC5B,SAAS,EAAE,8BAA8B,KAAK,GAAG;YACjD,SAAS,EAAE,CAAC,CAAiB,EAAE,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,iBAAiB,EAAE,CAAC,KAAK,CAAC;SAC3E,CAAC,CAAC;KACJ,CAAC;IACF,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;QAC9C,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,OAAO,CAAC,IAAI,CAAC;YACX,SAAS,EAAE,yBAAyB,KAAK,GAAG;YAC5C,SAAS,EAAE,CAAC,CAAC,EAAE;gBACb,IAAI,CAAC,CAAC,iBAAiB;oBAAE,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;YAChF,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAWD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAA+D,EAC/D,IAAY,EACZ,IAAI,GAA4B,EAAE,EAClC,MAAM,GAAa,EAAE;IAErB,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC;IACzD,IAAI,MAA2C,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC;QAC1C,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,YAAmD,CAAC;IACjG,CAAC;YAAS,CAAC;QACT,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IACD,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,6DAA6D,CAAC,CAAC;IAEnG,MAAM,OAAO,GAAG,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,KAAK,EAAE,CAAS,EAAE,MAAe,EAAkD,EAAE;QACjG,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,MAAM,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QACtF,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAChH,wEAAwE;YACxE,oEAAoE;YACpE,IAAI,CAAC,CAAC,OAAO;gBAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;YACjF,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QAC7B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACrF,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,CAAC;IAEF,6EAA6E;IAC7E,4EAA4E;IAC5E,uEAAuE;IACvE,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAE,CAAC;IAC5C,OAAO,OAAO,CAAC,GAAG,CAAC;QACjB,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,EAAC,CAAC,EAAC,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,SAAkB,EAAE,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAChH,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACjC,SAAS,EAAE,uCAAuC,SAAS,CAAC,SAAS,gBAAgB;YACrF,QAAQ,EAAE,OAAgB;YAC1B,GAAG,CAAC;SACL,CAAC,CAAC;KACJ,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sbissoli/mcp-surface",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Impressão digital da superfície de um servidor MCP: initialize (instructions + capabilities) + tools/resources/prompts + quem responde sem token, travados ao lado da versão — mudou sem subir a versão = build vermelho e deploy recusado; mais a conferência do endpoint no ar e o replay das versões publicadas",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,6 +17,10 @@
17
17
  ".": {
18
18
  "types": "./dist/index.d.ts",
19
19
  "default": "./dist/index.js"
20
+ },
21
+ "./cliente": {
22
+ "types": "./dist/cliente.d.ts",
23
+ "default": "./dist/cliente.js"
20
24
  }
21
25
  },
22
26
  "bin": {
@@ -33,9 +37,16 @@
33
37
  "test": "vitest run"
34
38
  },
35
39
  "peerDependencies": {
40
+ "@modelcontextprotocol/client": "^2.0.0",
36
41
  "@modelcontextprotocol/server": "^2.0.0"
37
42
  },
43
+ "peerDependenciesMeta": {
44
+ "@modelcontextprotocol/client": {
45
+ "optional": true
46
+ }
47
+ },
38
48
  "devDependencies": {
49
+ "@modelcontextprotocol/client": "^2.1.0",
39
50
  "@modelcontextprotocol/server": "^2.1.0"
40
51
  },
41
52
  "keywords": [