VibeVM
Contents
On this page
en
Publisher
org.vibevm.core
Version
1.0.0latest
Audiences
user, author, agent
Reading time
11 min
Rendered
Read aloud
never

PROP-015 — MCP server and agent integration

01Milestone: M1.7 (ROADMAP.md). The server slice shipped first; the agent-integration surface (vibe mcp install / status / upgrade / uninstall) followed.

02Status: IMPLEMENTED — this PROP is the retroactive spec home the CONVERT-PLAN v0.1 §7 endgame opened for the vibe-mcp crate and the vibe mcp command family. Units typed at REQ grain; the code carries the matching scope! / #[spec(implements)] edges.

03Related: PROP-004 §5 / §6 (the comparative research that motivated treating agent context as a managed, distributable artefact), PROP-002 §2.1 (content_hash identity the query_package tool surfaces), PROP-003 §2.5 (the subskill delivery modes the read_subskill / materialise_subskill tools read), VIBEVM-SPEC.md §5 (the product's AI-integration scope), and PROP-023 (the bridge-packages design that added the #skill-include req on 2026-06-24 — additive; §2.6 #skill is unchanged).

1. Motivation

04vibevm installs spec-and-discipline packages into a project; the consuming agent then has to find and read what landed. Two integration surfaces close that loop:

  1. 05A Model Context Protocol server (vibe mcp serve) that exposes the project's lockfile-derived state to any MCP-speaking agent as callable tools — so the agent queries package identity and pulls subskill content on demand instead of guessing from the file tree.
  2. An agent-integration command family (vibe mcp install and friends) that wires that server into each agent's own configuration and writes a per-agent skill manifest, so an operator runs one command instead of hand-editing five different config files.
  • 06Both are product scope (VIBEVM-SPEC.md §5).
  • Neither changes wire formats, the lockfile schema, or install behaviour — they are read-mostly surfaces over state the rest of vibevm already owns.

2. Decisions

2.1 The server: JSON-RPC 2.0 over stdio

07req r1

08Decision. vibe-mcp is a transport-agnostic MCP server.

  • 09It speaks JSON-RPC 2.0 over line-delimited stdin/stdout (the MCP stdio form), handling the initialize handshake, tools/list, tools/call, and ping.
  • The protocol version is a one-line const (PROTOCOL_VERSION); the server name/version surface in the handshake.
  • Transport is a trait (Transport) — production uses StdioTransport, tests inject MemoryTransport for deterministic round-trips.
  • Each tools/call reloads the project lockfile fresh, so a concurrent vibe install surfaces on the next call without a server restart.
  • A missing vibe.lock is an empty lockfile, not an error — the agent sees empty state through the normal tool response.

2.2 The tool surface: one seam, three tools

10req r1

11Decision. Every tool implements one seam (McpTool): it describes itself (name, human description, JSON-Schema input shape) and runs against parsed arguments plus the read-only ServerContext.

  • 12Tools register at one point; the dispatcher routes by registered name and does not know a tool's identity beyond it.
  • The seam is the cell boundary — a new tool is a new cell, not an edit to the dispatcher.

13The shipped tools (all group-qualified <group>/<name> pkgrefs in; structured JSON + a text rendering out):

  • 14query_package — the full lockfile entry for an installed package (kind, version, content_hash, registry, source, files_written, features, active subskills, describes PURL, language). Read-only.
  • read_subskill — the concatenated content of an active subskill's files. eager / lazy-push subskills read from the project tree; lazy-pull subskills read from the package cache (PROP-003 §2.5.0), so the agent gets bytes regardless of delivery mode. Read-only.
  • materialise_subskill — copy a lazy-pull subskill's content into the project tree. No-op for eager / lazy-push (already on disk); refuses to overwrite without force. The one writing tool.

15A tool failure renders as isError: true in the result payload (a tool-level failure), distinct from a transport-level JSON-RPC error.

2.2.1 Searching the map — the set-returning twin of explain

16query finds nodes; explain looks at one. Until 2026-08-06 the map could only be asked about a target already known by name, so «which of these exist?» was unanswerable — not in the host, not in any language stack, not in the engine. query is that question, and the two are deliberately separate verbs rather than one verb with a mode: a point lookup and a set filter render differently, cap differently, and fail differently.

17The filter level is a permanent level, not a first version to be replaced (owner ruling, 2026-08-06). An agent accustomed to grep reaches for filters; a query language demands a form it will not build without need. So the filters must work on their own and must never become a degenerate case of a grammar — in the library they are their own entry point, so a broken parser could not take them down with it.

18Three filters, combined with AND, under a hard ceiling: exact spec address, substring of a code symbol, element kind. None is required; those given narrow. The ceiling is not a convenience and cannot be removed — the answer is read by an agent with a bounded context, and an unbounded one is useless rather than generous. When it truncates it says so, with the total, in both renderings.

19Results are nodes, never edges. «Find me an edge» is not a question anyone asks; «find me what has, or lacks, an edge of this kind» is — so edges are a filter dimension for the query level above, and the result set stays spec units and code items.

20Every hit records where it came from, because a second producer is already designed: the code-quality engine's findings join these results at query time, by the owner's ruling that two engines must not merge their data. Nothing of that engine is built here; the result shape simply does not close the door — a discriminated field rather than a closed variant set, so a third source adds a value instead of breaking every reader.

21One library, two thin surfaces, per the omnichannel floor: the filtering lives in the host's trace crate, vibe query renders it for a person and the query tool renders it for an agent, and both call the same function. The MCP tool's description says when to reach for it instead of explain — that description is the surface's own, which is exactly what the agent-facing class owns and all it owns.

22kind filters over a measured vocabulary. The committed map carries nine code kinds, and a spec unit answers on its own kind rather than borrowing the code one; the two vocabularies are disjoint, so one filter serves both without ambiguity. Caveat worth carrying: every spec unit in this tree is legacy-unmarked, so a spec kind matches nothing here today. The path is open and exercised only by fixtures — stated because a filter that silently returns nothing is indistinguishable from one that is broken.

23The map is built fresh in memory per call, never read from the committed artefact — the same posture explain takes, for the same reason: a query answers for the tree as it is.

24The query language is designed and not built. Its shape is the filters plus graph traversal — depth, and «has no edge of kind X», which is what answers «which rules does nothing verify» — and it introduces a grammar that will need versioning. It stands on this level rather than replacing it. SUPERSEDED 2026-08-06 — it is built; the contract is §2.2.2 below.

2.2.2 The query language — traversal on top of the filters

25select is a third verb, for the same reason query and explain are two. explain looks at one target, query filters a set, and select walks the graph from a set. Three questions, three caps, three failure modes — a mode flag on one verb would make the result type depend on the argument, and every consumer would carry three readers.

26It stands on the filter level and cannot take it down with it. The three filter predicates are the ones §2.2.1 ships, reached rather than redefined; the parser lives in its own module behind its own entry point, so a broken grammar leaves query answering. That separation is the owner's ruling in ##MAP-QUERY-THE-SIMPLE-LEVEL-IS-PERMANENT made structural instead of promised.

27Seven predicates, whitespace separated, joined by AND, and no operators at all: uri:, symbol:, kind: (the filter level's own), scope: (prefix of a spec address), has: / lacks: (an edge verb), depth: (0..3). No disjunction, no parentheses, no precedence — each of those is a permanent versioning liability, and a predicate can be added without renumbering a language that has no operator layer to renumber.

28scope: is in the set because the level's own canonical question is unanswerable without it. Measured before the build: 5 742 of 5 825 spec units carry no verifies edge, and nothing that shipped could narrow that — kind is carried by 0 units in this tree and uri is exact, so composing the negative predicate with the filter level yields 5 742 or 1. A document prefix brings 67 of the corpus's 72 documents inside the ceiling; the five that do not are named in the design record rather than left to be rediscovered.

29has: and lacks: select seeds, and «touches» reads from each family's own side — incoming for a spec unit, outgoing for a code item. On a directed bipartite graph that is the only reading under which one predicate serves both families, and applying them after the walk instead would answer a question nobody asked.

30depth:N expands the seed set along edges, undirected, and the seeds stay in the answer. depth:0 is the default and the identity, so a query without it is exactly the seed selection — which is what keeps this level a strict superset of the one below rather than a different thing wearing its name. Every hit carries the hop count it was reached at, so a caller can tell what it asked for from what the walk brought with it.

31The depth bound is 3, and the number came from the graph. Exhaustively over all 1 205 edge-bearing nodes, 71.7 % reach more at depth 2 than at depth 1 and only 5.9 % reach more at depth 3; the largest connected component is 44 nodes. So depth is a precision control on this map rather than a safety one — and the result ceiling stays hard regardless, because it protects against a future map, not this one.

32An unknown predicate, an unknown verb, a repeated predicate, an out-of-range depth or an empty query is an ERROR that names the offending token and lists what was expected — never a silently ignored clause. Same law as the markup's typed fences, for the same reason: a grammar that ignores what it does not understand promises everything and checks nothing, and whoever trusted the promise is the one who finds out. An empty query is refused rather than read as «everything», because that answer already exists one verb away.

33The grammar carries a version and reports it in every answer rather than demanding it in every query. A query string stays free of ceremony; the structured answer states the version it was parsed under, so a consumer that cares can branch and one that does not is unaffected. Requiring a prefix would tax every caller forever to buy nothing until the first breaking change.

34The rejected shapes, the measurements above with their commands, and the correction of a sampled reading that was wrong about depth are in vibevm/vibespecs/design/map-query-language.xml — the lore this contract is the short form of.

2.3 Tool and server errors cite their REQ

35req r1

  • 36Decision. ToolError and ServerError are the crate's two error layers.
  • Each variant's Display text carries the violated spec:// unit and a fix surface (the Class-F product-error grammar), so a failing tool call is navigable back to this PROP without source access.

2.4 Agent detection

37req r1

38Decision. The integration surface supports a fixed set of MCP-capable coding agents (Claude Code, Claude Code Desktop, Cursor, OpenCode, Codex).

  • 39An agent is present in a project when its project-level markers exist (e.g. .claude / CLAUDE.md, .cursor / .cursorrules) or its user-level host config directory exists.
  • Detection drives the default target set for vibe mcp install; the operator can always override with an explicit agent filter.

2.5 Per-agent configuration

40req r2

41Decision. Each agent declares its config shape, and the writer is agent-aware but format-generic:

  • 42Format — JSON or TOML per agent (Codex is TOML-only).
  • Section key — the agent's MCP-servers table name (mcpServers, mcp, mcp_servers).
  • Scope — project (.<agent>/… in the repo) and/or user (the host config dir). Some agents are user-only (Claude Code Desktop, Codex).
  • Config path — resolved per (agent, scope), cross-platform. The path must be the file the agent actually reads for MCP discovery, not merely a settings file it happens to own. For Claude Code that is <project>/.mcp.json (project) and the top-level mcpServers of ~/.claude.json (user) — never settings.json, which only gates .mcp.json servers (enabledMcpjsonServers) and does not define them.
  • Merge discipline — installing upserts vibevm's one entry under the section key and preserves every foreign key, and their order: the JSON writer round-trips order-preserving (serde_json/preserve_order), so a merge into a large ~/.claude.json appends rather than re-alphabetising the operator's whole file. Uninstalling strips only vibevm's entry and leaves the rest. The operator's other MCP servers and unrelated config survive every operation.
  • 43The vibevm entry is scope-independent: vibe mcp serve with no --path, resolving its project root from the launcher's CWD (an MCP client sets CWD to the project directory for a project-scope server), so one shape serves every scope and a committed .mcp.json stays portable.
  • On Windows the launcher is wrapped as cmd /c vibe … because vibe is a vibe.cmd shim that an MCP client's bare process-spawn cannot exec directly.

2.6 Skill materialisation

44req r1

45Decision. For agents that support a skill manifest (Claude Code, OpenCode, Codex — not the JSON-config-only Cursor / Claude Code Desktop), vibe mcp install also writes a SKILL.md describing how to use vibevm through the MCP tools.

  • 46The skill body is data, not code — a vendored template (include_str!) rendered into each agent's skill directory (.<agent>/skills/vibevm/SKILL.md).
  • Writing is idempotent: identical content is left untouched (unchanged); a divergent file is updated.

2.7 The integration lifecycle

47req r1

48Decision. The agent-integration command family is a coherent lifecycle over the (agent × scope) matrix, every verb idempotent and every mutating verb offering --dry-run and a confirmation:

  • 49install — detect (or accept a filter), preview, confirm, write MCP entries and skills.
  • status — read-only: what would install / upgrade / uninstall do.
  • upgrade — refresh stale MCP blocks and SKILL.md files in place.
  • uninstall — strip vibevm's MCP entries and skill files, preserving foreign config.

50Per-(agent, scope) outcomes are reported as structured records (AgentInstallReport / SkillInstallReport) the CLI renders or emits as JSON.

2.8 Selective skill projection

51req r1

52Decision. SkillDecl gains an optional include — a list of glob patterns relative to the skill's path.

  • 53When present, only matching files are projected into the agent's skill directory, preserving their relative structure; when absent or empty, the whole path tree is projected — the existing §2.6 behaviour, unchanged.
  • Selection composes with the already-working nested path: a skill can point at a subdirectory and pick specific files out of it.
  • This is available to any skill but is load-bearing for bridge packages (PROP-023): a bridged upstream tree is full of unrelated files, and the maintainer projects just the SKILL.md and whatever it references without vendoring the noise.
  • Glob matching is deterministic; a pattern that matches nothing is a declared-but-empty selection (surfaced, not a silent no-op).
54[[skill]]
name = "vim"
path = "upstream/skills/vim"
include = ["SKILL.md", "references/**/*.md"]   # omit → whole tree (§2.6)

3. Out of scope

  • 55No new wire formats or lockfile changes. The server reads the existing lockfile schema; the tools surface existing fields.
  • No agent-specific behaviour beyond config shape. The integration knows each agent's file format and paths, not its runtime semantics.
  • HTTP / SSE transports. Stdio is the shipped transport; the Transport trait leaves room without committing to more today.
  • LLM-provider tools. PROP-003 §F virtual-capability emission waits on a real vibe-llm (VIBEVM-SPEC.md §10.4).

4. Acceptance

  • 56The server answers initialize / tools/list / tools/call over a MemoryTransport round-trip; each tool has a behavioural oracle.
  • query_package / read_subskill / materialise_subskill each behave per §2.2 against a lockfile fixture (found / not-found / invalid-pkgref; the delivery-mode split; the force / no-overwrite contract).
  • vibe mcp install writes the expected entry under each agent's section key, preserves foreign keys, and is idempotent; uninstall is its inverse; status writes nothing.

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.core/vibevm@1.0.0/modules/vibe-mcp/PROP-015-mcp-integration

.md.xmlllms.txt