From 3a7003e80eb3985d6b41193874c49b0adf689e92 Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Tue, 12 Nov 2024 14:23:13 +0000 Subject: [PATCH] add simple resource example --- .../servers/simple-resource/.python-version | 1 + examples/servers/simple-resource/README.md | 36 ++++++++ .../mcp_simple_resource/__init__.py | 1 + .../mcp_simple_resource/__main__.py | 5 ++ .../mcp_simple_resource/server.py | 89 +++++++++++++++++++ .../servers/simple-resource/pyproject.toml | 47 ++++++++++ uv.lock | 34 +++++++ 7 files changed, 213 insertions(+) create mode 100644 examples/servers/simple-resource/.python-version create mode 100644 examples/servers/simple-resource/README.md create mode 100644 examples/servers/simple-resource/mcp_simple_resource/__init__.py create mode 100644 examples/servers/simple-resource/mcp_simple_resource/__main__.py create mode 100644 examples/servers/simple-resource/mcp_simple_resource/server.py create mode 100644 examples/servers/simple-resource/pyproject.toml diff --git a/examples/servers/simple-resource/.python-version b/examples/servers/simple-resource/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/examples/servers/simple-resource/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/examples/servers/simple-resource/README.md b/examples/servers/simple-resource/README.md new file mode 100644 index 0000000..28569a7 --- /dev/null +++ b/examples/servers/simple-resource/README.md @@ -0,0 +1,36 @@ +# MCP Simple Resource + +A simple MCP server that exposes sample text files as resources. + +## Usage + +Start the server using either stdio (default) or SSE transport: + +```bash +# Using stdio transport (default) +mcp-simple-resource + +# Using SSE transport on custom port +mcp-simple-resource --transport sse --port 8000 +``` + +The server exposes some basic text file resources that can be read by clients. + +## Example + +Using the MCP client, you can read the resources like this: + +```python +from mcp.client import ClientSession + +async with ClientSession() as session: + await session.initialize() + + # List available resources + resources = await session.list_resources() + print(resources) + + # Read a specific resource + resource = await session.read_resource(resources[0].uri) + print(resource) +``` diff --git a/examples/servers/simple-resource/mcp_simple_resource/__init__.py b/examples/servers/simple-resource/mcp_simple_resource/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/servers/simple-resource/mcp_simple_resource/__init__.py @@ -0,0 +1 @@ + diff --git a/examples/servers/simple-resource/mcp_simple_resource/__main__.py b/examples/servers/simple-resource/mcp_simple_resource/__main__.py new file mode 100644 index 0000000..17889d0 --- /dev/null +++ b/examples/servers/simple-resource/mcp_simple_resource/__main__.py @@ -0,0 +1,5 @@ +import sys + +from server import main + +sys.exit(main()) diff --git a/examples/servers/simple-resource/mcp_simple_resource/server.py b/examples/servers/simple-resource/mcp_simple_resource/server.py new file mode 100644 index 0000000..ac8635d --- /dev/null +++ b/examples/servers/simple-resource/mcp_simple_resource/server.py @@ -0,0 +1,89 @@ +import anyio +import click +import mcp.types as types +from mcp.server import Server, AnyUrl + + +@click.group() +def cli(): + pass + + +@cli.command() +@click.option("--port", default=8000, help="Port to listen on for SSE") +@click.option( + "--transport", + type=click.Choice(["stdio", "sse"]), + default="stdio", + help="Transport type", +) +def main(port: int, transport: str) -> int: + return anyio.run(_amain, port, transport) + + +SAMPLE_RESOURCES = { + "greeting": "Hello! This is a sample text resource.", + "help": "This server provides a few sample text resources for testing.", + "about": "This is the simple-resource MCP server implementation.", +} + + +async def _amain(port: int, transport: str) -> int: + app = Server("mcp-simple-resource") + + @app.list_resources() + async def list_resources() -> list[types.Resource]: + return [ + types.Resource( + uri=AnyUrl(f"file:///{name}.txt"), + name=name, + description=f"A sample text resource named {name}", + mimeType="text/plain", + ) + for name in SAMPLE_RESOURCES.keys() + ] + + @app.read_resource() + async def read_resource(uri: AnyUrl) -> str | bytes: + assert uri.path is not None + name = uri.path.replace(".txt", "").lstrip('/') + + if name not in SAMPLE_RESOURCES: + raise ValueError(f"Unknown resource: {uri}") + + return SAMPLE_RESOURCES[name] + + if transport == "sse": + from mcp.server.sse import SseServerTransport + from starlette.applications import Starlette + from starlette.routing import Route + + sse = SseServerTransport("/messages") + + async def handle_sse(scope, receive, send): + async with sse.connect_sse(scope, receive, send) as streams: + await app.run( + streams[0], streams[1], app.create_initialization_options() + ) + + async def handle_messages(scope, receive, send): + await sse.handle_post_message(scope, receive, send) + + starlette_app = Starlette( + debug=True, + routes=[ + Route("/sse", endpoint=handle_sse), + Route("/messages", endpoint=handle_messages, methods=["POST"]), + ], + ) + + import uvicorn + + uvicorn.run(starlette_app, host="0.0.0.0", port=port) + else: + from mcp.server.stdio import stdio_server + + async with stdio_server() as streams: + await app.run(streams[0], streams[1], app.create_initialization_options()) + + return 0 diff --git a/examples/servers/simple-resource/pyproject.toml b/examples/servers/simple-resource/pyproject.toml new file mode 100644 index 0000000..5423082 --- /dev/null +++ b/examples/servers/simple-resource/pyproject.toml @@ -0,0 +1,47 @@ +[project] +name = "mcp-simple-resource" +version = "0.1.0" +description = "A simple MCP server exposing sample text resources" +readme = "README.md" +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" }, +] +keywords = ["mcp", "llm", "automation", "web", "fetch"] +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 = ["anyio>=4.6.2.post1", "click>=8.1.7", "httpx>=0.27.2", "mcp"] + +[project.scripts] +mcp-simple-resource = "mcp_simple_resource.server:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["mcp_simple_resource"] + +[tool.pyright] +include = ["mcp_simple_resource"] +venvPath = "." +venv = ".venv" + +[tool.ruff.lint] +select = ["E", "F", "I"] +ignore = [] + +[tool.ruff] +line-length = 88 +target-version = "py310" + +[tool.uv] +dev-dependencies = ["pyright>=1.1.378", "pytest>=8.3.3", "ruff>=0.6.9"] diff --git a/uv.lock b/uv.lock index 544bc14..7757505 100644 --- a/uv.lock +++ b/uv.lock @@ -9,6 +9,7 @@ resolution-markers = [ members = [ "mcp", "mcp-simple-prompt", + "mcp-simple-resource", "mcp-simple-tool", ] @@ -240,6 +241,39 @@ dev = [ { name = "ruff", specifier = ">=0.6.9" }, ] +[[package]] +name = "mcp-simple-resource" +version = "0.1.0" +source = { editable = "examples/servers/simple-resource" } +dependencies = [ + { name = "anyio" }, + { name = "click" }, + { name = "httpx" }, + { name = "mcp" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pyright" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "anyio", specifier = ">=4.6.2.post1" }, + { name = "click", specifier = ">=8.1.7" }, + { name = "httpx", specifier = ">=0.27.2" }, + { name = "mcp", editable = "." }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pyright", specifier = ">=1.1.378" }, + { name = "pytest", specifier = ">=8.3.3" }, + { name = "ruff", specifier = ">=0.6.9" }, +] + [[package]] name = "mcp-simple-tool" version = "0.1.0"