# MCP Server Shipping

> Use when building, installing or registering a personal MCP server — adding its `[project.scripts]` entry point, installing it with `uv tool install` from a local checkout or straight from git instead of publishing to PyPI, registering it with `claude mcp add` and choosing local/project/user scope, switching between an editable dev install and a released one without re-registering, and the per-repo dev loop (`inv dev-env.setup`, `inv quality.precommit`, why automation needs `uv run inv`) for the repos that produce these servers.

- Skill: `theodoread/mcp-server-shipping` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add theodoread/mcp-server-shipping`
- Raw SKILL.md: https://api.skillmd.com/api/skills/theodoread/mcp-server-shipping/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: theodoread (https://skillmd.com/u/theodoread)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/theodoread/mcp-server-shipping

---


# Shipping a personal MCP server

Workflow for repos whose entire purpose is producing an MCP server for personal, cross-project use,
rather than a library other code imports. Covers going from a working repo to "loadable in any
project," and how that differs between the dev machine and everywhere else. The worked examples name
the author's own servers — a family of `*-polite-mcp` repos — because a concrete name reads better
than a placeholder; substitute your own everywhere.

Shipping an **Agent Skill** is a different job with a different mechanism — see the
`skill-authoring` skill, which also covers getting an edit to an existing skill deployed.

## What this skill reads, runs and writes

This skill ships no scripts; what it touches, it touches through the commands it tells you to run.

- **Runs**: `uv tool install`, `claude mcp add`, `claude mcp list`.
- **Writes**: `uv tool install` puts a tool environment under uv's tool directory and a shim on
  `PATH` (`~/.local/bin/` by default); `claude mcp add --scope user` writes the server registration
  into Claude Code's user-level config, `--scope project` into the repo's `.mcp.json`. Both are
  reversible with the same tools' `uninstall`/`remove`. Nothing else, and nothing in the repo the
  server lives in beyond the `[project.scripts]` entry you add by hand.
- **Network**: `uv tool install git+…` clones the repo you name.

## Per-repo dev loop

**Two things here are general and one is the author's own stack.** The general ones: a repo of this
kind wants a **one-command setup** that syncs the environment from a committed lockfile rather than
resolving afresh, and a **one-command gate** that auto-fixes then runs the same checks CI runs.
Whatever your repo already uses for those is the right answer — the point is that both exist and are
one command, not which runner provides them.

The rest of this section describes the author's arrangement, and is here as a worked example rather
than as an instruction. **On this author's machine** every repo in this family takes
[`repo-tasks`](https://github.com/TheodoreAD/repo-tasks) as a dev dependency (git-as-artifact-store,
no PyPI) instead of hand-rolled `tasks.py` logic, so the repo's own `tasks.py` is just
`from repo_tasks import ns`; `inv dev-env.setup` is the setup command and `inv quality.precommit`
the gate, and the `AGENTS.md`/`CLAUDE.md`/skills scaffold is generated by
[`scaffoldapy`](https://github.com/TheodoreAD/scaffoldapy) at repo-creation time. Substitute your
own equivalents throughout.

**The rule worth taking from it, whatever your runner is:** a task runner is a **per-project
dependency**, never assumed to be on `PATH`. Anything invoking it from outside an already-activated
shell — a CI step, a project-generation hook, any automation — has to go through the project's own
environment (`uv run <runner> <task>` for a `uv`-managed project), because nothing guarantees a bare
name resolves to this project's copy rather than to some other one, or to nothing at all. That is
the mistake this section exists to prevent, and it does not depend on which runner you use.

## One entry point, `uv tool install` for a stable PATH binary

Add a `[project.scripts]` entry point to the MCP repo's `pyproject.toml` (e.g.
`olx-polite-mcp = "olx_polite_mcp.server:main"`) before writing `server.py`, not as a retrofit. Then
install it as a real tool via `uv tool` (validated end-to-end in `olx-polite-mcp/README.md`) rather
than pointing `claude mcp add` at a `uv run`/`uvx` invocation — `uv tool install` builds an isolated
env and drops a shim on `PATH` (`~/.local/bin/` by default), so registration itself becomes a bare
binary name with no path/flags to keep in sync:

Install against the **local working tree** while actively developing (editable — picks up local
edits without reinstalling, so the repo has to stay put at that path):

```shell
uv tool install -e <path-to-your-checkout>
```

Install **from GitHub** once not actively iterating (pin `@<tag>`/`@<sha>` for reproducibility, omit
for the default branch):

```shell
uv tool install git+https://github.com/TheodoreAD/olx-polite-mcp
```

Either way, registration is the same one-liner, independent of which source was installed:

```shell
claude mcp add --scope user olx-polite-mcp olx-polite-mcp
```

Switching sources is `uv tool install` again with the other source (uv replaces the existing tool) —
no `claude mcp remove`/re-`add` needed, since the registered command name never changes.
`--scope user` (not `local`/`project`) matches how skills already install globally, so the server is
available in every project on this machine, not just one. Use `--scope project` instead only for a
_consumer_ repo that wants the server offered automatically to anyone who clones it (see
`olx-polite-mcp/README.md`'s project-scope example) — a different case than personal cross-project
use.

Editable and from-GitHub installs can't be combined (editable needs a real working directory uv can
point at; a git-sourced install doesn't expose one) — for "edit locally, sourced as if from GitHub,"
`git clone` it yourself, then `uv tool install -e` that clone, which is just the editable-from-disk
case again.

Project- and user-scope servers need a one-time approval on `claude` startup before a _new_ session
launches them (`claude mcp list` shows pending ones) — an already-running session that had it
approved keeps it live.

## Distribution: skip PyPI

At this scale (personal, non-commercial), `uv tool install` resolving straight from a git remote
(see above) makes the GitHub repo itself the artifact store — no version-bump/publish/credentials
ceremony. Pin `@<tag>`/`@<sha>` once a repo has a stable point worth freezing; no ref (tracks the
default branch) is fine while iterating.

## Full rationale

[`references/rationale.md`](references/rationale.md) — why git+`uv` beat PyPI (and the
`uvx`/`uv run` draft that came first), why `--scope user`, and why one registered command covers
both install sources.

