Skip to content

Commit 33745c8

Browse files
committed
docs: modernize CLAUDE.md development guidelines
Remove outdated and generic guidance, add project-specific context that prevents real review churn. Removed: - Error Resolution section (generic 'add None checks / test thoroughly') - Ruff line-wrapping advice (ruff format handles it) and wrong '88 chars' - Stale pre-commit guidance ('update config rev' - ruff is repo: local) - Reported-by / Github-Issue commit trailers (unused in practice) - Duplicate co-authored-by note and generic PR prose - Generic Code Quality bullets ('focused and small', 'follow patterns') Added: - Branching Model: main is the V2 rework, no @deprecated shims, README.md is frozen (edit README.v2.md), [v1.x] backport prefix - Dependency floor policy: don't raise floors for CVEs alone (refs Kludex/uvicorn#2643, #1552) - __all__ in src/mcp/__init__.py defines the public API surface - Avoid new pragma/type:ignore/noqa; assert isinstance in tests; audit grep - filterwarnings=['error'] context for the no-silence-warnings rule - --frozen on all uv commands; --python <ver> for cross-version testing - Tests: in-memory > threads > subprocesses; Client(server) for E2E Restructured numbered-list-in-list into flat H2 sections with a dedicated Coverage subsection.
1 parent cf4e435 commit 33745c8

File tree

1 file changed

+100
-142
lines changed

1 file changed

+100
-142
lines changed

CLAUDE.md

Lines changed: 100 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,106 @@
11
# Development Guidelines
22

3-
This document contains critical information about working with this codebase. Follow these guidelines precisely.
4-
5-
## Core Development Rules
6-
7-
1. Package Management
8-
- ONLY use uv, NEVER pip
9-
- Installation: `uv add <package>`
10-
- Running tools: `uv run <tool>`
11-
- Upgrading: `uv lock --upgrade-package <package>`
12-
- FORBIDDEN: `uv pip install`, `@latest` syntax
13-
14-
2. Code Quality
15-
- Type hints required for all code
16-
- Public APIs must have docstrings
17-
- Functions must be focused and small
18-
- Follow existing patterns exactly
19-
- Line length: 120 chars maximum
20-
- FORBIDDEN: imports inside functions. THEY SHOULD BE AT THE TOP OF THE FILE.
21-
22-
3. Testing Requirements
23-
- Framework: `uv run --frozen pytest`
24-
- Async testing: use anyio, not asyncio
25-
- Do not use `Test` prefixed classes, use functions
26-
- Coverage: test edge cases and errors
27-
- New features require tests
28-
- Bug fixes require regression tests
29-
- IMPORTANT: The `tests/client/test_client.py` is the most well designed test file. Follow its patterns.
30-
- IMPORTANT: Be minimal, and focus on E2E tests: Use the `mcp.client.Client` whenever possible.
31-
- Coverage: CI requires 100% (`fail_under = 100`, `branch = true`).
32-
- Full check: `./scripts/test` (~23s). Runs coverage + `strict-no-cover` on the
33-
default Python. Not identical to CI: CI also runs 3.10–3.14 × {ubuntu, windows},
34-
and some branch-coverage quirks only surface on specific matrix entries.
35-
- Targeted check while iterating (~4s, deterministic):
36-
37-
```bash
38-
uv run --frozen coverage erase
39-
uv run --frozen coverage run -m pytest tests/path/test_foo.py
40-
uv run --frozen coverage combine
41-
uv run --frozen coverage report --include='src/mcp/path/foo.py' --fail-under=0
42-
UV_FROZEN=1 uv run --frozen strict-no-cover
43-
```
44-
45-
Partial runs can't hit 100% (coverage tracks `tests/` too), so `--fail-under=0`
46-
and `--include` scope the report. `strict-no-cover` has no false positives on
47-
partial runs — if your new test executes a line marked `# pragma: no cover`,
48-
even a single-file run catches it.
49-
- Coverage pragmas:
50-
- `# pragma: no cover` — line is never executed. CI's `strict-no-cover` fails if
51-
it IS executed. When your test starts covering such a line, remove the pragma.
52-
- `# pragma: lax no cover` — excluded from coverage but not checked by
53-
`strict-no-cover`. Use for lines covered on some platforms/versions but not
54-
others.
55-
- `# pragma: no branch` — excludes branch arcs only. coverage.py misreports the
56-
`->exit` arc for nested `async with` on Python 3.11+ (worse on 3.14/Windows).
57-
- Avoid `anyio.sleep()` with a fixed duration to wait for async operations. Instead:
58-
- Use `anyio.Event`set it in the callback/handler, `await event.wait()` in the test
59-
- For stream messages, use `await stream.receive()` instead of `sleep()` + `receive_nowait()`
60-
- Exception: `sleep()` is appropriate when testing time-based features (e.g., timeouts)
61-
- Wrap indefinite waits (`event.wait()`, `stream.receive()`) in `anyio.fail_after(5)` to prevent hangs
62-
63-
Test files mirror the source tree: `src/mcp/client/streamable_http.py``tests/client/test_streamable_http.py`
64-
Add tests to the existing file for that module.
65-
66-
- For commits fixing bugs or adding features based on user reports add:
3+
## Branching Model
4+
5+
<!-- TODO: drop this section once v2 ships and main becomes the stable line -->
6+
7+
- `main` is currently the V2 rework. Breaking changes are expected here — when removing or
8+
replacing an API, delete it outright and document the change in
9+
`docs/migration.md`. Do not add `@deprecated` shims or backward-compat layers
10+
on `main`.
11+
- `v1.x` is the release branch for the current stable line. Backport PRs target
12+
this branch and use a `[v1.x]` title prefix.
13+
- `README.md` is frozen at v1 (a pre-commit hook rejects edits). Edit
14+
`README.v2.md` instead.
15+
16+
## Package Management
17+
18+
- ONLY use uv, NEVER pip
19+
- Installation: `uv add <package>`
20+
- Running tools: `uv run --frozen <tool>`. Always pass `--frozen` so uv doesn't
21+
rewrite `uv.lock` as a side effect.
22+
- Cross-version testing: `uv run --frozen --python 3.10 pytest ...` to run
23+
against a specific interpreter (CI covers 3.10–3.14).
24+
- Upgrading: `uv lock --upgrade-package <package>`
25+
- FORBIDDEN: `uv pip install`, `@latest` syntax
26+
- Don't raise dependency floors for CVEs alone. The `>=` constraint already
27+
lets users upgrade. Only raise a floor when the SDK needs functionality from
28+
the newer version, and don't add SDK code to work around a dependency's
29+
vulnerability. See Kludex/uvicorn#2643 and python-sdk #1552 for reasoning.
30+
31+
## Code Quality
32+
33+
- Type hints required for all code
34+
- Public APIs must have docstrings
35+
- `src/mcp/__init__.py` defines the public API surface via `__all__`. Adding a
36+
symbol there is a deliberate API decision, not a convenience re-export.
37+
- IMPORTANT: All imports go at the top of the file — inline imports hide
38+
dependencies and obscure circular-import bugs. Only exception: when a
39+
top-level import genuinely can't work (lazy-loading optional deps, or
40+
tests that re-import a module).
41+
42+
## Testing
43+
44+
- Framework: `uv run --frozen pytest`
45+
- Async testing: use anyio, not asyncio
46+
- Do not use `Test` prefixed classes, use functions
47+
- IMPORTANT: Tests should be fast and deterministic. Prefer in-memory async execution;
48+
reach for threads only when necessary, and subprocesses only as a last resort.
49+
- For end-to-end behavior, an in-memory `Client(server)` is usually the
50+
cleanest approach (see `tests/client/test_client.py` for the canonical
51+
pattern). For narrower changes, testing the function directly is fine. Use
52+
judgment.
53+
- Test files mirror the source tree: `src/mcp/client/streamable_http.py`
54+
`tests/client/test_streamable_http.py`. Add tests to the existing file for that module.
55+
- Avoid `anyio.sleep()` with a fixed duration to wait for async operations. Instead:
56+
- Use `anyio.Event` — set it in the callback/handler, `await event.wait()` in the test
57+
- For stream messages, use `await stream.receive()` instead of `sleep()` + `receive_nowait()`
58+
- Exception: `sleep()` is appropriate when testing time-based features (e.g., timeouts)
59+
- Wrap indefinite waits (`event.wait()`, `stream.receive()`) in `anyio.fail_after(5)` to prevent hangs
60+
- Pytest is configured with `filterwarnings = ["error"]`, so warnings fail
61+
tests. Don't silence them with `filterwarnings` or `warnings.catch_warnings()`;
62+
fix the underlying cause instead.
63+
64+
### Coverage
65+
66+
CI requires 100% (`fail_under = 100`, `branch = true`).
67+
68+
- Full check: `./scripts/test` (~23s). Runs coverage + `strict-no-cover` on the
69+
default Python. Not identical to CI: CI also runs 3.10–3.14 × {ubuntu, windows},
70+
and some branch-coverage quirks only surface on specific matrix entries.
71+
- Targeted check while iterating (~4s, deterministic):
6772

6873
```bash
69-
git commit --trailer "Reported-by:<name>"
74+
uv run --frozen coverage erase
75+
uv run --frozen coverage run -m pytest tests/path/test_foo.py
76+
uv run --frozen coverage combine
77+
uv run --frozen coverage report --include='src/mcp/path/foo.py' --fail-under=0
78+
UV_FROZEN=1 uv run --frozen strict-no-cover
7079
```
7180

72-
Where `<name>` is the name of the user.
81+
Partial runs can't hit 100% (coverage tracks `tests/` too), so `--fail-under=0`
82+
and `--include` scope the report. `strict-no-cover` has no false positives on
83+
partial runs — if your new test executes a line marked `# pragma: no cover`,
84+
even a single-file run catches it.
7385

74-
- For commits related to a Github issue, add
86+
Avoid adding new `# pragma: no cover`, `# type: ignore`, or `# noqa` comments.
87+
In tests, use `assert isinstance(x, T)` to narrow types instead of
88+
`# type: ignore`. In library code (`src/`), a `# pragma: no cover` needs very
89+
good reasoning — it usually means a test is missing. Audit before pushing:
7590

76-
```bash
77-
git commit --trailer "Github-Issue:#<number>"
78-
```
79-
80-
- NEVER ever mention a `co-authored-by` or similar aspects. In particular, never
81-
mention the tool used to create the commit message or PR.
91+
```bash
92+
git diff origin/main... | grep -E '^\+.*(pragma|type: ignore|noqa)'
93+
```
8294

83-
## Pull Requests
95+
What the existing pragmas mean:
8496

85-
- Create a detailed message of what changed. Focus on the high level description of
86-
the problem it tries to solve, and how it is solved. Don't go into the specifics of the
87-
code unless it adds clarity.
88-
89-
- NEVER ever mention a `co-authored-by` or similar aspects. In particular, never
90-
mention the tool used to create the commit message or PR.
97+
- `# pragma: no cover` — line is never executed. CI's `strict-no-cover` fails if
98+
it IS executed. When your test starts covering such a line, remove the pragma.
99+
- `# pragma: lax no cover` — excluded from coverage but not checked by
100+
`strict-no-cover`. Use for lines covered on some platforms/versions but not
101+
others.
102+
- `# pragma: no branch` — excludes branch arcs only. coverage.py misreports the
103+
`->exit` arc for nested `async with` on Python 3.11+ (worse on 3.14/Windows).
91104

92105
## Breaking Changes
93106

@@ -100,68 +113,13 @@ When making breaking changes, document them in `docs/migration.md`. Include:
100113
Search for related sections in the migration guide and group related changes together
101114
rather than adding new standalone sections.
102115

103-
## Python Tools
104-
105-
## Code Formatting
106-
107-
1. Ruff
108-
- Format: `uv run --frozen ruff format .`
109-
- Check: `uv run --frozen ruff check .`
110-
- Fix: `uv run --frozen ruff check . --fix`
111-
- Critical issues:
112-
- Line length (88 chars)
113-
- Import sorting (I001)
114-
- Unused imports
115-
- Line wrapping:
116-
- Strings: use parentheses
117-
- Function calls: multi-line with proper indent
118-
- Imports: try to use a single line
119-
120-
2. Type Checking
121-
- Tool: `uv run --frozen pyright`
122-
- Requirements:
123-
- Type narrowing for strings
124-
- Version warnings can be ignored if checks pass
125-
126-
3. Pre-commit
127-
- Config: `.pre-commit-config.yaml`
128-
- Runs: on git commit
129-
- Tools: Prettier (YAML/JSON), Ruff (Python)
130-
- Ruff updates:
131-
- Check PyPI versions
132-
- Update config rev
133-
- Commit config first
134-
135-
## Error Resolution
136-
137-
1. CI Failures
138-
- Fix order:
139-
1. Formatting
140-
2. Type errors
141-
3. Linting
142-
- Type errors:
143-
- Get full line context
144-
- Check Optional types
145-
- Add type narrowing
146-
- Verify function signatures
147-
148-
2. Common Issues
149-
- Line length:
150-
- Break strings with parentheses
151-
- Multi-line function calls
152-
- Split imports
153-
- Types:
154-
- Add None checks
155-
- Narrow string types
156-
- Match existing patterns
157-
158-
3. Best Practices
159-
- Check git status before commits
160-
- Run formatters before type checks
161-
- Keep changes minimal
162-
- Follow existing patterns
163-
- Document public APIs
164-
- Test thoroughly
116+
## Formatting & Type Checking
117+
118+
- Format: `uv run --frozen ruff format .`
119+
- Lint: `uv run --frozen ruff check . --fix`
120+
- Type check: `uv run --frozen pyright`
121+
- Pre-commit runs all of the above plus markdownlint, a `uv.lock` consistency
122+
check, and README checks — see `.pre-commit-config.yaml`
165123

166124
## Exception Handling
167125

0 commit comments

Comments
 (0)