MCP Tools Reference
VMark exposes four composite MCP tools to AI assistants: session, workspace, document, and workflow. Together they cover 14 actions — the read/write spine plus the file/window lifecycle plus CST-safe edits for GitHub Actions YAML.
The previous 12-tool / 76-action surface was pruned because in-document formatting tools (bold, headings, tables, etc.) duplicate work that AI agents already do trivially via Markdown round-trip. See the MCP pruning plan for the full rationale.
Recommended Workflow
- Call
session.get_stateonce to see open windows, tabs, and per-tab{filePath, dirty, revision, kind}. - For Markdown:
document.read→ reason →document.write(passingexpected_revisionfor safe concurrency). - For GitHub Actions YAML (
kind: "yaml-workflow"):workflow.apply_patchfor CST-safe edits that preserve comments and anchors;workflow.validatefor actionlint diagnostics. - File operations (open, save, close, switch tabs) live on
workspace.
Mermaid Diagrams
When using AI to generate Mermaid via MCP, consider installing the mermaid-validator MCP server — it catches syntax errors using the same Mermaid v11 parsers before diagrams reach your document.
session
One-shot orientation. Discover every window, every tab, and the server's capabilities in a single call.
get_state
No arguments.
Returns {windows, capabilities}:
{
"windows": [
{
"label": "main",
"focused": true,
"tabs": [
{
"id": "tab-1",
"filePath": "/path/to/notes.md",
"title": "notes",
"dirty": false,
"revision": "rev-x7Q3aB1F",
"kind": "markdown"
},
{
"id": "tab-2",
"filePath": "/repo/.github/workflows/ci.yml",
"title": "ci",
"dirty": true,
"revision": "rev-x7Q3aB1F",
"kind": "yaml-workflow"
}
]
}
],
"capabilities": {
"version": "<vmark-mcp-server version>",
"supportedKinds": ["markdown", "yaml-workflow"],
"mcpProtocol": "0.1.0"
}
}The kind discriminator tells you whether to use document.write (for markdown) or workflow.apply_patch (for yaml-workflow) on that tab.
workspace
File and window lifecycle. Nothing in-document.
new
Create a new untitled tab.
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | string | No | "markdown" (default) or "yaml-workflow" |
windowLabel | string | No | Target window; defaults to focused |
Returns {tabId}.
open
Open a file from disk.
| Parameter | Type | Required |
|---|---|---|
filePath | string | Yes |
windowLabel | string | No |
Returns {tabId}.
save
Save a tab to its existing path.
| Parameter | Type | Required |
|---|---|---|
tabId | string | No (defaults to focused) |
Returns {filePath, revision}.
save_as
Save a tab to a new path.
| Parameter | Type | Required |
|---|---|---|
tabId | string | No |
filePath | string | Yes |
Returns {revision}.
close
Close a tab. Refuses to discard unsaved work without force.
| Parameter | Type | Required |
|---|---|---|
tabId | string | Yes |
force | boolean | No |
Returns {closed: true} on success, {closed: false, reason: "DIRTY"} if the tab is dirty and force was not supplied.
switch_tab
Activate a tab.
| Parameter | Type | Required |
|---|---|---|
tabId | string | Yes |
focus_window
Focus a window.
| Parameter | Type | Required |
|---|---|---|
windowLabel | string | Yes |
document
Read, write, transform. The spine of the surface.
read
| Parameter | Type | Required |
|---|---|---|
tabId | string | No (defaults to focused) |
Returns {content, revision, filePath, kind, dirty}. Always read before writing — the revision token must accompany the next write.
write
Replace full document content.
| Parameter | Type | Required | Description |
|---|---|---|---|
tabId | string | No | Target tab (defaults to focused) |
content | string | Yes | New full content |
expected_revision | string | No | Revision token from the most recent read |
If expected_revision is supplied and the document has changed since that read, the response is a STALE structured-error envelope with the current revision; re-read and retry.
// success
{ "revision": "rev-newAfterWrite" }
// stale
{ "error": "STALE", "message": "Document has changed since the last read", "current_revision": "rev-currentNow" }transform
Apply a deterministic rewrite. Currently supports CJK-specific transforms (full-width ↔ ASCII punctuation conversion, CJK ↔ Latin spacing).
| Parameter | Type | Required | Description |
|---|---|---|---|
tabId | string | No | Target tab |
kind | string | Yes | "cjk-format", "cjk-spacing", or "cjk-punctuation" |
expected_revision | string | No | Concurrency token |
cjk-format applies the user's CJK formatting settings end-to-end. cjk-spacing inserts single spaces between CJK characters and adjacent Latin/digits. cjk-punctuation converts ASCII punctuation that sits beside CJK characters to its full-width form.
Returns {revision}.
workflow
actionlint validation and CST-safe surgical edits for GitHub Actions workflow YAML. Available only for tabs whose kind is "yaml-workflow".
document.read / document.write work on every tab — including workflow YAML
The workflow tool is not a substitute for the read/write spine. For a workflow tab, you can:
document.readto get the raw YAML text (with all comments)document.writeto replace it wholesale (whatever string you send is stored verbatim — comments preserved if you include them)workflow.apply_patchwhen you want the server itself to guarantee that comments, anchors, and key order survive a partial edit
Use apply_patch when changing one field and leaving everything else untouched (the server can't drop comments it doesn't change). Use document.write when you're rewriting wholesale or generating a new workflow from scratch.
apply_patch
Apply an array of IRPatch objects. Patches are dispatched through VMark's CST-aware mutators, which preserve comments, anchors, and key order. Raw document.write to a YAML file would lose them.
| Parameter | Type | Required |
|---|---|---|
tabId | string | No |
patches | IRPatch[] | Yes |
expected_revision | string | No |
IRPatch is a discriminated union (kind field). Supported kinds:
kind | Effect |
|---|---|
workflow.set | Set top-level fields ({path, value}) — name, env.X, etc. |
job.set | Set a field on a job ({jobId, path, value}) |
step.set | Set a field on a step ({jobId, stepIndex, path, value}) |
with.set | Set a key in a step's with: block ({jobId, stepIndex, key, value}) |
with.remove | Remove a key from a step's with: block |
needs.add / needs.remove | Add or remove a job ID from needs: |
trigger.setFilters | Replace a trigger filter array — branches, paths, types, etc. ({event, filter, value: string[]}) |
Returns {revision} on success or a structured STALE / INVALID_PATCH / NOT_WORKFLOW error envelope.
validate
Run actionlint over the workflow YAML.
| Parameter | Type | Required |
|---|---|---|
tabId | string | No |
Returns {ok, diagnostics, binaryAvailable}. Each diagnostic carries {line, col, message, severity}. binaryAvailable: false means actionlint is not installed locally; install via Homebrew or upstream releases.
Errors
Two error shapes appear:
Domain errors — set success: false and return a JSON-encoded envelope in error:
{ "error": "STALE", "message": "...", "current_revision": "rev-..." }Argument-shape errors — for missing/invalid required arguments (e.g., document.write without a content field), error is a plain string describing the problem. The structured envelope is reserved for domain-level conditions.
| Code | Surfaced as | Meaning |
|---|---|---|
STALE | envelope | expected_revision did not match; re-read and retry |
INVALID_PATCH | envelope | workflow.apply_patch received a malformed patches array |
INVALID_TAB | envelope | tabId could not be resolved |
INVALID_PATH | envelope | workspace.open received a filePath that could not be read |
NOT_WORKFLOW | envelope | workflow.* was called on a non-YAML-workflow tab |
READ_ONLY | envelope | A mutation was attempted on a read-only document |
INTERNAL | envelope | Unexpected handler error |
| (plain string) | string | Required argument missing or wrong type |