Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b07744de4f |
||
|
|
40083e7301 |
||
|
|
7d921b9302 |
||
|
|
446072a2c0 |
||
|
|
35730e4a16 |
||
|
|
e5c6c63923 |
||
|
|
a40b42b534 |
||
|
|
b56d4c5186 |
||
|
|
6559948d34 |
||
|
|
6e43668dba |
||
|
|
e84ea45f8f |
||
|
|
74a6e01a30 |
||
|
|
1ccba01c1c |
||
|
|
6fef3bec62 |
||
|
|
2cebc73104 |
||
|
|
c8d622e085 |
||
|
|
eed5b350be |
||
|
|
84c807a5b8 |
||
|
|
ae6e31527a |
||
|
|
f2e71e60cb |
||
|
|
318a842fc9 |
||
|
|
aeada27c1f |
8 changed files with 382 additions and 167 deletions
43
CONTRIBUTING.md
Normal file
43
CONTRIBUTING.md
Normal file
|
|
@ -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.
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
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.
|
||||||
14
SECURITY.md
Normal file
14
SECURITY.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# 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).
|
||||||
|
|
@ -1,9 +1,22 @@
|
||||||
[project]
|
[project]
|
||||||
name = "create-mcp-server"
|
name = "create-mcp-server"
|
||||||
version = "1.0.1.dev0"
|
version = "1.0.6.dev0"
|
||||||
description = "Create an MCP 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"
|
||||||
|
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",
|
||||||
|
|
@ -14,6 +27,10 @@ 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"
|
||||||
|
|
@ -22,7 +39,4 @@ build-backend = "hatchling.build"
|
||||||
artifacts = ["src/create_mcp_server/template"]
|
artifacts = ["src/create_mcp_server/template"]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
dev-dependencies = [
|
dev-dependencies = ["pyright>=1.1.389", "ruff>=0.7.4"]
|
||||||
"pyright>=1.1.389",
|
|
||||||
"ruff>=0.7.4",
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,26 @@ 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:
|
||||||
|
|
@ -105,7 +120,9 @@ def get_package_directory(path: Path) -> Path:
|
||||||
return src_dir.parent
|
return src_dir.parent
|
||||||
|
|
||||||
|
|
||||||
def copy_template(path: Path, name: str, version: str = "0.1.0") -> None:
|
def copy_template(
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
@ -116,18 +133,28 @@ def copy_template(path: Path, name: str, version: str = "0.1.0") -> None:
|
||||||
env = Environment(loader=FileSystemLoader(str(template_dir)))
|
env = Environment(loader=FileSystemLoader(str(template_dir)))
|
||||||
|
|
||||||
files = [
|
files = [
|
||||||
("__init__.py.jinja2", "__init__.py"),
|
("__init__.py.jinja2", "__init__.py", target_dir),
|
||||||
("server.py.jinja2", "server.py"),
|
("server.py.jinja2", "server.py", target_dir),
|
||||||
|
("README.md.jinja2", "README.md", path),
|
||||||
]
|
]
|
||||||
|
|
||||||
template_vars = {"server_name": name, "server_version": version}
|
pyproject = PyProject(path / "pyproject.toml")
|
||||||
|
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 in files:
|
for template_file, output_file, output_dir 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 = target_dir / output_file
|
out_path = output_dir / output_file
|
||||||
out_path.write_text(rendered)
|
out_path.write_text(rendered)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -136,7 +163,7 @@ def copy_template(path: Path, name: str, version: str = "0.1.0") -> None:
|
||||||
|
|
||||||
|
|
||||||
def create_project(
|
def create_project(
|
||||||
path: Path, name: str, version: str, use_claude: bool = True
|
path: Path, name: str, description: 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)
|
||||||
|
|
@ -158,7 +185,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, version)
|
copy_template(path, name, description, version)
|
||||||
|
|
||||||
# Check if Claude.app is available
|
# Check if Claude.app is available
|
||||||
if (
|
if (
|
||||||
|
|
@ -313,7 +340,9 @@ 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
|
||||||
|
|
||||||
create_project(project_path, name, version, claudeapp)
|
project_path = project_path.resolve()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
||||||
119
src/create_mcp_server/template/README.md.jinja2
Normal file
119
src/create_mcp_server/template/README.md.jinja2
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
# {{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.
|
||||||
|
|
@ -6,173 +6,148 @@ 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] = {}
|
||||||
|
|
||||||
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:
|
List available note resources.
|
||||||
1. Resources - Simple key-value notes storage accessed via custom URIs
|
Each note is exposed as a resource with a custom note:// URI scheme.
|
||||||
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
|
|
||||||
"""
|
"""
|
||||||
|
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):
|
@server.read_resource()
|
||||||
super().__init__("{{server_name}}")
|
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
|
name = uri.path
|
||||||
self.notes: dict[str, str] = {}
|
if name is not None:
|
||||||
|
name = name.lstrip("/")
|
||||||
|
return notes[name]
|
||||||
|
raise ValueError(f"Note not found: {name}")
|
||||||
|
|
||||||
# RESOURCE HANDLERS
|
@server.list_prompts()
|
||||||
# Resources allow clients to discover and read content from the server
|
async def handle_list_prompts() -> list[types.Prompt]:
|
||||||
@self.list_resources()
|
"""
|
||||||
async def handle_list_resources() -> list[types.Resource]:
|
List available prompts.
|
||||||
"""
|
Each prompt can have optional arguments to customize its behavior.
|
||||||
List available note resources.
|
"""
|
||||||
Each note is exposed as a resource with a custom note:// URI scheme.
|
return [
|
||||||
"""
|
types.Prompt(
|
||||||
return [
|
name="summarize-notes",
|
||||||
types.Resource(
|
description="Creates a summary of all notes",
|
||||||
uri=AnyUrl(f"note:///{name}"),
|
arguments=[
|
||||||
name=f"Note: {name}",
|
types.PromptArgument(
|
||||||
description=f"A simple note named {name}",
|
name="style",
|
||||||
mimeType="text/plain",
|
description="Style of the summary (brief/detailed)",
|
||||||
|
required=False,
|
||||||
)
|
)
|
||||||
for name in self.notes
|
],
|
||||||
]
|
)
|
||||||
|
]
|
||||||
|
|
||||||
@self.read_resource()
|
@server.get_prompt()
|
||||||
async def handle_read_resource(uri: AnyUrl) -> str:
|
async def handle_get_prompt(
|
||||||
"""
|
name: str, arguments: dict[str, str] | None
|
||||||
Read a specific note's content by its URI.
|
) -> types.GetPromptResult:
|
||||||
The note name is extracted from the URI host component.
|
"""
|
||||||
"""
|
Generate a prompt by combining arguments with server state.
|
||||||
if uri.scheme != "note":
|
The prompt includes all current notes and can be customized via arguments.
|
||||||
raise ValueError(f"Unsupported URI scheme: {uri.scheme}")
|
"""
|
||||||
|
if name != "summarize-notes":
|
||||||
|
raise ValueError(f"Unknown prompt: {name}")
|
||||||
|
|
||||||
name = uri.path
|
style = (arguments or {}).get("style", "brief")
|
||||||
if name is not None:
|
detail_prompt = " Give extensive details." if style == "detailed" else ""
|
||||||
name = name.lstrip("/")
|
|
||||||
return self.notes[name]
|
|
||||||
raise ValueError(f"Note not found: {name}")
|
|
||||||
|
|
||||||
# PROMPT HANDLERS
|
return types.GetPromptResult(
|
||||||
# Prompts define templates that can be filled with server state
|
description="Summarize the current notes",
|
||||||
@self.list_prompts()
|
messages=[
|
||||||
async def handle_list_prompts() -> list[types.Prompt]:
|
types.PromptMessage(
|
||||||
"""
|
role="user",
|
||||||
List available prompts.
|
content=types.TextContent(
|
||||||
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(
|
|
||||||
type="text",
|
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():
|
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.1.dev0"
|
version = "1.0.4.dev0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue