Skip to content

Commit 1561224

Browse files
committed
Example server to expose a fetch tool
1 parent 3d670a3 commit 1561224

File tree

9 files changed

+580
-1
lines changed

9 files changed

+580
-1
lines changed

examples/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Python SDK Examples
2+
3+
This folders aims to provide simple examples of using the Python SDK. Please refer to the
4+
[example servers repository](https://github.com/modelcontextprotocol/example-servers)
5+
for real-world servers.
6+
7+
* **simple-tool**: provides a simple fetch tool
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.11

examples/servers/simple-tool/README.md

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import sys
2+
3+
from server import main
4+
5+
sys.exit(main())
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import anyio
2+
import click
3+
import httpx
4+
import mcp.types as types
5+
from mcp.server import Server
6+
7+
8+
async def fetch_website(
9+
url: str,
10+
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
11+
async with httpx.AsyncClient(follow_redirects=True) as client:
12+
response = await client.get(url)
13+
response.raise_for_status()
14+
return [types.TextContent(type="text", text=response.text)]
15+
16+
17+
@click.group()
18+
def cli():
19+
pass
20+
21+
22+
@cli.command()
23+
@click.option("--port", default=8000, help="Port to listen on for SSE")
24+
@click.option(
25+
"--transport",
26+
type=click.Choice(["stdio", "sse"]),
27+
default="stdio",
28+
help="Transport type",
29+
)
30+
def main(port: int, transport: str) -> int:
31+
return anyio.run(_amain, port, transport)
32+
33+
34+
async def _amain(port: int, transport: str) -> int:
35+
app = Server("mcp-website-fetcher")
36+
37+
@app.call_tool()
38+
async def fetch_tool(
39+
name: str, arguments: dict
40+
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
41+
if name != "fetch":
42+
raise ValueError(f"Unknown tool: {name}")
43+
if "url" not in arguments:
44+
raise ValueError("Missing required argument 'url'")
45+
return await fetch_website(arguments["url"])
46+
47+
@app.list_tools()
48+
async def list_tools() -> list[types.Tool]:
49+
return [
50+
types.Tool(
51+
name="fetch",
52+
description="Fetches a website and returns its content",
53+
inputSchema={
54+
"type": "object",
55+
"required": ["url"],
56+
"properties": {
57+
"url": {
58+
"type": "string",
59+
"description": "URL to fetch",
60+
}
61+
},
62+
},
63+
)
64+
]
65+
66+
if transport == "sse":
67+
from mcp.server.sse import SseServerTransport
68+
from starlette.applications import Starlette
69+
from starlette.routing import Route
70+
71+
sse = SseServerTransport("/messages")
72+
73+
async def handle_sse(scope, receive, send):
74+
async with sse.connect_sse(scope, receive, send) as streams:
75+
await app.run(
76+
streams[0], streams[1], app.create_initialization_options()
77+
)
78+
79+
async def handle_messages(scope, receive, send):
80+
await sse.handle_post_message(scope, receive, send)
81+
82+
starlette_app = Starlette(
83+
debug=True,
84+
routes=[
85+
Route("/sse", endpoint=handle_sse),
86+
Route("/messages", endpoint=handle_messages, methods=["POST"]),
87+
],
88+
)
89+
90+
import uvicorn
91+
92+
uvicorn.run(starlette_app, host="0.0.0.0", port=port)
93+
else:
94+
from mcp.server.stdio import stdio_server
95+
96+
async with stdio_server() as streams:
97+
await app.run(streams[0], streams[1], app.create_initialization_options())
98+
99+
return 0
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[project]
2+
name = "mcp-simple-tool"
3+
version = "0.1.0"
4+
description = "A simple MCP server exposing a website fetching tool"
5+
readme = "README.md"
6+
requires-python = ">=3.10"
7+
authors = [{ name = "Anthropic, PBC." }]
8+
maintainers = [
9+
{ name = "David Soria Parra", email = "davidsp@anthropic.com" },
10+
{ name = "Justin Spahr-Summers", email = "justin@anthropic.com" },
11+
]
12+
keywords = ["mcp", "llm", "automation", "web", "fetch"]
13+
license = { text = "MIT" }
14+
classifiers = [
15+
"Development Status :: 4 - Beta",
16+
"Intended Audience :: Developers",
17+
"License :: OSI Approved :: MIT License",
18+
"Programming Language :: Python :: 3",
19+
"Programming Language :: Python :: 3.10",
20+
]
21+
dependencies = [
22+
"anyio>=4.6.2.post1",
23+
"click>=8.1.7",
24+
"httpx>=0.27.2",
25+
"mcp>=0.7.0",
26+
]
27+
28+
[project.scripts]
29+
mcp-simple-tool = "mcp_simple_tool.server:main"
30+
31+
[build-system]
32+
requires = ["hatchling"]
33+
build-backend = "hatchling.build"
34+
35+
[tool.hatch.build.targets.wheel]
36+
packages = ["mcp_simple_tool"]
37+
38+
[tool.pyright]
39+
include = ["mcp_simple_tool"]
40+
venvPath = "."
41+
venv = ".venv"
42+
43+
[tool.ruff.lint]
44+
select = ["E", "F", "I"]
45+
ignore = []
46+
47+
[tool.ruff]
48+
line-length = 88
49+
target-version = "py310"
50+
51+
[tool.uv]
52+
dev-dependencies = ["pyright>=1.1.378", "pytest>=8.3.3", "ruff>=0.6.9"]

0 commit comments

Comments
 (0)