Skip to content

Commit 1e52765

Browse files
committed
Merge branch 'main' into v1.2.x
2 parents 552f797 + 58bc32e commit 1e52765

File tree

18 files changed

+345
-76
lines changed

18 files changed

+345
-76
lines changed

Diff for: CLAUDE.md

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Tool Usage Learnings
2+
3+
This file is intended to be used by an LLM such as Claude.
4+
5+
## UV Package Manager
6+
7+
- Use `uv run` to run Python tools without activating virtual environments
8+
- For formatting: `uv run ruff format .`
9+
- For type checking: `uv run pyright`
10+
- For upgrading packages:
11+
- `uv add --dev package --upgrade-package package` to upgrade a specific package
12+
- Don't use `@latest` syntax - it doesn't work
13+
- Be careful with `uv pip install` as it may downgrade packages
14+
15+
## Git and GitHub CLI
16+
17+
- When using gh CLI for PRs, always quote title and body:
18+
```bash
19+
gh pr create --title "\"my title\"" --body "\"my body\""
20+
```
21+
- For git commits, use double quotes and escape inner quotes:
22+
```bash
23+
git commit -am "\"fix: my commit message\""
24+
```
25+
26+
## Python Tools
27+
28+
### Ruff
29+
- Handles both formatting and linting
30+
- For formatting: `uv run ruff format .`
31+
- For checking: `uv run ruff check .`
32+
- For auto-fixing: `uv run ruff check . --fix`
33+
- Common issues:
34+
- Line length (default 88 chars)
35+
- Import sorting (I001 errors)
36+
- Unused imports
37+
- When line length errors occur:
38+
- For strings, use parentheses and line continuation
39+
- For function calls, use multiple lines with proper indentation
40+
- For imports, split into multiple lines
41+
42+
### Pyright
43+
- Type checker
44+
- Run with: `uv run pyright`
45+
- Version warnings can be ignored if type checking passes
46+
- Common issues:
47+
- Optional types need explicit None checks
48+
- String operations need type narrowing
49+
50+
## Pre-commit Hooks
51+
52+
- Configuration in `.pre-commit-config.yaml`
53+
- Runs automatically on git commit
54+
- Includes:
55+
- Prettier for YAML/JSON formatting
56+
- Ruff for Python formatting and linting
57+
- When updating ruff version:
58+
- Check available versions on PyPI
59+
- Update `rev` in config to match available version
60+
- Add and commit config changes before other changes
61+
62+
## Best Practices
63+
64+
1. Always check git status and diff before committing
65+
2. Run formatters before type checkers
66+
3. When fixing CI:
67+
- Start with formatting issues
68+
- Then fix type errors
69+
- Then address any remaining linting issues
70+
4. For type errors:
71+
- Get full context around error lines
72+
- Consider optional types
73+
- Add type narrowing checks when needed

Diff for: examples/fastmcp/unicode_example.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""
2+
Example FastMCP server that uses Unicode characters in various places to help test
3+
Unicode handling in tools and inspectors.
4+
"""
5+
6+
from mcp.server.fastmcp import FastMCP
7+
8+
mcp = FastMCP()
9+
10+
11+
@mcp.tool(
12+
description="🌟 A tool that uses various Unicode characters in its description: "
13+
"á é í ó ú ñ 漢字 🎉"
14+
)
15+
def hello_unicode(name: str = "世界", greeting: str = "¡Hola") -> str:
16+
"""
17+
A simple tool that demonstrates Unicode handling in:
18+
- Tool description (emojis, accents, CJK characters)
19+
- Parameter defaults (CJK characters)
20+
- Return values (Spanish punctuation, emojis)
21+
"""
22+
return f"{greeting}, {name}! 👋"
23+
24+
25+
@mcp.tool(description="🎨 Tool that returns a list of emoji categories")
26+
def list_emoji_categories() -> list[str]:
27+
"""Returns a list of emoji categories with emoji examples."""
28+
return [
29+
"😀 Smileys & Emotion",
30+
"👋 People & Body",
31+
"🐶 Animals & Nature",
32+
"🍎 Food & Drink",
33+
"⚽ Activities",
34+
"🌍 Travel & Places",
35+
"💡 Objects",
36+
"❤️ Symbols",
37+
"🚩 Flags",
38+
]
39+
40+
41+
@mcp.tool(description="🔤 Tool that returns text in different scripts")
42+
def multilingual_hello() -> str:
43+
"""Returns hello in different scripts and writing systems."""
44+
return "\n".join(
45+
[
46+
"English: Hello!",
47+
"Spanish: ¡Hola!",
48+
"French: Bonjour!",
49+
"German: Grüß Gott!",
50+
"Russian: Привет!",
51+
"Greek: Γεια σας!",
52+
"Hebrew: !שָׁלוֹם",
53+
"Arabic: !مرحبا",
54+
"Hindi: नमस्ते!",
55+
"Chinese: 你好!",
56+
"Japanese: こんにちは!",
57+
"Korean: 안녕하세요!",
58+
"Thai: สวัสดี!",
59+
]
60+
)
61+
62+
63+
if __name__ == "__main__":
64+
mcp.run()

Diff for: examples/servers/simple-prompt/mcp_simple_prompt/server.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ async def get_prompt(
9090
if transport == "sse":
9191
from mcp.server.sse import SseServerTransport
9292
from starlette.applications import Starlette
93-
from starlette.routing import Route
93+
from starlette.routing import Mount, Route
9494

95-
sse = SseServerTransport("/messages")
95+
sse = SseServerTransport("/messages/")
9696

9797
async def handle_sse(request):
9898
async with sse.connect_sse(
@@ -102,14 +102,11 @@ async def handle_sse(request):
102102
streams[0], streams[1], app.create_initialization_options()
103103
)
104104

105-
async def handle_messages(request):
106-
await sse.handle_post_message(request.scope, request.receive, request._send)
107-
108105
starlette_app = Starlette(
109106
debug=True,
110107
routes=[
111108
Route("/sse", endpoint=handle_sse),
112-
Route("/messages", endpoint=handle_messages, methods=["POST"]),
109+
Mount("/messages/", app=sse.handle_post_message),
113110
],
114111
)
115112

Diff for: examples/servers/simple-resource/mcp_simple_resource/server.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ async def read_resource(uri: AnyUrl) -> str | bytes:
4747
if transport == "sse":
4848
from mcp.server.sse import SseServerTransport
4949
from starlette.applications import Starlette
50-
from starlette.routing import Route
50+
from starlette.routing import Mount, Route
5151

52-
sse = SseServerTransport("/messages")
52+
sse = SseServerTransport("/messages/")
5353

5454
async def handle_sse(request):
5555
async with sse.connect_sse(
@@ -59,14 +59,11 @@ async def handle_sse(request):
5959
streams[0], streams[1], app.create_initialization_options()
6060
)
6161

62-
async def handle_messages(request):
63-
await sse.handle_post_message(request.scope, request.receive, request._send)
64-
6562
starlette_app = Starlette(
6663
debug=True,
6764
routes=[
6865
Route("/sse", endpoint=handle_sse),
69-
Route("/messages", endpoint=handle_messages, methods=["POST"]),
66+
Mount("/messages/", app=sse.handle_post_message),
7067
],
7168
)
7269

Diff for: examples/servers/simple-tool/mcp_simple_tool/server.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ async def list_tools() -> list[types.Tool]:
6060
if transport == "sse":
6161
from mcp.server.sse import SseServerTransport
6262
from starlette.applications import Starlette
63-
from starlette.routing import Route
63+
from starlette.routing import Mount, Route
6464

65-
sse = SseServerTransport("/messages")
65+
sse = SseServerTransport("/messages/")
6666

6767
async def handle_sse(request):
6868
async with sse.connect_sse(
@@ -72,14 +72,11 @@ async def handle_sse(request):
7272
streams[0], streams[1], app.create_initialization_options()
7373
)
7474

75-
async def handle_messages(request):
76-
await sse.handle_post_message(request.scope, request.receive, request._send)
77-
7875
starlette_app = Starlette(
7976
debug=True,
8077
routes=[
8178
Route("/sse", endpoint=handle_sse),
82-
Route("/messages", endpoint=handle_messages, methods=["POST"]),
79+
Mount("/messages/", app=sse.handle_post_message),
8380
],
8481
)
8582

Diff for: pyproject.toml

+4-4
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ mcp = "mcp.cli:app [cli]"
4242
[tool.uv]
4343
resolution = "lowest-direct"
4444
dev-dependencies = [
45-
"pyright>=1.1.378",
46-
"pytest>=8.3.3",
47-
"ruff>=0.8.1",
45+
"pyright>=1.1.391",
46+
"pytest>=8.3.4",
47+
"ruff>=0.8.5",
4848
"trio>=0.26.2",
4949
"pytest-flakefinder>=1.1.0",
5050
"pytest-xdist>=3.6.1",
@@ -83,4 +83,4 @@ target-version = "py310"
8383
members = ["examples/servers/*"]
8484

8585
[tool.uv.sources]
86-
mcp = { workspace = true }
86+
mcp = { workspace = true }

Diff for: src/mcp/cli/claude.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def update_claude_config(
8787
args = ["run"]
8888

8989
# Collect all packages in a set to deduplicate
90-
packages = {"fastmcp"}
90+
packages = {"mcp"}
9191
if with_packages:
9292
packages.update(pkg for pkg in with_packages if pkg)
9393

@@ -107,7 +107,7 @@ def update_claude_config(
107107
file_spec = str(Path(file_spec).resolve())
108108

109109
# Add fastmcp run command
110-
args.extend(["fastmcp", "run", file_spec])
110+
args.extend(["mcp", "run", file_spec])
111111

112112
server_config = {
113113
"command": "uv",

Diff for: src/mcp/cli/cli.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ def install(
373373
list[str],
374374
typer.Option(
375375
"--env-var",
376-
"-e",
376+
"-v",
377377
help="Environment variables in KEY=VALUE format",
378378
),
379379
] = [],

Diff for: src/mcp/client/stdio.py

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import sys
33
from contextlib import asynccontextmanager
4+
from typing import Literal
45

56
import anyio
67
import anyio.lowlevel
@@ -65,6 +66,21 @@ class StdioServerParameters(BaseModel):
6566
If not specified, the result of get_default_environment() will be used.
6667
"""
6768

69+
encoding: str = "utf-8"
70+
"""
71+
The text encoding used when sending/receiving messages to the server
72+
73+
defaults to utf-8
74+
"""
75+
76+
encoding_error_handler: Literal["strict", "ignore", "replace"] = "strict"
77+
"""
78+
The text encoding error handler.
79+
80+
See https://docs.python.org/3/library/codecs.html#codec-base-classes for
81+
explanations of possible values
82+
"""
83+
6884

6985
@asynccontextmanager
7086
async def stdio_client(server: StdioServerParameters):
@@ -93,7 +109,11 @@ async def stdout_reader():
93109
try:
94110
async with read_stream_writer:
95111
buffer = ""
96-
async for chunk in TextReceiveStream(process.stdout):
112+
async for chunk in TextReceiveStream(
113+
process.stdout,
114+
encoding=server.encoding,
115+
errors=server.encoding_error_handler,
116+
):
97117
lines = (buffer + chunk).split("\n")
98118
buffer = lines.pop()
99119

@@ -115,7 +135,12 @@ async def stdin_writer():
115135
async with write_stream_reader:
116136
async for message in write_stream_reader:
117137
json = message.model_dump_json(by_alias=True, exclude_none=True)
118-
await process.stdin.send((json + "\n").encode())
138+
await process.stdin.send(
139+
(json + "\n").encode(
140+
encoding=server.encoding,
141+
errors=server.encoding_error_handler,
142+
)
143+
)
119144
except anyio.ClosedResourceError:
120145
await anyio.lowlevel.checkpoint()
121146

Diff for: src/mcp/server/fastmcp/server.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -423,9 +423,9 @@ async def run_stdio_async(self) -> None:
423423
async def run_sse_async(self) -> None:
424424
"""Run the server using SSE transport."""
425425
from starlette.applications import Starlette
426-
from starlette.routing import Route
426+
from starlette.routing import Mount, Route
427427

428-
sse = SseServerTransport("/messages")
428+
sse = SseServerTransport("/messages/")
429429

430430
async def handle_sse(request):
431431
async with sse.connect_sse(
@@ -437,14 +437,11 @@ async def handle_sse(request):
437437
self._mcp_server.create_initialization_options(),
438438
)
439439

440-
async def handle_messages(request):
441-
await sse.handle_post_message(request.scope, request.receive, request._send)
442-
443440
starlette_app = Starlette(
444441
debug=self.settings.debug,
445442
routes=[
446443
Route("/sse", endpoint=handle_sse),
447-
Route("/messages", endpoint=handle_messages, methods=["POST"]),
444+
Mount("/messages/", app=sse.handle_post_message),
448445
],
449446
)
450447

Diff for: src/mcp/server/lowlevel/server.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@ def __init__(
101101

102102

103103
class Server:
104-
def __init__(self, name: str):
104+
def __init__(self, name: str, version: str | None = None):
105105
self.name = name
106+
self.version = version
106107
self.request_handlers: dict[
107108
type, Callable[..., Awaitable[types.ServerResult]]
108109
] = {
@@ -133,7 +134,7 @@ def pkg_version(package: str) -> str:
133134

134135
return InitializationOptions(
135136
server_name=self.name,
136-
server_version=pkg_version("mcp"),
137+
server_version=self.version if self.version else pkg_version("mcp"),
137138
capabilities=self.get_capabilities(
138139
notification_options or NotificationOptions(),
139140
experimental_capabilities or {},

Diff for: src/mcp/server/sse.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
Example usage:
77
```
88
# Create an SSE transport at an endpoint
9-
sse = SseServerTransport("/messages")
9+
sse = SseServerTransport("/messages/")
1010
1111
# Create Starlette routes for SSE and message handling
1212
routes = [
1313
Route("/sse", endpoint=handle_sse),
14-
Route("/messages", endpoint=handle_messages, methods=["POST"])
14+
Mount("/messages/", app=sse.handle_post_message),
1515
]
1616
1717
# Define handler functions
@@ -23,9 +23,6 @@ async def handle_sse(request):
2323
streams[0], streams[1], app.create_initialization_options()
2424
)
2525
26-
async def handle_messages(request):
27-
await sse.handle_post_message(request.scope, request.receive, request._send)
28-
2926
# Create and run Starlette app
3027
starlette_app = Starlette(routes=routes)
3128
uvicorn.run(starlette_app, host="0.0.0.0", port=port)

0 commit comments

Comments
 (0)