Skip to main content
Glama
Cyberistic

jj-mcp-server

by Cyberistic
README.md
# jj-mcp-server

A [Model Context Protocol](https://modelcontextprotocol.io) server for
[Jujutsu (`jj`)](https://github.com/jj-vcs/jj) version control, built on
[Effect v4](https://effect.website). Optional first-class support for
[`jjCoW`](https://github.com/Cyberistic/jjCoW) (clone-on-write workspace
manager, ~40× faster than `jj workspace add`).

- **Typed end-to-end**: parameter schemas are `effect/Schema` (decoded at the
  boundary, not coerced at runtime); results are typed; errors are
  `Schema.TaggedError` so the caller can pattern-match on `_tag`.
- **Effect-native**: `Effect.gen`, scoped resources, tagged errors,
  `Layer.launch`. No `any`, no thrown exceptions.
- **Curated**: 22 `jj` tools + 7 `jjcow` tools — the day-to-day operations an
  agent actually reaches for. Not 40 hand-rolled `args.push` blocks.
- **Stable tool surface**: `jjcow` tools are always registered and fail at
  call time with a typed `JjCowNotInstalled` error when `jjw` is missing,
  so clients never see a different schema across hosts.

## Install

```sh
# npm
npm install -g @cyberistic/jj-mcp-server

# pnpm
pnpm add -g @cyberistic/jj-mcp-server

# bunx (no install)
bunx @cyberistic/jj-mcp-server
```

Then point your MCP client at the `jj-mcp-server` bin. The exact
configuration depends on the client; for [opencode](https://opencode.ai):

```json
{
  "mcp": {
    "jj": {
      "type": "local",
      "command": ["jj-mcp-server"]
    }
  }
}
```

## Uninstall

```sh
npm uninstall -g @cyberistic/jj-mcp-server
# or
pnpm remove -g @cyberistic/jj-mcp-server
```

Then remove the `jj` entry from your MCP client's config.

## Tools

### Meta — call first

| Tool                          | Notes                                                                                       |
| ----------------------------- | ------------------------------------------------------------------------------------------- |
| `jj_workflow_how_to_use_info` | Returns the jj mental model + canonical workflow. Read this before any non-trivial jj op. |

### Read-only `jj` tools

| Tool              | Wraps                                                                |
| ----------------- | -------------------------------------------------------------------- |
| `jj_status`       | `jj status`                                                          |
| `jj_log`          | `jj log [-r revisions] [-n limit] [-T template] [-G] [-p]`             |
| `jj_show`         | `jj show [revision] [-T template] [-p]`                               |
| `jj_diff`         | `jj diff [-r revset] [-f from] [-t to] [--context N] [--summary\|--stat]` |
| `jj_files`        | `jj file list [-r revision] [paths] [-T template]`                   |
| `jj_bookmark_list`| `jj bookmark list [--all-remotes] [--remote] [--tracked] [-T template]` |
| `jj_git_fetch`    | `jj git fetch [--all-remotes] [--remote R] [-b branch]`              |
| `jj_workspace_root` | `jj workspace root`                                                |
| `jj_op_log`       | `jj op log [-n limit] [-T template] [-G] [-p]`                        |

### Mutating `jj` tools

| Tool                  | Wraps                                                  |
| --------------------- | ------------------------------------------------------ |
| `jj_describe`         | `jj describe -m message [revision]`                    |
| `jj_new`              | `jj new [revisions...] [-m message] [--no-edit]`       |
| `jj_abandon`          | `jj abandon [revisions...] [--retain-bookmarks]`       |
| `jj_rebase`           | `jj rebase [-s source] [-d destination]`               |
| `jj_squash`           | `jj squash [--from] [--into] [-k]`                     |
| `jj_duplicate`        | `jj duplicate [revs...] [-d destination] [-A after] [-B before]` |
| `jj_bookmark_create`  | `jj bookmark create <name> [-r revision]`              |
| `jj_bookmark_set`     | `jj bookmark set <name> -r revision [--allow-backwards]` |
| `jj_bookmark_delete`  | `jj bookmark delete <names...>`                        |
| `jj_git_push`         | `jj git push [--remote R] [-b bookmark] [--all] [--tracked]` |
| `jj_resolve`          | `jj resolve [-r revision] [--list]`                    |
| `jj_undo`             | `jj undo`                                              |

### Optional `jjcow` tools (always registered; fail with `JjCowNotInstalled` if `jjw` is missing)

| Tool           | Wraps                                                       |
| -------------- | ----------------------------------------------------------- |
| `jjcow_create` | `jjw create <name> [-r revision] [-b bookmark] [--no-cow] [--lazy]` |
| `jjcow_delete` | `jjw delete [name] [-f] [-k]`                               |
| `jjcow_list`   | `jjw list [-v]`                                            |
| `jjcow_cleanup`| `jjw cleanup [-n] [-f] [-k]`                               |
| `jjcow_root`   | `jjw root`                                                  |
| `jjcow_version`| `jjw version`                                               |
| `jjcow_init`   | `jjw init <shell>` for `zsh`/`bash`/`fish` shell integration |

Install [`jjCoW`](https://github.com/Cyberistic/jjCoW) to enable these:

```sh
git clone https://github.com/Cyberistic/jjCoW
cd jjCoW
just build && just install
```

Then `jjcow_*` tools start working without any change to the MCP server.
If `jjw` is not on `$PATH`, the tools return a typed `JjCowNotInstalled`
error explaining how to install it.

### What's deliberately **not** exposed

The keanemind-style reference exposes 40+ tools. We keep the surface small.
If an agent needs `bookmark-track`, `git-remote-add`, `file-annotate`,
`evolog`, `interdiff`, `revert`, `restore`, `edit`, or any of the
`operation-*` forensics commands, they can shell out to `jj` directly. This
keeps the tool catalog fast to scan and the LLM prompt smaller.

## Error taxonomy

Every failure is a `Schema.TaggedError` with a stable `_tag`:

| `_tag`               | Meaning                                                                |
| -------------------- | ---------------------------------------------------------------------- |
| `JjError`            | Catch-all for runtime failures (e.g. spawn error when `jj` is missing). |
| `CommandFailed`      | `jj` or `jjw` exited non-zero. Carries `exitCode` + `stderr`.          |
| `NotARepository`     | The target `cwd` / `repoPath` isn't inside a jj repo.                  |
| `JjCowNotInstalled`  | `jjw` isn't on `$PATH`. Install [jjCoW](https://github.com/Cyberistic/jjCoW). |

## Library use

The bin is the canonical entry; this package also exports the underlying
`Layer` and `Toolkit`s so an embedder can splice them into a richer layer
graph:

```ts
import { MainLayer, JjToolkit, JjCowToolkit } from "@cyberistic/jj-mcp-server";
```

## Development

This package uses [nub](https://nubjs.com) and
[bun](https://bun.sh) for tooling. Two CLIs cover the entire lifecycle —
nothing else is required.

```sh
nub install                # install deps
nub run typecheck          # tsc --noEmit
nub run lint               # oxlint .
nub run fmt                # oxfmt .
nub scripts/smoke.ts       # verify the toolkits expose the expected tools
nub src/cli.ts < scripts/smoke.json
                           # end-to-end JSON-RPC smoke over stdio
```

`lefthook` runs `oxfmt` + `oxlint` on pre-commit and `tsc --noEmit` on
pre-push (`LEFTHOOK=0` skips).

## License

MIT