[v2] MRTR wipe-cache example reads unvalidated elicitation content and defaults malformed scope to all

Fermée Adaptée aux débutants
#2,542 3 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

Personne n'a encore pris cette issue.

Évaluation

Difficulté
2/5
Temps estimé
1-3 heures
Accessibilité débutants
78/100
Type d'issue
Documentation
Clarté
Clairement spécifiée
Activité
Active
Stack technique
typescript

Piste de recherche

Ouvrez docs/servers/input-required.md et repérez l’exemple d’effacement du cache « Carry state across rounds with requestState ». Lisez l’utilisation précédente de acceptedContent() prenant en compte le schéma sur la page, puis mettez à jour la gestion de la confirmation et celle de la portée afin de valider le contenu client à l’exécution. C’est terminé lorsque les requêtes dont la portée est invalide ou absente redemandent une saisie au lieu d’utiliser « all » par défaut.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

spec-2026-07-28 v2
What happened?

The v2 input_required documentation correctly states that
ctx.mcpReq.inputResponses is client-provided and untrusted, and recommends
using the schema-aware overload of acceptedContent().

However, the sequential wipe-cache example uses the unchecked generic
overload for both confirmation and scope:

const confirmed = acceptedContent<{ confirm: boolean }>(
    ctx.mcpReq.inputResponses,
    'confirm'
);

It later treats that response as proof that the operator confirmed:

if (confirmed?.confirm !== true) {
    return inputRequired({
        inputRequests: {
            confirm: inputRequired.elicit({
                message: 'Really wipe the cache?',
                requestedSchema: {
                    type: 'object',
                    properties: {
                        confirm: {
                            type: 'boolean'
                        }
                    },
                    required: ['confirm']
                }
            })
        }
    });
}

return inputRequired({
    inputRequests: {
        scope: inputRequired.elicit({
            message: 'Which scope?',
            requestedSchema: {
                type: 'object',
                properties: {
                    scope: {
                        type: 'string'
                    }
                },
                required: ['scope']
            }
        })
    },
    requestState: await stateCodec.mint({
        step: 'confirmed'
    })
});

The generic type parameter only affects TypeScript typing. It does not perform
runtime validation of the client-provided value.

For example, a malformed accepted response could contain:

{
  "action": "accept",
  "content": {
    "confirm": "false"
  }
}

The string "false" is truthy in JavaScript. Because the example only checks:

confirmed?.confirm !== true

this particular value does not equal the boolean true, so the current
!== true check fails closed.

However, the example still demonstrates reading untrusted client content using
a compile-time generic instead of the runtime-validating overload. A copied or
slightly modified version using a normal truthiness check such as:

if (!confirmed?.confirm) {
    // request confirmation
}

would treat "false" as confirmation.

The same example later reads the requested scope with the unchecked overload:

const scope = acceptedContent<{ scope: string }>(
    ctx.mcpReq.inputResponses,
    'scope'
);

return {
    content: [{
        type: 'text',
        text: `Wiped ${scope?.scope ?? 'all'}`
    }]
};

If the scope response is missing, malformed, or of an unexpected type, the
example falls back to "all". That feels unsafe for a destructive example and
conflicts with the page's earlier guidance to validate inputResponses using
the schema-aware overload.

This is primarily a documentation and defensive API-usage issue, not a claim
that a malicious MCP client gains a capability it did not already possess.

What did you expect?

I expected the destructive wipe-cache example to follow the page's own
guidance and validate all client-provided elicitation content at runtime.

For example:

const confirmationSchema = z.object({
    confirm: z.boolean()
});

const scopeSchema = z.object({
    scope: z.string()
});

const confirmed = acceptedContent(
    ctx.mcpReq.inputResponses,
    'confirm',
    confirmationSchema
);

if (confirmed?.confirm !== true) {
    return inputRequired({
        inputRequests: {
            confirm: inputRequired.elicit({
                message: 'Really wipe the cache?',
                requestedSchema: confirmationSchema
            })
        }
    });
}

const scope = acceptedContent(
    ctx.mcpReq.inputResponses,
    'scope',
    scopeSchema
);

if (scope === undefined) {
    return inputRequired({
        inputRequests: {
            scope: inputRequired.elicit({
                message: 'Which scope?',
                requestedSchema: scopeSchema
            })
        }
    });
}

return {
    content: [{
        type: 'text',
        text: `Wiped ${scope.scope}`
    }]
};

The example should also fail closed when scope content is missing or invalid,
rather than defaulting to "all".

Code to reproduce
Documentation page:

`docs/servers/input-required.md`

Relevant section:

`Carry state across rounds with requestState`

Relevant example:


const confirmed = acceptedContent<{ confirm: boolean }>(
    ctx.mcpReq.inputResponses,
    'confirm'
);

const scope = acceptedContent<{ scope: string }>(
    ctx.mcpReq.inputResponses,
    'scope'
);

return {
    content: [{
        type: 'text',
        text: `Wiped ${scope?.scope ?? 'all'}`
    }]
};


Minimal demonstration that the generic type does not validate at runtime:


const clientContent: unknown = {
    confirm: 'false'
};

const confirmed =
    clientContent as {
        confirm: boolean;
    };

console.log(typeof confirmed.confirm);
// "string"

console.log(Boolean(confirmed.confirm));
// true


The cast changes the TypeScript type but does not transform or validate the
runtime value.
SDK version

main at commit 1e1392e3f91583884fe82a0b4b91335875c3fba6

Area

Documentation

Langage dominant
TypeScript
Étoiles
13.7k
Forks
2.4k
Merge moyen
2 j 7 h
PR mergées (30 j)
26

Préparer son environnement

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de modelcontextprotocol/typescript-sdk

Toutes les issues de modelcontextprotocol/typescript-sdk

Issues similaires

Plus d'issues TypeScript

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.