Skip to content

Commit d4a569b

Browse files
committed
Merge branch 'main' into RFC9728_www-authentication
2 parents 800ffa8 + 3abefee commit d4a569b

34 files changed

+2069
-765
lines changed

.github/workflows/shared.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
test:
3030
runs-on: ${{ matrix.os }}
3131
timeout-minutes: 10
32+
continue-on-error: true
3233
strategy:
3334
matrix:
3435
python-version: ["3.10", "3.11", "3.12", "3.13"]
@@ -48,8 +49,14 @@ jobs:
4849

4950
- name: Run pytest
5051
run: uv run --frozen --no-sync pytest
51-
continue-on-error: true
5252

53+
# This must run last as it modifies the environment!
54+
- name: Run pytest with lowest versions
55+
run: |
56+
uv sync --all-extras --upgrade
57+
uv run --no-sync pytest
58+
env:
59+
UV_RESOLUTION: lowest-direct
5360
readme-snippets:
5461
runs-on: ubuntu-latest
5562
steps:

.pre-commit-config.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ repos:
2727
- id: pyright
2828
name: pyright
2929
entry: uv run pyright
30-
args: [src]
3130
language: system
3231
types: [python]
3332
pass_filenames: false

CLAUDE.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ This document contains critical information about working with this codebase. Fo
1616
- Public APIs must have docstrings
1717
- Functions must be focused and small
1818
- Follow existing patterns exactly
19-
- Line length: 88 chars maximum
19+
- Line length: 120 chars maximum
2020

2121
3. Testing Requirements
2222
- Framework: `uv run --frozen pytest`
@@ -116,3 +116,15 @@ This document contains critical information about working with this codebase. Fo
116116
- Follow existing patterns
117117
- Document public APIs
118118
- Test thoroughly
119+
120+
## Exception Handling
121+
122+
- **Always use `logger.exception()` instead of `logger.error()` when catching exceptions**
123+
- Don't include the exception in the message: `logger.exception("Failed")` not `logger.exception(f"Failed: {e}")`
124+
- **Catch specific exceptions** where possible:
125+
- File ops: `except (OSError, PermissionError):`
126+
- JSON: `except json.JSONDecodeError:`
127+
- Network: `except (ConnectionError, TimeoutError):`
128+
- **Only catch `Exception` for**:
129+
- Top-level handlers that must not crash
130+
- Cleanup blocks (log at debug level)

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ _Full example: [examples/snippets/servers/completion.py](https://github.com/mode
546546
<!-- /snippet-source -->
547547
### Elicitation
548548

549-
Request additional information from users during tool execution:
549+
Request additional information from users. This example shows an Elicitation during a Tool Call:
550550

551551
<!-- snippet-source examples/snippets/servers/elicitation.py -->
552552
```python
@@ -1327,13 +1327,13 @@ The MCP protocol defines three core primitives that servers can implement:
13271327

13281328
MCP servers declare capabilities during initialization:
13291329

1330-
| Capability | Feature Flag | Description |
1331-
|-------------|------------------------------|------------------------------------|
1332-
| `prompts` | `listChanged` | Prompt template management |
1333-
| `resources` | `subscribe`<br/>`listChanged`| Resource exposure and updates |
1334-
| `tools` | `listChanged` | Tool discovery and execution |
1335-
| `logging` | - | Server logging configuration |
1336-
| `completion`| - | Argument completion suggestions |
1330+
| Capability | Feature Flag | Description |
1331+
|--------------|------------------------------|------------------------------------|
1332+
| `prompts` | `listChanged` | Prompt template management |
1333+
| `resources` | `subscribe`<br/>`listChanged`| Resource exposure and updates |
1334+
| `tools` | `listChanged` | Tool discovery and execution |
1335+
| `logging` | - | Server logging configuration |
1336+
| `completions`| - | Argument completion suggestions |
13371337

13381338
## Documentation
13391339

examples/servers/simple-auth/mcp_simple_auth/server.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,8 @@ def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http
160160
mcp_server.run(transport=transport)
161161
logger.info("Server stopped")
162162
return 0
163-
except Exception as e:
164-
logger.error(f"Server error: {e}")
165-
logger.exception("Exception details:")
163+
except Exception:
164+
logger.exception("Server error")
166165
return 1
167166

168167

pyproject.toml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,25 @@ dependencies = [
2525
"anyio>=4.5",
2626
"httpx>=0.27",
2727
"httpx-sse>=0.4",
28-
"pydantic>=2.7.2,<3.0.0",
28+
"pydantic>=2.8.0,<3.0.0",
2929
"starlette>=0.27",
3030
"python-multipart>=0.0.9",
3131
"sse-starlette>=1.6.1",
3232
"pydantic-settings>=2.5.2",
3333
"uvicorn>=0.23.1; sys_platform != 'emscripten'",
3434
"jsonschema>=4.20.0",
35+
"pywin32>=310; sys_platform == 'win32'",
3536
]
3637

3738
[project.optional-dependencies]
3839
rich = ["rich>=13.9.4"]
39-
cli = ["typer>=0.12.4", "python-dotenv>=1.0.0"]
40+
cli = ["typer>=0.16.0", "python-dotenv>=1.0.0"]
4041
ws = ["websockets>=15.0.1"]
4142

4243
[project.scripts]
4344
mcp = "mcp.cli:app [cli]"
4445

4546
[tool.uv]
46-
resolution = "lowest-direct"
4747
default-groups = ["dev", "docs"]
4848
required-version = ">=0.7.2"
4949

@@ -58,6 +58,7 @@ dev = [
5858
"pytest-examples>=0.0.14",
5959
"pytest-pretty>=1.2.0",
6060
"inline-snapshot>=0.23.0",
61+
"dirty-equals>=0.9.0",
6162
]
6263
docs = [
6364
"mkdocs>=1.6.1",
@@ -124,5 +125,7 @@ filterwarnings = [
124125
# This should be fixed on Uvicorn's side.
125126
"ignore::DeprecationWarning:websockets",
126127
"ignore:websockets.server.WebSocketServerProtocol is deprecated:DeprecationWarning",
127-
"ignore:Returning str or bytes.*:DeprecationWarning:mcp.server.lowlevel"
128+
"ignore:Returning str or bytes.*:DeprecationWarning:mcp.server.lowlevel",
129+
# pywin32 internal deprecation warning
130+
"ignore:getargs.*The 'u' format is deprecated:DeprecationWarning"
128131
]

src/mcp/cli/claude.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,10 @@ def update_claude_config(
7575
if not config_file.exists():
7676
try:
7777
config_file.write_text("{}")
78-
except Exception as e:
79-
logger.error(
78+
except Exception:
79+
logger.exception(
8080
"Failed to create Claude config file",
8181
extra={
82-
"error": str(e),
8382
"config_file": str(config_file),
8483
},
8584
)
@@ -139,11 +138,10 @@ def update_claude_config(
139138
extra={"config_file": str(config_file)},
140139
)
141140
return True
142-
except Exception as e:
143-
logger.error(
141+
except Exception:
142+
logger.exception(
144143
"Failed to update Claude config",
145144
extra={
146-
"error": str(e),
147145
"config_file": str(config_file),
148146
},
149147
)

src/mcp/cli/cli.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -349,12 +349,11 @@ def run(
349349

350350
server.run(**kwargs)
351351

352-
except Exception as e:
353-
logger.error(
354-
f"Failed to run server: {e}",
352+
except Exception:
353+
logger.exception(
354+
"Failed to run server",
355355
extra={
356356
"file": str(file),
357-
"error": str(e),
358357
},
359358
)
360359
sys.exit(1)
@@ -464,8 +463,8 @@ def install(
464463
if dotenv:
465464
try:
466465
env_dict |= {k: v for k, v in dotenv.dotenv_values(env_file).items() if v is not None}
467-
except Exception as e:
468-
logger.error(f"Failed to load .env file: {e}")
466+
except (OSError, ValueError):
467+
logger.exception("Failed to load .env file")
469468
sys.exit(1)
470469
else:
471470
logger.error("python-dotenv is not installed. Cannot load .env file.")

src/mcp/client/auth.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -494,8 +494,8 @@ async def _handle_refresh_response(self, response: httpx.Response) -> bool:
494494
await self.context.storage.set_tokens(token_response)
495495

496496
return True
497-
except ValidationError as e:
498-
logger.error(f"Invalid refresh response: {e}")
497+
except ValidationError:
498+
logger.exception("Invalid refresh response")
499499
self.context.clear_tokens()
500500
return False
501501

@@ -570,7 +570,7 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.
570570
token_response = yield token_request
571571
await self._handle_token_response(token_response)
572572
except Exception as e:
573-
logger.error(f"OAuth flow error: {e}")
573+
logger.exception("OAuth flow error")
574574
raise
575575

576576
# Retry with new tokens

src/mcp/client/sse.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ async def sse_reader(
9797
)
9898
logger.debug(f"Received server message: {message}")
9999
except Exception as exc:
100-
logger.error(f"Error parsing server message: {exc}")
100+
logger.exception("Error parsing server message")
101101
await read_stream_writer.send(exc)
102102
continue
103103

@@ -106,7 +106,7 @@ async def sse_reader(
106106
case _:
107107
logger.warning(f"Unknown SSE event: {sse.event}")
108108
except Exception as exc:
109-
logger.error(f"Error in sse_reader: {exc}")
109+
logger.exception("Error in sse_reader")
110110
await read_stream_writer.send(exc)
111111
finally:
112112
await read_stream_writer.aclose()
@@ -126,8 +126,8 @@ async def post_writer(endpoint_url: str):
126126
)
127127
response.raise_for_status()
128128
logger.debug(f"Client message sent successfully: {response.status_code}")
129-
except Exception as exc:
130-
logger.error(f"Error in post_writer: {exc}")
129+
except Exception:
130+
logger.exception("Error in post_writer")
131131
finally:
132132
await write_stream.aclose()
133133

0 commit comments

Comments
 (0)