Compare commits

..
Author SHA1 Message Date
David Soria Parra
c79927defb
v1.0.1 2024-11-18 21:29:40 +00:00
8 changed files with 167 additions and 382 deletions

View file

@ -1,43 +0,0 @@
# Contributing
Thank you for your interest in contributing to `create-mcp-server`! This tool helps developers quickly scaffold new MCP (Model Context Protocol) servers.
## Getting Started
1. Fork the repository
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/create-python-server.git`
3. Install dependencies: `uv sync --dev --all-extras`
## Development
- Make your changes in the `src` directory
- Test your changes by creating a new server: `uv run -m create_mcp_server test-server`
- Run type checking: `uv run pyright`
- Run linting: `uv run ruff check .`
## Pull Requests
1. Create a new branch for your changes
2. Make your changes
3. Ensure type checking and linting pass
4. Test server creation using your changes
5. Submit a pull request with a clear description of your changes
## Code of Conduct
This project follows our [Code of Conduct](CODE_OF_CONDUCT.md). Please review it before contributing.
## Reporting Issues
- Use the [GitHub issue tracker](https://github.com/modelcontextprotocol/create-python-server/issues)
- Provide clear reproduction steps
- Include relevant system information
- Specify the version you're using
## Security Issues
Please review our [Security Policy](SECURITY.md) for reporting security vulnerabilities.
## License
By contributing, you agree that your contributions will be licensed under the MIT License.

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Anthropic, PBC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,14 +0,0 @@
# Security Policy
Thank you for helping us keep this software secure.
## Reporting Security Issues
This software is maintained by [Anthropic](https://www.anthropic.com/) as part of the Model Context Protocol project.
The security of our systems and user data is Anthropic’s top priority. We appreciate the work of security researchers acting in good faith in identifying and reporting potential vulnerabilities.
Our security program is managed on HackerOne and we ask that any validated vulnerability in this functionality be reported through their [submission form](https://hackerone.com/anthropic-vdp/reports/new?type=team&report_type=vulnerability).
## Vulnerability Disclosure Program
Our Vulnerability Program Guidelines are defined on our [HackerOne program page](https://hackerone.com/anthropic-vdp).

View file

@ -1,22 +1,9 @@
[project] [project]
name = "create-mcp-server" name = "create-mcp-server"
version = "1.0.6.dev0" version = "1.0.1"
description = "Create an Model Context Protocol server project from a template." description = "Create an MCP server project from a template."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
authors = [{ name = "Anthropic, PBC." }]
maintainers = [
{ name = "David Soria Parra", email = "davidsp@anthropic.com" },
{ name = "Justin Spahr-Summers", email = "justin@anthropic.com" },
]
license = { text = "MIT" }
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
]
dependencies = [ dependencies = [
"click>=8.1.7", "click>=8.1.7",
"jinja2>=3.1.4", "jinja2>=3.1.4",
@ -27,10 +14,6 @@ dependencies = [
[project.scripts] [project.scripts]
create-mcp-server = "create_mcp_server:main" create-mcp-server = "create_mcp_server:main"
[project.urls]
Homepage = "https://modelcontextprotocol.io"
Repository = "https://github.com/modelcontextprotocol/create-python-server"
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
@ -39,4 +22,7 @@ build-backend = "hatchling.build"
artifacts = ["src/create_mcp_server/template"] artifacts = ["src/create_mcp_server/template"]
[tool.uv] [tool.uv]
dev-dependencies = ["pyright>=1.1.389", "ruff>=0.7.4"] dev-dependencies = [
"pyright>=1.1.389",
"ruff>=0.7.4",
]

View file

@ -5,26 +5,11 @@ import sys
from pathlib import Path from pathlib import Path
import click import click
import toml
from packaging.version import parse from packaging.version import parse
MIN_UV_VERSION = "0.4.10" MIN_UV_VERSION = "0.4.10"
class PyProject:
def __init__(self, path: Path):
self.data = toml.load(path)
@property
def name(self) -> str:
return self.data["project"]["name"]
@property
def first_binary(self) -> str | None:
scripts = self.data["project"].get("scripts", {})
return next(iter(scripts.keys()), None)
def check_uv_version(required_version: str) -> str | None: def check_uv_version(required_version: str) -> str | None:
"""Check if uv is installed and has minimum version""" """Check if uv is installed and has minimum version"""
try: try:
@ -120,9 +105,7 @@ def get_package_directory(path: Path) -> Path:
return src_dir.parent return src_dir.parent
def copy_template( def copy_template(path: Path, name: str, version: str = "0.1.0") -> None:
path: Path, name: str, description: str, version: str = "0.1.0"
) -> None:
"""Copy template files into src/<project_name>""" """Copy template files into src/<project_name>"""
template_dir = Path(__file__).parent / "template" template_dir = Path(__file__).parent / "template"
@ -133,28 +116,18 @@ def copy_template(
env = Environment(loader=FileSystemLoader(str(template_dir))) env = Environment(loader=FileSystemLoader(str(template_dir)))
files = [ files = [
("__init__.py.jinja2", "__init__.py", target_dir), ("__init__.py.jinja2", "__init__.py"),
("server.py.jinja2", "server.py", target_dir), ("server.py.jinja2", "server.py"),
("README.md.jinja2", "README.md", path),
] ]
pyproject = PyProject(path / "pyproject.toml") template_vars = {"server_name": name, "server_version": version}
bin_name = pyproject.first_binary
template_vars = {
"binary_name": bin_name,
"server_name": name,
"server_version": version,
"server_description": description,
"server_directory": str(path.resolve()),
}
try: try:
for template_file, output_file, output_dir in files: for template_file, output_file in files:
template = env.get_template(template_file) template = env.get_template(template_file)
rendered = template.render(**template_vars) rendered = template.render(**template_vars)
out_path = output_dir / output_file out_path = target_dir / output_file
out_path.write_text(rendered) out_path.write_text(rendered)
except Exception as e: except Exception as e:
@ -163,7 +136,7 @@ def copy_template(
def create_project( def create_project(
path: Path, name: str, description: str, version: str, use_claude: bool = True path: Path, name: str, version: str, use_claude: bool = True
) -> None: ) -> None:
"""Create a new project using uv""" """Create a new project using uv"""
path.mkdir(parents=True, exist_ok=True) path.mkdir(parents=True, exist_ok=True)
@ -185,7 +158,7 @@ def create_project(
click.echo("❌ Error: Failed to add mcp dependency.", err=True) click.echo("❌ Error: Failed to add mcp dependency.", err=True)
sys.exit(1) sys.exit(1)
copy_template(path, name, description, version) copy_template(path, name, version)
# Check if Claude.app is available # Check if Claude.app is available
if ( if (
@ -340,9 +313,7 @@ def main(
click.echo("❌ Error: Invalid path. Project creation aborted.", err=True) click.echo("❌ Error: Invalid path. Project creation aborted.", err=True)
return 1 return 1
project_path = project_path.resolve() create_project(project_path, name, version, claudeapp)
create_project(project_path, name, description, version, claudeapp)
update_pyproject_settings(project_path, version, description) update_pyproject_settings(project_path, version, description)
return 0 return 0

View file

@ -1,119 +0,0 @@
# {{server_name}} MCP server
{{server_description}}
## Components
### Resources
The server implements a simple note storage system with:
- Custom note:// URI scheme for accessing individual notes
- Each note resource has a name, description and text/plain mimetype
### Prompts
The server provides a single prompt:
- summarize-notes: Creates summaries of all stored notes
- Optional "style" argument to control detail level (brief/detailed)
- Generates prompt combining all current notes with style preference
### Tools
The server implements one tool:
- add-note: Adds a new note to the server
- Takes "name" and "content" as required string arguments
- Updates server state and notifies clients of resource changes
## Configuration
[TODO: Add configuration details specific to your implementation]
## Quickstart
### Install
#### Claude Desktop
On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
<details>
<summary>Development/Unpublished Servers Configuration</summary>
```
"mcpServers": {
"{{server_name}}": {
"command": "uv",
"args": [
"--directory",
"{{server_directory}}",
"run",
"{{server_name}}"
]
}
}
```
</details>
<details>
<summary>Published Servers Configuration</summary>
```
"mcpServers": {
"{{server_name}}": {
"command": "uvx",
"args": [
"{{server_name}}"
]
}
}
```
</details>
## Development
### Building and Publishing
To prepare the package for distribution:
1. Sync dependencies and update lockfile:
```bash
uv sync
```
2. Build package distributions:
```bash
uv build
```
This will create source and wheel distributions in the `dist/` directory.
3. Publish to PyPI:
```bash
uv publish
```
Note: You'll need to set PyPI credentials via environment variables or command flags:
- Token: `--token` or `UV_PUBLISH_TOKEN`
- Or username/password: `--username`/`UV_PUBLISH_USERNAME` and `--password`/`UV_PUBLISH_PASSWORD`
### Debugging
Since MCP servers run over stdio, debugging can be challenging. For the best debugging
experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).
{% if binary_name %}
You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:
```bash
npx @modelcontextprotocol/inspector uv --directory {{server_directory}} run {{binary_name}}
```
{% else %}
This project does not contain a script to start. However, you can still use the MCP Inspector for debugging. To start the inspector, use the following general command structure:
```bash
npx @modelcontextprotocol/inspector <command-to-run-your-server>
```
Replace `<command-to-run-your-server>` with the specific command you use to start your MCP server.
{% endif %}
Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.

View file

@ -6,29 +6,47 @@ from mcp.server import NotificationOptions, Server
from pydantic import AnyUrl from pydantic import AnyUrl
import mcp.server.stdio import mcp.server.stdio
# Store notes as a simple key-value dict to demonstrate state management
notes: dict[str, str] = {}
server = Server("{{server_name}}") class McpServer(Server):
"""
Example MCP server demonstrating the three core primitives:
1. Resources - Simple key-value notes storage accessed via custom URIs
2. Prompts - Configurable summarization prompt for notes
3. Tools - Ability to add new notes programmatically
@server.list_resources() This server shows how to:
async def handle_list_resources() -> list[types.Resource]: - Implement resource listing and reading with custom URI schemes
- Create parameterized prompts that can access server state
- Define tools with JSON schema validation
- Send notifications when server state changes
"""
def __init__(self):
super().__init__("{{server_name}}")
# Store notes as a simple key-value dict to demonstrate state management
self.notes: dict[str, str] = {}
# RESOURCE HANDLERS
# Resources allow clients to discover and read content from the server
@self.list_resources()
async def handle_list_resources() -> list[types.Resource]:
""" """
List available note resources. List available note resources.
Each note is exposed as a resource with a custom note:// URI scheme. Each note is exposed as a resource with a custom note:// URI scheme.
""" """
return [ return [
types.Resource( types.Resource(
uri=AnyUrl(f"note://internal/{name}"), uri=AnyUrl(f"note:///{name}"),
name=f"Note: {name}", name=f"Note: {name}",
description=f"A simple note named {name}", description=f"A simple note named {name}",
mimeType="text/plain", mimeType="text/plain",
) )
for name in notes for name in self.notes
] ]
@server.read_resource() @self.read_resource()
async def handle_read_resource(uri: AnyUrl) -> str: async def handle_read_resource(uri: AnyUrl) -> str:
""" """
Read a specific note's content by its URI. Read a specific note's content by its URI.
The note name is extracted from the URI host component. The note name is extracted from the URI host component.
@ -39,11 +57,13 @@ async def handle_read_resource(uri: AnyUrl) -> str:
name = uri.path name = uri.path
if name is not None: if name is not None:
name = name.lstrip("/") name = name.lstrip("/")
return notes[name] return self.notes[name]
raise ValueError(f"Note not found: {name}") raise ValueError(f"Note not found: {name}")
@server.list_prompts() # PROMPT HANDLERS
async def handle_list_prompts() -> list[types.Prompt]: # Prompts define templates that can be filled with server state
@self.list_prompts()
async def handle_list_prompts() -> list[types.Prompt]:
""" """
List available prompts. List available prompts.
Each prompt can have optional arguments to customize its behavior. Each prompt can have optional arguments to customize its behavior.
@ -62,10 +82,10 @@ async def handle_list_prompts() -> list[types.Prompt]:
) )
] ]
@server.get_prompt() @self.get_prompt()
async def handle_get_prompt( async def handle_get_prompt(
name: str, arguments: dict[str, str] | None name: str, arguments: dict[str, str] | None
) -> types.GetPromptResult: ) -> types.GetPromptResult:
""" """
Generate a prompt by combining arguments with server state. Generate a prompt by combining arguments with server state.
The prompt includes all current notes and can be customized via arguments. The prompt includes all current notes and can be customized via arguments.
@ -86,15 +106,17 @@ async def handle_get_prompt(
text=f"Here are the current notes to summarize:{detail_prompt}\n\n" text=f"Here are the current notes to summarize:{detail_prompt}\n\n"
+ "\n".join( + "\n".join(
f"- {name}: {content}" f"- {name}: {content}"
for name, content in notes.items() for name, content in self.notes.items()
), ),
), ),
) )
], ],
) )
@server.list_tools() # TOOL HANDLERS
async def handle_list_tools() -> list[types.Tool]: # Tools allow clients to modify server state in controlled ways
@self.list_tools()
async def handle_list_tools() -> list[types.Tool]:
""" """
List available tools. List available tools.
Each tool specifies its arguments using JSON Schema validation. Each tool specifies its arguments using JSON Schema validation.
@ -114,10 +136,10 @@ async def handle_list_tools() -> list[types.Tool]:
) )
] ]
@server.call_tool() @self.call_tool()
async def handle_call_tool( async def handle_call_tool(
name: str, arguments: dict | None name: str, arguments: dict | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
""" """
Handle tool execution requests. Handle tool execution requests.
Tools can modify server state and notify clients of changes. Tools can modify server state and notify clients of changes.
@ -135,10 +157,10 @@ async def handle_call_tool(
raise ValueError("Missing name or content") raise ValueError("Missing name or content")
# Update server state # Update server state
notes[note_name] = content self.notes[note_name] = content
# Notify clients that resources have changed # Notify clients that resources have changed
await server.request_context.session.send_resource_list_changed() await self.request_context.session.send_resource_list_changed()
return [ return [
types.TextContent( types.TextContent(
@ -147,7 +169,10 @@ async def handle_call_tool(
) )
] ]
async def main(): async def main():
server = McpServer()
# Run the server using stdin/stdout streams # Run the server using stdin/stdout streams
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run( await server.run(

2
uv.lock generated
View file

@ -24,7 +24,7 @@ wheels = [
[[package]] [[package]]
name = "create-mcp-server" name = "create-mcp-server"
version = "1.0.4.dev0" version = "1.0.1.dev0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },