Compare commits

..
6 changed files with 159 additions and 228 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.

View file

@ -1,6 +1,6 @@
[project] [project]
name = "create-mcp-server" name = "create-mcp-server"
version = "1.0.6.dev0" version = "1.0.3"
description = "Create an Model Context Protocol server project from a template." description = "Create an Model Context Protocol server project from a template."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"

View file

@ -24,7 +24,6 @@ class PyProject:
scripts = self.data["project"].get("scripts", {}) scripts = self.data["project"].get("scripts", {})
return next(iter(scripts.keys()), None) 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:
@ -256,6 +255,8 @@ def check_package_name(name: str) -> bool:
return True return True
@click.command() @click.command()
@click.option( @click.option(
"--path", "--path",
@ -340,8 +341,6 @@ 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, description, version, claudeapp) create_project(project_path, name, description, version, claudeapp)
update_pyproject_settings(project_path, version, description) update_pyproject_settings(project_path, version, description)

View file

@ -24,22 +24,14 @@ The server implements one tool:
- Takes "name" and "content" as required string arguments - Takes "name" and "content" as required string arguments
- Updates server state and notifies clients of resource changes - Updates server state and notifies clients of resource changes
## Configuration ## Install
[TODO: Add configuration details specific to your implementation] ### Claude Desktop
## Quickstart
### Install
#### Claude Desktop
On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json` On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
On Windows: `%APPDATA%/Claude/claude_desktop_config.json` On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
<details> ```
<summary>Development/Unpublished Servers Configuration</summary>
```
"mcpServers": { "mcpServers": {
"{{server_name}}": { "{{server_name}}": {
"command": "uv", "command": "uv",
@ -51,51 +43,9 @@ On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
] ]
} }
} }
```
</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: ## Develop and Debug
```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 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). experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).

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.3.dev0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },