Client.listTools() corrupts its cached tool metadata when an output schema fails to compile
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 76/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Tranquilo
- Stack tecnológico
- typescript
- Área
- api
Línea de trabajo
Comienza en src/client/index.ts, en cacheToolMetadata(), y ejecuta la reproducción contra el cliente. Verifica que un fallo posterior en la compilación del esquema de salida deje disponibles el validador de salida anterior y los metadatos de la tarea, de modo que la llamada posterior a callTool() siga rechazando el resultado que no coincide.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Description
Client.listTools() clears its existing output-validator and task-metadata caches before every schema in the replacement catalog has compiled successfully.
If compilation of a later output schema throws, listTools() rejects as expected, but the previously valid metadata has already been erased or partially replaced. Subsequent callTool() operations may
therefore skip output validation, and cached task-support information may also be lost.
A failed catalog refresh should leave the previous complete metadata generation unchanged.
This is separate from the concurrent callTool()/listTools() validator-generation race reported in: #2612
Reproduction
Tested with @modelcontextprotocol/[email protected].
import assert from "node:assert/strict";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { ListToolsResultSchema } from "@modelcontextprotocol/sdk/types.js";
const client = new Client({
name: "metadata-cache-repro",
version: "1.0.0",
});
const validCatalog = ListToolsResultSchema.parse({
tools: [{
name: "versioned",
inputSchema: {
type: "object",
additionalProperties: false,
},
outputSchema: {
type: "object",
properties: {
generation: { const: "old" },
},
required: ["generation"],
additionalProperties: false,
},
}],
});
// The MCP result schema accepts this catalog because the output schema has a
// valid object root. AJV later rejects the invalid nested `type`.
const invalidCatalog = ListToolsResultSchema.parse({
tools: [{
name: "invalid",
inputSchema: {
type: "object",
additionalProperties: false,
},
outputSchema: {
type: "object",
properties: {
value: { type: "not-a-json-schema-type" },
},
},
}],
});
const catalogs = [validCatalog, invalidCatalog];
client.request = async ({ method }) => {
if (method === "tools/list") {
return catalogs.shift();
}
if (method === "tools/call") {
return {
content: [{ type: "text", text: "new" }],
structuredContent: { generation: "new" },
isError: false,
};
}
throw new Error(`Unexpected method: ${method}`);
};
// Installs the validator requiring generation === "old".
await client.listTools();
// Compilation throws, which is expected for the invalid schema.
await assert.rejects(() => client.listTools());
// This should still use the validator from the last successful catalog and
// reject generation === "new". Instead, it resolves because that validator
// was cleared before the failed replacement compiled.
await assert.rejects(
() => client.callTool({
name: "versioned",
arguments: {},
}),
/does not match the tool's output schema/,
);
The final assertion fails with:
AssertionError: Missing expected rejection
Expected behavior
Metadata replacement should be failure-atomic:
- Compile all output validators and collect all task metadata into temporary collections.
- Publish the new collections only after the entire catalog succeeds.
- If any schema compilation throws, preserve the previous complete collections.
Actual behavior
cacheToolMetadata() clears the current collections before compilation begins:
this._cachedToolOutputValidators.clear();
this._cachedKnownTaskTools.clear();
this._cachedRequiredTaskTools.clear();
A later compilation error therefore leaves the client with empty or partially replaced metadata.
Suggested fix
One possible failure-atomic fix is to build replacement Map and Set instances locally, then publish them only after every tool has been processed and every output schema has compiled successfully.
diff --git a/src/client/index.ts b/src/client/index.ts
--- a/src/client/index.ts
+++ b/src/client/index.ts
@@
private cacheToolMetadata(tools: Tool[]): void {
- this._cachedToolOutputValidators.clear();
- this._cachedKnownTaskTools.clear();
- this._cachedRequiredTaskTools.clear();
+ // Compile the complete replacement before publishing it so a late schema
+ // failure leaves the previous successful metadata generation intact.
+ const toolOutputValidators = new Map<string, JsonSchemaValidator<unknown>>();
+ const knownTaskTools = new Set<string>();
+ const requiredTaskTools = new Set<string>();
for (const tool of tools) {
// If the tool has an outputSchema, create and cache the validator
if (tool.outputSchema) {
const toolValidator = this._jsonSchemaValidator.getValidator(
tool.outputSchema as JsonSchemaType
);
- this._cachedToolOutputValidators.set(tool.name, toolValidator);
+ toolOutputValidators.set(tool.name, toolValidator);
}
// If the tool supports task-based execution, cache that information
const taskSupport = tool.execution?.taskSupport;
if (taskSupport === 'required' || taskSupport === 'optional') {
- this._cachedKnownTaskTools.add(tool.name);
+ knownTaskTools.add(tool.name);
}
if (taskSupport === 'required') {
- this._cachedRequiredTaskTools.add(tool.name);
+ requiredTaskTools.add(tool.name);
}
}
+
+ this._cachedToolOutputValidators = toolOutputValidators;
+ this._cachedKnownTaskTools = knownTaskTools;
+ this._cachedRequiredTaskTools = requiredTaskTools;
}
- Lenguaje dominante
- TypeScript
- Estrellas
- 13.5k
- Forks
- 2.3k
- Merge medio
- 2 d 7 h
- PR fusionados (30 d)
- 54
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de modelcontextprotocol/typescript-sdk
-
v1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
modelcontextprotocol/typescript-sdk#2946 ·
Los mantenedores suelen responder en 1 día
-
[v2] URI template reserved expansions encode existing %HH sequences againPosiblemente ocupada @takagibit18 la tomó hace 3 días. Abiertov1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
modelcontextprotocol/typescript-sdk#2920 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
[v2] URI template strict expansions leave !'()* unencodedPosiblemente ocupada @takagibit18 la tomó hace 3 días. Abiertov1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
modelcontextprotocol/typescript-sdk#2919 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Malformed params on spec request methods return -32603 Internal error instead of -32602 Invalid paramsPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abiertov1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
modelcontextprotocol/typescript-sdk#2916 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Unconditional `prompt=consent` (when `offline_access` in scope) blocks OAuth in Entra tenants with user consent disabled + admin consent grantedPosiblemente ocupada @dasjideepak la tomó hace 9 días. Abiertov1 v2
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
modelcontextprotocol/typescript-sdk#2867 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de modelcontextprotocol/typescript-sdk
Issues similares
-
enhancement good first issue priority: low size: XS
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
Empty label or headline exports the editor hint ("LABEL" / "Headline goes here") into the PNGAbierto
Dificultad 1/5 1-3 horas Aptitud para principiantes 88/100
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
streamplace/streamplace#1351 ·
Los mantenedores suelen responder en 2 días
-
Link Checker ReportAbiertoautomated issue report
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
databendlabs/databend-docs#3511 ·
-
automated issue report
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100