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.
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/.claude-plugin/plugin.json +18 -2
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/AGENTS.md +2 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/PKG-INFO +19 -1
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/README.md +17 -0
- mcpsmith-0.3.0/docs/superpowers/plans/2026-10-21-setup-bundle.md +75 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/pyproject.toml +5 -1
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/server.json +2 -2
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/skills/build-mcp-server/SKILL.md +2 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/__init__.py +1 -1
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/cli.py +16 -0
- mcpsmith-0.3.0/src/mcpsmith/setup/__init__.py +0 -0
- mcpsmith-0.3.0/src/mcpsmith/setup/agents.py +158 -0
- mcpsmith-0.3.0/src/mcpsmith/setup/run.py +234 -0
- mcpsmith-0.3.0/src/mcpsmith/skills_path.py +8 -0
- mcpsmith-0.3.0/tests/test_setup.py +375 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/uv.lock +12 -1
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/.claude-plugin/marketplace.json +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/.github/workflows/ci.yml +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/.github/workflows/publish.yml +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/.gitignore +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/LICENSE +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/docs/design.md +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/docs/superpowers/plans/2026-10-21-hub-core.md +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/docs/superpowers/specs/2026-10-20-mcp-everything-design.md +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/skills/design-mcp-tools/SKILL.md +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/skills/extract-api/SKILL.md +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/browser.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/capture.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/check.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/codegen.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/__init__.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/_digest.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/_draft.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/_validate.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/guide.md +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/design/llm.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/hub.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/importers/__init__.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/importers/har.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/importers/openapi.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/importers/rea.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/model.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/record.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/__init__.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/http.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/interpret.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/server.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/shape.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/runtime/transforms.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/secrets.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/server.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/templates/ci.yml +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/templates/conftest.py.tmpl +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/templates/gitignore +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/src/mcpsmith/templates/publish.yml +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/conftest.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fake_cdp.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fixtures/har/shop.har +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fixtures/openapi/petstore2.yaml +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fixtures/openapi/petstore3.json +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fixtures/openapi/tasks31.json +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/fixtures/rea/scenario.json +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_browser.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_cli.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_codegen.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_design.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_hub.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_importers.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_interpret.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_record.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_runtime.py +0 -0
- {mcpsmith-0.2.2 → mcpsmith-0.3.0}/tests/test_secrets.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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).
|
|
@@ -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
|
+
}
|