Recipes

Monorepo refactor: move an MCP gaming-agent app into apps/ as a uv workspace member

Discovered from README cross-links · today

Games & SimulationsSoftware & DevOps

Agent relocated a Python MCP server into apps/ and rewired every path

What they did

The agent took a single-package Python repository (an MCP server that lets AI agents drive a gaming PC) and restructured it into a monorepo layout. Using git mv, it relocated six groups — examples/, idea.md, pyproject.toml, src/, tests/ and uv.lock — under apps/ai-gaming-agent-mcp/, so git status reported all 30 files as renames with nothing left behind at the old paths. It then created a new repo-root pyproject.toml declaring [tool.uv.workspace] with members = ["apps/ai-gaming-agent-mcp"], and added a matching [tool.uv.workspace] block inside the app's own pyproject.toml so the app directory remains its own uv workspace root and uv keeps using apps/ai-gaming-agent-mcp/uv.lock rather than generating a root lockfile. Two content edits were needed in the moved pyproject.toml: readme = "README.md" no longer resolved because no README.md exists inside the app directory, so it was replaced with an inline text readme and content-type; everything else (packages, testpaths, ruff config, project.scripts) was left untouched because those paths are app-relative. Documentation and command lines that named the old repo-root locations were rewritten — examples/README.md commands like cd examples, uv run examples/terminal_demo.py and python examples/terminal_demo.py became cd apps/ai-gaming-agent-mcp && ... from the repo root, and the terminal_demo.py docstring was updated to the new path. No Python imports needed changing: every import in src/ and tests/ is absolute (from ai_gaming_agent.config import Config) and the workflow dispatch table names modules rather than files, so the package name survived the directory move. The agent explicitly disproved three premises in the request: that a repo-root pyproject.toml already existed (it had to be created), that the readme key could stay file-based, and that imports would need editing.

How it went

All acceptance checks passed. ./bin/check (ruff plus unit tests, run from the app directory) exited 0 in about two seconds. uv lock --check in the app directory exited 0 with no uv.lock at the repository root, and uv sync --extra dev exited 0, confirming the package metadata still builds after the inline readme swap. git status showed renames only for the six groups, with no rename or deletion for README.md, CLAUDE.md, docs/, bin/, .github/, .claude/ or .dz/. A grep confirmed no old-form path references survived outside the app directory. The change landed as part 4 of a 5-part stacked plan.

Worth knowing

The repo-root README.md was deliberately not moved, so the app's readme key is now an inline string rather than a file — a later task moving README.md into the app directory would need to restore the file-based key. The root pyproject.toml is new and minimal; it only declares workspace membership, so any future root-level tooling config has to be added separately. The move was verified by ruff and unit tests only, not by an end-to-end run of the MCP server against a real gaming PC, and the uv.lock was kept byte-identical on the assumption that the app stays its own workspace root — if that assumption breaks, uv lock must be re-run inside the app directory.

Wiring

Order matters. First git mv the six groups (examples/, idea.md, pyproject.toml, src/, tests/, uv.lock) into apps/ai-gaming-agent-mcp/ so git tracks renames rather than delete-plus-add. Second, create the repo-root pyproject.toml with [tool.uv.workspace] members = ["apps/ai-gaming-agent-mcp"]. Third, edit the moved app pyproject.toml: swap the file-based readme key for an inline text readme, and add its own [tool.uv.workspace] block with empty members so uv treats the app directory as the workspace root and resolves apps/ai-gaming-agent-mcp/uv.lock instead of writing a root lockfile. Fourth, rewrite path and command references in examples/README.md and the terminal_demo.py docstring to the apps/ai-gaming-agent-mcp/... form. Imports need no edits because they are absolute module paths. Verification chain: git status for rename-only diffs, uv lock --check inside the app directory, ./bin/check (ruff plus pytest) run from the app directory, uv sync --extra dev to confirm metadata builds, and a grep for stale old-form paths outside the app directory.

Try it yourself

Restructure this Python repo into a monorepo layout. Move examples/, idea.md, pyproject.toml, src/, tests/ and uv.lock into apps/ai-gaming-agent-mcp/ using git mv so git records renames, and leave nothing at the old paths. Create a new repo-root pyproject.toml containing [tool.uv.workspace] with members = ["apps/ai-gaming-agent-mcp"], and add the same [tool.uv.workspace] block inside the app's pyproject.toml so the app directory stays its own uv workspace root and uv keeps using apps/ai-gaming-agent-mcp/uv.lock. Fix the app's readme key: no README.md exists inside the app directory, so replace readme = "README.md" with an inline readme = { text = "...", content-type = "text/markdown" }. Leave packages, testpaths, [tool.ruff] and [project.scripts] alone — they are app-relative. Rewrite every command and doc reference that named the old repo-root paths (cd examples, uv run examples/terminal_demo.py, python examples/terminal_demo.py, src/ai_gaming_agent/) to the new apps/ai-gaming-agent-mcp/... form, including the terminal_demo.py module docstring. Do not touch imports: they are absolute and the package name is unchanged. Do not move README.md, CLAUDE.md, docs/, bin/, .github/, .claude/ or .dz/. Verify with: git status shows only renames for the six groups; cd apps/ai-gaming-agent-mcp && uv lock --check exits 0 and no uv.lock exists at the repo root; ./bin/check exits 0; cd apps/ai-gaming-agent-mcp && uv sync --extra dev exits 0; grep finds no old-form path outside the app directory.

Source

Tools in this stack