Compare commits
6 changed files with 159 additions and 228 deletions
|
|
@ -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.
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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
2
uv.lock
generated
|
|
@ -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" },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue