diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index fe64dba..0000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -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.
diff --git a/pyproject.toml b/pyproject.toml
index 6293767..4e5df7b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "create-mcp-server"
-version = "1.0.6.dev0"
+version = "1.0.3"
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 b54038d..99f57fd 100644
--- a/src/create_mcp_server/__init__.py
+++ b/src/create_mcp_server/__init__.py
@@ -24,7 +24,6 @@ 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:
@@ -256,6 +255,8 @@ def check_package_name(name: str) -> bool:
return True
+
+
@click.command()
@click.option(
"--path",
@@ -340,8 +341,6 @@ def main(
click.echo("❌ Error: Invalid path. Project creation aborted.", err=True)
return 1
- project_path = project_path.resolve()
-
create_project(project_path, name, description, version, claudeapp)
update_pyproject_settings(project_path, version, description)
diff --git a/src/create_mcp_server/template/README.md.jinja2 b/src/create_mcp_server/template/README.md.jinja2
index c35655d..7abfe66 100644
--- a/src/create_mcp_server/template/README.md.jinja2
+++ b/src/create_mcp_server/template/README.md.jinja2
@@ -24,22 +24,14 @@ The server implements one tool:
- Takes "name" and "content" as required string arguments
- Updates server state and notifies clients of resource changes
-## Configuration
+## Install
-[TODO: Add configuration details specific to your implementation]
-
-## Quickstart
-
-### Install
-
-#### Claude Desktop
+### 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",
@@ -51,51 +43,9 @@ 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
```
-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
+## Develop and Debug
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 bc39f5c..bd07d11 100644
--- a/src/create_mcp_server/template/server.py.jinja2
+++ b/src/create_mcp_server/template/server.py.jinja2
@@ -6,148 +6,173 @@ 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] = {}
-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()
-async def handle_list_resources() -> list[types.Resource]:
+ 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
- ]
-@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}")
+ def __init__(self):
+ super().__init__("{{server_name}}")
- name = uri.path
- if name is not None:
- name = name.lstrip("/")
- return notes[name]
- raise ValueError(f"Note not found: {name}")
+ # Store notes as a simple key-value dict to demonstrate state management
+ self.notes: dict[str, str] = {}
-@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,
+ # 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",
)
- ],
- )
- ]
+ for name in self.notes
+ ]
-@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}")
+ @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}")
- style = (arguments or {}).get("style", "brief")
- detail_prompt = " Give extensive details." if style == "detailed" else ""
+ name = uri.path
+ if name is not None:
+ name = name.lstrip("/")
+ return self.notes[name]
+ raise ValueError(f"Note not found: {name}")
- 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 notes.items()
- ),
- ),
+ # 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()
+ ),
+ ),
+ )
+ ],
)
- ],
- )
-@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"],
- },
- )
- ]
+ # 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"],
+ },
+ )
+ ]
-@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}")
+ @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")
+ if not arguments:
+ raise ValueError("Missing arguments")
- note_name = arguments.get("name")
- content = arguments.get("content")
+ note_name = arguments.get("name")
+ content = arguments.get("content")
- if not note_name or not content:
- raise ValueError("Missing name or content")
+ if not note_name or not content:
+ raise ValueError("Missing name or content")
- # Update server state
- notes[note_name] = content
+ # Update server state
+ self.notes[note_name] = content
- # Notify clients that resources have changed
- await server.request_context.session.send_resource_list_changed()
+ # Notify clients that resources have changed
+ await self.request_context.session.send_resource_list_changed()
+
+ return [
+ types.TextContent(
+ type="text",
+ text=f"Added note '{note_name}' with content: {content}",
+ )
+ ]
- 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 5e0d240..372f52f 100644
--- a/uv.lock
+++ b/uv.lock
@@ -24,7 +24,7 @@ wheels = [
[[package]]
name = "create-mcp-server"
-version = "1.0.4.dev0"
+version = "1.0.3.dev0"
source = { editable = "." }
dependencies = [
{ name = "click" },