AI Integration
Drive vis from Claude, Cursor, Zed, ChatGPT, and other AI clients via the bundled MCP server and Claude Skill
AI Integration
vis ships an embedded Model Context Protocol server (vis-mcp) and a companion Claude Skill. Together they let an AI assistant inspect your workspace, plan target runs and scaffolds, and diagnose cache rotations — all through the same task graph the CLI uses.
- MCP server is the wire format. It works with any MCP-aware client: Claude Code, Claude Desktop, Cursor, Zed, ChatGPT (Pro+), Continue, and more.
- Claude Skill is workflow guidance layered on top. It teaches Claude when to call which tool, and reinforces the "agent prepares, human executes" pattern: every tool is read-only, so the assistant builds the right
vis run/vis generatecommand and a person actually runs it.
The MCP SDK and zod are declared as optional peer dependencies. They are only required when you actually launch vis-mcp — running plain vis does not pull them in.
Installation
Install the optional peers alongside vis:
pnpm add @modelcontextprotocol/sdk zod
# or
npm install @modelcontextprotocol/sdk zodVerify the bin is wired:
pnpm exec vis-mcp
# [vis-mcp] ready (workspace: /path/to/repo)The server speaks JSON-RPC over stdio and never writes to stdout outside the protocol — all logs go to stderr.
Client setup
Claude Code
claude mcp add vis -- pnpm exec vis-mcpOnce added, the eight vis tools are available in any Claude Code session opened in this repo.
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"vis": {
"command": "pnpm",
"args": ["exec", "vis-mcp"],
"cwd": "/absolute/path/to/your/repo"
}
}
}Restart Claude Desktop. The vis server will appear in the tools panel.
Cursor
Add to .cursor/mcp.json at the repo root (project-scoped) or ~/.cursor/mcp.json (global):
{
"mcpServers": {
"vis": {
"command": "pnpm",
"args": ["exec", "vis-mcp"]
}
}
}Zed
Add to your settings:
{
"context_servers": {
"vis": {
"command": "pnpm",
"args": ["exec", "vis-mcp"]
}
}
}ChatGPT (Developer Mode)
Add the server in Settings → Connectors → Add MCP server:
- Name:
vis - Command:
pnpm exec vis-mcp - Working directory: your repo path
Workspace override
By default vis-mcp operates against process.cwd(). Override it from the client config:
{
"mcpServers": {
"vis": {
"command": "pnpm",
"args": ["exec", "vis-mcp"],
"env": { "VIS_MCP_WORKSPACE_ROOT": "/absolute/path/to/repo" }
}
}
}Claude Skill
The package also exports a Claude Skill at node_modules/@visulima/vis/skills/vis/SKILL.md. The skill encodes opinionated workflow guidance — when to use each tool, the safety story for run_task, and how to investigate cache misses.
To activate it project-wide, symlink the skill into your repo:
mkdir -p .claude/skills
ln -s ../../node_modules/@visulima/vis/skills/vis .claude/skills/visOr copy it directly if you prefer to keep skills version-controlled:
cp -R node_modules/@visulima/vis/skills/vis .claude/skills/visSkills and the MCP server are independent — you can use either, or both. The MCP server alone gives Claude tool access; the skill alone gives Claude workflow guidance for the tools it can already see.
Tool reference
The MCP server exposes eight read-only tools.
list_projects
List all workspace projects with their language, type, layer, tags, and target metadata.
| Argument | Type | Description |
|---|---|---|
query | string? | vis query string (e.g. tag=frontend, type=application) |
describe_project
Return full metadata for a single project.
| Argument | Type | Description |
|---|---|---|
name | string | Project name (e.g. @visulima/cerebro) |
list_targets
List per-target rows across the workspace. Optionally narrow to one project.
| Argument | Type | Description |
|---|---|---|
project | string? | Limit to one project |
list_templates
List scaffolding templates discovered in .vis/templates/, .moon/templates/, vis.config.ts generator.templates, and the builtins shipped with @visulima/vis (e.g. buildkite-ci).
Takes no arguments. Returns each template's name, source (native | moon | config | builtin | remote), on-disk path, and one-line description. The agent uses source to tell the user when a template is the bundled preset vs. their own copy — and that vendoring .vis/templates/<builtin-name>/ is the supported way to override a builtin.
describe_template
Return the full schema for a single template — useful before suggesting a vis generate invocation.
| Argument | Type | Description |
|---|---|---|
name | string | Template name (e.g. package, component) |
Returns: description, destination (default target dir), and variables[] with name, type, required, default, prompt, and (for enums) values. The agent uses this to build a fully-qualified vis generate <name> -- --foo=bar command for the user to run.
get_run_logs
Read the run summary from .vis/. Defaults to last-summary.json.
| Argument | Type | Description |
|---|---|---|
runId | string? | Specific run ID (defaults to latest) |
taskId | string? | Filter to one task |
cache_why
Diff a task's cache hash against the previous run to pinpoint what changed.
| Argument | Type | Description |
|---|---|---|
taskId | string | Task ID like @my/app:build |
runId | string? | Specific run (defaults to latest) |
cache_hash
Print the recorded hash and per-input hash details (command, nodes, runtime, implicitDeps).
| Argument | Type | Description |
|---|---|---|
taskId | string | Task ID like @my/app:build |
runId | string? | Specific run (defaults to latest) |
Safety: agent prepares, human executes
vis-mcp deliberately ships no side-effecting tools. There is no run_task and no template-applying tool — the agent's job is to investigate (list_* / describe_* / get_run_logs / cache_*) and then propose the exact vis run or vis generate command for a human to execute. This mirrors the Nx MCP design and removes a whole class of failure modes (runaway builds, accidental writes, long timeouts) that come with letting an LLM drive the runner directly.
If you want the assistant to actually run something for you, paste the suggested command into your terminal — or wire it through a separate tool you trust (e.g. Claude Code's bash tool with explicit per-call approval).
Self-healing CI
vis ai heal (command reference) closes the loop on a failed CI run: it reads the latest failed task from .vis/runs/, asks the configured AI provider for a structured patch, applies it, validates the fix by re-running the failing target with --no-cache --summarize --fail-fast, and — when validation passes — posts the proposal back to the PR/MR.
CI surface detection lives in src/ai/ci-context.ts:
- GitHub Actions (
GITHUB_ACTIONS=true): resolves the PR number fromGITHUB_REF(refs/pull/<n>/merge) or theGITHUB_EVENT_PATHpayload, captures the head SHA from the event, and readsGITHUB_TOKENfor REST fallback. - GitLab CI (
GITLAB_CI=true): readsCI_MERGE_REQUEST_IID,CI_API_V4_URL,CI_PROJECT_ID(withCI_PROJECT_PATHfallback), andGITLAB_TOKEN/CI_TOKEN(the auto-injectedCI_JOB_TOKENcannot post MR notes —vis ai healskips it deliberately). - Buildkite (
BUILDKITE=true): capturesBUILDKITE_BUILD_ID(annotation dedup key),BUILDKITE_BUILD_NUMBER,BUILDKITE_ORGANIZATION_SLUG,BUILDKITE_PIPELINE_SLUG, the literalBUILDKITE_PULL_REQUESTvalue ("false"for non-PR builds), andBUILDKITE_API_BASE_URLfor self-hosted Enterprise. There is no PR-comment surface to listen to — heal acceptance flows through a block step instead. - Anything else: provider is reported as
unknownand the comment step is skipped — the patch is still applied and validated locally.
Comment posting in src/ai/pr-comment.ts dispatches on the resolved provider:
- GitHub: tries
gh pr comment --body-file -first (uses the runner's bundledghand auto-injected token, falls back to--repowhen the local clone lacksorigin). On failure, falls back toPOST /repos/:owner/:repo/issues/:n/commentswithGITHUB_TOKEN. - GitLab: posts directly to
POST /projects/:id/merge_requests/:iid/notesvia the REST API.glabis not preinstalled on shared runners and project tokens are the standard auth shape, sovisskips the CLI path entirely. - Buildkite: posts a build annotation via
buildkite-agent annotatewith--context vis-ai-heal-${BUILDKITE_BUILD_ID}so reruns update the same annotation in place. Falls back to the Buildkite REST API (POST /v2/.../builds/{n}/annotations, requiresBUILDKITE_API_TOKEN, honoursBUILDKITE_API_BASE_URL) when the agent CLI isn't available. - Push-event runs: report
posted: false, method: "skipped"— there's no PR/MR to comment on.
Pass --dry-run to inspect the proposal without applying or commenting; pass --validation-timeout <seconds> to cap how long the re-run is allowed to take (default 30 min). The PR/MR comment (or Buildkite annotation) is bounded at ~60 KB; oversized patch sets drop the inline diff body and leave a pointer to vis ai fix … --apply.
Acceptance: vis ai heal accept
vis ai heal accept (command reference) closes the loop: it re-validates the latest heal proposal, then commits the patch to the PR/MR branch via the upstream provider. The acceptance signal is provider-specific and lives in src/commands/ai/heal-accept.ts:
- GitHub Actions / GitLab CI: an allow-listed maintainer comments
/vis heal accepton the PR/MR. - Buildkite: an allow-listed maintainer unblocks a block step.
BUILDKITE_UNBLOCKER_EMAIL(preferred) orBUILDKITE_UNBLOCKERis the actor checked against the allow-list — there is no comment phrase. Because Buildkite has no commit API of its own, vis derives the upstream provider fromBUILDKITE_REPO(*github*→ GitHub,*gitlab*→ GitLab) and commits through whicheverGITHUB_TOKEN/GITLAB_TOKENis set. Both HTTPS and SSHBUILDKITE_REPOshapes are recognised, and non-default ports on self-hosted GitLab (e.g.:8443) are preserved in the synthesised API base URL.
When the trigger actor is missing from the allow-list, the error message is provider-aware so the operator can fix the right env var: GitHub/GitLab entries are platform usernames (no leading @), Buildkite entries are unblocker emails or Buildkite usernames. Acceptance is also refused when the change is on a forked repository — the CI token can't push to forks.
Troubleshooting
Cannot find module '@modelcontextprotocol/sdk' — install the optional peer: pnpm add @modelcontextprotocol/sdk zod.
Claude doesn't see the tools — check the client logs. Most clients write MCP startup errors to a debug log; in Claude Desktop it's ~/Library/Logs/Claude/mcp.log.
vis-mcp reports the wrong workspace — set VIS_MCP_WORKSPACE_ROOT in the MCP server's env block. The default process.cwd() depends on where the parent client launches the process.
JSON-RPC framing errors — something in your vis config wrote to stdout. The server is a stdio transport, so any console.log in a config plugin or task hook will corrupt the protocol stream. Move logs to console.error.