mcpsmith 0.2.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/.claude-plugin/plugin.json +18 -2
  2. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/AGENTS.md +2 -0
  3. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/PKG-INFO +19 -1
  4. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/README.md +17 -0
  5. mcpsmith-0.3.0/docs/superpowers/plans/2026-10-21-setup-bundle.md +75 -0
  6. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/pyproject.toml +5 -1
  7. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/server.json +2 -2
  8. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/skills/build-mcp-server/SKILL.md +2 -0
  9. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/__init__.py +1 -1
  10. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/cli.py +16 -0
  11. mcpsmith-0.3.0/src/mcpsmith/setup/__init__.py +0 -0
  12. mcpsmith-0.3.0/src/mcpsmith/setup/agents.py +158 -0
  13. mcpsmith-0.3.0/src/mcpsmith/setup/run.py +234 -0
  14. mcpsmith-0.3.0/src/mcpsmith/skills_path.py +8 -0
  15. mcpsmith-0.3.0/tests/test_setup.py +375 -0
  16. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/uv.lock +12 -1
  17. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/.claude-plugin/marketplace.json +0 -0
  18. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/.github/workflows/ci.yml +0 -0
  19. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/.github/workflows/publish.yml +0 -0
  20. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/.gitignore +0 -0
  21. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/LICENSE +0 -0
  22. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/docs/design.md +0 -0
  23. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/docs/superpowers/plans/2026-10-21-hub-core.md +0 -0
  24. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/docs/superpowers/specs/2026-10-20-mcp-everything-design.md +0 -0
  25. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/skills/design-mcp-tools/SKILL.md +0 -0
  26. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/skills/extract-api/SKILL.md +0 -0
  27. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/browser.py +0 -0
  28. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/capture.py +0 -0
  29. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/check.py +0 -0
  30. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/codegen.py +0 -0
  31. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/__init__.py +0 -0
  32. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/_digest.py +0 -0
  33. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/_draft.py +0 -0
  34. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/_validate.py +0 -0
  35. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/guide.md +0 -0
  36. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/llm.py +0 -0
  37. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/hub.py +0 -0
  38. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/importers/__init__.py +0 -0
  39. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/importers/har.py +0 -0
  40. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/importers/openapi.py +0 -0
  41. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/importers/rea.py +0 -0
  42. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/model.py +0 -0
  43. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/record.py +0 -0
  44. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/__init__.py +0 -0
  45. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/http.py +0 -0
  46. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/interpret.py +0 -0
  47. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/server.py +0 -0
  48. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/shape.py +0 -0
  49. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/transforms.py +0 -0
  50. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/secrets.py +0 -0
  51. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/server.py +0 -0
  52. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/templates/ci.yml +0 -0
  53. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/templates/conftest.py.tmpl +0 -0
  54. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/templates/gitignore +0 -0
  55. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/templates/publish.yml +0 -0
  56. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/conftest.py +0 -0
  57. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fake_cdp.py +0 -0
  58. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fixtures/har/shop.har +0 -0
  59. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fixtures/openapi/petstore2.yaml +0 -0
  60. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fixtures/openapi/petstore3.json +0 -0
  61. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fixtures/openapi/tasks31.json +0 -0
  62. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fixtures/rea/scenario.json +0 -0
  63. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_browser.py +0 -0
  64. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_cli.py +0 -0
  65. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_codegen.py +0 -0
  66. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_design.py +0 -0
  67. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_hub.py +0 -0
  68. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_importers.py +0 -0
  69. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_interpret.py +0 -0
  70. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_record.py +0 -0
  71. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_runtime.py +0 -0
  72. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_secrets.py +0 -0
  73. {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_transforms.py +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcpsmith",
3
3
  "displayName": "mcpsmith",
4
- "version": "0.2.2",
4
+ "version": "0.3.0",
5
5
  "description": "Turn an OpenAPI file or a recorded website session into a curated, tested MCP server. Skills and an MCP server for your coding agent.",
6
6
  "author": {
7
7
  "name": "Sepehr Radmard",
@@ -23,7 +23,23 @@
23
23
  "mcpsmith": {
24
24
  "command": "uvx",
25
25
  "args": [
26
- "mcpsmith@0.2.2",
26
+ "mcpsmith@0.3.0",
27
+ "mcp"
28
+ ]
29
+ },
30
+ "mcpsmith-chrome": {
31
+ "command": "npx",
32
+ "args": [
33
+ "-y",
34
+ "chrome-devtools-mcp@1.11.0",
35
+ "--browser-url=http://127.0.0.1:9222"
36
+ ]
37
+ },
38
+ "rea": {
39
+ "command": "npx",
40
+ "args": [
41
+ "-y",
42
+ "rea-agents@6.7.0",
27
43
  "mcp"
28
44
  ]
29
45
  }
@@ -4,6 +4,8 @@ Python framework that turns an OpenAPI/Swagger file or a recorded website sessio
4
4
 
5
5
  ## Workflow
6
6
 
7
+ Install for your agents with `uvx mcpsmith setup` (`--dry-run` first).
8
+
7
9
  Ask the user which tasks the server should support, then:
8
10
 
9
11
  1. Website only: `mcpsmith capture <url> -o site.har --host <host>` (user browses, closes the browser), or use a DevTools HAR export.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mcpsmith
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Turn an OpenAPI file or a recorded website session into a curated, tested MCP server, with an LLM designing the tools.
5
5
  Project-URL: Homepage, https://github.com/sepehr071/mcpsmith
6
6
  Project-URL: Issues, https://github.com/sepehr071/mcpsmith/issues
@@ -22,6 +22,7 @@ Requires-Dist: httpx>=0.27
22
22
  Requires-Dist: keyring>=25
23
23
  Requires-Dist: mcp<3,>=2.2
24
24
  Requires-Dist: pyyaml>=6
25
+ Requires-Dist: tomlkit>=0.12
25
26
  Requires-Dist: truststore>=0.9
26
27
  Requires-Dist: tzdata>=2024.1; sys_platform == 'win32'
27
28
  Requires-Dist: websockets>=12
@@ -40,6 +41,23 @@ Turn an OpenAPI/Swagger file, or a website session you recorded in the browser,
40
41
 
41
42
  Most generators mirror every endpoint as a tool and hand the model raw JSON, or stop at an OpenAPI spec or API client. mcpsmith designs a few workflow-level tools with plain argument names, descriptions that say when to call what next, and compact output. The input is an OpenAPI file or a HAR recording, so sites with no public API work too. The output is a server project with tests, CI, a PyPI + MCP Registry publish workflow and a Claude Code plugin.
42
43
 
44
+ ## Install
45
+
46
+ ```bash
47
+ uvx mcpsmith setup
48
+ ```
49
+
50
+ Registers the `mcpsmith` and `mcpsmith-chrome` (Chrome DevTools) MCP servers and copies the skills into each coding agent it finds (Claude Code, Codex, Cursor, Gemini CLI), and installs REA (`rea-agents`) for them. Options:
51
+
52
+ - `--dry-run` prints the plan and writes nothing.
53
+ - `--agents claude,codex` limits it to those agents (default: the ones found).
54
+ - `--no-rea` skips REA; `--no-skills` skips the skills; `--yes` skips the confirmation.
55
+ - `--remove` undoes what setup did. Entries and skill folders you made yourself are left alone. REA stays installed (you may have had it before); the command prints the line that removes it.
56
+
57
+ Before setup first changes a config file, it copies the file to `*.mcpsmith.bak`. The backup is made once and not refreshed by later runs. Restart your agents afterwards. Run `mcpsmith browser` before using Chrome DevTools: it starts the shared Chrome the server connects to.
58
+
59
+ Claude Code plugin users already get all three servers from the plugin and do not need `setup`.
60
+
43
61
  ## Quickstart: OpenAPI
44
62
 
45
63
  ```bash
@@ -6,6 +6,23 @@ Turn an OpenAPI/Swagger file, or a website session you recorded in the browser,
6
6
 
7
7
  Most generators mirror every endpoint as a tool and hand the model raw JSON, or stop at an OpenAPI spec or API client. mcpsmith designs a few workflow-level tools with plain argument names, descriptions that say when to call what next, and compact output. The input is an OpenAPI file or a HAR recording, so sites with no public API work too. The output is a server project with tests, CI, a PyPI + MCP Registry publish workflow and a Claude Code plugin.
8
8
 
9
+ ## Install
10
+
11
+ ```bash
12
+ uvx mcpsmith setup
13
+ ```
14
+
15
+ Registers the `mcpsmith` and `mcpsmith-chrome` (Chrome DevTools) MCP servers and copies the skills into each coding agent it finds (Claude Code, Codex, Cursor, Gemini CLI), and installs REA (`rea-agents`) for them. Options:
16
+
17
+ - `--dry-run` prints the plan and writes nothing.
18
+ - `--agents claude,codex` limits it to those agents (default: the ones found).
19
+ - `--no-rea` skips REA; `--no-skills` skips the skills; `--yes` skips the confirmation.
20
+ - `--remove` undoes what setup did. Entries and skill folders you made yourself are left alone. REA stays installed (you may have had it before); the command prints the line that removes it.
21
+
22
+ Before setup first changes a config file, it copies the file to `*.mcpsmith.bak`. The backup is made once and not refreshed by later runs. Restart your agents afterwards. Run `mcpsmith browser` before using Chrome DevTools: it starts the shared Chrome the server connects to.
23
+
24
+ Claude Code plugin users already get all three servers from the plugin and do not need `setup`.
25
+
9
26
  ## Quickstart: OpenAPI
10
27
 
11
28
  ```bash
@@ -0,0 +1,75 @@
1
+ # Setup Bundle Implementation Plan (plan 2 of 3)
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** `mcpsmith setup` registers the mcpsmith hub, Chrome DevTools MCP (attached to the shared Chrome) and REA with Claude Code, Codex, Cursor and Gemini CLI, and installs mcpsmith's skills — one command, previewed, backed up, reversible.
6
+
7
+ **Architecture:** A per-agent target module edits each agent's user-level MCP config (JSON for Claude Code/Cursor/Gemini, TOML via `tomlkit` for Codex) with read → merge → backup → write → verify, never touching unrelated entries. Skills ship inside the wheel and are copied to `~/.claude/skills` (Claude Code) and `~/.agents/skills` (Codex, Cursor, Gemini). REA is registered by delegating to REA's own non-interactive setup.
8
+
9
+ **Tech Stack:** Python ≥3.10 stdlib (json, shutil, importlib.resources, subprocess), `tomlkit` (new dependency, comment-preserving TOML). Tests: pytest with a fake HOME.
10
+
11
+ **Spec:** `docs/superpowers/specs/2026-10-20-mcp-everything-design.md` (G6; decisions 8, 9). Research facts (2026-10-21) are recorded in Global Constraints below.
12
+
13
+ ## Global Constraints
14
+
15
+ - Config locations (user level): Claude Code `~/.claude.json` (`$CLAUDE_CONFIG_DIR/.claude.json` if set), key `mcpServers.<name>` = `{"type":"stdio","command","args","env"}`; Codex `~/.codex/config.toml` (`$CODEX_HOME` if set), table `[mcp_servers.<name>]` with `command`, `args`, `startup_timeout_sec = 30`, sub-table `env`; Cursor `~/.cursor/mcp.json`, `mcpServers.<name>` = `{"type":"stdio","command","args","env"}`; Gemini `~/.gemini/settings.json` (`$GEMINI_CLI_HOME/.gemini/settings.json` if set), `mcpServers.<name>` = `{"command","args","env","timeout":30000}`.
16
+ - Skills: Claude Code `~/.claude/skills/<name>/`; Codex, Cursor, Gemini `~/.agents/skills/<name>/`. Skill folder name equals its frontmatter `name`.
17
+ - Server entries (names exactly): `mcpsmith` → `uvx mcpsmith@<package version> mcp`; `chrome-devtools` → `npx -y chrome-devtools-mcp@1.11.0 --browser-url=http://127.0.0.1:9222`; REA → delegated: `npx -y rea-agents@6.7.0 setup --client <id> --yes` with ids `claude_code`, `codex`, `cursor`, `gemini_cli`.
18
+ - On Windows, `npx` commands are written as `command: "cmd"`, `args: ["/d","/c","npx",...]`; `uvx` stays as is.
19
+ - `env` written for the `mcpsmith` entry: `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` from setup's environment when set, with userinfo (`user:pass@`) stripped (reuse `browser.clean_proxy`); never write any other secret.
20
+ - Never modify or remove entries mcpsmith did not create; preserve TOML comments and unrelated tables; JSON written with 2-space indent and UTF-8, original key order kept.
21
+ - Before the first write to a file: copy it to `<file>.mcpsmith.bak` (do not overwrite an existing backup). After writing, re-read and verify the entry; on failure restore the backup and report.
22
+ - Node.js missing (`npx` not on PATH) → skip `chrome-devtools` and REA with one clear line each; still do the rest.
23
+ - Default asks for confirmation after printing the plan; `--yes` skips it; `--dry-run` prints the plan and writes nothing; non-interactive stdin without `--yes` → error, exit 2.
24
+ - New dependency only: `tomlkit>=0.12`. Existing behaviour stays; full suite green; ruff clean; commits on branch `feat/setup` without AI attribution.
25
+
26
+ ## Review Focus
27
+
28
+ 1. Existing user configs with unrelated servers, comments (TOML) and other top-level keys → untouched after setup and after `--remove`. (Task 2 tests.)
29
+ 2. Running setup twice → idempotent: no duplicate entries, no second backup overwrite. (Task 3 test.)
30
+ 3. A config file that is malformed JSON/TOML → that agent is skipped with a clear message; nothing written to it. (Task 2 test.)
31
+ 4. Proxy env with credentials → credentials never written. (Task 2 test.)
32
+ 5. Agent not installed → not touched unless named with `--agents`. (Task 3 test.)
33
+
34
+ ---
35
+
36
+ ### Task 1: Ship skills in the package
37
+
38
+ **Files:** Modify `pyproject.toml`; Create `src/mcpsmith/skills_path.py`; Test `tests/test_setup.py`
39
+
40
+ **Interfaces:**
41
+ - Produces: `skills_path.skills_dir() -> Path` — the packaged skills directory (`importlib.resources.files("mcpsmith") / "skills"` when present, else the repo's `skills/` for editable installs); contains `build-mcp-server`, `design-mcp-tools`, `extract-api`.
42
+
43
+ - [ ] **Step 1:** Test `test_skills_dir_has_all_skills`: the three folders exist, each with a `SKILL.md` whose frontmatter `name` equals the folder name. Test `test_wheel_contains_skills`: `uv build --wheel` into `tmp_path`, the wheel's namelist contains `mcpsmith/skills/extract-api/SKILL.md` (mark the test `slow` only if it exceeds 20 s).
44
+ - [ ] **Step 2:** Run → FAIL. **Step 3:** Add `[tool.hatch.build.targets.wheel.force-include] "skills" = "mcpsmith/skills"`; implement `skills_dir`. **Step 4:** Run full suite → PASS. **Step 5:** Commit `build: ship skills inside the package`.
45
+
46
+ ### Task 2: Agent targets
47
+
48
+ **Files:** Create `src/mcpsmith/setup/__init__.py`, `src/mcpsmith/setup/agents.py`; Modify `pyproject.toml` (`tomlkit`); Test `tests/test_setup.py`
49
+
50
+ **Interfaces:**
51
+ - Produces:
52
+ - `SERVERS: dict[str, list[str]]` — base commands per server name (from Global Constraints), and `server_entry(name: str, windows: bool, env: dict[str,str]) -> dict` (command/args, Windows `cmd /d /c` wrapping for npx).
53
+ - `class Agent` (one per agent: `claude`, `codex`, `cursor`, `gemini`) with `id`, `rea_client`, `config_path(home: Path, environ) -> Path`, `skills_root(home) -> Path`, `detected(home, environ, which) -> bool` (config dir exists or CLI on PATH), `add(doc, name, entry) -> doc`, `remove(doc, name) -> doc`, `has(doc, name) -> bool`, `load(path) -> doc` (raises `ValueError` on malformed), `dump(doc) -> str`.
54
+ - `AGENTS: dict[str, Agent]`.
55
+ - [ ] **Step 1:** Tests on in-memory docs: each agent adds `mcpsmith` and `chrome-devtools` with exact shapes from Global Constraints; unrelated servers/keys preserved; Codex TOML comment and an unrelated `[mcp_servers.other]` survive add+remove; `remove` deletes only our names; malformed JSON/TOML → `ValueError`; `server_entry("chrome-devtools", windows=True, …)` → `command == "cmd"`, args start `["/d","/c","npx","-y","chrome-devtools-mcp@1.11.0"]`; mcpsmith env from `HTTPS_PROXY=http://u:p@h:1` → `"http://h:1"`; `CLAUDE_CONFIG_DIR`/`CODEX_HOME`/`GEMINI_CLI_HOME` honored.
56
+ - [ ] **Step 2:** Run → FAIL. **Step 3:** Implement. **Step 4:** Full suite → PASS. **Step 5:** Commit `feat(setup): agent config targets`.
57
+
58
+ ### Task 3: `mcpsmith setup` command
59
+
60
+ **Files:** Create `src/mcpsmith/setup/run.py`; Modify `src/mcpsmith/cli.py`; Test `tests/test_setup.py`
61
+
62
+ **Interfaces:**
63
+ - Consumes: `AGENTS`, `server_entry`, `skills_dir`.
64
+ - Produces: CLI `mcpsmith setup [--agents claude,codex,cursor,gemini] [--no-rea] [--no-skills] [--dry-run] [--yes] [--remove]`; `plan(home, environ, agents, rea, skills, remove) -> list[Change]` (`Change`: agent, kind `server|skill|rea`, target path or command, action `add|update|remove|skip`, reason); `apply(changes, runner=subprocess.run) -> list[str]` (result lines).
65
+ - Behaviour: detected agents by default (or `--agents`); prints the plan (paths, entries, skill folders, REA command), asks `Apply these changes? [y/N]` unless `--yes`/`--dry-run`; per file backup→write→verify (restore on verify failure); skills copied with `shutil.copytree(dirs_exist_ok=True)` (only mcpsmith's three folders; `--remove` deletes only those); REA via `runner([npx..., "setup", "--client", id, "--yes"])` (Windows-wrapped), `--remove` → `rea-agents@6.7.0 uninstall --client id --yes`; final line tells the user to restart their agents and to run `mcpsmith browser` before using Chrome DevTools.
66
+ - [ ] **Step 1:** Tests with fake HOME and stub `which`/runner: `--dry-run` writes nothing and lists every change; a real run writes entries + skills + backups and calls REA once per agent with the right client id; second run → no changes reported as `add`, backups unchanged; `--remove` restores a config equal to the original except formatting (JSON) / byte-identical comments (TOML), skills gone; undetected agent untouched; malformed config → that agent skipped, others done; no `npx` → chrome-devtools and REA skipped with messages; stdin not a TTY and no `--yes` → exit 2.
67
+ - [ ] **Step 2:** Run → FAIL. **Step 3:** Implement. **Step 4:** Full suite → PASS. **Step 5:** Commit `feat: mcpsmith setup registers the bundle with coding agents`.
68
+
69
+ ### Task 4: Plugin, docs, release 0.3.0
70
+
71
+ **Files:** Modify `.claude-plugin/plugin.json` (add `chrome-devtools` and `rea` servers, pinned, same args as setup on non-Windows), `README.md` (Install section: `uvx mcpsmith setup`; plugin users don't also run setup), `AGENTS.md`, `skills/build-mcp-server/SKILL.md` (setup line), version 0.3.0 in all version spots; `uv lock`.
72
+
73
+ - [ ] **Step 1:** `claude plugin validate .` → "Validation passed"; `uv run pytest -q`; ruff.
74
+ - [ ] **Step 2: Acceptance** — run `mcpsmith setup --dry-run` on the real machine and show the plan; run the real setup with `HOME`/`USERPROFILE` pointed at a scratch dir seeded with copies of the four real config files (no real config touched) and check: entries added, comments/unrelated servers preserved, `--remove` reverts. Real-machine install only with the user's yes.
75
+ - [ ] **Step 3:** Commit `chore: release 0.3.0`; PR `feat/setup` → main; CI green; merge; tag `v0.3.0`; publish workflow green.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcpsmith"
3
- version = "0.2.2"
3
+ version = "0.3.0"
4
4
  description = "Turn an OpenAPI file or a recorded website session into a curated, tested MCP server, with an LLM designing the tools."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -27,6 +27,7 @@ dependencies = [
27
27
  "websockets>=12",
28
28
  "keyring>=25",
29
29
  "tzdata>=2024.1; sys_platform == 'win32'",
30
+ "tomlkit>=0.12",
30
31
  ]
31
32
 
32
33
  [project.optional-dependencies]
@@ -49,6 +50,9 @@ dev = ["pytest>=8", "anyio>=4", "ruff>=0.6"]
49
50
  requires = ["hatchling"]
50
51
  build-backend = "hatchling.build"
51
52
 
53
+ [tool.hatch.build.targets.wheel.force-include]
54
+ "skills" = "mcpsmith/skills"
55
+
52
56
  [tool.pytest.ini_options]
53
57
  testpaths = ["tests"]
54
58
  addopts = "-m 'not live'"
@@ -8,13 +8,13 @@
8
8
  "source": "github"
9
9
  },
10
10
  "websiteUrl": "https://github.com/sepehr071/mcpsmith",
11
- "version": "0.2.2",
11
+ "version": "0.3.0",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "pypi",
15
15
  "registryBaseUrl": "https://pypi.org",
16
16
  "identifier": "mcpsmith",
17
- "version": "0.2.2",
17
+ "version": "0.3.0",
18
18
  "runtimeHint": "uvx",
19
19
  "transport": {
20
20
  "type": "stdio"
@@ -5,6 +5,8 @@ description: Build a curated, tested MCP server (agent tools) with mcpsmith. Use
5
5
 
6
6
  # Build an MCP server with mcpsmith
7
7
 
8
+ Not set up yet? `uvx mcpsmith setup` registers mcpsmith, Chrome DevTools MCP and REA with the user's agents.
9
+
8
10
  mcpsmith turns an OpenAPI file or a recorded website session (HAR) into a Python MCP server project with tests, CI, a publish workflow and a Claude Code plugin. You design the tools; mcpsmith generates and checks the code.
9
11
 
10
12
  Run commands with `uvx mcpsmith <cmd>` (or `mcpsmith <cmd>` if installed). No shell? Use the mcpsmith MCP tools instead: `smith_guide`, `smith_inspect`, `smith_draft`, `smith_validate`, `smith_generate`, `smith_check`, and the hub tools `smith_record_start`, `smith_record_stop`, `smith_connect`, `smith_disconnect`, `smith_connections`, `smith_tools`, `smith_call` (paths absolute).
@@ -1,3 +1,3 @@
1
1
  """mcpsmith: turn an OpenAPI file or a recorded website session into a curated, tested MCP server."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.3.0"
@@ -223,6 +223,12 @@ def cmd_connections(args) -> int:
223
223
  return 0
224
224
 
225
225
 
226
+ def cmd_setup(args) -> int:
227
+ from .setup.run import cmd
228
+
229
+ return cmd(args)
230
+
231
+
226
232
  def cmd_mcp(args) -> int:
227
233
  from .server import main as serve
228
234
 
@@ -290,6 +296,16 @@ def build_parser() -> argparse.ArgumentParser:
290
296
  sp.add_argument("--port", type=int, default=9222)
291
297
  sp.add_argument("--no-proxy", action="store_true", help="do not pass HTTPS_PROXY on to Chrome")
292
298
 
299
+ sp = command(
300
+ "setup", cmd_setup, "register mcpsmith, Chrome DevTools MCP and REA with your coding agents", workdir=False
301
+ )
302
+ sp.add_argument("--agents", help="comma-separated: claude,codex,cursor,gemini (default: the ones found)")
303
+ sp.add_argument("--no-rea", action="store_true", help="skip the REA install")
304
+ sp.add_argument("--no-skills", action="store_true", help="skip copying the skills")
305
+ sp.add_argument("--dry-run", action="store_true", help="print the plan, write nothing")
306
+ sp.add_argument("--yes", action="store_true", help="do not ask for confirmation")
307
+ sp.add_argument("--remove", action="store_true", help="undo what setup did")
308
+
293
309
  command("connections", cmd_connections, "list the connections saved for the mcpsmith server", workdir=False)
294
310
  command("guide", cmd_guide, "print the tool-design rules for plan.yaml", workdir=False)
295
311
  command("mcp", cmd_mcp, "run the mcpsmith MCP server on stdio", workdir=False)
File without changes
@@ -0,0 +1,158 @@
1
+ """Where each coding agent keeps its MCP servers and skills, and how to add/remove ours without touching the rest."""
2
+
3
+ import json
4
+ import shutil
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ import tomlkit
10
+
11
+ from mcpsmith import __version__
12
+ from mcpsmith.browser import clean_proxy
13
+
14
+ SERVERS = {
15
+ "mcpsmith": ["uvx", f"mcpsmith@{__version__}", "mcp"],
16
+ "mcpsmith-chrome": ["npx", "-y", "chrome-devtools-mcp@1.11.0", "--browser-url=http://127.0.0.1:9222"],
17
+ }
18
+
19
+
20
+ def proxy_env(environ) -> dict[str, str]:
21
+ """Proxy variables worth passing to the mcpsmith server: userinfo stripped, nothing else copied."""
22
+ env = {}
23
+ for key in ("HTTPS_PROXY", "HTTP_PROXY"):
24
+ value = clean_proxy(environ.get(key) or environ.get(key.lower()))
25
+ if value:
26
+ env[key] = value
27
+ if no_proxy := environ.get("NO_PROXY") or environ.get("no_proxy"):
28
+ env["NO_PROXY"] = no_proxy
29
+ return env
30
+
31
+
32
+ def server_entry(name: str, windows: bool, env: dict[str, str]) -> dict:
33
+ command, *args = SERVERS[name]
34
+ if windows and command == "npx":
35
+ command, args = "cmd", ["/d", "/c", "npx", *args]
36
+ return {"command": command, "args": args, "env": dict(env) if name == "mcpsmith" else {}}
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class Agent:
41
+ id: str
42
+ rea_client: str
43
+ toml: bool
44
+ config: Callable[[Path, dict], Path]
45
+ skills: Callable[[Path, dict], Path]
46
+ config_dir: Callable[[Path, dict], Path]
47
+ clis: tuple[str, ...]
48
+
49
+ def config_path(self, home: Path, environ) -> Path:
50
+ return self.config(home, environ)
51
+
52
+ def skills_root(self, home: Path, environ) -> Path:
53
+ return self.skills(home, environ)
54
+
55
+ def detected(self, home: Path, environ, which=shutil.which) -> bool:
56
+ return self.config_dir(home, environ).is_dir() or any(which(cli) for cli in self.clis)
57
+
58
+ def load(self, path: Path):
59
+ text = path.read_text(encoding="utf-8") if path.exists() else ""
60
+ try:
61
+ if self.toml:
62
+ return tomlkit.parse(text)
63
+ doc = json.loads(text) if text.strip() else {}
64
+ except Exception as e: # tomlkit and json raise different parse errors
65
+ raise ValueError(f"{path}: not valid {'TOML' if self.toml else 'JSON'} ({e})") from e
66
+ if not isinstance(doc, dict):
67
+ raise ValueError(f"{path}: expected an object at the top level")
68
+ return doc
69
+
70
+ def dump(self, doc) -> str:
71
+ if self.toml:
72
+ return tomlkit.dumps(doc)
73
+ return json.dumps(doc, indent=2, ensure_ascii=False) + "\n"
74
+
75
+ def _key(self) -> str:
76
+ return "mcp_servers" if self.toml else "mcpServers"
77
+
78
+ def has(self, doc, name: str) -> bool:
79
+ return name in doc.get(self._key(), {})
80
+
81
+ def add(self, doc, name: str, entry: dict):
82
+ if self.toml:
83
+ table = tomlkit.table()
84
+ table["command"] = entry["command"]
85
+ table["args"] = entry["args"]
86
+ table["startup_timeout_sec"] = 30
87
+ if entry["env"]:
88
+ table["env"] = tomlkit.item(entry["env"])
89
+ if "mcp_servers" not in doc:
90
+ doc["mcp_servers"] = tomlkit.table(is_super_table=True)
91
+ doc["mcp_servers"][name] = table
92
+ else:
93
+ entry = {**entry, "timeout": 30000} if self.id == "gemini" else {"type": "stdio", **entry}
94
+ doc.setdefault("mcpServers", {})[name] = entry
95
+ return doc
96
+
97
+ def remove(self, doc, name: str):
98
+ if self.has(doc, name):
99
+ del doc[self._key()][name]
100
+ return doc
101
+
102
+
103
+ def _claude_dir(home, environ):
104
+ return Path(environ.get("CLAUDE_CONFIG_DIR") or home / ".claude")
105
+
106
+
107
+ def _codex_home(home, environ):
108
+ return Path(environ.get("CODEX_HOME") or home / ".codex")
109
+
110
+
111
+ def _gemini_dir(home, environ):
112
+ return Path(environ.get("GEMINI_CLI_HOME") or home) / ".gemini"
113
+
114
+
115
+ def _agent_skills(home, environ):
116
+ return home / ".agents" / "skills"
117
+
118
+
119
+ AGENTS = {
120
+ "claude": Agent(
121
+ "claude",
122
+ "claude_code",
123
+ False,
124
+ lambda home, env: (
125
+ Path(env["CLAUDE_CONFIG_DIR"]) / ".claude.json" if env.get("CLAUDE_CONFIG_DIR") else home / ".claude.json"
126
+ ),
127
+ lambda home, env: _claude_dir(home, env) / "skills",
128
+ _claude_dir,
129
+ ("claude",),
130
+ ),
131
+ "codex": Agent(
132
+ "codex",
133
+ "codex",
134
+ True,
135
+ lambda home, env: _codex_home(home, env) / "config.toml",
136
+ _agent_skills,
137
+ _codex_home,
138
+ ("codex",),
139
+ ),
140
+ "cursor": Agent(
141
+ "cursor",
142
+ "cursor",
143
+ False,
144
+ lambda home, env: home / ".cursor" / "mcp.json",
145
+ _agent_skills,
146
+ lambda home, env: home / ".cursor",
147
+ ("cursor-agent", "cursor"),
148
+ ),
149
+ "gemini": Agent(
150
+ "gemini",
151
+ "gemini_cli",
152
+ False,
153
+ lambda home, env: _gemini_dir(home, env) / "settings.json",
154
+ _agent_skills,
155
+ _gemini_dir,
156
+ ("gemini",),
157
+ ),
158
+ }