mcp-creator-python 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,51 @@
1
+ """Safe subprocess.run wrapper for uv build / uv publish."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+
9
+ def run_command(
10
+ cmd: list[str],
11
+ cwd: str | Path | None = None,
12
+ env: dict[str, str] | None = None,
13
+ timeout: int = 120,
14
+ ) -> dict:
15
+ """Run a subprocess and return structured output.
16
+
17
+ Returns:
18
+ dict with keys: success (bool), command (str), stdout, stderr, return_code
19
+ """
20
+ try:
21
+ result = subprocess.run(
22
+ cmd,
23
+ cwd=str(cwd) if cwd else None,
24
+ capture_output=True,
25
+ text=True,
26
+ timeout=timeout,
27
+ env=env,
28
+ )
29
+ return {
30
+ "success": result.returncode == 0,
31
+ "command": " ".join(cmd),
32
+ "stdout": result.stdout.strip(),
33
+ "stderr": result.stderr.strip(),
34
+ "return_code": result.returncode,
35
+ }
36
+ except FileNotFoundError:
37
+ return {
38
+ "success": False,
39
+ "command": " ".join(cmd),
40
+ "stdout": "",
41
+ "stderr": f"Command not found: {cmd[0]}. Make sure it is installed and on your PATH.",
42
+ "return_code": -1,
43
+ }
44
+ except subprocess.TimeoutExpired:
45
+ return {
46
+ "success": False,
47
+ "command": " ".join(cmd),
48
+ "stdout": "",
49
+ "stderr": f"Command timed out after {timeout}s.",
50
+ "return_code": -1,
51
+ }
File without changes
@@ -0,0 +1,94 @@
1
+ """Add a new tool to an existing scaffolded MCP server project."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from mcp_creator.services import codegen, file_writer
9
+
10
+
11
+ def add_tool(project_dir: str, tool: str) -> str:
12
+ """Add a new tool to an existing scaffolded MCP server.
13
+
14
+ Args:
15
+ project_dir: Absolute path to the project root.
16
+ tool: JSON string — a single tool definition:
17
+ {"name": "get_forecast", "description": "...",
18
+ "parameters": [...], "returns": "..."}
19
+
20
+ Returns:
21
+ JSON string with created/modified files and next steps.
22
+ """
23
+ tool_def = json.loads(tool)
24
+ tool_name = tool_def["name"]
25
+ project = Path(project_dir).resolve()
26
+
27
+ # Detect module name from the src/ directory
28
+ src_dir = project / "src"
29
+ if not src_dir.exists():
30
+ return json.dumps({
31
+ "success": False,
32
+ "error": f"No src/ directory found at {project}. Is this a scaffolded MCP project?",
33
+ })
34
+
35
+ module_dirs = [d for d in src_dir.iterdir() if d.is_dir() and not d.name.startswith("_")]
36
+ if not module_dirs:
37
+ return json.dumps({
38
+ "success": False,
39
+ "error": "No module directory found under src/.",
40
+ })
41
+
42
+ module_name = module_dirs[0].name
43
+ package_name = module_name.replace("_", "-")
44
+
45
+ # 1. Create tool module
46
+ tool_file = f"src/{module_name}/tools/{tool_name}.py"
47
+ tool_content = codegen.render_tool_module(package_name, tool_def)
48
+
49
+ # 2. Create service stub
50
+ service_file = f"src/{module_name}/services/{tool_name}_service.py"
51
+ service_content = codegen.render_service_module(tool_def)
52
+
53
+ # 3. Create test
54
+ test_file = f"tests/test_{tool_name}.py"
55
+ test_content = codegen.render_test_tool(package_name, tool_def)
56
+
57
+ files_to_write = {
58
+ tool_file: tool_content,
59
+ service_file: service_content,
60
+ test_file: test_content,
61
+ }
62
+
63
+ written = file_writer.write_project_files(project, files_to_write)
64
+
65
+ # 4. Inject import into server.py
66
+ server_path = project / f"src/{module_name}/server.py"
67
+ import_line = codegen.render_add_tool_import(package_name, tool_name)
68
+ import_ok = file_writer.inject_after_sentinel(
69
+ server_path, "# --- IMPORTS ---", import_line
70
+ )
71
+
72
+ # 5. Inject tool registration into server.py
73
+ registration = codegen.render_add_tool_registration(tool_def)
74
+ reg_ok = file_writer.inject_after_sentinel(
75
+ server_path, "# --- END TOOLS ---", ""
76
+ )
77
+ # Actually inject before END TOOLS
78
+ reg_ok = file_writer.inject_after_sentinel(
79
+ server_path, "# --- TOOLS ---", registration
80
+ )
81
+
82
+ result = {
83
+ "success": True,
84
+ "tool_name": tool_name,
85
+ "files_created": [tool_file, service_file, test_file],
86
+ "server_updated": import_ok and reg_ok,
87
+ "next_steps": [
88
+ f"Tool '{tool_name}' added to the project.",
89
+ f"Implement your logic in src/{module_name}/services/{tool_name}_service.py",
90
+ "Run 'pytest -v' to verify it registers correctly.",
91
+ ],
92
+ }
93
+
94
+ return json.dumps(result, indent=2)
@@ -0,0 +1,50 @@
1
+ """Build an MCP server package with uv build."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from mcp_creator.services.subprocess_runner import run_command
9
+
10
+
11
+ def build_package(project_dir: str) -> str:
12
+ """Build the MCP server package using uv build.
13
+
14
+ Args:
15
+ project_dir: Absolute path to the project root.
16
+
17
+ Returns:
18
+ JSON string with build result and next steps.
19
+ """
20
+ project = Path(project_dir).resolve()
21
+
22
+ if not (project / "pyproject.toml").exists():
23
+ return json.dumps({
24
+ "success": False,
25
+ "error": f"No pyproject.toml found at {project}.",
26
+ "next_steps": ["Make sure you're pointing to the right project directory."],
27
+ })
28
+
29
+ result = run_command(["uv", "build"], cwd=project)
30
+
31
+ if result["success"]:
32
+ # Find built files
33
+ dist_dir = project / "dist"
34
+ built_files = []
35
+ if dist_dir.exists():
36
+ built_files = [f.name for f in dist_dir.iterdir()]
37
+
38
+ result["built_files"] = built_files
39
+ result["next_steps"] = [
40
+ "Build successful!",
41
+ f"Built files: {', '.join(built_files)}",
42
+ "Next: use publish_package to upload to PyPI, or test locally first.",
43
+ ]
44
+ else:
45
+ result["next_steps"] = [
46
+ "Build failed. Check the error output above.",
47
+ "Common fixes: make sure uv is installed, dependencies are correct, and there are no syntax errors.",
48
+ ]
49
+
50
+ return json.dumps(result, indent=2)
@@ -0,0 +1,37 @@
1
+ """Check if a package name is available on PyPI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ from mcp_creator.services.pypi_client import check_name_available
8
+
9
+
10
+ def check_pypi_name(package_name: str) -> str:
11
+ """Check if a PyPI package name is available.
12
+
13
+ Args:
14
+ package_name: The name to check (e.g. "my-cool-mcp").
15
+
16
+ Returns:
17
+ JSON string with availability info and next steps.
18
+ """
19
+ result = check_name_available(package_name)
20
+
21
+ if result.get("available"):
22
+ result["next_steps"] = [
23
+ f'Great — "{package_name}" is available on PyPI!',
24
+ "Next: define what tools your MCP server should have, then use scaffold_server to create the project.",
25
+ ]
26
+ elif result.get("available") is False:
27
+ result["next_steps"] = [
28
+ f'"{package_name}" is already taken on PyPI.',
29
+ result.get("suggestion", 'Try adding "-mcp" or a unique prefix.'),
30
+ ]
31
+ else:
32
+ result["next_steps"] = [
33
+ "Could not check PyPI right now.",
34
+ result.get("suggestion", "Try again in a moment."),
35
+ ]
36
+
37
+ return json.dumps(result, indent=2)
@@ -0,0 +1,104 @@
1
+ """Check the user's environment for required tools and setup status."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import shutil
8
+
9
+ from mcp_creator.services.subprocess_runner import run_command
10
+
11
+
12
+ def check_setup() -> str:
13
+ """Check what tools and accounts the user already has set up.
14
+
15
+ Detects: Python, uv, gh CLI, PyPI token, git.
16
+ Returns a status for each so the AI can skip setup steps the user
17
+ has already completed.
18
+
19
+ Returns:
20
+ JSON string with setup status and next steps.
21
+ """
22
+ checks: dict[str, dict] = {}
23
+
24
+ # Python
25
+ python_result = run_command(["python3", "--version"])
26
+ checks["python"] = {
27
+ "installed": python_result["success"],
28
+ "version": python_result["stdout"] if python_result["success"] else None,
29
+ }
30
+
31
+ # uv
32
+ checks["uv"] = {
33
+ "installed": shutil.which("uv") is not None,
34
+ }
35
+ if checks["uv"]["installed"]:
36
+ uv_ver = run_command(["uv", "--version"])
37
+ checks["uv"]["version"] = uv_ver["stdout"] if uv_ver["success"] else None
38
+
39
+ # git
40
+ checks["git"] = {
41
+ "installed": shutil.which("git") is not None,
42
+ }
43
+
44
+ # gh CLI
45
+ gh_installed = shutil.which("gh") is not None
46
+ gh_authed = False
47
+ gh_username = None
48
+ if gh_installed:
49
+ gh_auth = run_command(["gh", "auth", "status"])
50
+ gh_authed = gh_auth["success"]
51
+ if gh_authed:
52
+ whoami = run_command(["gh", "api", "user", "--jq", ".login"])
53
+ gh_username = whoami["stdout"].strip() if whoami["success"] else None
54
+ checks["github_cli"] = {
55
+ "installed": gh_installed,
56
+ "authenticated": gh_authed,
57
+ "username": gh_username,
58
+ }
59
+
60
+ # PyPI token
61
+ has_token = bool(os.environ.get("UV_PUBLISH_TOKEN"))
62
+ checks["pypi_token"] = {
63
+ "configured": has_token,
64
+ }
65
+
66
+ # Determine what's missing
67
+ missing = []
68
+ if not checks["uv"]["installed"]:
69
+ missing.append("Install uv: https://docs.astral.sh/uv/getting-started/installation/")
70
+ if not checks["git"]["installed"]:
71
+ missing.append("Install git: https://git-scm.com/downloads")
72
+ if not checks["github_cli"]["installed"]:
73
+ missing.append("Install GitHub CLI: https://cli.github.com/")
74
+ elif not checks["github_cli"]["authenticated"]:
75
+ missing.append("Authenticate GitHub CLI: run 'gh auth login'")
76
+ if not checks["pypi_token"]["configured"]:
77
+ missing.append(
78
+ "Set up PyPI token: get one at https://pypi.org/manage/account/token/ "
79
+ "then export UV_PUBLISH_TOKEN=pypi-..."
80
+ )
81
+
82
+ all_ready = len(missing) == 0
83
+
84
+ result = {
85
+ "checks": checks,
86
+ "all_ready": all_ready,
87
+ "missing_steps": missing,
88
+ "next_steps": [],
89
+ }
90
+
91
+ if all_ready:
92
+ result["next_steps"] = [
93
+ "Everything is set up! The user is ready to create MCP servers.",
94
+ "Skip all setup instructions — go straight to asking what they want to build.",
95
+ "Use check_pypi_name to verify their package name, then scaffold_server to create it.",
96
+ ]
97
+ else:
98
+ result["next_steps"] = [
99
+ f"The user needs to complete {len(missing)} setup step(s) before they can fully publish.",
100
+ "Walk them through only the missing steps listed above.",
101
+ "They can still scaffold and develop locally even without all steps complete.",
102
+ ]
103
+
104
+ return json.dumps(result, indent=2)
@@ -0,0 +1,106 @@
1
+ """Persistent creator profile — remembers setup state and project history."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ PROFILE_DIR = Path.home() / ".mcp-creator"
9
+ PROFILE_FILE = PROFILE_DIR / "profile.json"
10
+
11
+
12
+ def _load_profile() -> dict:
13
+ """Load or initialize the creator profile."""
14
+ if PROFILE_FILE.exists():
15
+ return json.loads(PROFILE_FILE.read_text(encoding="utf-8"))
16
+ return {
17
+ "setup_complete": False,
18
+ "github_username": None,
19
+ "pypi_username": None,
20
+ "default_output_dir": None,
21
+ "projects": [],
22
+ }
23
+
24
+
25
+ def _save_profile(profile: dict) -> None:
26
+ """Save the creator profile to disk."""
27
+ PROFILE_DIR.mkdir(parents=True, exist_ok=True)
28
+ PROFILE_FILE.write_text(json.dumps(profile, indent=2), encoding="utf-8")
29
+
30
+
31
+ def get_creator_profile() -> str:
32
+ """Load the creator's profile — their setup status and project history.
33
+
34
+ Call this at the start of every session to know the user's state.
35
+ If the profile exists, the user is returning — skip onboarding.
36
+
37
+ Returns:
38
+ JSON string with profile data and next steps.
39
+ """
40
+ profile = _load_profile()
41
+
42
+ if profile.get("setup_complete") and profile.get("projects"):
43
+ project_names = [p["name"] for p in profile["projects"]]
44
+ next_steps = [
45
+ f"Returning creator with {len(profile['projects'])} project(s): {', '.join(project_names)}.",
46
+ "Skip all setup — ask what they want to build next.",
47
+ "They can also add tools to existing projects or publish updates.",
48
+ ]
49
+ elif profile.get("setup_complete"):
50
+ next_steps = [
51
+ "Setup is complete but no projects yet.",
52
+ "Skip setup instructions — go straight to asking what they want to build.",
53
+ ]
54
+ else:
55
+ next_steps = [
56
+ "New creator — run check_setup to see what they need to install.",
57
+ "Walk them through any missing setup steps.",
58
+ ]
59
+
60
+ return json.dumps({"profile": profile, "next_steps": next_steps}, indent=2)
61
+
62
+
63
+ def update_creator_profile(
64
+ setup_complete: bool | None = None,
65
+ github_username: str | None = None,
66
+ pypi_username: str | None = None,
67
+ default_output_dir: str | None = None,
68
+ add_project: str | None = None,
69
+ ) -> str:
70
+ """Update the creator's profile after setup steps or project creation.
71
+
72
+ Args:
73
+ setup_complete: Mark setup as done (all tools installed, accounts ready).
74
+ github_username: Their GitHub username.
75
+ pypi_username: Their PyPI username.
76
+ default_output_dir: Where they want projects created by default.
77
+ add_project: JSON string of a project to add to history:
78
+ {"name": "my-mcp", "pypi_url": "...", "github_url": "...", "description": "..."}
79
+
80
+ Returns:
81
+ JSON string with updated profile.
82
+ """
83
+ profile = _load_profile()
84
+
85
+ if setup_complete is not None:
86
+ profile["setup_complete"] = setup_complete
87
+ if github_username is not None:
88
+ profile["github_username"] = github_username
89
+ if pypi_username is not None:
90
+ profile["pypi_username"] = pypi_username
91
+ if default_output_dir is not None:
92
+ profile["default_output_dir"] = default_output_dir
93
+ if add_project is not None:
94
+ project = json.loads(add_project)
95
+ # Don't duplicate
96
+ existing_names = [p["name"] for p in profile["projects"]]
97
+ if project["name"] not in existing_names:
98
+ profile["projects"].append(project)
99
+
100
+ _save_profile(profile)
101
+
102
+ return json.dumps({
103
+ "success": True,
104
+ "profile": profile,
105
+ "next_steps": ["Profile updated."],
106
+ }, indent=2)
@@ -0,0 +1,95 @@
1
+ """Generate a LAUNCHGUIDE.md for MCP Marketplace submission."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from mcp_creator.services.file_writer import write_project_files
9
+
10
+
11
+ LAUNCHGUIDE_TEMPLATE = """\
12
+ # {package_name}
13
+
14
+ ## Tagline
15
+ {tagline}
16
+
17
+ ## Description
18
+ {description}
19
+
20
+ ## Setup Requirements
21
+ {setup_requirements}
22
+
23
+ ## Category
24
+ {category}
25
+
26
+ ## Features
27
+ {features}
28
+
29
+ ## Getting Started
30
+ {getting_started}
31
+
32
+ ## Tags
33
+ {tags}
34
+
35
+ ## Documentation URL
36
+ {docs_url}
37
+ """
38
+
39
+
40
+ def generate_launchguide(
41
+ project_dir: str,
42
+ package_name: str,
43
+ tagline: str,
44
+ description: str,
45
+ category: str,
46
+ features: str,
47
+ tools_summary: str,
48
+ tags: str,
49
+ setup_requirements: str = "No environment variables required.",
50
+ docs_url: str = "",
51
+ ) -> str:
52
+ """Generate a LAUNCHGUIDE.md for MCP Marketplace submission.
53
+
54
+ Args:
55
+ project_dir: Absolute path to the project root.
56
+ package_name: PyPI package name.
57
+ tagline: One-liner (max 100 chars).
58
+ description: What the server does, how it works, who it's for.
59
+ category: One of: Developer Tools, Data & Analytics, Productivity, etc.
60
+ features: Bullet-point features (one per line, prefixed with "- "). Max 30 items.
61
+ tools_summary: Tool descriptions for Getting Started (one per line).
62
+ tags: Comma-separated tags. Max 30.
63
+ setup_requirements: Env vars or setup steps (default: none required).
64
+ docs_url: Link to docs or README.
65
+
66
+ Returns:
67
+ JSON string with file path and next steps.
68
+ """
69
+ project = Path(project_dir).resolve()
70
+
71
+ content = LAUNCHGUIDE_TEMPLATE.format(
72
+ package_name=package_name,
73
+ tagline=tagline,
74
+ description=description,
75
+ setup_requirements=setup_requirements,
76
+ category=category,
77
+ features=features,
78
+ getting_started=tools_summary,
79
+ tags=tags,
80
+ docs_url=docs_url or f"https://pypi.org/project/{package_name}/",
81
+ )
82
+
83
+ written = write_project_files(project, {"LAUNCHGUIDE.md": content})
84
+
85
+ result = {
86
+ "success": True,
87
+ "file": str(project / "LAUNCHGUIDE.md"),
88
+ "next_steps": [
89
+ "LAUNCHGUIDE.md created!",
90
+ "Review it and make any final edits.",
91
+ f"Submit to MCP Marketplace at https://mcp-marketplace.io with this file.",
92
+ ],
93
+ }
94
+
95
+ return json.dumps(result, indent=2)
@@ -0,0 +1,72 @@
1
+ """Publish an MCP server package to PyPI with uv publish."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+
9
+ from mcp_creator.services.subprocess_runner import run_command
10
+
11
+
12
+ def publish_package(project_dir: str, token: str | None = None) -> str:
13
+ """Publish the built package to PyPI using uv publish.
14
+
15
+ Args:
16
+ project_dir: Absolute path to the project root.
17
+ token: Optional PyPI API token. If not provided, uses UV_PUBLISH_TOKEN
18
+ environment variable.
19
+
20
+ Returns:
21
+ JSON string with publish result and next steps.
22
+ """
23
+ project = Path(project_dir).resolve()
24
+ dist_dir = project / "dist"
25
+
26
+ if not dist_dir.exists() or not list(dist_dir.iterdir()):
27
+ return json.dumps({
28
+ "success": False,
29
+ "error": "No dist/ directory or it's empty. Run build_package first.",
30
+ "next_steps": ["Use build_package to build the project before publishing."],
31
+ })
32
+
33
+ # Build env with token if provided
34
+ env = dict(os.environ)
35
+ if token:
36
+ env["UV_PUBLISH_TOKEN"] = token
37
+
38
+ if not token and "UV_PUBLISH_TOKEN" not in env:
39
+ return json.dumps({
40
+ "success": False,
41
+ "error": "No PyPI token found. Set UV_PUBLISH_TOKEN or pass a token.",
42
+ "next_steps": [
43
+ "Get a PyPI API token at https://pypi.org/manage/account/token/",
44
+ "Then either: export UV_PUBLISH_TOKEN=pypi-... or pass it to this tool.",
45
+ ],
46
+ })
47
+
48
+ result = run_command(["uv", "publish"], cwd=project, env=env)
49
+
50
+ if result["success"]:
51
+ # Try to extract package name from pyproject.toml
52
+ pyproject_path = project / "pyproject.toml"
53
+ package_name = "your-package"
54
+ if pyproject_path.exists():
55
+ for line in pyproject_path.read_text().splitlines():
56
+ if line.strip().startswith("name"):
57
+ package_name = line.split("=")[1].strip().strip('"').strip("'")
58
+ break
59
+
60
+ result["next_steps"] = [
61
+ f"Published to PyPI! Install with: pip install {package_name}",
62
+ f"View at: https://pypi.org/project/{package_name}/",
63
+ "Next: use setup_github to create a GitHub repo and push the code.",
64
+ "Then use generate_launchguide to create a LAUNCHGUIDE.md for marketplace submission.",
65
+ ]
66
+ else:
67
+ result["next_steps"] = [
68
+ "Publish failed. Check the error output.",
69
+ "Common issues: invalid token, package name conflict, or network error.",
70
+ ]
71
+
72
+ return json.dumps(result, indent=2)