Client.listTools() corrupts its cached tool metadata when an output schema fails to compile
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 76/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Tranquilla
- Stack tecnologico
- typescript
- Ambito
- api
Direzione di ricerca
Inizia in src/client/index.ts, in cacheToolMetadata(), ed esegui la riproduzione con il client. Verifica che una compilazione successiva dello schema di output che fallisce lasci disponibili il validatore dell'output precedente e i metadati del task, così che la chiamata successiva a callTool() continui a rifiutare il risultato non corrispondente.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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;
}
- Lingua principale
- TypeScript
- Stelle
- 13.5k
- Fork
- 2.3k
- Merge medio
- 2g 18h
- PR unite (30g)
- 55
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di modelcontextprotocol/typescript-sdk
-
v1 v2
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
modelcontextprotocol/typescript-sdk#2946 ·
I maintainer di solito rispondono entro 1 giorno
-
[v2] URI template reserved expansions encode existing %HH sequences againForse già presa @takagibit18 l’ha presa 4 giorni fa. Apertav1 v2
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
modelcontextprotocol/typescript-sdk#2920 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[v2] URI template strict expansions leave !'()* unencodedForse già presa @takagibit18 l’ha presa 4 giorni fa. Apertav1 v2
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
modelcontextprotocol/typescript-sdk#2919 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Malformed params on spec request methods return -32603 Internal error instead of -32602 Invalid paramsForse già presa Una pull request collegata a questa issue è aperta o già unita. Apertav1 v2
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
modelcontextprotocol/typescript-sdk#2916 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Unconditional `prompt=consent` (when `offline_access` in scope) blocks OAuth in Entra tenants with user consent disabled + admin consent grantedForse già presa @dasjideepak l’ha presa 10 giorni fa. Apertav1 v2
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 90/100
modelcontextprotocol/typescript-sdk#2867 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di modelcontextprotocol/typescript-sdk
Issue simili
-
triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
github/docs#46222 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
agent-ready area: config area: skills type: chore upstream: brain-kit
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 95/100
-
enhancement priority:low ready-for-dev
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno
-
bug escritorio mapa
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
marcosferr/reporte-ciudadano#4 · 1 commento ·
-
area: material/sort gemini-triaged needs triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
angular/components#33933 ·
I maintainer di solito rispondono entro 1 giorno