create-mcp 0.1.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 (41) hide show
  1. create_mcp-0.1.0/.gitignore +21 -0
  2. create_mcp-0.1.0/LICENSE +21 -0
  3. create_mcp-0.1.0/PKG-INFO +157 -0
  4. create_mcp-0.1.0/README.md +131 -0
  5. create_mcp-0.1.0/pyproject.toml +85 -0
  6. create_mcp-0.1.0/src/create_mcp/__init__.py +10 -0
  7. create_mcp-0.1.0/src/create_mcp/__main__.py +8 -0
  8. create_mcp-0.1.0/src/create_mcp/cli.py +193 -0
  9. create_mcp-0.1.0/src/create_mcp/generator.py +178 -0
  10. create_mcp-0.1.0/src/create_mcp/presets.py +57 -0
  11. create_mcp-0.1.0/src/create_mcp/templates/__init__.py +7 -0
  12. create_mcp-0.1.0/src/create_mcp/templates/auth/src/__pkg__/auth.py.jinja +34 -0
  13. create_mcp-0.1.0/src/create_mcp/templates/auth/tests/test_auth.py.jinja +44 -0
  14. create_mcp-0.1.0/src/create_mcp/templates/base/Dockerfile.jinja +32 -0
  15. create_mcp-0.1.0/src/create_mcp/templates/base/LICENSE.jinja +21 -0
  16. create_mcp-0.1.0/src/create_mcp/templates/base/README.md.jinja +122 -0
  17. create_mcp-0.1.0/src/create_mcp/templates/base/dot-dockerignore.jinja +13 -0
  18. create_mcp-0.1.0/src/create_mcp/templates/base/dot-env.example.jinja +21 -0
  19. create_mcp-0.1.0/src/create_mcp/templates/base/dot-github/workflows/ci.yml.jinja +32 -0
  20. create_mcp-0.1.0/src/create_mcp/templates/base/dot-gitignore.jinja +29 -0
  21. create_mcp-0.1.0/src/create_mcp/templates/base/dot-pre-commit-config.yaml.jinja +15 -0
  22. create_mcp-0.1.0/src/create_mcp/templates/base/pyproject.toml.jinja +58 -0
  23. create_mcp-0.1.0/src/create_mcp/templates/base/src/__pkg__/__init__.py.jinja +3 -0
  24. create_mcp-0.1.0/src/create_mcp/templates/base/src/__pkg__/__main__.py.jinja +17 -0
  25. create_mcp-0.1.0/src/create_mcp/templates/base/src/__pkg__/app.py.jinja +31 -0
  26. create_mcp-0.1.0/src/create_mcp/templates/base/src/__pkg__/server.py.jinja +30 -0
  27. create_mcp-0.1.0/src/create_mcp/templates/base/src/__pkg__/settings.py.jinja +30 -0
  28. create_mcp-0.1.0/src/create_mcp/templates/base/tests/__init__.py.jinja +0 -0
  29. create_mcp-0.1.0/src/create_mcp/templates/base/tests/conftest.py.jinja +18 -0
  30. create_mcp-0.1.0/src/create_mcp/templates/base/tests/test_server.py.jinja +19 -0
  31. create_mcp-0.1.0/src/create_mcp/templates/presets/agent_tools/src/__pkg__/tools.py.jinja +73 -0
  32. create_mcp-0.1.0/src/create_mcp/templates/presets/agent_tools/tests/test_tools.py.jinja +45 -0
  33. create_mcp-0.1.0/src/create_mcp/templates/presets/api_wrapper/src/__pkg__/tools.py.jinja +41 -0
  34. create_mcp-0.1.0/src/create_mcp/templates/presets/api_wrapper/tests/test_tools.py.jinja +29 -0
  35. create_mcp-0.1.0/src/create_mcp/templates/presets/db/src/__pkg__/tools.py.jinja +79 -0
  36. create_mcp-0.1.0/src/create_mcp/templates/presets/db/tests/test_tools.py.jinja +29 -0
  37. create_mcp-0.1.0/src/create_mcp/templates/presets/minimal/src/__pkg__/tools.py.jinja +40 -0
  38. create_mcp-0.1.0/src/create_mcp/templates/presets/minimal/tests/test_tools.py.jinja +25 -0
  39. create_mcp-0.1.0/tests/__init__.py +0 -0
  40. create_mcp-0.1.0/tests/test_cli.py +47 -0
  41. create_mcp-0.1.0/tests/test_generate.py +95 -0
@@ -0,0 +1,21 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+
8
+ # uv / venv
9
+ .venv/
10
+ uv.lock
11
+
12
+ # Caches
13
+ .pytest_cache/
14
+ .ruff_cache/
15
+ .mypy_cache/
16
+ .coverage
17
+
18
+ # Editors / OS
19
+ .idea/
20
+ .vscode/
21
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Blaze (https://blaze.uz)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,157 @@
1
+ Metadata-Version: 2.4
2
+ Name: create-mcp
3
+ Version: 0.1.0
4
+ Summary: The create-next-app for production MCP servers — scaffold a typed, tested, auth-ready Python MCP server in one command.
5
+ Project-URL: Homepage, https://github.com/blaze-uz/create-mcp
6
+ Project-URL: Repository, https://github.com/blaze-uz/create-mcp
7
+ Project-URL: Issues, https://github.com/blaze-uz/create-mcp/issues
8
+ Author-email: Blaze <hello@blaze.uz>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agents,ai,cli,fastapi,fastmcp,generator,mcp,model-context-protocol,oauth,scaffold
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Code Generators
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: jinja2>=3.1
23
+ Requires-Dist: rich>=13.7
24
+ Requires-Dist: typer>=0.15
25
+ Description-Content-Type: text/markdown
26
+
27
+ <div align="center">
28
+
29
+ # create-mcp
30
+
31
+ **The `create-next-app` for production MCP servers.**
32
+
33
+ One command scaffolds a typed, tested, auth-ready Python [MCP](https://modelcontextprotocol.io) server you can *ship* — not just run.
34
+
35
+ [![CI](https://github.com/blaze-uz/create-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/blaze-uz/create-mcp/actions/workflows/ci.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/create-mcp.svg)](https://pypi.org/project/create-mcp/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/create-mcp.svg)](https://pypi.org/project/create-mcp/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
39
+
40
+ </div>
41
+
42
+ ```bash
43
+ uvx create-mcp my-server
44
+ ```
45
+
46
+ <!-- A terminal recording (vhs / asciinema) can replace this block before launch. -->
47
+
48
+ ```text
49
+ $ uvx create-mcp
50
+ Project name: pay-tools
51
+ Preset: minimal
52
+ Transport: streamable-http
53
+ Auth: oauth
54
+
55
+ ✓ Scaffolded pay-tools (Minimal · streamable-http · auth: oauth)
56
+
57
+ cd pay-tools
58
+ uv sync # install dependencies
59
+ uv run pay_tools # start the server
60
+ uv run pytest # green ✓
61
+ ```
62
+
63
+ ## Why
64
+
65
+ The official MCP SDK quickstart and `create-mcp-server` hand you a single
66
+ hello-world tool and stop. You still have to add tests, typing, linting, CI, a
67
+ Dockerfile, config, and — the hard one — spec-compliant **OAuth 2.1**. Most MCP
68
+ servers in the wild are hello-world demos that never reach production.
69
+
70
+ `create-mcp` generates the *whole* repo, green on the first run:
71
+
72
+ - ⚡ **`uvx create-mcp` — zero install.** Same ergonomics as `create-next-app`.
73
+ - 🧱 **Production defaults, not a toy.** Tests, CI, Docker, ruff, mypy, pre-commit, `.env`, a real README.
74
+ - 🔐 **OAuth 2.1 in one flag.** `--auth oauth` scaffolds an [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) resource server (Protected Resource Metadata + `401`/`WWW-Authenticate` discovery + JWT validation). Timed for the 2026 MCP authorization spec.
75
+ - 🌊 **Streamable HTTP first.** The modern transport (SSE is deprecated), plus a stdio preset for Claude Desktop / Cursor.
76
+ - 🧪 **Tests pass out of the box.** Generated tests use FastMCP's in-memory client — no network, milliseconds.
77
+ - 🎯 **Presets, not questionnaires.** Scaffold a *use case*: `minimal`, `api-wrapper`, `db`, `agent-tools`.
78
+ - 🐍 **Typed end-to-end.** Pydantic models for tool I/O; ruff- and mypy-clean.
79
+
80
+ Built on [FastMCP](https://gofastmcp.com) + [`uv`](https://docs.astral.sh/uv/).
81
+
82
+ ## Usage
83
+
84
+ ```bash
85
+ # Interactive
86
+ uvx create-mcp
87
+
88
+ # Scripted / non-interactive
89
+ uvx create-mcp pay-tools --preset api-wrapper --auth oauth --yes
90
+ ```
91
+
92
+ Then:
93
+
94
+ ```bash
95
+ cd pay-tools
96
+ uv sync
97
+ uv run pay_tools # start the server
98
+ uv run pytest # green ✓
99
+ ```
100
+
101
+ ### Options
102
+
103
+ | Flag | Values | Default | Description |
104
+ |------|--------|---------|-------------|
105
+ | `--preset` `-p` | `minimal`, `api-wrapper`, `db`, `agent-tools` | `minimal` | Starting set of tools/resources/prompts |
106
+ | `--transport` `-t` | `streamable-http`, `stdio` | `streamable-http` | MCP transport |
107
+ | `--auth` `-a` | `none`, `oauth` | `none` | OAuth 2.1 resource server (RFC 9728) |
108
+ | `--package-name` | identifier | derived | Override the Python package name |
109
+ | `--output-dir` `-o` | path | `.` | Where to create the project |
110
+ | `--no-git` / `--git` | | `--git` | Initialise a git repo + first commit |
111
+ | `--no-install` / `--install` | | `--install` | Run `uv sync` after scaffolding |
112
+ | `--no-precommit` / `--precommit` | | `--precommit` | Install pre-commit hooks |
113
+ | `--force` | | off | Overwrite a non-empty target directory |
114
+ | `--yes` `-y` | | off | Accept all defaults; never prompt (CI) |
115
+
116
+ ### Presets
117
+
118
+ | Preset | What you get |
119
+ |--------|--------------|
120
+ | **minimal** | A clean typed server: one tool (structured output), a resource, a prompt. |
121
+ | **api-wrapper** | Wrap an HTTP/JSON API as MCP tools, with the network call isolated for easy mocking. |
122
+ | **db** | A SQLite-backed store exposed as CRUD tools (swap in your real DB). |
123
+ | **agent-tools** | A toolbox for agents: safe calculator, scratchpad memory, clock. |
124
+
125
+ ## What the generated project looks like
126
+
127
+ ```
128
+ my-server/
129
+ ├── src/my_server/
130
+ │ ├── server.py # FastMCP instance (+ /health, + auth when enabled)
131
+ │ ├── tools.py # your tools, resources, prompts
132
+ │ ├── settings.py # typed config (pydantic-settings)
133
+ │ ├── app.py # FastAPI host mounting MCP at /mcp
134
+ │ ├── auth.py # OAuth 2.1 resource server (only with --auth oauth)
135
+ │ └── __main__.py # `uv run my_server`
136
+ ├── tests/ # in-memory tests, green out of the box
137
+ ├── Dockerfile # uv-based image
138
+ ├── .github/workflows/ci.yml
139
+ ├── .pre-commit-config.yaml
140
+ ├── .env.example
141
+ └── pyproject.toml
142
+ ```
143
+
144
+ ## Requirements
145
+
146
+ - [`uv`](https://docs.astral.sh/uv/) (for `uvx` and the generated projects)
147
+ - Python 3.11+
148
+
149
+ ## Contributing
150
+
151
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Every release runs the full matrix —
152
+ generating a project for **each preset × auth mode**, then installing, linting,
153
+ type-checking and testing it — so the templates can't rot silently.
154
+
155
+ ## License
156
+
157
+ MIT © [Blaze](https://blaze.uz)
@@ -0,0 +1,131 @@
1
+ <div align="center">
2
+
3
+ # create-mcp
4
+
5
+ **The `create-next-app` for production MCP servers.**
6
+
7
+ One command scaffolds a typed, tested, auth-ready Python [MCP](https://modelcontextprotocol.io) server you can *ship* — not just run.
8
+
9
+ [![CI](https://github.com/blaze-uz/create-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/blaze-uz/create-mcp/actions/workflows/ci.yml)
10
+ [![PyPI](https://img.shields.io/pypi/v/create-mcp.svg)](https://pypi.org/project/create-mcp/)
11
+ [![Python](https://img.shields.io/pypi/pyversions/create-mcp.svg)](https://pypi.org/project/create-mcp/)
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
13
+
14
+ </div>
15
+
16
+ ```bash
17
+ uvx create-mcp my-server
18
+ ```
19
+
20
+ <!-- A terminal recording (vhs / asciinema) can replace this block before launch. -->
21
+
22
+ ```text
23
+ $ uvx create-mcp
24
+ Project name: pay-tools
25
+ Preset: minimal
26
+ Transport: streamable-http
27
+ Auth: oauth
28
+
29
+ ✓ Scaffolded pay-tools (Minimal · streamable-http · auth: oauth)
30
+
31
+ cd pay-tools
32
+ uv sync # install dependencies
33
+ uv run pay_tools # start the server
34
+ uv run pytest # green ✓
35
+ ```
36
+
37
+ ## Why
38
+
39
+ The official MCP SDK quickstart and `create-mcp-server` hand you a single
40
+ hello-world tool and stop. You still have to add tests, typing, linting, CI, a
41
+ Dockerfile, config, and — the hard one — spec-compliant **OAuth 2.1**. Most MCP
42
+ servers in the wild are hello-world demos that never reach production.
43
+
44
+ `create-mcp` generates the *whole* repo, green on the first run:
45
+
46
+ - ⚡ **`uvx create-mcp` — zero install.** Same ergonomics as `create-next-app`.
47
+ - 🧱 **Production defaults, not a toy.** Tests, CI, Docker, ruff, mypy, pre-commit, `.env`, a real README.
48
+ - 🔐 **OAuth 2.1 in one flag.** `--auth oauth` scaffolds an [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) resource server (Protected Resource Metadata + `401`/`WWW-Authenticate` discovery + JWT validation). Timed for the 2026 MCP authorization spec.
49
+ - 🌊 **Streamable HTTP first.** The modern transport (SSE is deprecated), plus a stdio preset for Claude Desktop / Cursor.
50
+ - 🧪 **Tests pass out of the box.** Generated tests use FastMCP's in-memory client — no network, milliseconds.
51
+ - 🎯 **Presets, not questionnaires.** Scaffold a *use case*: `minimal`, `api-wrapper`, `db`, `agent-tools`.
52
+ - 🐍 **Typed end-to-end.** Pydantic models for tool I/O; ruff- and mypy-clean.
53
+
54
+ Built on [FastMCP](https://gofastmcp.com) + [`uv`](https://docs.astral.sh/uv/).
55
+
56
+ ## Usage
57
+
58
+ ```bash
59
+ # Interactive
60
+ uvx create-mcp
61
+
62
+ # Scripted / non-interactive
63
+ uvx create-mcp pay-tools --preset api-wrapper --auth oauth --yes
64
+ ```
65
+
66
+ Then:
67
+
68
+ ```bash
69
+ cd pay-tools
70
+ uv sync
71
+ uv run pay_tools # start the server
72
+ uv run pytest # green ✓
73
+ ```
74
+
75
+ ### Options
76
+
77
+ | Flag | Values | Default | Description |
78
+ |------|--------|---------|-------------|
79
+ | `--preset` `-p` | `minimal`, `api-wrapper`, `db`, `agent-tools` | `minimal` | Starting set of tools/resources/prompts |
80
+ | `--transport` `-t` | `streamable-http`, `stdio` | `streamable-http` | MCP transport |
81
+ | `--auth` `-a` | `none`, `oauth` | `none` | OAuth 2.1 resource server (RFC 9728) |
82
+ | `--package-name` | identifier | derived | Override the Python package name |
83
+ | `--output-dir` `-o` | path | `.` | Where to create the project |
84
+ | `--no-git` / `--git` | | `--git` | Initialise a git repo + first commit |
85
+ | `--no-install` / `--install` | | `--install` | Run `uv sync` after scaffolding |
86
+ | `--no-precommit` / `--precommit` | | `--precommit` | Install pre-commit hooks |
87
+ | `--force` | | off | Overwrite a non-empty target directory |
88
+ | `--yes` `-y` | | off | Accept all defaults; never prompt (CI) |
89
+
90
+ ### Presets
91
+
92
+ | Preset | What you get |
93
+ |--------|--------------|
94
+ | **minimal** | A clean typed server: one tool (structured output), a resource, a prompt. |
95
+ | **api-wrapper** | Wrap an HTTP/JSON API as MCP tools, with the network call isolated for easy mocking. |
96
+ | **db** | A SQLite-backed store exposed as CRUD tools (swap in your real DB). |
97
+ | **agent-tools** | A toolbox for agents: safe calculator, scratchpad memory, clock. |
98
+
99
+ ## What the generated project looks like
100
+
101
+ ```
102
+ my-server/
103
+ ├── src/my_server/
104
+ │ ├── server.py # FastMCP instance (+ /health, + auth when enabled)
105
+ │ ├── tools.py # your tools, resources, prompts
106
+ │ ├── settings.py # typed config (pydantic-settings)
107
+ │ ├── app.py # FastAPI host mounting MCP at /mcp
108
+ │ ├── auth.py # OAuth 2.1 resource server (only with --auth oauth)
109
+ │ └── __main__.py # `uv run my_server`
110
+ ├── tests/ # in-memory tests, green out of the box
111
+ ├── Dockerfile # uv-based image
112
+ ├── .github/workflows/ci.yml
113
+ ├── .pre-commit-config.yaml
114
+ ├── .env.example
115
+ └── pyproject.toml
116
+ ```
117
+
118
+ ## Requirements
119
+
120
+ - [`uv`](https://docs.astral.sh/uv/) (for `uvx` and the generated projects)
121
+ - Python 3.11+
122
+
123
+ ## Contributing
124
+
125
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Every release runs the full matrix —
126
+ generating a project for **each preset × auth mode**, then installing, linting,
127
+ type-checking and testing it — so the templates can't rot silently.
128
+
129
+ ## License
130
+
131
+ MIT © [Blaze](https://blaze.uz)
@@ -0,0 +1,85 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "create-mcp"
7
+ version = "0.1.0"
8
+ description = "The create-next-app for production MCP servers — scaffold a typed, tested, auth-ready Python MCP server in one command."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Blaze", email = "hello@blaze.uz" }]
14
+ keywords = [
15
+ "mcp",
16
+ "model-context-protocol",
17
+ "fastmcp",
18
+ "scaffold",
19
+ "generator",
20
+ "cli",
21
+ "fastapi",
22
+ "oauth",
23
+ "ai",
24
+ "agents",
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "Environment :: Console",
29
+ "Intended Audience :: Developers",
30
+ "License :: OSI Approved :: MIT License",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Topic :: Software Development :: Code Generators",
35
+ "Typing :: Typed",
36
+ ]
37
+ dependencies = [
38
+ "typer>=0.15",
39
+ "rich>=13.7",
40
+ "jinja2>=3.1",
41
+ ]
42
+
43
+ [project.urls]
44
+ Homepage = "https://github.com/blaze-uz/create-mcp"
45
+ Repository = "https://github.com/blaze-uz/create-mcp"
46
+ Issues = "https://github.com/blaze-uz/create-mcp/issues"
47
+
48
+ [project.scripts]
49
+ create-mcp = "create_mcp.cli:main"
50
+
51
+ [dependency-groups]
52
+ dev = [
53
+ "pytest>=8.3",
54
+ "ruff>=0.7",
55
+ "mypy>=1.13",
56
+ ]
57
+
58
+ [tool.hatch.build.targets.wheel]
59
+ packages = ["src/create_mcp"]
60
+
61
+ # Ship every template file (including the dot-* placeholders) inside the wheel.
62
+ [tool.hatch.build.targets.sdist]
63
+ include = ["src/create_mcp", "tests", "README.md", "LICENSE"]
64
+
65
+ [tool.ruff]
66
+ line-length = 100
67
+ target-version = "py311"
68
+ # The templates/ tree is rendered Jinja, not importable Python — don't lint it.
69
+ extend-exclude = ["src/create_mcp/templates"]
70
+
71
+ [tool.ruff.lint]
72
+ select = ["E", "F", "I", "UP", "B", "SIM", "C4"]
73
+
74
+ [tool.ruff.lint.per-file-ignores]
75
+ # Typer requires typer.Option/Argument calls in parameter defaults.
76
+ "src/create_mcp/cli.py" = ["B008"]
77
+
78
+ [tool.pytest.ini_options]
79
+ testpaths = ["tests"]
80
+ addopts = "-q"
81
+
82
+ [tool.mypy]
83
+ python_version = "3.11"
84
+ strict = true
85
+ exclude = ["src/create_mcp/templates"]
@@ -0,0 +1,10 @@
1
+ """create-mcp — scaffold production-ready Python MCP servers in one command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ # The FastMCP release the generated projects are pinned to / tested against.
8
+ FASTMCP_TARGET = "3.4"
9
+
10
+ __all__ = ["__version__", "FASTMCP_TARGET"]
@@ -0,0 +1,8 @@
1
+ """Enable ``python -m create_mcp``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ main()
@@ -0,0 +1,193 @@
1
+ """The ``create-mcp`` command-line interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ import typer
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ from rich.prompt import Confirm, Prompt
13
+ from rich.text import Text
14
+
15
+ from . import FASTMCP_TARGET, __version__
16
+ from .generator import GeneratorError, ProjectConfig, generate, to_package_name
17
+ from .presets import DEFAULT_PRESET, PRESETS, preset_choices
18
+
19
+ console = Console()
20
+ err_console = Console(stderr=True)
21
+
22
+ TRANSPORTS = ["streamable-http", "stdio"]
23
+ AUTH_MODES = ["none", "oauth"]
24
+
25
+
26
+ def _version_callback(value: bool) -> None:
27
+ if value:
28
+ console.print(f"create-mcp {__version__} [dim](targets FastMCP {FASTMCP_TARGET}.x)[/dim]")
29
+ raise typer.Exit()
30
+
31
+
32
+ def _run(cmd: list[str], cwd: Path) -> bool:
33
+ """Run a command, streaming nothing; return True on success, False otherwise."""
34
+ try:
35
+ subprocess.run(cmd, cwd=cwd, check=True, capture_output=True)
36
+ return True
37
+ except (subprocess.CalledProcessError, FileNotFoundError):
38
+ return False
39
+
40
+
41
+ def _maybe_git_init(target: Path) -> None:
42
+ if shutil.which("git") is None:
43
+ console.print(" [yellow]›[/yellow] git not found — skipping repository init")
44
+ return
45
+ if _run(["git", "init", "-q"], target) and _run(["git", "add", "-A"], target):
46
+ _run(["git", "commit", "-q", "-m", "Initial commit (create-mcp)"], target)
47
+ console.print(" [green]✓[/green] initialised git repository")
48
+ else:
49
+ console.print(" [yellow]›[/yellow] could not initialise git repository")
50
+
51
+
52
+ def _maybe_uv_sync(target: Path) -> None:
53
+ if shutil.which("uv") is None:
54
+ console.print(" [yellow]›[/yellow] uv not found — skipping dependency install")
55
+ return
56
+ console.print(" [dim]…[/dim] installing dependencies with uv")
57
+ if _run(["uv", "sync"], target):
58
+ console.print(" [green]✓[/green] installed dependencies (uv sync)")
59
+ else:
60
+ console.print(" [yellow]›[/yellow] uv sync failed — run it yourself later")
61
+
62
+
63
+ def _maybe_precommit(target: Path) -> None:
64
+ if shutil.which("git") is None or not (target / ".git").exists():
65
+ return
66
+ if shutil.which("uv") is not None and _run(["uv", "run", "pre-commit", "install"], target):
67
+ console.print(" [green]✓[/green] installed pre-commit hooks")
68
+
69
+
70
+ def _prompt_choice(label: str, choices: list[str], default: str) -> str:
71
+ return Prompt.ask(f"[bold]{label}[/bold]", choices=choices, default=default)
72
+
73
+
74
+ def create( # noqa: C901 - the CLI orchestration is intentionally linear
75
+ project_name: str = typer.Argument(None, help="Name of the project / directory to create."),
76
+ preset: str = typer.Option(
77
+ None, "--preset", "-p", help=f"Preset: {', '.join(preset_choices())}."
78
+ ),
79
+ transport: str = typer.Option(
80
+ None, "--transport", "-t", help="Transport: streamable-http or stdio."
81
+ ),
82
+ auth: str = typer.Option(
83
+ None, "--auth", "-a", help="Auth mode: none or oauth (OAuth 2.1 resource server)."
84
+ ),
85
+ package_name: str = typer.Option(
86
+ None, "--package-name", help="Override the derived Python package name."
87
+ ),
88
+ description: str = typer.Option(None, "--description", help="One-line project description."),
89
+ output_dir: Path = typer.Option(
90
+ None, "--output-dir", "-o", help="Directory to create the project in (default: cwd)."
91
+ ),
92
+ do_git: bool = typer.Option(True, "--git/--no-git", help="Initialise a git repository."),
93
+ do_install: bool = typer.Option(
94
+ True, "--install/--no-install", help="Run `uv sync` after scaffolding."
95
+ ),
96
+ do_precommit: bool = typer.Option(
97
+ True, "--precommit/--no-precommit", help="Install pre-commit hooks."
98
+ ),
99
+ force: bool = typer.Option(False, "--force", help="Overwrite a non-empty target directory."),
100
+ yes: bool = typer.Option(
101
+ False, "--yes", "-y", help="Accept all defaults; never prompt (CI / scripting)."
102
+ ),
103
+ _version: bool = typer.Option(
104
+ None, "--version", callback=_version_callback, is_eager=True, help="Show version."
105
+ ),
106
+ ) -> None:
107
+ """Scaffold a production-ready Python MCP server."""
108
+ interactive = not yes
109
+ output_dir = output_dir or Path.cwd()
110
+
111
+ def choose(label: str, choices: list[str], default: str, current: str | None) -> str:
112
+ if current is not None:
113
+ return current
114
+ return _prompt_choice(label, choices, default) if interactive else default
115
+
116
+ if not project_name:
117
+ if interactive:
118
+ project_name = Prompt.ask("[bold]Project name[/bold]", default="my-mcp-server")
119
+ else:
120
+ err_console.print("[red]error:[/red] project name is required with --yes")
121
+ raise typer.Exit(code=2)
122
+
123
+ preset = choose("Preset", preset_choices(), DEFAULT_PRESET, preset)
124
+ transport = choose("Transport", TRANSPORTS, "streamable-http", transport)
125
+ auth = choose("Auth", AUTH_MODES, "none", auth)
126
+
127
+ if interactive:
128
+ do_git = Confirm.ask("Initialise a git repository?", default=do_git)
129
+ do_install = Confirm.ask("Install dependencies now (uv sync)?", default=do_install)
130
+ if do_git:
131
+ do_precommit = Confirm.ask("Install pre-commit hooks?", default=do_precommit)
132
+
133
+ try:
134
+ config = ProjectConfig(
135
+ project_name=project_name,
136
+ preset=preset,
137
+ transport=transport,
138
+ auth=auth,
139
+ description=description or "",
140
+ )
141
+ if package_name:
142
+ # Validate + override the derived package name.
143
+ config.package_name = to_package_name(package_name)
144
+ target = output_dir / project_name
145
+ generate(config, target, force=force)
146
+ except GeneratorError as exc:
147
+ err_console.print(f"[red]error:[/red] {exc}")
148
+ raise typer.Exit(code=1) from exc
149
+
150
+ console.print()
151
+ console.print(
152
+ f"[green]✓[/green] Scaffolded [bold]{project_name}[/bold] "
153
+ f"[dim]({PRESETS[preset].title} · {transport} · auth: {auth})[/dim]"
154
+ )
155
+
156
+ if do_git:
157
+ _maybe_git_init(target)
158
+ if do_install:
159
+ _maybe_uv_sync(target)
160
+ if do_precommit:
161
+ _maybe_precommit(target)
162
+
163
+ _print_next_steps(config, target, output_dir)
164
+
165
+
166
+ def _print_next_steps(config: ProjectConfig, target: Path, output_dir: Path) -> None:
167
+ rel = target.relative_to(output_dir) if target.is_relative_to(output_dir) else target
168
+ pkg = config.package_name
169
+ run_cmd = f"uv run {pkg}"
170
+ body = Text()
171
+ body.append("Next steps\n\n", style="bold")
172
+ body.append(f" cd {rel}\n")
173
+ body.append(" uv sync ", style="cyan")
174
+ body.append("# install dependencies\n", style="dim")
175
+ body.append(f" {run_cmd}{' ' * max(1, 17 - len(run_cmd))}", style="cyan")
176
+ body.append(f"# run the server ({config.transport})\n", style="dim")
177
+ body.append(" uv run pytest ", style="cyan")
178
+ body.append("# run the test suite\n\n", style="dim")
179
+ body.append("Inspect it with the MCP Inspector:\n")
180
+ body.append(f" npx @modelcontextprotocol/inspector uv run {pkg}\n", style="cyan")
181
+ if config.auth_enabled:
182
+ body.append("\nAuth is on. ", style="bold yellow")
183
+ body.append("Set OAUTH_* vars in .env (see .env.example) and point them at\n")
184
+ body.append("your identity provider (Keycloak / WorkOS / Auth0 / Azure).")
185
+ console.print(Panel(body, border_style="green", expand=False))
186
+
187
+
188
+ def main() -> None:
189
+ typer.run(create)
190
+
191
+
192
+ if __name__ == "__main__":
193
+ main()