Workflow for repos whose entire purpose is producing an MCP server or a skill for personal,
cross-project use (not a library other code imports) — e.g. olx-polite-mcp, emag-polite-mcp,
altex-polite-mcp, temu-polite-mcp, product-research-pipeline. Covers going from a working repo
to "loadable by Claude Code in any project," and how that differs between the dev machine and
everywhere else.
Per-repo dev loop
Every repo in this family takes repo-tasks as a dev
dependency (git-as-artifact-store, no PyPI —
uv add --dev git+https://github.com/TheodoreAD/repo-tasks) instead of hand-rolled tasks.py
logic; the repo's own tasks.py is then just from repo_tasks import ns. See that repo's own
README for the full task catalog (one invoke module per facility — quality, venv, deps,
direnv, agents, docs, ...) — the two that matter for day-to-day work here:
inv dev-env.setuponce after cloning — syncs.venvfromuv.lock(fails loudly on a missing or stale lockfile rather than silently rewriting it),direnv allows it, and wires Claude Code's Bash tool to auto-activate the same venv (a no-op if the repo has no.envrc).inv quality.precommitbefore considering a change done — fixes everything auto-fixable (ruff/dprint/shfmt), then runs the full CI-style gate (lint/format/type-check/shell-check/ test).pytestruns against checked-in fixtures (saved HTML snapshots, etc.) — no live-network calls in tests.AGENTS.md+CLAUDE.mdsymlink +.agents/skills/.claude/skillsscaffold comes fromscaffoldapyautomatically at generation time — nothing to run for it.
invoke is a per-project venv dependency (pulled in transitively via repo-tasks), never assumed
to be a machine-wide tool — unlike power-user-linux-setup's own inv, which bootstrap.sh
installs as a global uv tool for real end users. Anything invoking inv from outside an
already-activated shell (a CI step, a copier.yml _tasks hook, any automation) needs
uv run inv <task>, not bare inv — nothing guarantees the latter resolves. scaffoldapy's
copier.yml (_tasks: [uv sync, uv run inv configure]) and every generated repo's own
.github/workflows/ci.yml (uv run inv quality.check) both already follow this; keep new
automation consistent with it rather than assuming a global inv.
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):
uv tool install -e ~/projects/github.com-personal/olx-polite-mcp
Install from GitHub once not actively iterating (pin @<tag>/@<sha> for reproducibility, omit
for the default branch):
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:
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.
Skills ship through the skills CLI — don't build a new mechanism
A skill repo (like product-research-pipeline's orchestrator skill) needs none of the above. Put
the skill at skills/<name>/SKILL.md in a public repo — no manifest, no marketplace entry, no
vendor directory — and it is installable by anyone:
npx skills add TheodoreAD/<repo> --global # every skill in the repo
npx skills add TheodoreAD/<repo> --skill <name> # just one
The skills CLI discovers skills up to three levels deep
in the standard locations, copies the chosen ones into the canonical ~/.agents/skills/ hub, and
links each detected agent at it. A skill that is a repo's own interface documentation belongs
committed in that repo under .agents/skills/; a cross-project convention skill belongs in a
dedicated skills repo. Both are installed by the same command.
[PITFALL: The CLI announces a Claude Code symlink it does not always create. Measured
2026-08-27: installing alongside any universal agent prints symlink → Claude Code in the summary
and leaves no .claude/skills behind. Claude Code reads .claude/skills, not .agents/skills, so
verify that link exists rather than trusting the install summary — ln -s it at the hub yourself if
it doesn't.]
Two properties worth knowing before building tooling around this: installs are copies, never
links to the source checkout, so an edit needs a re-install to take effect; and the source is cloned
from the remote, so an unpushed edit is invisible. Iterating on a skill means either pushing each
time or pointing the CLI at a local path (skills add ../my-skills), which it accepts as a
first-class source.
Cut a skill by responsibility, with triggers that don't contend
Before writing a new skill, settle two things — they are the same decision seen from the authoring side and the retrieval side:
- One clear responsibility. Not a theme, not a bundle of things that happen to be needed
together. A skill covering two responsibilities has to describe both in one
description, which is the field selection actually matches on, so the dilution is paid on every prompt. - Trigger conditions that don't contend with any other installed skill's. Non-contention is a requirement, not a nice-to-have (user, 2026-08-26). A description that wins against a prompt meant for a sibling skill is a defect even when its own cases pass — the failure is invisible, because the wrong skill loading looks exactly like the right one loading.
Write the description from the request side: the words someone would actually type, not the
topic's own vocabulary. A description built from internal jargon is a structural under-trigger, not
bad luck — plans/2026-08-22-skill-trigger-quality-review.md in power-user-linux-setup has the
measured case and the eval methodology, including why scoring one description in isolation can't
catch contention.
Corollary when a finding needs a home: prefer extending the skill that already owns the topic over adding a new one. Skill count is itself a context tax and each added description is one more thing for selection to confuse.
Convention skills should self-update on friction
A skill that encodes a convention (not a one-shot task) should build in a way to improve itself from
real usage, not just get read and followed. Default pattern for any new convention skill: when using
it produces a genuinely ambiguous call the skill's own rules don't resolve, or the user corrects a
decision it made, ask rather than guessing — then fold the resolution back into the skill's own
source as a small, additive edit, and re-install so the fix reaches every project. Never edit the
installed ~/.agents/skills/<name> copy: it is a plain copy, so the edit is clobbered by the next
install and reaches no other machine in the meantime. The session-harvest skill is the worked
example — its "On friction, ask" and "Self-update mechanics" sections are the pattern to copy when
authoring the next one.
A skill's follow-up checks are procedures it runs, not chores it hands back
When a skill's own research ends in "re-measure after a week", "verify X live", "compare against the
baseline", that list is the skill's job, not the user's: encode each item as something the skill
executes on the next invocation — a script flag with pass/fail output (--compare <baseline> with
per-expectation verdicts), a stored baseline file the skill diffs against, a printed probe plan with
expected outcomes the agent runs step by step. What genuinely can't be automated (a human watching
for a permission prompt) is reduced to one yes/no question, not left as a numbered to-do. Stated by
the user 2026-08-24 on session-bash-audit's first version, which closed with a manual "open / to
re-measure" list: "i don't want to do this manually, the skill should do this for me." That skill's
Measure / Compare / Probe split is the pattern to copy.
Full rationale
references/rationale.md — why git+uv beat PyPI (and the
uvx/uv run draft that came first), why --scope user, why one registered command across two
install sources, and why "self-update on friction" generalizes rather than being one skill's quirk.