Référence des outils MCP
VMark expose neuf outils MCP composites aux assistants IA : session, workspace, document, workflow, selection, browser, browser_read, coherence et coherence_resolve. Ensemble, ils couvrent la colonne vertébrale de l'éditeur, le cycle de vie fichier/fenêtre, les modifications de workflow sûres au CST, les modifications ciblées sur la sélection, la navigation web bornée et une vue de la couche de cohérence de l'espace de travail.
Trois des neuf — session, browser_read et coherence — déclarent readOnlyHint: true, de sorte qu'un client MCP peut les approuver automatiquement. C'est précisément pour cela que browser/browser_read et coherence/coherence_resolve sont des outils distincts : les annotations sont par outil, pas par action ; un outil qui regroupe un instantané ARIA avec execute_js doit donc annoncer le danger d'execute_js. Répartir selon « est-ce que ceci modifie quelque chose ? » permet à chaque moitié de dire la vérité, et garde les actions véritablement destructrices de la surface bien visibles dans la liste des outils.
La précédente surface de 12 outils / 76 actions a été élaguée parce que les outils de mise en forme intra-document (gras, titres, tableaux, etc.) dupliquent un travail que les agents IA effectuent déjà trivialement via un aller-retour Markdown. selection a été conservé (conformément à l'ADR-7 du plan d'élagage) parce que l'aller-retour sur le document complet n'est pas économique sur les gros fichiers — chaque modification paie le document entier en jetons d'entrée, le document entier en jetons de sortie (~5× le prix de l'entrée), et une fenêtre d'écriture plus longue qui élargit la boucle de réessai sur révision obsolète. Voir le plan d'élagage MCP pour la justification complète.
Flux de travail recommandé
- Appelez
session.get_stateune fois pour voir les fenêtres ouvertes, les onglets et{filePath, dirty, revision, kind}par onglet. - Pour de petites modifications Markdown ou des réécritures complètes :
document.read→ raisonner →document.write(en passantexpected_revisionpour une concurrence sûre). - Pour des modifications ciblées sur un gros fichier Markdown quand l'utilisateur a sélectionné la région à changer :
selection.get→ raisonner →selection.set(réduit le coût en jetons d'entrée comme de sortie à la seule sélection). - Pour le YAML GitHub Actions (
kind: "yaml-workflow") :workflow.apply_patchpour des modifications sûres au CST qui préservent les commentaires et les ancres ;workflow.validatepour les diagnostics actionlint. - Les opérations sur fichiers (ouvrir, enregistrer, fermer, basculer d'onglet) résident sur
workspace.
Diagrammes Mermaid
Lors de l'utilisation de l'IA pour générer du Mermaid via MCP, envisagez d'installer le serveur MCP mermaid-validator — il détecte les erreurs de syntaxe en utilisant les mêmes parseurs Mermaid v11 avant que les diagrammes n'atteignent votre document.
session
Orientation en un coup. Découvrez chaque fenêtre, chaque onglet et les capacités du serveur en un seul appel.
get_state
Aucun argument.
Retourne {windows, capabilities} :
{
"windows": [
{
"label": "main",
"focused": true,
"activeWorkspaceInstanceId": "wsi-a1b2c3",
"tabs": [
{
"id": "tab-1",
"filePath": "/path/to/notes.md",
"title": "notes",
"dirty": false,
"revision": "rev-x7Q3aB1F",
"kind": "markdown",
"active": true,
"visible": true
},
{
"id": "tab-2",
"filePath": "/repo/.github/workflows/ci.yml",
"title": "ci",
"dirty": true,
"revision": "rev-x7Q3aB1F",
"kind": "yaml-workflow",
"active": false,
"visible": false
}
]
}
],
"capabilities": {
"version": "<vmark-mcp-server version>",
"supportedKinds": ["markdown", "yaml-workflow"],
"mcpProtocol": "0.2.0"
}
}Savoir ce qui est réellement à l'écran
Un onglet peut exister, être adressable et ne toujours pas être affiché. Trois champs le disent :
| Champ | Signification |
|---|---|
tab.active | Cet onglet est l'onglet courant de sa fenêtre. |
tab.visible | Cet onglet est rendu à l'instant présent. Il vaut false quand l'onglet appartient à une instance d'espace de travail que la fenêtre n'affiche pas actuellement. |
window.activeWorkspaceInstanceId | L'instance d'espace de travail que la fenêtre affiche, ou null quand le rail des espaces de travail est désactivé (tous les onglets sont alors visibles). |
window.focused est la fenêtre que l'utilisateur regarde, lue depuis le système d'exploitation. Ce n'est pas « la fenêtre qui a répondu à cette requête » — VMark achemine une requête vers la fenêtre qui possède l'espace de travail concerné, ce qui, dans une session multifenêtre, est souvent une autre fenêtre.
Considérez ces champs comme l'étape de confirmation : après workspace.switch_tab, un get_state de suivi vous indique si l'onglet est réellement devant l'utilisateur. switch_tab relit lui-même les stores avant de répondre ; il rapporte donc activated: false quand une activation n'a pas abouti, au lieu de renvoyer la requête en écho.
Le discriminant kind vous indique s'il faut utiliser document.write (pour markdown) ou workflow.apply_patch (pour yaml-workflow) sur cet onglet.
workspace
Cycle de vie des fichiers et fenêtres. Rien dans le document.
Portée des chemins. Les opérations sur fichiers (
open,save,save_as) sont confinées à la racine de l'espace de travail ouvert et aux répertoires des documents déjà ouverts. Une requête portant sur un chemin hors de cette portée est refusée avecINVALID_PATH. Sans espace de travail ni document ouvert, il n'y a pas de portée ; les opérations sur fichiers sont alors refusées. Cela maintient un client automatisé dans les limites de ce que vous avez ouvert.
new
Créer un nouvel onglet sans titre.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
kind | string | Non | "markdown" (par défaut) ou "yaml-workflow" |
windowLabel | string | Non | Fenêtre cible ; par défaut, la fenêtre focalisée |
Retourne {tabId}.
open
Ouvrir un fichier depuis le disque dans un onglet en arrière-plan — l'onglet visible de l'utilisateur et son espace de travail ne changent pas. Enchaînez le tabId retourné dans des appels document / selection ; n'utilisez switch_tab que lorsque l'utilisateur doit voir l'onglet.
| Paramètre | Type | Requis |
|---|---|---|
filePath | string | Oui |
windowLabel | string | Non |
Retourne {tabId, workspaceInstanceId, activationChanged, workspaceSwitched}.
open_workspace
Ouvrir un dossier comme espace de travail actif. Contrairement à open (un fichier unique à l'intérieur d'une arborescence déjà consentie), ceci accorde à l'assistant l'accès à une toute nouvelle arborescence de fichiers ; l'opération est donc encadrée par une approbation ponctuelle de l'utilisateur et n'est pas couverte par la portée des chemins ci-dessus.
| Paramètre | Type | Requis |
|---|---|---|
folderPath | string | Oui |
windowLabel n'est pas accepté ici, contrairement à new et open. Le dossier s'ouvre toujours dans la fenêtre où la requête arrive. C'est délibéré : la boîte de dialogue d'approbation et l'ouverture doivent atterrir dans la même fenêtre, et une étiquette fournie par le client pourrait présenter l'invite devant une fenêtre tout en en modifiant une autre — approuver une chose et en obtenir une autre. Le ciblage multifenêtre nécessite un routage des requêtes qui n'existe pas encore.
Flux d'approbation. Le premier appel retourne {needsApproval: true} et affiche une boîte de dialogue de consentement nommant le chemin canonique du dossier (liens symboliques résolus). L'assistant devrait demander à l'utilisateur, puis réessayer le même appel ; une fois que l'utilisateur approuve, le nouvel essai ouvre le dossier. Une requête refusée continue d'échouer jusqu'à ce qu'elle soit ré-approuvée. Il n'y a pas d'option « se souvenir » — chaque ouverture est approuvée individuellement.
Accès aux dossiers. Approuver la boîte de dialogue ne suffit pas pour que VMark lise un dossier situé hors des emplacements qu'il peut toujours lire (votre dossier personnel et les volumes montés). Pour un tel dossier, la nouvelle tentative approuvée ouvre le sélecteur de dossiers de VMark sur ce dossier et répond APPROVAL_REQUIRED, en demandant à l'utilisateur de le choisir ; ensuite, la tentative suivante l'ouvre. Si une autre boîte de dialogue de dossier est déjà ouverte, rien n'est affiché et la réponse est BUSY — l'approbation est conservée, réessayez donc une fois cette boîte fermée. BUSY est aussi la réponse tant qu'un changement d'espace de travail est déjà en cours dans cette fenêtre.
save
Enregistrer un onglet vers son chemin existant.
| Paramètre | Type | Requis |
|---|---|---|
tabId | string | Non (par défaut, focalisé) |
Retourne {filePath, revision}.
save_as
Enregistrer un onglet vers un nouveau chemin.
| Paramètre | Type | Requis |
|---|---|---|
tabId | string | Non |
filePath | string | Oui |
Retourne {revision}.
Enregistrer vers un chemin autre que le fichier courant de l'onglet est traité comme une nouvelle écriture. Lorsque Approuver automatiquement les enregistrements vers un nouvel emplacement et les résultats des génies (Paramètres → Intégrations) est désactivé (par défaut), une telle requête est refusée avec APPROVAL_REQUIRED et une notification vous indique ce qui a été bloqué. Enregistrer de nouveau vers le chemin propre de l'onglet est toujours autorisé.
close
Fermer un onglet de document. Refuse de jeter du travail non enregistré sans force, et ne ferme jamais un onglet épinglé.
| Paramètre | Type | Requis |
|---|---|---|
tabId | string | Oui |
force | boolean | Non |
Retourne {closed: true} en cas de succès. Sinon {closed: false, reason} :
reason | Signification |
|---|---|
DIRTY | L'onglet a des modifications non enregistrées et force n'a pas été fourni |
DIVERGENT | Le fichier a changé sur le disque et l'utilisateur a conservé la version de l'onglet ; sans force, la fermeture la perdrait |
PINNED | L'onglet est épinglé — refusé même avec force ; l'utilisateur doit le désépingler |
Les frappes que l'éditeur n'a pas encore transmises sont comptées comme non enregistrées avant la vérification. Un onglet de navigateur est refusé avec une erreur INVALID_TAB — fermez-le avec l'action close de l'outil browser.
switch_tab
Activer un onglet et le rendre visible. Avec le rail des espaces de travail activé, ceci peut changer le contexte d'espace de travail actif de l'utilisateur — la réponse rapporte workspaceSwitched: true lorsque c'est le cas, l'assistant devrait donc le signaler à l'utilisateur.
| Paramètre | Type | Requis |
|---|---|---|
tabId | string | Oui |
Retourne {activated, workspaceSwitched, workspaceInstanceId, activeTabId}.
focus_window
Donner le focus à une fenêtre.
| Paramètre | Type | Requis |
|---|---|---|
windowLabel | string | Oui |
document
Lire, écrire, transformer. La colonne vertébrale de la surface.
read
| Paramètre | Type | Requis |
|---|---|---|
tabId | string | Non (par défaut, focalisé) |
Retourne {content, revision, filePath, kind, dirty}. Toujours lire avant d'écrire — le jeton revision doit accompagner le prochain write.
write
Remplacer le contenu complet du document.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
tabId | string | Non | Onglet cible (par défaut, focalisé) |
content | string | Oui | Nouveau contenu complet |
expected_revision | string | Non | Jeton de révision de la lecture la plus récente |
save | boolean | Non | Enregistrer aussi sur le disque (par défaut true) ; false ne modifie que l'onglet |
Par défaut, l'écriture est enregistrée : la réponse porte saved: true, ou saved: false avec save_skipped ("untitled" — l'onglet n'a pas encore de fichier, utilisez save_as ; "opt_out" — vous avez passé save: false) ou save_error (l'écriture sur le disque a échoué). Lorsque la cible est l'onglet WYSIWYG actif d'un document Markdown, le texte est chargé dans l'éditeur en direct (en une seule étape annulable), et ce qui est enregistré est la sérialisation qu'en fait l'éditeur — le même Markdown, éventuellement normalisé, pas nécessairement les caractères exacts envoyés. Les autres onglets enregistrent le texte tel qu'envoyé, fins de ligne normalisées.
Chaque enregistrement effectué par un client IA — via write, workspace.save ou workspace.save_as — est classé dans l'historique du document comme un instantané mcp (marqué (mcp) dans la barre latérale Historique), afin que les versions écrites par une IA se distinguent des vôtres. Comme un enregistrement manuel, il n'est jamais fusionné avec un enregistrement automatique voisin ni ignoré en raison de sa taille.
Si expected_revision est fourni et que le document a changé depuis cette lecture, la réponse est une enveloppe d'erreur structurée STALE avec la révision actuelle ; relire et réessayer.
// success
{ "revision": "rev-newAfterWrite", "saved": true }
// stale
{ "error": "STALE", "message": "Document has changed since the last read", "current_revision": "rev-currentNow" }Pendant que l'utilisateur compose du texte avec une méthode de saisie (IME) dans l'éditeur WYSIWYG qui affiche l'onglet, l'écriture est refusée avec BUSY et rien ne change : le texte en cours de composition appartient à la méthode de saisie jusqu'à sa validation. Réessayez peu après. En mode Source, l'écriture est acceptée, et l'éditeur l'affiche dès que la composition se termine.
transform
Appliquer une réécriture déterministe. Prend actuellement en charge les transformations spécifiques au CJK (conversion ponctuation pleine largeur ↔ ASCII, espacement CJK ↔ Latin).
| Paramètre | Type | Requis | Description |
|---|---|---|---|
tabId | string | Non | Onglet cible |
kind | string | Oui | "cjk-format", "cjk-spacing" ou "cjk-punctuation" |
expected_revision | string | Non | Jeton de concurrence |
cjk-format applique de bout en bout les paramètres de mise en forme CJK de l'utilisateur. cjk-spacing insère des espaces simples entre les caractères CJK et les caractères latins/chiffres adjacents. cjk-punctuation convertit la ponctuation ASCII qui se trouve à côté des caractères CJK vers sa forme pleine largeur.
Retourne {revision}. Comme write, il est refusé avec BUSY, sans rien modifier, pendant que l'utilisateur compose avec une méthode de saisie dans l'éditeur WYSIWYG qui affiche l'onglet.
workflow
Validation actionlint et modifications chirurgicales sûres au CST pour le YAML de workflow GitHub Actions. Disponible uniquement pour les onglets dont le kind est "yaml-workflow".
document.read / document.write fonctionnent sur tous les onglets — y compris le YAML de workflow
L'outil workflow n'est pas un substitut à la colonne vertébrale lecture/écriture. Pour un onglet de workflow, vous pouvez :
document.readpour obtenir le texte YAML brut (avec tous les commentaires)document.writepour le remplacer en gros (la chaîne que vous envoyez est stockée verbatim — commentaires préservés si vous les incluez)workflow.apply_patchlorsque vous voulez que le serveur lui-même garantisse que les commentaires, ancres et ordre des clés survivent à une modification partielle
Utilisez apply_patch lors du changement d'un champ en laissant tout le reste intact (le serveur ne peut pas supprimer les commentaires qu'il ne change pas). Utilisez document.write quand vous réécrivez en gros ou générez un nouveau workflow à partir de zéro.
apply_patch
Appliquer un tableau d'objets IRPatch. Les patches sont distribués via les mutateurs sensibles au CST de VMark, qui préservent les commentaires, ancres et ordre des clés. Un document.write brut sur un fichier YAML les perdrait.
| Paramètre | Type | Requis |
|---|---|---|
tabId | string | Non |
patches | IRPatch[] | Oui |
expected_revision | string | Non |
IRPatch est une union discriminée (champ kind). Types pris en charge :
kind | Effet |
|---|---|
workflow.set | Définir des champs de premier niveau ({path, value}) — name, env.X, etc. |
job.set | Définir un champ sur un job ({jobId, path, value}) |
step.set | Définir un champ sur une étape ({jobId, stepIndex, path, value}) |
with.set | Définir une clé dans le bloc with: d'une étape ({jobId, stepIndex, key, value}) |
with.remove | Supprimer une clé du bloc with: d'une étape |
needs.add / needs.remove | Ajouter ou supprimer un ID de job de needs: |
trigger.setFilters | Remplacer un tableau de filtres de déclencheur — branches, paths, types, etc. ({event, filter, value: string[]}) |
Retourne {revision} en cas de succès ou une enveloppe d'erreur structurée STALE / INVALID_PATCH / NOT_WORKFLOW.
validate
Exécuter actionlint sur le YAML du workflow.
| Paramètre | Type | Requis |
|---|---|---|
tabId | string | Non |
Retourne {ok, diagnostics, binaryAvailable}. Chaque diagnostic porte {line, col, message, severity}. binaryAvailable: false signifie qu'actionlint n'est pas installé localement ; installez via Homebrew ou les versions amont.
selection
Lire ou remplacer la sélection actuelle de l'utilisateur dans l'éditeur. Utilisez ceci plutôt que document.read/document.write lorsque l'utilisateur a mis en surbrillance la région à changer — selection.get retourne uniquement la portion sélectionnée, et selection.set réécrit uniquement cette plage, de sorte que le coût en jetons croît avec la modification, non avec le document.
La sélection est un état de vue — onglet focalisé uniquement
La sélection n'existe que dans l'éditeur actuellement rendu. Si tabId est fourni, il doit correspondre à l'onglet focalisé ; une non-correspondance retourne INVALID_TAB. Si l'onglet focalisé n'a pas d'éditeur actif (par ex. une visionneuse en lecture seule), la réponse est NO_EDITOR.
get
| Paramètre | Type | Requis |
|---|---|---|
tabId | string | Non |
Retourne :
| Champ | Type | Remarques |
|---|---|---|
text | string | Sérialisation Markdown de la portion sélectionnée (mode WYSIWYG), ou texte sélectionné brut (mode source). Chaîne vide quand la sélection est réduite. |
isEmpty | boolean | true quand la sélection est réduite (curseur seul). |
range | {from, to} | Positions ProseMirror en mode WYSIWYG ; décalages de caractères en mode source. |
mode | "wysiwyg" | "source" | Lève l'ambiguïté sur l'espace de positions de range. |
kind | "markdown" | "yaml-workflow" | Discriminant du type de document. |
tabId | string | Renvoyé en écho pour confirmation. |
revision | string | À repasser dans set pour une concurrence optimiste. |
set
| Paramètre | Type | Requis |
|---|---|---|
tabId | string | Non |
content | string | Oui |
expected_revision | string | Non (recommandé) |
Remplace ce que l'éditeur signale comme la sélection actuelle. En mode WYSIWYG, le texte en ligne simple est inséré comme un nœud de texte littéral, de sorte que les espaces de début/fin sont préservés à l'identique ; un contenu portant des marqueurs markdown (**bold**, *italic*, `code`, code clôturé, citations, listes, etc.) est analysé comme du markdown et inséré sous forme des nœuds correspondants. En mode source, content est toujours inséré comme du texte brut — la surface source est déjà des octets markdown. Un content vide supprime la sélection. Lorsque la sélection est réduite, content est inséré au niveau du curseur.
Retourne {revision, replaced_chars} en cas de succès. replaced_chars est la longueur du texte qui était sélectionné avant l'appel — utile pour que l'IA confirme qu'elle a bien modifié ce qu'elle attendait.
STALE retourne {error: "STALE", message, current_revision}, exactement comme document.write. La révision au niveau du document capte les frappes entre get et set. Le simple déplacement du curseur (sans frappe) n'est pas arbitré par le serveur — si l'utilisateur a déplacé le curseur entre get et set, la modification se produit à la nouvelle position.
set renvoie BUSY, sans rien modifier, pendant que l'utilisateur compose du texte avec une méthode de saisie dans l'éditeur actif, en mode WYSIWYG comme en mode Source ; réessayez peu après. get n'est jamais refusé pour cette raison.
browser
La moitié mutante de la surface du navigateur intégré — tout ce qui change la page, l'onglet ou une connexion enregistrée. Lisez d'abord la page avec browser_read : chaque mode de ciblage ici renvoie à ce qu'une lecture a retourné.
Les outils du navigateur suivent Paramètres → Avancé → macOS → Navigateur intégré, qui est activé par défaut sur macOS — ces outils sont donc disponibles pour un client IA connecté sauf si vous le désactivez. Chaque action échoue avec BROWSER_DISABLED tant qu'il est désactivé. Les URL retournées à MCP sont expurgées via la même frontière que celle utilisée par l'état de session du navigateur de l'application.
Annoté readOnlyHint: false, destructiveHint: true — exact plutôt que simplement prudent, car chaque action ici modifie quelque chose.
Les erreurs sont typées. Un refus arrive sous la forme TOKEN: message (STALE_COMMAND, NOT_GRANTED, EVAL_TIMEOUT, TAB_LIMIT, …) avec le même jeton — et toute donnée structurée que l'application a jointe (un ticket de navigation, la reason d'une action, le verbe à réessayer) — dans structuredContent. Fiez-vous au jeton, pas au texte.
Un EVAL_TIMEOUT est indéterminé, pas un échec net : le script soumis a pu s'exécuter jusqu'au bout après que le pilote a cessé d'attendre ; il porte donc data.detail.indeterminate: true et ne doit pas être réessayé comme si rien ne s'était passé — lisez la page (browser_read) pour connaître son état avant d'agir à nouveau.
act
Arguments : tabId?, operation: "click" | "type" | "scroll" | "key", et des cibles propres à chaque opération :
- click / type — une cible, soit
ref(issu d'une lecture antérieure), soitrole+name, ettext?pour la saisie. Unrefest précis et indépendant de l'ordre, mais il n'est honoré que pour une opération déjà accordée ; si l'action peut nécessiter une autorisation, utilisezrole+nameafin que l'invite montre à l'utilisateur un élément lisible. - scroll —
ref(le faire défiler jusqu'à la vue) oudy(un delta vertical en pixels). - key —
key(par ex."Enter","Escape","Tab"), unrefcible optionnel, et desmodifiers: {ctrl, shift, alt, meta}optionnels.
scroll et key sont de classe Action (soumis à autorisation) et émettent des événements DOM synthétiques ; un site qui se fie à event.isTrusted peut donc les ignorer. Les opérations mutantes nécessitent une autorisation à la portée de l'origine ; les téléversements choisis par l'IA ne sont jamais permis.
Un clic vérifie son effet avant de signaler un succès, et refuse plutôt que de deviner. La cible est amenée dans la vue, doit être rendue visiblement (les styles calculés et les ancêtres repliés ou transparents sont vérifiés, de sorte qu'un bouton en double à l'intérieur d'une étape d'accordéon fermée est ignoré, pas cliqué), et le point de clic est testé par collision — une cible recouverte par une surcouche est refusée en nommant l'élément qui la masque (covered by div.cmp-overlay, données de la page) plutôt que cliquée au travers. Lorsque plusieurs éléments visibles partagent le rôle et le nom, l'action est refusée comme ambiguous et candidates liste leurs ref — elle n'en choisit jamais un selon l'ordre du document. Autres motifs de refus : hidden, offscreen (ne peut pas être amené dans la zone visible), disabled (y compris pointer-events: none et les sous-arbres inertes), upload (les champs de fichier ne sont jamais automatisés) et rejected-value (le champ a assaini le texte). Les shadow roots ouvertes sont parcourues ; la réponse inclut les décomptes matchedTotal / matchedVisible, l'url et la generation actuelles de l'onglet en cas de succès comme d'échec, et popup: {url} lorsque la page a tenté d'ouvrir une fenêtre pendant l'action (VMark bloque les fenêtres surgissantes ; l'URL est celle que la page voulait ouvrir). type gère les champs de texte, les contrôles <select> (passez le libellé ou la valeur de l'option ; une option absente est refusée comme no-such-option), et les régions contenteditable. key émule les actions par défaut qui manquent aux événements synthétiques — Entrée dans un formulaire le soumet, Tab déplace le focus — et rapporte defaultAction.
Ce qu'une autorisation engage. Une autorisation click engage l'élément (rôle + nom). Une autorisation type, key ou scroll engage aussi le texte, la touche (avec ses modificateurs) ou le delta exacts que vous avez demandés — l'invite les affiche — de sorte qu'un nouvel essai avec un contenu différent redemande l'autorisation.
workflow_run / workflow_cancel
workflow_run exécute un workflow que vous fournissez sous forme de texte source sur un onglet appartenant à l'IA. Arguments : tabId?, source (le texte du workflow — une petite grammaire orientée lignes ; c'est vous qui l'écrivez, l'IA qui le fait, ou workflow_record qui le capture à partir de vos propres actions), inputs? (une table {name: value} substituée dans les références {name} ; chaque entrée déclarée doit être fournie et celles qui ne sont pas déclarées sont refusées), allowRepeat? et resumeRunId? (voir ci-dessous). Il retourne {runId, steps, firstStep}immédiatement — l'exécution se déroule de manière asynchrone, car une exécution à plusieurs étapes peut survivre à une seule requête. Interrogez le workflow_status de browser_read pour suivre la progression ; tant que l'exécution vous attend, il rapporte pendingApproval.
Les étapes déterministes — click / type / navigate dans cette grammaire, ainsi qu'extract — s'exécutent à l'intérieur de VMark et sont soumises individuellement à autorisation, exactement comme un act émis à la main : l'exécution autorise chacune d'elles séparément, un workflow n'est donc pas un moyen de contourner les invites d'autorisation. goal, confirm, api et toute étape en prose libre mettent l'exécution en pause pour que l'IA les traite à la main. Reprendre après une pause : effectuez l'étape en pause (ou faites-vous aider par l'IA), puis lancez une nouvelle exécution avec resumeRunId défini sur l'exécution en pause — elle hérite des étapes terminées et considère l'étape en pause comme faite, de sorte que rien n'est soumis deux fois. Une réexécution de la même source avec les mêmes entrées ignore aussi les étapes d'écriture qui ont déjà réussi durant cette session (le registre des écritures terminées ; les étapes ignorées sont signalées comme skipped), sauf si allowRepeat est défini. Des entrées différentes constituent une autre tâche et s'exécutent en entier.
workflow_cancel {tabId?, runId} arrête une exécution. Elle n'est jamais soumise à autorisation — arrêter est toujours autorisé — et elle retire les invites en attente de l'exécution, interrompt une étape qui attend votre autorisation et vous rend l'onglet. Une exécution terminée rapporte already-terminal et reste en l'état ; un runId inconnu donne RUN_NOT_FOUND. L'exécution s'arrête également dès que vous reprenez le contrôle du navigateur (toute interaction avec la page ou son interface reprend la main) — y compris pendant qu'elle attend une invite.
Les exécutions sont bornées (≤ 25 étapes, source ≤ 64 Kio, et 120 s de temps d'exécution — le temps passé à vous attendre ne compte pas) et une à la fois par onglet.
workflow_record
Enregistre vos propres actions sur un onglet appartenant à l'IA sous forme de workflow rejouable. Arguments : tabId?, recordOp ("start" ou "stop") et site? (l'identifiant de site du frontmatter du workflow enregistré ; recording par défaut).
start est soumis au consentement via la permission record qui — comme execute_js et session — n'est jamais une autorisation permanente : chaque enregistrement vous le redemande, de sorte que l'IA ne peut jamais vous enregistrer en silence. Tant que vous ne l'autorisez pas, start retourne needsApproval ; une fois que vous l'avez fait, VMark arme un shim de capture dormant du monde de la page et commence à enregistrer les clics et saisies de champ que vous effectuez. stop retourne {source, inputs, eventCount} — le source est un texte de workflow que vous pouvez enregistrer ou passer directement à workflow_run.
L'enregistrement est sans valeurs par construction, et il ne s'agit pas d'un filtre qui fait confiance à la page : rien de ce que vous saisissez n'est jamais capturé. Chaque champ de texte devient une variable {input} nommée (la valeur est fournie au moment de la relecture, jamais enregistrée) ; un champ de mot de passe ou de code à usage unique devient une étape confirm: — une barrière humaine que vous franchissez à la main lors de la relecture — de sorte qu'un secret n'est même jamais paramétré ; et chaque URL est réduite à l'origine + le chemin, de sorte qu'un jeton présent dans une chaîne de requête ne peut pas survivre. Ce qui est enregistré, ce sont les localisateurs que vous avez touchés (rôle ARIA + nom accessible), jamais leurs données. L'enregistrement vous suit à travers les navigations de page et est borné (200 événements par page, 1 000 par session).
open
Arguments : url, un timeoutMs optionnel (1–9 000 ms) et un profile optionnel ([A-Za-z0-9._-], macOS 14+, posture sandbox) : un contexte persistant nommé pour qu'une connexion puisse être réutilisée par son nom — en ouvrir un nécessite une nouvelle autorisation à chaque utilisation, et l'IA ne voit jamais les identifiants. Crée un onglet appartenant à l'IA en utilisant la posture Sandbox ou Partagée actuelle, l'amène au premier plan et retourne ses tabId, navigationId, URL, titre et génération une fois le chargement terminé. Au plus 8 onglets appartenant à l'IA peuvent être ouverts (TAB_LIMIT) ; l'IA ferme ceux dont elle n'a plus besoin. En posture Partagée, un open qui nécessite votre autorisation de destination conserve son onglet et demande à l'IA de réessayer avec navigate sur ce tabId (data.retry) — un nouvel open créerait un onglet que l'autorisation ne peut pas couvrir.
navigate
Arguments : tabId?, url, et un timeoutMs optionnel. Navigue avec un onglet appartenant à l'IA (en l'amenant au premier plan) et retourne le résultat du ticket de navigation. Un TIMEOUT porte tout de même le ticket, afin qu'un wait ultérieur puisse récupérer le résultat final.
close
Arguments : tabId. Ferme un onglet appartenant à l'IA que l'IA a ouvert. Jamais soumis à autorisation — arrêter est toujours autorisé. Un onglet humain est refusé (TAB_NOT_AI_OWNED). Le résultat est {tabId, closed: true, destroyed: true} une fois la disparition de la vue native confirmée ; un démontage que le pilote n'a pas pu confirmer après ses nouvelles tentatives est signalé comme TAB_TEARDOWN_FAILED avec data.destroyed: false — l'enregistrement de l'onglet a déjà disparu, ne réessayez donc pas la fermeture ; indiquez à l'utilisateur qu'une vue native est peut-être encore active.
Détection de barrière. Un résultat open / navigate / wait chargé peut porter gate: {kind, hint} lorsque la page atteinte se lit comme un mur de connexion, une page de consentement intercalée, un défi de vérification humaine ou une limitation de débit — de sorte que l'IA apprend qu'elle ne regarde pas le contenu qu'elle a demandé, au moment même où elle lit le résultat. La détection privilégie la précision (un widget de défi rendu, ou au moins deux signaux indépendants sur une page laconique — un prix $429, un pied de page « Protected by Cloudflare » ou un article au sujet des CAPTCHA ne déclenchent jamais de classement) et purement consultative : elle change ce qui est dit à l'IA, jamais ce qui est autorisé, et chaque indice invite à vous impliquer plutôt qu'à contourner la barrière.
style
Arguments : tabId?, une cible (ref ou selector), et l'un de set: {prop: value}, addClasses, removeClasses ou injectCss. Écarter une surcouche bloquante, mettre une cible en évidence, etc. Classe Action (soumise à autorisation, op style). Monde de contenu isolé.
execute_js
Arguments : tabId?, script — un corps de fonction asynchrone qui return (ou attend) une valeur sérialisable en JSON. La trappe de secours pour ce que les verbes structurés ne peuvent pas exprimer. Il s'exécute dans le monde de contenu isolé — il partage le DOM (donc querySelector, element.style fonctionnent) mais ne peut pas voir le tas/les variables globales JS propres à la page. La valeur revient sous forme de result (undefined devient null) ; une exception, ou une valeur que JSON ne peut pas encoder, est un échec qui nomme l'erreur, jamais un résultat. Il est approuvé par appel uniquement (jamais une autorisation permanente, imposé dans le pilote Rust), l'approbation montre le script, et la valeur de retour est marquée non fiable et n'est jamais réinjectée automatiquement dans un act ultérieur. Préférez d'abord query/style.
session_save / session_load
Arguments : tabId?, handle ([A-Za-z0-9._-], 1–128 caractères). session_save capture la session de l'onglet dans une entrée du keychain du système nommée par handle et retourne un récapitulatif sans valeurs (des décomptes) ; session_load la restaure et retourne {loaded: true, handle} — une confirmation plus le handle fourni par l'IA, jamais aucune valeur. Un session_load ne s'applique qu'à une page ayant la même origine que celle où la session a été enregistrée. Il s'agit d'un identifiant par référence (ADR-A7) : l'IA nomme une session enregistrée et ne reçoit jamais les valeurs de cookies/jetons, qui ne sont jamais journalisées. Toutes deux relèvent de la permission session — jamais une autorisation permanente (approuvée par appel), et une autorisation pour un handle ne peut pas être dépensée sur un autre. Une session enregistrée couvre localStorage et les cookies, tous deux limités à l'origine à laquelle la page était engagée au moment de l'enregistrement.
console_clear
Arguments : tabId?. Retourne {entries: [{level, text}], url}, exactement comme le console de browser_read, et vide le tampon afin que la lecture suivante ne voie que la nouvelle sortie. Elle se trouve ici plutôt qu'avec l'autre lecture de console parce que la vidange évalue element.textContent = "[]" dans la page — une écriture DOM.
La posture Partagée demande une autorisation de destination pour chaque nouvelle origine, sauf s'il existe une autorisation navigate correspondante. Un onglet créé par un humain nécessite une autorisation d'attachement éphémère avant toute lecture/action de l'IA. Les onglets Sandbox utilisent un magasin de cookies IA distinct et non persistant.
browser_read
La moitié en lecture seule : observer l'onglet sans le modifier. Annoté readOnlyHint: true, de sorte qu'un client MCP peut l'approuver automatiquement — ce qui est tout l'intérêt de la séparation. Ces actions résidaient auparavant sur browser, où une seule annotation au niveau de l'outil devait aussi décrire execute_js ; prendre un instantané ARIA coûtait donc une autorisation humaine.
openWorldHint reste true : la lecture seule décrit ce que l'outil modifie, non si les octets sont dignes de confiance. Tout ce qui est retourné est contrôlé par la page et non fiable — ne réinjectez jamais un résultat directement comme cible d'action browser.
read
Retourne {url, snapshot, truncated?, unreachable?} pour l'onglet de navigateur focalisé, ou pour l'onglet désigné par tabId. snapshot est une liste orientée ARIA de nœuds {role, name, ref} — plus level pour les titres, checked, disabled, et upload: true pour un champ de fichier que l'IA ne peut jamais manipuler — chaque ref (par ex. "e5") étant un identifiant stable pour cet élément, valable le temps de la vue actuelle. Le parcours entre dans les shadow roots ouvertes ; unreachable compte les shadow roots fermées et les cadres dans lesquels il n'a pas pu entrer, et truncated: true signifie que la limite de nœuds (2 000) ou la limite de nom (200 caractères) a été atteinte.
screenshot
Arguments : tabId?. Retourne un bloc de contenu image (JPEG en base64, qualité bornée) du rendu actuel de l'onglet, plus une ligne de texte nommant la page — un canal visuel sur la mise en page et l'état rendu que l'instantané ARIA ne peut pas décrire. Il est capturé nativement (takeSnapshot) et ne lit aucun DOM ni JavaScript de la page. Classe Lecture : autorisé exactement comme read (permis sur un onglet appartenant à l'IA ; un onglet humain nécessite un attachement, consommé lors de la capture). Un onglet qui n'est pas la page visible peut s'afficher vide — open et navigate amènent un onglet au premier plan.
query
Arguments : tabId?, selector (CSS), et un fields: {attributes, box, styles:[...]} optionnel. Retourne {count, elements: [{ref, tag, text, …}], truncated?} — des données DOM structurées que l'instantané ARIA ne peut pas nommer (tableaux, valeurs calculées) — plafonnées à 50 éléments et 500 caractères de texte chacun (truncated: true lorsque le sélecteur en a trouvé davantage). Classe Lecture. S'exécute dans le monde de contenu isolé.
extract
Arguments : tabId?. Retourne {title, byline, url, markdown, textLength, truncated} — la page en Markdown mode lecture, pour les pages que l'IA souhaite lire plutôt qu'opérer. Une capture plafonnée exporte le HTML de la page ; l'extraction elle-même s'exécute dans VMark, jamais dans la page : un plugin de site enregistré pour l'origine a la priorité (le plugin Wikipedia intégré retire l'habillage wiki — infoboxes, navboxes, hatnotes, liens d'édition — par leur nom), et un lecteur générique fondé sur l'heuristique de densité sert de solution de repli pour tous les autres sites. truncated: true signifie que la page a dépassé le plafond de capture et que la fin n'a pas été lue. Classe Lecture. Tout ce qui est retourné provient de la page et n'est pas fiable.
workflow_status
Arguments : tabId?, runId (issu de workflow_run). Retourne {status, completedSteps, skippedSteps, stepCount, firstStep, pausedAt?, pendingApproval?, reasonCode?, reason?, resumedFrom?, stepResults} où status vaut l'un de running / paused / completed / failed / cancelled / superseded, stepResults contient une entrée par étape ({index, status, attempts, reason?, data?}), et pendingApproval est présent tant que l'exécution attend votre décision. Un statut paused nomme dans pausedAt l'étape qui a besoin de vous. Classe Lecture — interrogez-le librement.
console
Arguments : tabId?. Retourne {entries: [{level, text}], url} — la sortie console.* capturée de la page, plus les erreurs non interceptées et les rejets de promesses non gérés (enregistrés comme des entrées level: "error" préfixées Uncaught / Unhandled rejection: — le signal que l'interception de console.* seule ne voit jamais). Onglets appartenant à l'IA uniquement (en posture Sandbox comme Partagée ; un onglet humain ne porte pas de shim de capture), cadre principal uniquement. La capture fonctionne via un shim du monde de la page qui écrit dans un tampon DOM masqué que le pilote lit depuis le monde isolé — ainsi aucun canal de messagerie n'est ouvert en retour vers VMark (la garantie sans pont tient). La sortie est contrôlée par la page et non fiable — traitez-la comme un read, jamais comme une cible d'act.
Le tampon est un anneau borné, de sorte que des lectures consécutives se chevauchent. Pour le vider au fil de la lecture, utilisez le console_clear de browser — la vidange écrit [] dans l'élément tampon de la page, ce qui est une écriture DOM et ne peut donc pas relever de readOnlyHint: true.
wait
Arguments : tabId?, un navigationId optionnel (omettez-le pour le dernier ticket de l'onglet), et un timeoutMs optionnel (1–9 000 ms). Elle ne démarre jamais de navigation, ne change jamais le focus ni l'onglet actif, et ne crée jamais de vue — elle se contente d'observer, ce qui lui permet de résider dans l'outil en lecture seule. Onglets appartenant à l'IA uniquement. Elle retourne un résultat de chargement/échec mis en tampon, NAVIGATION_SUPERSEDED, ou TIMEOUT lorsque le ticket ne se termine pas dans la limite.
wait_for
Arguments : tabId?, exactement l'un de ref (issu d'une lecture), role (+ un name optionnel), text (une sous-chaîne du texte visible), ou urlContains (une sous-chaîne que l'URL de l'onglet doit contenir — confirme qu'une navigation déclenchée par un clic a abouti, répondu à partir de l'état de l'onglet sans aller-retour vers la page), et un timeoutMs optionnel (1–9 000 ms). Interroge de façon répétée jusqu'à ce que la condition soit remplie ou que le délai s'écoule, et retourne {matched: true|false} (plus le ref de l'élément correspondant pour une condition ref/role) — vous pouvez ainsi distinguer « trouvé » de « délai dépassé ». Classe Lecture. Utilisez-la pour rendre un flux déterministe : agir, wait_for le résultat, puis lire.
Deux règles découlent de ce qu'elle a le droit de voir. urlContains porte sur l'URL expurgée — la chaîne de requête et le fragment sont retirés, car un jeton qu'une redirection y aurait placé ne doit pas pouvoir être sondé — de sorte qu'une recherche contenant ? ou # est refusée d'emblée. Et sur un onglet humain attaché avec Autoriser une fois, elle est refusée (ATTACHMENT_ONCE_INSUFFICIENT) : l'interrogation répétée représente de nombreuses lectures, et un attachement pour une seule lecture ne peut pas la couvrir — demandez Autoriser jusqu'à la navigation.
coherence
Une vue en lecture seule de la couche de cohérence de l'espace de travail — quels documents dérivés sont obsolètes par rapport aux amonts dont ils ont été générés. Aucune action ne modifie les documents ni l'état de l'éditeur. status est en lecture seule ; edges procède d'abord à un rapprochement et peut ajouter des enregistrements de provenance au registre de l'espace de travail, mais ne change jamais le contenu d'un document. Toutes sont entièrement traitées par le backend Rust à partir du noyau propre à chaque espace de travail, elles fonctionnent donc même quand aucune fenêtre d'éditeur n'est au premier plan.
Deux actions supplémentaires en lecture seule exposent la couche sémantique :
claims— les affirmations canoniques actuelles :{claim, entryId, statement, maturity, invalidAt, visible}. Seules les affirmationsestablishedcontraignent les vérifications sémantiques ;visiblereflète le contexte default.contexts— l'ensemble des contextes (ledefaultimplicite est toujours présent) :{id, name, parent, enforcement, visibleClaims, errors}.
Annoté readOnlyHint: true. L'unique action mutante, resolve, réside dans son propre outil — voir coherence_resolve — ce qui permet à celui-ci d'être approuvable automatiquement. La mutation des affirmations et des contextes n'est jamais exposée : le canon reste sous contrôle humain.
Toutes les actions exigent workspace_root : le chemin absolu de l'espace de travail à interroger. Obtenez-le via session.get_state (le filePath des onglets ouverts) ou l'outil workspace. Un chemin manquant, non absolu ou qui n'est pas un répertoire est refusé avec une erreur en chaîne simple.
status
Compteurs d'état du noyau pour un espace de travail.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
workspace_root | string | Oui | Chemin absolu de l'espace de travail à interroger |
Retourne :
{
"initialized": true,
"objects": 12,
"open_items": 2,
"quarantined": 0,
"writer": "0198c0de-0000-7000-8000-000000000001"
}| Champ | Signification |
|---|---|
initialized | false quand l'espace de travail n'a pas encore de registre de cohérence (pas de répertoire .vmark/). Tous les compteurs sauf objects valent alors 0. |
objects | Objets suivis (fichiers dotés d'une identité de cohérence). |
open_items | Arêtes vivantes non à jour — la taille actuelle du détail. |
quarantined | Lignes de registre malformées mises en quarantaine lors de la dernière lecture. |
writer | L'identifiant writer (UUID) de cette installation. |
edges
Le détail : chaque arête de dépendance vivante dont l'amont a bougé. Exécute d'abord une analyse-rapprochement, la réponse reflète donc les fichiers sur disque au moment de l'appel.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
workspace_root | string | Oui | Chemin absolu de l'espace de travail à interroger |
Retourne un tableau — vide quand tout est cohérent :
[
{
"txf": "0198c0de-0000-7000-8000-00000000000a",
"input": 0,
"upstream": "0198c0de-0000-7000-8000-00000000000b",
"upstream_path": "characters/elena.md",
"pinned": "rev-a1b2c3",
"downstream": "0198c0de-0000-7000-8000-00000000000c",
"downstream_path": "scenes/chapter-3.md",
"downstream_rev": "rev-d4e5f6",
"state": "version-stale"
}
]| Champ | Signification |
|---|---|
txf / input | L'entrée de transformation et l'emplacement d'entrée qui identifient cette arête (à passer aux actions de résolution dans l'application). |
upstream / upstream_path | L'objet dont dépend l'aval, et son dernier chemin connu. |
pinned | La révision amont dont l'aval a été généré. |
downstream / downstream_path / downstream_rev | L'objet dérivé, son chemin et sa révision actuelle. |
state | "version-stale", "stale-valid", "stale-contradicted", "stale-unknown", "waived", "diverged", "diverged-multi-head" ou "unpinnable". |
Résoudre une arête (accept-newer / waive) est normalement une action humaine effectuée dans la vue Détail de VMark. Une IA ne peut le faire que via coherence_resolve, et uniquement lorsque le propriétaire de l'espace de travail le lui a explicitement délégué.
coherence_resolve
L'unique action mutante de la couche de cohérence, dans son propre outil afin que coherence puisse rester approuvable automatiquement — et afin qu'une opération non annulable soit bien visible dans la liste des outils plutôt qu'enfouie comme une valeur d'énumération parmi cinq. Annoté readOnlyHint: false, destructiveHint: true.
resolve
Arguments : {workspace_root, txf, input, resolution: "accept-newer" | "waive", reason? (required for waive)}. txf et input proviennent d'une ligne coherence → edges.
Résoudre une arête obsolète active en tant qu'agent explicitement délégué. L'autorisation est fail-closed : le propriétaire de l'espace de travail doit avoir accordé à votre identité de pont authentifiée une délégation active et non expirée couvrant le type de résolution (accordée dans l'application, depuis la vue Détail), et l'arête doit toujours être active. Chaque résolution déléguée est journalisée au titre de la délégation, et l'entrée ne peut pas être annulée.
Un refus signifie que l'autorisation est manquante ou expirée — demandez à l'utilisateur de l'accorder plutôt que de réessayer. Extraire cela de coherence n'a changé aucune propriété de sécurité : l'autorisation s'est toujours appuyée sur le principal de pont authentifié, jamais sur quoi que ce soit que le client affirme.
Erreurs
Deux formes d'erreurs apparaissent :
Erreurs de domaine — définissent success: false et retournent une enveloppe encodée en JSON dans error :
{ "error": "STALE", "message": "...", "current_revision": "rev-..." }Erreurs de forme d'argument — pour les arguments requis manquants/invalides (par ex. document.write sans champ content), error est une simple chaîne décrivant le problème. L'enveloppe structurée est réservée aux conditions au niveau du domaine.
| Code | Apparaît comme | Signification |
|---|---|---|
STALE | enveloppe | expected_revision ne correspondait pas ; relire et réessayer |
INVALID_PATCH | enveloppe | workflow.apply_patch a reçu un tableau patches malformé |
INVALID_TAB | enveloppe | tabId n'a pas pu être résolu |
INVALID_PATH | enveloppe | Un filePath n'a pas pu être lu, ou se trouve hors de la portée de l'espace de travail ouvert / des documents |
APPROVAL_REQUIRED | enveloppe | save_as vers un nouvel emplacement alors que Approuver automatiquement les enregistrements vers un nouvel emplacement et les résultats des génies est désactivé ; ou open_workspace attend l'approbation de l'utilisateur, ou qu'il choisisse le dossier dans le sélecteur de dossiers de VMark |
BUSY | enveloppe | open_workspace n'a pas pu continuer : une autre boîte de dialogue de dossier est ouverte, ou un changement d'espace de travail est en cours dans cette fenêtre ; l'approbation est conservée — réessayez. Ou bien document.write, document.transform ou selection.set est arrivé pendant que l'utilisateur composait du texte avec une méthode de saisie ; rien n'a été modifié — réessayez peu après |
NOT_WORKFLOW | enveloppe | workflow.* a été appelé sur un onglet non-YAML-workflow |
READ_ONLY | enveloppe | Une mutation a été tentée sur un document en lecture seule |
NO_EDITOR | enveloppe | selection.* a été appelé mais l'onglet focalisé n'a pas d'éditeur actif |
INTERNAL | enveloppe | Erreur de gestionnaire inattendue |
| (chaîne simple) | chaîne | Argument requis manquant ou type incorrect |