diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..fe64dba
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,43 @@
+# 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.
diff --git a/pyproject.toml b/pyproject.toml
index cc6c4ac..6293767 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "create-mcp-server"
-version = "1.0.4"
+version = "1.0.6.dev0"
description = "Create an Model Context Protocol server project from a template."
readme = "README.md"
requires-python = ">=3.10"
diff --git a/src/create_mcp_server/__init__.py b/src/create_mcp_server/__init__.py
index c7af81d..b54038d 100644
--- a/src/create_mcp_server/__init__.py
+++ b/src/create_mcp_server/__init__.py
@@ -24,6 +24,7 @@ class PyProject:
scripts = self.data["project"].get("scripts", {})
return next(iter(scripts.keys()), None)
+
def check_uv_version(required_version: str) -> str | None:
"""Check if uv is installed and has minimum version"""
try:
@@ -255,8 +256,6 @@ def check_package_name(name: str) -> bool:
return True
-
-
@click.command()
@click.option(
"--path",
diff --git a/src/create_mcp_server/template/README.md.jinja2 b/src/create_mcp_server/template/README.md.jinja2
index 7abfe66..c35655d 100644
--- a/src/create_mcp_server/template/README.md.jinja2
+++ b/src/create_mcp_server/template/README.md.jinja2
@@ -24,14 +24,22 @@ The server implements one tool:
- Takes "name" and "content" as required string arguments
- Updates server state and notifies clients of resource changes
-## Install
+## Configuration
-### Claude Desktop
+[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`
-```
+
+ Development/Unpublished Servers Configuration
+ ```
"mcpServers": {
"{{server_name}}": {
"command": "uv",
@@ -43,9 +51,51 @@ On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
]
}
}
+ ```
+
+
+
+ Published Servers Configuration
+ ```
+ "mcpServers": {
+ "{{server_name}}": {
+ "command": "uvx",
+ "args": [
+ "{{server_name}}"
+ ]
+ }
+ }
+ ```
+
+
+## Development
+
+### Building and Publishing
+
+To prepare the package for distribution:
+
+1. Sync dependencies and update lockfile:
+```bash
+uv sync
```
-## Develop and Debug
+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).
diff --git a/src/create_mcp_server/template/server.py.jinja2 b/src/create_mcp_server/template/server.py.jinja2
index bd07d11..bc39f5c 100644
--- a/src/create_mcp_server/template/server.py.jinja2
+++ b/src/create_mcp_server/template/server.py.jinja2
@@ -6,173 +6,148 @@ from mcp.server import NotificationOptions, Server
from pydantic import AnyUrl
import mcp.server.stdio
+# Store notes as a simple key-value dict to demonstrate state management
+notes: dict[str, str] = {}
-class McpServer(Server):
+server = Server("{{server_name}}")
+
+@server.list_resources()
+async def handle_list_resources() -> list[types.Resource]:
"""
- 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
-
- This server shows how to:
- - 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
+ List available note resources.
+ Each note is exposed as a resource with a custom note:// URI scheme.
"""
+ return [
+ types.Resource(
+ uri=AnyUrl(f"note://internal/{name}"),
+ name=f"Note: {name}",
+ description=f"A simple note named {name}",
+ mimeType="text/plain",
+ )
+ for name in notes
+ ]
- def __init__(self):
- super().__init__("{{server_name}}")
+@server.read_resource()
+async def handle_read_resource(uri: AnyUrl) -> str:
+ """
+ Read a specific note's content by its URI.
+ The note name is extracted from the URI host component.
+ """
+ if uri.scheme != "note":
+ raise ValueError(f"Unsupported URI scheme: {uri.scheme}")
- # Store notes as a simple key-value dict to demonstrate state management
- self.notes: dict[str, str] = {}
+ name = uri.path
+ if name is not None:
+ name = name.lstrip("/")
+ return notes[name]
+ raise ValueError(f"Note not found: {name}")
- # 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.
- Each note is exposed as a resource with a custom note:// URI scheme.
- """
- return [
- types.Resource(
- uri=AnyUrl(f"note:///{name}"),
- name=f"Note: {name}",
- description=f"A simple note named {name}",
- mimeType="text/plain",
+@server.list_prompts()
+async def handle_list_prompts() -> list[types.Prompt]:
+ """
+ List available prompts.
+ Each prompt can have optional arguments to customize its behavior.
+ """
+ return [
+ types.Prompt(
+ name="summarize-notes",
+ description="Creates a summary of all notes",
+ arguments=[
+ types.PromptArgument(
+ name="style",
+ description="Style of the summary (brief/detailed)",
+ required=False,
)
- for name in self.notes
- ]
+ ],
+ )
+ ]
- @self.read_resource()
- async def handle_read_resource(uri: AnyUrl) -> str:
- """
- Read a specific note's content by its URI.
- The note name is extracted from the URI host component.
- """
- if uri.scheme != "note":
- raise ValueError(f"Unsupported URI scheme: {uri.scheme}")
+@server.get_prompt()
+async def handle_get_prompt(
+ name: str, arguments: dict[str, str] | None
+) -> types.GetPromptResult:
+ """
+ Generate a prompt by combining arguments with server state.
+ The prompt includes all current notes and can be customized via arguments.
+ """
+ if name != "summarize-notes":
+ raise ValueError(f"Unknown prompt: {name}")
- name = uri.path
- if name is not None:
- name = name.lstrip("/")
- return self.notes[name]
- raise ValueError(f"Note not found: {name}")
+ style = (arguments or {}).get("style", "brief")
+ detail_prompt = " Give extensive details." if style == "detailed" else ""
- # PROMPT HANDLERS
- # Prompts define templates that can be filled with server state
- @self.list_prompts()
- async def handle_list_prompts() -> list[types.Prompt]:
- """
- List available prompts.
- Each prompt can have optional arguments to customize its behavior.
- """
- return [
- types.Prompt(
- name="summarize-notes",
- description="Creates a summary of all notes",
- arguments=[
- types.PromptArgument(
- name="style",
- description="Style of the summary (brief/detailed)",
- required=False,
- )
- ],
- )
- ]
-
- @self.get_prompt()
- async def handle_get_prompt(
- name: str, arguments: dict[str, str] | None
- ) -> types.GetPromptResult:
- """
- Generate a prompt by combining arguments with server state.
- The prompt includes all current notes and can be customized via arguments.
- """
- if name != "summarize-notes":
- raise ValueError(f"Unknown prompt: {name}")
-
- style = (arguments or {}).get("style", "brief")
- detail_prompt = " Give extensive details." if style == "detailed" else ""
-
- return types.GetPromptResult(
- description="Summarize the current notes",
- messages=[
- types.PromptMessage(
- role="user",
- content=types.TextContent(
- type="text",
- text=f"Here are the current notes to summarize:{detail_prompt}\n\n"
- + "\n".join(
- f"- {name}: {content}"
- for name, content in self.notes.items()
- ),
- ),
- )
- ],
- )
-
- # TOOL HANDLERS
- # Tools allow clients to modify server state in controlled ways
- @self.list_tools()
- async def handle_list_tools() -> list[types.Tool]:
- """
- List available tools.
- Each tool specifies its arguments using JSON Schema validation.
- """
- return [
- types.Tool(
- name="add-note",
- description="Add a new note",
- inputSchema={
- "type": "object",
- "properties": {
- "name": {"type": "string"},
- "content": {"type": "string"},
- },
- "required": ["name", "content"],
- },
- )
- ]
-
- @self.call_tool()
- async def handle_call_tool(
- name: str, arguments: dict | None
- ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
- """
- Handle tool execution requests.
- Tools can modify server state and notify clients of changes.
- """
- if name != "add-note":
- raise ValueError(f"Unknown tool: {name}")
-
- if not arguments:
- raise ValueError("Missing arguments")
-
- note_name = arguments.get("name")
- content = arguments.get("content")
-
- if not note_name or not content:
- raise ValueError("Missing name or content")
-
- # Update server state
- self.notes[note_name] = content
-
- # Notify clients that resources have changed
- await self.request_context.session.send_resource_list_changed()
-
- return [
- types.TextContent(
+ return types.GetPromptResult(
+ description="Summarize the current notes",
+ messages=[
+ types.PromptMessage(
+ role="user",
+ content=types.TextContent(
type="text",
- text=f"Added note '{note_name}' with content: {content}",
- )
- ]
+ text=f"Here are the current notes to summarize:{detail_prompt}\n\n"
+ + "\n".join(
+ f"- {name}: {content}"
+ for name, content in notes.items()
+ ),
+ ),
+ )
+ ],
+ )
+@server.list_tools()
+async def handle_list_tools() -> list[types.Tool]:
+ """
+ List available tools.
+ Each tool specifies its arguments using JSON Schema validation.
+ """
+ return [
+ types.Tool(
+ name="add-note",
+ description="Add a new note",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "name": {"type": "string"},
+ "content": {"type": "string"},
+ },
+ "required": ["name", "content"],
+ },
+ )
+ ]
+
+@server.call_tool()
+async def handle_call_tool(
+ name: str, arguments: dict | None
+) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
+ """
+ Handle tool execution requests.
+ Tools can modify server state and notify clients of changes.
+ """
+ if name != "add-note":
+ raise ValueError(f"Unknown tool: {name}")
+
+ if not arguments:
+ raise ValueError("Missing arguments")
+
+ note_name = arguments.get("name")
+ content = arguments.get("content")
+
+ if not note_name or not content:
+ raise ValueError("Missing name or content")
+
+ # Update server state
+ notes[note_name] = content
+
+ # Notify clients that resources have changed
+ await server.request_context.session.send_resource_list_changed()
+
+ return [
+ types.TextContent(
+ type="text",
+ text=f"Added note '{note_name}' with content: {content}",
+ )
+ ]
async def main():
- server = McpServer()
-
# Run the server using stdin/stdout streams
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
diff --git a/uv.lock b/uv.lock
index 372f52f..5e0d240 100644
--- a/uv.lock
+++ b/uv.lock
@@ -24,7 +24,7 @@ wheels = [
[[package]]
name = "create-mcp-server"
-version = "1.0.3.dev0"
+version = "1.0.4.dev0"
source = { editable = "." }
dependencies = [
{ name = "click" },