← Retour au blog
Analyse

Après MCP TypeScript SDK 2.4.0 (23 oct. 2026) et Skills over MCP : comment l'hôte Agent valide-t-il le digest de skills/list avant de charger SKILL.md ?

Publié le 2026-11-06 Dernière mise à jour le 2026-11-06 Environ 11 min JSON Toolbox

Objectif de cette note

Au 06/11/2026, le MCP TypeScript SDK 2.4.0 (23/10/2026) intègre SEP-2640 Skills over MCP dans @modelcontextprotocol/server/ext/skills et client/ext/skills. Le serveur monte des dossiers avec loadSkills(folder) ; le client utilise SkillsClient pour skills/list, skills/get et resources/read. Avant d'injecter SKILL.md dans le contexte, l'hôte doit traiter size + digest SHA-256 + frontmatter du listing comme second contrat — non interchangeable avec inputSchema de tools/list. Retours d'outil : outputSchema et structuredContent. Manuels malveillants : fake skills.

Beaucoup d'équipes voient d'abord les skills comme des dossiers Markdown lus localement. Une fois MCP en jeu, les octets viennent du réseau ou d'un serveur distant — sans digest, c'est la même classe de risque que faire confiance à n'importe quelle description d'outil. Le SDK 2.4.0 encode la vérification côté client ; la couche policy décide encore quels URI de skill charger et si un skill chargé peut déclencher des outils à privilèges élevés.

Ce que le SDK 2.4.0 apporte le 23 oct.

  • Serveur : SkillsExtension.attach(server) expose skills/list, skills/get, resources/directory/read et le modèle skill://{+path} ; côté Node, loadSkills('./skills') scanne les répertoires, skills invalides ignorés par défaut (warn), strict fait échouer tout le paquet.
  • Client : SkillsClient avec list(), get(), read(), readDirectory() ; read lit par défaut le SKILL.md du skill.
  • Protocole : support 2025-11-25 et 2026-07-28 ; ce dernier ajoute ttlMs et cacheScope sur les pages de listing — champs manquants → erreur SDK.
  • Stabilité : attach est @experimental ; le reste des API Skills et les schemas dans core/ext/skills sont stable.

En une phrase. Un skill est un « paquet de ressources avec digest », pas un « system prompt sans contrôle ». Les tools restent des « RPC avec inputSchema ».

Métadonnées dans skills/list

Chaque entrée est du JSON : URI, nom affiché, liste de fichiers, pour chaque fichier size et digest (SHA-256), plus le frontmatter parsé depuis SKILL.md. Exemple aligné sur la doc SDK (noms de champs selon SEP-2640 / SDK ; vérifier votre version de protocole à l'intégration) :

{
  "skills": [
    {
      "uri": "skill://deploy/runbook/",
      "name": "deploy-runbook",
      "description": "Production deploy checklist",
      "root": {
        "path": "SKILL.md",
        "size": 2048,
        "digest": "sha256:abc123…",
        "mimeType": "text/markdown"
      },
      "files": [
        {
          "path": "SKILL.md",
          "size": 2048,
          "digest": "sha256:abc123…"
        },
        {
          "path": "references/rollback.md",
          "size": 890,
          "digest": "sha256:def456…"
        }
      ],
      "frontmatter": {
        "name": "deploy-runbook",
        "description": "Production deploy checklist"
      }
    }
  ],
  "ttlMs": 300000,
  "cacheScope": "session"
}

list() pagine jusqu'à absence de cursor ; URI dupliquées ou entrées invalides lèvent SdkError par défaut, ou skipInvalid: true avec tableau invalid. Traitez invalid comme signal de sécurité, pas comme bruit à ignorer.

Ce que SkillsClient.read vérifie

Le client officiel applique trois contrôles avant de remettre le texte à l'agent :

  1. Appartenance au listing : le chemin demandé doit figurer dans files ; le serveur ne peut pas ajouter des fichiers non déclarés.
  2. size + digest : longueur et hash des octets lus doivent correspondre au listing ; sinon SkillVerificationError.
  3. Frontmatter SKILL.md : pour le Markdown racine du skill, le frontmatter doit correspondre au listing.

Si la réponse vient du cache client et échoue la vérification, le SDK tente d'abord cacheMode: 'refresh'. Les skills marqués dynamic ignorent listing/size/digest (verified: false) mais vérifient encore le frontmatter — allowlist séparée côté hôte pour les skills dynamic.

{
  "read_ok": {
    "path": "SKILL.md",
    "verified": true,
    "text": "---\nname: deploy-runbook\n---\n# Checklist\n"
  },
  "read_fail_digest": {
    "path": "SKILL.md",
    "error": "SkillVerificationError",
    "reason": "digest mismatch vs skills/list"
  },
  "read_fail_extra_file": {
    "path": "references/evil.sh",
    "error": "not in listing.files"
  }
}

Division du travail avec JSON Schema

Dans la même session, trois contrats JSON coexistent souvent :

  • Tool inputSchema — arguments de tools/call ; racine object (MCP 2026-07-28).
  • Tool outputSchema — structuredContent ; voir l'article outputSchema.
  • Listing de skill — pas un JSON Schema mais du content addressing : digest lie les octets, frontmatter lie les métadonnées. Vous pouvez ajouter un JSON Schema pour un snapshot de manifest hôte ; cela ne remplace pas le digest MCP.
{
  "host_manifest_schema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
      "allowed_skill_uris": {
        "type": "array",
        "items": { "type": "string", "pattern": "^skill://" }
      },
      "require_verified": { "const": true }
    },
    "required": ["allowed_skill_uris", "require_verified"],
    "additionalProperties": false
  },
  "snapshot_ok": {
    "allowed_skill_uris": ["skill://deploy/runbook/"],
    "require_verified": true
  }
}

Valider snapshot_ok contre host_manifest_schema attrape les URI mal configurées ; cela n'empêche pas un serveur de mentir dans le listing — read() et le digest restent la ligne de défense.

Ordre d'intégration côté hôte

  1. Discover : SkillsClient.list() ; sous 2026-07-28 vérifier ttlMs / cacheScope par page.
  2. Policy : filtrer par origin, préfixe (ex. skill://acme/) ; rejeter les URI hors allowlist.
  3. Load : read() seulement pour les skills autorisés ; par défaut ne pas injecter si verified: false.
  4. Tools : une fois le texte du skill dans le prompt, les appels d'outil passent encore par validation locale inputSchema — voir enum / additionalProperties dans fake skills.
  5. Cache : respecter ttlMs ; relister après expiration pour ne pas garder un vieux digest.

Essayer dans JSON Toolbox

{
  "tool_call_after_skill": {
    "name": "run_deploy_step",
    "arguments": "{\"step\":\"rollback\",\"env\":\"prod\"}"
  },
  "tool_inputSchema": {
    "type": "object",
    "properties": {
      "step": { "type": "string", "enum": ["plan", "apply", "rollback"] },
      "env": { "type": "string", "enum": ["staging", "prod"] }
    },
    "required": ["step", "env"],
    "additionalProperties": false
  }
}
  1. Validation Schema : tool_call_after_skill.arguments contre tool_inputSchema.
  2. MCP Tool : aligner les noms de champs inputSchema / outputSchema.
  3. Formater le JSON de listing avec Formatage JSON, puis vérifier les champs digest.
  4. Pour la sortie structurée finale, utiliser Sortie structurée à part — ne pas mélanger avec le texte du skill.

Questions fréquentes

Skills over MCP et inputSchema de tools/call, est-ce la même chose ?

Non — skills via listing + digest ; tools via objet d'arguments JSON Schema.

Faut-il donner le contenu au modèle si le digest ne correspond pas ?

Non — contenu non fiable, journaliser SkillVerificationError.

Quels champs en plus sous le protocole 2026-07-28 pour skills/list ?

ttlMs et cacheScope ; champs manquants → erreur SDK.

Comment cela se complète avec l'article sécurité fake skills ?

Fake skills = surface d'attaque ; cet article = chaîne de vérification SEP-2640 officielle.

SkillsExtension.attach est-il une API stable ?

attach experimental ; SkillsClient stable — encapsuler attach.

En résumé

Au 06/11/2026, MCP TypeScript SDK 2.4.0 fait des skills une extension MCP de premier ordre avec contrat skills/list et vérification digest, au lieu d'une lecture de dossier côté hôte. Intégration agent : list → filtre policy → read() vérifié → seulement alors influence sur le routage ; tout tools/call reste borné par inputSchema. Prochaine étape : serveur loadSkills réel, SkillsClient.read, altérer des octets localement et confirmer le blocage digest — en parallèle de la validation schema des outils du même agent.