# Ship tools and MCP servers {#root}

@status:doc/work @audience:author

[p01] A package can deliver programs: command-line tools built on install, or a server your agent talks to. This page declares both, builds them in the package's own folder, and explains why a server pins the exact version of the tools it serves.

[p02]
```prompt
In the current VibeVM project, create the tool package org.acme/notes-tools as an in-tree package with a small Rust crate crates/notes-check inside it, declare the crate as a binary named notes-check, install the package into the project, build the tool through vibe, and run it through vibe bin exec with --help to prove the dispatch works.
```

- needs: the vibevm skill installed for your agent; a package with a Cargo workspace at its root and a binary crate; the Rust toolchain on the `PATH`

outcome: the manifest carries a `[[binary]]` table; `vibe bin list` shows the tool; `vibe bin build` produced it in the package's own target folder; `vibe bin exec notes-check -- --help` prints the tool's help

- assert: `vibe bin list`
- assert: `vibe bin exec notes-check -- --help`

## What happens {#what-happens}

[p03] The agent scaffolds the package slot with `vibe init package`, puts the crate inside it, adds a `[[binary]]` table naming the tool and the crate directory, and installs the package into the project from the project's own [registry](../glossary/index.xml#registry). It runs `vibe bin build`, which asks for consent and runs a release build inside the package's own workspace. Then it runs `vibe bin exec`, which resolves the tool through the project's [lock file](../glossary/index.xml#lock-file) to the artifact of the exact version installed and runs it. Consumers get the same: installing the package materialises its source, building on first use produces the tool beside it, and the artifact never enters the package's [fingerprint](../glossary/index.xml#fingerprint).

> [p04] MUST: a package declares its binaries; vibe builds and
>   dispatches them.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-025#BINARY-MUST>

> [p05] Build output sits outside the shippable tree (PROP-024 §2.2), so content
>   hashes never move.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-025#HASHES-STABLE>

## Code in a package {#code-in-a-package}

[p06] A package is a project made installable, so it may carry arbitrary code at its root beside `vibevm/vibespecs/`: a Cargo workspace, crates, tests. The shippable tree is the source minus build output; `target/`, `node_modules/` and anything in `.vibeignore` never travel. Consumers receive the source and build it themselves, which keeps identity a property of what the author committed.

> [p07] **The package root holds arbitrary code** (e.g. `Cargo.toml` + `crates/`) and
>   `vibe.toml`, exactly as a project root does. Code is optional — a prompt-only
>   package (e.g. `discipline-core`) simply has no code at its root.
>
> <spec://org.vibevm.core/vibevm/common/PROP-024#ROOT-CODE>

> [p08] **Why.** Identity is the *source*, never build artifacts: build output is
>   non-deterministic (timestamps, host paths, incremental state) and may be
>   gigabytes — hashing or copying it would make identity unstable and
>   materialisation ruinous, the exact failure PROP-022 §1.1 names for "big in file
>   count".
>
> <spec://org.vibevm.core/vibevm/common/PROP-024#WHY-SOURCE-IDENTITY>

[p09] A code-bearing package keeps its own workspace [manifest](../glossary/index.xml#manifest), and a consumer that is itself a Rust project excludes the dependency tree from its own workspace, so the two builds never collide.

> [p10] **Decision.** A code-bearing package carries its **own** workspace manifest
>   (for Rust, a root `Cargo.toml` with `[workspace]`) — it is a standalone,
>   independently-buildable project.
>
> <spec://org.vibevm.core/vibevm/common/PROP-024#OWN-WORKSPACE>

## Binaries {#binaries}

[p11] Each tool is one `[[binary]]` entry: a `name`, unique in the package, and a `crate`, a directory inside the shippable tree with a `Cargo.toml`. `vibe bin list` shows what the installed packages declare, `vibe bin build` builds the named tools or all of them, `vibe bin path` prints an artifact's location, and `vibe bin exec <name> -- <args>` runs it through the lock file. Building executes the package's build scripts, so it asks for consent the first time, like an install does.

> [p12] A code-bearing package declares each shipped tool in its `vibe.toml`:
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-025#BINARY-TABLE>

> [p13] **Successor to the historical first-build prompt.** Building executes package build scripts and proc-macros, so installation plus explicit target/route selection is the authorisation and the engine narrates the exact provider and target before execution. Lifecycle build does not add an allow-list, first-run prompt or `--allow-hooks` analogue; provider identity, artifact records and outcomes supply the audit trail. Existing direct `vibe bin` compatibility remains routed through its declared package rather than silently choosing ambient code.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-025#BUILD-CONSENT>

[p14] The `name` is unique within the package and should be safe from collisions across packages, which the [family](../glossary/index.xml#family) prefix gives you for free. The `crate` names a folder inside the shippable tree with a Cargo package whose binary is called exactly `name`.

> [p15] Constraints: `name` MUST be unique within the package and SHOULD be
>   globally collision-safe (the family-prefix convention, PROP-028 §2.4).
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-025#NAME-CONSTRAINTS>

> [p16] `crate` MUST name a directory inside the shippable tree carrying a
>   `[[bin]]`-bearing (or default-bin) Cargo package whose bin name equals
>   `name`.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-025#CRATE-CONSTRAINT>

[p17]
```sh
vibe bin list
```

```output
```

```stderr
bin list: no installed package declares a [[binary]].
```

## MCP servers {#servers}

[p18] A package of kind `mcp` delivers a server an agent talks to: one or more `[[mcp_server]]` tables, each naming the binary that serves it and its arguments. The server is built like any binary and registered into the agents' configurations by `vibe mcp install`, beside vibe's own server. Once built, it runs without vibe: the agent launches the artifact directly.

> [p19] An **`mcp` package** is one whose primary deliverable is one or more
> Model Context Protocol servers.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-mcp/PROP-027#MCP-KIND-DEF>

> [p20] An mcp package's servers run without vibe: the artifact is launched by
>   the agent host directly from the slot path, links its vendored engines,
>   and speaks stdio MCP.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-mcp/PROP-027#VIBE-FREE-SERVING>

[p21] A server that serves another package's toolchain, the gates of a language discipline for example, must require that package with an exact pin, `=X.Y.Z`. The engines behind the agent's tools and the gates the consumer runs must resolve to one version set: one engine, one truth, enforced by the resolver rather than by the protocol.

> [p22] The pin closes it: **every
>   `[requires.packages]` entry of an `mcp`-kind package MUST be an exact
>   `=X.Y.Z` requirement** — the resolver holds the served engines and the
>   consumer's gates to ONE version set; no runtime handshake exists or is
>   needed.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-mcp/PROP-027#EXACT-PIN-LAW>

[p23] The kind promises a server: an `mcp` manifest without an `[[mcp_server]]` table is refused. The server is one of the package's own binaries, so `binary` must name a `[[binary]]` of the same manifest, and its delivery, consent and staleness follow the binary machinery. In `args`, the only substitution is `{project_root}`, resolved at registration; an unknown token is refused.

> [p24] An
>   `mcp`-kind manifest that declares NO `[[mcp_server]]` is refused: the
>   kind promises a server.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-mcp/PROP-027#KIND-PROMISES-SERVER>

> [p25] The server IS a [PROP-025](../vibe-workspace/PROP-025-binary-delivery.xml)
>   binary: delivery, consent, staleness, and slot residence come from that
>   machinery wholesale — `binary` must resolve to a `[[binary]]` declared
>   in the same manifest.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-mcp/PROP-027#SERVER-IS-BINARY>

> [p26] `args` may carry
>   substitution tokens ONLY from the closed set `{project_root}` (the
>   absolute, verbatim-free root of the consuming project, resolved at
>   registration time); unknown `{…}` tokens are refused at validation.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-mcp/PROP-027#ARGS-CLOSED-SET>

## Install hooks {#hooks}

[p27] A package may run a script when it is installed. `[hooks]` names a base path without extension, and the package ships `<base>.sh`, `<base>.ps1` or both; the runner picks the one for the host. `pre-install` runs as soon as the package's folder is complete and before vibe uses it; `post-install` runs after the install is durable, with the lock written and the boot files regenerated. The working directory is the package's own folder in the dependency tree, and the environment names the package's group, name, version, kind and folder, and which of the two moments it is. A [hook](../glossary/index.xml#hook)'s edits to files vibe owns are ephemeral: a reinstall or an update restores those bytes and runs the hook again.

> [p28] The value is a **base path without extension**; the runner resolves `.sh` /
>   `.ps1` beside it per §2.2.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-020#BASE-PATH-VALUE>

> [p29] A package ships a phase script as `<base>.sh` (portable, POSIX shell) and/or
> `<base>.ps1` (PowerShell). The runner picks per host:
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-020#SCRIPT-FORMS>

> [p30] **`pre-install`** — runs immediately after the package's slot is fully
>   populated (content materialised, submodules fetched per
>   [PROP-021](../vibe-registry/PROP-021-submodule-sources.xml)) and **before**
>   vibevm uses the slot (before boot regeneration, before any later
>   `vibe skill` projection reads it). This is the "bring the tree into order"
>   hook.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-020#PHASE-PRE-INSTALL>

> [p31] **`post-install`** — runs after the install run is durable for that package
>   (lockfile written, boot artefacts regenerated). For finalisation that needs
>   the package already registered.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-020#PHASE-POST-INSTALL>

> [p32] The hook's **working directory is the package's materialised slot**; it sees
> exactly the tree vibevm will use.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-020#CWD-IS-SLOT>

> [p33] The runner passes a documented environment: `VIBE_PACKAGE_GROUP`,
>   `VIBE_PACKAGE_NAME`, `VIBE_PACKAGE_VERSION`, `VIBE_PACKAGE_KIND`,
>   `VIBE_PACKAGE_DIR` (the slot, also CWD), `VIBE_HOOK_PHASE`.
>   ([PROP-024 §2.3](../../common/PROP-024-code-bearing-packages.xml#build) adds
>   `VIBE_PROJECT_ROOT`, the workspace absolute root, so a build hook can target a
>   gitignored build dir *outside* the slot; it lands with that work.)
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-020#HOOK-ENV>

> [p34] Only a hook's edits
>   to materialiser-owned recorded payload are ephemeral: reinstall, update or
>   integrity repair restores those bytes per
>   [PROP-022](PROP-022-materialization-modes.xml), then reruns hooks exactly
>   when that payload diff is nonempty. Hook-created unrecorded state is outside
>   `.vibe-slot.toml` ownership and survives by design; a rerun may compound it,
>   so hooks must be idempotent until a separate hook-output ownership contract
>   exists.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-020#EFFECTS-EPHEMERAL>

## Edge cases and rules {#edge-cases}

[p35] A tool's artifact belongs to the exact version installed; after an update the old artifact is not trusted, and the next `vibe bin exec` builds the new version first.

> [p36] Current-slot existence alone is not trust. The selected artifact must belong to the current resolved provider root and pass the shared record's exact source/config/platform/provider/output-path/digest revalidation. A same-slot refresh preserves unrecorded build output; a version change moves current-root identity and cannot reuse the old slot's record.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-025#TRUST-CURRENT-SLOT>

[p37] Building needs the language's own package sources, from crates.io for Rust, unless they are vendored; an offline build says so rather than pretending.

> [p38] Cargo needs crates.io for third-party deps unless the
>   local cargo cache is warm: offline boxes get the same honest failure
>   cargo gives, plus the hint that `cargo install --path <slot>/crates/…`
>   (the documented degraded path, which stays valid indefinitely) has the
>   same network shape — there is no offline shortcut to a first build.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-025#OFFLINE-HONESTY>

[p39] The `[[mcp_server]]` table is legal only in packages of kind `mcp`; a `tool` package ships binaries but no servers.

> [p40] The
>   `[[mcp_server]]` table (§2.2) is **legal only in this kind** — the kind
>   IS the taxonomy, enforced by `Manifest::validate`, not advisory.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-mcp/PROP-027#TABLE-ONLY-IN-KIND>

[p41] A `.vibeignore` at the package root adds globs to the list of build output that never enters the shippable tree.

> [p42] **Optional `.vibeignore`** at the package root — newline-delimited globs added
>   to the §2.2 build-output denylist.
>
> <spec://org.vibevm.core/vibevm/common/PROP-024#SURF-VIBEIGNORE>

